/* Shared by every page.

   The ground is a warm forest green and the ink is bone. This is a departure
   from Tokens.swift, which specifies cool uncoated paper and says warmth "reads
   as nostalgia" — the web now deliberately reads that way and the iOS app does
   not. What survives the change is the rule underneath it: colour still means
   availability and nothing else. The powder blue appears when a work is
   audible, in the same place the app spends its exhibition red, and nowhere
   else. Green and bone are the ground, not colour. It is the one cool thing on
   a warm page, which is most of why it reads as a signal at all.

   One typeface. Fraunces is a Windsor revival — the soft splayed serif of
   1960s and 70s paperbacks and title cards — and it is asked to do everything
   here, from 11px tracked labels to the display line, using its SOFT and WONK
   axes rather than a second family. Plex Mono survives for tabular data only:
   coordinates and radii, where figures must line up.

   Self-hosted rather than linked, so the pages look identical on every
   operating system and no visitor's request reaches a font CDN. */

@font-face {
  font-family: "Fraunces";
  src: url("/fonts/fraunces.woff2") format("woff2-variations");
  font-weight: 100 900; font-style: normal; font-display: swap;
}
@font-face {
  font-family: "Fraunces";
  src: url("/fonts/fraunces-italic.woff2") format("woff2-variations");
  font-weight: 100 900; font-style: italic; font-display: swap;
}
@font-face {
  font-family: "Plex Mono";
  src: url("/fonts/plexmono-400.woff2") format("woff2");
  font-weight: 400; font-style: normal; font-display: swap;
}
@font-face {
  font-family: "Plex Mono";
  src: url("/fonts/plexmono-500.woff2") format("woff2");
  font-weight: 500; font-style: normal; font-display: swap;
}

:root {
  color-scheme: dark;

  /* D66. Greens in steps, and bone paper for what you read.

     The ground was one green with a second only 1.18:1 away, so nothing stood
     apart from anything else and every subject ran into the next. Now: a deeper
     ground, two clear steps up for bands and tiles, and bone paper for the
     things read closely — a work, a request, a form. `.paper` re-points the
     ink tokens, so anything set in ink reads dark on bone without knowing
     where it sits. */
  --field:    #26361F;  /* the ground: page, map frames */
  --raised:   #34482B;  /* one step up: inputs, bands, the current segment */
  --raised-2: #43593A;  /* two up: stat tiles */
  --bone:     #FFFFFF;  /* paper: what you read closely — D70, pure white */
  --ink:     #F2ECDC;   /* bone ink on green */
  --ink-muted: #D3D8C0;
  --ink-faint: #B5C0A2; /* 6.8:1 on the ground — the old one fell below 4.5 */
  --rule:    #6B8660;

  /* The one colour. A work you can hear from where you are standing. Never
     emphasis, never decoration, never an ordinary control. */
  --signal:  #A6CADF;
  /* Errors and anything that cannot be undone. It used to borrow the signal,
     which made "something went wrong" look like "a work is in reach". */
  --danger:  #EE8C74;

  --face: "Fraunces", ui-serif, Georgia, serif;
  --mono: "Plex Mono", ui-monospace, SFMono-Regular, Menlo, monospace;

  --track: 0.2em;

  /* Fraunces' optical size, softness and wonk, set once per role. WONK swaps in
     the splayed, kicked-leg forms; it earns its keep large and gets in the way
     small, so only the display line uses it. */
  --vf-display: "opsz" 144, "SOFT" 70, "WONK" 1;
  --vf-text:    "opsz" 18, "SOFT" 40, "WONK" 0;
  --vf-label:   "opsz" 9,  "SOFT" 20, "WONK" 0;
}

* { box-sizing: border-box; }
/* No image is ever wider than what holds it. Sized images (covers, plates,
   previews) set their own dimensions; this only catches the ones that don't. */
img { max-width: 100%; }
/* `hidden` has to win. The browser's own rule for it is the weakest in the
   cascade, so any display set here — `label { display: block }` below — quietly
   showed elements the pages had hidden. */
[hidden] { display: none !important; }
body {
  margin: 0; background: var(--field); color: var(--ink);
  font-family: var(--face); font-variation-settings: var(--vf-text);
  font-size: 16px; line-height: 1.65;
  -webkit-font-smoothing: antialiased;
}
.wrap { max-width: 900px; margin: 0 auto; padding: 32px 24px 96px; }
.narrow { max-width: 540px; }

/* Every small label on the site. Fraunces at a text optical size, tracked wide
   enough that the caps stop looking like a heading and start looking like a
   caption. */
.eyebrow {
  font-family: var(--face); font-variation-settings: var(--vf-label);
  font-size: 11px; font-weight: 500; letter-spacing: var(--track);
  text-transform: uppercase; color: var(--ink-faint);
}
.eyebrow.live { color: var(--signal); }

/* Optical size follows the actual size it is set at: the display axis thins the
   hairlines out beautifully at 44px and makes them fragile at 20px. */
h1, h2, h3 { font-family: var(--face); margin: 6px 0 0; font-weight: 400; }
h1 { font-variation-settings: var(--vf-display);
     font-size: clamp(32px, 5.5vw, 44px); letter-spacing: -0.02em; line-height: 1.1; }
h2 { font-variation-settings: "opsz" 60, "SOFT" 60, "WONK" 0;
     font-size: 26px; letter-spacing: -0.015em; line-height: 1.25; }
h3 { font-variation-settings: "opsz" 24, "SOFT" 50, "WONK" 0;
     font-size: 20px; letter-spacing: -0.005em; line-height: 1.35; }

/* Reading copy. */
.prose {
  font-variation-settings: var(--vf-text);
  font-size: 17px; line-height: 1.72; color: var(--ink-muted); max-width: 60ch;
}
.prose strong { font-weight: 600; color: var(--ink); }

hr { border: 0; border-top: 0.5px solid var(--rule); margin: 16px 0; }
.row { padding: 24px 0; border-bottom: 0.5px solid var(--rule); }

/* A passage: a heading and its text under a hairline. No numerals — the rule
   and the space do the separating. */
.passage { border-top: 0.5px solid var(--rule); padding: 26px 0 4px; }
.passage:first-of-type { border-top: 0.5px solid var(--ink); }
.passage > h2, .passage > h3 {
  font-variation-settings: var(--vf-label);
  font-size: 11px; font-weight: 500; letter-spacing: var(--track);
  text-transform: uppercase; color: var(--ink); margin: 0 0 10px;
}
.passage p {
  margin: 0 0 12px; font-size: 16.5px; line-height: 1.7;
  color: var(--ink-muted); max-width: 54ch;
}
.passage p:last-child { margin-bottom: 0; }
.passage p strong { font-weight: 600; color: var(--ink); }

/* Buttons are pebbles (D24): softly rounded, lit from above, and they sink a
   pixel when pressed. Only things you press are rounded, so the rounding itself
   says "this does something". The light is kept faint on purpose — a surface
   catching light, not a gradient — and the shadow is one tight line beneath
   rather than a glow. Sentence case: a button is an instruction to a person,
   not a caption on a plate. */
button {
  font-family: var(--face); font-variation-settings: "opsz" 14, "SOFT" 50, "WONK" 0;
  font-size: 15px; font-weight: 550; line-height: 1.2;
  letter-spacing: 0; text-transform: none;
  padding: 12px 20px; cursor: pointer; color: var(--ink);
  border: 0; border-radius: 0; /* D69. Square, like every plate here. */
  background: linear-gradient(180deg, #54703F 0%, #4B6538 100%);
  box-shadow: inset 0 1px 0 rgba(255,255,255,.10), 0 1px 2px rgba(0,0,0,.28);
  transition: background 140ms ease, box-shadow 140ms ease, transform 100ms ease;
}
button:hover { background: linear-gradient(180deg, #5A7644 0%, #506B3C 100%); }
button:active { transform: translateY(1px);
                box-shadow: inset 0 1px 2px rgba(0,0,0,.25); }
button:focus-visible { outline: 2px solid var(--signal); outline-offset: 2px; }
button.go {
  color: #1F2E1A;
  background: linear-gradient(180deg, #B4D3E6 0%, #A2C6DC 100%);
  box-shadow: inset 0 1px 0 rgba(255,255,255,.4), 0 1px 2px rgba(0,0,0,.25);
}
button.go:hover { background: linear-gradient(180deg, #BEDAEA 0%, #ABCDE1 100%); }
button.go:active { box-shadow: inset 0 1px 2px rgba(0,0,0,.2); }
button.small { font-size: 13px; padding: 7px 13px; border-radius: 0; }
/* D66. Anything that cannot be undone: withdraw, discard, block. An outline in
   the danger colour, filling once it has been pressed and asks to be sure. */
button.danger, .paper button.danger:not(.go):not(.link):not(.x) {
  color: var(--danger); background: transparent; box-shadow: inset 0 0 0 1px var(--danger);
}
button.danger:hover { background: rgba(238, 140, 116, .10); }
button.danger.armed, .paper button.danger.armed:not(.go):not(.link):not(.x) {
  color: var(--bone); background: var(--danger); box-shadow: none;
}
button:disabled, button:disabled:hover {
  color: var(--ink-faint); background: var(--raised);
  box-shadow: none; transform: none; cursor: not-allowed;
}

input, textarea, select {
  font-family: var(--face); font-variation-settings: var(--vf-text);
  font-size: 16px; padding: 11px 12px; width: 100%;
  background: var(--raised); color: var(--ink);
  border: 0.5px solid var(--rule); border-radius: 0;
}
input:focus, textarea:focus, select:focus { outline: none; border-color: var(--ink); }
input::placeholder, textarea::placeholder { color: var(--ink-faint); }
textarea { min-height: 90px; resize: vertical; }
label { display: block; margin-top: 18px; }
label .eyebrow { margin-bottom: 7px; }

.map { height: 320px; border: 0.5px solid var(--rule); border-radius: 0; margin: 16px 0; }
.actions { display: flex; gap: 12px; flex-wrap: wrap; margin-top: 18px; align-items: center; }
.meta { color: var(--ink-muted); font-size: 14px; }

/* Figures must line up; this is the only thing mono is still here for. */
.data, .code, input[id^="lat-"], input[id^="lon-"], input[id^="rad-"] {
  font-family: var(--mono); font-variant-numeric: tabular-nums; font-size: 14px;
}
.code { letter-spacing: 0.06em; }
.alert { color: var(--danger); font-size: 14px; }
.empty { color: var(--ink-muted); padding: 32px 0; }
.field { display: grid; grid-template-columns: 1fr 1fr 1fr; gap: 12px; margin-top: 12px; }
.tracklist { margin: 12px 0; }
.track {
  display: flex; align-items: center; gap: 12px;
  padding: 10px 0; border-bottom: 0.5px solid var(--rule);
}
.track .num { font-family: var(--mono); font-variant-numeric: tabular-nums;
              color: var(--ink-faint); min-width: 24px; font-size: 13px; }
.track .name { flex: 1; }
.warn {
  border-left: 2px solid var(--signal); padding-left: 16px; margin: 24px 0;
  color: var(--ink-muted); font-size: 15px;
}
.state {
  font-variation-settings: var(--vf-label);
  font-size: 10px; letter-spacing: var(--track); text-transform: uppercase;
}
.state.live { color: var(--signal); }
audio { width: 100%; margin-top: 8px; }
a { color: var(--ink); }

/* The mark: an ear reduced to the only two curves that make one — the rim and
   the ridge inside it — with the listening point at the canal. The inner curve
   stops short and turns back so that it cradles the point rather than running
   past it, which is the relation the whole product describes: a place, with
   something at the middle of it that you have to be there to hear.

   Stroke weight lives here rather than on the element, because the same drawing
   has to hold at 22px in a letterhead and at 230px on the front page, and one
   weight cannot do both. */
.mark .helix, .mark .antihelix {
  fill: none; stroke: var(--ink); stroke-width: 5;
  stroke-linecap: round; stroke-linejoin: round;
}
.mark .point { fill: var(--signal); }

/* The mark and the place on one line with a hairline under it, so every page
   sits under the same letterhead. */
.letterhead {
  display: flex; align-items: baseline; gap: 14px;
  padding-bottom: 14px; border-bottom: 0.5px solid var(--rule);
  margin-bottom: 44px;
}
.letterhead .mark { flex: 0 0 auto; align-self: center; }
.letterhead .wordmark {
  font-variation-settings: var(--vf-label);
  font-size: 12px; font-weight: 500; letter-spacing: var(--track);
  text-transform: uppercase; margin-right: auto;
  text-decoration: none; color: var(--ink);
}

/* D44. Every list of works leads with its cover, the way a music app's does.
   Square like every plate here; a work without one gets the ground, not a
   broken-image box. */
.titled { display: flex; gap: 16px; align-items: center; }
.titled > div { min-width: 0; }
.cover-thumb {
  width: 56px; height: 56px; flex: 0 0 56px; object-fit: cover; display: block;
  border: 0.5px solid var(--rule); background: var(--raised);
}

/* ————— one-screen pages —————

   The front page and "What we ask" never scroll as a whole. The letterhead
   stays put and the words scroll inside their own region beneath it. That region
   has no border, no ground and no scrollbar: its top and bottom edges fade, so
   the boundary is felt rather than drawn, and the fade is what says there is
   more. */
body.fixed { overflow: hidden; }
.screen {
  height: 100vh; height: 100dvh;
  display: grid; grid-template-rows: auto minmax(0, 1fr);
}
.wrap.screen { padding-bottom: 0; }
.screen > .letterhead { margin-bottom: 0; }

.scroll {
  min-height: 0; overflow-y: auto; overscroll-behavior: contain;
  display: flex; flex-direction: column;
  padding: 32px 0 64px;
  -webkit-mask-image: linear-gradient(to bottom, transparent 0, #000 28px, #000 calc(100% - 56px), transparent 100%);
          mask-image: linear-gradient(to bottom, transparent 0, #000 28px, #000 calc(100% - 56px), transparent 100%);
  scrollbar-width: none;
}
.scroll::-webkit-scrollbar { display: none; }
/* Centred when it fits, scrolling from the top when it does not. Auto margins
   rather than flex centring, which would push the start of an overflowing
   column out of reach. */
.scroll.centred > .inner { margin-block: auto; }

/* Two columns on one screen: a picture on the left, the words on the right in
   their invisible scrolling region. The front page and the join page. */
.sheet { max-width: 1320px; margin: 0 auto; padding: 28px 40px 0; }
.split {
  display: grid; min-height: 0;
  grid-template-columns: minmax(0, 1.15fr) minmax(0, 1fr);
  gap: clamp(28px, 5vw, 88px);
}
.art { display: grid; place-items: center; min-height: 0; }
.words { display: flex; flex-direction: column; min-height: 0; }
.words > .scroll { flex: 1 1 auto; }

/* Too narrow for two columns: the picture goes above the words rather than
   beside them. The page still never scrolls. */
@media (max-width: 760px) {
  .sheet { padding: 20px 20px 0; }
  .split { grid-template-columns: minmax(0, 1fr); grid-template-rows: auto minmax(0, 1fr); gap: 8px; }
}

/* The wallpaper: the mark, repeated on a grid and drifting, held in a panel.
   The picture side of the join and "What we ask" pages, with the words to its
   right in the invisible scrolling region.

   The drift moves a layer one tile further than the panel in each direction
   and translates it by exactly one tile, so the loop is seamless — the last
   frame is the first. Transform only, so it runs on the compositor and costs
   nothing to scroll the form beside it. */
.wallpaper {
  position: relative; overflow: hidden; min-height: 0;
  margin: 32px 0 28px;
  background: var(--raised); border: 0.5px solid var(--rule);
}
.wallpaper::before {
  content: ""; position: absolute; inset: -88px 0 0 -88px;
  background: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='88' height='88' viewBox='0 0 88 88'%3E%3Cg transform='translate(22 22) scale(0.6875) translate(-1 0.5)'%3E%3Cg fill='none' stroke='%23F2ECDC' stroke-width='3.4' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M19.5 40.5C15.5 30.5 17.5 17.5 28 13 38.5 8.5 49.5 16 48.5 28.5c-.9 11-5 19-13 24-3.5 2.2-7 1-6.5-2.5'/%3E%3Cpath d='M26.5 42.5c-5-7-3.5-17 4.5-19.5C37.5 21 43 25.5 41 32c-1.4 4.6-5 6.2-8 5'/%3E%3C/g%3E%3Ccircle cx='31.8' cy='36.6' r='3.3' fill='%23A6CADF'/%3E%3C/g%3E%3C/svg%3E") 0 0 / 88px 88px repeat;
  animation: drift 11s linear infinite;
  will-change: transform;
}
@keyframes drift { to { transform: translate(88px, 88px); } }

@media (prefers-reduced-motion: reduce) {
  .wallpaper::before { animation: none; }
}

@media (max-width: 760px) {
  /* A band across the top rather than a column: the form needs the room,
     and the keyboard will take half of what is left. */
  .wallpaper { height: 104px; margin: 18px 0 0; }
  .scroll { padding-top: 22px; }
}

/* Phones. /studio is meant to be used outdoors, standing at the spot, using the
   location button — so this is the width that matters most for it, not least.
   Three coordinate fields side by side clip their own values at 375px, which
   means an artist cannot read the position they just captured. */
@media (max-width: 560px) {
  .wrap { padding: 24px 18px 72px; }
  .field { grid-template-columns: 1fr; gap: 10px; }
  h2 { font-size: 22px; }

  /* Full-width controls are easier to hit and stop buttons wrapping awkwardly. */
  .actions { gap: 10px; }
  .actions button { flex: 1 1 auto; min-height: 44px; }
  .actions input { flex: 1 1 100%; max-width: none !important; }

  .map { height: 260px; }

  /* A row of title, meta and a button has nowhere to go at this width. */
  .track { flex-wrap: wrap; row-gap: 6px; }
  .track .name { flex: 1 1 100%; }
}

/* Artwork plates (D20). Shared: the studio shows an artist their own gallery,
   the review page shows an editor the same images with a way to take one down. */
.plates { display: grid; grid-template-columns: repeat(auto-fill, minmax(96px, 1fr));
          gap: 10px; margin: 12px 0 0; }
.plate { margin: 0; }
.plate img { width: 100%; aspect-ratio: 1; object-fit: cover; display: block;
             border: 1px solid var(--rule); }
.plate figcaption { display: flex; justify-content: space-between; align-items: baseline;
                    gap: 6px; margin-top: 5px; }
.link { background: none; border: 0; padding: 0; cursor: pointer;
        color: var(--ink-faint); font: inherit; text-decoration: underline; }
.link:hover { color: var(--signal); }

/* ————— tabs —————

   The studio and admin each hold several distinct jobs; tabs name them and
   keep one on screen at a time. Set as tracked labels, like every other small
   label here, with a signal-coloured rule under the one you are on. A count
   after a label is work waiting there. */
.tabs {
  display: flex; gap: 24px; flex-wrap: nowrap; align-items: baseline;
  border-bottom: 0.5px solid var(--rule); margin: -30px 0 36px; padding-bottom: 0;
  /* D66. One line, scrolling sideways when there are more tabs than width —
     admin's nine wrapped onto a second row and looked like two menus. */
  overflow-x: auto; scrollbar-width: none;
}
.tabs::-webkit-scrollbar { display: none; }
.tabs a {
  font-variation-settings: var(--vf-label); white-space: nowrap;
  font-size: 12px; font-weight: 500; letter-spacing: 0.14em;
  text-transform: uppercase; color: var(--ink-faint); text-decoration: none;
  padding: 11px 0 12px; border-bottom: 2px solid transparent; margin-bottom: -0.5px;
}
.tabs a:hover { color: var(--ink); }
.tabs a[aria-current="page"] { color: var(--ink); border-bottom-color: var(--signal); }
.tabs .count {
  font-family: var(--mono); letter-spacing: 0; margin-left: 6px;
  color: var(--field); background: var(--signal); border-radius: 9px;
  padding: 1px 6px; font-size: 11px;
}
.tabs .spacer { margin-left: auto; }
@media (max-width: 760px) { .tabs { gap: 18px; } }

/* A back link above a page reached from a list. */
.back {
  display: inline-block; margin-bottom: 18px; text-decoration: none;
  font-variation-settings: var(--vf-label); font-size: 11px; font-weight: 500;
  letter-spacing: var(--track); text-transform: uppercase; color: var(--ink-faint);
}
.back:hover { color: var(--ink); }

/* ————— dashboards (D62, D64) —————

   Shared by the admin and artist dashboards: stat tiles in a hairline grid, a
   range control, charts drawn by /charts.js, tables, and the funnel. */
  .grid { display: grid; gap: 2px; background: var(--field); margin-top: 14px; }
  .grid.four { grid-template-columns: repeat(4, minmax(0, 1fr)); }
  .grid.five { grid-template-columns: repeat(5, minmax(0, 1fr)); }
  .grid.three { grid-template-columns: repeat(3, minmax(0, 1fr)); }
  .tile { background: var(--raised-2); padding: 16px 16px 14px; text-decoration: none;
          color: var(--ink); display: block; min-width: 0; }
  a.tile:hover { background: #4E6844; }
  .tile .value { font-family: var(--face); font-variation-settings: "opsz" 60, "SOFT" 60;
                 font-size: 34px; line-height: 1.1; font-variant-numeric: tabular-nums; }
  .tile .label { font-size: 13px; color: var(--ink-muted); margin-top: 4px; }
  .tile.quiet .value { color: var(--ink-faint); }
  .tile.urgent .value { color: var(--signal); }
  .tile .sub { font-size: 12px; color: var(--ink-faint); margin-top: 2px; }

  .block { margin-top: 44px; }
  .block > h2 { margin: 0; }
  .block-head { display: flex; justify-content: space-between; align-items: baseline;
                gap: 16px; flex-wrap: wrap; }

  /* The range control: one row, above everything it filters. */
  .segmented { display: inline-flex; border: 0.5px solid var(--rule); }
  .segmented button { border-radius: 0; box-shadow: none; background: transparent;
                      font-size: 13px; padding: 7px 12px; color: var(--ink-muted); }
  .segmented button[aria-pressed="true"] { background: var(--raised); color: var(--ink); }
  .segmented button + button { border-left: 0.5px solid var(--rule); }

  .charts { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 28px;
            margin-top: 22px; }
  .chart h3 { font-size: 16px; margin: 0; }
  .chart .total { font-size: 13px; color: var(--ink-muted); }
  .chart svg { display: block; width: 100%; height: auto; margin-top: 8px; overflow: visible; }
  .chart .axis { font-family: var(--mono); font-size: 10px; fill: var(--ink-faint); }
  .chart .grid-line { stroke: var(--rule); stroke-width: 1; }
  .chart .mark { fill: var(--signal); }
  .chart .hit { fill: transparent; cursor: default; }
  .chart .hit:hover + .mark, .chart .bar:hover .mark { fill: #C3DDEC; }
  .chart .line { fill: none; stroke: var(--signal); stroke-width: 2; stroke-linejoin: round; stroke-linecap: round; }
  .chart .wash { fill: var(--signal); opacity: .1; }
  .chart .cross { stroke: var(--ink-faint); stroke-width: 1; }
  .chart details { margin-top: 6px; }
  .chart summary { font-size: 12px; color: var(--ink-faint); cursor: pointer; }
  .tip { position: fixed; pointer-events: none; background: var(--raised);
         border: 0.5px solid var(--rule); padding: 6px 9px; font-size: 13px;
         box-shadow: 0 2px 8px rgba(0,0,0,.3); z-index: 10; }
  .tip strong { display: block; font-size: 16px; font-variant-numeric: tabular-nums; }
  .tip span { color: var(--ink-muted); }

  table.data-table { width: 100%; border-collapse: collapse; margin-top: 12px; font-size: 14px; }
  .data-table th { text-align: left; font-weight: 500; color: var(--ink-faint);
                   font-variation-settings: var(--vf-label); font-size: 10px;
                   letter-spacing: var(--track); text-transform: uppercase;
                   padding: 8px 10px 8px 0; border-bottom: 0.5px solid var(--ink); }
  .data-table td { padding: 9px 10px 9px 0; border-bottom: 0.5px solid var(--rule);
                   vertical-align: top; }
  .data-table .num { text-align: right; font-variant-numeric: tabular-nums; white-space: nowrap; }
  .data-table th.num { text-align: right; }
  .data-table .sub { color: var(--ink-muted); font-size: 13px; }


.funnel-row { display: grid; grid-template-columns: 170px minmax(0, 1fr) 56px 150px;
              gap: 12px; align-items: center; padding: 7px 0; font-size: 14px; }
.funnel-track { height: 14px; }
.funnel-fill { height: 14px; background: var(--signal); border-radius: 0 4px 4px 0; }
.funnel-n { text-align: right; font-variant-numeric: tabular-nums; }
.funnel-share { color: var(--ink-faint); font-size: 12px; }

@media (max-width: 860px) {
  .grid.four, .grid.five { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .grid.three { grid-template-columns: 1fr; }
  .charts { grid-template-columns: 1fr; }
  .funnel-row { grid-template-columns: 110px minmax(0, 1fr) 44px; }
  .funnel-share { grid-column: 2 / -1; margin-top: -6px; }
}

/* ————— paper (D66) —————

   Bone paper for what is read closely. Everything inside re-points the ink
   tokens to dark-on-bone, so text, rules, inputs, stamps and links follow
   without their own rules. Square, like every plate here; set on the ground
   with space around it rather than a border. */
.paper {
  --ink: #26361F; --ink-muted: #4F5E45; --ink-faint: #5A6852; --rule: #D8DCD3;
  --signal: #2F6A8C; --danger: #A5402B;
  --raised: #F4F6F2; --raised-2: #E8ECE4;
  background: var(--bone); color: var(--ink);
  padding: 20px 22px; margin: 14px 0 0;
}
.paper + .paper { margin-top: 10px; }
.paper .mark .helix, .paper .mark .antihelix { stroke: var(--ink); }
.paper button:not(.go):not(.link):not(.x) { color: #F2ECDC; }
.paper button:focus-visible { outline-color: #2F6A8C; }
.paper .cover-thumb { border-color: var(--rule); background: var(--raised-2); }
.paper input:focus, .paper textarea:focus, .paper select:focus { border-color: var(--ink); }
.paper audio { filter: sepia(.15); }
.paper .warn { border-left-color: var(--signal); }
/* A list of rows inside one sheet: rules between, none at the ends. */
.paper .track:last-child, .paper .row:last-child { border-bottom: 0; }
.paper > :first-child { margin-top: 0; }

/* A row that is its own sheet: a request, a work to review, a report. */
.row.paper { border-bottom: 0; padding: 22px 24px; }
.row.paper .map { margin: 14px 0; }

/* A section heading with its sheet under it. */
.sheet-title { margin: 34px 0 0; font-size: 22px; }

/* Status stamps (D66). Distinct at a glance: in progress is an outline,
   waiting is solid ink, live is the signal, and trouble is danger. */
.stamp {
  flex: 0 0 auto; align-self: center; display: inline-block;
  font-variation-settings: var(--vf-label);
  font-size: 10px; font-weight: 500; letter-spacing: var(--track);
  text-transform: uppercase; white-space: nowrap;
  color: var(--ink-faint); border: 1px solid var(--ink-faint); padding: 5px 7px 4px 9px;
}
.stamp.live    { color: var(--signal); border-color: var(--signal); }
.stamp.waiting { color: var(--field); background: var(--ink); border-color: var(--ink); }
.paper .stamp.waiting { color: var(--bone); }
.stamp.trouble { color: var(--danger); border-color: var(--danger); }

/* ————— D74. circular charts (charts.js ring, orbit, clock, donut) —————
   Arcs are dashed circles that sweep round from nothing as they arrive;
   spokes and bars grow out. All of it holds still for reduced motion. */
.ring-track { fill: none; stroke: var(--raised-2); stroke-width: 6; }
.ring-track.faint { stroke-width: .5; stroke: var(--rule); }
.ring-fill { fill: none; stroke: var(--signal); stroke-width: 8; stroke-linecap: butt; }
.ring-value { font-family: var(--face); font-variation-settings: "opsz" 60, "SOFT" 60;
              font-size: 22px; fill: var(--ink); }
.sweep { animation: sweep 1100ms cubic-bezier(.2,.8,.2,1) both; }
@keyframes sweep { from { stroke-dasharray: 0 1000; } }
.orbit-fill { fill: none; stroke: var(--signal); stroke-width: 5; cursor: default; }
.orbit-chart .ring-track { stroke-width: 5; }
.o0 { opacity: 1; } .o1 { opacity: .82; } .o2 { opacity: .66; } .o3 { opacity: .5; } .o4 { opacity: .36; }
.clock-chart .spoke { stroke: var(--signal); stroke-width: 2.6; stroke-linecap: round; cursor: default;
                      transform-box: view-box; transform-origin: 50% 50%;
                      animation: bloom 800ms cubic-bezier(.2,.8,.2,1) both; }
.clock-chart .spoke.none { stroke: var(--rule); }
.clock-chart .axis { font-size: 5px; }
@keyframes bloom { from { transform: scale(.3); opacity: 0; } }
.donut-part { fill: none; stroke-width: 12; cursor: default; }
.donut-part.d0, .swatch.d0 { stroke: var(--signal); background: var(--signal); }
.donut-part.d1, .swatch.d1 { stroke: var(--ink-faint); background: var(--ink-faint); }
.donut-part.d2, .swatch.d2 { stroke: #7FA6BF; background: #7FA6BF; }
.donut-part.d3, .swatch.d3 { stroke: var(--rule); background: var(--rule); }
.donut-part.d4, .swatch.d4 { stroke: var(--ink); background: var(--ink); }
.donut-chart .ring-track { stroke-width: 12; }
.donut-chart .ring-value { font-size: 16px; }
.donut-chart .axis { font-size: 6px; }
.swatch { display: inline-block; width: 9px; height: 9px; background: var(--signal); flex: 0 0 9px; }
@media (prefers-reduced-motion: reduce) {
  .sweep, .clock-chart .spoke { animation: none; }
}

/* ————— D74/D75. a dashboard on one screen —————
 The studio's and admin's dashboards share this: a strip of headline numbers
 (as many as --kpis says), then six panels in named areas: s (over time),
 m (map), l (a tall scrolling list), and j, w, a along the bottom. */
.dash-head { align-items: center; }
.dash-title { display: flex; align-items: baseline; gap: 6px 22px; flex-wrap: wrap; }
.dash-title p { margin: 0; }
select.compact { width: auto; padding: 7px 10px; font-size: 13px; }
.dash-kpis.paper { margin: 18px 0 0; padding: 0; display: grid;
                   grid-template-columns: repeat(var(--kpis, 6), minmax(0, 1fr)); }
.kpi { padding: 14px 16px 12px; display: flex; gap: 12px; align-items: center; min-width: 0; }
.kpi + .kpi { border-left: 0.5px solid var(--rule); }
.kpi .value { font-family: var(--face); font-variation-settings: "opsz" 60, "SOFT" 60;
              font-size: 30px; line-height: 1.05; font-variant-numeric: tabular-nums; }
.kpi .label { font-size: 13px; color: var(--ink-muted); margin-top: 2px; }
.kpi .sub { font-size: 12px; color: var(--ink-faint); margin-top: 1px; white-space: nowrap;
            overflow: hidden; text-overflow: ellipsis; }
.kpi.quiet .value { color: var(--ink-faint); }
.kpi-text { min-width: 0; }
.kpi-ring { flex: 0 0 52px; }
.kpi-ring .ring-value { display: none; }
.kpi-ring svg { display: block; margin: 0; }

.dash-grid { display: grid; gap: 14px; margin-top: 14px;
             grid-template-columns: repeat(2, minmax(0, 1fr));
             grid-template-areas: "s s" "m l" "j w" "a l"; }
.panel.paper { margin: 0; padding: 14px 16px; display: flex; flex-direction: column;
               min-width: 0; min-height: 0; height: 340px; }
.panel-head { display: flex; justify-content: space-between; align-items: center; gap: 6px 10px; flex-wrap: wrap;
              flex: 0 0 auto; }
.panel-head h2 { font-size: 17px; margin: 0; }
.panel-head .segmented button { padding: 5px 12px; font-size: 12px; }
.panel-body { flex: 1 1 auto; min-height: 0; position: relative; margin-top: 10px; }
.panel-body.scroll-y { overflow-y: auto; margin-right: -8px; padding-right: 8px; }
.panel-note { flex: 0 0 auto; margin: 8px 0 0; font-size: 12px; color: var(--ink-faint); }
.p-series { grid-area: s; } .p-map { grid-area: m; } .p-list { grid-area: l; }
.p-journey { grid-area: j; } .p-when { grid-area: w; } .p-audience { grid-area: a; }
.panel.p-list { height: auto; max-height: 694px; }
.chart.fill svg { width: 100%; height: 100%; margin: 0; }
.map.fill { height: 100%; margin: 0; }
.split-body { display: flex; gap: 14px; align-items: center; }
/* The circle as large as the panel's height allows; the legend takes the rest. */
.circle-host { flex: 0 1 auto; height: 100%; aspect-ratio: 1; max-width: 46%; min-width: 0;
               display: grid; place-items: center; }
.circle-host svg { width: 100%; height: 100%; margin: 0; }
.legend { flex: 1 1 0; list-style: none; margin: 0; padding: 0; font-size: 12.5px; min-width: 0;
          max-height: 100%; overflow-y: auto; }
.legend li { display: flex; align-items: center; gap: 7px; padding: 5px 0; white-space: nowrap;
             border-bottom: 0.5px solid var(--rule); color: var(--ink-muted); }
.legend li > .text { overflow: hidden; text-overflow: ellipsis; min-width: 0; }
.paper .segmented button:not(.go):not(.link):not(.x) { color: var(--ink-muted); }
.paper .segmented button[aria-pressed="true"]:not(.go):not(.link):not(.x) { background: var(--ink); color: #FFFFFF; }
.legend li b { margin-left: auto; font-weight: 550; color: var(--ink); font-variant-numeric: tabular-nums; }
.legend .share { font-size: 11px; color: var(--ink-faint); width: 34px; text-align: right; }
.legend.stats li:not(:has(.swatch)) { padding-left: 16px; }
.legend.week li { gap: 8px; }
.legend.week .day { width: 30px; }
.legend.week .bar { flex: 1 1 auto; height: 6px; background: var(--raised-2); }
.legend.week .bar span { display: block; height: 100%; background: var(--signal);
                         animation: grow 800ms cubic-bezier(.2,.8,.2,1) both; transform-origin: left; }
.legend.week li b { min-width: 28px; text-align: right; margin-left: 0; }
@keyframes grow { from { transform: scaleX(0); } }
@media (prefers-reduced-motion: reduce) { .legend.week .bar span { animation: none; } }
table.data-table.compact { margin: 0; font-size: 13px; }
.data-table.compact thead th { position: sticky; top: 0; background: #FFFFFF; z-index: 1; }
.data-table.compact td { padding: 8px 8px 8px 0; }
.data-table.compact .sub { font-size: 12px; }

/* The whole dashboard in the window when the window allows: the grid takes
   what is left below the numbers, in two rows, with the works and tracks
   list running down the right. */
@media (min-width: 1100px) and (min-height: 680px) {
  body.dash-fit .wrap { max-width: 1400px; padding-bottom: 0; }
  body.dash-fit .dash-grid {
    height: calc(100dvh - var(--dash-top, 300px) - 20px);
    grid-template-columns: repeat(12, minmax(0, 1fr));
    grid-template-rows: minmax(0, 1.1fr) minmax(0, 1fr);
    grid-template-areas: "s s s s s m m m m l l l" "j j j w w w a a a l l l";
  }
  body.dash-fit .panel.paper { height: auto; max-height: none; }
}
@media (max-width: 1099px) {
  .dash-kpis.paper { grid-template-columns: repeat(3, minmax(0, 1fr)); }
  .kpi:nth-child(3n + 1) { border-left: 0; }
  .kpi:nth-child(n + 4) { border-top: 0.5px solid var(--rule); }
}
@media (max-width: 700px) {
  .dash-grid { grid-template-columns: minmax(0, 1fr); grid-template-areas: "s" "m" "l" "j" "w" "a"; }
  .panel.p-list { max-height: 460px; }
  .dash-kpis.paper { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .kpi:nth-child(n) { border-left: 0; border-top: 0.5px solid var(--rule); }
  .kpi:nth-child(-n + 2) { border-top: 0; }
  .kpi:nth-child(even) { border-left: 0.5px solid var(--rule); }
  .kpi:nth-child(odd) { border-left: 0; }
}
