/* The dashboard, and nothing else. Loaded by dashboard.html and coverage.html
   and by no other screen.

   Split out of app.css rather than appended to it for two reasons, and the
   second is the load-bearing one. The entry form has no use for three hundred
   lines of panel and calendar rules. And P1-03's criterion 9 -- the screen that
   counts people shows nothing about money, enforced by a test that greps every
   file the entry screen serves -- has to keep meaning what it says now that
   there is a screen whose whole subject is the money. A separate file is how
   both stay true.

   Every colour here is a token from app.css. There is no palette in this file
   and no palette in dashboard.js; see the token block at the top of app.css for
   what the three groups mean. */

/* -- layout ----------------------------------------------------------------
   A dashboard is scanned, not read. Everything below is arranged so that the
   thing needing attention is found by shape and colour before any digit is
   parsed: a chip, an arrow, a hole in a grid.

   Wider than the entry form, and only where there is room. `main.wide` opts a
   page out of the 34rem reading measure the card screens want -- a chart at
   34rem on a 27" monitor is a chart nobody can read a trend off. Below 34rem
   nothing here differs from the rest of the site: one column, no fixed widths,
   and every chart in a box that scrolls on its own before the page does. */

@media (min-width: 34rem) {
  main.wide { max-width: 64rem; }
}

.dash h1 { margin-bottom: 0.25rem; }
.dash .lede {
  margin: 0 0 1rem;
  color: var(--quiet);
  font-size: 0.9rem;
}

/* -- 1. the headline row -- */

.tiles {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(100%, 13rem), 1fr));
  gap: 0.6rem;
  margin: 0 0 1.25rem;
  padding: 0;
  list-style: none;
}

.tile {
  padding: 0.7rem 0.8rem;
  border: 1px solid var(--line);
  border-radius: 0.5rem;
  background: var(--field);
  min-width: 0;
}
.tile .name {
  display: block;
  font-size: 0.75rem;
  text-transform: uppercase;
  letter-spacing: 0.06em;
  color: var(--quiet);
}
.tile .figure {
  display: flex;
  align-items: baseline;
  flex-wrap: wrap;
  gap: 0.4rem;
  margin: 0.25rem 0 0.15rem;
}
.tile .big {
  font-size: 1.7rem;
  font-weight: 700;
  line-height: 1.1;
  /* Digits line up column to column and tile to tile, so four figures read as
     one row rather than four ragged ones. */
  font-variant-numeric: tabular-nums;
}
.tile .of { font-size: 0.95rem; color: var(--quiet); font-variant-numeric: tabular-nums; }
.tile .note { display: block; margin: 0.15rem 0 0; font-size: 0.78rem; color: var(--quiet); }
.tile .none { font-size: 1.7rem; font-weight: 700; color: var(--quiet); }

/* The as-of line is a fixed part of every tile, not a footnote: a number whose
   date is missing is a number nobody can date. */
.asof {
  display: block;
  margin-top: 0.4rem;
  padding-top: 0.35rem;
  border-top: 1px solid var(--line);
  font-size: 0.75rem;
  color: var(--quiet);
  font-variant-numeric: tabular-nums;
}
.asof.stale { color: var(--flag); font-weight: 600; }

/* State, never identity. A chip only ever appears where there is a threshold
   somebody would act on. */
.chip {
  display: inline-block;
  padding: 0.1rem 0.45rem;
  border-radius: 0.7rem;
  font-size: 0.78rem;
  font-weight: 600;
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}
.chip.good { background: var(--good-bg); color: var(--good); }
.chip.warn { background: var(--warn-bg); color: var(--warn); }
.chip.bad  { background: var(--flag-bg); color: var(--flag); }
/* There is no `.chip.unknown`. `_chip()` has a fourth state for "there is no
   threshold to compare against" -- a week with a contribution and no budget on
   record -- and the tile answers it by printing the reason in words instead of
   drawing a grey chip that says nothing. A state with no threshold is not a
   state to colour. */

/* The arrow carries direction in its shape, and takes the ink colour. Falling
   attendance is not a red state -- it might be August -- and the shape is also
   what keeps it readable to somebody who cannot tell two colours apart. */
.arrow {
  font-size: 1.1rem;
  line-height: 1;
  color: var(--ink);
  font-variant-numeric: tabular-nums;
}
.arrow .delta { font-size: 0.85rem; color: var(--quiet); }

/* -- panels -- */

.panel {
  margin: 0 0 1.25rem;
  padding: 0.75rem;
  border: 1px solid var(--line);
  border-radius: 0.5rem;
  background: var(--field);
  min-width: 0;
}
.panel > header {
  display: flex;
  align-items: baseline;
  flex-wrap: wrap;
  gap: 0.5rem 0.75rem;
  margin-bottom: 0.5rem;
}
.panel > header h2 { margin: 0; flex: 1 1 auto; }
.panel .says { margin: 0 0 0.6rem; font-size: 0.82rem; color: var(--quiet); }
.panel .says.after { margin: 0.6rem 0 0; }

/* The window control. It governs four panels, so it sits above all of them
   and names them rather than hiding in one panel's header. */
.toolbar {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 0.4rem 0.75rem;
  margin: 0 0 1rem;
}
.toolbar-label {
  font-size: 0.75rem;
  text-transform: uppercase;
  letter-spacing: 0.06em;
  color: var(--quiet);
}
.toolbar-note {
  font-size: 0.78rem;
  color: var(--quiet);
  display: flex;
  flex-wrap: wrap;
  gap: 0.15rem 0.5rem;
}
/* The gesture, said once. It is the only thing on the page that is a hint
   rather than a fact, so it is the quietest thing on it -- and every one of
   the gestures it names has a button beside it for anybody not making them. */
.toolbar-hint { opacity: 0.75; }

/* Window buttons. Small targets are fine on a pointer and not on a thumb, so
   they keep the site's minimum height. */
.windows { display: flex; gap: 0.3rem; flex-wrap: wrap; }
.windows button {
  min-height: 2.25rem;
  padding: 0.2rem 0.7rem;
  font-size: 0.85rem;
  color: var(--quiet);
  cursor: pointer;
}
.windows button[aria-pressed="true"] {
  background: var(--ink);
  border-color: var(--ink);
  color: var(--paper);
  font-weight: 600;
}
/* The step and reset controls. Same targets as the spans beside them, because
   they are pressed as often, but never latched: they move the view, they do
   not describe it. */
.windows.steps button { padding: 0.2rem 0.6rem; }
.windows.steps button[data-role="latest"] { padding: 0.2rem 0.7rem; }
.windows.steps button[disabled] { opacity: 0.4; cursor: default; }

/* A chart is the one thing on this page that is genuinely wide. It scrolls
   inside its own box; the page body never does. */
.chartbox {
  max-width: 100%;
  overflow-x: auto;
  overflow-y: hidden;
  -webkit-overflow-scrolling: touch;
}
.chart {
  /* min-width, not width: below 30rem the box scrolls and the chart keeps a
     readable time axis instead of squeezing 52 weeks into a phone. */
  min-width: 30rem;
  height: 17rem;
}
/* Taller than it was by the height of one zoom slider. The slider is drawn
   inside the chart's own box, so a chart that kept its old height would have
   paid for the control out of the plot. */
.chart.tall { height: 24rem; }

/* Before the script runs, and if it never does. */
.chart:empty::after {
  content: attr(data-empty);
  display: flex;
  align-items: center;
  justify-content: center;
  height: 100%;
  color: var(--quiet);
  font-size: 0.85rem;
}

/* Direct labels beat a legend at three series. This is the key for the two
   charts that carry more. */
.keys { display: flex; flex-wrap: wrap; gap: 0.4rem 1rem; margin: 0.5rem 0 0; padding: 0; list-style: none; }
.keys li { display: flex; align-items: center; gap: 0.35rem; font-size: 0.8rem; color: var(--quiet); }
.keys .swatch { width: 0.85rem; height: 0.2rem; border-radius: 0.1rem; background: var(--quiet); }
.keys .h1 { background: var(--hue-1); }
.keys .h2 { background: var(--hue-2); }
.keys .h3 { background: var(--hue-3); }
/* The coverage ramp, as squares rather than rules: these are cells, not lines.
   `color-mix` is how the key stays in step with the heatmap, which builds the
   same four steps by alpha off the same token. */
.keys .swatch[class*="cov"] { width: 0.7rem; height: 0.7rem; border-radius: 0.1rem; }
.keys .cov1 { background: color-mix(in srgb, var(--hue-1) 20%, transparent); }
.keys .cov2 { background: color-mix(in srgb, var(--hue-1) 40%, transparent); }
.keys .cov3 { background: color-mix(in srgb, var(--hue-1) 60%, transparent); }
.keys .cov4 { background: var(--hue-1); }
.keys .covnone { background: var(--paper); border: 1px solid var(--line); }

/* -- 7. the reconciliation strip --
   Small and quiet, and always visible. If it ever goes red every number above
   it is suspect, so it is the one panel that states its own consequence. */

.strip {
  display: flex;
  align-items: flex-start;
  gap: 0.6rem;
  margin: 0 0 1.25rem;
  padding: 0.6rem 0.75rem;
  border: 1px solid var(--line);
  border-left: 4px solid var(--quiet);
  border-radius: 0.4rem;
  background: var(--field);
  font-size: 0.85rem;
}
.strip.good { border-left-color: var(--good); }
.strip.warn { border-left-color: var(--warn); }
.strip.bad  { border-left-color: var(--flag); background: var(--flag-bg); }
.strip .light {
  flex: 0 0 auto;
  width: 0.7rem; height: 0.7rem;
  margin-top: 0.3rem;
  border-radius: 50%;
  background: var(--quiet);
}
.strip.good .light { background: var(--good); }
.strip.warn .light { background: var(--warn); }
.strip.bad  .light { background: var(--flag); }
.strip .said { min-width: 0; }
.strip strong { display: block; font-weight: 600; }
.strip .fine { color: var(--quiet); font-variant-numeric: tabular-nums; }
.strip.bad .fine { color: var(--flag); }
.strip details { margin-top: 0.35rem; }
.strip summary { cursor: pointer; color: var(--quiet); min-height: 2rem; }

/* -- 6. the coverage calendar --
   A grid of weeks. A week with nothing recorded is drawn as a hole -- the page
   background inside a dashed outline -- rather than as a colour, because a
   colour on a scale makes an absence look like a low reading. */

.calendar { margin: 0.4rem 0 0; }
.calyear { margin: 0 0 0.8rem; }
.calyear h3 {
  margin: 0 0 0.3rem;
  font-size: 0.85rem;
  font-variant-numeric: tabular-nums;
  color: var(--quiet);
}
.calweeks {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(2.6rem, 1fr));
  gap: 2px;
  margin: 0; padding: 0;
  list-style: none;
}
.calweek {
  display: block;
  padding: 0.2rem 0.15rem 0.25rem;
  border: 1px solid var(--line);
  border-radius: 0.25rem;
  background: var(--sunk);
  color: var(--ink);
  text-decoration: none;
  text-align: center;
  min-width: 0;
}
.calweek.gap { background: var(--paper); border-style: dashed; border-color: var(--quiet); }
.calweek.complete { background: var(--field); border-color: var(--hue-1); }
.calweek .when { display: block; font-size: 0.62rem; color: var(--quiet); font-variant-numeric: tabular-nums; }
.calweek .pips { display: flex; justify-content: center; gap: 1px; margin-top: 0.15rem; }
.calweek .pip {
  width: 0.42rem; height: 0.42rem;
  border-radius: 1px;
  background: transparent;
  border: 1px solid var(--line);
}
.calweek .pip.on { background: var(--hue-1); border-color: var(--hue-1); }
/* Where it came from, in the pip itself: a bulletin is solid, anything typed
   or transcribed off a card is hollow. A parser that turns out to be wrong
   takes the solid ones with it and not the others.

   A scan is a bulletin -- the same block, read off paper instead of out of the
   mailbox -- so it is solid too, and drawn in the third hue rather than the
   first. Painting it hollow said "somebody typed this off a card", which is
   the one thing it is not, and made a fully ingested year of paper read as a
   year nobody had scanned. The distinction that matters is a re-read one: if
   the OCR turns out to have been wrong it takes the hue-3 pips and not the
   hue-1 ones. */
.calweek .pip.card { background: transparent; border-color: var(--hue-2); box-shadow: inset 0 0 0 1px var(--hue-2); }
.calweek .pip.scan { background: var(--hue-3); border-color: var(--hue-3); }
.calweek .src { display: block; margin-top: 0.1rem; font-size: 0.55rem; letter-spacing: 0.04em; color: var(--quiet); }

.calkey { display: flex; flex-wrap: wrap; gap: 0.4rem 1rem; margin: 0.5rem 0 0; padding: 0; list-style: none; font-size: 0.78rem; color: var(--quiet); }
.calkey li { display: flex; align-items: center; gap: 0.35rem; }

/* -- 8. what needs a person -- */

.todo { margin: 0; padding: 0; list-style: none; }
.todo li {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: 0.4rem;
  padding: 0.35rem 0;
  font-size: 0.88rem;
}
.todo li + li { border-top: 1px solid var(--line); }
.todo .num { font-variant-numeric: tabular-nums; font-weight: 700; }
.todo a { color: var(--hue-1); }
.todo .clear { color: var(--quiet); font-size: 0.85rem; }

/* -- tables (the coverage page's per-slot roll-up) -- */

.tablebox { max-width: 100%; overflow-x: auto; }
.grid {
  border-collapse: collapse;
  font-size: 0.85rem;
  font-variant-numeric: tabular-nums;
}
.grid th, .grid td { padding: 0.3rem 0.6rem; text-align: right; border-bottom: 1px solid var(--line); white-space: nowrap; }
.grid th:first-child, .grid td:first-child { text-align: left; }
.grid thead th { color: var(--quiet); font-weight: 600; }

/* The charts render as SVG, not canvas, for one reason: SVG text inherits the
   page's font and this rule, and canvas text can inherit neither. It is what
   lines the digits up down an axis and across a tooltip. */
.chart svg text { font-variant-numeric: tabular-nums; }
.chart svg { max-width: none; }
