/* Iași Zilnic — a page on an e-paper device.

   One flat sheet of warm grey, ink printed on it, structure drawn in quiet
   hairlines, and the only highlight the medium has ever had: a region
   inverted to solid ink. Nothing floats, nothing blurs, nothing slides. Colour
   is Kaleido-muted and carries state only. One humanist sans, Source Sans 3,
   for everything -- the way a device is set -- with size and weight, not a
   second family, separating what is read from what is operated. */

@font-face {
  font-family: "Source Sans 3";
  font-style: normal;
  font-weight: 400 700;
  font-display: swap;
  src: url("/static/fonts/source-sans-3-latin.woff2") format("woff2");
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA,
    U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193,
    U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
  font-family: "Source Sans 3";
  font-style: normal;
  font-weight: 400 700;
  font-display: swap;
  src: url("/static/fonts/source-sans-3-latin-ext.woff2") format("woff2");
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF,
    U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020,
    U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}


/* E-paper, daylight. Every pair below clears WCAG AA in this theme; the
   arithmetic is in research/DIRECTION.md. Token names are kept from the card
   world so the sheet keeps resolving; what they point at is the new one. */
:root {
  --sans: "Source Sans 3", ui-sans-serif, -apple-system, "Segoe UI", Roboto, sans-serif;

  --ink: #171613;
  --ink-soft: #474540;
  --ink-faint: #5f5c55;
  --paper: #e9e7e1;
  --card: #f3f1ec;
  --line: #cdc9c0;
  --accent: #a4392c;
  --accent-ink: #8f2f24;
  --action: #171613;
  --ok: #3d6a45;
  --ok-ink: #3d6a45;
  --chip-rail: #dedbd3;
  --scrim: rgba(23, 22, 19, .45);
  --dot: #e9e7e1;
  --radius: 3px;
  --card-radius: 2px;
  --shadow: none;
  --lift: none;
  --dock: #171613;
  --dock-ink: #efede8;
  --dock-dim: #aaa79e;

  /* Reading size. An e-reader resizes what you read, not its own chrome, so
     this multiplies the text that carries content and leaves tabs, labels and
     the two bands at their measured sizes. */
  --read: 1;
}

:root[data-text="sm"] { --read: .92; }
:root[data-text="lg"] { --read: 1.12; }
:root[data-text="xl"] { --read: 1.25; }

/* Night, 20:00–08:00. The inline script in <head> stamps data-theme before
   first paint; prefers-color-scheme only covers the no-JS case. */
:root[data-theme="dark"] {
  --ink: #e7e4dd;
  --ink-soft: #b4b1a9;
  --ink-faint: #9b988f;
  --paper: #141311;
  --card: #1c1a17;
  --line: #33302b;
  --accent: #d9776d;
  --accent-ink: #e0837a;
  --action: #e7e4dd;
  --ok: #8fc49a;
  --ok-ink: #8fc49a;
  --chip-rail: #232019;
  --scrim: rgba(0, 0, 0, .6);
  --dot: #141311;
  --shadow: none;
  --lift: none;
  --dock: #0c0b09;
  --dock-ink: #e7e4dd;
  --dock-dim: #a19e95;
}

/* The warm light. An e-reader's amber frontlight, not a filter over the page:
   every token is picked and measured for this state, so a warm page is as
   readable as a neutral one rather than a tinted approximation of it. */
:root[data-warm="1"] {
  --ink: #1b1610;
  --ink-soft: #4d473c;
  --ink-faint: #655e50;
  --paper: #efe4d0;
  --card: #f7eedd;
  --line: #d8cbb2;
  --accent: #a33a25;
  --accent-ink: #8d2f1e;
  --action: #1b1610;
  --ok: #3c6640;
  --ok-ink: #3c6640;
  --chip-rail: #e4d7bf;
  --scrim: rgba(27, 22, 16, .45);
  --dot: #efe4d0;
  --dock: #1b1610;
  --dock-ink: #f3ead8;
  --dock-dim: #b0a58e;
}

:root[data-theme="dark"][data-warm="1"] {
  --ink: #ece2d0;
  --ink-soft: #b8ae9b;
  --ink-faint: #9e9483;
  --paper: #17130d;
  --card: #201b13;
  --line: #3a3228;
  --accent: #dc8071;
  --accent-ink: #e3897a;
  --action: #ece2d0;
  --ok: #93c599;
  --ok-ink: #93c599;
  --chip-rail: #261f15;
  --scrim: rgba(0, 0, 0, .62);
  --dot: #17130d;
  --dock: #100d08;
  --dock-ink: #ece2d0;
  --dock-dim: #a59b88;
}

@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) {
  --ink: #e7e4dd;
  --ink-soft: #b4b1a9;
  --ink-faint: #9b988f;
  --paper: #141311;
  --card: #1c1a17;
  --line: #33302b;
  --accent: #d9776d;
  --accent-ink: #e0837a;
  --action: #e7e4dd;
  --ok: #8fc49a;
  --ok-ink: #8fc49a;
  --chip-rail: #232019;
  --scrim: rgba(0, 0, 0, .6);
  --dot: #141311;
  --shadow: none;
  --lift: none;
  --dock: #0c0b09;
  --dock-ink: #e7e4dd;
  --dock-dim: #a19e95;
}

  :root:not([data-theme="light"])[data-warm="1"] {
  --ink: #ece2d0;
  --ink-soft: #b8ae9b;
  --ink-faint: #9e9483;
  --paper: #17130d;
  --card: #201b13;
  --line: #3a3228;
  --accent: #dc8071;
  --accent-ink: #e3897a;
  --action: #ece2d0;
  --ok: #93c599;
  --ok-ink: #93c599;
  --chip-rail: #261f15;
  --scrim: rgba(0, 0, 0, .62);
  --dot: #17130d;
  --dock: #100d08;
  --dock-ink: #ece2d0;
  --dock-dim: #a59b88;
}
}

/* House rule: titles are never centred. Text sets its own left edge and the
   eye tracks one vertical line down the page. */
h1, h2, h3, .daybreak { text-align: left; }

* { box-sizing: border-box; }

/* The surfaces we did not draw: selection, caret, focus and scrollbars ship
   with browser defaults that belong to no design system. */
::selection { background: color-mix(in srgb, var(--accent) 24%, transparent); }

:root {
  caret-color: var(--accent);
  scrollbar-color: var(--line) transparent;
  scrollbar-width: thin;
  accent-color: var(--ink);
}

:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
  border-radius: 2px;
}

/* A pointer press should never leave the focus ring behind on a card. */
:focus:not(:focus-visible) { outline: none; }

html { -webkit-text-size-adjust: 100%; }

body {
  margin: 0;
  background: var(--paper);      /* one sheet; a device has no grid and no grain */
  color: var(--ink);
  font: 400 calc(16px * var(--read)) /1.5 var(--sans);
  -webkit-font-smoothing: antialiased;
  /* The dock is fixed, so the document must reserve its band whatever the
     view's length: a short room used to leave the credits under the bar. */
  min-height: 100dvh;
  padding: 0 16px calc(96px + env(safe-area-inset-bottom));
  max-width: 640px;
  margin-inline: auto;
}

/* ------------------------------------------------------------------ header */

.top { padding: calc(30px + env(safe-area-inset-top)) 0 6px; }

.top-row {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: 12px;
}

/* The date is the masthead. No eyebrow above it: the heading carries its own
   weight and the app's name lives on the home screen. */
.top h1 {
  margin: 0;
  font-size: 30px;
  font-weight: 400;
  line-height: 1.1;
  letter-spacing: -.02em;
}

.settings {
  flex: 0 0 auto;
  display: grid;
  place-items: center;
  width: 44px;
  height: 44px;
  margin: -8px -10px 0 0;
  border: 0;
  border-radius: 50%;
  background: transparent;
  color: var(--ink-faint);
  cursor: pointer;
}

.settings svg {
  width: 20px;
  height: 20px;
  fill: none;
  stroke: currentColor;
  stroke-width: 1.6;
  stroke-linecap: round;
}

.settings:hover { color: var(--ink); background: var(--chip-rail); }

/* ------------------------------------------------------------------ blocks */

/* No section headings: the active tab names the room, so a title here would
   only repeat it. What is left is the rhythm between a view's own parts. */

/* -------------------------------------------------------- tabs and filters */

/* ONE tab control for the whole app: news scopes, event lenses, alert kinds and
   cinema days all wear this. It spans the column and its tabs take equal shares
   of it — a rail that shrank to its labels left the widest thing on screen
   surrounded by empty pill, and the targets smaller than the space allowed. */
/* A ruled segmented control, the active segment inverted to ink: the one
   highlight e-paper has ever had. */
.tabs {
  display: flex;
  gap: 3px;
  width: 100%;
  padding: 3px;
  border: 1px solid var(--line);
  background: transparent;
  border-radius: 2px;
}

.tab {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 6px;
  /* Equal shares of the rail. min-width: 0 lets a long label ellipsize instead
     of forcing the row wider than its container. */
  flex: 1 1 0;
  min-width: 0;
  min-height: 44px;           /* 54px rail with the cradle */
  padding: 0 12px;
  border: 0;
  border-radius: 2px;
  background: transparent;
  font: inherit;
  font-size: 14px;
  white-space: nowrap;
  color: var(--ink-soft);
  text-decoration: none;          /* a segment can be a link as well as a button */
  cursor: pointer;
  transition: background .12s ease, color .12s ease;
}

.tab.on {
  background: var(--ink);
  color: var(--paper);
}
/* Everything inside a selected tab takes the paper it was inverted to. A
   count or glyph that keeps its own ink colour sits dark on the dark fill. */
.tab.on .tab-count, .tab.on .kind { color: inherit; opacity: .85; }

.tab:hover:not(.on) { color: var(--ink); }

.tab-count {
  font-size: 12px;
  color: var(--ink-faint);
  font-variant-numeric: tabular-nums;
}


/* A set too wide to share the rail scrolls inside it instead, and its tabs go
   back to their natural width — squeezing six alert kinds into equal shares
   would leave each one a sliver. */
.tabs.scroll {
  justify-content: flex-start;
  overflow-x: auto;
  scrollbar-width: none;
  -webkit-overflow-scrolling: touch;
  scroll-snap-type: x proximity;
  scroll-padding-left: 5px;
  overscroll-behavior-x: contain;
}

.tabs.scroll::-webkit-scrollbar { display: none; }
/* Grow to fill when the set fits, never shrink when it does not: four alert
   kinds share the rail like any other set, while eight cinema days keep their
   natural width and scroll. */
.tabs.scroll .tab { flex: 1 0 auto; padding: 0 18px; scroll-snap-align: start; }

/* The alert kinds are the same tab with a glyph in place of a word. */
.icon-tab { min-width: 58px; padding: 0 14px; }
.icon-tab .kind { margin-right: 0; vertical-align: 0; }

/* Screen-reader-only: the room's name and the swap announcement. */
.sr-only {
  position: absolute;
  width: 1px;
  height: 1px;
  margin: -1px;
  padding: 0;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
}

/* Says what the tab means without making the reader infer it. Flush with the
   page gutter — it was inset 2px, which reads as a wobble against the titles. */
.lens-hint {
  margin: 12px 0 0;
  font-size: 13px;
  color: var(--ink-faint);
}

/* The end of the brief. */
.brief-end {
  margin: 18px 0 0;
  font-size: 12px;
  letter-spacing: .08em;
  text-transform: uppercase;
  color: var(--ink-faint);
}

/* Settings form. */
/* A setting states itself and opens on tap. Three textareas standing
   permanently open were mostly empty boxes, and they buried the thing you came
   to check: what is currently set. */
.setting {
  margin-top: 10px;
  border: 1px solid var(--line);
  border-radius: 2px;
  background: var(--card);
}

.setting > summary {
  display: flex;
  align-items: center;
  gap: 12px;
  /* A row, not a word: the whole thing is the tap target, comfortably past
     44px on a phone. */
  min-height: 56px;
  padding: 10px 14px;
  border-radius: 2px;
  cursor: pointer;
  list-style: none;
}

.setting > summary::-webkit-details-marker { display: none; }

.setting-head {
  display: flex;
  flex: 1;
  flex-direction: column;
  gap: 5px;
  min-width: 0;
}

.setting-label {
  font-size: 11px;
  font-weight: 500;
  letter-spacing: .1em;
  text-transform: uppercase;
  color: var(--ink-soft);
}

.setting-chips {
  display: flex;
  flex-wrap: wrap;
  gap: 5px;
}

/* Read, not pressed — so no 44px target and no growing to fill the row. */
.setting-tag {
  flex: none;
  max-width: 100%;
  padding: 3px 9px;
  border: 1px solid var(--line);
  border-radius: 2px;
  background: var(--paper);
  font-size: 12px;
  line-height: 1.45;
  color: var(--ink-soft);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

/* Named `rest` rather than `more`, which used to be the brief's tail button —
   a full-width 48px flex row with a chevron of its own — and stretched this to
   278px. That rule is gone now, but the name stays distinct on purpose. */
.setting-tag.rest {
  border-color: transparent;
  background: transparent;
  padding-left: 2px;
  color: var(--ink-faint);
}

.setting-empty {
  font-size: 13px;
  color: var(--ink-faint);
}

.setting-mark {
  flex: none;
  width: 16px;
  height: 16px;
  fill: none;
  stroke: var(--ink-faint);
  stroke-width: 1.6;
  stroke-linecap: round;
  transition: transform .18s ease;
}

.setting[open] > summary .setting-mark { transform: rotate(180deg); }

/* Open: the chips would only repeat what the textarea already shows. */
.setting[open] > summary .setting-chips,
.setting[open] > summary .setting-empty { display: none; }

.setting .field-hint { margin: 0 14px; }

.setting textarea {
  display: block;
  width: calc(100% - 28px);
  margin: 10px 14px 14px;
  padding: 12px 14px;
  border: 1px solid var(--line);
  border-radius: 2px;
  background: var(--paper);
  color: var(--ink);
  font: inherit;
  font-size: 14px;
  line-height: 1.5;
  resize: vertical;
}

.setting textarea:focus {
  outline: none;
  border-color: var(--ink-faint);
}

@media (prefers-reduced-motion: reduce) {
  .setting-mark { transition: none; }
}

.field-hint {
  display: block;
  margin-top: 4px;
  font-size: 13px;
  color: var(--ink-faint);
}

.save {
  width: 100%;
  min-height: 52px;
  margin-top: 28px;
  border: 0;
  border-radius: 2px;
  background: var(--ink);
  color: var(--paper);
  font: inherit;
  font-size: 16px;
  cursor: pointer;
}

.saved {
  margin: 14px 0 0;
  font-size: 13px;
  color: var(--ok-ink);
}

/* ------------------------------------------------------------ filter list */

/* Two dozen categories in a scrolling rail meant most were off-screen. One
   control, one checklist, and a count of what is on. */
/* A filter is a control, not a field: it sits inline at label size so the
   first event card outweighs it, and only its open panel takes width. */
.filters {
  display: inline-block;
  min-width: 0;
  margin-top: 14px;
  border: 1px solid var(--line);
  border-radius: 2px;
  background: var(--card);
  overflow: hidden;
}

.filters[open] { display: block; }

.filters > summary {
  display: flex;
  align-items: center;
  gap: 8px;
  min-height: 44px;
  padding: 0 14px;
  list-style: none;
  font-size: 14px;
  color: var(--ink);
  cursor: pointer;
}

.filters > summary::-webkit-details-marker { display: none; }
.filters[open] > summary > span:first-child { flex: 1; }

.filters > summary::after {
  content: "";
  width: 7px;
  height: 7px;
  border-right: 1.5px solid var(--ink-faint);
  border-bottom: 1.5px solid var(--ink-faint);
  transform: translateY(-2px) rotate(45deg);
  transition: transform .2s ease;
}

.filters[open] > summary::after { transform: translateY(1px) rotate(-135deg); }
.filters[open] > summary { border-bottom: 1px solid var(--line); }

.filters-count {
  display: grid;
  place-items: center;
  min-width: 22px;
  height: 22px;
  padding: 0 6px;
  border-radius: 2px;
  background: var(--ink);
  color: var(--paper);
  font-size: 12px;
  font-variant-numeric: tabular-nums;
}

/* A wide column gap so the two lists read as two lists: the checkbox now
   leads its own label, and the space between columns keeps them apart. */
.filters-form {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 2px 14px;
  padding: 6px 8px 8px;
}

.opt {
  display: flex;
  align-items: center;
  gap: 10px;
  min-height: 44px;
  padding: 0 6px;
  border-radius: 2px;
  font-size: 14px;
  color: var(--ink-soft);
  cursor: pointer;
}

.opt input {
  position: absolute;
  opacity: 0;
  pointer-events: none;
}

.opt-name {
  flex: 1;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.opt-tick {
  display: grid;
  place-items: center;
  width: 20px;
  height: 20px;
  border: 1px solid var(--line);
  border-radius: 2px;
  color: transparent;
}

.opt-tick svg {
  width: 12px;
  height: 12px;
  fill: none;
  stroke: currentColor;
  stroke-width: 2;
  stroke-linecap: round;
  stroke-linejoin: round;
}

.opt input:checked + .opt-tick {
  background: var(--ink);
  border-color: var(--ink);
  color: var(--paper);
}

.opt input:checked ~ .opt-name { color: var(--ink); font-weight: 500; }
.opt input:focus-visible + .opt-tick {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
}

.opt:hover .opt-tick { border-color: var(--ink-faint); }

.filters-clear {
  grid-column: 1 / -1;
  min-height: 44px;
  margin-top: 2px;
  border: 0;
  border-top: 1px solid var(--line);
  background: transparent;
  font: inherit;
  font-size: 14px;
  color: var(--action);
  cursor: pointer;
}

/* One scrolling row — the chip set grows with whatever the data contains,
   and bleeds to the screen edges so it reads as swipeable. */
.chips {
  display: flex;
  gap: 8px;
  margin: 16px -16px 0;
  padding: 2px 16px;
  overflow-x: auto;
  scrollbar-width: none;
  -webkit-overflow-scrolling: touch;
  scroll-snap-type: x proximity;
  /* Without this the first chip snaps to the padding box and lands flush with
     the screen edge, breaking the 20px gutter every other element sits on. */
  scroll-padding-left: 16px;
  overscroll-behavior-x: contain;
}

.chips::-webkit-scrollbar { display: none; }

.chip {
  flex: 0 0 auto;
  scroll-snap-align: start;
  display: inline-flex;
  align-items: center;
  min-height: 44px;           /* comfortable thumb target */
  white-space: nowrap;
  border: 1px solid var(--line);
  background: transparent;
  border-radius: 2px;
  padding: 0 16px;
  font: inherit;
  font-size: 14px;
  color: var(--ink-soft);
  cursor: pointer;
  transition: background .18s ease, color .18s ease, border-color .18s ease;
}

.chip.on {
  background: var(--ink);
  border-color: var(--ink);
  color: var(--paper);
}

.chip.clear { color: var(--action); border-style: dashed; }

/* ------------------------------------------------------------------ cinema */

/* Film rows use the shared .row grid; only the sheet's own pieces live here. */

.showtimes {
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
  margin-top: 2px;
}

/* 3D / 4DX / IMAX. 2D is never badged — it is the default, and flagging it
   would drown the formats actually worth noticing. */
.fmt {
  display: inline-block;
  margin-left: 6px;
  padding: 2px 5px;
  border: 1px solid var(--line);
  border-radius: 2px;
  font-size: 10px;
  font-weight: 600;
  letter-spacing: .06em;
  line-height: 1.3;
  color: var(--ink-soft);
  vertical-align: 2px;
  white-space: nowrap;
}

.showtime .fmt {
  margin-left: 5px;
  padding: 1px 4px;
  font-size: 10px;
  vertical-align: 1px;
}

.showtime {
  padding: 3px 8px;
  border-radius: 2px;
  border: 1px solid var(--line);
  background: transparent;
  font-size: 12px;
  font-variant-numeric: tabular-nums;
  color: var(--ink);
  text-decoration: none;
}

.showtime.more { background: transparent; color: var(--ink-faint); }

.showtime.book { border: 1px solid transparent; }
.showtime.book:hover { border-color: var(--ink-faint); }

.showtime.sold {
  color: var(--ink-faint);
  text-decoration: line-through;
}

.movie-head {
  display: flex;
  gap: 18px;
  align-items: flex-start;
}

/* At 132px the poster is the anchor of the sheet, so the title sits beside it
   rather than shouting over it. */
.sheet .movie-head h2 { font-size: 22px; margin-top: 4px; }

.poster-large {
  flex: 0 0 auto;
  width: 132px;
  height: 196px;
  border-radius: 2px;
  object-fit: cover;
  background: var(--chip-rail);
  border: 1px solid var(--line);
}

.cinema-block { margin-top: 18px; }
.cinema-block .showtimes { margin-top: 10px; }

.chips.tight { margin-top: 22px; }

/* --------------------------------------------------------- week accordions */

.weeks { margin-top: 22px; }

.week { border-bottom: 1px solid var(--line); }

.week:first-child { border-top: 1px solid var(--line); }

.week > summary {
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 18px 2px;
  list-style: none;
  cursor: pointer;
  font-size: 14px;
  color: var(--ink);
}

.week > summary::-webkit-details-marker { display: none; }

.week-name { flex: 1; }

/* Chevron, drawn in CSS so it needs no asset and follows the theme. */
.week > summary::after {
  content: "";
  width: 7px;
  height: 7px;
  margin-right: 4px;
  border-right: 1.5px solid var(--ink-faint);
  border-bottom: 1.5px solid var(--ink-faint);
  transform: translateY(-2px) rotate(45deg);
  transition: transform .2s ease;
}

.week[open] > summary::after { transform: translateY(1px) rotate(-135deg); }

.week[open] > summary { color: var(--ink); }

.week > .list { margin-top: 0; padding-bottom: 12px; }

.week .daybreak:first-child { margin-top: 8px; }

/* ------------------------------------------------------------------- lists */

.list { margin-top: 14px; }

/* A hairline under the last row of one section and the heading of the next
   would collide, so sections carry the breathing room instead. */
.block { margin-top: 44px; }

.card-link {
  display: block;
  color: inherit;
  text-decoration: none;
}

/* -------------------------------------------------------------- disruptions */

/* Saying "nothing is cut off" is itself the answer, so the empty state is a
   real line rather than a hidden section. */
.all-clear {
  display: flex;
  align-items: center;
  gap: 9px;
  margin: 0;
  font-size: 14px;
  color: var(--ink-faint);
}

.all-clear::before {
  content: "";
  flex: 0 0 auto;
  width: 7px;
  height: 7px;
  border-radius: 50%;
  background: var(--ok);
}

/* On the detail line rather than in a column: "acum" answers "does this
   affect me right now", which is a property of the notice, not a date. */
.when-now,
.when-soon {
  font-weight: 600;
  letter-spacing: .02em;
}

/* Restrained: the word reads in ink and a single 5px dot carries the colour.
   Five red ACUMs down a list shouted without adding information. */
.when-now {
  display: inline-flex;
  align-items: center;
  gap: 5px;
  color: var(--ink);
  text-transform: uppercase;
  font-size: 12px;
}

.live-dot {
  width: 5px;
  height: 5px;
  border-radius: 50%;
  background: var(--accent);
}

.when-soon { color: var(--ink); }

/* The kind leads the detail line as a glyph: faster to recognise than a word,
   and it hands the width back to the street list. */
.kind {
  display: inline-flex;
  align-items: center;
  margin-right: 7px;
  vertical-align: -4px;
  color: var(--ink-soft);
}

.kind-icon {
  width: 17px;
  height: 17px;
  fill: none;
  stroke: currentColor;
  stroke-width: 1.5;
  stroke-linecap: round;
  stroke-linejoin: round;
}

.kind-line {
  display: flex;
  align-items: center;
  gap: 4px;
}

.kind-line .kind { margin-right: 2px; vertical-align: 0; }

/* Restrained on purpose: every kind reads in the same neutral ink, and the
   day-track plus the single "acum" mark do the work colour was doing. Eight
   red rows made the section shout and told the reader nothing extra. */

/* ----------------------------------------------------------------- the view */

/* One authored moment: the room changes. Exponential ease-out from an
   already-visible default, so a slow network never leaves a blank screen. */
.view { animation: view-in .19s cubic-bezier(.16, 1, .3, 1); }

@keyframes view-in {
  from { opacity: .6; }            /* a page turn is a cut, not a slide */
}

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

.view-hint {
  margin: 0 0 18px;
  font-size: 14px;
  line-height: 1.5;
  color: var(--ink-faint);
}

/* ---------------------------------------------------------------- next up */

/* It used to be dark, which made it read as a second floating bar right above
   the real one. It is a card like every other card now, distinguished by being
   the only one with a rule above it and by carrying its own day-track. */
.nextup {
  display: block;
  width: 100%;
  margin-top: 26px;
  padding: 14px 16px 16px;
  border: 0;
  border-top: 1px solid var(--line);
  border-radius: 0;
  background: transparent;
  font: inherit;
  color: inherit;
  text-align: left;
  cursor: pointer;
}

.nextup-title {
  display: block;
  font-size: calc(16px * var(--read));
  font-weight: 500;
  letter-spacing: 0;
  line-height: 1.3;
}

/* The same sub-line every row carries, not a label above the title. */
.nextup-sub {
  display: block;
  margin-top: 4px;
  font-size: calc(13px * var(--read));
  color: var(--ink-faint);
}

.nextup .tk { margin-top: 12px; }
.nextup:hover .nextup-title { text-decoration: underline;
  text-decoration-color: var(--line); text-underline-offset: 4px; }

/* ---------------------------------------------------------------- the dock */

/* Real navigation now, not a scroll shortcut: four rooms, the active one lit,
   and each tab carrying its own count so nothing is hidden behind it. */
/* The bottom band. Full width and flush, like the top one: the page is
   bracketed by two printed bands, not decorated by a floating pill. */
.dock {
  position: fixed;
  left: 0;
  right: 0;
  bottom: 0;
  z-index: 9;
  display: flex;
  gap: 4px;
  /* The rooms share the band. On a phone that is the whole width; past the
     app's own column the padding grows instead, so the row never stretches
     wider than the page it belongs to. */
  /* Extra room above the buttons: the fade needs height to be a fade rather
     than an edge, and the buttons stay at the bottom of it where the ink is
     solid. */
  padding-block: 30px calc(6px + env(safe-area-inset-bottom));
  padding-inline: max(12px, calc((100% - 640px) / 2));
  align-items: flex-end;

  /* Ink rising from the bottom edge instead of a band with a rule on it, so
     the page runs out under the navigation rather than stopping at a line.
     The stops fade the dock's own colour rather than to `transparent`, which
     is rgba(0,0,0,0) and would drag the ramp toward grey on the way. */
  background: linear-gradient(
    to top,
    var(--dock) 0%,
    var(--dock) 42%,
    color-mix(in srgb, var(--dock) 72%, transparent) 64%,
    color-mix(in srgb, var(--dock) 28%, transparent) 84%,
    color-mix(in srgb, var(--dock) 0%, transparent) 100%);
}

.dock-item {
  position: relative;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 3px;
  flex: 1 1 0;                 /* equal shares of the band */
  min-width: 0;
  min-height: 52px;
  padding: 0 4px;
  border: 0;
  border-radius: 2px;
  background: transparent;
  color: var(--dock-dim);
  text-decoration: none;
  cursor: pointer;
  transition: background .18s ease, color .18s ease;
}

.dock-item svg {
  width: 20px;
  height: 20px;
  fill: none;
  stroke: currentColor;
  stroke-width: 1.6;
  stroke-linecap: round;
  stroke-linejoin: round;
}

/* Every room says its name. Only the active one used to, which left a new
   reader with one word and three abstract glyphs as the app's whole
   navigation -- unlabelled icons are the most reliably failing pattern on a
   phone, and the band has room: equal shares of 375px are 70px each, and the
   longest name is "Planuri". */
.dock-label {
  display: block;
  max-width: 100%;
  overflow: hidden;
  text-overflow: ellipsis;
  font-size: 11px;
  letter-spacing: .01em;
}

.dock-item.on { padding: 0 6px; }

.dock-item.on {
  background: var(--dock-ink);   /* inverted selection, paper on the band */
  color: var(--dock);
}
.dock-item.on .dot { box-shadow: 0 0 0 2px var(--dock-ink); }

.dock-item:hover { color: var(--dock-ink); }

/* A dot, not a count: the tab cannot be acted on from here, so the number
   would cost attention and buy nothing. It lives in the tab's aria-label. */
.dot {
  position: absolute;
  top: 11px;
  right: 13px;
  width: 6px;
  height: 6px;
  border-radius: 50%;
  background: var(--dock-dim);
  box-shadow: 0 0 0 2px var(--dock);
}

.dot.live { background: var(--accent); }

.dock-item.on .dot { top: 9px; right: 19px; background: var(--dock-ink); }
.dock-item.on .dot.live { background: var(--accent); }

/* --------------------------------------------------------------- swiping *//* --------------------------------------------------------------- swiping */

/* The cues sit under the row and are revealed by dragging it, so the gesture
   explains itself the first time it is half-performed. */
.swipe {
  position: relative;
  display: grid;
  grid-template-rows: 1fr;          /* collapses to 0fr, never animating height */
  /* Items are separated by the same 14px they are padded with. 10px was
     tuned for cards that floated on a shadow; every item now carries a 1px
     edge, and two edges that close read as one thick rule rather than as two
     things. */
  margin-bottom: 14px;
  border-radius: var(--card-radius);
  background: var(--chip-rail);     /* the bed the cues sit on */
  overflow: hidden;
  touch-action: pan-y;              /* vertical scrolling stays the page's */
  transition: grid-template-rows .18s ease;
}

.swipe > .row {
  position: relative;
  z-index: 1;
  background: var(--card);          /* opaque, so it covers the cues */
  border-radius: var(--card-radius);
  border: 1px solid var(--line);    /* a printed panel: rule, no shadow */
  transform: translateX(var(--dx, 0));
  transition: transform .22s cubic-bezier(.2, .8, .3, 1),
              background-color .12s ease;
}

/* A press is acknowledged before the sheet arrives. A tint rather than a
   scale, because the row's transform belongs to the swipe. */
.swipe:not(.dragging) > .row:active {
  background: color-mix(in srgb, var(--card), var(--ink) 4%);
}

.swipe.dragging > .row { transition: none; }

.swipe-cue {
  position: absolute;
  top: 0;
  bottom: 0;
  display: flex;
  align-items: center;
  padding: 0 4px;
  font-size: 13px;
  font-weight: 500;
  letter-spacing: .04em;
  opacity: var(--cue, 0);
  pointer-events: none;
}

.swipe-cue { padding: 0 20px; }
.swipe-cue.keep { left: 0; color: var(--ok-ink); }
.swipe-cue.hide { right: 0; color: var(--accent-ink); }

/* Only the cue being pulled towards shows. */
.swipe[data-dir="keep"] .hide,
.swipe[data-dir="hide"] .keep { opacity: 0; }

/* On the row, not the wrapper: the row is opaque and would hide it. */
.swipe.flash > .row {
  background: color-mix(in srgb, var(--ok) 12%, var(--card));
  transition: background .9s ease;
}

/* Row removal collapses its grid track rather than its height: animating
   height thrashes layout, and the track interpolates on the compositor. */
/* Row removal collapses its grid track, never its height: animating height
   thrashes layout. The gap goes with it because the margin lives on an inner
   spacer rather than on the animated box. */
.swipe.collapsing { grid-template-rows: 0fr; }

/* A write that did not land says so on the row it failed on. */
.swipe-failed {
  margin: 0;
  padding: 10px 16px 12px;
  font-size: 13px;
  color: var(--accent-ink);
}

.undone {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  padding: 14px 0;
  font-size: 14px;
  color: var(--ink-faint);
}

.undo {
  min-height: 44px;
  padding: 0 4px;
  border: 0;
  background: transparent;
  font: inherit;
  font-size: 14px;
  color: var(--action);
  cursor: pointer;
}

@media (prefers-reduced-motion: reduce) {
  .swipe > .news, .swipe > .event, .swipe > .movie { transition: none; }
}

/* One grid for all three lists — when · media · what · status. Sharing it means
   the eye learns the layout once, and the columns line up down the whole page
   even though each card is its own floating object. */
.row {
  display: grid;
  grid-template-columns: 52px minmax(0, 1fr) 28px;
  align-items: center;
  gap: 10px 14px;
  width: 100%;
  padding: 13px 14px 13px 0;
  border: 0;
  background: transparent;
  font: inherit;
  color: inherit;
  text-align: left;
  cursor: pointer;
}

/* No date column at all: news carries its source on the line under the title,
   and a favicon in a column of its own only repeated it. */
.row.bare {
  grid-template-columns: minmax(0, 1fr) 28px;
  padding-left: 16px;
}

/* The track sits under everything and spans every column, so its scale is the
   same in a list with poster thumbnails as in one without. */
.row-foot {
  grid-column: 1 / -1;
  display: block;
}

/* Rows with a when column carry no left padding of their own, so the foot
   supplies it and every track — poster list or not — starts on one line. */
.row:not(.bare) .row-foot { padding-left: 16px; }

/* The grid gap already separates the foot from the text above it. */
.row-foot .tk { margin-top: 0; }

/* The when-column, set off by a hairline the way a ledger separates its date. */
.row-when {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 1px;
  align-self: stretch;
  min-height: 48px;
  padding-right: 2px;
  border-right: 1px solid var(--line);
}

.when-major {
  font-size: 19px;
  letter-spacing: 0;
  font-variant-numeric: tabular-nums;
  color: var(--ink);
}

.when-minor {
  font-size: 11px;
  font-weight: 500;
  letter-spacing: .08em;
  text-transform: uppercase;
  color: var(--ink-faint);
}

/* Real imagery where it exists — poster, outlet favicon — a monogram where it
   does not, rather than a stock icon that means nothing. */
/* The badge rides under the date instead of holding its own column. */
.tile {
  display: grid;
  place-items: center;
  overflow: hidden;
  background: var(--chip-rail);
}

.tile.sm {
  width: 22px;
  height: 22px;
  margin-top: 3px;
  border-radius: 2px;
}

.tile.sm img { width: 14px; height: 14px; object-fit: contain; }

.tile.mono {
  font-size: 11px;
  font-weight: 600;
  letter-spacing: .02em;
  color: var(--ink-soft);
}

/* A film's poster does the job the date column does elsewhere: it identifies
   the item faster than any text. */
.tile.poster {
  width: 34px;
  height: 50px;
  border-radius: 2px;
}

.tile.poster img { width: 100%; height: 100%; object-fit: cover; }

.row-text { min-width: 0; }

.row-title {
  display: block;
  font-size: calc(16px * var(--read));
  font-weight: 500;
  line-height: 1.35;
  letter-spacing: 0;
  text-wrap: pretty;
}

.row-sub {
  display: block;
  margin-top: 3px;
  font-size: calc(13px * var(--read));
  color: var(--ink-faint);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* For a disruption the affected streets are the whole point, so that line
   wraps instead of being cut off at the card edge. */
.row-sub.wrap {
  white-space: normal;
  display: -webkit-box;
  -webkit-line-clamp: 3;
  -webkit-box-orient: vertical;
  line-height: 1.4;
}

/* Showtimes are what the eye is hunting for in a film row. */
.row-times { color: var(--ink); font-variant-numeric: tabular-nums; }

/* ------------------------------------------------------------ the track key

   Shown once above a list, never per row. Without it the tracks below are
   just lines: this gives them their scale, names the mark, and says what the
   red line is. */
/* The spacing lives on the wrapper, not the axis, so the two states measure
   honestly against each other — a child margin collapsing through the wrapper
   made the collapsed line look like it saved nothing. */
/* A panel like every other secondary container, not a bare axis floating on
   the page. The horizontal padding is not decoration: with the 1px rule, it
   puts the axis at exactly the left and right a row's track lands on, so the
   scale printed here measures the tracks below it. */
/* The panel has neighbours above it -- a tab rail, a filter row, a note -- and
   it sat flush against them, two bordered boxes sharing an edge. */
.tk-key-wrap {
  margin-top: 14px;
  padding: 12px 14px 12px 16px;
  border: 1px solid var(--line);
  border-radius: 2px;
  background: var(--card);
}

/* Dismissed, the panel stops being one: the single control is its own frame,
   and keeping the box would leave a bordered button inside a bordered box. */
.tk-key-wrap.off {
  margin-top: 12px;
  padding: 2px 0 6px;
  border: 0;
  background: transparent;
}

/* No padding-right: that reserved 30px for the dismiss and shortened the axis
   by the same amount, so the legend's 18 sat 29px left of where 18:00 falls on
   every track under it. The dismiss lives in the panel's corner instead. */
.tk-key {
  position: relative;
}

.tk-key-label {
  display: block;
  margin-bottom: 9px;
  padding-right: 38px;            /* clear of the dismiss */
  font-size: 11px;
  font-weight: 500;
  letter-spacing: .1em;
  text-transform: uppercase;
  color: var(--ink-soft);
}

.tk-key .tk { margin-top: 0; }

/* Dismissing the legend. Sits at the axis's own top-right, quiet enough not to
   compete with the scale it closes, but a real 32px target. */
/* The same object as the sheet's close, so a dismiss looks like a dismiss
   wherever it appears: a filled circle with a stroked cross. Pulled into the
   panel's own padding so it sits in the corner and costs no height; the
   ::after below carries the 44px target whatever the circle measures. */
.tk-key-hide {
  position: absolute;
  top: -6px;
  right: -8px;
  display: grid;
  place-items: center;
  width: 32px;
  height: 32px;
  padding: 0;
  border: 0;
  border-radius: 50%;
  background: var(--chip-rail);
  color: var(--ink-soft);
  cursor: pointer;
}

.tk-key-hide svg {
  width: 15px;
  height: 15px;
  fill: none;
  stroke: currentColor;
  stroke-width: 1.6;
  stroke-linecap: round;
}

.tk-key-hide:hover { color: var(--ink); }

/* The collapsed state: one line, aligned to the same left edge as the axis it
   replaces, costing a row of text rather than a block. */
.tk-key-show {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  margin: 0;
  padding: 6px 12px 6px 9px;
  border: 1px solid var(--line);
  border-radius: 2px;
  background: var(--card);
  font: inherit;
  font-size: 11px;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--ink-faint);
  cursor: pointer;
}

.tk-key-show svg {
  width: 15px;
  height: 15px;
  fill: none;
  stroke: currentColor;
  stroke-width: 1.5;
  stroke-linecap: round;
}

.tk-key-show:hover {
  background: var(--card);
  color: var(--ink-soft);
}

/* Both controls stay visually small — the point is that they cost no space —
   but a thumb needs 44px, so the hit area is extended past the paint. The
   dismiss button is already absolutely positioned; re-declaring `position`
   here would drop it out of its corner and into the flow. */
.tk-key-show {
  position: relative;
}

.tk-key-show::after,
.tk-key-hide::after {
  content: "";
  position: absolute;
  top: 50%;
  left: 50%;
  min-width: 44px;
  min-height: 44px;
  width: 100%;
  height: 100%;
  transform: translate(-50%, -50%);
}

.tk-hours {
  position: relative;
  display: block;
  height: 14px;
  margin-top: 4px;
}

.tk-hours i {
  position: absolute;
  transform: translateX(-50%);
  font-style: normal;
  font-size: 10px;
  font-weight: 500;
  letter-spacing: .04em;
  color: var(--ink-faint);
  font-variant-numeric: tabular-nums;
}

.tk-hours i:first-child { transform: none; }
.tk-hours i:last-child { transform: translateX(-100%); }

.tk-key-note {
  display: block;
  font-size: 12px;
  line-height: 1.4;
  color: var(--ink-faint);
}

.tk-key-now { color: var(--accent-ink); font-weight: 500; }

/* The mark has to look like a marker, not a gap in the bar. */
.tk-now {
  position: absolute;
  top: -2px;
  bottom: -2px;
  width: 2px;
  border-radius: 1px;
  background: var(--accent);
}

/* ------------------------------------------------------------- the day track

   The signature device: a 4px slice of the day, 06:00 to midnight, shared by
   alerts, films and tonight's events. Hairlines at noon and 18:00 give the eye
   something to measure against, so it is an instrument rather than a bar.
   Built from the same rules and squares the rest of the page is made of. */
.tk {
  position: relative;
  display: block;
  height: 4px;
  margin-top: 9px;
  border-radius: 2px;
  background: var(--line);
  overflow: hidden;
}

.tk-noon,
.tk-eve {
  position: absolute;
  top: 0;
  bottom: 0;
  width: 1px;
  background: var(--paper);
  opacity: .9;
}

.tk-noon { left: 33.33%; }
.tk-eve { left: 66.67%; }

/* The window a cut or a closure occupies. */
.tk-span {
  position: absolute;
  top: 0;
  bottom: 0;
  border-radius: 2px;
  background: var(--ink-soft);
  /* A window ending in twenty minutes is barely a percent of the day. Sub-pixel
     is the same as absent, so every real span keeps a readable minimum. */
  min-width: 5px;
}

/* A screening, or an event's start. */
.tk-tick {
  position: absolute;
  top: 0;
  bottom: 0;
  width: 2px;
  border-radius: 1px;
  background: var(--ink);
}

/* Under a liked row the track stays legible against the tinted ground. */
.swipe.liked .tk { background: color-mix(in srgb, var(--ink) 12%, transparent); }

/* One square per day of a run — length is read at a glance, without a date
   the eye has to subtract from today's. */
.run {
  display: flex;
  gap: 3px;
  margin-top: 7px;
}

.run i {
  width: 4px;
  height: 4px;
  border-radius: 1px;
  background: var(--ink-faint);
}

/* A run longer than the row can show fades out rather than pretending the
   twelfth square is the last day. */
.run.more i:nth-last-child(3) { opacity: .55; }
.run.more i:nth-last-child(2) { opacity: .35; }
.run.more i:last-child { opacity: .18; }

/* Status: a chevron normally, a tick once kept. */
.mark {
  display: grid;
  place-items: center;
  width: 28px;
  height: 28px;
  border-radius: 50%;
  border: 1px solid var(--line);
  color: var(--ink-faint);
}

.mark svg {
  width: 14px;
  height: 14px;
  fill: none;
  stroke: currentColor;
  stroke-width: 1.8;
  stroke-linecap: round;
  stroke-linejoin: round;
}

.mark.keep {
  border-color: var(--ok);
  background: transparent;
  color: var(--ok-ink);
}

/* 11px uppercase with wide tracking looked neat and read poorly at arm's
   length. 12px with tighter tracking is legible without shouting. */
.meta {
  margin: 0;
  font-size: 12px;
  font-weight: 500;
  letter-spacing: .06em;
  text-transform: uppercase;
  color: var(--ink-faint);
}


/* Source identity: vendored favicon or a monogram, plus a country flag on
   foreign stories. */
.badges {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  margin-right: 8px;
  vertical-align: -3px;
}

.badge-icon {
  width: 16px;
  height: 16px;
  border-radius: 2px;
  object-fit: contain;
}

.badge-mono {
  display: grid;
  place-items: center;
  width: 16px;
  height: 16px;
  border-radius: 2px;
  background: var(--chip-rail);
  color: var(--ink-soft);
  font-size: 10px;
  font-weight: 600;
  letter-spacing: 0;
}

.badge-flag { font-size: 13px; line-height: 1; }

.summary {
  margin: 10px 0 0;
  font-size: 14px;
  line-height: 1.6;
  color: var(--ink-soft);
}

.daybreak {
  display: flex;
  align-items: baseline;
  gap: 10px;
  margin: 28px 0 12px;
  font-size: 14px;
  color: var(--ink);
}

.daybreak:first-child { margin-top: 4px; }

.daybreak span {
  font-size: 10px;
  letter-spacing: .16em;
  color: var(--ink-faint);
}

.event-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  margin-bottom: 10px;
}

.tag {
  font-size: 10px;
  font-weight: 500;
  letter-spacing: .14em;
  text-transform: uppercase;
  color: var(--ink-soft);
}

.price { font-size: 12px; color: var(--ink-faint); }

.empty {
  margin: 26px 0;
  font-size: 14px;
  color: var(--ink-faint);
}

/* ----------------------------------------------------------- story overlay */

.scrim {
  position: fixed;
  inset: 0;
  background: var(--scrim);        /* flat: e-paper cannot blur */
  animation: fade .14s ease;
  z-index: 10;
}

.sheet {
  position: fixed;
  z-index: 11;
  left: 0;
  right: 0;
  bottom: 0;
  max-height: 82vh;
  overflow-y: auto;
  background: var(--card);
  border-radius: 3px 3px 0 0;
  padding: 32px 20px calc(32px + env(safe-area-inset-bottom));
  border-top: 1px solid var(--line);
  animation: rise .14s ease;
}

.meta.under { margin-top: 10px; }

.sheet h2 {
  margin: 0;
  font-size: calc(22px * var(--read));
  font-weight: 400;
  line-height: 1.3;
  letter-spacing: -.01em;
}

.story-body {
  margin: 16px 0 0;
  font-size: calc(15px * var(--read));
  line-height: 1.65;
  color: var(--ink-soft);
}

/* A notice that reaches the reader's own street, named so a wrong match is
   visible rather than silently reordering the list. Quiet on purpose: this
   sorts the list, it does not need to shout over the titles. */
.mine {
  display: inline-block;
  margin-right: 8px;
  padding: 1px 7px;
  border: 1px solid var(--line);
  border-radius: 2px;
  /* Deliberately not the success green: this marks relevance, not good news,
     and a green pill beside a road closure reads as reassurance. Neutral also
     keeps the row calm, which is what a disruption list needs. */
  background: transparent;
  font-size: 10px;
  font-weight: 500;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--ink-soft);
  vertical-align: 1px;
}

/* Where the calendar turns over. A week gets air, a month gets a name — a long
   run of cards otherwise reads as one column with no landmarks, and "18" three
   screens down could be any month. */
.span-break { height: 18px; }

.span-break.month {
  display: flex;
  align-items: center;
  height: auto;
  margin: 26px 0 6px;
  font-size: 11px;
  font-weight: 500;
  letter-spacing: .1em;
  text-transform: uppercase;
  color: var(--ink-faint);
}

.span-break.month::after {
  content: "";
  flex: 1;
  height: 1px;
  margin-left: 12px;
  background: var(--line);
}

/* The saved room is the one place two kinds of thing share a screen, so each
   gets a heading. Everywhere else the tab names the room and a heading would
   only repeat it. */
.saved-head {
  margin: 26px 0 2px;
  font-size: 11px;
  font-weight: 500;
  letter-spacing: .1em;
  text-transform: uppercase;
  color: var(--ink-faint);
}

.saved-head:first-child { margin-top: 14px; }

/* A saved film that has left both cinemas. It stays on the list and says so:
   silently dropping something the reader put aside is worse than a dead row. */
.row-gone { color: var(--ink-faint); font-style: italic; }

.near-note {
  margin: 10px 0 0;
  font-size: 12px;
  color: var(--ink-faint);
}

/* What the reader's own filters removed, stated under the list they thinned.
   Filtering at render time is silent by design, which is exactly its danger:
   a badly chosen word quietly guts a section and there is nothing to notice.
   So the count is always visible, and always one tap from being undone. */
/* The day's instruments, as a bar across the top of the document.

   Inverted, in the dock's own colours, so the page is bracketed: readings at
   the top, navigation at the bottom, paper and cards between them. A row of
   marks is read faster than a sentence of words joined by middots, and the
   glyph is what makes a value findable -- you look for the droplet, not for
   the third item.

   This is not a weather app and the bar must never look like the start of one:
   no colour sky, no gradient, no emoji. Same stroke weight as every other
   glyph on the page.

   Full-bleed by cancelling the body's gutter rather than by leaving the flow,
   so it spans the app's column exactly and needs no second layout system. */
/* Seven readings need about 430px to sit in a row, which is wider than every
   phone this is built for. They used to wrap onto a second line, doubling the
   height of the band above the day's own heading. Now the band stays one line
   deep and the readings travel through it. */
.wx {
  display: flex;
  align-items: center;
  flex-wrap: nowrap;
  justify-content: space-between;
  gap: 2px 10px;
  padding: calc(9px + env(safe-area-inset-top)) 16px 9px;
  color: var(--dock-dim);
  font-size: 13px;
  font-variant-numeric: tabular-nums;
  overflow-x: auto;
  scrollbar-width: none;
  -webkit-overflow-scrolling: touch;
  /* So a swipe along the band never becomes the browser's back gesture. */
  overscroll-behavior-x: contain;
}

.wx::-webkit-scrollbar { display: none; }

/* Nothing may shrink: a reading squeezed to "24 km…" is worse than one you
   have to travel to. */
.wx > * { flex: 0 0 auto; }

/* Only when there is something past the edge. The band is one of the two
   places the page is allowed to fade rather than stop (see the dock), and it
   is the same fade for the same reason: ink running out at an edge says the
   content continues, where a hard cut says it ended. The left side only
   appears once there is something behind you.

   The opaque stop is `--dock` rather than the `#000` a mask is usually
   written with. A mask reads only the alpha channel, so any fully opaque
   colour does the same work -- and naming the band's own colour keeps the
   palette honest, says which surface is fading, and matches how the dock's
   ramp is written one screen down. */
.wx.roams {
  mask-image: linear-gradient(to right, transparent 0, var(--dock) 16px,
              var(--dock) calc(100% - 22px), transparent 100%);
}

.wx.roams.behind {
  mask-image: linear-gradient(to right, transparent 0, var(--dock) 22px,
              var(--dock) calc(100% - 22px), transparent 100%);
}

.wx.roams.ahead-none {
  mask-image: linear-gradient(to right, transparent 0, var(--dock) 22px,
              var(--dock) 100%);
}

.wx-item {
  display: inline-flex;
  align-items: center;
  gap: 5px;
  white-space: nowrap;
}

.wx-icon {
  width: 15px;
  height: 15px;
  flex: 0 0 auto;
  fill: none;
  stroke: currentColor;
  stroke-width: 1.5;
  stroke-linecap: round;
  stroke-linejoin: round;
}

/* The temperature is the reading people come for: the bar's full ink and a
   slightly larger mark, with weight rather than size separating it from a
   reading that has merely gone bright. */
.wx-lead { color: var(--dock-ink); }
.wx-lead .wx-icon { width: 17px; height: 17px; }
.wx-now { font-weight: 600; }

/* Two levels of concern, not five, and on a dark bar they are levels of
   brightness rather than of colour: the accent measures 4.43 against this
   ground, just under the 4.5 this size needs, so it draws the ring and never
   the words.

   Worth noticing goes to full ink against its dim neighbours; worth changing
   plans over gets the ring as well. */
.wx-item.wet,
.wx-item.warn { color: var(--dock-ink); }

.wx-item.bad {
  padding: 1px 8px;
  border: 1px solid var(--accent);
  border-radius: 2px;
  color: var(--dock-ink);
}

.wx-item.bad .wx-icon { stroke-width: 1.7; }

/* Once the band scrolls, space-between has nothing to distribute and the gap
   is what separates the readings, so give it a little more room. */
.wx.roams { justify-content: flex-start; gap: 2px 14px; }

/* Rain at the hour an outdoor plan starts, on the row itself. Inline with the
   detail line rather than a badge floated away from it: it qualifies the
   event's time, so it belongs beside the time. */
.wx-warn {
  margin-left: 6px;
  padding: 1px 6px;
  border-radius: 2px;
  background: var(--chip-rail);
  color: var(--accent-ink);
  font-size: 11px;
  white-space: nowrap;
}

/* Official met warnings, above everything else in the room. A cod
   portocaliu outranks a closed lane.

   The code is said in words, not shown as a hue: the public knows "cod galben"
   and "cod roșu" as terms, so the word carries the colour and the page keeps
   its one accent. The accent marks the two upper codes -- the ones that
   change plans -- on the code's ring and the kind's glyph, never as filled
   text, for the same contrast reason as everywhere else on this page.

   No coloured edge down the side. A galben card would wear a grey one that
   says nothing, and a portocaliu card would say the same thing three times;
   the ring already carries it, and the stripe is the one tell every generated
   alert card shares. */
.warns {
  display: grid;
  gap: 10px;
  margin: 14px 0 0;
}

.warn {
  padding: 12px 14px;
  border: 1px solid var(--line);
  border-radius: 2px;
  background: var(--card);
}

.warn-top {
  display: flex;
  align-items: center;
  gap: 8px;
  margin: 0;
  font-size: 12px;
  color: var(--ink-soft);
}

.warn-top .kind {
  width: 16px;
  height: 16px;
  margin: 0;
  color: var(--ink-soft);
}

.warn.bad .warn-top .kind { color: var(--accent-ink); }

.warn-code {
  padding: 1px 8px;
  border: 1px solid var(--line);
  border-radius: 2px;
  font-weight: 600;
  letter-spacing: .02em;
  text-transform: uppercase;
  font-size: 11px;
  color: var(--ink);
}

.warn.bad .warn-code {
  border-color: var(--accent);
  color: var(--accent-ink);
}

.warn-kind { font-weight: 500; }

.warn-text {
  margin: 8px 0 0;
  font-size: calc(14px * var(--read));
  line-height: 1.5;
  color: var(--ink);
  text-wrap: pretty;
}

.warn-when {
  margin: 8px 0 0;
  font-size: 12px;
  color: var(--ink-faint);
  font-variant-numeric: tabular-nums;
}

.warn-when a {
  color: inherit;
  text-decoration: underline;
  text-decoration-color: var(--line);
  text-underline-offset: 2px;
}

/* Air quality, shown only when it is bad, so it is allowed to look like a
   warning rather than a readout. */
.air {
  display: flex;
  align-items: center;
  gap: 9px;
  margin: 12px 0 0;
  padding: 10px 12px;
  border: 1px solid var(--line);
  border-radius: 2px;
  font-size: 13px;
  color: var(--ink-soft);
}

.air-value {
  flex: 0 0 auto;
  min-width: 30px;
  padding: 2px 7px;
  border-radius: 2px;
  background: var(--chip-rail);
  color: var(--ink);
  font-size: 12px;
  font-weight: 600;
  text-align: center;
}

.air.warn .air-value { background: var(--accent); color: #fff; }
.air.bad { border-color: var(--accent); }
.air.bad .air-value { background: var(--accent-ink); color: #fff; }

/* The category control and the money question, on one line. Separate controls
   because they answer different questions: what kind of thing, and what it
   costs. */
/* No side margin: the body already holds the 16px gutter, and the control has
   always started at it. The row inherits the margin-top the control used to
   carry, so nothing above it moves. */
.filters-row {
  display: flex;
  align-items: center;
  gap: 8px;
  margin-top: 14px;
}

.filters-row .filters { margin: 0; flex: 0 1 auto; min-width: 0; }

.only-free {
  flex: 0 0 auto;
  height: 44px;
  padding: 0 14px;
  border: 1px solid var(--line);
  border-radius: 2px;
  background: var(--card);
  color: var(--ink-soft);
  font: inherit;
  font-size: 13px;
  cursor: pointer;
}

.only-free.on {
  border-color: transparent;
  background: var(--ink);
  color: var(--paper);
}

.filter-note {
  display: flex;
  align-items: baseline;
  flex-wrap: wrap;
  gap: 8px;
  margin: 12px 0 0;
  font-size: 12px;
  color: var(--ink-faint);
}

.filter-note button {
  padding: 0;
  border: 0;
  background: none;
  font: inherit;
  color: var(--accent);
  text-decoration: underline;
  text-underline-offset: 2px;
  cursor: pointer;
}

/* The one prompt a blank device gets, on the screen where the answer pays off
   in the same session. Card-shaped rather than a banner: it is an offer, and
   an offer should sit in the flow, not clamp itself to the top of it. */
.tip {
  position: relative;
  margin: 14px 0 0;
  padding: 14px 44px 14px 16px;
  border: 1px solid var(--line);
  border-radius: 2px;
  background: var(--card);
}

.tip p {
  margin: 0;
  font-size: 13px;
  line-height: 1.5;
  color: var(--ink-soft);
}

.tip-go {
  margin: 10px 0 0;
  padding: 0;
  border: 0;
  background: none;
  font: inherit;
  font-size: 13px;
  font-weight: 600;
  color: var(--accent);
  cursor: pointer;
}

/* 44px of tap area from a 20px mark, without the mark growing to match. */
.tip-x {
  position: absolute;
  top: 4px;
  right: 4px;
  display: flex;
  align-items: center;
  justify-content: center;
  width: 40px;
  height: 40px;
  padding: 0;
  border: 0;
  background: none;
  color: var(--ink-faint);
  cursor: pointer;
}

.tip-x svg {
  width: 15px;
  height: 15px;
  fill: none;
  stroke: currentColor;
  stroke-width: 1.6;
  stroke-linecap: round;
}

/* Destructive, so it is the quietest control in the sheet rather than the
   loudest: nobody should reach it by accident, and anyone who wants it will
   look for it. */
.forget {
  display: block;
  width: 100%;
  margin: 22px 0 0;
  padding: 12px 0;
  border: 0;
  background: none;
  font: inherit;
  font-size: 13px;
  color: var(--ink-faint);
  text-decoration: underline;
  text-underline-offset: 3px;
  cursor: pointer;
}

.forget:active { color: var(--accent); }

/* The transfer code. White plate under it whatever the theme: a QR is read by
   a camera, and inverting it in night mode halves the number of scanners that
   will take it. */
/* As wide as the panel allows: a code is the one thing on this page that
   gets bigger with no downside, and every pixel is a module a camera can
   resolve. It is also a link to the full-screen view. */
.qr {
  display: block;
  margin: 12px 0 0;
  padding: 0 14px;
  color: inherit;
}

/* The code is an <img> of the SVG, with its own width and height: a replaced
   element renders the same way in every engine inside any container, where an
   inline <svg> in a <details> once did not paint on a phone. The paper plate is
   part of the image; the page adds only the corner. */
.qr img {
  display: block;
  width: 100%;
  height: auto;
  aspect-ratio: 1 / 1;
  border-radius: 2px;
}

/* ------------------------------------------------------ the transfer page
   The code alone, on paper, as large as the screen allows: the smaller of
   the viewport's width and most of its height, so it never scrolls and never
   clips. Nothing else on the page competes for the light. */
.transfer-full {
  display: flex;
  flex-direction: column;
  min-height: 100dvh;
  padding: 0 16px calc(24px + env(safe-area-inset-bottom));
}

.transfer-stage {
  flex: 1 1 auto;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 18px;
}

.transfer-code {
  display: block;
  width: min(100%, 72vh);
  height: auto;
  aspect-ratio: 1 / 1;
  border-radius: 2px;
}

.transfer-note,
.transfer-empty {
  max-width: 34ch;
  margin: 0;
  text-align: center;
  font-family: var(--sans);
  font-size: 14px;
  line-height: 1.5;
  color: var(--ink-faint);
}

.transfer-full .transfer-link {
  width: min(100%, 520px);
  margin: 0;
}

.transfer-close {
  position: fixed;
  top: calc(12px + env(safe-area-inset-top));
  right: 12px;
  display: grid;
  place-items: center;
  width: 44px;
  height: 44px;
  border-radius: 50%;
  border: 1px solid var(--line);
  background: var(--card);
  color: var(--ink-soft);
}

.transfer-close svg {
  width: 18px;
  height: 18px;
  fill: none;
  stroke: currentColor;
  stroke-width: 1.6;
  stroke-linecap: round;
}

.qr-caption {
  margin: 8px 14px 0;
  font-variant-numeric: tabular-nums;
}

/* Sized and shaped like the textarea a field opens onto, because that is what
   it is: the last row inside a settings panel. Not monospaced -- a transfer
   link is scanned or copied whole, never read character by character, and a
   second typeface is a large thing to spend on a string nobody reads. */
.transfer-link {
  display: block;
  width: calc(100% - 28px);
  margin: 10px 14px 14px;
  padding: 10px 14px;
  border: 1px solid var(--line);
  border-radius: 2px;
  background: var(--paper);
  color: var(--ink-faint);
  font: inherit;
  font-size: 12px;
  text-overflow: ellipsis;
}

/* The practical caveat, after the facts rather than mixed into the description:
   quieter than the synopsis because it qualifies rather than explains. */
.sheet-note {
  margin: 12px 0 0;
  font-size: 13px;
  line-height: 1.55;
  color: var(--ink-faint);
}

.detail-rows {
  margin: 20px 0 0;
  border-top: 1px solid var(--line);
}

.detail-row {
  display: flex;
  gap: 16px;
  padding: 12px 0;
  border-bottom: 1px solid var(--line);
}

.detail-row dt {
  flex: 0 0 72px;
  font-size: 11px;
  font-weight: 500;
  letter-spacing: .1em;
  text-transform: uppercase;
  color: var(--ink-faint);
  padding-top: 3px;
}

.detail-row dd {
  margin: 0;
  font-size: 14px;
  color: var(--ink);
}

.stale {
  margin: 16px 0 0;
  padding: 12px 14px;
  border-radius: 2px;
  background: transparent;         /* no soft tints on e-paper: a rule says it */
  border: 1px solid var(--accent);
  font-size: 13px;
  line-height: 1.5;
  color: var(--accent-ink);
}

/* Rows by construction: put-aside actions, the calendar routes, the way out
   to the source. Wrapping used to decide the grouping by whatever fit, which
   changed with the length of the title. Within a row, controls share a
   height, so a primary and its outlined sibling read as one group. */
.sheet-actions {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 12px;
  margin-top: 24px;
}

.action-row {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 10px;
}

.sheet-actions .story-source { margin-top: 0; }

.sheet-action {
  font-size: 14px;
  color: var(--ink);
  text-decoration: none;
  border-bottom: 1px solid var(--line);
  padding-bottom: 2px;
}

/* The calendar link is the action people came for, so it looks like a button
   rather than another line of text. */
.sheet-action.primary {
  display: inline-flex;
  align-items: center;
  min-height: 46px;
  padding: 0 18px;
  border: 0;
  border-radius: 2px;
  background: var(--ink);
  color: var(--paper);
  font-size: 14px;
}



/* Putting an event aside. An outlined button beside the solid calendar one:
   saving is the lighter commitment of the two, and the filled bookmark carries
   the state once it is made. */
/* Save and Send are the same kind of control -- a bordered pill with a
   stroked glyph -- so they share the rule rather than drifting apart. */
.sheet-action.keep,
.sheet-action.share,
.sheet-action.quiet {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  min-height: 46px;
  padding: 0 16px;
  border: 1px solid var(--line);
  border-bottom-color: var(--line);
  border-radius: 2px;
  background: transparent;
  font: inherit;
  font-size: 14px;
  color: var(--ink);
  cursor: pointer;
}

.sheet-action.keep svg,
.sheet-action.share svg,
.sheet-action.quiet svg {
  width: 17px;
  height: 17px;
  fill: none;
  stroke: currentColor;
  stroke-width: 1.5;
  stroke-linejoin: round;
}

.sheet-action.keep.on {
  border-color: var(--ink);
}

.sheet-action.keep.on svg { fill: currentColor; }

.sheet-actions .story-source { margin-top: 0; }

.story-source {
  display: inline-block;
  margin-top: 24px;
  font-size: 14px;
  color: var(--action);
  text-decoration: underline;
  text-decoration-color: var(--line);
  text-decoration-thickness: 1px;
  text-underline-offset: 4px;
}

.story-source:hover { text-decoration-color: var(--ink-faint); }

/* A link that leads to a listing rather than the event's own page. It
   still goes -- it is where you would look -- but it does not get the
   weight of a link that lands on the thing you tapped. */
.story-source.quiet { color: var(--ink-faint); }

/* Rating reuses the chip vocabulary — same pill, same fill-on-active — so it
   reads as part of the page rather than a widget bolted onto it. */
/* A full-width block on the sheet's own left edge — same rhythm as the story
   text above it, rather than two small controls floated off to one side. */
.votes {
  margin-top: 32px;
  padding-top: 24px;
  border-top: 1px solid var(--line);
}

.votes-label {
  display: block;
  margin-bottom: 14px;
  font-size: 11px;
  font-weight: 500;
  letter-spacing: .1em;
  text-transform: uppercase;
  color: var(--ink-faint);
  transition: color .18s ease;
}

.vote-pair {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 10px;
}

.vote {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 10px;
  min-height: 54px;
  padding: 0 20px;
  border: 1px solid var(--line);
  border-radius: 2px;
  background: transparent;
  font: inherit;
  font-size: 16px;
  color: var(--ink-soft);
  cursor: pointer;
  transition: background .18s ease, color .18s ease, border-color .18s ease;
}

.vote-icon {
  width: 18px;
  height: 18px;
  fill: none;
  stroke: currentColor;
  stroke-width: 1.6;
  stroke-linecap: round;
  stroke-linejoin: round;
}

.vote[data-down] .vote-icon { transform: rotate(180deg); }

.vote.on {
  background: var(--ink);
  border-color: var(--ink);
  color: var(--paper);
}

.vote.on[data-down] {
  background: transparent;
  border-color: var(--accent);
  color: var(--accent-ink);
}


.sheet-close {
  position: absolute;
  top: 14px;
  right: 14px;
  display: grid;
  place-items: center;
  width: 40px;
  height: 40px;
  border: 0;
  border-radius: 50%;
  background: var(--chip-rail);
  color: var(--ink-soft);
  cursor: pointer;
}

.sheet-close svg {
  width: 16px;
  height: 16px;
  fill: none;
  stroke: currentColor;
  stroke-width: 1.6;
  stroke-linecap: round;
}

.sheet-close:hover { color: var(--ink); }

@keyframes rise { from { opacity: 0; } }
@keyframes fade { from { opacity: 0; } }

/* On wider screens the sheet becomes a centred card. */
@media (min-width: 640px) {
  .sheet {
    left: 50%;
    bottom: auto;
    top: 50%;
    transform: translate(-50%, -50%);
    width: min(520px, calc(100vw - 48px));
    border-radius: var(--radius);
    animation: fade .18s ease;
  }
}

/* ------------------------------------------------------------------ footer */

.foot {
  /* Clears the bottom band. Without this the credits read from behind it. */
  margin-top: 32px;
  padding-top: 20px;
  border-top: 1px solid var(--line);
  font-size: 11px;
  letter-spacing: .08em;
  text-transform: uppercase;
  color: var(--ink-faint);
}

/* --------------------------------------------------------------- htmx feel */

/* Work in progress has to be visible: a swap used to blank its target, which
   reads as an empty section rather than as loading. */
.htmx-request.list,
.htmx-request .list { position: relative; }

.htmx-request.list::after,
.htmx-request .list::after {
  content: "";
  position: absolute;
  inset: 0;
  border-radius: var(--card-radius);
  background: linear-gradient(100deg,
    transparent 20%,
    color-mix(in srgb, var(--ink) 5%, transparent) 42%,
    transparent 64%) var(--paper);
  background-size: 220% 100%;
  animation: sweep .9s ease-in-out infinite alternate;
}

@keyframes sweep { to { opacity: .55; } }

/* Controls say they are working rather than looking inert. */
.htmx-request.tab,
.htmx-request.chip,
.htmx-request.more,
.htmx-request.dock-item { opacity: .55; }

.htmx-request.save { color: transparent; position: relative; }

.htmx-request.save::after {
  content: "";
  position: absolute;
  inset: 0;
  margin: auto;
  width: 18px;
  height: 18px;
  border: 2px solid color-mix(in srgb, var(--paper) 45%, transparent);
  border-top-color: var(--paper);
  border-radius: 50%;
  animation: spin .7s linear infinite;
}

@keyframes spin { to { transform: rotate(360deg); } }

/* Nothing is disabled today, but a control that becomes so must look it. */
:disabled,
[aria-disabled="true"] {
  opacity: .45;
  cursor: not-allowed;
}

@media (prefers-reduced-motion: reduce) {
  .htmx-request.list::after,
  .htmx-request .list::after,
  .htmx-request.save::after { animation: none; }
}
/* Every swapped region fades the same way; two of the five used to. */
#news-panel, #events-panel, #alerts-panel, #movies-panel, #saved-panel,
#wx-slot { transition: opacity .12s ease; }

/* The band's ink lives on the slot, not on the scrolling row: the row is
   masked at its edges so the readings fade out, and a mask takes the
   background with it -- the ends of the band went pale. Here the ground stays
   solid and only the readings fade. Empty when the forecast fails, so a
   missing band is still no band.

   Its own rule, deliberately. The line above is a shared selector list, and
   the first version of this was written into the end of it, which painted
   every content panel the colour of the band. */
#wx-slot {
  margin-inline: -16px;
  background: var(--dock);
}

/* One press, one duration. Controls that already animate their own transform
   (tabs, dock items, the swipe) are left to it and get a tint instead. */
.sheet-action, .save, .vote, .only-free, .tip-go, .filters-clear, .undo,
.nextup, .tk-key-show, .tk-key-hide, .sheet-close, .settings {
  transition-property: transform, background-color, color, border-color;
  transition-duration: .12s, .18s, .18s, .18s;
  transition-timing-function: ease;
}

/* A press inverts, instantly, and fades back: the device acknowledging the
   finger the only way e-paper can. Rows and the next-up block take the darker
   wash instead, because their contents would not survive a full inversion. */
.sheet-action:active, .save:active, .vote:active, .only-free:active,
.tip-go:active, .filters-clear:active, .undo:active, .tk-key-show:active,
.chip:active, .tab:active:not(.on) {
  transition: none;
  background: var(--ink);
  border-color: var(--ink);
  color: var(--paper);
}
.tk-key-hide:active, .sheet-close:active, .settings:active {
  transition: none;
  background: var(--ink);
  color: var(--paper);
}
.nextup:active,
.swipe:not(.dragging) > .row:active { transition: none; background: var(--chip-rail); }

@media (prefers-reduced-motion: reduce) {
  * { transition: none !important; }
}

/* Numerals that are compared are tabular. */
.when-major, .tab-count, .row-times, .showtime, .wx, .warn-when, .tk-hours {
  font-variant-numeric: tabular-nums;
}

/* ------------------------------------------------------- display settings
   The three controls an e-reader has: how big the words are, which light it
   is, and how warm. They wear the app's one tab control, so the choice reads
   the way every other choice does -- the selected segment inverted to ink. */
.display-set {
  margin-top: 10px;
  padding: 12px 14px 14px;
  border: 1px solid var(--line);
  border-radius: 2px;
  background: var(--card);
}

.display-set + .display-set { margin-top: 8px; }

.display-set > .setting-label {
  display: block;
  margin-bottom: 8px;
}

/* The panel is already the frame; a bordered rail inside it would be a box
   in a box. */
.display-set .tabs {
  background: transparent;
  border: 0;
  padding: 0;
  gap: 4px;
}

.display-set .tab {
  min-height: 40px;
  font-size: 13px;
}

/* ------------------------------------------------------------- route map
   The affected main corridors, drawn on a grey city above the Acum list. A
   panel like any other -- hairline, 6px corner, 14px from its neighbours --
   so it reads as part of the page, not as an embed dropped into it. The only
   colour on it is the accent, because the only thing drawn is what is wrong:
   solid where a road is closed, dashed where it is only restricted. */
.route-map-wrap { position: relative; margin: 14px 0 0; }

.route-map {
  height: 210px;                /* until map.js sizes it to the roads */
  border: 1px solid var(--line);
  border-radius: 2px;
  overflow: hidden;
  background: var(--card);
  cursor: pointer;
  /* Leaflet sets its own z-indexes in the hundreds; keep them inside here so
     the sheet and the dock still sit above the map. */
  isolation: isolate;
}

/* The expand mark: the preview takes no gestures, so it has to say that it
   opens. 44px, in the corner the attribution does not use. */
.route-open {
  position: absolute;
  top: 8px;
  right: 8px;
  z-index: 2;
  display: grid;
  place-items: center;
  width: 44px;
  height: 44px;
  padding: 0;
  border: 1px solid var(--line);
  border-radius: 2px;
  background: var(--card);
  color: var(--ink);
  cursor: pointer;
}

.route-open svg, .route-close svg {
  width: 18px;
  height: 18px;
  fill: none;
  stroke: currentColor;
  stroke-width: 1.6;
  stroke-linecap: round;
  stroke-linejoin: round;
}

.route-open:active { background: var(--ink); color: var(--paper); transition: none; }

.route-key {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 6px;
  margin-top: 8px;
  font-size: 13px;
  color: var(--ink-faint);
}

.route-swatch {
  display: inline-block;
  flex: 0 0 auto;
  width: 22px;
  border-top: 3px solid var(--accent);
}

.route-swatch.restricted { border-top-style: dashed; }
.route-key .route-swatch.restricted { margin-left: 10px; }
.route-hint { margin-left: auto; }

/* The road's own name, laid on the road: a small paper tag rather than a
   floating tooltip, so it reads as part of the map. */
.leaflet-tooltip.route-label {
  padding: 2px 7px;
  font: 600 12px/1.3 var(--sans);
  color: var(--ink);
  background: var(--card);
  border: 1px solid var(--line);
  border-radius: 2px;
  box-shadow: none;
  white-space: nowrap;
  transition: opacity .15s ease;
}

.leaflet-tooltip.route-label::before { display: none; }
.leaflet-tooltip.route-label.on { border-color: var(--accent); }
.leaflet-tooltip.route-label.dim { opacity: .45; }
.leaflet-tooltip.route-label.crowded { visibility: hidden; }

/* ---------------------------------------------------- full-screen map
   The whole screen belongs to the map: drag and pinch, nothing behind it
   scrolling. The bar on top says what this is and closes it; the strip at
   the bottom is the affected routes -- tap one to look, tap again to read. */
html.route-full-open { overflow: hidden; }

.route-full {
  position: fixed;
  inset: 0;
  z-index: 60;
  display: flex;
  flex-direction: column;
  background: var(--paper);
}

.route-full-top {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  padding: calc(8px + env(safe-area-inset-top)) 12px 8px 16px;
  background: var(--dock);
  color: var(--dock-ink);
}

.route-full-title { margin: 0; font-size: 16px; font-weight: 600; }

.route-close {
  display: grid;
  place-items: center;
  width: 44px;
  height: 44px;
  padding: 0;
  border: 0;
  border-radius: 2px;
  background: transparent;
  color: var(--dock-ink);
  cursor: pointer;
}

.route-close:active { background: var(--dock-ink); color: var(--dock); transition: none; }

.route-full-map { flex: 1 1 auto; min-height: 0; background: var(--card); }

.route-chips {
  display: flex;
  gap: 8px;
  overflow-x: auto;
  scrollbar-width: none;
  overscroll-behavior-x: contain;
  padding: 10px 12px calc(10px + env(safe-area-inset-bottom));
  border-top: 1px solid var(--line);
  background: var(--paper);
}

.route-chips::-webkit-scrollbar { display: none; }

.route-chip {
  display: flex;
  flex: 0 0 auto;
  align-items: center;
  gap: 10px;
  min-height: 52px;
  padding: 6px 14px 6px 12px;
  border: 1px solid var(--line);
  border-radius: 2px;
  background: var(--card);
  color: var(--ink);
  font: inherit;
  text-align: left;
  cursor: pointer;
}

.route-chip-text { display: flex; flex-direction: column; line-height: 1.25; }
.route-chip-name { font-size: 14px; font-weight: 600; white-space: nowrap; }
.route-chip-effect { font-size: 12px; color: var(--ink-faint); white-space: nowrap; }

/* The one highlight: the chosen road's chip inverts, like every selection. */
.route-chip.on { background: var(--ink); border-color: var(--ink); color: var(--paper); }
.route-chip.on .route-chip-effect { color: inherit; opacity: .85; }

/* Leaflet's zoom buttons, redrawn as the app's own controls. */
.route-full .leaflet-bar {
  border: 1px solid var(--line);
  border-radius: 2px;
  box-shadow: none;
  overflow: hidden;
}

.route-full .leaflet-bar a {
  width: 44px;
  height: 44px;
  line-height: 42px;
  font: 400 22px/42px var(--sans);
  color: var(--ink);
  background: var(--card);
  border-bottom-color: var(--line);
}

.route-full .leaflet-bar a:active { background: var(--ink); color: var(--paper); }

.route-full .leaflet-control-attribution {
  font: 10px/1.4 var(--sans);
  background: color-mix(in srgb, var(--card) 85%, transparent);
  color: var(--ink-faint);
}

.route-full .leaflet-control-attribution a { color: inherit; }

/* The tiles arrive in OpenStreetMap's colours. Desaturated and lifted they
   become the paper's grey; at night the same tiles are inverted, so the city is
   pale streets on dark ground instead of a bright rectangle in a dark page.
   Filtered on the tile pane only -- the drawn routes sit in their own pane and
   keep the accent. */
.route-map .leaflet-tile-pane,
.route-full-map .leaflet-tile-pane { filter: grayscale(1) contrast(.85) brightness(1.06); }
:root[data-theme="dark"] .route-map .leaflet-tile-pane,
:root[data-theme="dark"] .route-full-map .leaflet-tile-pane {
  filter: grayscale(1) invert(1) contrast(.8) brightness(.9);
}

.route-map .leaflet-control-attribution {
  font: 10px/1.4 var(--sans);
  background: color-mix(in srgb, var(--card) 85%, transparent);
  color: var(--ink-faint);
}

.route-map .leaflet-control-attribution a { color: inherit; }

/* ------------------------------------------------------------ day verdicts
   The weather band opened: the forecast as things to do. Three across like
   an instrument panel; cautions in full ink, reassurances dimmed so the eye
   lands on what changes the plan. */
.wx[role="button"] { cursor: pointer; }
.wx[role="button"]:focus-visible { outline: 2px solid var(--accent); outline-offset: -2px; }

.day-grid {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: 18px 10px;
  margin: 18px 0 8px;
  padding: 0;
  list-style: none;
}
.day-tile {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 8px;
  text-align: center;
  font-size: 13px;
  line-height: 1.3;
  color: var(--muted, inherit);
  opacity: .72;
}
.day-tile.heads-up { opacity: 1; font-weight: 600; }
.day-tile .wx-icon { width: 30px; height: 30px; stroke-width: 1.3; }

/* ------------------------------------------------------------ the device
   The page as a dedicated e-ink instrument, not an app with an e-ink palette:
   no dark bars. Both bands are the screen itself, printed on paper and closed
   off by a heavy ink rule, the way a device frames its status line. Panels are
   square and outlined in ink; numbers that are read at a glance are set heavy.
   The band and dock tokens follow the page in every light, so night mode is
   the same device with its frontlight off. */
:root {
  --dock: var(--paper) !important;
  --dock-ink: var(--ink) !important;
  --dock-dim: var(--ink-soft) !important;
}

#wx-slot { border-bottom: 2px solid var(--ink); }
.wx { color: var(--ink-soft); font-weight: 500; }
.wx-now { font-weight: 700; font-size: 16px; letter-spacing: -.01em; }

.dock {
  background: var(--paper);
  border-top: 2px solid var(--ink);
  padding-top: 6px;
}

/* Panels are framed, but softly: a line between ink and paper rather than
   ink itself, so a long list reads as a page and not as a grid of boxes. */
.swipe > .row { border-color: color-mix(in srgb, var(--ink) 18%, var(--card)); }
.swipe, .swipe > .row { border-radius: 6px; }
.swipe { border-radius: var(--card-radius); }

h1 { font-weight: 700; letter-spacing: -.02em; }
.tab-count, .when-major, .showtime { font-weight: 700; }

/* The day sheet reads like the device's face: bold verdicts, a rule above. */
.day-grid { border-top: 1.5px solid var(--ink); padding-top: 18px; }
.day-tile .wx-icon { stroke-width: 1.7; }

/* The drawer. Rounded at the top, and its close button sits outside it,
   just above the top-right corner, where it never covers a word and never
   scrolls away. One look for the button in every drawer: a paper disc with
   an ink ring. The drawer itself does not scroll -- swipe.js wraps its
   contents in .sheet-body, which does -- so the button can live outside it. */
.sheet {
  overflow: visible;
  display: flex;
  flex-direction: column;
  padding: 0;
  border-radius: 18px 18px 0 0;
  border-top: 1px solid var(--ink);
}
.sheet-body {
  min-height: 0;
  overflow-y: auto;
  overscroll-behavior: contain;
  padding: 26px 20px calc(32px + env(safe-area-inset-bottom));
  border-radius: inherit;
}
.sheet > .sheet-close {
  position: absolute;
  top: -52px;
  right: 14px;
  margin: 0;
  width: 40px;
  height: 40px;
  border: 1px solid var(--ink);
  border-radius: 50%;
  background: var(--card);
  color: var(--ink);
}

/* ---------------------------------------------------------- loading in
   Content arrives the way an e-ink screen draws it: not a smooth glide but a
   few discrete passes of ink, row by row from the top. `steps()` gives the
   stepped refresh; the stagger draws the list downwards. Runs whenever rows
   are inserted -- first load, a tab switch, a swap -- and never on rows that
   merely stay put. */
@keyframes ink-in {
  0%   { opacity: 0; transform: translateY(6px); }
  100% { opacity: 1; transform: none; }
}
@keyframes ink-flash {
  0%   { opacity: 0; }
  100% { opacity: 1; }
}

.swipe, .nextup, .day-tile, .daybreak, .warn-card, .film-row {
  animation: ink-in .32s steps(4, end) both;
}
.swipe:nth-child(1)  { animation-delay: 0ms; }
.swipe:nth-child(2)  { animation-delay: 40ms; }
.swipe:nth-child(3)  { animation-delay: 80ms; }
.swipe:nth-child(4)  { animation-delay: 120ms; }
.swipe:nth-child(5)  { animation-delay: 160ms; }
.swipe:nth-child(6)  { animation-delay: 200ms; }
.swipe:nth-child(7)  { animation-delay: 240ms; }
.swipe:nth-child(n+8) { animation-delay: 280ms; }
.day-tile:nth-child(3n+2) { animation-delay: 50ms; }
.day-tile:nth-child(3n)   { animation-delay: 100ms; }

/* The drawer's contents settle in after the drawer itself has risen. */
.sheet-body { animation: ink-flash .24s steps(3, end) .08s both; }
.wx { animation: ink-flash .3s steps(3, end) both; }

@media (prefers-reduced-motion: reduce) {
  .swipe, .nextup, .day-tile, .daybreak, .warn-card, .film-row,
  .sheet-body, .wx { animation: none; }
}
