/* ──────────────────────────────────────────────────────────────────────────
   app.css — designed at 360×640, widened for anything larger.

   MOBILE FIRST, LITERALLY. There is no desktop layout in this file. There is a
   phone layout and a max-width that stops it sprawling. Every media query here
   raises a floor rather than rearranging anything, so a change made for the
   phone cannot silently break a layout nobody opened.

   Two things this file is careful about, both of which are structural:

     1. THE TAB BAR AND THE HOME INDICATOR. env(safe-area-inset-bottom) plus
        viewport-fit=cover, or the last tab sits under the gesture bar on every
        notched phone and is untappable. It looks fine in a desktop browser,
        which is exactly why it ships broken.

     2. 44px MINIMUM TAP TARGETS. Not a guideline here — a min-height on every
        control. A 32px button is comfortable with a mouse and a coin toss with
        a thumb.
   ────────────────────────────────────────────────────────────────────────── */

:root {
  /* ── COLOUR — TASK 12.5 ────────────────────────────────────────────────
     THE VIOLET STAYS. It was carrying every job — links, buttons, highlights,
     focus rings, selected states — and one colour doing all of that is why the
     app read flat. Two changes, and the identity is not one of them:

       1. --accent gains saturation. Same hue (267 deg, was 266), 72% -> 100%.
       2. --cta arrives: a warm secondary that means PRESS THIS, so the violet
          can go back to meaning "this is a link" and nothing else.

     EVERY RATIO BELOW WAS MEASURED, NOT EYEBALLED — 4.5:1 for text, 3:1 for
     the boundary of an interactive control (WCAG 1.4.11). docs/acceptance-
     task12.5.txt carries the full table, including the three that FAILED
     before this task and had nothing to do with it. */
  --ink:        #1b1523;
  --ink-soft:   #574a63;
  --ink-faint:  #8b7f96;
  --bg:         #fdfbff;
  --surface:    #ffffff;
  --line:       #e7e0ee;

  /* --line IS DECORATIVE AND --line-strong IS NOT. The old --line was doing
     both jobs, and as an INPUT BORDER it measured 1.27:1 against the surface
     it sits on — a control whose edge you cannot see. Dividers and card edges
     keep the quiet colour; anything that is the boundary of something you can
     type in or press uses this one. */
  --line-strong: #9d8fae;

  --accent:     #5b21b6;
  --accent-ink: #ffffff;
  --cta:        #b45309;
  --cta-ink:    #ffffff;
  --danger:     #a02f3f;
  --ok:         #2f7d55;

  --pad:   16px;
  --round: 14px;
  --tap:   44px;

  --safe-b: env(safe-area-inset-bottom, 0px);
  --safe-t: env(safe-area-inset-top, 0px);

  /* The height of `.bar`, spelled out so anything that has to sit UNDER it can
     say so without a magic number: 10px + safe-t of padding, a 44px minimum
     row, 10px of padding and a 1px border. It is arithmetic about another
     rule, which is fragile, so scripts/screenshots.cjs measures the real
     .bar against the real sticky element rather than trusting it. */
  --bar-h: calc(var(--tap) + 21px + var(--safe-t));
}

@media (prefers-color-scheme: dark) {
  :root {
    --ink:       #f4eefb;
    --ink-soft:  #c3b6d0;
    --ink-faint: #8d8199;
    --bg:        #140f1a;
    --surface:   #1e1726;
    --line:      #322940;
    --line-strong: #726282;

    --accent:    #cba0ff;   /* was #c4a2f0 — same hue, 72% -> 100% saturation */
    --accent-ink:#1b1523;
    --cta:       #f5a25d;
    --cta-ink:   #1b1523;

    /* --danger AND --ok WERE NEVER OVERRIDDEN FOR DARK MODE, and the light
       values were being painted on the dark ground: 2.67:1 and 3.76:1, both
       failing 4.5:1. The danger one colours the REFUSAL role label, so the
       screen least able to afford being hard to read was the one carrying it.
       Found by measuring the palette that was already here, not by this task's
       own changes.

       BOTH ARE PITCHED TO CLEAR THE FLOOR AND STOP. A refusal must not become
       vivid — it is a dashed, transparent block with a muted rose label, and
       raising it to legible is not the same as raising it to loud. */
    --danger:    #d9737f;   /* 6.03:1 — the most restrained value that passes */
    --ok:        #5fb88b;
  }
}

* { box-sizing: border-box; }

/* [hidden] MUST WIN, and without this it does not.
   `[hidden] { display: none }` lives in the UA stylesheet, and ANY author rule
   that sets display beats it regardless of specificity — author styles outrank
   user-agent styles. So `.btn { display: block }` silently un-hides every
   button the code hides.
   Found by looking at 06-seventh-refused.png: "Archive this profile" was on
   screen on an ADD form, where there is nothing to archive. The JS was correct
   and had been correct all along; the stylesheet was quietly overruling it.
   One rule, because the next class that sets display would do it again. */
[hidden] { display: none !important; }

html, body {
  margin: 0;
  padding: 0;
  background: var(--bg);
  color: var(--ink);
  font: 16px/1.5 -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
  -webkit-text-size-adjust: 100%;
}

body { min-height: 100dvh; }

.sr-only {
  position: absolute; width: 1px; height: 1px;
  padding: 0; margin: -1px; overflow: hidden;
  clip: rect(0 0 0 0); white-space: nowrap; border: 0;
}

#root { max-width: 520px; margin: 0 auto; min-height: 100dvh; }

.screen { display: block; }
.screen[hidden] { display: none; }

.pad { padding: var(--pad); padding-bottom: calc(var(--pad) + var(--safe-b)); }

.centre {
  min-height: 100dvh;
  display: flex; flex-direction: column;
  align-items: center; justify-content: center; gap: 8px;
}

/* ── brand ─────────────────────────────────────────────────────────────── */
.mark { font-size: 22px; font-weight: 650; letter-spacing: -0.02em; }
.mark span { color: var(--accent); }
.mark-lg { font-size: 30px; margin: 28px 0 4px; }

.lede { color: var(--ink-soft); margin: 0 0 22px; }
.lede.sm { font-size: 14px; margin-bottom: 14px; }
.muted { color: var(--ink-faint); }
.sm { font-size: 13px; }

/* ── top bar ───────────────────────────────────────────────────────────── */
.bar {
  position: sticky; top: 0; z-index: 5;
  display: flex; align-items: center; gap: 8px;
  padding: calc(10px + var(--safe-t)) 12px 10px;
  background: var(--bg);
  border-bottom: 1px solid var(--line);
  min-height: var(--tap);
}
.bar-title {
  flex: 1; text-align: center;
  font-weight: 600; font-size: 16px;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.bar-spacer { width: 44px; flex: 0 0 44px; }

/* ── segmented control ─────────────────────────────────────────────────── */
.seg {
  display: flex; gap: 4px; padding: 4px;
  background: var(--surface); border: 1px solid var(--line);
  border-radius: var(--round); margin-bottom: 18px;
}
.seg-btn {
  flex: 1; min-height: 38px; border: 0; border-radius: 10px;
  background: transparent; color: var(--ink-soft);
  font: inherit; font-size: 15px; font-weight: 550; cursor: pointer;
}
.seg-btn.is-on { background: var(--accent); color: var(--accent-ink); }

/* ── forms ─────────────────────────────────────────────────────────────── */
.field { display: block; margin-bottom: 16px; }
.field > span {
  display: block; font-size: 13px; font-weight: 600;
  color: var(--ink-soft); margin-bottom: 6px;
}
.field > span em { font-weight: 400; font-style: normal; color: var(--ink-faint); }

input[type=text], input[type=email], input[type=password],
input[type=date], input[type=time], select {
  width: 100%; min-height: var(--tap);
  padding: 10px 12px;
  font: inherit; font-size: 16px;      /* 16px or iOS zooms the page on focus */
  color: var(--ink); background: var(--surface);
  border: 1px solid var(--line-strong); border-radius: 12px;
  appearance: none;
}
input:focus-visible, select:focus-visible, button:focus-visible {
  outline: 2px solid var(--accent); outline-offset: 2px;
}

.check {
  display: flex; align-items: center; gap: 10px;
  margin-top: 10px; min-height: var(--tap);
  font-size: 15px; color: var(--ink-soft);
}
/* accent-color, or the tick is system blue on a purple screen. */
.check input { width: 20px; height: 20px; accent-color: var(--accent); }

.hint { font-size: 13px; color: var(--ink-faint); margin: 8px 0 0; }
.chosen { font-size: 13px; color: var(--ok); margin: 8px 0 0; font-weight: 550; }

.error, .notice {
  font-size: 14px; margin: 12px 0 0; padding: 10px 12px;
  border-radius: 10px; border: 1px solid;
}
.error  { color: var(--danger); border-color: var(--danger); background: color-mix(in srgb, var(--danger) 8%, transparent); }
.notice { color: var(--ok);     border-color: var(--ok);     background: color-mix(in srgb, var(--ok) 8%, transparent); }

/* ── buttons ───────────────────────────────────────────────────────────── */
.btn {
  display: block; width: 100%; min-height: var(--tap);
  margin-top: 18px; padding: 12px 16px;
  font: inherit; font-size: 16px; font-weight: 600;
  border-radius: 12px; border: 1px solid transparent; cursor: pointer;
}
.btn:disabled { opacity: .55; cursor: default; }
/* THE PRIMARY ACTION IS WARM, AND THAT IS THE POINT. It used to be var(--accent)
   — the same violet as every link on the screen — so "this is a link" and
   "press this" were one colour and neither read as itself. */
.btn-primary { background: var(--cta); color: var(--cta-ink); }
.btn-ghost   { background: transparent; color: var(--accent); border-color: var(--line-strong); }
.btn-danger-ghost { background: transparent; color: var(--danger); border-color: var(--line-strong); }

/* THE RESIDENCE CLAIM — Task 12.5.
   It replaced a pre-ticked checkbox. It is a SENTENCE, at body size, naming the
   city: the point is that somebody who has moved reads their old city's name
   and reacts. Set smaller or greyer it would be a hint, and hints are what got
   scrolled past. Not accent-coloured either — this is a statement of fact, not
   a link, and the palette work in this same task exists so those read
   differently. */
.claim { margin: 0 0 4px; font-size: 15px; line-height: 1.5; }
.claim strong { font-weight: 650; }

/* The claim's own action sits directly under it, so `.btn-link`'s default
   top-margin would float it away from the sentence it belongs to. */
#p-here-change { margin-top: 2px; text-align: left; }

.btn-link {
  min-height: var(--tap); padding: 0 4px;
  background: none; border: 0; cursor: pointer;
  color: var(--accent); font: inherit; font-size: 15px;
}

/* A BARE LINK INSIDE A SCREEN — added in Task 9.
   `.doc a` already colours the legal documents, but a screen is not a document
   and nothing covered an <a> in ordinary app copy. Those fell back to the
   browser's default blue, which on this dark ground is close to unreadable and
   is not one of our colours. It affects the sign-up screen's "terms of service
   and privacy policy" line as well as the two new auth landing pages, so it has
   been wrong on an existing screen for some time.
   FOUND BY LOOKING AT 10-confirmed.png. No test in this repo can see a colour,
   which is §0g's whole point about screenshots.
   :not([class]) so that an anchor carrying its own class — .legal-link — goes
   on deciding its own colour instead of being overridden from here. */
.screen a:not([class]) { color: var(--accent); }

/* ── profile cards ─────────────────────────────────────────────────────── */
.cards { display: flex; flex-direction: column; gap: 10px; }
.card {
  display: flex; align-items: center; gap: 12px; width: 100%;
  min-height: 66px; padding: 12px 14px; text-align: left;
  background: var(--surface); border: 1px solid var(--line);
  border-radius: var(--round); cursor: pointer;
  font: inherit; color: var(--ink);
}
.card-initial {
  flex: 0 0 40px; height: 40px; border-radius: 50%;
  display: grid; place-items: center;
  background: var(--accent); color: var(--accent-ink); font-weight: 650;
}
/* The body is a COLUMN, and min-width:0 is what lets it shrink.
   Found by looking at 05-selector-two.png: name and meta were rendering on one
   line — "MeeraJan 15, 1990 · 06:30 · Chennai, Tamil Nad…" — and the meta ran
   straight past the card's right edge. They are spans, so they were inline;
   the ellipsis rules below did nothing because there was nothing to ellipsize
   against. A flex item will not shrink below its content without min-width:0,
   which is the half that stops the overflow. */
.card-body { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: 2px; }
.card-name { display: block; font-weight: 600; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.card-meta { display: block; font-size: 13px; color: var(--ink-faint); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

.empty {
  padding: 26px 16px; text-align: center;
  border: 1px dashed var(--line); border-radius: var(--round);
  color: var(--ink-faint);
}

/* ── place search results ──────────────────────────────────────────────── */
.results {
  list-style: none; margin: 8px 0 0; padding: 0;
  border: 1px solid var(--line); border-radius: 12px;
  background: var(--surface); overflow: hidden;
  max-height: 240px; overflow-y: auto;
}
.results li { border-bottom: 1px solid var(--line); }
.results li:last-child { border-bottom: 0; }
.results button {
  display: block; width: 100%; min-height: var(--tap);
  padding: 10px 12px; text-align: left;
  background: none; border: 0; cursor: pointer; font: inherit; color: var(--ink);
}
.results .rc-name { font-weight: 550; }
.results .rc-meta { font-size: 12px; color: var(--ink-faint); }

/* ── panels + tab bar ──────────────────────────────────────────────────── */
#panels {
  padding: var(--pad);
  /* room for the tab bar AND the home indicator underneath it */
  padding-bottom: calc(var(--tap) + 26px + var(--safe-b));
}
.panel[hidden] { display: none; }

.tabbar {
  position: fixed; left: 0; right: 0; bottom: 0; z-index: 10;
  display: flex;
  max-width: 520px; margin: 0 auto;
  padding-bottom: var(--safe-b);
  background: var(--surface);
  border-top: 1px solid var(--line);
}
.tab {
  flex: 1; min-height: calc(var(--tap) + 8px);
  display: flex; flex-direction: column; align-items: center; justify-content: center;
  gap: 2px; padding: 6px 2px;
  background: none; border: 0; cursor: pointer;
  color: var(--ink-faint); font: inherit; font-size: 11px; font-weight: 550;
}
.tab .tab-dot {
  width: 18px; height: 18px; border-radius: 6px;
  border: 2px solid currentColor;
}
.tab[aria-selected="true"] { color: var(--accent); }
.tab[aria-selected="true"] .tab-dot { background: currentColor; }

/* ── coming soon ───────────────────────────────────────────────────────── */
.soon { padding: 40px 8px; text-align: center; }
.soon h2 { margin: 0 0 8px; font-size: 20px; }
.soon p { color: var(--ink-soft); margin: 0 auto; max-width: 30ch; }
.soon .tag {
  display: inline-block; margin-bottom: 14px;
  padding: 4px 10px; border-radius: 999px;
  background: var(--surface); border: 1px solid var(--line);
  font-size: 12px; font-weight: 600; color: var(--ink-faint);
  text-transform: uppercase; letter-spacing: .06em;
}

/* ── the day card ──────────────────────────────────────────────────────── */
.daycard { display: flex; flex-direction: column; gap: 14px; }
.daycard h2 { margin: 0; font-size: 20px; }
.daysection {
  background: var(--surface); border: 1px solid var(--line);
  border-radius: var(--round); padding: 14px;
}
.daysection h3 {
  margin: 0 0 6px; font-size: 12px; font-weight: 700;
  text-transform: uppercase; letter-spacing: .07em; color: var(--ink-faint);
}
.daysection p { margin: 0; }
.dayaction { border-color: var(--accent); }
.dayaction h3 { color: var(--accent); }

.state {
  padding: 34px 12px; text-align: center; color: var(--ink-soft);
}
.state h2 { font-size: 18px; margin: 0 0 8px; color: var(--ink); }
.state .btn { margin-top: 18px; }

/* ── the wait, which is part of the product now (D4) ───────────────────────
   NOT a spinner over an empty layout. A spinner says "something is happening";
   the copy beside this bar says what is happening and roughly how long it
   takes, which is the difference between waiting and wondering whether it is
   broken.

   An indeterminate bar rather than a percentage, because there is no honest
   percentage to show — the generation is one call whose duration is not known
   in advance, and a fake progress bar is a lie with a shape. */
.prep-bar {
  height: 3px; width: 132px; margin: 0 auto 20px;
  background: var(--line); border-radius: 2px; overflow: hidden;
}
.prep-bar span {
  display: block; height: 100%; width: 40%;
  background: var(--accent); border-radius: 2px;
  animation: prep-slide 1.4s ease-in-out infinite;
}
@keyframes prep-slide {
  0%   { transform: translateX(-110%); }
  100% { transform: translateX(360%); }
}

/* Vestibular disorders are a real accessibility need and a looping animation
   is a real trigger. The bar stays as a static rule so the layout does not
   move, which keeps the state legible without the motion. */
@media (prefers-reduced-motion: reduce) {
  .prep-bar span { animation: none; width: 100%; }
}

@media (min-width: 560px) {
  /* A widened phone, not a different layout. */
  .pad { padding: 24px; }
  #panels { padding: 24px; padding-bottom: calc(var(--tap) + 34px + var(--safe-b)); }
}

/* ── account screen ──────────────────────────────────────────────────────────
   Added in Task 5. The delete control is the only thing in this app that
   destroys data, so it is styled to look like it: filled red, not an outline.
   .btn-danger-ghost already existed for "Archive this profile", which is
   reversible — the two must not look the same. */
.legal-links { display: flex; flex-direction: column; gap: 2px; margin: 4px 0 0; }
.legal-link {
  display: block; padding: 12px 0; color: var(--accent);
  text-decoration: none; font-weight: 550;
  border-bottom: 1px solid var(--line);
}
.legal-link:last-child { border-bottom: 0; }
.sep { border: 0; border-top: 1px solid var(--line); margin: 22px 0; }
.danger-h { font-size: 17px; margin: 0 0 6px; color: var(--danger); }
.danger-panel {
  margin-top: 16px; padding: 14px; border-radius: 12px;
  border: 1px solid var(--danger);
  background: color-mix(in srgb, var(--danger) 6%, transparent);
}
.danger-list { margin: 6px 0 14px; padding-left: 20px; }
.danger-list li { margin-bottom: 4px; }
.btn-danger { background: var(--danger); color: #fff; border-color: var(--danger); }

/* ── standalone legal pages ──────────────────────────────────────────────────
   privacy.html, terms.html and delete-account.html use these. They are plain
   documents with no script: they must render for somebody who has never signed
   in, on a Play reviewer's laptop, with JavaScript disabled. */
.doc { max-width: 640px; margin: 0 auto; padding: var(--pad); padding-bottom: calc(var(--pad) + var(--safe-b)); }
.doc h1 { font-size: 24px; margin: 18px 0 4px; letter-spacing: -0.02em; }
.doc h2 { font-size: 17px; margin: 26px 0 8px; }
.doc h3 { font-size: 15px; margin: 18px 0 6px; }
.doc p, .doc li { color: var(--ink-soft); line-height: 1.55; }
.doc ul, .doc ol { padding-left: 22px; }
.doc li { margin-bottom: 6px; }
.doc table { width: 100%; border-collapse: collapse; margin: 12px 0; font-size: 14px; }
.doc th, .doc td { text-align: left; padding: 8px 10px; border-bottom: 1px solid var(--line); vertical-align: top; }
.doc th { color: var(--ink); font-weight: 600; }
.doc .updated { color: var(--ink-faint); font-size: 13px; margin: 0 0 20px; }
.doc a { color: var(--accent); }
.doc .callout {
  border: 1px solid var(--line); border-left: 3px solid var(--accent);
  border-radius: 8px; padding: 12px 14px; margin: 16px 0;
}
.doc .callout p:last-child { margin-bottom: 0; }
.doc-nav { padding: 14px var(--pad) 0; max-width: 640px; margin: 0 auto; }
.doc-nav a { color: var(--accent); text-decoration: none; font-weight: 550; }
/* Task 72: the legal footer on every policy page, and Account → Credits' test-mode note and legal line. */
.doc-footer { border-top: 1px solid var(--line); margin-top: 24px; font-size: 13px; }
.doc-footer p { color: var(--ink-faint); margin: 6px 0; }
.wallet-test { border-left: 3px solid var(--accent); padding: 6px 10px; font-size: 13px; }
.wallet-legal a { color: var(--accent); }

/* ── the credits ribbon (Task 75, RUNBOOK §0ch) ─────────────────────────────
   Right of every signed-in top bar, before the bar's own right-hand control.
   Phones: the pill and an icon-only "+". From 480 px the button carries words.
   The title gives way (it already ellipsizes); the ribbon never wraps or shrinks.
   No new colours: --cta for the one warm action (the empty wallet's "Get
   credits"), --accent and --line-strong for the rest. */
.bar-title { min-width: 0; }
.credits-ribbon { display: flex; align-items: center; gap: 6px; flex: 0 0 auto; }
.credits-ribbon[hidden] { display: none; }
.credits-pill {
  display: inline-flex; align-items: center; gap: 5px; height: 32px; padding: 0 10px;
  border: 1px solid var(--line-strong); border-radius: 999px; background: var(--surface);
  color: var(--ink); font: inherit; font-size: 14px; font-weight: 650; white-space: nowrap; cursor: pointer;
}
.credits-allowance { cursor: default; font-weight: 550; font-size: 13px; color: var(--ink-soft); }
.credits-coin {
  width: 14px; height: 14px; border-radius: 50%; flex: 0 0 14px;
  background: radial-gradient(circle at 35% 35%, #fde68a, #f59e0b 60%, #b45309);
  box-shadow: inset 0 0 0 1.5px rgba(120, 53, 15, .45);
}
.credits-buy {
  display: inline-flex; align-items: center; justify-content: center; gap: 4px;
  height: 32px; min-width: 32px; padding: 0 9px; border-radius: 999px;
  border: 1px solid var(--line-strong); background: transparent; color: var(--accent);
  font: inherit; font-size: 14px; font-weight: 650; white-space: nowrap; cursor: pointer;
}
.credits-buy.empty { background: var(--cta); color: var(--cta-ink); border-color: var(--cta); }
.credits-plus { font-size: 18px; line-height: 1; }
.credits-buy-label { display: none; }
@media (min-width: 480px) {
  .credits-buy-label { display: inline; }
}
.credits-toast {
  position: fixed; left: 50%; transform: translateX(-50%); top: calc(64px + var(--safe-t)); z-index: 30;
  max-width: calc(100vw - 32px); padding: 10px 14px; border-radius: 12px;
  background: var(--ink); color: var(--bg); font-size: 14px; font-weight: 600; box-shadow: 0 6px 24px rgba(0,0,0,.25);
}
.credits-toast[hidden] { display: none; }

/* The pack picker: one component (packs.js), in a sheet and inline in Account → Credits. */
.wallet-balance-row { display: flex; align-items: center; justify-content: space-between; gap: 12px; }
.wallet-balance-row .btn { width: auto; margin-top: 0; padding: 8px 14px; min-height: 40px; }
.packs-list { display: grid; gap: 10px; margin-top: 12px; }
@media (min-width: 560px) { .packs-list { grid-template-columns: 1fr 1fr; } }
.pack {
  display: flex; flex-direction: column; gap: 2px; padding: 12px 14px;
  border: 1px solid var(--line); border-radius: 14px; background: var(--surface);
}
.pack.highlight { border: 2px solid var(--cta); }
.pack-head { display: flex; align-items: center; justify-content: space-between; gap: 8px; }
.pack-badge { font-size: 12px; font-weight: 650; color: var(--cta); }
.pack-credits { font-size: 15px; font-weight: 600; }
.pack-bonus { font-size: 13px; color: var(--ok); }
.pack-price { font-size: 20px; font-weight: 700; margin-top: 4px; }
.pack .btn { margin-top: 10px; min-height: 44px; padding: 10px 14px; }
.packs-free { margin: 0; font-weight: 600; }
.packs-why { margin: 0 0 4px; }
.packs-legal a { color: var(--accent); }
.packs-sheet {
  position: fixed; inset: 0; z-index: 40; display: flex; align-items: flex-end; justify-content: center;
  background: rgba(0, 0, 0, .45);
}
.packs-card {
  width: 100%; max-width: 560px; max-height: 88vh; overflow-y: auto;
  padding: 16px 16px calc(16px + var(--safe-b, 0px)); border-radius: 18px 18px 0 0; background: var(--bg);
}
@media (min-width: 600px) { .packs-sheet { align-items: center; } .packs-card { border-radius: 18px; } }
.packs-sheet-head { display: flex; align-items: center; justify-content: space-between; }
.packs-sheet-head h3 { margin: 0; }

/* ── Google sign-in row ──────────────────────────────────────────────────────
   Task 5. The row is REMOVED by auth.js when no client id is configured, so
   these rules describe a control that is either fully present or not in the
   document at all. There is deliberately no disabled state to style. */
.or {
  display: flex; align-items: center; gap: 12px;
  margin: 22px 0 16px; color: var(--ink-faint); font-size: 13px;
}
.or::before, .or::after {
  content: ''; flex: 1; height: 1px; background: var(--line);
}
.btn-google {
  display: flex; align-items: center; justify-content: center; gap: 10px;
}
.btn-google .g-mark { flex: 0 0 18px; }

/* ── myAstrologer ──────────────────────────────────────────────────────────
   Designed at 360×640 first, which is where §0g's three defects were found and
   where every screenshot in docs/screenshots/ is taken. The composer is
   sticky at the bottom, above the tab bar, so the question box is reachable
   with a thumb without scrolling past a long reading. */

.ast-head { margin-bottom: 14px; }
.ast-head h2 { margin: 0 0 4px; font-size: 20px; }
.ast-head p  { margin: 0; }

.ast-list {
  display: flex; flex-direction: column; gap: 14px;
  /* Room for the sticky composer AND the tab bar, or the last paragraph of a
     reading sits underneath both and looks like the reading was truncated. */
  padding-bottom: 12px;
}

.ast-empty { padding: 26px 4px; }
.ast-empty p:first-child { margin: 0 0 6px; font-size: 16px; color: var(--ink); }
.ast-empty p:last-child  { margin: 0; }

.ast-bubble {
  background: var(--surface); border: 1px solid var(--line);
  border-radius: var(--round); padding: 14px;
  /* min-width: 0 is the half that actually stops the overflow — a flex item
     will not shrink below its content without it. §0g defect 2. */
  min-width: 0;
  /* scrollIntoView({block:'end'}) aligns the element's bottom with the
     VIEWPORT's bottom — which is behind the sticky composer and the fixed tab
     bar, so the last two lines of the newest answer land underneath them. This
     is the room those two need. Same defect as the composer at bottom:0, one
     layer up, and scroll-margin is the tool made for it. */
  scroll-margin-bottom: calc(var(--tap) * 3 + 16px + var(--safe-b));
}
.ast-you { background: transparent; border-style: dashed; }
.ast-you p { margin: 0; color: var(--ink-soft); }

.ast-role {
  font-size: 11px; font-weight: 700; letter-spacing: .08em;
  text-transform: uppercase; color: var(--ink-faint); margin-bottom: 6px;
}

.ast-reading { display: flex; flex-direction: column; gap: 10px; }
.ast-reading p { margin: 0; line-height: 1.62; overflow-wrap: anywhere; }
.ast-h {
  margin: 6px 0 0; font-size: 12px; font-weight: 700;
  letter-spacing: .08em; text-transform: uppercase; color: var(--accent);
}
.ast-reading > .ast-h:first-child { margin-top: 0; }

.ast-waiting { padding: 26px 12px; text-align: center; color: var(--ink-soft); }
.ast-step { margin: 0 0 6px; font-size: 16px; color: var(--ink); font-weight: 600; }
.ast-waiting .muted { margin: 0; }

/* THE COMPOSER STICKS ABOVE THE TAB BAR, NOT UNDER IT.
   `bottom: 0` was the obvious spelling and it is wrong: `.tabbar` is
   `position: fixed; bottom: 0` with `z-index: 10`, so a sticky element at
   `bottom: 0` parks itself underneath it. The screenshot at 360x640 showed the
   textarea as a rounded sliver peeking out from behind the tab bar — the
   question box, unreachable, on the one screen whose whole purpose is asking a
   question.
   Found by LOOKING at 08d-astrologer-reading.png. Nothing else would have.
   The offset mirrors #panels' own padding-bottom so the two cannot drift. */
.ast-composer {
  position: sticky;
  bottom: calc(var(--tap) + 12px + var(--safe-b));
  z-index: 5;                       /* above the reading, below the tab bar */
  display: flex; gap: 8px; align-items: flex-end;
  padding: 10px 0 2px;
  background: linear-gradient(to bottom, transparent, var(--bg) 26%);
}
.ast-composer textarea {
  flex: 1 1 auto; min-width: 0;
  font: inherit; color: var(--ink);
  background: var(--surface); border: 1px solid var(--line-strong);
  border-radius: var(--round); padding: 10px 12px;
  resize: none; line-height: 1.45;
}
.ast-composer textarea:focus-visible { outline: 2px solid var(--accent); outline-offset: 1px; }
.ast-composer textarea:disabled { opacity: .6; }
/* width AND margin-top RESET EXPLICITLY, and this was a real defect found at
   360px by the screenshot harness — not by any other check.

   `.btn` is `display:block; width:100%; margin-top:18px`. As a FLEX ITEM,
   `flex: 0 0 auto` resolves flex-basis to `auto`, which then reads that
   `width: 100%` — so the button took the whole row, the textarea collapsed to a
   ~50px sliver with its placeholder wrapping one letter per line, and the
   button ran past the right edge of a 360px screen.

   Same family as §0g defect 1: a `.btn` class rule quietly beating what the
   specific rule intended. The lesson has now cost this project twice, so the
   reset is explicit rather than relying on `flex` to win an argument with
   `width`. */
.ast-send {
  flex: 0 0 auto;
  width: auto; margin-top: 0;
  min-height: var(--tap); padding-inline: 18px;
}

/* Visible to a screen reader, not to the eye. The composer's label needs to
   exist; putting it on screen would waste a line at 360px. */
.sr-only {
  position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px;
  overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; border: 0;
}

/* A refusal is not a reading and must not be framed as one. The accent border
   and the "Not read" label say the chart was never consulted — which is true,
   and which a reader would otherwise have no way of knowing. */
.ast-refusal {
  background: transparent;
  border-style: dashed;
  border-color: var(--ink-faint);
}
.ast-refusal p { margin: 0 0 8px; line-height: 1.62; overflow-wrap: anywhere; }
.ast-refusal p:last-child { margin-bottom: 0; }
.ast-refusal .ast-role { color: var(--danger); }

/* Task 17 — the crisis reply. NOT the dashed refusal frame ("declined") and NOT
   the reader's frame ("a reading"): a solid, quiet box of its own, with the
   padding carried here rather than borrowed from .ast-bubble, so it is the same
   at a scripture door (no .ast-bubble) as in myAstrologer. §0g's ninth defect
   was exactly that borrowed padding going missing. */
.crisis-reply { background: var(--surface); border: 1px solid var(--line); border-radius: var(--round); padding: 14px; }
.crisis-reply p { margin: 0 0 8px; line-height: 1.62; overflow-wrap: anywhere; }
.crisis-reply p:last-child { margin-bottom: 0; }

/* Task 43 — "Ask about this reading", under a reading. The composer is myAstrologer's,
   but NOT sticky: under a reading it sits at the end of the thread, where the eye is. */
.fu { margin-top: 22px; padding-top: 14px; border-top: 1px solid var(--line); }
.fu-h { font-size: 1rem; margin: 0 0 8px; }
.fu-thread { list-style: none; margin: 0; padding: 0; }
.fu-turn { margin: 0 0 14px; }
.fu-who { margin: 0 0 2px; }
.fu-q { margin: 0 0 6px; font-style: italic; overflow-wrap: anywhere; }
.fu-a p { margin: 0 0 8px; line-height: 1.62; overflow-wrap: anywhere; }
.fu-status > * { margin-top: 10px; }
.fu-composer { position: static; background: none; padding-top: 6px; }
.fu-cost, .fu-paused { margin: 6px 0 0; }

/* ── THE THREAD LIST (B1, B2, B6) ───────────────────────────────────────────
   Every rule below is sized for 360x640, because that is where the two Task 6
   layout defects were found and neither was visible at desktop width. */

/* B2. The primary action, first, full width, and it must not inherit `.btn`'s
   18px top margin: it sits directly under the header and that gap reads as a
   missing element. Same family of surprise as .ast-send's `width: 100%`. */
.ast-new { margin-top: 0; margin-bottom: 18px; }

.ast-threads { list-style: none; margin: 0; padding: 0; }
.ast-threads li + li { margin-top: 10px; }

/* A whole row is the tap target, not the title inside it. At 360px a title-only
   hit area is about 18px tall, which is a third of the 44px minimum. */
.ast-thread {
  display: block; width: 100%; text-align: left;
  font: inherit; color: var(--ink); cursor: pointer;
  background: var(--surface); border: 1px solid var(--line);
  border-radius: var(--round); padding: 12px 14px; min-height: var(--tap);
}
.ast-thread:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
.ast-thread-title {
  display: block; font-weight: 600; line-height: 1.4;
  /* A title is truncated on the SERVER at a word boundary (threadTitle), so
     this is a second line of defence for a long word, not the mechanism. */
  overflow-wrap: anywhere;
}
.ast-thread-meta {
  display: flex; align-items: center; gap: 8px; flex-wrap: wrap; margin-top: 8px;
}
.ast-chip {
  font-size: 11px; font-weight: 700; letter-spacing: .06em; text-transform: uppercase;
  color: var(--accent); border: 1px solid var(--line);
  border-radius: 999px; padding: 2px 8px; white-space: nowrap;
}
.ast-when { font-size: 12px; color: var(--ink-faint); }

/* The examples in the empty state. Buttons, because tapping one does something
   — it opens a new thread with the question typed in, ready to send or edit. */
.ast-examples { list-style: none; margin: 16px 0 0; padding: 0; }
.ast-examples li + li { margin-top: 8px; }
.ast-eg {
  display: block; width: 100%; text-align: left; font: inherit;
  color: var(--accent); cursor: pointer;
  background: transparent; border: 1px dashed var(--line);
  border-radius: var(--round); padding: 10px 12px; min-height: var(--tap);
}
.ast-eg:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }

/* ── ONE THREAD ─────────────────────────────────────────────────────────────*/

/* The only way back to the list, so it is a real 44px target rather than a
   text link. The panel has no route and therefore no browser back button. */
/* AND IT STICKS, because of what the 360x640 photograph of an open thread
   showed: a reading is two or three screens tall, the back button is at the
   top of it, and the only route to the thread list was a long scroll upwards.
   The list is the home view of this surface; making it expensive to reach
   would undo B1 while every assertion still passed.
   Under `.bar`, not at 0 — .bar is itself sticky at top:0 with z-index 5, so
   an element at top:0 parks underneath it. Exactly the composer's bug from
   Task 6, one edge up. The full-bleed background is what stops a reading
   scrolling visibly through the button. */
.ast-back {
  position: sticky; top: var(--bar-h); z-index: 4;
  display: flex; align-items: center; justify-content: flex-start;
  font: inherit; font-size: 14px; color: var(--accent); cursor: pointer;
  border: 0; border-bottom: 1px solid var(--line); border-radius: 0;
  padding: 0 var(--pad); min-height: var(--tap);
  /* FULL BLEED, AND THE WIDTH IS NOT OPTIONAL.
     A <button> sizes to its content even at `display: flex` — form controls
     have fit-content width unless told otherwise — so the first version painted
     its background over about 240px of a 360px row and the reading scrolled
     visibly past it on the right, through the gap. It looked broken rather than
     sticky. Found by LOOKING at 08i-astrologer-thread-opened.png.
     `100% + 2 × --pad` because the negative margins below pull it out to the
     screen edges, and `width: 100%` would leave 16px bare on the right. */
  width: calc(100% + var(--pad) * 2);
  margin: 0 calc(var(--pad) * -1) 8px;
  background: var(--bg);
}
.ast-back:focus-visible { outline: 2px solid var(--accent); outline-offset: -2px; }

/* The thread's own subject, and the only heading in this view. */
.ast-title {
  margin: 6px 0 14px; font-size: 18px; line-height: 1.3;
  overflow-wrap: anywhere;
}

/* B5. The answer went to a new conversation and this says so. Deliberately not
   styled as a warning: nothing went wrong, and a red box would say it did. */
.ast-moved {
  border-left: 3px solid var(--accent);
  padding: 2px 0 2px 12px; margin-bottom: 4px;
}
.ast-moved p { margin: 0; font-size: 13px; color: var(--ink-soft); }

/* C2. The deferred follow-ups. They sit AFTER the answer, because they are what
   the answer chose not to cover, and they look like the questions they are
   rather than like a reading. */
.ast-deferred {
  border: 1px dashed var(--line); border-radius: var(--round);
  padding: 12px 14px;
  /* Same reason as .ast-bubble: scrollIntoView aligns to the viewport bottom,
     which is behind the sticky composer and the fixed tab bar. */
  scroll-margin-bottom: calc(var(--tap) * 3 + 16px + var(--safe-b));
}
.ast-deferred ul { list-style: none; margin: 0; padding: 0; }
.ast-deferred li + li { margin-top: 8px; }
.ast-followup {
  display: block; width: 100%; text-align: left; font: inherit;
  color: var(--accent); cursor: pointer;
  background: transparent; border: 1px solid var(--line);
  border-radius: var(--round); padding: 10px 12px; min-height: var(--tap);
  overflow-wrap: anywhere;
}
.ast-followup:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }

/* D1. A follow-up question INSIDE the birth-time field, not a field of its own:
   it has no meaning without the time above it, and at 360px a second top-level
   field reads as a second unrelated thing to fill in. */
.sub { display: block; margin-top: 14px; }
.sub > span {
  display: block; font-size: 13px; font-weight: 600;
  color: var(--ink-soft); margin-bottom: 6px;
}

/* ── myApps — Task 13 ──────────────────────────────────────────────────────
   NO NEW COLOURS. Every value here is a Task 12.5 token: --surface, --line,
   --line-strong, --ink, --ink-soft, --ink-faint, --accent, --cta. The palette
   split that task made is what this section leans on — the violet means "this
   is a link", --cta means "press this" — and a tile is neither: it is a card
   you press, so it takes the surface treatment and earns its emphasis from the
   heading rather than from a fill.

   THE 44px FLOOR APPLIES. A tile is a tap target and is comfortably over it,
   but `min-height` is stated rather than assumed, because the tagline is what
   currently makes the tile tall and a tile without one would collapse. */

.apps { display: flex; flex-direction: column; gap: 22px; }

.appsection { display: flex; flex-direction: column; gap: 10px; }

.appsection-h {
  margin: 0;
  font-size: 13px;
  font-weight: 650;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: var(--ink-faint);
}

/* The master slot. It has no heading, so it gets the space a heading would
   have taken rather than sitting flush against the section below it. */
.appsection-master { margin-bottom: 4px; }

.apptiles { display: flex; flex-direction: column; gap: 10px; }

.apptile {
  display: flex;
  flex-direction: column;
  gap: 3px;
  width: 100%;
  min-height: var(--tap);
  padding: 13px 14px;
  text-align: left;
  background: var(--surface);
  color: var(--ink);
  /* --line-strong, NOT --line. This is the boundary of something you press, so
     it takes the colour Task 12.5 introduced for exactly that — the decorative
     --line measured 1.27:1 against the surface and is for dividers. */
  border: 1px solid var(--line-strong);
  border-radius: var(--round);
  font: inherit;
  cursor: pointer;
}

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

.apptile-name { font-size: 16px; font-weight: 600; }

/* min-width: 0 is what actually stops a long tagline overflowing inside a flex
   column — a flex item will not shrink below its content without it. §0g.2 is
   the defect this line exists to prevent, and it was found by looking at a
   picture rather than by any assertion. */
.apptile-tag { font-size: 13px; color: var(--ink-soft); min-width: 0; }

.apptile-meta {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
  margin-top: 5px;
  font-size: 12px;
}

.apptile-cost {
  color: var(--ink-faint);
  border: 1px solid var(--line);
  border-radius: 999px;
  padding: 1px 8px;
}

/* Task 29 — the day's allowance. The number is /api/quota's; hidden when unread. */
.quota { margin: 0; font-size: 0.875rem; color: var(--ink-soft, var(--ink)); }
.quota[hidden] { display: none; }

/* "Needs birth details" — said BEFORE the tap, on the tile. It is information,
   not a warning, so it is not --danger: being told what a thing needs is not
   being told something went wrong. */
.apptile-need {
  color: var(--accent);
  border: 1px solid var(--accent);
  border-radius: 999px;
  padding: 1px 8px;
}

/* A locked tile is NOT disabled and NOT greyed out. It stays focusable, keeps
   its full contrast, and carries the chip above saying what it needs. A grey
   rectangle that cannot be focused gives a person no reason and no recourse —
   which is the defect §0g.3 found in a different control. */
.apptile.is-locked { border-style: dashed; }

/* The ask view for one tile. It borrows myAstrologer's reading and refusal
   classes rather than restating them — .ast-reading, .ast-refusal, .ast-back
   and .ast-step are already the house treatment for "a model wrote this", and
   a second set of near-identical rules is a second place for them to drift. */
.appask { display: flex; flex-direction: column; gap: 4px; }
.appask-h { margin: 10px 0 2px; font-size: 20px; }
.appask-out { margin-top: 16px; }
/* A REFUSAL IN THE APPS PANEL IS A WHOLE BOX, NOT HALF OF ONE.
   .ast-refusal only restyles a box: dashed, --ink-faint, no fill. Its padding,
   radius and 1px border come from .ast-bubble, which the astrologer panel always
   pairs it with. The apps panel used .ast-refusal ALONE — so it got the dashed
   border at the browser's default width and no padding at all, and the text sat
   flush against its own border. Every assertion passed, including "nothing
   overflows 360px", because nothing did. Only the screenshot showed it (§0g, the
   ninth). Task 16 found it on the daily-cap refusal; Task 13's pre-flight box
   uses the same class and had the same defect, unphotographed.
   These are .ast-bubble's own values, so the two panels' refusals match. */
.appask-out .ast-refusal { border-width: 1px; border-radius: var(--round); padding: 14px; }

/* THE DAILY-CAP REFUSAL. A heading weight, so the sentence that says WHICH cap
   bound reads first. Existing tokens only — no new colour (Task 12.5 palette). */
.appask-cap-h { font-weight: 600; color: var(--ink); }
.appask-cap .btn { margin-top: 10px; }
.appask-refs { margin: 10px 0 0; }
/* ONE CITATION PER LINE. The refs carry the name of their work as of Task 15
   ("Bṛhadāraṇyaka Upaniṣad, adhyāya 3, §18") and several contain commas, so a
   comma-joined line gave the reader no way to see where one citation ended.
   `display: block` on each is what separates them; no separator character is
   safe, because , and · both appear inside refs. See public/js/apps.js. */
.appask-refs-label { display: block; }
.appask-ref { display: block; margin-top: 2px; }

/* ── myKRISHNA (Task 23) ─────────────────────────────────────────────────────
   Four movements, each its own block with room between them, so the reading reads
   as four steps rather than one wall. A verse is a figure: Devanāgarī first and
   largest, IAST beneath in italics, the translation in the body face, the citation
   faint — each its own line, so no reader has to find their script inside another.
   Existing tokens only (Task 12.5 palette). */
.kr-reading { gap: 18px; }
.kr-move { display: flex; flex-direction: column; gap: 10px; }
.kr-move + .kr-move { padding-top: 14px; border-top: 1px solid var(--line); }
.kr-text { margin: 0; line-height: 1.62; overflow-wrap: anywhere; }
.kr-verse {
  margin: 2px 0; padding: 12px 14px; background: var(--surface);
  border-left: 3px solid var(--accent); border-radius: var(--round);
  display: flex; flex-direction: column; gap: 6px;
}
.kr-verse p { margin: 0; overflow-wrap: anywhere; }
.kr-deva { font-size: 18px; line-height: 1.7; color: var(--ink); }
.kr-iast { font-style: italic; line-height: 1.55; color: var(--ink-soft); }
.kr-tr { line-height: 1.55; }
.kr-cite { font-size: 13px; color: var(--ink-faint); margin: 0; }
.kr-pleasantry p { margin: 0; line-height: 1.62; }

/* ── DIVINATION (Task 36) ─────────────────────────────────────────────────────
   One screen for five tiles. Text and CSS only: a card is a named box, a face-down
   card is a patterned back, a rune is its glyph in Noto Sans Runic, a hexagram is six
   CSS bars, a geomantic figure is dots. Existing tokens only. */
.div-stage { display: flex; flex-direction: column; gap: 10px; margin-top: 8px; }
.div-spread-name { margin: 0; font-weight: 600; color: var(--ink); }
.div-pills { display: flex; flex-wrap: wrap; gap: 6px; }
.div-pill { font-size: 12px; padding: 3px 9px; border: 1px solid var(--line-strong); border-radius: 999px; color: var(--ink-soft); }
.div-fan { display: grid; grid-template-columns: repeat(5, 1fr); gap: 8px; max-width: 360px; }
.div-card-back {
  aspect-ratio: 2 / 3; min-height: var(--tap); border-radius: 6px; cursor: pointer;
  border: 1px solid var(--accent);
  background: repeating-linear-gradient(45deg, var(--surface) 0 6px, var(--line) 6px 12px);
  transition: transform .15s ease, opacity .15s ease;
}
.div-card-back:hover:not(:disabled) { transform: translateY(-3px); }
.div-card-back:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
.div-card-back.picked { opacity: .35; cursor: default; }
.div-slots { list-style: none; margin: 0; padding: 0; display: flex; flex-wrap: wrap; gap: 6px; }
.div-slot { display: flex; align-items: center; gap: 6px; font-size: 13px; padding: 4px 8px;
  border: 1px dashed var(--line-strong); border-radius: var(--round); color: var(--ink-faint); }
.div-slot.filled { border-style: solid; border-color: var(--accent); color: var(--ink); }
.div-slot-n { font-weight: 600; }
.div-left { margin: 0; }
.div-skip { align-self: flex-start; }
.div-drawn { display: flex; flex-direction: column; gap: 8px; margin-bottom: 12px; }
.div-cards { list-style: none; margin: 0; padding: 0; display: grid; grid-template-columns: repeat(auto-fill, minmax(110px, 1fr)); gap: 8px; }
.div-card { display: flex; flex-direction: column; gap: 2px; padding: 10px; min-height: 92px;
  background: var(--surface); border: 1px solid var(--line-strong); border-top: 3px solid var(--accent); border-radius: 6px; }
.div-card.reversed { border-top: 0; border-bottom: 3px solid var(--accent); }
.div-card-name { font-weight: 600; line-height: 1.3; color: var(--ink); }
.div-card-orient { font-size: 12px; color: var(--ink-soft); }
.div-card-pos { font-size: 12px; color: var(--ink-faint); margin-top: auto; }
.div-reading { gap: 14px; }
.div-sec { display: flex; flex-direction: column; gap: 6px; }
.div-sec + .div-sec { padding-top: 12px; border-top: 1px solid var(--line); }
.div-h { margin: 0; font-size: 15px; color: var(--ink); }
h3.div-h { font-size: 17px; }
.div-p { margin: 0; line-height: 1.62; overflow-wrap: anywhere; }
/* Runes: the glyphs in Noto Sans Runic, bundled with its licence (public/fonts,
   SIL Open Font License 1.1). A merkstave rune is drawn upside down; Wyrd is an
   empty stone. */
@font-face { font-family: 'Noto Sans Runic'; src: url('../fonts/NotoSansRunic-Regular.ttf') format('truetype'); font-display: swap; }
.div-runes { list-style: none; margin: 0; padding: 0; display: grid; grid-template-columns: repeat(auto-fill, minmax(96px, 1fr)); gap: 8px; }
.div-rune-item { display: flex; flex-direction: column; align-items: center; gap: 2px; padding: 10px 6px; text-align: center;
  background: var(--surface); border: 1px solid var(--line-strong); border-radius: 6px; }
.div-rune { font-family: 'Noto Sans Runic', serif; font-size: 28px; line-height: 1; color: var(--accent);
  display: inline-flex; align-items: center; justify-content: center; width: 44px; height: 56px;
  border: 1px solid var(--line-strong); border-radius: 12px; background: var(--bg); }
.div-rune.big { font-size: 34px; }
.div-rune.merkstave { transform: rotate(180deg); }
.div-rune.blank { color: transparent; }
.div-bag { align-self: flex-start; }
/* I Ching: a hexagram is six CSS bars, top line first on screen. A yin line is two
   halves with a gap; a changing line carries a ring. No font, no image. */
.div-hex { display: flex; flex-direction: column; gap: 6px; width: 96px; }
.div-hex-line { height: 10px; border-radius: 2px; position: relative; }
.div-hex-line.yang { background: var(--accent); }
.div-hex-line.yin { display: flex; justify-content: space-between; }
.div-hex-line.yin span { width: 42%; height: 100%; background: var(--accent); border-radius: 2px; }
.div-hex-line.changing::after { content: ''; position: absolute; right: -18px; top: -2px; width: 10px; height: 10px;
  border: 2px solid var(--cta); border-radius: 50%; }
.div-hex-build { min-height: 96px; justify-content: flex-end; }
.div-hex-pair { display: flex; align-items: center; gap: 16px; flex-wrap: wrap; }
.div-hex-card { display: flex; flex-direction: column; gap: 4px; padding: 12px 24px 12px 12px; min-width: 140px;
  background: var(--surface); border: 1px solid var(--line-strong); border-radius: 6px; }
.div-hex-arrow { font-size: 22px; color: var(--ink-faint); }
.div-coins { margin: 0; font-size: 14px; color: var(--ink-soft); min-height: 1.5em; }
/* Ramal: a figure is four rows of one or two dots; the shield chart is a grid of the
   sixteen houses, the Witnesses, Judge and Reconciler marked. */
.div-domain { font: inherit; min-height: var(--tap); padding: 0 10px; border: 1px solid var(--line-strong);
  border-radius: var(--round); background: var(--surface); color: var(--ink); }
.div-geo { display: flex; flex-direction: column; gap: 3px; align-items: center; }
.div-geo-row { display: flex; gap: 6px; justify-content: center; min-height: 8px; }
.div-geo-dot { width: 8px; height: 8px; border-radius: 50%; background: var(--accent); }
.div-geo-mothers { display: grid; grid-template-columns: repeat(4, 1fr); gap: 6px; }
.div-geo-chart { display: grid; grid-template-columns: repeat(4, 1fr); gap: 6px; }
.div-geo-cell { display: flex; flex-direction: column; align-items: center; gap: 4px; padding: 8px 4px; text-align: center;
  background: var(--surface); border: 1px solid var(--line); border-radius: 6px; min-width: 0; }
.div-geo-cell.key { border-color: var(--accent); }
.div-geo-house { font-size: 11px; color: var(--ink-faint); }
.div-geo-name { font-size: 12px; font-weight: 600; line-height: 1.2; overflow-wrap: anywhere; }
.div-geo-latin { font-size: 10px; color: var(--ink-soft); line-height: 1.2; overflow-wrap: anywhere; }
/* Moksha Patam: the 72-square board as a 9 × 8 grid, the token a filled square;
   snake heads and ladder feet marked; the result's panels as CSS bars. */
.div-mp-board { display: grid; grid-template-columns: repeat(9, 1fr); gap: 2px; }
.div-mp-cell { aspect-ratio: 1; display: flex; align-items: center; justify-content: center; font-size: 10px;
  color: var(--ink-soft); background: var(--surface); border: 1px solid var(--line); border-radius: 3px; }
.div-mp-cell.virtue { color: var(--ok); }
.div-mp-cell.vice { color: var(--danger); }
.div-mp-cell.snake { border-color: var(--danger); border-width: 2px; }
.div-mp-cell.ladder { border-color: var(--ok); border-width: 2px; }
.div-mp-cell.here { background: var(--accent); color: var(--accent-ink); font-weight: 700; }
.div-mp-log { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 4px; font-size: 13px; color: var(--ink-soft); }
.div-mp-log .snake { color: var(--danger); }
.div-mp-log .ladder { color: var(--ok); }
.div-panel { display: flex; flex-direction: column; gap: 6px; padding: 10px 12px; background: var(--surface);
  border: 1px solid var(--line-strong); border-radius: 6px; }
.div-bar-row { display: grid; grid-template-columns: 96px 1fr 32px; align-items: center; gap: 8px; font-size: 12px; }
.div-bar-label { color: var(--ink-soft); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.div-bar-track { height: 8px; background: var(--line); border-radius: 4px; overflow: hidden; }
.div-bar-fill { height: 100%; background: var(--accent); }
.div-bar-fill.virtue { background: var(--ok); }
.div-bar-fill.vice { background: var(--danger); }
.div-bar-value { text-align: right; color: var(--ink-soft); }
.div-stages { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 6px; }
.div-stage-row { display: flex; flex-direction: column; gap: 2px; padding: 6px 8px; border-left: 3px solid var(--line-strong); }
.div-stage-row.now { border-left-color: var(--accent); }
.div-stage-row.lived { opacity: .75; }

/* ── REPORT (Task 22) ─────────────────────────────────────────────────────────
   Play requires a way to report what the model wrote without leaving the app.
   The control sits at the foot of the output and is the quietest thing near it:
   faint ink, small type, no fill, no border — but a full --tap target, because
   something nobody can hit is not a control. The reasons open inline below it.
   Existing tokens only. */
.report { display: flex; flex-direction: column; align-items: flex-end; margin-top: 6px; }
.report-open, .report-cancel {
  font: inherit; font-size: 13px; color: var(--ink-faint); background: none; border: 0;
  min-height: var(--tap); padding: 0 6px; cursor: pointer; text-decoration: underline;
  text-underline-offset: 3px;
}
.report-cancel { color: var(--ink-soft); }
.report-open:focus-visible, .report-cancel:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
.report-form {
  align-self: stretch; margin: 4px 0 0; padding: 8px 12px 10px;
  border: 1px solid var(--line-strong); border-radius: var(--round); background: var(--surface);
}
.report-form legend { font-size: 13px; color: var(--ink-soft); padding: 0 4px; }
.report-choice { display: flex; align-items: center; gap: 10px; min-height: var(--tap); font-size: 15px; color: var(--ink); }
.report-choice input { width: 20px; height: 20px; margin: 0; accent-color: var(--accent); }
.report-actions { display: flex; align-items: center; justify-content: flex-end; gap: 8px; margin-top: 4px; }
.report-msg { margin: 6px 0 0; }
.report-done { margin: 8px 0 0; }

/* `font: inherit` IS THE WHOLE POINT OF THIS RULE, and it was found by looking
   at the panel rather than by any assertion.

   A <textarea> does NOT inherit the page font. Its user-agent default is
   monospace, so the question box rendered in a typewriter face while every
   other word in the app was the system sans — on the one control a person types
   their own question into. Every check was green: the right text, the right
   length, no overflow at 360px, a 44px tap target.

   `.ast-composer textarea` already carried `font: inherit` for exactly this
   reason. This is the same defect arriving at the second place somebody types,
   which is why the rule is stated here rather than left to be inherited from a
   selector that does not cover it. §0g, for the seventh time in this project. */
.appask textarea {
  width: 100%;
  font: inherit;
  color: var(--ink);
  background: var(--surface);
  border: 1px solid var(--line-strong);
  border-radius: var(--round);
  padding: 10px 12px;
  line-height: 1.45;
  resize: vertical;
}
.appask textarea:focus-visible { outline: 2px solid var(--accent); outline-offset: 1px; }
.appask textarea:disabled { opacity: .6; }

/* ── the retired build screen — Task 14 ────────────────────────────────────
   NO NEW COLOURS. --cta for the one action (Task 12.5's rule: the warm colour
   means PRESS THIS, the violet means "this is a link"), --ink-soft for the
   sentence, --ink-faint for the version line.

   IT REUSES .centre, .mark, .mark-lg, .lede and .btn deliberately — this is the
   same shape as the loading screen it replaces, so the app does not appear to
   have become a different application at the moment it stops working. */
#screen-retired .centre { padding: var(--pad); text-align: center; }

/* .btn IS WRITTEN FOR <button> AND THIS IS AN <a>. An anchor is not centred by
   .btn's rules and carries an underline, so both are stated here rather than
   left to look almost right. It is an anchor and not a button because it
   navigates to the store — and on a device platform.openExternal() is not
   available to markup, so a real href is what hands the URL to the system. */
#retired-store {
  max-width: 320px;
  text-align: center;
  text-decoration: none;
  line-height: 1.4;
}
#retired-store:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }

#retired-version { margin: 14px 0 0; }

/* ── Task 24 — your readings, and deleting them ─────────────────────────────── */
.sub-h { font-size: 1.05rem; margin: 18px 0 6px; }
.hist-section { margin: 18px 0 8px; }
.hist-h { font-size: 1.05rem; margin: 0 0 8px; }
.hist-list { list-style: none; margin: 8px 0 0; padding: 0; }
.hist-item {
  border: 1px solid var(--line); border-radius: var(--round); background: var(--surface);
  padding: 12px 14px; margin: 0 0 10px; overflow-wrap: anywhere;
}
.hist-label { margin: 0 0 4px; font-weight: 600; }
.hist-open { min-height: var(--tap); }
.hist-text { margin: 8px 0; }
.hist-who { margin: 12px 0 2px; }
.hist-empty { margin: 4px 0 12px; }
.hist-all { margin: 0 0 6px; }
.hist-q { margin: 0 0 4px; font-style: italic; }
.tile-hist { margin-top: 28px; padding-top: 12px; border-top: 1px solid var(--line); }
.del { margin: 6px 0 0; }
.del-open { min-height: var(--tap); color: var(--danger); }
.del-ask { margin: 6px 0; }
.del-ask .btn { margin: 6px 8px 0 0; }
.del-q { margin: 0 0 4px; }
.del-msg { margin: 6px 0 0; color: var(--ink-soft); }
/* ── form tiles — Task 39 ──────────────────────────────────────────────────
   ONE SCREEN FOR EVERY FORM TILE (public/js/forms.js) and ONE RENDERER FOR ITS
   ANSWER (public/js/form-blocks.js), new and in history. No new colours: every
   value is a Task 12.5 token. A field is the profile form's `.field`; a place is
   the profile form's city picker, `.results` and `.chosen` and all. */
.fm-for { margin: 10px 0 2px; font-weight: 600; color: var(--ink); }
.fm-intro { margin: 6px 0 0; color: var(--ink-soft); line-height: 1.55; }
.fm-body .fm-field { margin: 16px 0 0; }
.fm-label {
  display: block; font-size: 13px; font-weight: 600;
  color: var(--ink-soft); margin: 0 0 6px;
}
.fm-label em { font-weight: 400; font-style: normal; color: var(--ink-faint); }
.fm-field input[type=time]:disabled { opacity: .55; }
.fm-error { margin: 6px 0 0; font-size: 13px; color: var(--danger); }
.fm-seg { margin: 0 0 4px; }
.fm-person-saved select { margin-top: 4px; }
.fm-person-inline .sub:first-child { margin-top: 6px; }
.fm-person-inline .fm-place { margin-top: 0; }
.fm-inline-note { margin-top: 10px; }
.fm-body > .muted.sm { margin: 18px 0 10px; }

/* THE ANSWER. Each block a card on the surface, so a table and the prose around it
   read as separate things at 360px. */
.fb-answer { gap: 12px; }
.fb { background: var(--surface); border: 1px solid var(--line); border-radius: var(--round); padding: 12px 14px; }
.fb-section, .fb-flat { background: transparent; border: 0; padding: 0; }
.fb-section, .fb-flat { display: flex; flex-direction: column; gap: 8px; }
.fb-h { margin: 0 0 8px; font-size: 15px; }
.fb-section .fb-h { margin: 4px 0 0; }
.fb-p { margin: 0; line-height: 1.62; overflow-wrap: anywhere; }
.fb-cite-label { margin: 0 0 4px; font-size: 12px; font-weight: 600; color: var(--ink-faint); text-transform: uppercase; letter-spacing: .04em; }
.fb-cite-list { margin: 0; padding-left: 18px; font-size: 14px; color: var(--ink-soft); }
.fb-cite-list li { margin: 2px 0; overflow-wrap: anywhere; }
.fb-facts-list { margin: 0; display: flex; flex-direction: column; }
.fb-fact { display: grid; grid-template-columns: minmax(0, 2fr) minmax(0, 3fr); gap: 10px; padding: 6px 0; border-top: 1px solid var(--line); }
.fb-fact:first-child { border-top: 0; padding-top: 0; }
.fb-fact dt { color: var(--ink-soft); font-size: 14px; overflow-wrap: anywhere; }
.fb-fact dd { margin: 0; font-weight: 550; overflow-wrap: anywhere; }
.fb-score-table { width: 100%; border-collapse: collapse; font-size: 14px; table-layout: fixed; }
.fb-score-table th { text-align: left; font-weight: 400; color: var(--ink-soft); padding: 5px 8px 5px 0; overflow-wrap: anywhere; width: 40%; }
.fb-score-table td { padding: 5px 0; }
.fb-bar-cell { width: 38%; padding-right: 8px !important; }
.fb-bar { display: block; height: 8px; background: var(--line); border-radius: 4px; overflow: hidden; }
.fb-bar-fill { display: block; height: 100%; background: var(--accent); }
.fb-score-n { text-align: right; white-space: nowrap; font-variant-numeric: tabular-nums; width: 22%; }
.fb-score-table tfoot th, .fb-score-table tfoot td { border-top: 1px solid var(--line-strong); padding-top: 8px; font-weight: 650; color: var(--ink); }
.fb-score-verdict { margin: 10px 0 0; font-weight: 600; }
/* THE VERDICT HAS ITS OWN FRAME. Dashed is the refusal's ("declined", .ast-refusal)
   and a plain 1px line on the surface is the crisis reply's; a verdict is neither.
   Solid, a 6px edge in the value's colour and a faint wash of it: the value leads —
   a mark and its word — and the line follows. Existing tokens only. */
.fb-verdict {
  --v: var(--ink-soft);
  border: 1px solid var(--v); border-left-width: 6px;
  background: color-mix(in srgb, var(--v) 7%, var(--surface));
}
.fb-verdict-yes { --v: var(--ok); }
.fb-verdict-no { --v: var(--danger); }
.fb-verdict-unclear { --v: var(--ink-soft); }
.fb-verdict-label { display: flex; align-items: center; gap: 10px; margin: 0; font-size: 20px; font-weight: 700; color: var(--v); }
.fb-verdict-mark {
  flex: 0 0 28px; width: 28px; height: 28px; border-radius: 50%;
  display: inline-flex; align-items: center; justify-content: center;
  background: var(--v); color: var(--surface); font-size: 16px; font-weight: 700; line-height: 1;
}
.fb-verdict .fb-p { margin-top: 8px; color: var(--ink); }
.fb-grid-cells { display: grid; gap: 4px; max-width: 280px; margin: 0 auto; }
.fb-grid-row { display: grid; grid-template-columns: repeat(var(--cols, 3), minmax(0, 1fr)); gap: 4px; }
.fb-grid-cell {
  display: flex; align-items: center; justify-content: center; min-height: 52px; padding: 4px;
  border: 1px solid var(--line-strong); border-radius: 8px; font-size: 18px; font-weight: 600;
  font-variant-numeric: tabular-nums; overflow-wrap: anywhere; text-align: center;
}
.fb-grid-cell.empty { color: var(--ink-faint); font-weight: 400; border-style: dashed; }
.fb-caption { margin: 8px 0 0; text-align: center; }

/* ── the intro screen (Task 47, RUNBOOK §0bh) ─────────────────────────────
   One tile's intro: shown in the panel on its first open, and as a sheet over the
   main screen from the ⓘ. Existing tokens and components only: .btn .btn-primary is
   the button, .ast-back the way out, --line-strong the boundary of anything pressed.

   THE BUTTON IS PINNED WHERE THE THUMB IS, and above the tab bar rather than under it
   — the same offset .ast-composer takes, for the same reason (Task 6's bug). In the
   sheet there is no tab bar under it, so only the home indicator is cleared.

   A LONG TITLE WRAPS. Malayalam and Tamil titles run long and have no spaces to break
   at in a compound; overflow-wrap: anywhere is what keeps one from widening the page
   at 360px. */
.appintro { display: flex; flex-direction: column; gap: 4px; }
.appintro-title { margin: 10px 0 2px; font-size: 22px; line-height: 1.3; overflow-wrap: anywhere; }
.appintro-what { margin: 0 0 6px; font-size: 16px; line-height: 1.5; color: var(--ink); }
.appintro-part { margin-top: 12px; }
.appintro-h { margin: 0 0 6px; font-size: 13px; font-weight: 650; letter-spacing: .02em; color: var(--ink-soft); }
.appintro-ul, .appintro-ol { margin: 0; padding-left: 22px; line-height: 1.5; }
.appintro-ul li, .appintro-ol li { margin: 0 0 4px; overflow-wrap: anywhere; }
.appintro-p { margin: 0; line-height: 1.5; overflow-wrap: anywhere; }
.appintro-example {
  margin: 0; padding: 10px 12px; line-height: 1.5; font-style: italic;
  background: var(--surface); border: 1px solid var(--line); border-radius: var(--round);
  overflow-wrap: anywhere;
}
.appintro-cost { margin: 14px 0 0; }
.appintro-cta {
  position: sticky;
  bottom: calc(var(--tap) + 12px + var(--safe-b));
  z-index: 5;
  padding: 10px 0 2px;
  background: linear-gradient(to bottom, transparent, var(--bg) 26%);
}
.appintro-cta .btn { margin-top: 6px; }

/* The ⓘ beside a main screen's name: a 44px target, the name keeps the rest of the row. */
.appask-h.has-info { display: flex; align-items: center; justify-content: space-between; gap: 8px; }
.appask-info {
  flex: 0 0 var(--tap); width: var(--tap); height: var(--tap);
  display: inline-flex; align-items: center; justify-content: center;
  font: inherit; font-size: 20px; line-height: 1; color: var(--accent);
  background: transparent; border: 0; border-radius: 50%; cursor: pointer;
}
.appask-info:focus-visible { outline: 2px solid var(--accent); outline-offset: 0; }

/* The sheet: the whole screen, above the tab bar, scrolled on its own. */
.appintro-sheet {
  position: fixed; inset: 0; z-index: 20;
  overflow-y: auto; -webkit-overflow-scrolling: touch;
  background: var(--bg); color: var(--ink);
  padding: calc(8px + var(--safe-t)) var(--pad) 0;
}
.appintro-sheet-body { max-width: 520px; margin: 0 auto; }
.appintro-sheet .appintro-cta { bottom: 0; padding-bottom: calc(12px + var(--safe-b)); }
.appintro-close {
  align-self: flex-end;
  width: var(--tap); height: var(--tap);
  display: inline-flex; align-items: center; justify-content: center;
  font: inherit; font-size: 18px; color: var(--ink-soft);
  background: transparent; border: 1px solid var(--line-strong); border-radius: 50%; cursor: pointer;
}
.appintro-close:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }

/* ══ Task 69 (build 1006): myDay v2, the tarot card of the day, the morning notification ══ */

:root { --amber: #a35f00; }
@media (prefers-color-scheme: dark) { :root { --amber: #e3ac4f; } }

/* the verse, first */
.day-verse {
  text-align: center; padding: 14px 12px;
  border-top: 1px solid var(--line); border-bottom: 1px solid var(--line);
}
.day-verse p { margin: 0; }
.day-sanskrit { font-size: 18px; line-height: 1.6; color: var(--ink); }
.day-iast { font-style: italic; color: var(--ink-soft); margin-top: 4px !important; }
.day-verse-line { margin-top: 10px !important; font-size: 16px; }
.day-verse-ref { margin-top: 6px !important; font-size: 12px; color: var(--ink-faint); letter-spacing: .04em; }

/* today strip */
.day-today {
  background: var(--surface); border: 1px solid var(--line); border-radius: var(--round);
  padding: 10px 14px; font-size: 14px;
}
.day-today p { margin: 0; }
.day-today-names { font-weight: 600; overflow-wrap: anywhere; }
.day-today-times { color: var(--ink-soft); margin-top: 4px !important; overflow-wrap: anywhere; }

.day-mindset { margin-top: 8px !important; color: var(--ink-soft); }

/* the five areas */
.day-area-list { list-style: none; margin: 0; padding: 0; }
.day-area { padding: 10px 0; border-top: 1px solid var(--line); }
.day-area:first-child { border-top: 0; padding-top: 2px; }
.day-area p { margin: 4px 0 0 !important; }
.day-area-head { display: flex; align-items: center; gap: 8px; }
.day-light { flex: 0 0 12px; width: 12px; height: 12px; border-radius: 50%; }
.day-light.light-green  { background: var(--ok); }
.day-light.light-yellow { background: var(--amber); }
.day-light.light-red    { background: var(--danger); }
.day-area-label { font-weight: 700; }
.day-area-word { margin-left: auto; font-size: 12px; color: var(--ink-faint); text-transform: uppercase; letter-spacing: .06em; }
.day-area-reason { color: var(--ink-soft); font-size: 14px; }
.day-area-do { font-weight: 600; font-size: 14px; }
.day-area-do::before { content: '→ '; color: var(--danger); }
.day-avoid ul { margin: 0; padding-left: 18px; }
.day-avoid li + li { margin-top: 6px; }

/* chips: a question typed in, never sent */
.day-chips { display: flex; flex-direction: column; gap: 8px; margin-top: 10px; }
.day-chip {
  display: block; width: 100%; text-align: left; font: inherit; font-size: 15px;
  color: var(--accent); cursor: pointer; background: transparent;
  border: 1px dashed var(--line-strong); border-radius: var(--round);
  padding: 10px 12px; min-height: var(--tap);
}
.day-chip:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }

/* the tarot card of the day */
.tarot-daily { margin-top: 18px; display: flex; flex-direction: column; gap: 10px; }
.tarot-h { margin: 0; font-size: 18px; }
.tarot-prompt { margin: 0; color: var(--ink-soft); }
.tarot-spread {
  display: grid; grid-template-columns: repeat(7, minmax(0, 1fr)); gap: 6px; padding: 4px 0;
}
.tarot-back {
  padding: 0; border: 0; background: transparent; cursor: pointer; border-radius: 6px;
  min-height: var(--tap); transition: transform .2s ease;
}
.tarot-back img { display: block; width: 100%; height: auto; border-radius: 6px; box-shadow: 0 1px 3px rgba(0,0,0,.25); }
.tarot-back:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
.tarot-back:not(:disabled):hover { transform: translateY(-4px); }
.tarot-back:disabled { cursor: default; opacity: .55; }
.tarot-back.tarot-chosen { opacity: 1; transform: translateY(-8px); }
.tarot-face { display: flex; flex-direction: column; align-items: center; gap: 12px; }
.tarot-fig { margin: 0; width: min(58vw, 220px); }
.tarot-img { display: block; width: 100%; height: auto; border-radius: 8px; box-shadow: 0 2px 8px rgba(0,0,0,.25); }
.tarot-reversed { transform: rotate(180deg); }
.tarot-words { width: 100%; }
.tarot-name { margin: 0 0 6px; text-align: center; font-size: 18px; }
.tarot-words > p { margin: 8px 0 0; }
.tarot-keywords { list-style: none; display: flex; flex-wrap: wrap; justify-content: center; gap: 6px; margin: 8px 0 0; padding: 0; }
.tarot-keywords li {
  font-size: 13px; padding: 3px 10px; border-radius: 999px;
  background: var(--surface); border: 1px solid var(--line); color: var(--ink-soft);
}
.tarot-flavour { color: var(--ink-soft); }
.tarot-turn { animation: tarot-turn .45s ease-out both; }
@keyframes tarot-turn {
  from { transform: perspective(800px) rotateY(90deg); opacity: .2; }
  to   { transform: perspective(800px) rotateY(0); opacity: 1; }
}
@media (prefers-reduced-motion: reduce) {
  .tarot-turn { animation: none; }
  .tarot-back, .tarot-back:not(:disabled):hover { transition: none; transform: none; }
}

/* the morning notification: the explainer under a day card, and Account's settings */
.push-offer {
  margin-top: 18px; padding: 14px; border-radius: var(--round);
  background: var(--surface); border: 1px solid var(--accent);
}
.push-offer h3 { margin: 0 0 6px; font-size: 16px; }
.push-offer p { margin: 0 0 6px; }
.push-actions { display: flex; gap: 8px; flex-wrap: wrap; margin-top: 10px; }
.push-actions .btn { flex: 1 1 140px; }
.push-done { margin: 0; font-weight: 600; }
.push-status:empty { display: none; }
.push-settings h3 { margin: 0 0 8px; font-size: 16px; }
.push-settings p { margin: 6px 0 0; }
.push-toggle, .push-hour {
  display: flex; align-items: center; gap: 10px; min-height: var(--tap);
}
.push-toggle input { width: 22px; height: 22px; accent-color: var(--accent); }
.push-hour > span { white-space: nowrap; }
.push-hour select {
  width: auto; flex: 0 0 auto; min-width: 7.5em; appearance: auto;   /* the native arrow: it is a picker, not a text box */
  margin-left: auto; font: inherit; min-height: var(--tap); padding: 0 10px;
  border: 1px solid var(--line-strong); border-radius: 10px; background: var(--surface); color: var(--ink);
}
.push-settings:not([hidden]) { padding-bottom: 16px; margin-bottom: 16px; border-bottom: 1px solid var(--line); }
