/* ==================================================================
   Reduced motion (global) — cognitive & sensory accessibility (8C / D7).
   Honors the OS "Reduce Motion" setting for everyone, site-wide, so
   animations, transitions, and smooth-scroll are effectively neutralized
   (helps autistic / vestibular / migraine / ADHD users). The natal wheel
   has its own targeted rules too; this is the safety net for the rest of
   the site (spinners, fades, transforms). Near-zero (not 0) keeps any
   JS that waits on transitionend/animationend firing.
   ================================================================== */
@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: 0.001ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.001ms !important;
    scroll-behavior: auto !important;
  }
}

/* ==================================================================
   Calm / reduced-stimulation mode (D7 Q47) — opt-in via the footer
   toggle (persisted in localStorage; the `calm` class is set on <html>
   before paint). Flattens the busy paper-grain textures, mutes the
   brightest orange accents, forces motion off regardless of the OS
   setting, and adds breathing room. Helps autistic / sensory-sensitive
   / migraine / ADHD users who want a quieter page.
   ================================================================== */
html.calm body { background: #f3f1ea; line-height: 1.65; }
html.calm body.paper-bg,
html.calm .paper_box,
html.calm .paper_box_wide,
html.calm .paper_dropdown,
html.calm .shadow-paper { background-image: none !important; }
html.calm { --logo-color: #b07a42; }                 /* muted logo */
html.calm .text-orange-600 { color: #8f5316 !important; }  /* muted, AA-dark */
html.calm *, html.calm *::before, html.calm *::after {
  animation-duration: 0.001ms !important;
  animation-iteration-count: 1 !important;
  transition-duration: 0.001ms !important;
  scroll-behavior: auto !important;
}
/* The footer toggle control */
.calm-toggle {
  background: none;
  border: 1px solid currentColor;
  border-radius: 9999px;
  padding: 2px 10px;
  font: inherit;
  cursor: pointer;
  color: inherit;
}
.calm-toggle:focus-visible { outline: 2px solid #fb8500; outline-offset: 2px; }

/* ==================================================================
   "In plain words" anchor (cognitive accessibility / D7 Q47) — a literal,
   plain-language takeaway that opens a lyrical reading. Soft blue accent,
   navy text (AA contrast); calm + reduced-motion friendly (no animation).
   ================================================================== */
.plain-words {
  border-left: 3px solid #8ecae6;
  background: rgba(142, 202, 230, 0.12);
  border-radius: 0 6px 6px 0;
  padding: 0.5rem 0.75rem;
  margin: 0.25rem 0 0.5rem;
}
.plain-words__label {
  display: block;
  font-size: 0.72rem;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  font-weight: 700;
  color: #023047;
  margin-bottom: 0.15rem;
}
.plain-words__body { margin: 0; color: #023047; }
.plain-words__tag { font-weight: 700; }
html.calm .plain-words { background: rgba(142, 202, 230, 0.18); }

/* ==================================================================
   Type scale — named, rem-based (respects the user's browser font
   size for accessibility). One ratio, six steps. Body >= 16px,
   secondary text >= 14px, line-height ~1.5 for readable copy.
   Use the .t-* classes; vars let us tune the whole scale in one place.
   ================================================================== */
:root {
  --t-display: 2rem;     /* page hero (fancy-header), 32px — was over-loud at 40px */
  --t-h1: 1.5rem;        /* section title   (= old text-2xl) */
  --t-h2: 1.25rem;       /* subsection      (= old text-xl) */
  --t-h3: 1.125rem;      /* minor header    (= old text-lg) */
  --t-body: 1rem;        /* body copy, 16px (= old text-base) */
  --t-small: 0.875rem;   /* captions, meta  (= old text-sm) */
  --t-micro: 0.75rem;    /* dense data, chips (= old text-xs) */

  /* ★ ONE ORANGE, AND IT IS THE LETTER'S (owner, 2026-09-27: "All of the orange is
     reading brown", then "sweep the site templates too"). ★ Same value as
     branding.EMBER, so the site and the Pocket Horoscope cannot come to disagree about
     what our accent looks like — they had drifted to FIVE spellings in the letter and
     four more here.

     Why this value: darkening amber along its own hue is what made it brown, so the
     hue moved toward red (32 degrees -> 22) and the RATIO was held. 6.87:1 on the site
     paper #fffaf0 and 6.06:1 on the .tstand__rank pill.

     It REPLACES #a95a00, which was the site's own orange at 4.86:1 on paper — and
     4.29:1 on that pill, which is under AA. Nothing was using that combination yet;
     one token means it cannot start. */
  --ember: #9a3800;
}

/* ==================================================================
   ★ LARGER TEXT, AS A READER'S OWN SETTING (owner, 2026-09-19). ★
   "Can we also add a large text option in account settings? ... follow
   accessibility best practice on that one while still maintaining our
   style sheet guidelines."

   THE WHOLE IMPLEMENTATION IS THIS ONE RULE, AND THAT IS THE POINT.
   Every step above is in rem, so moving the ROOT font size moves the
   entire scale together and every ratio in it is preserved. Nothing
   overrides an individual token, no page gets a special case, and the
   standing rule -- one scale, seven steps, defined once -- survives a
   feature that would otherwise be spelled as seven more numbers.

   It is also what accessibility best practice asks for:
     · the reader's BROWSER setting still works and still compounds,
       because a percentage on html scales whatever they already chose
       (this is why the scale is rem and why 1.4.4 is satisfied);
     · two steps rather than a slider, because three sizes a reader can
       tell apart beat a continuum nobody can aim;
     · applied server-side from their saved preference, so there is no
       flash of small text before a script runs;
     · -webkit-text-size-adjust stays 100% below, so iOS does not add a
       second, invisible scaling of its own on top.

   112.5% = 18px base, 125% = 20px. Both are ordinary browser zoom
   steps, so line lengths and wrapping stay in territory the layout has
   already been seen at.
   ================================================================== */
:root[data-text-size="large"]  { font-size: 112.5%; }
:root[data-text-size="larger"] { font-size: 125%; }

/* ------------------------------------------------------------------
   SCREEN-SIZE ADJUSTMENTS (added 2026-08-02, owner's ask).
   The scale is tuned HERE, once, in the tokens — never per page. Every
   .t-* consumer on the site adapts automatically, which is the whole
   reason the tokens exist.

   RULE, and it is not negotiable: only the HEADING steps move. Body,
   small and micro NEVER shrink — they already sit on the accessibility
   floor (16 / 14 / 12px), and shrinking text to make a layout fit is
   the bug, not the fix. If it does not fit, change the layout.

   Phone: a 32px hero in a 390px column is shouting, and it is the
   single loudest thing on a small screen. Stepped down through the top
   three only.

   Tablet (640-1023): given its own tier deliberately. Tablets were
   previously handed the untuned DESKTOP treatment because the
   breakpoint is 640px, which is exactly how the cosmic strip ended up
   116px tall at 768 (fixed 2026-07-30). Do not let that recur.

   >= 1024 keeps today's values EXACTLY, so this change cannot regress
   any page that has already been reviewed at desktop width.
   ------------------------------------------------------------------ */
@media (max-width: 639px) {
  :root {
    --t-display: 1.625rem;  /* 26px — was 32px */
    --t-h1: 1.375rem;       /* 22px — was 24px */
    --t-h2: 1.1875rem;      /* 19px — was 20px */

    /* ★ THE READING STEPS GO UP ON A PHONE (owner, 2026-09-19: "darken and enlarge the
       fonts across the full site for small screens"). ★ The rule above used to say body,
       small and micro are identical at every width because they are already on the floor.
       That was true of the SCALE and wrong about the reader: the floor is the minimum a
       thing may be, not the right size for the screen it is hardest to read on. A phone is
       held at arm's length in whatever light the morning has, and this letter is read
       before the day starts.

       One step each, so the relationships in the scale are untouched — nothing crosses
       another step, and every page keeps its hierarchy. The HEADINGS deliberately do NOT
       go back up: they were reduced here because 32px read as over-loud on a narrow
       screen, and that finding has not changed. This is about the text you have to read,
       not the hero.

       Desktop and tablet are untouched, so no reviewed page can regress at those widths —
       the same guarantee the tiering was built with. */
    --t-body: 1.0625rem;    /* 17px — was 16 */
    --t-small: 0.9375rem;   /* 15px — was 14 */
    --t-micro: 0.8125rem;   /* 13px — was 12, and this is the one that hurt */
  }
}
@media (min-width: 640px) and (max-width: 1023px) {
  :root {
    --t-display: 1.8125rem; /* 29px — between phone and desktop */
    --t-h1: 1.4375rem;      /* 23px */
    /* h2 and below unchanged. */
  }
}
/* ══════════════════════════════════════════════════════════════════════════════════
   ★ WHICH STEP FOR WHAT — WRITTEN DOWN BECAUSE IT KEPT COMING UP (owner, 2026-09-07). ★
   "Let's make sure that the font size preferences are recorded in the style sheet
   because it keeps coming up as we move through the site."

   The seven steps above say what the sizes ARE. This says which one to REACH FOR, so the
   question is answered here rather than re-litigated on every page.

     t-display  the page's one hero. One per page, or it is not a hero.
     t-h1       a section title.
     t-h2       a subsection.
     t-h3       a minor header — and the largest thing that belongs INSIDE a card.
     t-body     prose. ★ THE FLOOR FOR ANYTHING A READER READS AS SENTENCES. ★
     t-small    captions, meta, and LABELS IN A DENSE LIST — a name in a grid of tiles is
                a label, not prose, and this is the step it wants.
     t-micro    dense data, chips, counts. ★ THE ABSOLUTE FLOOR. Nothing below it. ★

   FIVE RULES, all of which this project has already paid for:

   1. NEVER a raw `font-size` in a template. Use a .t-* class, or var(--t-*) in a rule.
      MEASURED on /nest 2026-09-07: eleven hardcoded sizes in one block, SEVEN of them
      below the 12px floor — 0.58rem is 9.3px.

   2. NEVER shrink text to make a layout fit. If it does not fit, the layout is wrong.

   3. rem or var(), never px. A px size ignores the reader's own browser font setting
      (WCAG 2.2 AA, 1.4.4 Resize Text). `em` IS allowed as a deliberate relative tweak,
      and is the right tool for an EMOJI or a glyph — it is a picture, not prose, and it
      should scale with whatever step its container is on.

   4. ★ A TAILWIND TEXT UTILITY LOSES TO A .t-* CLASS, SILENTLY. ★ base.html loads
      tailwind.css BEFORE styles.css and the two have the same specificity, so the later
      one wins. MEASURED 2026-09-07: 23 headings across /nest carried
      `class="t-body font-semibold sm:text-lg"` and rendered at 16px at EVERY width — the
      sm:text-lg had never once applied. It is compiled and present in tailwind.css; it
      simply never wins. So `text-lg`, `text-sm`, `text-xs` and friends are not a second
      way to set a size here: they are dead markup that reads like intent. Do not add
      them, and delete them when you meet them.

   5. Screen-size adjustments live in the TOKENS, never per page. Only the heading steps
      move; body/small/micro are identical at every width because they are already on the
      floor.

   Measure, do not eyeball: `venv/bin/python scripts/type_scale.py`, --source for
   file:line and unit violations, --rendered for computed sizes at 390/768/1440.
   ══════════════════════════════════════════════════════════════════════════════════ */

/* Font-size only — line-height is left to existing leading-* utilities so the
   rename across the site is a pure, zero-regression size standardization. */
.t-display { font-size: var(--t-display); }
.t-h1 { font-size: var(--t-h1); }
.t-h2 { font-size: var(--t-h2); }
.t-h3 { font-size: var(--t-h3); }
.t-body { font-size: var(--t-body); }
.t-small { font-size: var(--t-small); }
.t-micro { font-size: var(--t-micro); }

/* ══════════════════════════════════════════════════════════════════════════════════
   CHART-PAGE DESIGN SYSTEM — one source of truth for the western / vedic / chinese chart
   + detail pages. WESTERN is the reference; vedic + chinese match it. Full conventions
   (type scale, ink, card anatomy, reading cards, section headers) live in
   docs/CHART_PAGE_STYLE.md. Reusable macros live in templates/_chart_detail.html.
   Change a value HERE, once — never per page.
   ══════════════════════════════════════════════════════════════════════════════════ */
:root {
  --card-border: #d4cfc4;                 /* header/detail card + reading-card border */
  --card-rule:   rgba(138,161,171,.4);    /* #8aa1ab @40% — the teal divider strip */
  /* Zodiac elements — western + vedic sign colouring */
  --elem-fire:  #c1440e;  --elem-earth: #6b7d3a;  --elem-air: #3f7a8c;  --elem-water: #2c6e8f;
  /* Chinese five elements (Wu Xing) */
  --wuxing-wood: #3f8a4f; --wuxing-fire: #c0392b; --wuxing-earth: #b8860b;
  --wuxing-metal:#8a8f99; --wuxing-water:#2c6fb3;
}
/* Element-coloured text (sign/phase glyphs, ✦, chips). Pick .elem-<zodiac element>
   (western/vedic) or .wuxing-<phase> (chinese). */
.elem-fire{color:var(--elem-fire)}   .elem-earth{color:var(--elem-earth)}
.elem-air{color:var(--elem-air)}     .elem-water{color:var(--elem-water)}
.wuxing-wood{color:var(--wuxing-wood)}   .wuxing-fire{color:var(--wuxing-fire)}
.wuxing-earth{color:var(--wuxing-earth)} .wuxing-metal{color:var(--wuxing-metal)}
.wuxing-water{color:var(--wuxing-water)}
/* Glyphs are reset to `currentcolor` site-wide (.astro-glyph,…{color:currentcolor}); these
   compound rules out-specify that so an element class actually colours a glyph. */
.astro-glyph.elem-fire{color:var(--elem-fire)}     .astro-glyph.elem-earth{color:var(--elem-earth)}
.astro-glyph.elem-air{color:var(--elem-air)}       .astro-glyph.elem-water{color:var(--elem-water)}
.astro-glyph.wuxing-wood{color:var(--wuxing-wood)} .astro-glyph.wuxing-fire{color:var(--wuxing-fire)}
.astro-glyph.wuxing-earth{color:var(--wuxing-earth)} .astro-glyph.wuxing-metal{color:var(--wuxing-metal)}
.astro-glyph.wuxing-water{color:var(--wuxing-water)}

/* Breadcrumb back-link — sits ABOVE the page's paper box on every detail page. */
.chart-crumb{ font-family:ui-sans-serif, system-ui, -apple-system, "Segoe UI", Roboto, sans-serif; font-size:var(--t-micro); color:#6b7280; }
.chart-crumb a{ transition:color .15s ease; }
.chart-crumb a:hover{ color:#5d7782; }

/* The header/detail info card (the "House N / Bhava N of … in <sign>" summary). */
.chart-detail-card{ border:1px solid var(--card-border); border-radius:.5rem; overflow:hidden; }
.chart-detail-card__body{
  background:rgba(255,255,255,.4); padding:.625rem .75rem;
  font-family:"Special Elite", Georgia, serif;
}

/* The teal rule between the header card and the narrative (also a section-top divider). */
.chart-card-rule{ border-top:2px solid var(--card-rule); }

/* Narrative / reading prose — the body voice on every chart page: softened gray-700 ink,
   Trocchi serif (inherited from body), 14px, relaxed leading. Wrap reading paragraphs in it. */
.reading-prose{ font-size:var(--t-small); color:#374151; line-height:1.7; }
/* Chinese pages: compact the reading prose + detail-page title to match the polished chart-page
   size (the chart cards read at ~0.82rem; the §14 detail pages were still at 14px/loose). */
body.chinese-chart .reading-prose{ font-size:0.82rem; line-height:1.6; }
body.chinese-chart .reading-prose p{ margin-bottom:0.7rem; }

/* Reading card — the boxed AI-reading blocks (tab readings, spotlights) on chart pages. */
.reading-card{
  border:1px solid var(--card-border); border-radius:.75rem;
  box-shadow:0 1px 2px rgba(0,0,0,.05);
  background:#fefae0 url("/static/images/paper.png") repeat;
  padding:.625rem .75rem;
}
/* Shared Readings-accordion body + link (promoted from western's house-full / house-full-link so
   /vedic + /chinese Readings tabs match western EXACTLY, not approximately). */
/* ★ TWO INVENTED STEPS, BOTH OFF THE SCALE (owner, 2026-08-21). ★ The text-size standard
   is seven steps defined once, and "never invent a step". .chart-full was 0.82rem = 13.12px,
   which is not one of them, and it is the narrative class on /chart/vedic and /chart/chinese
   (18 uses across six templates), so those pages' readings sat at a size that appears
   nowhere else on the site. It is flowing prose, so it takes --t-small, the standard's
   floor for secondary text, rather than being rounded DOWN toward the absolute floor.
   .chart-full-link was 0.74rem = 11.84px, BELOW the 12px absolute floor: "nothing a reader
   is meant to read goes below 12px", and it is a link a reader is meant to read. */
.chart-full { border-top:1px solid #e0dccf; padding-top:0.6rem; font-size:var(--t-small); color:#374151; line-height:1.65; }
.chart-full p { margin-bottom:0.7rem; }
.chart-full p:last-child { margin-bottom:0; }
.chart-full-link { display:inline-block; margin-top:0.6rem; font-size:var(--t-micro); font-weight:600; color:#5d7782; text-decoration:none; }
.chart-full-link:hover { color:#023047; }
.chart-full-link:focus-visible { outline:2px solid #8aa1ab; outline-offset:2px; border-radius:2px; }
/* The header-card one-liner teaser — 12px Trocchi, muted. Its OWN class (not .trocchi-text)
   so the body.western-wheel .trocchi-text size rule can't inflate it past t-micro. */
.chart-card-oneliner{ font-family:"Trocchi",serif; font-size:var(--t-micro); color:#4b5563; line-height:1.4; }

/* ================================================================================== Brand fonts =======================================================================
   ★ NEW AMERICAN VINTAGE WAS REMOVED ON 2026-08-14, AND NOT FOR TASTE. ★ It is a Lucky
   Type commercial face held under a Creative Fabrica subscription whose licence (v1.6)
   prohibits exactly this use: "(a) Embedding files, including fonts, into software,
   applications, ebooks, documents, or websites (for example, webfont usage)" and
   "(d) Installing assets on a server for use by multiple users or automated processes".
   @font-face is all three of webfont use, server installation, and transferring the file
   to every reader. See THIRD_PARTY_NOTICES.md for the clauses and the reasoning.

   ★ THE LOGO KEEPS THE ORIGINAL FACE, LEGITIMATELY. ★ static/images/logo.png is a
   FLATTENED design, which the same licence expressly permits ("Fonts can be used to create
   quotes, texts, names, templates"), and no font file travels with it. That is why the
   wordmark did not have to change.

   Special Elite replaces it for headings (owner, 2026-08-16). ★ APACHE 2.0, NOT OFL —
   the first non-OFL font in the product, so it carries APACHE-SpecialElite.txt rather than
   an OFL. Self-hosted like the rest. A distressed typewriter face of the same weight and
   proportion, so the pages read as they did. woff2 only — 26 KB against the old 200 KB TTF,
   and every browser we support has handled woff2 for years. */
@font-face {
  font-family: "Special Elite";
  src: url("/static/fonts/SpecialElite-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-weight: 400;
  font-style: normal;
  font-display: swap;
}

@font-face {
  font-family: "Special Elite";
  src: url("/static/fonts/SpecialElite-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;
  font-weight: 400;
  font-style: normal;
  font-display: swap;
}

/* Symbols (served locally) — keep woff2 if you have it */
@font-face{
  font-family: "AstroSymbols";
  src: url("/static/fonts/NotoSansSymbols2-AstroSymbols.woff2") format("woff2");
  font-weight: 400;
  font-style: normal;
  font-display: swap;
  unicode-range: U+2609, U+263D-263F, U+2640-2647, U+2648-2653, U+260A-260B, U+26B7;
}


/* ================================================================================== Global base + typography ================================================================================*/

html { -webkit-text-size-adjust: 100%; }
body {
  font-family: "Trocchi", Georgia, "Times New Roman", serif;
  background: #f5f2e8;
  /* Body/reading ink — a softened gray-700 (#374151), not full navy. This is the
     single source of truth for default prose color across the site (the /wheel house
     reading already used text-gray-700; everything else now matches). Headers
     (h1–h5/.fancy-header), <strong>, and emphasis set the deep navy #023047
     explicitly, so ONLY default body prose picks this up. NOTE: base.html used to put
     a `text-gray-900` utility on <body> that overrode this — it was removed so this
     rule wins. Change the ink here, in one place. */
  color: #374151;
  text-rendering: optimizeLegibility;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
}

/* ★ TWO SUBSETS, AND THE LATIN ONE IS THE ESSENTIAL HALF. ★ Google Fonts serves this face
   as latin-ext (U+0100 upward: accented and extended letters) and latin (U+0000-00FF: the
   ordinary alphabet). The first swap shipped ONLY latin-ext, which contains no basic ASCII
   at all — so every normal English letter fell back to Georgia and the typewriter face was
   invisible on the page while being technically "installed". It looked like a subtle
   design change rather than a broken font, which is why it needed the owner's eye to
   catch. unicode-range lets the browser pick the right file per character. */
/* Headings + feature headers use Special Elite (Apache 2.0) */
h1, h2, h3, h4, h5, .fancy-header {
  font-family: "Special Elite", Georgia, serif;
  color: #023047;
}
/* If you don’t have an Outline TTF, keep this fallback */
.fancy-header--outline { font-family: "Special Elite", Georgia, serif; }

/* ★ THE COSMIC STRIP AND THE NEST BUTTON JOIN THE HEADINGS (owner, 2026-08-19). ★ Same
   stack as .fancy-header above rather than a second declaration of it — one value, one
   table, which is the rule this file keeps relearning. The strip sets the family on the
   container so its names and labels move together. ★ AND THE GLYPHS NEEDED SAYING OUT
   LOUD. ★ I first wrote that AstroSymbols "still wins because that rule is more specific"
   — it does not: .cosmic-strip .zodiac-glyph sets no family at all, so the glyphs were
   inheriting whatever the container had and would now inherit a Latin typewriter face
   with no ♌︎ in it, leaving them to per-character fallback. Named explicitly below
   instead of trusted to luck.
   Only the two auth links exist, Sign In and Nest, so scoping to .auth-link is exact. */
.cosmic-strip, .auth-link { font-family: "Special Elite", Georgia, serif; }

/* ★ STARLING IS THINKING (owner, 2026-08-19). ★ The steaming mug, used wherever a page
   is loading, something is processing, or a reading is being written.

   ★ THE SOURCE WAS 4.6 MB, WHICH IS AN ODD THING FOR A LOADING INDICATOR TO BE. ★
   steamy_mug.png is a 2550x3300 APNG of six frames; at that weight it would itself be the
   slowest thing on the page. steamy_mug_180.png is the same six frames cropped to the
   drawing (measured from the alpha across ALL frames, because the steam reaches higher
   than the mug does) and scaled to 180px tall: 60 KB. The original is untouched.

   ★ REDUCED MOTION GETS A STILL, NOT A HIDDEN SPINNER. ★ An APNG cannot be paused from
   CSS, so the still frame is a separate file and the media query swaps to it. Somebody who
   asked for less motion should still be told we are working. */
.starling-thinking {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 10px;
  color: #023047;
}
.starling-thinking__mug {
  width: 108px;
  height: 180px;
  background: center/contain no-repeat;
  /* ★ SIX APNG FRAMES ARE 59.5 KB; THE SAME SIX AS ANIMATED WEBP ARE 19.3 KB. ★ The plain
     url() is declared FIRST so a browser that cannot parse image-set() keeps the PNG; one
     that can overrides it and fetches only the webp. Neither ever fetches both. */
  background-image: url("/static/images/steamy_mug_180.png");
  background-image: image-set(url("/static/images/steamy_mug_180.webp") type("image/webp"),
                              url("/static/images/steamy_mug_180.png") type("image/png"));
}

/* ★ A DORMANT VEIL STILL DOWNLOADS ITS BACKGROUNDS (MEASURED 2026-09-30). ★ .starling-veil
   sits in every page at visibility:hidden so it can fade in — and visibility, unlike
   display:none, does NOT stop a background-image being fetched. So this loading animation
   was downloaded by every reader on every page, to be shown on almost none of them.
   Suppressing it here rather than switching the veil to display:none keeps the opacity
   transition the veil exists for; base.html prefetches the file at idle priority so it is
   in cache before the veil is ever raised. An inline .starling-thinking outside a veil is
   untouched and still loads eagerly, which is right — that one is already on screen. */
.starling-veil:not([data-on="1"]) .starling-thinking__mug { background-image: none; }
.starling-thinking__label {
  font-family: "Special Elite", Georgia, serif;
  font-size: 1rem;
  color: #023047;
  text-align: center;
}
@media (prefers-reduced-motion: reduce) {
  .starling-thinking__mug { background-image: url("/static/images/steamy_mug_still.png"); }
}

/* The full-page version: a veil over the paper while a slow page is being built. */
/* ★ HIDDEN, NOT display:none (2026-09-12). ★ The owner, on an iPhone: page transitions
   "are not showing the steamy_mug". Under display:none the mug's background image is not
   fetched until the veil turns on, 450ms into a navigation that is already under way, and a
   page that is leaving is the worst moment to start a download. Laid out but invisible, the
   image is fetched and decoded with the page, so the veil has it the instant it appears.
   visibility:hidden also keeps it out of the tab order and the pointer's way. */
.starling-veil {
  position: fixed;
  inset: 0;
  z-index: 9999;
  display: flex;
  visibility: hidden;
  opacity: 0;
  pointer-events: none;
  align-items: center;
  justify-content: center;
  background: rgba(250, 244, 232, 0.92);
}
.starling-veil[data-on="1"] { visibility: visible; opacity: 1; pointer-events: auto; }
/* ★ THE VEIL FOLLOWED THE SYSTEM, NOT US — FOUND 2026-10-19. ★ This was a
   `@media (prefers-color-scheme: dark)` block, which is the ONE thing base.html's theme
   comment says we do not do: dark here is opt-in, chosen in our own footer, and it stays
   that way until the contrast scan reads zero site-wide. So the full-screen loading
   overlay read the PHONE's setting while every other surface read ours, and the two
   disagree for most readers:

     phone dark + our light  ->  a DARK veil flashing over a light page
     phone light + our dark  ->  a LIGHT veil flashing over a dark page

   It is the most visible element on the site when it is up, covering everything, so this
   is not a corner case — it is a wrong-coloured flash on every navigation that raises it.
   Keyed to data-theme now, like everything else. */
:root[data-theme="dark"] .starling-veil { background: rgba(1, 34, 50, 0.93); }
:root[data-theme="dark"] .starling-thinking,
:root[data-theme="dark"] .starling-thinking__label { color: var(--d-ink); }

/* ★ THE NEST BUTTON IS ARTWORK (owner, 2026-08-19; new art 2026-08-20). ★ nest.png is
   900x900 and mostly transparent padding, so it is cropped as a BACKGROUND and the file
   itself is never touched: it stays the owner's asset at full size for any other use.

   ★ RE-MEASURED FOR THE NEW ART, BECAUSE EVERY NUMBER HERE DEPENDS ON IT. ★ Three
   drawings so far: x136-770,y183-559 (634x376, 1.69:1), then x31-851,y205-527 (820x322,
   2.55:1), and since 2026-09-05 x134-788,y273-561 (654x288, 2.27:1). Each one changed the
   SHAPE, so the size, position and box ratio were all wrong for the next - which is the
   whole reason scripts/make_nest_button.py now exists and prints them. Taken from the alpha channel with PIL, never guessed.

   The maths, so the next replacement can be done from the bbox alone:
     background-size     image / crop           900/820 = 109.756%, 900/322 = 279.503%
     background-position crop / (image - crop)  31/80 = 38.75%, 205/578 = 35.467%
   CSS percentage positioning aligns the same relative point of image and box, which is
   why the offset is over (image - crop) and not over the image.

   ★ aspect-ratio FROM THE MEASURED CROP, so the drawing cannot be stretched. ★ The two
   background axes scale independently, so a box of any other shape distorts the art. The
   ratio is now declared once, from the same two numbers as the crop, rather than being
   implied by a width and a height that could drift apart.

   ★ 76px DECLARED, 61px RENDERED - AND THE DIAGNOSIS BELOW WAS WRONG. ★ This note used to
   read "because it is a flex item in the header and was being shrunk to fit", and credited
   flex: 0 0 auto with fixing it. RE-MEASURED 2026-08-23, with that flex declaration in
   place the whole time: still 61px. The binding constraint was PADDING, not flex - see the
   two-class rule below for the arithmetic. flex: 0 0 auto stays as a guard and has not been
   tested in isolation, so this says nothing about whether it is load-bearing; it says only
   that it was not what was taking the width. The symptom was real and correctly recorded;
   the cause was not, and a wrong cause is the more expensive half to leave lying around. */
/* ★ THE PADDING IS WHAT WAS SQUASHING THE ART. ★ MEASURED 2026-08-23: the pill is
   width: clamp(78px, 12vw, 87px) = 87px, border-box, so 10px of padding a side plus the
   1px border left 65px of content for artwork that needs 76.4px (30px tall at 820/322).
   The old background-image version hid this by simply windowing a smaller crop; an <img>
   cannot -- it stretches to the box it is given, so the wordmark came out a fifth narrow.
   4px a side leaves 77px, which fits, and keeps the pill the same width as the menu pill
   opposite it. object-fit below is the belt to this braces. */
/* ★ TWO CLASSES, AND THAT IS THE WHOLE FIX. ★ MEASURED 2026-08-23: this rule sets the
   nest pill's padding at line ~400, and `.auth-link { padding: 0 .75rem }` sets it again
   at line ~770. Equal specificity, so the LATER one won and the padding was 12px a side,
   not 4 -- leaving 87 - 24 - 2 = 61px of content for artwork that needs 76.4px. That 61 is
   the exact number the note above this rule records, and it is why adding flex: 0 0 auto
   never brought the width back: flex was not what was taking it. `.auth-link.auth-link--nest` is two classes and wins wherever it sits. */
.auth-link.auth-link--nest { display: inline-flex; align-items: center; padding: 6px 4px; }
.nest-btn-img {
  display: block;
  /* Kept as a guard against a future header layout shrinking this. It is NOT what fixed
     the 61px squash -- see the corrected note above the pill rule -- and it has not been
     tested in isolation, so it is left in place rather than removed on a guess. */
  flex: 0 0 auto;
  height: 30px;
  width: auto;
  /* ★ FROM THE ARTWORK'S ALPHA BBOX, AND IT MOVES WHEN THE ART DOES. ★ New art
     2026-09-05 ("a bit more space between the letters and the bottom of the nest
     constellation") changed the crop from 820x322 (2.55:1) to 654x288 (2.27:1) -- a
     different SHAPE, so the old ratio would have letterboxed the new drawing inside a box
     built for the old one. scripts/make_nest_button.py measures the bbox, rebuilds the
     1x/2x/3x files and prints this line; do not hand-edit it. */
  aspect-ratio: 654 / 288;
  /* ★ contain, so a box that is ever too small scales the art instead of distorting it. ★
     The default for an <img> is fill, which is exactly how a 76px-wide wordmark in a 61px
     box became a wordmark a fifth too narrow. A letterboxed Nest is a smaller Nest; a
     stretched one is a different logo. */
  object-fit: contain;
  /* The file is now the crop, at the size it is drawn -- no background window, no 10.7x
     squeeze for the browser to alias. See _banner.html for the measurement. */
}
@media (max-width: 640px) {
  .nest-btn-img { height: 24px; }
}
/* The zodiac glyphs keep the face that actually contains them. */
.cosmic-strip .zodiac-glyph { font-family: "AstroSymbols", "Segoe UI Symbol", sans-serif; }

/* Strong colors */
strong { color:#023047; font-weight:bold; }
.orange, .strong-orange, strong.orange, strong.strong-orange { color:var(--ember); }  /* was strong-only + #b35900 (4.41:1); a <span class="strong-orange"> matched nothing and inherited #fb8500 at 2.27:1; then #a95a00, which read brown beside the letter and was 4.29:1 on a tinted pill */
strong.strong-navy { color:#023047; }

.deg-readout{
  font-variant-numeric: tabular-nums;
  font-weight: 700;
  font-size: 1.15em;
  letter-spacing: .01em;
  white-space: nowrap; /* keep 03° 58′ on one line */
}
.sign-chunk{
  display: inline-flex;
  align-items: baseline;
  gap: .35rem;          /* degree ↔ sign breathing room */
  white-space: nowrap;  /* keep the whole unit together */
}

.text-navy{
  color:#023047;
}

.current-sky-label{
  font-family:"Trocchi", serif;
  font-weight:700;
  color:#023047;
}

.degree-readout{
  font-family:"Trocchi", serif;
  font-variant-numeric: tabular-nums;
  font-weight:700;
  font-size:1.15em;
  letter-spacing:.01em;
  white-space:nowrap;
  color:#023047;
}


/* utilities */
.indent-first { text-indent: 1.25rem; }

/* type + spacing utilities (Tailwind-ish) */
.text-sm      { font-size: .875rem; line-height: 1.5; }
.text-gray-600{ color: #4b5563; }            /* or: color: rgba(2,48,71,.75); */
.mt-0         { margin-top: 0 !important; }
.mb-6         { margin-bottom: 1.5rem !important; }

/* type + spacing utilities (Tailwind-ish) */
.text-sm      { font-size: .875rem; line-height: 1.5; }
.text-gray-600{ color: #4b5563; }            /* or: color: rgba(2,48,71,.75); */

/* ✅ add these two */
.text-gray-800{ color: #1f2937; }
/* ★ 8C: THE BRAND ORANGE IS NOT A TEXT COLOUR. ★ #fb8500 on our paper (#faf4e8)
   measures 2.27:1 — half of the 4.5:1 that body-size text needs, and this class is
   used on 43 LINKS. It is a fine colour for a rule, an icon, a focus ring or a large
   heading, where the requirement is 3:1 or nothing at all; it simply cannot carry
   14px prose. Darkened along its own hue to #a95a00 (4.63:1) — and "along its own
   hue" is exactly what later made it read brown beside the letter, so it is now
   --ember, the same value the Pocket Horoscope uses (6.87:1 on this paper).
   If the brand wants the brighter orange back, the place for it is decoration —
   not link text. */
.text-orange-600{ color:var(--ember) !important; }  /* was #fb8500, 2.27:1 -> 4.63:1; force wins over inherited/navy */

/* ★ 8C: Tailwind's gray-400 (#6a7383) is a placeholder grey on white, and we do not
   have white. On our paper backgrounds it measures 2.18:1 across 80 uses — the
   parentheticals on the Vedic toggles, separators, captions. Everything this class
   was reaching for (quieter than the body ink) survives at #626b79, which clears
   4.5:1 on the lightest background we actually use. */
.text-gray-400{ color: #626b79; }  /* was Tailwind #6a7383, 2.18:1 -> 4.63:1 */

/* ★ 8C: text-gray-500 WAS FIXED ONCE, IN ONE PLACE, IN JUNE. ★ The 2026-06-06 pass
   recorded "footer text contrast #6b7280 on #f5f2e8 = 4.31:1 -> #4b5563" — and fixed
   the FOOTER, leaving the class itself on Tailwind's #6b7280 for the other 475 uses.
   It surfaced again on /signin, the first page a new reader ever sees.

   ★ AND IT HAS TO BE SOLVED AGAINST THE DARKEST PAPER, NOT THE LIGHTEST. ★ #6b7280
   measures 4.67:1 on #fdfbf4 and PASSES — then 4.32:1 on #f5f2e8 and fails. Checking
   one background would have "proved" the colour fine. #676d7b clears 4.5:1 on all
   three of our paper tones (4.63 / 4.73 / 5.01). */
.text-gray-500{ color: #676d7b; }  /* was Tailwind #6b7280, 4.32:1 on our darkest paper -> 4.63:1 */

/* ★ 8C: text-gray-300 at 1.32:1 — axe passed it, and axe was not wrong. ★ All 21 uses
   are the em-dash placeholder that means "no value for this cell". They sit inside
   x-show branches, so a scan often finds them hidden and skips them — which is why
   this needed a sweep of the PALETTE, not only of the pages.

   It is still text carrying meaning: at 1.32:1 the cell reads as blank, and "we have
   no data" becomes indistinguishable from "this failed to render". Same ink as the
   other secondary text, which is what a placeholder should have been all along. */
.text-gray-300{ color: #676d7b; }  /* was Tailwind #d1d5db, 1.32:1 -> 4.63:1 */

/* optional: other indent styles */
.indent       { margin-left: 1.25rem; }
.hanging-indent{ padding-left: 1.25rem; text-indent: -1.25rem; }

/* ===== Current Sky: one-paragraph-tight blocks ===== */
.current-sky-list{ list-style:none; margin:0; padding:0; }

/* small space between planet blocks */
.current-sky-list > li{ margin:0 0 .6rem 0; }

/* 1) reset ALL margins inside each block */
.current-sky-list li > *{ margin:0 !important; }

/* 2) then apply ONE consistent tiny gap between consecutive lines */
.current-sky-list li > * + *{ margin-top:.12rem !important; }

/* 3) compact line-height just in this section */
.current-sky-list .text-sm{ line-height:1.35 !important; }

/* 4) header sits tight, same rhythm */
.current-sky-list .sky-line{
  margin:0 !important;
  line-height:1.3;
  font-size:1.05rem;
}

/* 5) keep the ingress line indented (but still using the same tiny gap) */
.current-sky-list p.indent-first{ text-indent:1rem; }

/* Wider spacing between all blocks except the first one under each planet section */
.current-sky-list li > *:not(:first-child) {
  margin-top: 0.75rem !important;  /* adjust 0.7–0.9rem to taste */
}

/* ===== Current Sky: spacing tweaks ===== */

/* give extra breathing room before the natal Moon section */
.current-sky-list .natal-moon-block {
  margin-top: 0.375rem !important;
}

/* Let each current-sky-block behave like "Earth Weather the Day You Arrived":
   no forced extra top margin; spacing within the block is handled by its
   inner .block spans and mt-* utilities. */
.current-sky-list .current-sky-block {
  margin-top: 0 !important;
}

/* Restore small vertical gap between stacked lines INSIDE current-sky blocks
   (e.g. between solar weather and the "The Sun is currently..." paragraph,
   and between Earth Weather conditions and the personality text) */
.current-sky-block .mt-2 {
  margin-top: 0.4rem !important;  /* tweak 0.35–0.5rem to taste */
}

/* Subtext styling for primary results details (smaller and indented) */
/* ── horoscope blocks (horoscope_blocks/*) ────────────────────────────────────────
   Shared skeleton for every block, so a new block inherits the look instead of
   inventing one. Block-specific rules go under .hb-<key> and should be rare: if a
   block needs much of its own CSS, it is probably two blocks. */
.hb { margin-top:1rem; border-top:1px solid #e4dfd4; padding-top:.8rem; }
.hb:first-child { border-top:0; padding-top:0; margin-top:0; }
/* ★ WRAPS, BECAUSE A FLEX ROW THAT CANNOT WRAP PUSHES ITS LAST CHILD OFF THE PHONE. ★
   The heading is words plus a phase emoji, and the words vary: "Waxing Gibbous In
   Sagittarius" is eleven characters longer than "Full In Leo". Without wrap the emoji
   was rendering at x=399 in a 390px viewport — measured on /nest at the larger text
   size, but it is the long sign that does it, not the setting. */
.hb-title { display:flex; flex-wrap:wrap; align-items:baseline; gap:.4rem; }
.hb-emoji { font-size:var(--t-h2); line-height:1; }
/* Two facts of equal weight, side by side. Equal because naming one of them alone is
   the mistake most of these blocks exist to prevent. */
.hb-pair { display:flex; gap:1rem; margin-top:.5rem; }
.hb-half { flex:1 1 0; min-width:0; }
.hb-label { color:#8a97a0; font-style:italic; }
.hb-value { display:flex; align-items:baseline; gap:.3rem; color:#023047;
            font-size:var(--t-h3); margin-top:.1rem; }
.hb-deg { color:#8a97a0; font-variant-numeric:tabular-nums; }
.hb-gloss { color:#3d4f57; margin:.25rem 0 0; }
.hb-void { color:#6b6350; margin:.6rem 0 0; line-height:1.55; }
.hb-natal { color:#3d4f57; margin:.6rem 0 0; line-height:1.55; }
.hb-natal-same { color:#023047; }
/* A timing flag. Never colour alone (WCAG 1.4.1) — each carries its own word. */
.hb-flag { background:#956b23; color:#fffaf0; border-radius:5px; padding:.05rem .35rem;
           font-size:var(--t-micro); margin-right:.35rem; white-space:nowrap; }
.hb-flag-soon { background:#e8f2f6; color:#1c6b8c; }
/* The reading, above the working. t-small (14px), which is what every AI narrative on
   /my-primary-results renders at — measured, twelve of twelve. See _blocks.html. */
.hb-reading { color:#3b3527; line-height:1.6; }
.hb-reading p { margin:0; }
/* The subtext under a block's headline: what each named thing IS. Micro size and a
   muted colour because it is reference, not reading — it should be there when a reader
   wants it and out of the way when they do not. */
/* A link from one body to another, on the per-body page. Underlined on hover only:
   a list of a dozen housemates and aspects all underlined at once reads as noise. */
.hb-bodylink { color:#023047; text-decoration:none; border-bottom:1px dotted #b9c2c7; }
.hb-bodylink:hover { border-bottom-color:var(--ember); color:var(--ember); }
.hb-touching-row { padding:.45rem 0; border-bottom:1px dashed #e4dfd4; }
/* The two explanatory lines under an aspect. Indented and muted so the row still scans
   as a list, with the meanings available to anyone who slows down. */
.hb-aspect-does { color:#3d4f57; margin:.15rem 0 0 1.1rem; }
.hb-aspect-what { color:#6f7b83; margin:.1rem 0 0 1.1rem; }
.hb-touching-row:last-child { border-bottom:0; }
.hb-inset, .hb-tldr { color:#6f7b83; margin:1rem 2.25rem 1.25rem; line-height:1.6; }
.hb-inset-list { margin-top:.35rem; margin-bottom:1.1rem; }
.hb-callout > * { margin-left:0; margin-right:0; }
.hb-callout > *:first-child { margin-top:0; }
.hb-callout > *:last-child { margin-bottom:0; }
.hb-callout > p, .hb-callout > ul { margin-top:.4rem; margin-bottom:0; }
.hb-tie { border-top:1px solid #e4dfd4; padding-top:.5rem; margin-top:.6rem;
          font-style:italic; color:#5b6a72; }
.hb-inset-list > li { color:#6f7b83; }
.hb-tldr { font-style:italic; }
.hb-tldr .glossed { font-style:normal; font-weight:600; color:var(--ember); }
/* Phones have no room to give away: the inset collapses toward the page edge rather
   than squeezing the line length that carries the meaning. */
@media (max-width:640px){
  .hb-inset, .hb-tldr { margin-left:1rem; margin-right:1rem; }
}
.hb-sub { color:#6f7b83; margin:.25rem 0 0; line-height:1.5; }
.hb-dot { color:#b9c2c7; margin:0 .15rem; }
.hb-natalmoon { color:#3d4f57; margin:.5rem 0 0; line-height:1.55; }
.hb-touching { margin:.5rem 0 0; }
.hb-touching li { padding:.3rem 0; border-bottom:1px dashed #e4dfd4; color:#3d4f57;
                  line-height:1.5; }
.hb-touching li:last-child { border-bottom:0; }
.hb-more { margin:.6rem 0 0; }
.hb-more a { color:var(--ember); text-decoration:underline; font-size:var(--t-small); }
@media (max-width:640px){ .hb-pair { flex-direction:column; gap:.5rem; } }

.primary-results-subtext {
  /* t-small (14px). Was 0.85rem / 13.6px — off-scale, and this class carries most
     of the reading's secondary prose, so it was the page's single biggest source
     of drift. */
  font-size: var(--t-small);
  margin-left: 1rem;
}

/* ★ THE SCALE ALWAYS WINS OVER A COMPONENT CLASS (found 2026-08-26). ★
   `.primary-results-subtext` is defined at line ~637 and `.t-micro` at ~140, so on equal
   specificity the component class won on source order — and `class="primary-results-subtext
   t-micro"` rendered at 14px when the author had explicitly asked for 12px.

   THIRTY-SEVEN SITES did that, across eight templates, and every one of them is a Vedic or
   Chinese page. That is not a coincidence: those are the traditions the owner cannot check
   line by line, which is exactly where a silent visual drift survives longest. Founding
   principle 3 is usually about structure and content; this is the same failure in
   typography.

   Two-class specificity beats one, so these win regardless of source order. The component
   keeps its indent; the scale decides the size, which is what CLAUDE.md's TEXT SIZE
   STANDARD means by "one scale, defined once". */
.primary-results-subtext.t-micro { font-size: var(--t-micro); }
.primary-results-subtext.t-small { font-size: var(--t-small); }
.primary-results-subtext.t-body  { font-size: var(--t-body); }



/* ----------------------------------------------------------------------------------------------------------------- Z layers -------------------------------------------------------------- */
:root{
  --z-paper: 10;
  --z-header: 100;
  --z-menu: 110;
  /* bump these way up */
  --z-modal-overlay: 9997;
  --z-modal: 9999;
}

.cosmic-overlay{ z-index: var(--z-modal-overlay); }
.cosmic-popup{   z-index: var(--z-modal); }

/* [x-cloak]{ display:none !important; } */

/* Lift the whole banner above page content */
header.site-banner{
  position: sticky;     /* always-accessible top menu + Dashboard while scrolling */
  top: 0;
  z-index: var(--z-header);
  overflow: visible;  /* don't clip the dropdown */
}

/* Ensure the dropdown panel sits above the banner itself */
.banner-menu__panel{
  z-index: var(--z-menu); /* was 40 */
}

/* Optional (keeps paper things below header if they ever get z-indexed) */
.paper_box,
.wheel-wrap,
.rounded-lg.shadow-sm.ring-1{
  position: relative;        /* so z-index applies if ever set */
  z-index: var(--z-paper);
}


/* =================================================================================================== Banner =============================================================================== */
header.site-banner {
  background:#023047;                    /* navy */
  color:#FFB703;                         /* gold text */
  border-bottom:1px solid rgba(255,255,255,.08);
}
header.site-banner a { color:inherit; text-decoration:none; }

/* Tokens */
:root { --logo-color:#f97316; }
.site-banner{
  /* Logo sizing drives everything else */
  --logo-h: 69px;                        /* mobile */
  --logo-ratio: 6.6;                     /* width = height * ratio */

  /* Unified nav button size (Menu & Sign In) */
  --nav-btn-h: clamp(45px, calc(var(--logo-h) * 0.75), 54px);
  --nav-btn-w: clamp(78px, 12vw, 87px);
}
@media (min-width:640px){  .site-banner { --logo-h: 78px; } }   /* sm */
@media (min-width:1024px){ .site-banner { --logo-h: 86px; } }   /* lg */

/* =========================
   Banner Row layout (L | center | R)
*/
.banner-row{
  position: relative;
  isolation: isolate;                    /* stacking context for z-index */
  display: grid;
  grid-template-columns: 1fr 1fr;        /* left & right; logo is absolute center */
  align-items: center;
  min-height: var(--logo-h);
  padding-inline: clamp(12px, 3vw, 24px);
}

/* Left/Right cells sit above the centered logo */
.site-banner__menu,
.site-banner__auth{
  position: relative;
  z-index: 2;                            /* ensure clickable above center logo */
  display:flex;
  align-items:center;
  height: var(--logo-h);
}

/* ★ THE MENU CELL MUST OUT-RANK THE SEARCH FIELD, NOT JUST THE LOGO. ★
   `z-index: 2` above establishes a STACKING CONTEXT, so .banner-menu__panel's
   z-index:110 is resolved INSIDE it and never gets compared against the search input at
   all — the panel was capped at 2 and the input painted over it. Measured 2026-08-10 at
   390px: the "Account" item sat at y=92 and elementFromPoint returned
   INPUT.sitesearch__input, so every tap opened the search box instead. The menu looked
   perfectly fine and was simply not reachable, which is why a reader had to SEARCH for
   their own account page — the route to billing, cancellation and data deletion.

   Raising the CELL (not the panel) is the fix, because the cap is the cell's. Auth keeps
   z-index 2: it has no dropdown, so it only ever needs to clear the logo.
   Same lesson as the glossary chips: visible is not reachable. */
.site-banner__menu { z-index: var(--z-menu); }
.site-banner__menu { justify-self: start; }
.site-banner__auth { justify-self: end; gap:.5rem; }

/* =========================
   True Centered Logo
*/
.site-banner__logo,
.site-banner__title{                     /* alias for older template name */
  position: absolute;
  top: 50%; left: 50%;
  transform: translate(-50%, -50%);
  display:flex; align-items:center; justify-content:center;
  height: var(--logo-h);
  /* Cap to viewport on small phones so the 6.6:1 wordmark can't overflow (D6). */
  width: min(calc(var(--logo-h) * var(--logo-ratio) * 0.9), 90vw); /* constrain anchor box */
  line-height: 0;                        /* no extra height */
  font-size: 0;                          /* no extra inline gap */
  z-index: 1;
}

/* Menu list = navy text */
.banner-menu__panel {
  color: #023047;                /* navy */
}
.banner-menu__panel a,
.banner-menu__item {
  color: inherit;                /* inherit navy from the panel */
}

/* Hover/focus keeps navy, just add a soft background */
.banner-menu__item:hover,
.banner-menu__item:focus {
  color: #023047;
  background: rgba(2, 48, 71, 0.08);
}

/* Masked (tintable) logo using logo.png alpha channel */
.site-logo-mask{
  display:inline-block;
  height: calc(var(--logo-h) * 0.9);
  width:  min(calc(var(--logo-h) * var(--logo-ratio) * 0.9), 90vw);
  background-color: var(--logo-color);
  -webkit-mask-image: url("/static/images/logo_web.png");
  -webkit-mask-image: image-set(url("/static/images/logo_web.webp") type("image/webp"),
                                 url("/static/images/logo_web.png") type("image/png"));
  -webkit-mask-repeat: no-repeat;
  -webkit-mask-position: center;
  -webkit-mask-size: contain;
  mask-image: url("/static/images/logo_web.png");
  mask-image: image-set(url("/static/images/logo_web.webp") type("image/webp"),
                         url("/static/images/logo_web.png") type("image/png"));
  mask-repeat: no-repeat;
  mask-position: center;
  mask-size: contain;
}

/* Fallback image if CSS masks aren’t supported */
.site-logo-img { display:none; height: calc(var(--logo-h) * 0.9); width:auto; }
@supports not ((-webkit-mask-image: url("")) or (mask-image: url(""))) {
  .site-logo-mask { display:none; }
  .site-logo-img  { display:inline-block; }
}

/* =======================================
   Unified nav button sizing (Menu & Sign In)
*/
header.site-banner .banner-menu__trigger,
.auth-link{
  border-radius: 9999px;
  border: 1px solid rgba(255,255,255,.28);
  /* Navy fill so amber text clears WCAG 2.2 AA — was amber-on-orange (1.52:1),
     now amber #FFB703 on navy #023047 ≈ 8:1 (ADA-grade). */
  background: #023047;
  color: #FFB703;
}

/* MENU (left): pill matches Sign In */
header.site-banner .banner-menu__trigger{
  display: flex;
  align-items: center;
  justify-content: center;
  width: var(--nav-btn-w);
  height: var(--nav-btn-h);
  padding: 0;                            /* center the icon */
}

/* Icon inside the menu pill */
.banner-menu__icon{
  display:block;
  width:  calc(var(--nav-btn-h) * 1.27);
  height: calc(var(--nav-btn-h) * 0.96);
}

/* SIGN IN (right): same pill size as menu */
.auth-link{
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: var(--nav-btn-w);
  height: var(--nav-btn-h);
  padding: 0 .75rem;                     /* breathing room for text */
  /* Fluid, but both ends land ON the scale: t-small (14px) -> t-body (16px).
     Was clamp(.9rem, 1.8vw, 1rem) — 14.4px at the low end, off-scale. */
  font-size: clamp(var(--t-small), 1.8vw, var(--t-body));
  line-height: 1;
}

/* Hover feedback for both */
header.site-banner .banner-menu__trigger:hover,
.auth-link:hover{
  background: rgba(255,255,255,.12);
}

/* =========================
   Banner Utilities
*/
.sr-only{
  position:absolute !important;
  width:1px !important;
  height:1px !important;
  padding:0 !important;
  margin:-1px !important;
  overflow:hidden !important;
  clip:rect(0,0,0,0) !important;
  white-space:nowrap !important;
  border:0 !important;
}

/* Old layout leftovers */
.menu-wrap, .header-spacer { display:none !important; }


/* ====================================================================================================== Cosmic Strip ======================================================================= */
/* ensure the paper box spans the row */
.cosmic-strip.paper_box--inline { display:block; width:100%; }
.cosmic-cols { display:flex; align-items:flex-start; justify-content:space-between; gap:.5rem 1.5rem; width:100%; }
/* right column hugs the edge on desktop */
.cosmic-desktop .cosmic-col:last-child { margin-left:auto; align-items:flex-end; text-align:right; }

/* tidy glyphs */
.cosmic-strip .zodiac-glyph{
  display:inline-block; font-size:1.1em; line-height:1; margin-right:.25rem; vertical-align:-0.05em; font-variant-emoji:text;
}
.cosmic-strip .zodiac-name{ white-space:nowrap; }

/* Show ONLY one strip: mobile <640px, desktop ≥640px */
.cosmic-mobile{ display:block !important; }
.cosmic-desktop{ display:none !important; }

@media (min-width:640px){
  .cosmic-mobile{ display:none !important; }
  .cosmic-desktop{ display:block !important; }
}

/* --- Sun/Moon badges (emoji inside navy circle) --- */
.sun-badge,
.moon-badge{
  display:inline-flex; align-items:center; justify-content:center;
  width:1.25rem; height:1.25rem; border-radius:9999px;
  background:#023047; box-shadow:0 1px 1px rgba(0,0,0,.18);
  outline:1px solid rgba(255,255,255,.4);
  line-height:1; font-size:1rem;
  /* ★ THE BADGE MUST NOT GROW TO ITS GLYPH. ★ MEASURED: a 1.5rem (24px) badge was
     rendering 24 x 25 — an emoji's line box is taller than its em square, so the
     flex box stretched and the glyph was clipped at the top. flex-shrink/grow off
     and a positioned child keep the circle exactly the size it declares. */
  position:relative; flex:0 0 auto; overflow:hidden;
}

/* ★ CENTRING AN EMOJI MEANS CENTRING ITS INK, NOT ITS BOX. ★ align-items:center centres
   the LINE BOX; the visible glyph sits high inside that box because the em square carries
   descender room the emoji does not use. MEASURED on this stack (Noto Color Emoji,
   Chromium) by screenshotting the badge with and without the glyph and differencing the
   two: the ink centre sat 2.50px above the badge centre at an 18px font, which is
   0.139em, and 0.25px right, which is below a pixel and left alone.

   Expressed in em so it holds at both badge sizes. It IS font-metric dependent — Apple
   Color Emoji is not Noto — so it is a correction, not a constant of nature. The
   deterministic fix is to draw these as SVG the way the eclipse badge already is, and
   that is the direction to go if this ever looks wrong on another platform. */
.badge-glyph{
  position:absolute; left:50%; top:50%;
  transform:translate(-50%, calc(-50% + 0.139em));
  line-height:1; font-style:normal;
}
@media (min-width:640px){
  .sun-badge, .moon-badge{ width:1.5rem; height:1.5rem; font-size:1.125rem; }
}

/* Full-column invisible link without changing layout */
.cosmic-stretched-link{
  position:absolute;
  inset:0;                 /* cover the whole column */
  z-index:1;               /* above the row contents */
  background:transparent;
  text-decoration:none;
  cursor:pointer;
}

/* Make the full-column link in the cosmic strip clickable above its contents */
.cosmic-strip .link-overlay { 
  z-index: 20 !important;   /* higher than siblings and defeats the reset */
}

/* (Optional, defensive) ensure the left column is a positioning context */
.cosmic-strip .link-scope { 
  position: relative; 
}

/* (Optional) sink clicks on inner visuals so taps go to the overlay link */
.cosmic-strip .click-sink { 
  pointer-events: none; 
}

/* --- Unified dot sizing (Schumann + GCP footprint) --- */
:root{ --dot-size:1.25rem; --dot-font:1rem; }                /* mobile */
@media (min-width:640px){ :root{ --dot-size:1.5rem; --dot-font:1.125rem; } }  /* desktop */

.cosmic-item, .cosmic-row { min-height: var(--dot-size); }

/* Schumann dot (emoji) */
.dot-wrap{
  display:inline-flex; width:var(--dot-size); height:var(--dot-size);
  align-items:center; justify-content:center; line-height:1;
}
.dot-emoji{ font-size:var(--dot-font); line-height:1; vertical-align:-0.1em; }

/* GCP iframe dot — scale safely without breaking content */
:root{ --gcp-base:20px; --gcp-scale:1.0; }        /* base served size; tweak scale only */
@media (min-width:640px){ :root{ --gcp-scale:1.25; } }

.gcp-wrap{
  position:relative;
  display:inline-flex;
  align-items:center; justify-content:center;
  width:var(--dot-size); height:var(--dot-size);
  margin-left:.3rem !important;
  flex-shrink:0;
}
.gcp-iframe{
  display:block;
  width:var(--gcp-base); height:var(--gcp-base);
  border:0 !important;
  transform:scale(var(--gcp-scale));
  transform-origin:center;
  pointer-events: none;
}
.gcp-link{ position:absolute; inset:0; z-index:2; }

.gcp-wrap, .gcp-iframe { z-index: auto !important; }


/* right column + its rows align flush-right */
.right-col{ align-items:flex-end !important; text-align:right !important; }
.right-col .right-row{
  width:100% !important;
  display:flex !important;
  justify-content:flex-end !important;
  margin-left:auto !important; /* pushes row to the far right of the column */
}


/* =========================================================================================== Paper backgrounds / boxes =====================================================================*/
body.paper-bg{
  background-color:#fefae0;
  background-image:url("/static/images/paper.png");
  background-repeat:repeat;
  background-size:512px 512px;
}
.shadow-paper {
  box-shadow: 0 33px 45px rgba(0,0,0,.3), 0 0 10px rgba(255,183,3,.2);
  border-radius: 9999px;
  background:#fefae0;
  padding:1rem;
}
.paper_box {
  background:#fefae0 url("/static/images/paper.png") repeat;
  border-radius:1rem;
  box-shadow:0 33px 45px rgba(0,0,0,.30), 0 0 10px rgba(255,183,3,.20);
  padding:2rem;
  max-width:800px;
  margin:2rem auto;
  backdrop-filter:blur(3px);
}
.paper_box--inline{ max-width:none; margin:0; display:inline-block; }

/* Chart-page panels — the ONLY boxed elements on /chart/*: the chart visual gets one panel,
   the data table(s) another; every other section is text on the body's paper background
   (body.paper-bg). The box is DESKTOP-ONLY (matches western's .tab-area-box @900px): on small
   screens there's no room, so the panel is plain — content sits straight on the paper. Same
   paper-card look as western's .tab-area-box / .wheel-wrap and vedic's .vchart-card. */
.chart-panel{ }
/* Chinese chart interactive filter bar (the BaZi analogue of the western/vedic filter row). */
.cn-filterbar { row-gap:6px; }
.cn-fsel { border:1px solid #cbd5e1; border-radius:9999px; background:rgba(255,255,255,.72);
           /* var(--t-micro), not .72rem: that was 11.5px, under the 12px floor. Wider cap so
              "All life domains" and "All Ten Gods" are not cut mid-word (2026-09-16). */
           padding:.28rem .6rem; font-size:var(--t-micro); color:#333; max-width:11rem; min-height:24px;
           -webkit-appearance:menulist; appearance:auto; cursor:pointer; }
.cn-fsel:hover { border-color:#c8781e; }
.cn-fsel:focus-visible { outline:2px solid #c8781e; outline-offset:1px; }
.cn-fsel-lens { border-color:#c8a24e; background:rgba(255,244,232,.75); }
.cn-freset { border-color:#cbd5e1; color:#555; background:rgba(255,255,255,.6); cursor:pointer; }
.cn-freset:hover { border-color:#c8781e; color:#8a3d00; }
.cn-showpill { font-size:var(--t-micro); padding:.16rem .55rem; border-radius:9999px; border:1px solid #cbd5e1; cursor:pointer; transition:all .12s; }
.cn-showon { background:#023047; color:#fff; border-color:#023047; }
.cn-showoff { background:transparent; color:#9a9082; text-decoration:line-through; }
@media (min-width:900px){
  .chart-panel{
    background:#fefae0 url("/static/images/paper.png") repeat;
    border-radius:1rem;
    box-shadow:0 33px 45px rgba(0,0,0,.30), 0 0 10px rgba(255,183,3,.20);
    backdrop-filter:blur(3px);
    padding:2rem;
  }
}

/* Chart-page tab + filter controls — one vocabulary across /chart/western, /vedic, /chinese.
   Subtle active state (light bg + slate border), never a filled navy pill. Promoted from
   western's inline styles so every chart page shares them. */
.wheel-tab { padding:0.5rem 0.75rem; border:1px solid; border-radius:0.375rem; text-align:center; white-space:normal; line-height:1.15; cursor:pointer; }
.wheel-tab-on  { background:rgba(0,0,0,0.05); border-color:#8aa1ab; font-weight:600; color:#111827; }
.wheel-tab-off { background:transparent; border-color:#cbd5e1; color:#6b7280; }
.wheel-tab-off:hover { color:#374151; border-color:#9aa7b1; }
/* Shared chart-tab TABLE look (the "vgrid") — vedic defines this inline; promoted here so the
   chinese chart's Table tabs match western/vedic exactly (navy-grey grid, vintage caps header,
   Trocchi body, row hover, column separators). */
/* ★ .vgrid's TYPOGRAPHY IS THE SAME AS .table-planets NOW (owner, 2026-08-21). ★
   This block used to set its own font-family, size and weight, and beat the template's
   own `brand-sans t-micro` classes on specificity (.vgrid thead th is 0,1,2 against
   .brand-sans at 0,1,0). So /chart/vedic's headers rendered 12px ui-sans-serif 400 while
   /chart/western's rendered 16px Special Elite 700 from the SAME class list, and the two
   tables looked like different products. Only colour, borders, padding and hover stay
   here; everything typographic is shared below so the two cannot drift again. */
.vgrid thead th{ color:#1f2937; }
.vgrid thead tr{ border-bottom:1px solid #8aa1ab; }

.vgrid tbody td{ padding:.25rem .5rem; }
.vgrid tbody tr{ border-bottom:1px solid #8aa1ab; }
.vgrid tbody tr:hover{ background:rgba(0,0,0,.05); }
.vgrid th + th, .vgrid td + td{ border-left:1px solid #8aa1ab; }
/* Responsive card-collapse (parity with /western + /vedic .resp-cards): a .vgrid.resp-cards inside
   a .cn-tablewrap container becomes stacked, labelled cards when the container is narrow, instead
   of scrolling. Each td flips to a "LABEL      value" row using its data-label. */
.cn-tablewrap{ container-type:inline-size; container-name:cntab; }
@container cntab (max-width:640px){
  .vgrid.resp-cards, .vgrid.resp-cards tbody, .vgrid.resp-cards tr, .vgrid.resp-cards td{ display:block; }
  .vgrid.resp-cards{ min-width:0 !important; }
  .vgrid.resp-cards thead{ display:none; }
  .vgrid.resp-cards tbody tr{ border:1px solid #d4cfc4; border-radius:.5rem; margin-bottom:.55rem; padding:.35rem .6rem; background:rgba(255,255,255,.45); }
  .vgrid.resp-cards tbody td{ display:flex; justify-content:space-between; align-items:baseline; gap:1rem; border:0 !important; padding:.14rem 0; text-align:left !important; }
  .vgrid.resp-cards tbody td::before{ content:attr(data-label); font-family:ui-sans-serif, system-ui, -apple-system, "Segoe UI", Roboto, sans-serif; text-transform:uppercase; letter-spacing:.05em; font-size:.6rem; color:#6b7280; flex:0 0 auto; white-space:nowrap; }
}

/* Chinese Four Pillars — a proper pillar chart, not a flat spreadsheet: four columns (Stem over
   Branch over Hidden, stacked as in a traditional 八字 chart), each a bordered pillar, the Day
   pillar (the self) accented. Like /western + /vedic the cells carry NO fill — they sit on the
   page's paper texture; only the borders/dividers give structure and the element colour lives in
   the glyph text. */
.fourpillars { display:grid; grid-template-columns:repeat(4,1fr); gap:10px; }
.fp-pillar { border:1px solid #d8d2c4; border-radius:12px; overflow:hidden; display:flex; flex-direction:column; }
.fp-pillar-self { border-color:#a2652d; box-shadow:0 0 0 1px #a2652d; }
.fp-phead { text-align:center; font-weight:600; padding:6px 4px; font-size:var(--t-small); border-bottom:1px solid #e2ddd0; color:#023047; }
.fp-phead-self { color:#8a3d00; }
.fp-cell { text-align:center; padding:10px 4px; border-bottom:1px dashed #e0dacb; }
.fp-cell:last-child { border-bottom:none; }
/* 2026-09-17 (queue item 26): .56rem rendered 8.96px, under the 12px floor, and #a99e8b was 2.5:1 on the paper;
   now the floor and #797164 (4.58:1), the same hue walked darker. */
.fp-celllabel { font-size:var(--t-micro); letter-spacing:.07em; text-transform:uppercase; color:#797164; margin-bottom:3px; }
.fp-glyph { font-weight:700; font-size:var(--t-body); line-height:1.15; }
.fp-glyph .glossed { text-decoration-thickness:1px; }
.fp-sub { font-size:var(--t-micro); color:#6b6153; margin-top:2px; line-height:1.3; }
.fp-hidden-row { font-size:var(--t-micro); font-weight:600; }
@media (max-width:560px){
  .fourpillars { gap:6px; }
  .fp-cell { padding:8px 2px; }
  .fp-glyph { font-size:var(--t-small); }
  .fp-phead { font-size:var(--t-micro); padding:5px 2px; }
  .fp-sub { font-size:var(--t-micro); }
}
/* Pillar header as a popup trigger, and the quick-view popup itself (the western house-popup /
   vedic bhava-popup analogue) — a centred paper card over a dimmed backdrop. */
/* The whole pillar card is the popup trigger (tap anywhere), like /western's house wedge. */
.fp-pillar-btn { cursor:pointer; transition:border-color .15s, box-shadow .15s; }
.fp-pillar-btn:hover { border-color:#a2652d; }
.fp-pillar-btn:focus-visible { outline:2px solid #a2652d; outline-offset:2px; }
.fp-phead { -webkit-user-select:none; user-select:none; }
.fp-info { opacity:.4; font-size:var(--t-micro); }
/* Chinese chart popups (pillar + palace) render as a native <dialog> in the browser top layer —
   escapes the chart-panel's backdrop-filter stacking, same mechanism as /western + /vedic. */
.cn-dialog { position:fixed; inset:0; margin:auto; max-width:min(94vw,420px); max-height:84vh;
             padding:0; border:0; background:transparent; overflow:visible; }
.cn-dialog::backdrop { background:rgba(2,24,37,.45); }
.fp-popup { position:relative; background:#fefae0 url("/static/images/paper.png") repeat; border:1px solid #d4cfc4; border-radius:1rem; box-shadow:0 24px 55px rgba(0,0,0,.42), 0 0 12px rgba(255,183,3,.22); backdrop-filter:blur(3px); padding:1.2rem 1.35rem; max-width:min(94vw,420px); max-height:84vh; overflow-y:auto; }
/* Close ✕ sits above the card link (like /western's house popup) so the whole card can navigate. */
.fp-popup-x { position:absolute; top:8px; right:11px; z-index:2; background:none; border:0; cursor:pointer;
              color:#6a7383; font-size:1.4rem; line-height:1; padding:0; }
.fp-popup-x:hover { color:#374151; }

/* The 六十甲子 sexagenary-cycle reference grid — 60 stem-branch pairs, the four pillars + this
   year highlighted. 10 columns on desktop (6 rows), 6 on small screens. */
.sexagenary { display:grid; grid-template-columns:repeat(10,1fr); gap:3px; }
.gz-cell { text-align:center; padding:4px 1px; border-radius:5px; border:1px solid transparent; }
.gz-hanzi { display:block; font-weight:600; font-size:.8rem; line-height:1.1; }
.gz-n { display:block; font-size:.5rem; color:#a99e8b; }
.gz-tag { display:block; font-size:.5rem; font-weight:600; color:#8a3d00; line-height:1.05; }
.gz-mark { background:rgba(179,89,0,.12); border-color:#a2652d; }
@media (max-width:560px){ .sexagenary { grid-template-columns:repeat(6,1fr); } }

/* ── Zǐ Wēi Dǒu Shù 命盤 (Purple-Star chart) — twelve palaces on a fixed 4×4 branch board, the
   centre 2×2 holding the birth facts. Offered as a chart-style beside the Four Pillars, the way
   /vedic offers North / South / East Indian styles. ── */
.ziwei-board { display:grid; grid-template-columns:repeat(4,1fr); grid-template-rows:repeat(4,minmax(96px,auto)); gap:5px; }
.zw-pal { position:relative; border:1px solid #d8d2c4; border-radius:9px;
          padding:5px 5px 3px; display:flex; flex-direction:column; min-width:0; }
.zw-pal-life { border-color:#a2652d; box-shadow:0 0 0 1px #a2652d; }
.zw-pal-body { border-color:#b58a4a; box-shadow:inset 0 0 0 1px rgba(138,90,0,.22); }
.zw-stars { display:flex; flex-wrap:wrap; gap:2px 6px; align-content:flex-start; flex:1; }
.zw-star { font-size:.75rem; line-height:1.15; white-space:nowrap; }
.zw-sname { font-weight:600; }
.zw-star-maj  .zw-sname { color:#3a2f6b; }                    /* major stars — deep violet (紫微 = purple) */
.zw-star-luck .zw-sname { color:#4a7c59; font-weight:500; }  /* lucky helpers — green */
.zw-star-harm .zw-sname { color:#a2543a; font-weight:500; }  /* harmful stars — rust */
/* Star brightness (亮度 廟旺得利平不陷) — a tiny mark after a major star. */
.zw-bright { font-size:var(--t-micro); font-weight:700; vertical-align:top; margin-left:1px; }
.zw-bright-good { color:#4a7c59; }   /* 廟/旺 — shining */
.zw-bright-mid  { color:#9a9082; }   /* 得/利/平 — steady */
.zw-bright-dim  { color:#a2543a; }   /* 不/陷 — dimmed */
.zw-hua { font-size:var(--t-micro); font-weight:700; padding:0 3px; border-radius:4px; vertical-align:top; }
.zw-hua-lu   { background:#dff0e0; color:#2f6b3b; }  /* 祿 prosperity */
.zw-hua-quan { background:#dce6f5; color:#2b5a8a; }  /* 權 power */
.zw-hua-ke   { background:#ece0f2; color:#6a3d8a; }  /* 科 fame */
.zw-hua-ji   { background:#f5dcdc; color:#a23a3a; }  /* 忌 trouble */

/* ── Palace-card enrichments (Palace Readings): timing "now" chips, prominence pills, and the
   四化 callout. Sized to sit inside the .brand-sans t-micro card body. ── */
.zw-now { font-size:var(--t-micro); font-weight:700; letter-spacing:.02em; padding:0 4px; border-radius:5px;
          background:#f6e4cf; color:#9c5313; white-space:nowrap; vertical-align:middle; }  /* was #b45f16, 3.69:1 -> 4.63:1 */
.zw-prom { font-size:var(--t-micro); font-weight:700; letter-spacing:.03em; text-transform:uppercase;
           padding:0 5px; border-radius:5px; white-space:nowrap; vertical-align:middle; }
.zw-fav { font-size:.56rem; font-weight:700; letter-spacing:.02em; padding:0 5px; border-radius:5px;
          background:#e2efdf; color:#3f6b4a; white-space:nowrap; vertical-align:middle; }
.zw-rel-tag { font-size:var(--t-micro); font-weight:700; letter-spacing:.04em; text-transform:uppercase;
              padding:0 5px; border-radius:5px; white-space:nowrap; vertical-align:middle; }
.zw-rel-ally    { background:#8aa1ab33; color:#4e6a73; }  /* was #5a7a85, 3.59:1 -> 4.50:1 */
.zw-rel-tension { background:#e8c9c966; color:#9e5239; }  /* was #a2543a, 4.35:1 -> 4.50:1 */
/* ── 8C: WCAG 2.5.8 Target Size (Minimum), AA — new in WCAG 2.2 ──────────────
   A pointer target must be at least 24x24 CSS px. Measured offenders were 21x14
   (graha links in the Vedic dasha tables) and 111x18 (the external source links
   on Space Weather): wide enough, but far too short, because a bare inline <a>
   is only as tall as its line box.

   inline-block is what makes the padding count — vertical padding on a plain
   inline element paints outward but does NOT grow the element's box, so the hit
   area would look bigger and measure exactly the same. This is why the obvious
   "just add padding" fix silently fails the check it was meant to pass. */
.tap-min {
  display: inline-block;
  min-height: 24px; min-width: 24px;
  padding-block: 5px;
  box-sizing: border-box;
}

/* -- Nest card controls (collapse + reorder) --------------------------------
   * THESE WERE INLINE STYLE ATTRIBUTES IN nest_cards.js AND THAT WAS THREE FAULTS. *
   Found by scripts/cross_file_identifiers.py, which flagged .nest-card-toggle as a class
   nothing renders and nothing styles. The class turned out to be built in JS, so the
   buttons DID paint -- the finding was right for a reason I had not considered:

     1. `font: 400 12px/1.4` is a hardcoded PIXEL size. The TEXT SIZE STANDARD says rem,
        never px, because a px size ignores the reader's own browser font setting
        (WCAG 1.4.4). It is now var(--t-micro): the same 12px at default settings, and
        one that scales when the reader scales.
     2. The buttons measured under the 24px minimum of WCAG 2.5.8 Target Size -- the exact
        rule 8C spent this sprint fixing everywhere else. The .tap-min sizing is folded in
        here rather than added as a second class, so a new control cannot forget it.
     3. Colours were literals, one of them a copy of --card-border by value. A token that
        exists and is copied rather than referenced is a token that will drift.

   The lesson is one already in the journal wearing another costume: styling from JS puts
   the rule outside every tool we own that checks rules.

   The three controls are named individually rather than matched as `.nest-card-bar button`
   on purpose: cross_file_identifiers.py looks for the CLASS, so a descendant selector
   leaves it still reporting a class nothing defines -- and a checker that stays red is a
   checker people learn to ignore. */
.nest-card-bar {
  display: flex;
  gap: 6px;
  justify-content: flex-end;
  align-items: center;
  margin: 0 0 6px;
}

.nest-card-up,
.nest-card-down,
.nest-card-toggle {
  /* 24px floor, per WCAG 2.5.8 -- same sizing as .tap-min */
  min-height: 24px;
  min-width: 24px;
  box-sizing: border-box;
  padding: 3px 8px;
  border: 1px solid var(--card-border);
  border-radius: 6px;
  background: #fff;
  font-family: Georgia, serif;
  font-size: var(--t-micro);
  line-height: 1.4;
  color: #5a5342;
  cursor: pointer;
}

.nest-card-up:hover,
.nest-card-down:hover,
.nest-card-toggle:hover { background: #faf4e8; }

.nest-card-up:focus-visible,
.nest-card-down:focus-visible,
.nest-card-toggle:focus-visible {
  outline: 2px solid var(--ember);
  outline-offset: 2px;
}

/* ── 8C: skip link (WCAG 2.4.1 Bypass Blocks) ────────────────────────────────
   Off-screen, not hidden: display:none and visibility:hidden both remove an
   element from the tab order, which would leave the link present in the markup
   and unreachable by the only readers it exists for. Focus brings it back. */
.skip-link {
  position: absolute; left: 0; top: -100px; z-index: 10000;
  padding: 0.6rem 1rem; border-radius: 0 0 8px 0;
  background: #023047; color: #fff; font-size: var(--t-body);
  text-decoration: none; font-weight: 600;
  transition: top .15s ease-in-out;
}
.skip-link:focus, .skip-link:focus-visible {
  top: 0; outline: 3px solid #fb8500; outline-offset: 2px;
}

/* ══════════════════════════════════════════════════════════════════════════════
   8C — WCAG 2.2 AA. ★ THE CHIPS FAILED TWO STANDARDS AT ONCE, AND ONE CAUSED THE
   OTHER. ★ Measured 2026-08-15 with axe-core against the live pages.

   Every chip below sat at .55rem or .62rem — 8.8px and 9.92px — under the 12px
   ABSOLUTE FLOOR this file defines twenty lines up, and under it for the reason
   the standard names and forbids: to make a dense layout fit. Small text then
   made the colour worse, because WCAG only relaxes to 3:1 for LARGE text (18pt,
   or 14pt bold) and 8.8px bold is nowhere near it — so the same palette that
   would have passed at a readable size failed at 4.5:1.

   Raising the size to var(--t-micro) is therefore the accessibility fix and the
   type-scale fix in one move, and the colours are re-derived against it.

   ★ THE NEW FOREGROUNDS ARE COMPUTED, NOT PICKED. ★ Each is its original hue and
   saturation with lightness reduced until the measured ratio against its OWN
   background reaches 4.5:1 — so the palette keeps its character instead of
   collapsing toward black. Ratios in the comments are measured, not intended.

   ★ AND TWO OF THESE WERE NEVER FLAGGED. ★ .zw-rel-ally (3.59) and
   .zw-rel-tension (4.35) fail too; axe never saw them because they were not on
   an audited page. An automated sweep reports the pages it reached, so the whole
   family was checked by hand rather than only the members that happened to show.
   ══════════════════════════════════════════════════════════════════════════════ */
/* Vedic card enrichment chips (strength, running dasha, Atmakaraka, yoga, vargottama) */
.v-chip { font-size:var(--t-micro); font-weight:700; letter-spacing:.03em; padding:0 5px; border-radius:5px;
          white-space:nowrap; vertical-align:middle; }
.v-chip-str  { background:#e3ecf2; color:#2a6bac; }  /* was #2c6fb3, 4.35:1 -> 4.62:1 */
.v-chip-now  { background:#f6e4cf; color:#9c5313; }  /* was #b45f16, 3.69:1 -> 4.63:1 */
.v-chip-ak   { background:#efe0f0; color:#7a3d8a; }
.v-chip-yoga { background:#e6efe0; color:#467554; }  /* was #4a7c59, 4.12:1 -> 4.51:1 */
.v-chip-vgm  { background:#e3ecf2; color:#2a6bac; }  /* was #2c6fb3, 4.35:1 -> 4.62:1 */
.v-str-pill  { font-size:var(--t-micro); font-weight:600; padding:1px 8px; border-radius:999px;
               border:1px solid #cfdae3; background:#eef3f7; color:#37536b; white-space:nowrap; }
.zw-prom.zw-prom-headline { background:#efe0c4; color:#8a5a00; }
.zw-prom.zw-prom-strong   { background:#e6ddc9; color:#706135; }  /* was #7a6a3a, 3.94:1 -> 4.51:1 */
/* Palace ranking strip pills */
.zw-prom-pill { font-size:var(--t-micro); font-weight:600; padding:1px 7px; border-radius:999px;
                border:1px solid transparent; white-space:nowrap; }
.zw-prom-headline { background:#f3e6c9; border-color:#e0cfa0; color:#7a5300; }
.zw-prom-strong   { background:#eae3d1; border-color:#d8cdb2; color:#6a5a34; }
.zw-prom-moderate { background:#eef0ea; border-color:#dde0d5; color:#5f6a55; }
.zw-prom-quiet    { background:#f1f0ec; border-color:#e2e0d8; color:#716d64; }  /* was #8a857a, 3.22:1 -> 4.51:1 */
/* Palace-card order toggle (Traditional vs Loudest first) */
.zw-ord { font-size:var(--t-micro); font-weight:600; padding:2px 10px; border-radius:999px;
          border:1px solid #d4cfc4; background:#fdfbf4; color:#6a6250; cursor:pointer;
          transition:border-color .15s, background .15s, color .15s; }
.zw-ord:hover { border-color:#a2652d; }
.zw-ord-on { background:#023047; border-color:#023047; color:#fff; }
/* 四化 callout — a foregrounded block for a transformation landing in the palace */
.zw-hua-callout { display:block; margin-top:3px; padding:3px 8px; border-radius:6px;
                  border-left:3px solid; font-size:var(--t-micro); line-height:1.4; }
.zw-hua-callout.zw-hua-lu   { background:#eef7ef; border-color:#4a8a5a; color:#2f5a3b; }
.zw-hua-callout.zw-hua-quan { background:#eef2f9; border-color:#3b6aa0; color:#2b4a6a; }
.zw-hua-callout.zw-hua-ke   { background:#f4eef8; border-color:#7a4da0; color:#553a6a; }
.zw-hua-callout.zw-hua-ji   { background:#f9eeee; border-color:#b04a4a; color:#7a3a3a; }
.zw-foot { display:flex; align-items:baseline; justify-content:space-between; gap:4px; margin-top:3px;
           border-top:1px dashed #e0dacb; padding-top:2px; -webkit-user-select:none; user-select:none; }
/* The whole palace cell is the popup trigger (tap anywhere), like /western's house wedge. */
.zw-pal-btn { cursor:pointer; transition:border-color .15s, box-shadow .15s; }
.zw-pal-btn:hover { border-color:#a2652d; }
.zw-pal-btn:focus-visible { outline:2px solid #a2652d; outline-offset:2px; z-index:1; }
.zw-info { font-size:.6rem; color:#b7ad98; }
.zw-pname { font-size:.67rem; font-weight:600; color:#4a4030; }
.zw-tag-life { color:#a2652d; font-weight:700; }
.zw-tag-body { color:#8a5a00; font-weight:700; }
.zw-pbranch { font-size:.74rem; font-weight:700; }
.zw-daxian { font-size:.55rem; color:#9a9082; text-align:right; margin-top:1px; letter-spacing:.02em;
             -webkit-user-select:none; user-select:none; }
.zw-daxian-now { color:#a2652d; font-weight:700; }
.zw-center { grid-row:2/4; grid-column:2/4; border:1px solid #cbc3b2; border-radius:9px;
             background:rgba(2,48,71,.045); display:flex; flex-direction:column; justify-content:center;
             gap:5px; padding:10px; text-align:center; }
.zw-c-name { font-weight:700; color:#023047; font-size:1rem; }
.zw-c-row { font-size:.75rem; color:#4a4030; }
.zw-c-hua { display:flex; flex-wrap:wrap; justify-content:center; gap:4px 8px; margin-top:2px; }
.zw-c-huaitem { font-size:.73rem; font-weight:600; }
@media (max-width:560px){
  .ziwei-board { gap:3px; grid-template-rows:repeat(4,minmax(82px,auto)); }
  .zw-star { font-size:.66rem; }
  .zw-pname { font-size:.58rem; }
  .zw-pbranch { font-size:.66rem; }
  .zw-c-name { font-size:.85rem; }
  .zw-c-row, .zw-c-huaitem { font-size:.64rem; }
}
/* Five-Elements chart view — element-balance bars beneath the Wu Xing pentagon. */
.zw-wx-bars { max-width:340px; margin:0 auto; display:flex; flex-direction:column; gap:5px; }
.zw-wx-bar-row { display:flex; align-items:center; gap:8px; font-size:.8rem; }
.zw-wx-bar-lbl { width:56px; flex:none; }
.zw-wx-bar-track { flex:1; height:9px; background:rgba(0,0,0,.06); border-radius:5px; overflow:hidden; }
.zw-wx-bar-fill { display:block; height:100%; border-radius:5px; }
.zw-wx-bar-n { width:16px; flex:none; text-align:right; color:#6b6153; font-size:.72rem; }

.paper_box_wide {
  background:#fefae0 url("/static/images/paper.png") repeat;
  border-radius:1rem;
  box-shadow:0 33px 45px rgba(0,0,0,.30), 0 0 10px rgba(255,183,3,.20);
  padding:2rem;
  max-width:1040px;
  margin:2rem auto;
  backdrop-filter:blur(3px);
}

/* ── Small screens: drop the paper-box FRAMING to reclaim width ───────────────
   The page's paper texture (body.paper-bg) shows through, so content spans nearly
   edge-to-edge over paper instead of inside a padded, shadowed card. A centered ✦
   ornament divides stacked sections in place of the box edges. Scoped to
   body.paper-bg so pages WITHOUT the page-level paper texture keep their boxes. */
@media (max-width: 640px) {
  body.paper-bg .paper_box,
  body.paper-bg .paper_box_wide {
    background: transparent;
    box-shadow: none;
    border-radius: 0;
    -webkit-backdrop-filter: none;
    backdrop-filter: none;
    max-width: none;
    margin: 1rem 0;
    padding: 0.75rem 0.5rem;
  }
  /* Pretty section divider: a centered ✦ before each stacked section (not the first). */
  body.paper-bg .paper_box + .paper_box::before,
  body.paper-bg .paper_box_wide + .paper_box_wide::before,
  body.paper-bg .paper_box_wide + .paper_box::before,
  body.paper-bg .paper_box + .paper_box_wide::before {
    content: "✦";
    display: block;
    text-align: center;
    color: #fb8500;
    opacity: .55;
    font-size: 1.05rem;
    letter-spacing: .2em;
    margin: .35rem 0 1.25rem;
  }
}

/* ── "All Together" page ─────────────────────────────────────────────────────
   Scale the synthesis prose to the compact reading size the chart pages use, and
   restack the 3-tradition comparison table into per-dimension cards on small
   screens (system labels via nth-child, so no per-cell markup is needed). */
body.all-together .at-prose{ font-size:0.82rem; line-height:1.62; }
body.all-together .at-prose p{ margin-bottom:0.65rem; }
@media (max-width:640px){
  table.at-compare, table.at-compare tbody, table.at-compare tr, table.at-compare td{ display:block; }
  table.at-compare thead{ display:none; }
  table.at-compare{ min-width:0 !important; }
  table.at-compare tr{ border:1px solid #d4cfc4; border-radius:.55rem; margin-bottom:.6rem;
                       padding:.45rem .65rem; background:rgba(255,255,255,.4) !important; }
  table.at-compare td{ padding:.18rem 0 !important; border:0 !important; }
  table.at-compare td:first-child{ font-weight:700; color:#023047;
                                   border-bottom:1px solid #ece8df !important;
                                   margin-bottom:.3rem; padding-bottom:.3rem !important; }
  table.at-compare td:not(:first-child)::before{ display:inline-block; min-width:54px; margin-right:.45rem;
      vertical-align:top; font-family:ui-sans-serif, system-ui, -apple-system, "Segoe UI", Roboto, sans-serif; text-transform:uppercase;
      letter-spacing:.04em; font-size:.58rem; font-weight:700; }
  table.at-compare td:nth-child(2)::before{ content:"Western"; color:#2c6fb3; }
  table.at-compare td:nth-child(3)::before{ content:"Vedic"; color:var(--ember); }
  table.at-compare td:nth-child(4)::before{ content:"Chinese"; color:#c0392b; }
}

/* Importance/strength hairline bar for the Chinese & Vedic tables — the same navy→blue bar the
   western Aspects Table uses (that one is defined inline in western_wheel.html); here globally so
   the other chart tables can size a bar by their equivalent weight (palace prominence, Shadbala,
   SAV bindus). Floor the fill at ~4% so even the quietest row shows a hairline. */
.table-strength{ display:block; height:4px; border-radius:999px; background:#e8e2d3; overflow:hidden; min-width:52px; }
.table-strength > span{ display:block; height:100%; border-radius:999px;
                        background:linear-gradient(90deg,#023047,#8aa1ab); transition:width .4s ease; }
@media (prefers-reduced-motion: reduce){ .table-strength > span{ transition:none; } }

/* Paper dropdown container */
.paper_dropdown {
  position: absolute;
  inset-inline: 0;
  top: calc(100% + 0.25rem);
  z-index: 50;
  max-height: 16rem;
  overflow: auto;
  padding: 0.25rem;
  border: 1px solid rgba(0,0,0,.08);
  border-radius: 0.75rem;
  box-shadow: 0 12px 30px rgba(0,0,0,.12);
  background-color: #fefae0 !important;
  background-image:
    linear-gradient(rgba(254,250,224,.45), rgba(254,250,224,.45)),
    url("/static/images/paper.png") !important;
  background-repeat: no-repeat, repeat !important;
  background-size: auto auto, 220px auto !important;
  background-position: 0 0, 0 0 !important;
  backdrop-filter: saturate(1.05);
}
.paper_dropdown__item {
  display: block;
  padding: 0.5rem 0.75rem;
  margin: 0.125rem;
  border-radius: 0.5rem;
  line-height: 1.4;
  color: #1f2937;
  cursor: pointer;
  outline: none;
}
.paper_dropdown__item:hover,
.paper_dropdown__item:focus,
.paper_dropdown__item[aria-selected="true"] {
  background: #fefae0;
  box-shadow: inset 0 0 0 2px rgba(0,0,0,.05);
}
.paper_dropdown__item + .paper_dropdown__item {
  border-top: 1px dashed rgba(0,0,0,.05);
}
.place-autocomplete { position: relative; }

/* Utilities */
.trocchi-text { font-family: "Trocchi", serif; }

/* Wheel pages (self + kindred): the narrative prose blocks inherited 16px while
   the surrounding sections use the t-small (14px) scale, so they read oversized.
   Bring narrative text down to match; section TITLES (brand-sans/t-body) are
   untouched, preserving the hierarchy. */
body.western-wheel .trocchi-text,
body.western-wheel .trocchi-text p,
body.western-wheel .trocchi-text li,
body.chinese-chart .trocchi-text,
body.chinese-chart .trocchi-text p,
body.chinese-chart .trocchi-text li,
body.vedic-chart .trocchi-text,
body.vedic-chart .trocchi-text p,
body.vedic-chart .trocchi-text li {
  font-size: 0.875rem;
  line-height: 1.55;
}
body.western-wheel .trocchi-text h1, body.chinese-chart .trocchi-text h1, body.vedic-chart .trocchi-text h1 { font-size: 1.05rem; }
body.western-wheel .trocchi-text h2, body.chinese-chart .trocchi-text h2, body.vedic-chart .trocchi-text h2 { font-size: 0.95rem; }
body.western-wheel .trocchi-text h3, body.chinese-chart .trocchi-text h3, body.vedic-chart .trocchi-text h3 { font-size: 0.9rem; }
.brand-sans  { font-family: "Special Elite", Georgia, serif; }
.wheel-sans  { font-family: "Special Elite", Georgia, serif; }
.wheel-serif { font-family: "Trocchi", serif; }
.astro-glyph { font-family: "AstroSymbols"; font-weight:400; font-variant-emoji:text; }
.tabular-nums { font-variant-numeric: tabular-nums; }


/* =========================================================================================== Western Wheel + Popup (your existing styles) ===================================================*/
:root{ --wheel-gap-top: calc(1rem + 1.5rem + clamp(10px, 2.5vw, 18px)); }

.western-wheel{
  --wheel-gap-top: calc(1rem + 1.5rem + clamp(10px, 2.5vw, 18px));

  /* D: wheel popup layering */
  --z-wheel-overlay: 200;
  --z-wheel-popup: 210;

}

/* Wheel sizing controls */
.western-wheel{
  --wheel-max: 759px;        /* desktop max */
  --wheel-vw:  96vw;         /* mobile responsiveness */
}
@media (min-width: 1024px){
  .western-wheel{ --wheel-max: 759px; } /* bump to 920/980 later if you want */
}

/* only on the wheel page */
.western-wheel .rounded-lg.shadow-sm.ring-1{
  padding-top: var(--wheel-gap-top) !important;
}

.western-wheel .rounded-lg.shadow-sm.ring-1.table-frame-pad{
  padding-top: var(--wheel-gap-top) !important;
}

.western-wheel .mt-2 { margin-top: 0 !important; }

/* Table layout knobs */
:root{
  --wheel-col-top: calc(1rem + 1.5rem);
  --table-top-nudge: 0rem;
  --tp-grid-color:  #bbbbbb;
  --tp-frame-color: #bbbbbb;
  --tp-row-py:      0.25rem;
  --tp-row-px:      0.50rem;
  --tp-header-font-size: 1rem;
  --tp-body-font-size:   0.90rem;
  --tp-caption-font-size: 1.30rem;
  --tp-frame-pt:           .60rem;
}
.western-wheel .table-col{ padding-top: calc(var(--wheel-col-top) + var(--table-top-nudge)); }
@media (max-width: 767.98px){
  .western-wheel .table-col{ padding-top: 0; }
}

.western-wheel .table-frame,
.western-wheel .rounded-lg.shadow-sm.ring-1 {
  border: 1px solid var(--tp-frame-color, #bbbbbb) !important;
  border-radius: 0.5rem;
  background: transparent !important;
  box-shadow: none !important;
}
.table-planets caption.table-caption {
  caption-side: top;
  display: table-caption;
  text-align: center;
  padding: var(--tp-row-py, .25rem) var(--tp-row-px, .5rem);
  border-bottom: 1px solid var(--tp-grid-color, #bbbbbb);
  background: transparent;
  color: #023047;
  font-family: "Special Elite", Georgia, serif;
  font-size: var(--tp-caption-font-size);
  font-weight: 700;
  text-transform: none;
  letter-spacing: .01em;
  line-height: 1.2;
}
.rounded-lg.shadow-sm.ring-1,
.table-frame { padding-top: var(--tp-frame-pt) !important; }
.table-planets {
  table-layout: auto !important;
  border-collapse: separate !important;
  border-spacing: 0 !important;
  background: transparent !important;
}
.table-planets thead,
.table-planets tbody,
.table-planets tr,
.table-planets th,
.table-planets td { background: transparent !important; }
.table-planets thead th { border-bottom: 1px solid var(--tp-grid-color, #bbbbbb) !important; }
.table-planets tbody td { border-bottom: 1px solid var(--tp-grid-color, #bbbbbb) !important; }
.table-planets thead th + th,
.table-planets tbody td + td { border-left: 1px solid var(--tp-grid-color, #bbbbbb) !important; }
.table-planets th,
.table-planets td,
.vgrid th,
.vgrid td {
  padding: var(--tp-row-py, 0.25rem) var(--tp-row-px, 0.5rem) !important;
  line-height: 1.25 !important;
  /* Top-justify, so a cell that stacks a lead word over its description starts at the
     same level as every other column. .vgrid inherited the default `middle`, which is
     what made the Grahas and Drishti tables read as floating against their neighbours. */
  vertical-align: top !important;
}
.table-planets thead th,
.vgrid thead th {
  font-family: "Special Elite", Georgia, serif !important;
  font-weight: 700 !important;
  font-size: var(--tp-header-font-size, 1rem) !important;
  text-transform: uppercase;
  letter-spacing: 0.05em;
}
.table-planets tbody td,
.vgrid tbody td {
  font-family: "Trocchi", Georgia, serif !important;
  font-size: var(--tp-body-font-size, 0.9rem) !important;
}
.table-planets tbody td.tabular-nums { font-variant-numeric: tabular-nums; }
.table-planets .astro-glyph { font-size: 1.1rem; line-height: 1; vertical-align: -8%; }

/* Wheel wrapper */
.western-wheel .wheel-wrap {
  position:relative;
  --wheel-pad: clamp(10px, 2.5vw, 18px);
  padding:var(--wheel-pad);
  background:#fefae0 url("/static/images/paper.png") repeat;
  border-radius:9999px;
  box-shadow:0 33px 45px rgba(0,0,0,.30), 0 0 10px rgba(255,183,3,.20);
  display:inline-block;
  backdrop-filter:blur(3px);
  width: min(var(--wheel-vw), var(--wheel-max));
  max-width: var(--wheel-max);
}
.western-wheel .wheel-wrap::after{
  content:""; position:absolute; inset:0; border-radius:inherit;
  box-shadow: inset 0 0 0 1px rgba(0,0,0,.08); pointer-events:none;
}

/* The square stage that holds the SVG */
.western-wheel .wheel-stage{
  width: 100%;
  aspect-ratio: 1 / 1;
}
.western-wheel .wheel-stage svg{
  width: 100%;
  height: 100%;
  display: block;
}

/* Labels/lines in the SVG */
svg .astro-glyph{ paint-order: normal; stroke: none; }

.western-wheel svg .wheel-serif,
.western-wheel svg .wheel-sans{
  paint-order: stroke fill;
  stroke: rgba(255,255,255,.45);
  stroke-width: .45px;
}

.western-wheel svg text{
  stroke:none;
  paint-order:normal;
}

.western-wheel svg .astro-glyph{
  paint-order: normal;
  stroke: none;
}

.western-wheel svg #house-lines,
.western-wheel svg #house-nums,
.western-wheel svg #zodiac-glyphs,
.western-wheel svg #sign-labels,
.western-wheel svg #planets{
  pointer-events:none;
}


.western-wheel svg .aspect{
}


/* === Wheel: ensure zodiac glyphs actually render === */
.western-wheel svg #zodiac-glyphs text{
  fill: #111 !important;
  opacity: 1 !important;
}

.western-wheel svg #zodiac-glyphs text,
.western-wheel svg #planets text{
  fill: #111;
  opacity: 1;
}

/* Degree labels on the wheel (below planet glyphs + beside the angle axes) — muted + tabular
   so they read as a secondary annotation, not competing with the body glyphs. */
.western-wheel svg text.wheel-deg{
  fill: #7a7060;
  font-family: "Trocchi", serif;
  font-variant-numeric: tabular-nums;
}
.western-wheel svg #planets text.wheel-deg{ fill: #7a7060 !important; }  /* beat #planets text{fill:#111} */


/* Popup */

.western-wheel .wheel-overlay{
  position: fixed;
  inset: 0;
  background: rgba(0,0,0,0.08);
  z-index: 9997;
}

/* Wheel overlay/popup should only affect the wheel page */
.western-wheel .wheel-popup{
  position: fixed;
  z-index: 9999;
  left: 50%;
  top: 12vh;
  transform: translateX(-50%);
  width: min(92vw, 520px);
  max-height: 78vh;
  overflow: auto;
}

.western-wheel .wheel-popup .close{
  position: absolute;
  top: 10px;
  right: 10px;

  width: 40px;
  height: 40px;
  border-radius: 999px;

  display: grid;
  place-items: center;

  font-size: 28px;
  line-height: 1;

  cursor: pointer;
  user-select: none;

  background: rgba(255,255,255,0.85);
  border: 1px solid rgba(0,0,0,0.12);
  box-shadow: 0 2px 10px rgba(0,0,0,0.12);
}

.western-wheel .wheel-popup .close:hover{
  background: rgba(255,255,255,0.95);
}

/* Star toggle: hollow (off) vs solid (on) */
.star-btn{
  font-size: 18px;
  line-height: 1;
  background: transparent;
  border: 0;
  padding: 0;
  cursor: pointer;
  /* Touch target: WCAG 2.2 AA floor (24px), centered so the ★ glyph stays put.
     The aspect-line toggles were 16x18 — too small to thumb. */
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: 24px;
  min-height: 24px;
}
/* Homepage submit spinner — shown while the chart is being cast. Honors
   prefers-reduced-motion (8C): the ring stops spinning but stays visible, so the
   "working" signal survives without motion. */
.chart-spinner{
  display: inline-block; width: 1em; height: 1em; margin-right: .5em;
  vertical-align: -0.125em; border: 2px solid currentColor;
  border-right-color: transparent; border-radius: 50%;
  animation: chart-spin .7s linear infinite;
}
@keyframes chart-spin{ to{ transform: rotate(360deg); } }
@media (prefers-reduced-motion: reduce){
  .chart-spinner{ animation: none; opacity: .6; }
}
#chart-submit[aria-busy="true"]{ cursor: progress; }

@media (pointer: coarse){
  .star-btn{ min-width: 44px; min-height: 44px; }   /* comfortable on touch */

  /* Cosmic-strip rows carry a .cosmic-stretched-link (inset:0), so the ROW is the
     tap target. The rows measured 20px, under the WCAG 2.2 SC 2.5.8 (AA) 24px
     floor — this lifts them to the minimum on touch without changing the strip's
     compact desktop look. Site-wide: the strip is on every page. */
  .cosmic-strip .link-scope{ min-height: 24px; }

  /* Standalone ACTION links (their own line, not inline in a sentence, so the
     SC 2.5.8 inline exception does not cover them). Measured 18-20px on the
     wheel; inline-flex + min-height lifts them to the 24px floor without
     disturbing text flow. */
  .house-full-link,
  .returns-teaser-link,
  .section-action,
  .wheel-action-link{
    min-height: 24px; display: inline-flex; align-items: center;
  }

  /* Homepage birth-chart form: 44px touch targets for app-grade tapping.
     Inputs sit at ~38px (py-2) by default; bump to the 44px floor, enlarge the
     two checkboxes, and give every autocomplete row a full tap height. */
  #birth-chart-form input[type="text"],
  #birth-chart-form input[type="date"]{ min-height: 44px; }
  /* flex-shrink:0 so the age-confirm box (a flex item beside long label text)
     can't be squeezed below its set size.
     1.5rem = 24px, the WCAG 2.2 SC 2.5.8 (Target Size Minimum, AA) floor. Was
     1.375rem (22px), which was under it. The checkboxes are wrapped in their
     labels so the real activation target is the whole row (see .tap-row), but
     the box itself should still clear the floor on its own. */
  #birth-chart-form input[type="checkbox"]{ width: 1.5rem; height: 1.5rem; flex-shrink: 0; }
  /* A checkbox row is a tap target in its own right — give it the 44px comfort
     height so the label text is as easy to hit as the box. */
  #birth-chart-form .tap-row{ min-height: 44px; }
  #birth-chart-form button[type="submit"]{
    min-height: 44px; display: inline-flex; align-items: center; justify-content: center;
  }
  .autocomplete-item{ min-height: 44px; display: flex; align-items: center; }
  .paper_dropdown__item{ min-height: 44px; display: flex; align-items: center; }
}
.star-btn.is-on{ color: #f59e0b; }      /* amber */
.star-btn.is-off{ color: #6a7383; }     /* gray */
.star-btn:focus-visible{
  outline: 2px dashed #FB8500;
  outline-offset: 3px;
}

/* Aspect lines base */
.western-wheel svg .aspect{
  stroke-linecap: round;
  vector-effect: non-scaling-stroke;
}

/* ============================================================
   Aspect line styling: body-type color system
   ============================================================
   Each aspect line is colored by the type(s) of bodies it connects.
   Same-type aspects (planet-planet, lot-lot, etc.) use a solid color
   from CSS custom properties. Mixed-type aspects use a per-line SVG
   linearGradient generated by the JS render layer (half color A, half
   color B, sky-hierarchy ordering).

   Two palettes provided: DEFAULT (muted, designed to not fight when
   lines overlap) and ALTERNATIVE (more saturated). Swap by commenting
   the appropriate block.
   ============================================================ */

/* === DEFAULT PALETTE: muted, low-conflict === */
.western-wheel {
  --aspect-color-angle:    #6366f1;  /* indigo-500, structural anchor */
  --aspect-color-planet:   #0284c7;  /* sky-600, classic chart blue */
  --aspect-color-asteroid: #059669;  /* emerald-600, vegetal */
  --aspect-color-point:    #8b5cf6;  /* violet-500, mysterious */
  --aspect-color-lot:      #d97706;  /* amber-600, warm gold */
  --aspect-color-fixed_star: #e11d48; /* rose-600, fixed-star fire */
}

/* === ALTERNATIVE PALETTE: more saturated === */
/*
.western-wheel {
  --aspect-color-angle:    #4338ca;
  --aspect-color-planet:   #0369a1;
  --aspect-color-asteroid: #047857;
  --aspect-color-point:    #6d28d9;
  --aspect-color-lot:      #b45309;
  --aspect-color-fixed_star: #be123c;
}
*/

/* Same-type aspects: solid color from CSS variable */
.western-wheel svg .aspect.type-angle    { stroke: var(--aspect-color-angle); }
.western-wheel svg .aspect.type-planet   { stroke: var(--aspect-color-planet); }
.western-wheel svg .aspect.type-asteroid { stroke: var(--aspect-color-asteroid); }
.western-wheel svg .aspect.type-point    { stroke: var(--aspect-color-point); }
.western-wheel svg .aspect.type-lot      { stroke: var(--aspect-color-lot); }
.western-wheel svg .aspect.type-fixed_star { stroke: var(--aspect-color-fixed_star); }

/* Major: solid, full opacity, regular weight */
.western-wheel svg .aspect.major {
  stroke-width: 1.4;
  stroke-opacity: 0.75;
}

/* Minor: dashed, lower opacity, thinner */
.western-wheel svg .aspect.minor {
  stroke-width: 0.9;
  stroke-opacity: 0.5;
  stroke-dasharray: 3 3;
}

/* Mixed-type aspects use stroke="url(#aspect-grad-N)" from inline
   gradient; only opacity and dashing come from these rules. */
.western-wheel svg .aspect.mixed.major {
  stroke-width: 1.4;
  stroke-opacity: 0.8;
}
.western-wheel svg .aspect.mixed.minor {
  stroke-width: 0.9;
  stroke-opacity: 0.6;
  stroke-dasharray: 3 3;
}

.western-wheel .wheel-stage,
.western-wheel .wheel-stage svg{
  overflow: visible;
}


/* ====================================================================================== Banner Menu Panel (paper) ============================================================================*/
.banner-menu { position: relative; display: inline-block; }
.banner-menu__panel {
  position: absolute;
  top: calc(100% + 8px);
  left: 0;
  min-width: 200px;
  padding: .5rem;
  border-radius: 12px;
  background: #fefae0 url("/static/images/paper.png") repeat;
  background-size: 512px 512px;
  box-shadow: 0 18px 40px rgba(0,0,0,.25), 0 0 0 1px rgba(0,0,0,.06) inset;
  /* The menu has grown long (Western/Vedic/Chinese + Returns & Events). Cap it to
     the viewport and let it scroll instead of running off the bottom of the screen. */
  max-height: 80vh;
  overflow-y: auto;
  overflow-x: hidden;
  overscroll-behavior: contain;         /* don't scroll-chain to the page behind it */
  -webkit-overflow-scrolling: touch;    /* momentum scroll on iOS */
  /* ★ THIS LATER RULE WAS QUIETLY WINNING. ★ Line ~417 sets this to var(--z-menu) and
     even carries the comment "was 40" — but THIS declaration comes afterwards, same
     specificity, so 40 is what the browser actually used. The token looked adopted and
     never was. Measured, not read: computed z-index was 40. */
  z-index: var(--z-menu);
}
.banner-menu__panel::after{
  content:""; position:absolute; inset:0; border-radius:inherit;
  box-shadow: inset 0 0 0 1px rgba(0,0,0,.08); pointer-events:none;
}
.banner-menu__item {
  display:block; padding:.45rem .65rem; border-radius:.5rem;
  color:#023047; text-decoration:none;
}
.banner-menu__item:hover, .banner-menu__item:focus {
  background: rgba(255, 183, 3, 0.14);
  outline: none;
}
.banner-menu__item:focus-visible { outline: 2px dashed #FB8500; outline-offset: 3px; }
.banner-menu[open] .banner-menu__panel::before{
  content:""; position:absolute; top:-6px; left:12px; width:12px; height:12px;
  transform: rotate(45deg); background:#fefae0; box-shadow: -1px -1px 0 0 rgba(0,0,0,.06);
}
.banner-menu:not([open]) .banner-menu__panel { display: none; }

/* ★ WHILE THE MENU IS OPEN, THE SEARCH FIELD STANDS DOWN. ★
   MEASURED at 390px: the panel occupies top 84 / left 28 / 200x675 and the search input
   top 91 / left 67 / 281x31 — they genuinely overlap, and the input won the hit test, so
   13 of 27 menu items could not be tapped. "Account" was one of them, which meant a
   reader had to SEARCH for their own account page: the route to billing, cancellation and
   data deletion.

   Two z-index faults were found and fixed on the way here (a stacking context on
   .site-banner__menu, and a duplicate panel declaration overriding the token) and NEITHER
   changed the hit test — so this is deliberately not a third z-index guess. visibility
   removes the field from hit testing outright, which is the one behaviour that cannot be
   out-ranked by a layer we have not accounted for.

   It is also the better interaction: nobody needs the search box while the menu they just
   opened is covering it. Restored the instant the menu closes.

   Same lesson as the glossary chips: VISIBLE IS NOT REACHABLE. Verify by hit test
   (document.elementFromPoint), never by looking at it. */
.site-banner:has(.banner-menu[open]) .sitesearch { visibility: hidden; }
.banner-menu__trigger {
  list-style: none; -webkit-appearance: none; display: inline-flex; align-items: center;
  gap: 0; cursor: pointer; padding: 0; margin: 0; border: 0; background: transparent;
}
.banner-menu__trigger::-webkit-details-marker { display: none; }
.banner-menu__trigger::marker { content: ""; }
.banner-menu__trigger:focus-visible { outline: 2px dashed #FB8500; outline-offset: 4px; }


/* ====================================================================================== Current Sky Page ============================================================================*/

/* moon current sky mobile helpers */
.sky-line {
  word-break: break-word;
  min-width: 0;
}
@media (max-width: 480px) {
  .sky-line .moon-deg {
    font-size: 0.75rem;
  }
  .sky-line .text-gray-800 {
    font-size: 0.7rem;
  }
}
/* Collapsible summary caret styling (used in current_sky templates) */
summary.collapsible-summary {
  list-style: none;
  display: flex;
  align-items: center;
  cursor: pointer;
}
summary.collapsible-summary::-webkit-details-marker {
  display: none;
}
.caret-icon {
  display: inline-block;
  transition: transform 0.2s ease;
  margin-right: 0.35rem;
  color: #6b7280;
  /* ★ 0.7rem MEASURED 11.2px, BELOW THE SCALE'S FLOOR. ★ It is a disclosure caret rather
     than prose, and it is still something a reader looks at to know whether a section is
     open. --t-micro is the floor and costs nothing here. */
  font-size: var(--t-micro);
}
details[open] .caret-icon {
  transform: rotate(90deg);
}



/* ====================================================================================== Kindred Rows (Family & Connections) ============================================================================*/

/* ── The wingmate list ──────────────────────────────────────────────────────
   Each person is a CARD, and the eye needs to see where one ends and the next
   begins without reading. Three things do that work together, and dropping any
   one of them is what made these read as a single block:

     gap      0.85rem — 8px between bordered cards reads as a rendering artefact
                        rather than a deliberate space. This is the whole ask.
     surface  a card that is lighter than the paper behind it, so the separation
                        survives even where the hairline border is hard to see.
     border   a hairline, slightly stronger than before, since it is now the
                        thing carrying the card edge on a busy background.

   NOTE for the next rename: these selectors were `.kindred-*` and the TEMPLATE was
   renamed without the STYLESHEET, so every one of them silently stopped matching and
   the list rendered completely unstyled — measured 0px gaps. A CSS class is an
   identifier that lives in two files. */
.wingmate-list { display: flex; flex-direction: column; gap: 0.85rem; }

.wingmate-row {
  border: 1px solid rgba(0,0,0,0.12);
  border-radius: 0.625rem;
  background: rgba(255,255,255,0.62);
  padding: 0.85rem 1rem;
  transition: border-color .15s ease, box-shadow .15s ease;
}

/* A little lift on hover, so it is obvious each block is one person and the row
   actions belong to the name above them. */
.wingmate-row:hover {
  border-color: rgba(2,48,71,0.28);
  box-shadow: 0 2px 8px rgba(0,0,0,0.06);
}

/* The name is the thing being scanned for, so give it room to lead the card. */
.wingmate-row__name { margin-top: 0.3rem; }
.wingmate-row__footer { margin-top: 0.5rem; }

.wingmate-row__header {
  display: flex;
  justify-content: space-between;
  align-items: center;
  gap: 0.5rem;
}

.wingmate-row__relationship {
  font-size: 0.75rem;
  text-transform: lowercase;
  letter-spacing: 0.06em;
  color: #6b7280;
  font-weight: 600;
}

.wingmate-row__name {
  font-weight: 600;
  font-size: var(--t-body);
  color: #023047;
  margin-top: 0.15rem;
  line-height: 1.2;
}

.wingmate-row__birth {
  font-weight: 400;
  font-size: 0.875rem;
  color: #6b7280;
  margin-left: 0.25rem;
}

.wingmate-row__big3 {
  display: flex;
  flex-wrap: wrap;
  gap: 0.75rem;
  margin-top: 0.25rem;
  font-size: var(--t-small);
  color: #4b5563;
}

.wingmate-big3-item { white-space: nowrap; }
.wingmate-big3-item .astro-glyph { color: #023047; }

/* Kindred status badges */
.wingmate-badge {
  display: inline-block;
  padding: 0.125rem 0.5rem;
  border-radius: 0.375rem;
  font-size: var(--t-micro);
  font-weight: 500;
  text-transform: lowercase;
  letter-spacing: 0.02em;
}
.wingmate-badge--ready      { background: #dcfce7; color: #166534; }
.wingmate-badge--generating { background: #fef3c7; color: #92400e; }
.wingmate-badge--error      { background: #fee2e2; color: #991b1b; }
.wingmate-badge--pending    { background: #f3f4f6; color: #4b5563; }
/* Kindred row footer: big-3 on left, edit/delete actions on right */
.wingmate-row__footer {
  display: flex;
  justify-content: space-between;
  align-items: center;
  gap: 0.75rem;
  margin-top: 0.25rem;
  flex-wrap: wrap;
}

/* Big-3 in the footer doesn't need its own top margin anymore */
.wingmate-row__footer .wingmate-row__big3 { margin-top: 0; }

.wingmate-row__actions {
  display: flex;
  align-items: center;
  /* ★ WRAP, DON'T PUSH (2026-09-16). ★ flex-shrink:0 with four links in one row ran the
     last one 13px past the edge of a 390px screen on every wingmate. */
  flex-wrap: wrap;
  gap: 0 0.75rem;
  min-width: 0;
}

.wingmate-action-link {
  font-size: var(--t-micro);
  text-decoration: underline;
  background: none;
  border: none;
  padding: 0;
  cursor: pointer;
  font-family: inherit;
  /* A 24px target: WCAG 2.2 AA 2.5.8. These stand alone, not inside a sentence. */
  display: inline-flex;
  align-items: center;
  min-height: 24px;
}
.wingmate-action-link--edit   { color: #4b5563; }
.wingmate-action-link--edit:hover   { color: #023047; }
.wingmate-action-link--delete { color: #b91c1c; }
.wingmate-action-link--delete:hover { color: #7f1d1d; }

.wingmate-delete-form { display: inline; margin: 0; padding: 0; }

/* Inline tip shown only on the edit-kindred form */
.wingmate-edit-tip {
  background: #fef3c7;
  border-left: 3px solid #f59e0b;
  border-radius: 0.375rem;
  padding: 0.75rem 1rem;
  font-size: 0.875rem;
  color: #78350f;
  margin-bottom: 1rem;
}
.wingmate-edit-tip strong { color: #78350f; }

/* Kindred form: multi-select checkbox grids for pronouns + relationships */
.wingmate-checkbox-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(180px, 1fr));
  gap: 0.4rem 0.75rem;
  margin-top: 0.25rem;
}

.wingmate-checkbox-item {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  font-size: var(--t-small);
  cursor: pointer;
  user-select: none;
}

.wingmate-checkbox-item input[type="checkbox"] {
  cursor: pointer;
}

.wingmate-checkbox-item input[type="checkbox"]:disabled + span {
  color: #6a7383;
  text-decoration: line-through;
}

/* ============================================================
   Synastry page (account_kindred_synastry.html)
   ============================================================ */

/* Header: relationship tag inline with title */
.synastry-rel-tag {
  font-size: 0.6em;
  color: #555;
  font-weight: normal;
  margin-left: 0.5em;
}

/* Big-3 strip — both people side by side with a divider */
.synastry-big3-strip {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 1.5rem;
  margin-top: 1rem;
  flex-wrap: wrap;
}

.synastry-big3-person {
  text-align: center;
  flex: 0 0 auto;
}

.synastry-big3-name {
  font-weight: 600;
  font-size: 1rem;
  color: #023047;
  margin-bottom: 0.25rem;
}

.synastry-big3-signs {
  display: flex;
  gap: 0.75rem;
  font-size: 0.85rem;
  color: #555;
}

.synastry-big3-signs span {
  white-space: nowrap;
}

.synastry-big3-divider {
  font-size: 1.5rem;
  color: #999;
  font-weight: 300;
}

/* Narrative section — gives markdown some breathing room */
.synastry-narrative {
  line-height: 1.6;
  color: #374151;   /* softened body ink — matches the site default (see body{color}) */
}
.synastry-narrative h1,
.synastry-narrative h2,
.synastry-narrative h3 {
  font-family: serif;
  color: #023047;
  margin-top: 1em;
  margin-bottom: 0.4em;
}
.synastry-narrative h1 { font-size: 1.5rem; }
.synastry-narrative h2 { font-size: 1.25rem; }
.synastry-narrative h3 { font-size: 1.1rem; }
.synastry-narrative p {
  margin: 0 0 1em 0;
}
.synastry-narrative strong { color: #023047; }

/* Aspect list — one block per aspect */
.synastry-aspect-list {
  list-style: none;
  padding: 0;
  margin: 0;
}

.synastry-aspect-item {
  border-top: 1px solid #d6d0c0;
  padding: 1rem 0;
}
.synastry-aspect-item:first-child {
  border-top: none;
  padding-top: 0.5rem;
}

.synastry-aspect-header {
  display: flex;
  flex-wrap: wrap;
  justify-content: space-between;
  align-items: baseline;
  gap: 0.5rem;
  margin-bottom: 0.5rem;
}

.synastry-aspect-line {
  font-weight: 600;
  color: #023047;
  font-size: 0.95rem;
}

.synastry-aspect-body {
  display: inline;
}

.synastry-aspect-glyph {
  font-size: 1.1rem;
  margin: 0 0.4em;
  color: #fb8500;
}

.synastry-aspect-meta {
  font-size: 0.75rem;
  color: #777;
  font-style: italic;
}

.synastry-perspective {
  margin: 0.3rem 0 0 0;
  font-size: 0.9rem;
  line-height: 1.5;
}

.synastry-perspective-label {
  font-weight: 600;
  color: #023047;
  margin-right: 0.4em;
}

.synastry-perspective-text {
  color: #374151;   /* softened body ink — matches the site default (see body{color}) */
}

/* Kindred action link — "View" variant for synastry */
.wingmate-action-link--view {
  color: #ae5c00;
}
.wingmate-action-link--view:hover {
  text-decoration: underline;
}

/* Synastry aspect — house tag (e.g. "(3rd house)") sits next to sign */
.synastry-house-tag {
  font-size: 0.85em;
  color: #6b7280;
  font-weight: normal;
}

/* Banner menu — Kindred Spirits nested submenu */
.banner-menu__submenu {
  display: block;
}
.banner-menu__submenu-trigger {
  cursor: pointer;
  list-style: none;
}
.banner-menu__submenu-trigger::-webkit-details-marker { display: none; }
.banner-menu__submenu-trigger::after {
  content: " ▾";
  font-size: 0.75em;
  margin-left: 0.25rem;
  opacity: 0.6;
}
.banner-menu__submenu[open] .banner-menu__submenu-trigger::after {
  content: " ▴";
}
.banner-menu__submenu-panel {
  padding-left: 1rem;
}
.banner-menu__subitem {
  display: block;
  padding-left: 1.25rem !important;
  font-size: 0.95em;
  opacity: 0.9;
}

/* ==================================================================
   Canonical primary button — navy with orange text. The site-wide
   default look for primary actions/CTAs. Use class="btn-primary"
   (composes with width utilities like w-full).
   ================================================================== */
.btn-primary {
  display: inline-block;
  font-weight: 600;
  padding: 0.5rem 1.25rem;
  border-radius: 0.375rem;
  background-color: #023047;   /* navy */
  color: #fb8500;              /* orange */
  border: none;
  cursor: pointer;
  text-align: center;
  text-decoration: none;
  box-shadow: 0 6px 12px rgba(0,0,0,0.30);
  transition: box-shadow .2s ease, background-color .2s ease, transform .05s ease;
}
.btn-primary:hover  { background-color: #033b59; color: #ffa733; box-shadow: 0 8px 16px rgba(0,0,0,0.38); }
.btn-primary:active { transform: translateY(1px); }
.btn-primary:focus-visible { outline: 2px solid #fb8500; outline-offset: 2px; }

/* ── A PRIMARY BUTTON THAT IS NOT READY YET ─────────────────────────────────────────
   Two spellings, one look. `:disabled` is the server's (payments not open, or the plan
   the reader already has). `[aria-disabled]` is the one declarative.js applies while a
   form still has an unticked required box: it stays focusable and announces itself, so
   a screen reader meets a control that says "unavailable" rather than one that is
   simply absent from the tab order.

   ★ MUTED IN HUE, NOT IN CONTRAST. ★ `opacity` was the obvious way and is the wrong one:
   the label is 16px at weight 600, which is NOT large text by WCAG (that starts at
   18.66px bold), so it owes 4.5:1 like any other text. COMPUTED against the navy:
   #9aa8b1 is 5.68:1, which is a shade BETTER than the live button's orange at 5.58:1.
   It reads as held back because it is grey where the live one is orange, and a reader
   who needs the contrast loses nothing. */
.btn-primary:disabled,
.btn-primary[aria-disabled="true"] {
  color: #9aa8b1;
  box-shadow: 0 2px 4px rgba(0,0,0,0.18);
  cursor: not-allowed;
}
.btn-primary:disabled:hover,
.btn-primary[aria-disabled="true"]:hover {
  background-color: #023047;
  color: #9aa8b1;
  box-shadow: 0 2px 4px rgba(0,0,0,0.18);
}
.btn-primary[aria-disabled="true"]:active { transform: none; }

/* The line under such a button saying what is still missing. Empty until it is needed,
   so it takes no room on a page where nothing is wrong. #5d6a72 on the card's #fefae0
   is 5.30:1, which clears AA at this size (14px). */
.gate-hint:empty { display: none; }
.gate-hint {
  margin: .4rem 0 0;
  color: #5d6a72;
  text-align: center;
}

/* Paper-faced primary button — the same stock as the card it sits on (owner, 2026-08-13,
   for the Pocket Horoscope card's "Save my choices"). Keeps .btn-primary's geometry and
   replaces only the surface, so a paper button and a navy one are the same object in
   different clothes.

   ★ IT MUST SIT BELOW EVERY .btn-primary STATE, NOT JUST BELOW .btn-primary. ★ First
   attempt put it directly after the base rule, which is enough for the resting state and
   NOT for :hover — `.btn-primary:hover` is (0,2,0) against this block's (0,1,0), so the
   button turned navy the moment a cursor touched it, and `.btn-paper:hover` placed above
   it lost the tie on source order too. Equal specificity is settled by who comes last.

   ★ A BUTTON THE COLOUR OF ITS BACKGROUND NEEDS ITS EDGES BACK. ★ Take the navy away and
   the only things still saying "pressable" are the border and the lift, so both are
   explicit here rather than inherited. The ink goes navy: orange on cream is roughly 2:1
   and fails WCAG AA outright, while #023047 on #fefae0 is about 14:1. */
.btn-paper {
  background: #fefae0 url("/static/images/paper.png") repeat;
  color: #023047;
  border: 1px solid rgba(2, 48, 71, .35);
}
.btn-paper:hover {
  background: #fdf6d8 url("/static/images/paper.png") repeat;
  color: #023047;
  border-color: rgba(2, 48, 71, .55);
  box-shadow: 0 8px 16px rgba(0,0,0,0.28);
}
.btn-paper:focus-visible { outline: 2px solid #023047; outline-offset: 2px; }
/* ── .btn-secondary: the quieter partner of .btn-primary (a Close beside a Send, "Copy the
   text" beside a link). ★ IT WAS USED AND NEVER DEFINED (found 2026-09-17 by
   scripts/cross_file_identifiers.py) ★, so the feedback box's Close and the invitation page's
   buttons rendered as bare browser buttons. Same geometry as .btn-primary, paper surface, navy
   ink and a visible edge. */
.btn-secondary {
  display: inline-flex; align-items: center; justify-content: center;
  min-height: 36px; padding: 0.4rem 1rem; border-radius: 0.375rem;
  font-weight: 600; font-family: inherit; text-decoration: none; cursor: pointer;
  color: #023047; background: transparent; border: 1px solid rgba(2, 48, 71, .45);
  transition: background-color .15s ease, border-color .15s ease;
}
.btn-secondary:hover { background: rgba(2, 48, 71, .06); border-color: rgba(2, 48, 71, .7); color: #023047; }
.btn-secondary:focus-visible { outline: 2px solid #fb8500; outline-offset: 2px; }

/* Touch devices: ensure every primary CTA meets the 44px tap-target standard. */
@media (pointer: coarse) {
  .btn-primary { min-height: 44px; display: inline-flex; align-items: center; justify-content: center; }
}

/* Planetary-return season rows link to their detail page (returns/per_planet). */
.return-detail-link { text-decoration: none; }
.return-detail-link:hover { text-decoration: underline; }
.return-detail-arrow {
  opacity: 0;
  margin-left: .15rem;
  transition: opacity .15s ease;
}
.return-detail-link:hover .return-detail-arrow,
.return-detail-link:focus-visible .return-detail-arrow { opacity: .65; }

/* "Freshly updated" ribbon on the family reading (family_updates events). */
.family-update-ribbon {
  border-left: 3px solid #fb8500;
  background: rgba(251, 133, 0, 0.06);
  border-radius: 6px;
  padding: .7rem .9rem;
}
.family-update-ribbon__head {
  font-weight: 600;
  color: #023047;
  font-size: .92rem;
}
.family-update-ribbon__list {
  list-style: none;
  padding: .25rem 0 0;
}
.family-update-ribbon__list li {
  color: #023047;
  font-size: .92rem;
  padding: .1rem 0 .1rem 1rem;
  position: relative;
}
.family-update-ribbon__list li::before {
  content: "—";
  position: absolute;
  left: 0;
  color: #fb8500;
}
.family-update-ribbon__foot {
  margin-top: .4rem;
  font-size: .82rem;
  font-style: italic;
  color: #6b7280;
}

/* Dashboard "Aunt Starling has news" family-update notice card. */
.family-news-card {
  display: block;
  text-decoration: none;
  border: 1px solid rgba(251, 133, 0, 0.45);
  background: rgba(251, 133, 0, 0.07);
  border-radius: 10px;
  padding: .8rem 1rem;
  transition: background-color .15s ease, box-shadow .15s ease;
}
.family-news-card:hover {
  background: rgba(251, 133, 0, 0.12);
  box-shadow: 0 4px 12px rgba(0,0,0,0.10);
}
.family-news-card__head {
  font-weight: 700;
  color: #023047;
  font-size: .95rem;
}
.family-news-card__list {
  list-style: none;
  padding: .3rem 0 0;
  margin: 0;
}
.family-news-card__list li {
  color: #023047;
  font-size: .9rem;
  padding: .08rem 0 .08rem 1rem;
  position: relative;
}
.family-news-card__list li::before {
  content: "—";
  position: absolute;
  left: 0;
  color: #fb8500;
}
.family-news-card__cta {
  margin-top: .45rem;
  font-size: .82rem;
  font-weight: 600;
  color: #ae5c00;
}

/* "Returns on the Horizon" household spotlight rows (link to each detail page). */
.returns-horizon-row {
  display: flex;
  align-items: center;
  gap: 10px;
  text-decoration: none;
  color: inherit;
  border-radius: 8px;
  padding: 6px 8px;
  transition: background-color .15s ease;
}
.returns-horizon-row:hover { background: rgba(2, 48, 71, 0.05); }
.returns-horizon-arrow {
  color: #fb8500;
  opacity: 0;
  transition: opacity .15s ease;
  white-space: nowrap;
}
.returns-horizon-row:hover .returns-horizon-arrow,
.returns-horizon-row:focus-visible .returns-horizon-arrow { opacity: .8; }

/* "From Aunt Starling" notices stack (templates/_notices_stack.html). */
/* ★ THE NOTICES STACK WAS THE ONLY THING OFF THE SCALE ON THIS PAGE (2026-09-09). ★
   MEASURED in a browser at 390px: 214 text nodes, and the only genuinely off-scale sizes
   were these three — .9rem and .8rem, which are not steps at any breakpoint. Everything
   else that looked wrong at first (19px headings, a 22px h1) is the PHONE TIER doing
   exactly what the TEXT SIZE STANDARD says it should: the heading steps move per
   breakpoint, the body/small/micro steps do not. I nearly filed those as violations. */
.notices-stack__head {
  font-weight: 700;
  color: #023047;
  font-size: var(--t-small);
  margin-bottom: .5rem;
}
.notices-stack__list { display: flex; flex-direction: column; gap: 8px; }
.notice-card {
  display: flex;
  align-items: center;
  gap: 10px;
  border: 1px solid rgba(2, 48, 71, 0.12);
  background: rgba(255, 255, 255, 0.55);
  border-radius: 8px;
  padding: .5rem .7rem;
  transition: opacity .15s ease;
}
.notice-card--new {
  border-left: 3px solid #fb8500;
  background: rgba(251, 133, 0, 0.06);
}
.notice-icon { color: #fb8500; font-size: 1.05rem; line-height: 1; flex: 0 0 auto; }
.notice-body { flex: 1; text-decoration: none; color: inherit; display: block; }
.notice-body:hover .notice-msg { text-decoration: underline; }
.notice-title {
  display: block;
  font-weight: 600;
  color: #023047;
  /* .8rem was 12.8px — below the 12px floor's own step and off the scale. --t-micro IS
     the floor, so this is the smallest it is allowed to be, not a size chosen by eye. */
  font-size: var(--t-micro);
}
.notice-msg { display: block; color: #374151; font-size: var(--t-small); }
.notice-dismiss {
  flex: 0 0 auto;
  border: none;
  background: transparent;
  color: #6b757c;
  font-size: 1.2rem;
  line-height: 1;
  cursor: pointer;
  padding: 0 .25rem;
  border-radius: 4px;
  /* ★ 24px, AND A COLOUR THAT CAN BE SEEN. ★ Measured 17x19 at 390px; #9aa0a6 was 2.6:1
     on white, under the 3:1 a control needs (WCAG 1.4.11). #6b757c is 4.6:1. */
  min-width: 24px; min-height: 24px;
}
.notice-dismiss:hover { color: #023047; background: rgba(2,48,71,0.06); }

/* ═══════════════════════════════════════════════════════════════════════════
   "Today's Sky Over Your Chart" — SHARED transit/current-sky component.
   Promoted here (was inline in western_wheel.html) so /chart/western,
   /chart/vedic and /chart/chinese render the identical section. See the
   Chart-Page Parity Blueprint §9 for the data contract + markup.
   ═══════════════════════════════════════════════════════════════════════════ */
/* Measured 11.8px on the Vedic chart; --t-micro is the floor. */
.today-sky-date  { font-size: var(--t-micro); color: #6b7280; font-style: italic; }
.today-sky-count { font-size: 0.78rem; color: #6b7280; font-style: italic; }
/* Standard expand-toggle / section action link — same size/weight/colour everywhere. */
.section-action { font-size: var(--t-micro); font-weight: 600; color:#5d7782; font-style: normal; }
.section-action:hover { color: #023047; }
.today-sky-intro { font-size: var(--t-small); color: #374151; line-height: 1.6; margin-bottom: 0.6rem; }
.today-sky-intro p { margin-bottom: 0.5rem; }
.today-sky-intro p:last-child { margin-bottom: 0; }
/* Measured 11.2px on the wheel; --t-micro is the floor. */
.today-sky-horo-label { font-size: var(--t-micro); font-weight: 700; text-transform: uppercase; letter-spacing: 0.06em; color:#5d7782; margin-bottom: 0.35rem; }
.today-sky-list { display: flex; flex-direction: column; gap: 0.5rem; }
.ts-row { padding: 0.35rem 0.5rem; border-radius: 0.5rem; }
.ts-row:nth-child(odd) { background: rgba(0,0,0,0.025); }
.ts-head { display: flex; align-items: baseline; gap: 0.45em; flex-wrap: wrap; }
.ts-glyph { font-size: 0.95rem; color: #023047; }
.ts-line { font-size: 0.82rem; color: #374151; }
.ts-body { font-weight: 600; color: #023047; }
.ts-asp { color: #6b7280; font-weight: 600; font-style: italic; }
.ts-retro { color: #6d28d9; font-weight: 700; }
.ts-orb { font-size: 0.72rem; color: #6b7280; font-variant-numeric: tabular-nums; margin-left: auto; white-space: nowrap; }
.ts-read { font-size: 0.76rem; color: #6b7280; font-style: italic; margin-top: 0.15rem; padding-left: 1.45rem; line-height: 1.45; }
.ts-fast { color: #9a8a5a; }
.today-sky-more { font-size: 0.74rem; color: #6b7280; font-style: italic; margin-top: 0.6rem; }
.today-sky-note { font-size: 0.72rem; color: #6b7280; margin-top: 0.4rem; }

/* ═══════════════════════════════════════════════════════════════════════════
   Chart-ruler / Lagnesha hero — SHARED component (was inline in western_wheel.html).
   Used by /chart/western "Chart Ruler" and /chart/vedic "Lagna Lord (Lagnesha)".
   See Chart-Page Parity Blueprint §8/§14. (Western keeps an identical inline copy.)
   ═══════════════════════════════════════════════════════════════════════════ */
.chart-ruler-body { min-width: 0; }
.chart-ruler-headline { font-size: var(--t-body); color: #023047; line-height: 1.4; }
.chart-ruler-blurb { margin-top: 0.5rem; }
.chart-ruler-blurb p { font-size: 0.82rem; color: #374151; line-height: 1.6; margin-bottom: 0.5rem; }
.chart-ruler-blurb p:last-child { margin-bottom: 0; }
.chart-ruler-name { font-weight: 700; }
.chart-ruler-sub { font-weight: 400; color: #374151; font-size: var(--t-small); }
.chart-ruler-place { font-size: 0.82rem; color: #4b5563; margin-top: 0.35rem; line-height: 1.5; }
.chart-ruler-house-theme { color: #6b7280; }
.chart-ruler-dignity { color: #7c2d12; font-weight: 600; }
.chart-ruler-meaning { font-size: 0.8rem; color: #6b7280; font-style: italic; margin-top: 0.3rem; line-height: 1.45; }
.chart-ruler-modern { font-size: 0.78rem; color: #8a7a5a; margin-top: 0.35rem; }

/* ── Chart filter bar (mobile review #22/#24) ──────────────────────────────
   The pill/select row clipped at 390pt with "Lots" cut mid-word. It already had
   flex-wrap; the failure was the selects not being allowed to shrink and the row
   having no breathing room. Nothing is hidden now — it wraps. */
.filter-bar-row{ row-gap: .5rem; }
.filter-bar-row > select{ max-width: 100%; }
@media (max-width: 430px){
  /* Let each control take a sensible share instead of forcing one long line. */
  .filter-bar-row > select{ flex: 1 1 45%; min-width: 0; }
}

/* ── Popup/dialog hardening, site-wide (mobile review #13/#14/#18) ──────────
   Any paper card inside a top-layer <dialog> must not use backdrop-filter: on
   iOS Safari it composites badly there and can drop the card's own background,
   which is what made the house popup look transparent with the wheel showing
   through. The cards are already opaque, so nothing is lost visually. */
dialog[open] .paper_box,
dialog[open] .fp-popup{ backdrop-filter:none !important; -webkit-backdrop-filter:none !important; }

/* ── Sticky-header anchor offset, site-wide (mobile review #8/#15/#20) ──────
   Every in-page jump link landed with its target heading hidden behind the
   sticky header (a "Birthstone:" line rendered sliced in half). One rule fixes
   every anchor on every page. --header-h is overridable per page if a header
   ever differs. */
:root{ --header-h: 56px; }
[id]{ scroll-margin-top: calc(var(--header-h) + env(safe-area-inset-top) + 12px); }
html{ scroll-padding-top: calc(var(--header-h) + env(safe-area-inset-top) + 12px); }

/* ── Branded focus ring (mobile review #16) ────────────────────────────────
   Custom controls showed the default blue browser outline, off-palette. Never
   remove focus styling — recolour it. */
:focus-visible{ outline:2px solid #fb8500; outline-offset:2px; }

/* ── Tap targets, site-wide (mobile review #4/#10/#12/#16/#24/#28) ──────────
   A 390pt sweep of 14 pages found ~197 standalone controls under the WCAG 2.2
   SC 2.5.8 (AA) 24px floor, concentrated in these patterns. Scoped to anchors,
   buttons and summaries so inline prose is untouched — the measurement already
   excluded links sitting inside <p>/<li>, which are covered by the SC 2.5.8
   inline exception. */
@media (pointer: coarse){
  /* ★ A FLOOR MUST NOT ALSO BE A CEILING, SO IT CARRIES NO SPECIFICITY (2026-10-22). ★
     This list was written to LIFT small standalone links to the WCAG 2.2 SC 2.5.8 (AA)
     24px minimum. Written as `a.t-small` it is (0,1,1) — and a plain class is (0,1,0),
     so it did not lift those links, it CAPPED every deliberate 44px target that happened
     to be an anchor carrying a type class.

     MEASURED at 390px before the fix, four controls on eight pages:

       .fb-fab              Need it Fixed?      asked 44, got 24 (32px tall)
       a.btn-primary        /nest, /account     asked 44, got 24 (39px tall)
       .summary-jump        /chart/western      asked 44, got 24 (33px tall)
       .wheel-action-link   /chart/western, /today   asked 44, got 24 (24px tall)

     Every one of them is the type-beats-utility fault this project already paid for on
     the type scale: `.nest-reading p` quietly claiming every `.t-micro` in its card.
     `:where()` makes this list weigh nothing, which is what a floor should weigh —
     anything that states a target of its own wins, and an element with no opinion still
     gets its 24px. */
     ★ AND `display:inline-flex` WAS EATING SPACES, ON TOUCH DEVICES ONLY (2026-10-22). ★
     A flex container drops whitespace-only children, so any link holding more than one
     inline child lost the spaces between them. MEASURED on /returns-and-events, where
     "<span>Uranus</span> in <span>Gemini</span>" rendered as "UranusinGemini": that
     anchor computes inline-flex at 123px on a phone and inline at 130px on a desktop,
     and the 7px IS the two spaces. `textContent` reads "Uranus in Gemini" either way,
     so no test asserting on text could ever have caught it; only the pixels show it.
     An inline-flex anchor also cannot break across lines, so a long one overflows
     instead of wrapping.

     The split below is by what the control IS. The four entries that were being
     flexified have no display of their own and sit inside sentences; they reach the
     24px target with vertical PADDING, which leaves them inline, keeps their spaces and
     lets them wrap. The others already declare their own box (`.chart-full-link` is
     inline-block, `.wingmate-action-link` sets inline-flex itself with the note "these
     stand alone, not inside a sentence", and a <button> is a box that centres its own
     label), so min-height alone does the work there, and dropping the flex from the
     buttons protects their labels from the same fault. */
  :where(a.chart-full-link,
         a.wingmate-action-link, button.wingmate-action-link,
         button.cn-showpill){
    min-height:24px;
  }
  :where(a.strong-navy,
         a.t-micro, a.t-small,
         a.hover\:underline){
    padding-block:0.35rem;
  }
  /* summary keeps its default display so the disclosure marker survives. */
  summary{ min-height:24px; }
  /* Checkboxes site-wide to the 24px floor (matches the homepage form fix). */
  input[type="checkbox"], input[type="radio"]{ width:1.5rem; height:1.5rem; flex-shrink:0; }
}

/* ── Jump-link chips (device review #9/#10) ─────────────────────────────────
   Was inline flow text: ragged 5-line block, inconsistent gaps, sub-44pt taps.
   Now wrapped chips with a real tap height. Shared by the Western, Vedic and
   Chinese primary readings via templates/_jump_links.html. */
/* ── The Looking Glass page editor ──────────────────────────────────────────
   These pages hold 71 long-form answers. The old layout put each one in a
   3-row textarea inside a 56rem column, so reading a single answer meant
   scrolling a tiny inner window — 71 times. Now: a wider measure, body-size
   type with reading leading, and boxes that grow to their content. */
.audit-editor{ max-width: 78rem; }
.audit-answer{
  field-sizing: content;          /* native auto-grow where supported */
  min-height: 4.5rem;
  line-height: 1.65;              /* reading leading, not form leading */
  color:#374151;
  background:#fffdf8;             /* warm ground so long prose is easier on the eye */
  overflow-y: hidden;             /* it grows instead of scrolling internally */
  /* A textarea carries an intrinsic width from its `cols` default and will not
     shrink below it, which dragged the fieldset 427px wide inside a 358px column
     and overflowed the phone viewport by 81px. Both lines are needed. */
  box-sizing: border-box;
  max-width: 100%;
}
/* Fieldsets do not shrink below their content by default — the other half of the
   same overflow. */
.audit-editor fieldset{ min-width: 0; }
.audit-answer:focus{
  background:#fff; outline:2px solid #8ecae6; outline-offset:1px;
}
@media (max-width: 900px){
  /* Keep a comfortable measure on tablets rather than stretching the column. */
  .audit-editor{ max-width: 100%; }
}

.jump-chips{ display:flex; flex-wrap:wrap; gap:.5rem; }
.jump-chip{
  display:inline-flex; align-items:center; min-height:44px;
  padding:.35rem .75rem; border-radius:999px;
  background:rgba(255,255,255,.55); border:1px solid #d4cfc4;
  color:var(--ember); text-decoration:none;
  /* t-micro — the step the standard designates for chips. Was .78rem/12.48px. */
  font-size:var(--t-micro); line-height:1.2;
}
.jump-chip:hover{ background:#fff; border-color:var(--ember); }
.jump-chip-on{ background:#023047; color:#fffaf0; border-color:#023047; font-weight:600; }
/* ★ THE ONE PLACE --ember IS THE WRONG COLOUR, AND THE SWEEP FOUND IT BY MAKING IT
   WORSE (2026-09-28). ★ This border sits on NAVY, not on paper. The light-background
   accent measured 2.74:1 there before today and 1.94:1 after, both under the 3:1 that
   WCAG 1.4.11 asks of a non-text control — so the old value was already failing and the
   sweep simply made it visible. The brand amber is 7.93:1 on this navy, which is the
   case the comment further down this file was written for: "the brand orange is right
   here, and it is the wrong colour everywhere else." */
.jump-chip-on:hover{ background:#023047; color:#fffaf0; border-color:#ffb703; }
@media (pointer: coarse){ .jump-chip{ min-height:44px; } }

/* Kindred-return relationship reading on the Nest page — the "what does their
   return mean for me" paragraph, set apart from the scannable row list. */
.kin-return-read{
  margin:.25rem 0 .5rem 1.65rem; padding:.6rem .8rem;
  background:rgba(255,255,255,.5); border-left:3px solid #fb8500;
  border-radius:.4rem; color:#3a3a3a;
}
.kin-return-read p{ margin:0 0 .5rem; }
.kin-return-read p:last-child{ margin-bottom:0; }

/* ── COSMIC STRIP — the three breakpoints ───────────────────────────────────
   Measured before changing anything: at tablet 768 the strip was 116px tall for
   52px of content, because it inherits .paper_box's 2rem padding — right for a
   reading card, far too much for a one-line status band. On phones the right-hand
   rows ("Schumann 🟡", "Space Weather 🟡") wrapped to two lines each because the
   column was pinned to w-1/2, and the Moon stack ran to three.

   It is a STATUS STRIP at every size: one compact band, never a card. */
.cosmic-strip{ padding: .7rem 1rem; }
/* "The sky right now" caption — the Clause 1 note that these are transits, not the
   reader's chart. Deliberately quiet: it must inform without competing with the
   readings, and without costing the strip its tuned height. */
.cosmic-caption{
  color:#6b6153; line-height:1.25; margin-bottom:.3rem;
  letter-spacing:.01em;
}
.cosmic-caption strong{ color:#4b4436; font-weight:700; }
/* Wide variant: sits in the row, not above it, so it costs no vertical space. */
.cosmic-caption--inline{
  flex:0 1 auto; margin-bottom:0; margin-right:.75rem;
  max-width:14rem; border-right:1px solid #e0dccf; padding-right:.75rem;
}
@media (max-width: 767px){
  /* Tablets 640-767 use the wide variant but are too narrow to carry the caption
     inline as well as both columns — give it its own line back rather than let it
     squeeze the readings. */
  .cosmic-caption--inline{
    flex:1 0 100%; max-width:none; border-right:0; padding-right:0;
    margin-right:0; margin-bottom:.3rem;
  }
}

/* PHONE (<640px) — columns size to CONTENT, so the right rows fit on one line
   instead of being squeezed into a fixed half-width. */
@media (max-width: 639px){
  /* !important because the markup carries Tailwind's w-1/2 on both columns, which
     otherwise wins and forces the right-hand rows to wrap. */
  .cosmic-mobile .right-col{ width: auto !important; flex: 0 0 auto; margin-left: auto; }
  .cosmic-mobile > div > div:first-child{ width: auto !important; flex: 1 1 auto; min-width: 0; }
  /* On the scale at the micro step. Was .72rem (11.5px) — under the 12px floor,
     which is a Q74 finding: a status strip every page carries is still text a
     reader is meant to read. */
  .cosmic-strip .label{ font-size: var(--t-micro); }
  /* Moon: keep the phase name and its sign on ONE line when they fit, so the
     glossed dotted underline stops breaking mid-phrase (device review #6).
     Was .68rem (10.9px) — the smallest text on the homepage, and below the
     floor. Line-length is a layout problem; it does not get solved by shrinking. */
  .cosmic-mobile .moon-special{ font-size: var(--t-micro); }
}

/* TABLET (640–1023px) — the single-row desktop layout is the right shape here,
   but it needs tighter type and gaps than a 1280px monitor gets. Previously
   tablets were simply handed the desktop strip untuned. */
@media (min-width: 640px) and (max-width: 1023px){
  .cosmic-strip{ padding: .55rem .8rem; }
  .cosmic-desktop .cosmic-cols{ column-gap: 1rem; }
  .cosmic-strip .label{ font-size: var(--t-micro); }   /* was .72rem, under the floor */
}

/* LARGE SCREENS (≥1024px) — room to breathe, still a strip and not a card. */
@media (min-width: 1024px){
  .cosmic-strip{ padding: .8rem 1.25rem; }
}

/* Moon phase + its sign now share a line and wrap only when they must; the special
   ("✦ Buck Moon") still takes its own line because it is the notable bit. */
.cosmic-mobile .moon-stack{ row-gap: 0; }
.cosmic-mobile .moon-stack .moon-special{ flex: 1 0 100%; }

/* ─────────────────────────────────────────────────────────────────────────────
   COLLAPSIBLE CARDS  (.acct-card)
   Added 2026-08-08. A settings page is a list of categories, and a reader who
   came to change one thing should not have to scroll past six others.

   Built on native <details>/<summary> rather than a JS accordion, because the
   native element already: is keyboard-operable, announces its own expanded
   state to a screen reader, is found by the browser's in-page search even while
   closed, and still opens if JavaScript never loads. A hand-rolled div + aria
   version has to earn all four back, and usually earns three.

   The open/closed state is NOT carried by colour alone (WCAG 2.2 SC 1.4.1): the
   chevron rotates, and the native element exposes the state to assistive tech
   regardless of what the CSS does.
   ────────────────────────────────────────────────────────────────────────── */
.acct-card { padding: 0; }

.acct-card > summary {
  display: flex;
  align-items: flex-start;
  gap: 0.75rem;
  padding: 1.25rem 1.5rem;
  cursor: pointer;
  list-style: none;          /* Firefox */
  /* Comfortably past the 24x24 CSS px minimum for SC 2.5.8 Target Size. */
  min-height: 2.75rem;
}
.acct-card > summary::-webkit-details-marker { display: none; }  /* Safari */

.acct-card__title { flex: 1 1 auto; min-width: 0; }
.acct-card__title > :first-child { margin: 0; }

/* The chevron. ::after so it sits at the end regardless of title length. */
.acct-card > summary::after {
  content: "";
  flex: 0 0 auto;
  width: 0.55rem;
  height: 0.55rem;
  margin-top: 0.45rem;
  border-right: 2px solid #7a6f57;
  border-bottom: 2px solid #7a6f57;
  transform: rotate(45deg);          /* points down = "opens downward" */
  transition: transform 150ms ease;
}
.acct-card[open] > summary::after { transform: rotate(-135deg); }

@media (prefers-reduced-motion: reduce) {
  .acct-card > summary::after { transition: none; }
}

.acct-card > summary:hover { background: rgba(2, 48, 71, 0.035); }
.acct-card > summary:focus-visible {
  outline: 2px solid #023047;
  outline-offset: -2px;
}

.acct-card[open] > summary { border-bottom: 1px solid #e6dfca; }
.acct-card__body { padding: 1.25rem 1.5rem 1.5rem; }

/* The "open me to change something" nudge — only meaningful while closed. */
.acct-card[open] .acct-card__hint { display: none; }
.acct-card__hint { display: block; margin-top: 0.25rem; }

/* ── The subscription card's row of actions ──────────────────────────────────
   Manage Subscription / Update Payment Method / View Account History were three
   separately-sized inline-block buttons in three separate paragraphs, so each one was as
   wide as its own words. Three peer decisions of the same weight looked like a ragged
   list of unrelated things, and at the widths in between they stacked half-and-half.

   auto-fit + minmax is doing the work: every column in a row is 1fr, so the buttons are
   ALWAYS equal to each other — three across on a desktop, two, or one full-width column
   on a phone — without a single media query, and adding a fourth action later needs no
   change here. Grid stretch equalises their heights too, so a label that wraps to two
   lines doesn't leave its neighbours short.

   min-height 44px unconditionally, not just under (pointer: coarse) as .btn-primary does:
   it is the WCAG 2.2 target size, a laptop with a touchscreen reports a fine pointer, and
   it is also what keeps a one-line and a two-line button the same height. */
/* ── Two lists on one card ────────────────────────────────────────────────────
   The Wingmates and Murmurations card carries two sets of .wingmate-row boxes. With
   two sets on one card, an unlabelled second set reads as more of the first — so each
   gets a heading, and the second gets a rule above it so the eye finds the boundary
   before it starts reading the boxes. Hand-written rather than Tailwind, because a new
   utility class is inert until someone remembers to run the CSS build. */
.acct-list-heading {
  color: #023047;
  font-weight: 700;
  margin: 0 0 0.15rem;
}
.acct-list-heading--second {
  margin-top: 2rem;
  padding-top: 1.25rem;
  border-top: 1px solid #e6dfca;
}

/* "default" as a status pill, in the paper palette rather than the green/amber/red the
   other badges use — those three mean "how is the computation going", and this means
   something else entirely. Its MEANING is printed under the name; the pill is only the
   marker that lets you spot which one it is. */
.wingmate-badge--default { background: #fff8e1; color: #7a5c00; }

.murmuration-row__default {
  color: #6b6350;
  font-style: italic;
  margin-top: 0.25rem;
}

.acct-actions {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(13.5rem, 1fr));
  gap: 0.75rem;
  margin-top: 0.75rem;
}
.acct-actions .btn-primary {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-height: 44px;
  padding: 0.5rem 0.75rem;   /* narrower than the 1.25rem default: the grid sets the width
                                now, so the padding only has to keep the label off the edge */
  text-align: center;
}

/* The chosen plan on /subscribe. Was Tailwind's border-purple-400/bg-purple-50 — a colour
   that appears nowhere else on the site, so the one card we most want to look deliberate
   looked borrowed. Navy edge + the paper-yellow wash already used for emphasis site-wide. */
.brand-selected{
  border: 2px solid #023047;
  background: #fff8e1;
}

/* ── THE BILLING-FREQUENCY SELECTOR ON /subscribe (2026-10-23) ─────────────────────
   Three links, not radio buttons and not a <select>: the cadence lives in the URL, so the
   selector needs no JavaScript, and the price a reader reads is rendered from the same
   value the checkout form posts. Nothing on the page can show one price and buy another.

   ★ IT WRAPS RATHER THAN SCROLLS. ★ The chart pages' control rows were rebuilt the day
   before this for exactly that fault: a nowrap strip cuts its last control off on a phone,
   and the last control here is the yearly plan, which is the one we most want read. Each
   chip grows to fill its row, so three on one line and two over two lines both look
   deliberate rather than like a layout that ran out of room. */
/* ── The renewal disclosure and its tick box (counsel, 2026-10-23) ──────────────
   California wants this "clear and conspicuous" before the billing details are
   confirmed, so it is NOT 12px grey small print: it sits at --t-small on a tinted
   panel, above the button, inside the form it gates. The box itself is 20px with a
   44px touch target around it, because a consent nobody can tap on a phone is not
   one. Never style this smaller to make a card fit — the whole legal weight of the
   page is in these two elements. */
.renewal-disclosure{
  margin: .85rem 0 0; padding: .6rem .7rem;
  border: 1px solid rgba(2,48,71,.18); border-radius: 8px;
  background: #fff8e1; color: #3d3524;
  line-height: 1.45;
}
/* ★ TWO CONSENT BOXES, ONE TREATMENT (2026-10-23). ★ `.terms-consent` is the sign-up
   acceptance on /account and `.renewal-consent` is the renewal agreement at checkout.
   They are the same kind of control doing the same legal job, so they share these rules
   rather than growing two copies that drift — the lesson this codebase learned from one
   filter predicate with two callers.
   ★ THE ORDER OF THE TWO SELECTORS IS NOT ARBITRARY. ★ test_renewal_consent.py pins the
   `.renewal-consent input[type="checkbox"]...{` line verbatim, so it is written LAST in
   each group and still reads as a selector start. */
.terms-consent,
.billing-country,
.renewal-consent{
  display: flex; align-items: flex-start; gap: .6rem;
  margin: .7rem 0 0; padding: .45rem .15rem;
  min-height: 44px;
  font-size: var(--t-small); line-height: 1.45; color: #2f2a1f;
  cursor: pointer;
}
/* ★ THIS ONE BOX READS EMPTY WHEN IT IS EMPTY, AND THAT OVERRIDES THE HOUSE STYLE. ★
   The site's checkbox is a NAVY FILLED square with a pale star when unchecked and an
   orange star when checked — a deliberate, contrast-measured design (see the block
   further down) that differs by more than hue, so it passes WCAG 1.4.1 and it stays
   everywhere else. Do not "tidy" this override onto the rest of the site.

   It is wrong HERE for one reason that has nothing to do with accessibility. California
   requires an UNTICKED box, and the evidentiary value of the whole consent record rests
   on the box plainly being unticked. Screenshotted at 4x, the house unchecked state is a
   solid navy square with a star in it: a reader glancing at it reads "already agreed",
   and so would anyone looking at the page in a dispute. "Distinguishable from the checked
   state" is a lower bar than "obviously empty", and only this box has to clear the
   second one.

   So: unchecked is the paper ground inside a navy edge, with no star at all. Checked is
   the house state exactly — navy ground, orange star — so the tick still looks like every
   other tick on the site. Empty to full, not dim-star to bright-star. */
.terms-consent input[type="checkbox"],
.billing-country input[type="checkbox"],
.renewal-consent input[type="checkbox"]{
  flex: 0 0 auto; width: 24px; height: 24px; margin: .05rem 0 0;
  cursor: pointer;
}
.terms-consent input[type="checkbox"]:not(:checked),
.billing-country input[type="checkbox"]:not(:checked),
.renewal-consent input[type="checkbox"]:not(:checked){
  background-color: #fefae0;
  border: 2px solid #023047;
}
.terms-consent input[type="checkbox"]:not(:checked)::before,
.billing-country input[type="checkbox"]:not(:checked)::before,
.renewal-consent input[type="checkbox"]:not(:checked)::before{
  content: none;
}
/* ── A scrolling inset inside a consent card (owner, 2026-10-25) ─────────────
   /welcome/terms asks a returning reader to agree to a changed document, and the
   changelog for a reader who last agreed in September is 37 bullets. MEASURED at
   390x844 before this existed: the agree button sat 10,841px down, 12.8 screens.
   Owner: "I want the new terms they're agreeing to in a scrolling inset card within a
   card, not for the entire card to be the terms to scroll through."

   ★ IT SCROLLS, IT DOES NOT HIDE. ★ Every line stays in the document and in the
   accessibility tree — this is not a <details>, because a disclosure a screen reader
   has to open is a worse disclosure. The region is given tabindex="0" by its callers so
   a keyboard can scroll it, and max-height is in rem so it grows with the reader's own
   font size instead of clipping more of the text the larger she sets it. */
.consent-scroll{
  max-height: 13rem; overflow-y: auto;
  margin: .7rem 0 0; padding: .6rem .8rem;
  background: #fffdf6; border: 1px solid #e6dfcb; border-radius: 8px;
}
.consent-scroll:focus-visible{ outline: 2px solid #fb8500; outline-offset: 2px; }

/* ── The billing-address declaration (counsel, 2026-10-23; one box, owner 2026-10-25)
   It was a two-option select and is now a tick box, so it takes the treatment the two
   consent boxes above already share rather than a third look of its own — same 24px
   box, same 44px target, same plainly-empty unchecked state. It sits ABOVE the renewal
   consent, because whether we can sell to her at all comes before what she would be
   agreeing to, and the shared rules are written with .billing-country in the middle of
   each group so the .renewal-consent line test_renewal_consent.py pins stays last. */
.cadence-row{
  display: flex; flex-wrap: wrap; gap: 8px;
  margin: 0; padding: 0; list-style: none;
}
.cadence-chip{
  flex: 1 1 auto; min-width: 0;
  display: inline-flex; align-items: center; justify-content: center;
  min-height: 44px; padding: 8px 14px;
  border: 1px solid rgba(2,48,71,.28); border-radius: 999px;
  background: #fffaf0; color: #023047; text-decoration: none;
  font-size: var(--t-small); line-height: 1.2; text-align: center;
}
/* ★ A PRICED CHIP STACKS, AND ITS PRICE IS AT THE SCALE'S FLOOR, NOT BELOW IT. ★ The
   figure is dense data in the sense --t-micro exists for, and it keeps the full navy so
   it clears 4.5:1 on the chip's paper ground — a 12px label may not be dimmed. */
.cadence-chip-priced{ flex-direction: column; gap: 2px; padding: 6px 10px; }
.cadence-chip-price{ font-size: var(--t-micro); }
.cadence-chip:hover{ background: #fff3d6; }
.cadence-chip:focus-visible{ outline: 2px solid #fb8500; outline-offset: 2px; }
/* The chosen one wears the same navy edge and paper wash as the chosen plan below it,
   because they are one decision shown twice. */
.cadence-chip[aria-current]{
  border: 2px solid #023047; background: #fff8e1; font-weight: 600;
}

/* ══════════════════════════════════════════════════════════════════════════
   CHECKBOXES — navy boxes carrying the chart pages' star
   ══════════════════════════════════════════════════════════════════════════
   Owner, 2026-08-18: "can we update the checkboxes across the site to look
   like navy boxes with the same little stars from the chart pages either
   paper texture or orange depending on whether they're checked? There are so
   many checkboxes on /account and they just don't fit aesthetically."

   ✦ is the star the chart pages already use (.astro-glyph, .fancy-header),
   so this borrows a mark the reader has met rather than inventing one.

   ★ SCOPED BY ELEMENT, NOT BY CLASS, ON PURPOSE. ★ /account builds a good
   number of its checkboxes in JavaScript (see the `row()` helper and
   `cb.type = "checkbox"`), so a class-based rule would style the ones written
   in the template and quietly miss the ones the page draws for itself — which
   are most of the ones the owner is actually looking at. An element selector
   cannot be forgotten by a future builder.

   ★ THE BRAND ORANGE IS RIGHT HERE, AND IT IS THE WRONG COLOUR EVERYWHERE
   ELSE. ★ This file already records that #fb8500 is not a text colour: on our
   paper it measures 2.27:1 and the accessible substitute is --ember (#9a3800;
   it was #a95a00 until 2026-09-28). On NAVY that reverses. MEASURED: #fb8500 on
   #023047 is 5.58:1, while the light-background substitute on that same navy is
   under 3:1 and would FAIL what WCAG 1.4.11 asks of a
   non-text control. The substitute exists for light backgrounds; using it
   here out of habit would have made the checked state harder to see.

   Contrasts, measured: paper star on navy 13.17:1, orange star on navy
   5.58:1, navy box on paper 13.17:1.

   ★ AND THE TWO STATES DIFFER BY MORE THAN HUE. ★ WCAG 1.4.1 does not accept
   colour as the only carrier of meaning, and "orange star vs cream star" is
   exactly that. The unchecked star is also SMALLER and set back, so the
   difference survives greyscale, colour blindness and a dimmed screen.
   ══════════════════════════════════════════════════════════════════════════ */

input[type="checkbox"]{
  -webkit-appearance: none;
  appearance: none;
  /* 1.5rem = 24px, the floor this file already applies to coarse pointers.
     Applied at every width now: a checkbox is a target on a mouse too. */
  width: 1.5rem;
  height: 1.5rem;
  flex-shrink: 0;
  margin: 0;
  display: inline-grid;
  place-content: center;
  vertical-align: -0.3em;          /* sits on the text baseline when inline */
  border: 1px solid #023047;
  border-radius: 5px;
  background-color: #023047;
  cursor: pointer;
  /* Colour only — no transition on size, which would make a rapid tick feel
     laggy rather than responsive. */
  transition: background-color .12s ease, border-color .12s ease;
}

input[type="checkbox"]::before{
  content: "✦";
  /* Not a .t-* step: this is an icon inside a fixed 24px box, not prose, and
     the type scale governs text the reader reads. Sized to the box. */
  font-size: 0.95rem;
  line-height: 1;
  /* The star is decoration; the checkbox's own state is what carries meaning
     to assistive technology, and a pseudo-element is not announced anyway. */
  color: #fefae0;
  /* Unchecked: smaller and set back, so the state is legible without colour. */
  transform: scale(0.72);
  opacity: 0.55;
  transition: transform .12s ease, opacity .12s ease, color .12s ease;
}

/* The literal paper texture the owner asked for, clipped to the glyph. Wrapped
   in @supports because background-clip:text needs `color:transparent` to show,
   and a browser without it would render an invisible star. Everyone else keeps
   the solid paper colour above, which at 24px is what the texture looks like
   anyway. */
@supports ((-webkit-background-clip: text) or (background-clip: text)){
  input[type="checkbox"]:not(:checked):not(:indeterminate)::before{
    background-image: url("/static/images/paper.png");
    background-color: #fefae0;
    -webkit-background-clip: text;
    background-clip: text;
    color: transparent;
  }
}

input[type="checkbox"]:checked::before{
  color: #fb8500;
  background-image: none;
  -webkit-background-clip: border-box;
  background-clip: border-box;
  transform: scale(1);
  opacity: 1;
}

/* Some pages use the indeterminate state for "some of these are on". It reads
   as a middle state rather than a third colour: full-size star, paper-toned. */
input[type="checkbox"]:indeterminate::before{
  color: #fefae0;
  transform: scale(1);
  opacity: 0.8;
}

input[type="checkbox"]:hover:not(:disabled){ border-color: #fb8500; }

/* The native focus ring goes with appearance:none in several browsers, so it
   is put back explicitly. Orange at 2px, matching .calm-toggle and .star-btn
   so keyboard focus looks the same wherever it lands. */
input[type="checkbox"]:focus-visible{
  outline: 2px solid #fb8500;
  outline-offset: 2px;
}

input[type="checkbox"]:disabled{
  cursor: not-allowed;
  opacity: 0.45;
}

/* ★ WINDOWS HIGH CONTRAST TURNS OFF OUR COLOURS, NOT OUR LAYOUT. ★ A custom
   control painted with background-color vanishes there, leaving an empty
   square with no way to tell checked from unchecked. Handing the native
   control back is the one reliable answer: the OS draws a real checkbox in
   the user's own palette. */
@media (forced-colors: active){
  input[type="checkbox"]{
    -webkit-appearance: auto;
    appearance: auto;
    background-color: Field;
    border-color: CanvasText;
  }
  input[type="checkbox"]::before{ content: none; }
}

/* Calm mode already flattens the paper grain everywhere else; the star follows
   it rather than being the one textured thing left on a calmed page. */
html.calm input[type="checkbox"]:not(:checked)::before{
  background-image: none;
}

/* Radios are deliberately left native. The owner asked for checkboxes, and a
   round control that reads as a different KIND of choice — one of several,
   rather than on or off — is doing useful work by not matching. */

/* ── Two-tier table cells (canonical, 2026-08-20) ──────────────────────────
   A lead word at the row's own size, then the descriptive line beneath it at
   the 12px floor. Defined HERE, not in a page, because /chart/western and
   /chart/vedic and /chart/chinese all use it and a per-page copy is how two
   tables that are meant to match drift apart. 0.75rem is the absolute floor
   from the text-size standard: these sub-lines carry house and bhava
   meanings, sign traits, rulers and dignities, which a reader is very much
   meant to read. */
.tbl-sub { font-size: 0.75rem; color: #4b5563; line-height: 1.35; }


/* ── The demo strip ────────────────────────────────────────────────────────────────
   Shown on every page while browsing Aunt Starling's fictional profile. Deliberately
   loud and deliberately undismissable: the whole job is that nobody mistakes an invented
   woman's chart for a real reader's. sticky rather than fixed so it never covers content
   on a short screen, and it sits above the banner in the source so a screen reader meets
   it before anything else on the page. */
.demo-strip {
  position: sticky; top: 0; z-index: 60;
  display: flex; align-items: baseline; justify-content: center;
  flex-wrap: wrap; gap: 0.5em;
  background: #7a2d00; color: #ffe9c9;
  padding: 0.5rem 1rem;
  font-family: var(--font-sans, system-ui, sans-serif);
  font-size: var(--t-small);
  line-height: 1.45;
  text-align: center;
  border-bottom: 2px solid #ffb703;
}
.demo-strip strong { color: #fff; }
.demo-strip__tag {
  flex: 0 0 auto;
  background: #ffb703; color: #3a1500;
  font-weight: 800; letter-spacing: 0.12em;
  font-size: var(--t-micro);
  padding: 0.1rem 0.45rem; border-radius: 3px;
}
.demo-strip__cta { color: #ffb703; font-weight: 700; text-decoration: underline; white-space: nowrap;
                  display: inline-flex; align-items: center; min-height: 24px; } /* 2.5.8, 2026-09-16 */
.demo-strip__cta:hover { color: #fff; }
@media (max-width: 640px) { .demo-strip { text-align: left; } }


/* The demo's family-tier note. Quieter than the demo strip: that one is a warning about
   what this account IS, this one is information about what a page COSTS. */
.tier-note {
  display: flex; align-items: baseline; gap: 0.6em; flex-wrap: wrap;
  max-width: 62ch; margin: 0.9rem auto 0;
  background: rgba(2,48,71,0.06); border: 1px solid #8aa1ab; border-left-width: 4px;
  border-radius: 0.5rem; padding: 0.55rem 0.8rem;
  font-family: var(--font-sans, system-ui, sans-serif);
  font-size: var(--t-small); color: #23415a; line-height: 1.5;
}
.tier-note__tag {
  flex: 0 0 auto; background: #023047; color: #ffb703;
  font-size: var(--t-micro); font-weight: 700;
  padding: 0.12rem 0.5rem; border-radius: 999px; white-space: nowrap;
}

/* ★ The partner of a declination mark, lit on click (2026-08-23). ★ A parallel is a
   relationship in DECLINATION, not an angle round the zodiac, so it gets no chord across
   the wheel -- that would assert a longitude relationship the chart does not have, and
   would contradict the Aspects Table on the same page. Lighting the other glyph answers
   "which one?" and claims nothing further. */
/* ★ SPECIFICITY, NOT COLOUR, WAS THE BUG. ★ A bare `.glyph-partner-lit` (0,1,0) loses to
   `.western-wheel svg #planets text` (1,1,2) on fill and to `.western-wheel svg .astro-glyph`
   (0,2,1) on stroke — so the glyph kept its #111 and its stroke:none, and only font-weight
   and stroke-width (which nothing else claimed) got through. MEASURED in the browser: the
   class WAS applied to the right glyph and computed out as plain black text.
   This file already documents the same fight three rules up ("beat #planets text{fill:#111}"),
   which is the tell that this trap is structural here rather than a one-off. */
.western-wheel svg .astro-glyph.glyph-partner-lit,
.western-wheel svg #planets text.glyph-partner-lit,
.western-wheel svg #points text.glyph-partner-lit {
  fill: #b45309 !important;
  font-weight: 700;
  paint-order: stroke !important;
  stroke: #fde68a !important;
  stroke-width: 3px !important;
}

/* ★ A Top 9 item the current filter is hiding (2026-08-23). ★ The nine are the CHART's,
   ranked over every aspect, so they do not change when the reader narrows the view — which
   means a narrowing can hide several of them. Dimming says "still one of your nine, just
   not on screen"; clicking clears the narrowing and jumps there. Never removed from the
   list, because a list that shrinks under a filter was never the chart's top nine. */
.wmm-item-hidden { opacity: 0.55; }
.wmm-item-hidden .wmm-item-title::after {
  content: " (filtered out)";
  font-size: var(--t-micro);
  color: #6b7280;
  font-style: italic;
}

/* ★ The life-chapter strands (2026-08-23). ★ Three stacked rows, one per tradition, each
   segmented at ITS OWN period boundaries — Zodiacal Releasing L2, Vimshottari mahādaśā and
   Da Yun. Owner chose stacked over a tradition switcher: where the three agree is the
   whole point, and you cannot see agreement one row at a time. Bar HEIGHT carries that
   tradition's own strength reading, never a shared score wearing three hats. */
.strand-head { font-size: var(--t-micro); fill: #6b7280; letter-spacing: .03em; }
.strand-bar { fill: #c8a35a; fill-opacity: .55; stroke: #a9832f; stroke-width: .75; }
.strand-seg:hover .strand-bar,
.strand-seg:focus .strand-bar { fill-opacity: .85; }
.strand-seg.is-flagged .strand-bar { fill: #b45309; fill-opacity: .7; }
.strand-label { font-size: var(--t-micro); fill: #3f3a33; }
/* "Now" is the only thing the three strands genuinely share. */
.strand-now { stroke: #023047; stroke-width: 1.25; stroke-dasharray: 3 3; opacity: .75; }

/* ── THE ENERGY INDEX (Nest) ───────────────────────────────────────────────────
   Five strands drawn as separate rows, never summed. The colours are the only way
   a reader tells them apart at a glance, so they are distinct in HUE rather than
   in lightness — a lightness ramp collapses for anyone with a colour vision
   difference, and the row LABEL is always present beside the bar for the same
   reason. Nothing here is the only carrier of meaning.

   Every size is a --t-* token. No raw font-size (CLAUDE.md TEXT SIZE STANDARD).   */
.ei-card{background:#fffaf0;border:1px solid #d4cfc4;border-radius:14px;
         padding:1.1rem 1.15rem 1.25rem;margin:1.25rem 0}
.ei-head{margin-bottom:.85rem}
.ei-sub{color:#5c6b73;margin-top:.2rem}

/* The axis, in words. #5c6b73 on #fffaf0 is 5.31:1, so it clears 4.5:1 at --t-micro,
   where the large-text exemption does not apply (CLAUDE.md 8C). */
/* The use comes first and reads as prose, not as apparatus: --t-small and the body ink,
   where the axis note below it is --t-micro and grey. */
.ei-use{color:#3d4f57;margin:0 0 .45rem;line-height:1.55}
.ei-axis{color:#5c6b73;margin:0 0 .65rem;line-height:1.5}

/* The scale key. Four steps, each a miniature of the thing it describes. */
.ei-key{display:flex;align-items:center;gap:.9rem;flex-wrap:wrap;
        margin:.7rem 0 0;padding:.5rem .2rem 0;border-top:1px solid #ece7dc}
.ei-key-item{display:flex;align-items:center;gap:.35rem;color:#5c6b73}
.ei-key-box{position:relative;width:.85rem;height:1.5rem;flex:0 0 auto;
            background:rgba(2,48,71,.045);border-radius:3px}
.ei-key-bar{position:absolute;left:0;right:0;bottom:50%;background:#5c6b73;
            border-radius:2px 2px 0 0;opacity:.85}
.ei-key-bar.is-flat{opacity:.3;border-radius:1px}
.ei-key-mid{position:absolute;left:0;right:0;top:50%;height:1px;
            background:rgba(2,48,71,.18)}
.ei-key-note{color:#5c6b73;flex:1 1 14rem;min-width:0;line-height:1.45}
@media (max-width:640px){ .ei-key-note{flex-basis:100%} }

/* The one shared sentence a family of windows hands up, printed once under the group. */
.ei-wgroup-foot{margin:.5rem 0 0;color:#5c6b73;font-style:italic;line-height:1.45}
/* ★ THE CURSOR IS THE ONLY THING THAT ADVERTISES A TOOLTIP. ★ glossary.css gives the help
   cursor to [data-gloss] and .glossed but NOT to [data-tip], so a chart wired entirely with
   data-tip would keep every one of its explanations behind a hover nobody thinks to try.
   Scoped to this card rather than added globally: [data-tip] is used elsewhere on elements
   whose cursor already carries a different meaning. Buttons keep theirs -- .ei-name is
   genuinely clickable and saying "help" over it would understate what it does. */
.ei-card [data-tip]:not(button){cursor:help}
.ei-grid{display:flex;flex-direction:column;gap:.35rem}
.ei-row{display:flex;align-items:center;gap:.6rem}
.ei-row.is-dim{opacity:.55}
.ei-name{display:flex;align-items:center;gap:.4rem;flex:0 0 8.5rem;
         background:none;border:0;padding:.15rem .2rem;cursor:pointer;text-align:left;
         color:#023047;border-radius:6px}
.ei-name:hover{background:rgba(2,48,71,.06)}
.ei-name:focus-visible{outline:2px solid #219ebc;outline-offset:2px}
.ei-scope{color:#67767c;font-style:italic}
.ei-swatch{width:.62rem;height:.62rem;border-radius:3px;flex:0 0 auto}

/* ★ 2.6rem WAS TOO SHORT TO SHOW THE SHAPE THE DATA HAS. ★ Half a track is the whole
   range in one direction, so at 2.6rem a bar had 20.8px to work with and a change of 0.2 in
   value moved it FOUR PIXELS. MEASURED the day the Moon curve landed: the Sky row went from
   2 distinct heights to 31, and still read as a solid slab. The numbers were right and the
   picture was not showing them. 3.4rem gives 27px a side — a third more resolution on every
   row, the Chinese cells and Vedic blocks included. */
.ei-track{position:relative;flex:1 1 auto;height:3.4rem;background:rgba(2,48,71,.045);
          border-radius:7px;overflow:hidden}
/* Positive grows UP from the midline, negative DOWN. Magnitude alone told a reader
   nothing about direction, which is most of what a strand is for. */
.ei-bar{position:absolute;bottom:50%;border-radius:2px 2px 0 0;opacity:.9}
.ei-bar.is-neg{bottom:auto;top:50%;border-radius:0 0 2px 2px;opacity:.75}
.ei-bar.is-flat{opacity:.22;border-radius:1px}
/* ★ DECORATION MUST NOT EAT THE POINTER. ★ Both of these are drawn AFTER the blocks, so
   they sit on top of them. The midline is 1px and cost a hover only along the exact centre
   -- but a cell is full-height, so its centre point IS the midline, and that is where a
   pointer aiming at a cell most naturally lands. `.ei-now` is worse: 2px wide and the full
   height of the track, so it swallowed the readout for whatever is happening right now,
   which is the one column a reader is most likely to point at. Found by a browser refusing
   to hover; nothing about the rendered HTML says which element is on top. */
.ei-mid{position:absolute;left:0;right:0;top:50%;height:1px;background:rgba(2,48,71,.16);
        pointer-events:none}

/* ── BAND ROWS ────────────────────────────────────────────────────────────────
   The Vedic and Chinese rows are step functions -- three rectangular dips and twelve
   two-hour steps -- and were drawn as 48 sampled bars, which claimed a resolution neither
   has. A cell is ONE real thing: a kaala window to the minute, or one 時辰. The GRAMMAR is
   unchanged (up is lift, down is drag, same midline), so all five rows still read alike.
   The faint full-height backdrop is what makes an hour with no strong tie visible at all --
   its value block is a hairline, and twelve of those would look like an empty row. */
.ei-cell{position:absolute;top:0;bottom:0;border-right:1px solid rgba(2,48,71,.09)}
.ei-cell:last-of-type{border-right:0}
.ei-c-vedic{background:rgba(224,122,95,.10)}
.ei-c-chinese{background:rgba(155,106,155,.10)}
.ei-cell-v{position:absolute;left:0;right:0;bottom:50%;border-radius:2px 2px 0 0;opacity:.9}
.ei-cell-v.is-neg{bottom:auto;top:50%;border-radius:0 0 2px 2px;opacity:.8}
.ei-cell-v.is-flat{opacity:.32;border-radius:1px}
/* The label takes the half the value block left free -- bottom under a lift, top over a
   drag -- so they cannot collide however deep the value runs. */
.ei-cell-label{position:absolute;left:0;right:0;bottom:2px;text-align:center;
               color:#3d4f57;pointer-events:none;line-height:1}
.ei-cell.is-neg .ei-cell-label{bottom:auto;top:2px}

/* ── WINDOW UNDERLAYS ON CURVE ROWS ───────────────────────────────────────────
   Behind the bars and taking no pointer: on a curve the useful hover is "what hour is
   this", and the window's own name is one hover away in the list. */
/* ★ A BORDER, SO IT READS AS A MARKED SPAN AND NOT AS A SMUDGE. ★ Owner, on the Sky row:
   "the sky has a light blue spot near a dip and it's still not clear what that means." A
   12px wash of tint is a spot; a bracketed region with its name beside it is a window. */
.ei-under{position:absolute;top:0;bottom:0;border-radius:4px;pointer-events:none;
          border:1px solid rgba(2,48,71,.22)}
.ei-u-sky{background:rgba(33,158,188,.16)}
.ei-u-field{background:rgba(142,202,230,.20)}
.ei-u-weather{background:rgba(92,148,110,.13)}
/* ★ THE LABEL IS THE UNDERLAY'S ONLY HANDLE. ★ The block itself is pointer-events:none so
   the bars above it own the hover; the chip is raised out of that, so the one window wide
   enough to be named can also be asked what it is. */
/* ★ pointer-events:auto, EXPLICITLY. ★ .ei-under is pointer-events:none and that INHERITS,
   so simply deleting the property from this rule left the chip exactly as unreachable as
   before -- the track kept swallowing the hover and the one window wide enough to be named
   could not be asked what it was. It has to opt back IN. Nothing in the rendered HTML shows
   this; a browser refusing to hover is what found it. */
/* Vertical placement is COMPUTED, not chosen: bars occupy one half of the track and the
   label takes the other. See energy_index's label_v -- a chip over the bars covers them,
   and a covered bar cannot be pointed at. */
.ei-under-label{position:absolute;z-index:2;padding:0 .25rem;white-space:nowrap;
                border-radius:4px;background:rgba(255,250,240,.88);color:#3d4f57;
                line-height:1.35;pointer-events:auto}
.ei-under-label.at-top{top:.15rem}
.ei-under-label.at-bottom{bottom:.15rem}
.ei-under-label.is-in{left:.3rem}
/* Beside the block when the block cannot hold the name. energy_index._band picks the side,
   because the only thing that knows how much track is left is the position it just
   computed. */
.ei-under-label.is-right{left:100%;margin-left:.3rem;
                         box-shadow:-.35rem 0 0 -.28rem rgba(2,48,71,.35)}
.ei-under-label.is-left{right:100%;margin-right:.3rem;
                        box-shadow:.35rem 0 0 -.28rem rgba(2,48,71,.35)}

/* Both ends of one hover: the list row and its block on the graph. */
.ei-cell.is-lit,.ei-under.is-lit{outline:2px solid #023047;outline-offset:-2px}
.ei-cell.is-lit{background:rgba(2,48,71,.12)}
.ei-window.is-lit{background:rgba(2,48,71,.055);border-radius:7px;
                  box-shadow:0 0 0 .4rem rgba(2,48,71,.055)}
.ei-now{position:absolute;top:0;bottom:0;width:2px;background:#023047;opacity:.55;
        pointer-events:none}
.ei-unavailable{position:absolute;left:.6rem;top:50%;transform:translateY(-50%);
                color:#8a97a0;font-style:italic}
/* Top-left, not centred: a flat strand's bars ARE the centre line, and a note sitting on
   them is a note sitting on the thing it is about. */
.ei-flatnote{position:absolute;left:.35rem;top:.15rem;z-index:2;padding:0 .25rem;
             border-radius:4px;background:rgba(255,250,240,.82);color:#5c6b73;
             font-style:italic;line-height:1.35}

/* Distinct hues, one per strand. */
.ei-sw-sky,.ei-b-sky{background:#219ebc}
.ei-sw-field,.ei-b-field{background:#8ecae6}
.ei-sw-weather,.ei-b-weather{background:#5c946e}
.ei-sw-vedic,.ei-b-vedic{background:#e07a5f}
.ei-sw-chinese,.ei-b-chinese{background:#9b6a9b}

.ei-ruler .ei-track{height:1.1rem;background:none}
.ei-tick{position:absolute;top:0;color:#6b757c;font-size:var(--t-micro);
         transform:translateX(-50%)}
/* 00 sits at left:0, so centring it hangs half the label outside a track that clips
   (.ei-track is overflow:hidden). Left-align that one instead: it then starts exactly
   where the day does, which is where it belongs anyway. */
.ei-tick:first-child{transform:none}

.ei-detail{margin:.15rem 0 .55rem 9.1rem;padding:.6rem .75rem;background:#fff;
           border:1px solid #e4dfd4;border-radius:9px}
.ei-scope-note{color:#5c6b73;margin-top:.3rem}
.ei-res{color:#7a8b93;margin-top:.3rem;font-style:italic}
.ei-contrib{margin:.5rem 0 0;padding-left:1.05rem;list-style:disc}
.ei-contrib li{margin:.22rem 0;color:#3d4f57}

.ei-windows{margin-top:1.1rem;border-top:1px solid #e4dfd4;padding-top:.85rem}
.ei-wgroup{margin-top:.85rem}
.ei-wgroup:first-of-type{margin-top:.5rem}
.ei-wgroup-title{margin:0;color:#023047}
.ei-wgroup-note{margin:.15rem 0 0;color:#5c6b73;line-height:1.45}
.ei-windows ul{margin:.5rem 0 0;padding:0;list-style:none}
.ei-window{padding:.5rem 0;border-bottom:1px dashed #e4dfd4}
.ei-window:last-child{border-bottom:0}
.ei-w-top{display:flex;align-items:center;gap:.45rem;flex-wrap:wrap}
.ei-span{color:#5c6b73;font-variant-numeric:tabular-nums}
.ei-mins{color:#6b757c}
.ei-why{color:#3d4f57;margin-top:.25rem}
.ei-missing{color:#8a97a0;margin-top:.8rem;font-style:italic}

/* Phone: the label column is what has to give, not the text size — never shrink
   text to make a layout fit (CLAUDE.md). The name moves above its own track. */
@media (max-width:640px){
  .ei-row{flex-direction:column;align-items:stretch;gap:.2rem}
  .ei-name{flex:0 0 auto}
  .ei-detail{margin-left:0}
  /* The ruler STAYS. It was display:none here, which left a stacked phone layout with no
     time axis at all -- on a card whose whole argument is WHEN. Eight labels do not fit at
     this width; four do, so the minor ones drop and 00 / 06 / 12 / 18 remain. The empty
     label cell goes, since stacking would otherwise give it a row of its own. */
  .ei-ruler .ei-name{display:none}
  .ei-tick-minor{display:none}
}
.ei-nextday{color:#5c6b73;font-style:italic}

/* ── "What today is touching in your chart" (Nest) ────────────────────────────
   The structural half of the day: which parts of the reader's own chart today
   reaches. Every size is a --t-* token; nothing below --t-micro.               */
.ts-card .ts-list{list-style:none;margin:.6rem 0 0;padding:0}
.ts-house{padding:.55rem 0;border-bottom:1px dashed #e4dfd4}
.ts-house:last-child{border-bottom:0}
.ts-because{color:#023047;font-weight:600}
.ts-line{color:#3d4f57;margin-top:.15rem}
.ts-block{margin-top:1rem;border-top:1px solid #e4dfd4;padding-top:.8rem}
.ts-top9{padding:.5rem 0;border-bottom:1px dashed #e4dfd4}
.ts-top9:last-child{border-bottom:0}
.ts-asp{display:flex;align-items:baseline;gap:.35rem;flex-wrap:wrap;color:#023047}
.ts-rank{font-size:var(--t-micro);background:#023047;color:#fffaf0;border-radius:5px;
         padding:.05rem .3rem;font-variant-numeric:tabular-nums}
.ts-verb{color:#5c6b73}
.ts-via{color:#69737a;margin-left:.15rem}
.ts-gist{color:#3d4f57;margin:.2rem 0 0}
.ts-clock{display:flex;gap:.5rem;align-items:baseline;padding:.4rem 0;
          border-bottom:1px dashed #e4dfd4}
.ts-clock:last-child{border-bottom:0}
.ts-trad{flex:0 0 4.2rem;color:#5c6b73;font-style:italic}
.ts-c-western .ts-trad{color:#219ebc}
.ts-c-vedic .ts-trad{color:#e07a5f}
.ts-c-chinese .ts-trad{color:#9b6a9b}
@media (max-width:640px){ .ts-clock{flex-direction:column;gap:.1rem} .ts-trad{flex:0 0 auto} }

/* Today's own verdict, under the dateline. Stars never travel alone — the band
   word rides with them (WCAG 2.2 SC 1.4.1), and stars_label carries the spoken
   form for a screen reader. */
.today-verdict{display:block;margin-top:.15rem;color:#3d4f57;font-size:var(--t-small)}
.today-stars{color:#956b24;letter-spacing:.06em}
.today-band{font-weight:600;color:#023047}
.ts-prose{margin-top:.5rem}
.ts-divider{display:flex;align-items:center;gap:.6rem;margin:.9rem 0 .2rem;color:#69737a}
.ts-divider::before,.ts-divider::after{content:"";flex:1 1 auto;height:1px;background:#e4dfd4}
.ts-divider span{font-style:italic;white-space:nowrap}
.ts-sky{color:#3d4f57;margin:.5rem 0 0}
.ts-void{color:#6b6350;margin:.35rem 0 0;font-style:italic}
.ts-phaserel{color:#3d4f57;margin:.35rem 0 0}
.ts-rel-same{color:#023047;font-weight:600}
.ts-dayruler{color:#3d4f57}

/* The two ruling planets, side by side. Two equal columns because the reading is the
   RELATION between them — sizing one larger would answer the question before the prose
   does. Stacks on a phone; the ampersand keeps its place as a row divider so the pair
   still reads as a pair rather than as two unrelated blocks. */
.ts-pair{display:flex;align-items:stretch;gap:.9rem;margin-top:.5rem}
.ts-pair-side{flex:1 1 0;min-width:0}
.ts-pair-join{flex:0 0 auto;align-self:center;color:#8a97a0;font-size:var(--t-h2)}
.ts-pair-same{color:#956b24;font-weight:700}
.ts-pair-label{color:#69737a;font-style:italic}
.ts-pair-name{display:flex;align-items:baseline;gap:.3rem;color:#023047;
              font-size:var(--t-h3);margin-top:.1rem}
.ts-pair-is{color:#3d4f57;margin:.25rem 0 0}
.ts-pair-notes{margin-top:.7rem}
.ts-pair-notes li{padding:.35rem 0;border-bottom:1px dashed #e4dfd4;color:#3d4f57}
.ts-pair-notes li:last-child{border-bottom:0}
.ts-live{background:#c08a2e;color:#fffaf0;border-radius:5px;padding:.05rem .35rem;
         font-size:var(--t-micro);margin-right:.35rem;white-space:nowrap}
@media (max-width:640px){
  .ts-pair{flex-direction:column;gap:.5rem}
  .ts-pair-join{align-self:flex-start}
}

/* A transit row. Same skeleton as .ts-top9 so the two lists read as one family. */
.ts-tr{padding:.5rem 0;border-bottom:1px dashed #e4dfd4}
.ts-tr:last-child{border-bottom:0}
.ts-note{color:#69737a;margin:.1rem 0 .4rem}
/* Building and easing are opposite facts and must not look alike. Colour alone would
   fail WCAG 1.4.1, so each keeps its own WORD — the tint only reinforces it. */
.ts-motion{border-radius:5px;padding:.05rem .3rem;white-space:nowrap}
.ts-motion-building{background:#e8f2f6;color:#1c6b8c}
.ts-motion-easing{background:#f1efe8;color:#6b6350}
.ts-echo{color:#023047;font-weight:600}
.ts-fact{display:flex;gap:.6rem;align-items:baseline;padding:.4rem 0;border-bottom:1px dashed #e4dfd4}
.ts-fact:last-child{border-bottom:0}
.ts-tag{flex:0 0 7.5rem;color:#69737a;font-style:italic}
@media (max-width:640px){ .ts-fact{flex-direction:column;gap:.1rem} .ts-tag{flex:0 0 auto} }
/* ★ ONE SHAPE FOR ALL THREE TRADITIONS (2026-09-12). ★ Each tradition on the Touching
   card is a heading, one line on how it measures, and its techniques as sub-sections at
   one depth: Western's transit lists in exactly the form of Jyotish's dasha and BaZi's
   hidden stems. A tagged row under a heading is a demotion when the tradition beside it
   gets a heading per technique. See partials/_today_structure.html. */
.ts-sub{padding:.55rem 0;border-bottom:1px dashed #e4dfd4}
.ts-sub:last-child{border-bottom:0}
.ts-subhead{font-size:var(--t-small);font-weight:600;color:#023047;margin:0 0 .2rem}
.ts-signpost{padding:.55rem 0 0;color:#3d4f57}
/* ★ THE CARD'S GREYS, COMPUTED AGAINST ITS OWN PAPER (2026-09-12). ★ The Touching card
   sits on .paper_box_wide, #fefae0 — not the #fffaf0 CLAUDE.md's /nest fix measured
   against. #8a97a0 there is 2.85:1, and even that fix's #6b757c is 4.48:1: a hair UNDER
   the 4.5:1 12px text needs. Walked along #8a97a0's own hue toward black until it
   cleared with margin for the paper texture: #69737a, 4.61 on #fefae0, 4.66 on #fffaf0,
   4.85 on white. Applied to every small grey on this card; .ts-pair-join keeps its
   colour because it is an aria-hidden glyph, not text. */
.ts-signpost-label{color:#69737a;font-style:italic;margin-right:.35rem}

/* --- Eclipse badge: the Moon as it actually is, replacing the phase emoji --- */
/* ★ SAME BOX AS .moon-badge SO THE STRIP HEIGHT CANNOT MOVE. ★ The tuned heights
   (390 68px / 768 70px / 1440 78px) are recorded in cosmic_strip.html and were paid
   for once already — a swap that changed the badge's box would regress them. */
.moon-badge--eclipse{ padding:0; overflow:hidden; }
.eclipse-svg{ width:100%; height:100%; display:block; }
.eclipse-moon{ fill:#f4e7cd; }
.eclipse-umbra{ fill:#0a1626; fill-opacity:.88; }
.eclipse-rim{ fill:none; stroke:rgba(255,255,255,.45); stroke-width:1.1; }
/* Maria: faint on purpose. At 20px they should suggest a face, not read as dirt. */
.eclipse-maria ellipse{ fill:#c3b291; fill-opacity:.55; }
/* The occulting Moon on the solar badge — opaque, it is the Moon's night side. */
.sun-badge--eclipse{ padding:0; overflow:hidden; }
.solar-moon{ fill:#1a1a1f; }

/* ── Body cards (/nest): one small tile per body, sorted by importance ──────────
   Replaces the first full-width accordion, which was too big for one of thirteen
   tiles. The face is emoji + name + phase + score; everything else is in the popup. */
/* ★ ONE HEIGHT FOR EVERY TILE, AND NOT BY PICKING ONE (owner, 2026-09-02: "standardize
   the body card sizes"). ★ MEASURED before the change, identical at 390, 768 and 1440:
   THREE heights in one row — ten bodies at 60px, the Moon at 78, and the two Nodes at
   84 — from two different causes. The Moon's sub-line wrapped ("Waning Gibbous" over
   two lines); the Nodes' NAME wrapped, because "North Node" does not fit the 1fr
   column beside the emoji and the stars. Two causes, one ragged row.

   A flex row cannot fix this: flex equalises items within a LINE, so every wrapped
   line got its own height and the row read as three sizes. A grid can, and
   `grid-auto-rows:1fr` is the whole fix — with no height on the container, 1fr on the
   implicit rows resolves to the tallest row's content, so every tile on the page is
   that height. No magic number is written down anywhere, which is the point: when a
   body turns up with a longer phase or a longer name, they all grow together instead
   of one tile sticking out and someone re-measuring a pixel constant.

   `repeat(auto-fill, 11.5rem)` keeps the tile at exactly the width the owner settled
   on rather than letting the columns stretch — this change is about height only. */
.body-score-row{ display:grid; grid-template-columns:repeat(auto-fill, 11.5rem);
  grid-auto-rows:1fr; gap:.6rem; justify-content:start; }
.body-card{ position:relative; }
/* ★ THE ROW BREAK INSIDE THE TILE GRID. ★ Spanning every column is what ends the
   current row, so the day-ruler tile after it starts a fresh one — while staying in
   the SAME grid, which is the only way it can inherit the height the body tiles
   settled on. A second grid cannot: grid-auto-rows:1fr equalises within one grid,
   so a grid of one tile equalises it against itself and it came out 24px short.

   `grid-row: auto / auto` with its own content height would still be stretched by
   grid-auto-rows:1fr, so the label is pulled OUT of that sizing by giving its row a
   fixed height and letting the text sit inside it. */
.bsr-break{ grid-column:1 / -1; height:1.4rem; margin:.45rem 0 0; align-self:end;
  color:#6b6350; font-weight:600; }
/* Only the FIRST tile after the break is pinned to column 1. A second tile carrying
   this class was pushed onto a row of its own — two cards meant to sit side by side,
   ninety pixels apart vertically, because both demanded the same column. The tiles
   after it auto-place beside it, which is what the break already guarantees. */
.body-card--day{ grid-column:1; }
/* ★ THE SMALL CARDS ARE PAPER TOO (owner, 2026-08-31: "keep the paper texture on all of
   the cards, with the shadow"). ★ Same texture and the same warm ground as .paper_box, at
   a scale that suits a tile: the big boxes carry a 33px drop shadow, which on a 184px tile
   reads as a smudge rather than a lift, so the SHADOW is scaled down while the SURFACE is
   identical. One background declaration, reused by every small card below. */
/* ★ THREE ROWS, NOT TWO (owner, 2026-09-07). ★ "Move the star rating down to the 3rd row
   because they're bleeding off the card in many cases. We can also scoot the numeric score
   over to be closer to the sign so that everything fits better."

   THE CAUSE, measured rather than guessed: the stars lived in COLUMN 3 of row 1, beside
   the name. Column 3 is `auto`, so it takes whatever "★★★" needs (~30px at --t-body) and
   column 2 — the name — gets 1fr of what is left of 184px after a 25px glyph and the gaps.
   "Lot of Necessity" and "Lot of Exaltation" have nowhere to go, so they push the stars
   past the edge. Nothing in a grid clips by default; a track that cannot shrink simply
   overflows its parent, silently and only for the longest names.

   Moving the stars to their own row hands the WHOLE remaining width back to the name, and
   the number joins the sign on row 2, where a reader is already looking for "what is this
   body doing" rather than scanning a third column.

       row 1   glyph │ name .......................
       row 2         │ in Cancer .............. 64
       row 3         │ ★★☆

   The glyph spans all three rows so it stays optically centred against the block. */
.bc-face{ display:grid; grid-template-columns:auto 1fr auto; grid-template-rows:auto auto auto;
  align-items:center; align-content:center; gap:0 .38rem; width:100%; height:100%;
  padding:.5rem .5rem;
  background:#fefae0 url("/static/images/paper.png") repeat;
  border:1px solid rgba(0,0,0,.12); border-radius:.6rem;
  box-shadow:0 3px 8px rgba(0,0,0,.16), 0 0 6px rgba(255,183,3,.14);
  cursor:pointer; font:inherit; color:inherit; text-align:left; }
.bc-face:hover{ box-shadow:0 5px 14px rgba(0,0,0,.20), 0 0 8px rgba(255,183,3,.20); }
/* ★ THE CHART RULER, SHINING AT TODAY'S BRIGHTNESS (owner, 2026-09-03). ★ The planet
   ruling the Rising sign steers the whole chart, so its tile is marked every day — and
   the STRENGTH of the mark is the day's own star count, so the halo and the rating beside
   it can never tell different stories. One star is a ring you notice; three is lit.

   ★ COLOUR IS NEVER THE ONLY CARRIER (WCAG 2.2 SC 1.4.1). ★ The crown glyph beside the
   name carries the same fact for a reader who cannot see the halo, and it is a SHAPE
   rather than a hue, so it survives greyscale, high-contrast mode and a colour-blind
   reader. The glow is the pleasure; the crown is the information.

   Built on the same warm gold (255,183,3) the rest of this card already uses for its lift,
   so a lit tile reads as more of what the row is, not as a different design. */
.bc-face--ruler{ border-color:rgba(169,90,0,.42); }
.bc-face--ruler.bc-shine-1{ box-shadow:0 3px 8px rgba(0,0,0,.16),
  0 0 0 1px rgba(255,183,3,.28); }
.bc-face--ruler.bc-shine-2{ box-shadow:0 3px 9px rgba(0,0,0,.17),
  0 0 0 2px rgba(255,183,3,.40), 0 0 12px rgba(255,183,3,.26); }
.bc-face--ruler.bc-shine-3{ box-shadow:0 4px 11px rgba(0,0,0,.18),
  0 0 0 2px rgba(255,183,3,.62), 0 0 20px rgba(255,183,3,.44); }
.bc-face--ruler:hover{ box-shadow:0 5px 14px rgba(0,0,0,.20), 0 0 0 2px rgba(255,183,3,.70),
  0 0 22px rgba(255,183,3,.50); }
/* The crown sits with the name like the retrograde mark, at the same --t-micro floor and
   in the same brown, so the two marks read as one family rather than two decisions. */
.bc-crown{ font-size:var(--t-micro); color:var(--ember); margin-left:.2em; }
/* ★ A READER WHO ASKED FOR LESS MOTION AND LESS FLASH GETS THE RING, NOT THE BLOOM. ★
   The outer glow is atmosphere; the ring is the fact. Dropping the bloom keeps the tile
   identifiable without the halo that a reader with vestibular or photosensitivity
   settings has explicitly asked us not to paint. */
@media (prefers-reduced-motion: reduce) {
  .bc-face--ruler.bc-shine-2, .bc-face--ruler.bc-shine-3 {
    box-shadow:0 3px 8px rgba(0,0,0,.16), 0 0 0 2px rgba(255,183,3,.45); }
}
/* ★ OFF-SCALE ON PURPOSE, AND THIS IS THE EXCEPTION NOT A MISS. ★ The seven-step scale is
   for TEXT a reader reads; this is a decorative glyph (aria-hidden — the body's name is
   beside it in words) sized to fill a tile's left column. No step fits 25.6px, and forcing
   --t-h2 or --t-display would resize the tile around a symbol rather than around its text.
   Recorded here so a future type audit sees a decision instead of a stray value. */
/* ★ ONE WIDTH FOR THE GLYPH COLUMN, WHATEVER IS IN IT. ★ A planet glyph measures
   about 23px and a moon-phase EMOJI about 32 — so a tile carrying an emoji quietly
   took 9px off its own text column, which is where "Waxing Gibbous" ran out of room
   and wrapped, and grid-auto-rows:1fr then grew all sixteen tiles by 18px. Fixing
   the column makes the text width the same on every tile instead of a function of
   which symbol it happens to hold. */
.bc-emoji{ grid-row:1 / span 3; font-size:1.6em; line-height:1; width:1.55rem;
  text-align:center; }
/* ★ A NAME IN A GRID OF THIRTEEN TILES IS A LABEL, NOT PROSE (owner, 2026-09-07: "all of
   the cards on bit by bit have font that's too big"). ★ It inherited t-body — 16px, on 89
   nodes, MEASURED — which is the step for sentences somebody reads, not for a word in a
   chip. --t-small is the scale's own answer for "captions, meta, and labels in a dense
   list": a step DOWN the scale rather than a number picked to make things fit. Everything
   else on this tile was already on the scale. */
/* ★ THE NAME NOW OWNS THE WHOLE REMAINING WIDTH. ★ It shared row 1 with the stars and
   was the track that gave way; with the stars on row 3 there is nothing beside it to
   squeeze it, which is what stops the overflow at its cause rather than clipping it. */
.bc-name{ grid-row:1; grid-column:2 / -1; font-size: var(--t-small); font-weight:700; }
.bc-sub{ grid-row:2; grid-column:2; color:#6b6350; min-width:0; }
/* ★ SPANNING .bc-sub WORKS ONLY WHERE ROW 2 OF COLUMN 3 IS EMPTY. ★ Recorded because it
   was got wrong twice before it was got right. (Updated 2026-09-07: the stars moved to
   row 3 and span to the edge; the rest of this still holds, because the numeric SCORE is
   what occupies row 2 of column 3.) The numeric SCORE sits in row 2 of column 3 — so on
   an ordinary tile that cell is
   OCCUPIED, a sub told to span 2/-1 cannot be placed there, and grid auto-placement drops
   it into a THIRD row: 48 name + 18 score + 18 sub instead of 66. That is what took the
   whole row from 84px to 102 globally on 2026-09-02, and again scoped to one tile on
   2026-09-03.

   The natal-phase tile does not RENDER the numeric score, which is what makes the span
   legal there: the cell is genuinely free, the sub gets ~134px instead of 86, "Waxing
   Gibbous" fits on one line, and the row holds at 84 even though "Natal Moon" itself
   wraps. Use this class only on a face that omits .bc-score-sm. */
.bc-face--wide-sub .bc-sub{ grid-column:2 / -1; }

/* ★ ON A PHONE THE TILE BECOMES A STRIP (owner, 2026-09-03: "we really need to optimize
   for phone users... a less busy screen that you can easily identify what's going on and
   drill down for more information only if you want to"). ★

   MEASURED before this rule, at 390px: the tile grid is 11.5rem wide and the column it
   sits in is 310px, so exactly ONE tile fits per line. Twenty-three tiles became
   twenty-three lines and 2,237px — 2.7 screensful of nothing but tiles, on the surface the
   owner says most readers will use. Desktop got three per line and one screen.

   A full-width strip is the phone-native answer and it is not a compromise: the face
   already holds emoji, name, value and stars, and given the whole width those four sit on
   ONE line with room to spare — no wrapping, nothing truncated, and roughly half the
   height. The card is identical when opened; only the closed state changes.

   ★ THE TWO-ROW GRID COLLAPSES TO ONE, WHICH IS WHY THE HEIGHT HALVES. ★ The sub-line moves
   from under the name to beside it and pushes right, so the value a reader is scanning for
   lands in a consistent place down the column — which is what makes a list scannable in a
   way a grid of cards is not. */
@media (max-width: 639px) {
  .body-score-row{ grid-template-columns:1fr; gap:.4rem; }
  .bc-face{ grid-template-columns:auto auto 1fr auto; grid-template-rows:auto;
    align-items:center; padding:.5rem .65rem; gap:0 .5rem; }
  .bc-emoji{ grid-row:1; grid-column:1; font-size:1.35em; }
  .bc-name{ grid-row:1; grid-column:2; white-space:nowrap; }
  /* Right-aligned so the values line up down the page. --wide-sub is a no-op here: with
     the full width there is nothing to span. */
  .bc-sub, .bc-face--wide-sub .bc-sub{ grid-row:1; grid-column:3; text-align:right;
    padding-left:.5rem; }
  .bc-stars{ grid-row:1; grid-column:4; text-align:right; }
  /* The numeric score is the one thing that does not survive the trim. It is secondary to
     the stars everywhere it appears, and it is in every popup that shows it. */
  .bc-score-sm{ display:none; }
  /* ★ AND THE BIG FALLBACK NUMBER HAS TO BE PUT BACK ON ROW 1 TOO. ★ The desktop rule
     spans it down the face (1 / span 3), and this strip declares a SINGLE explicit row —
     so a span would generate two implicit rows and quietly make the no-stars tiles taller
     than every other tile in the list. It sits in the stars' column, which is free on
     exactly the tiles that render this instead of stars.

     This was already half-wrong before the three-row change (the old span was 1 / span 2,
     which generated one implicit row); widening the span is what would have made it
     visible. Found by asking which desktop rules the phone block does NOT override —
     .bc-score-sm was overridden and .bc-score never had been. */
  .bc-score{ grid-row:1; grid-column:4; }
  /* The break label stops being a row-breaker (there is only one column) and becomes a
     plain heading again. */
  .bsr-break{ grid-column:1; height:auto; margin:.5rem 0 .1rem; }
}
/* ★ THE RETROGRADE MARK. ★ ℞ is the symbol every ephemeris and every chart uses, so it is
   the one a reader will meet everywhere else. It is NOT below the 12px floor: --t-micro is
   the absolute floor for anything a reader reads, and this is a mark beside a word rather
   than text, but it is set at --t-micro anyway rather than shrunk to fit. Colour supports
   the symbol and never carries it alone. */
.bc-rx{ font-size:var(--t-micro); color:var(--ember); font-weight:700; margin-left:.2em;
  cursor:help; vertical-align:.15em; }
.bc-pop-rx{ margin-top:.4rem; padding-top:.4rem; border-top:1px solid rgba(0,0,0,.10);
  color:#6b6350; }
/* --t-h3, not 1.1rem. The text-size standard says never invent a step, and 1.1rem was one
   — 17.6px, sitting between --t-body and --t-h3 for no reason. The visible difference is
   0.4px; the point is that the scale has one definition. */
.bc-score{ grid-row:1 / span 3; grid-column:3; font-weight:700; font-size:var(--t-h3);
  border-radius:.4rem; padding:.05rem .4rem; min-width:2.1rem; text-align:center; }
/* Bands: colour supports the word, never carries it alone. */
.bc-loud{ background:#ffd08a; } .bc-strong{ background:#e5f0d8; }
.bc-ordinary{ background:#ece8df; } .bc-quiet{ background:#e3e6ec; }
.bc-very-quiet{ background:#dfe1e6; }
/* ★ THE OPEN POPUP HAS TO LIFT ITS WHOLE SECTION (owner, 2026-08-31: "the popup appears
   under the card below it"). ★ .paper_box_wide carries `backdrop-filter: blur(3px)` above
   640px, and a backdrop filter MAKES A STACKING CONTEXT. So .bc-pop's z-index:40 was
   resolved inside this card's own section and could never beat the NEXT section, which
   paints later in document order — no z-index on the popup could ever have fixed it. The
   section itself is what must be raised, and only while a popup is open, so the rest of
   the time the Nest's cards stack exactly as they did.

   It looked correct on a phone, which is why it survived: the small-screen rule at 640px
   sets `backdrop-filter: none`, so below that width there is no stacking context and no
   trap. A bug that only exists above a breakpoint is invisible to a phone-first check.

   ★ ANY FUTURE POPUP INSIDE A PAPER BOX NEEDS THIS SAME LIFT. ★ The trap is the paper
   box, not this card. */
section[data-card]:has(.body-card.bc-open){ position:relative; z-index:60; }

/* ★ BOUNDED, BECAUSE THE MOON'S POPUP OUTGREW THE SCREEN (2026-09-02). ★ When "The
   Moon today" moved in here from its retired card, this dialog went from 591px to
   1285px on desktop and 2046px on a phone — 1705px of it below the fold, with no
   max-height and no scroller, so the only way to reach the end was to scroll the
   PAGE behind an open overlay. MEASURED at 390x844 and 1440x900.

   70vh with its own scroller is the ordinary answer for a role="dialog", it keeps
   the whole thing on screen at any height, and it applies to every body rather than
   to the one that happened to grow. `overscroll-behavior:contain` stops a flick at
   the end of the popup from carrying on into the page underneath it. */
.bc-pop{ position:absolute; z-index:40; top:calc(100% + .35rem); left:0;
  width:min(34rem, calc(100vw - 3.5rem)); padding:.6rem .7rem;
  max-height:70vh; overflow-y:auto; overscroll-behavior:contain;
  background:#fefae0 url("/static/images/paper.png") repeat;
  border:1px solid rgba(0,0,0,.14); border-radius:.6rem;
  box-shadow:0 10px 28px rgba(0,0,0,.24), 0 0 8px rgba(255,183,3,.18); }
.bc-pop-lede{ margin:0 0 .5rem; color:#4a4636; }
/* "The Moon today", moved into this popup from its retired card. Ruled off from the
   three tradition tiles above it because it is a different KIND of thing: those are
   scores with a way through, this is the day's reading. Same hairline the long-version
   collapsible uses on the check-in card, so the two read as one idea. */
.bc-pop-moon{ margin-top:.5rem; padding-top:.45rem; border-top:1px solid #e6dfcb; }
/* Three equal columns: equal standing is a layout fact, not a sentence about one. */
.bc-tri{ display:grid; grid-template-columns:repeat(3, 1fr); gap:.4rem; }
.bc-tile{ display:flex; flex-direction:column; align-items:flex-start; gap:.15rem;
  padding:.4rem .45rem; background:#fefae0 url("/static/images/paper.png") repeat;
  border:1px solid rgba(0,0,0,.12); border-radius:.5rem;
  box-shadow:0 2px 6px rgba(0,0,0,.14); color:inherit; text-decoration:none; }
.bc-tile:hover{ border-color:rgba(0,0,0,.28);
  box-shadow:0 4px 10px rgba(0,0,0,.18), 0 0 6px rgba(255,183,3,.18); }
.bc-tile:focus-visible{ outline:2px solid #a2652d; outline-offset:2px; }
.bc-tile-name{ font-weight:700; }
/* The token, never a literal: a raw rem here would sit outside scripts/type_scale.py and
   outside the per-breakpoint retune of the heading steps. */
.bc-tile-score{ font-weight:700; font-size:var(--t-h2); line-height:1.15;
  border-radius:.4rem; padding:0 .35rem; }
.bc-tile-band{ font-weight:700; color:#4a4636; }
.bc-tile-why{ color:#6b6350; }
.bc-tile-go{ margin-top:auto; padding-top:.25rem; color:#a45c1a; font-weight:600; }
.bc-pop-foot{ margin:.5rem 0 0; padding-top:.35rem;
  border-top:1px dotted rgba(0,0,0,.14); color:#6b6350; }

/* ── Narrow phones: three columns of 12px text stop being readable somewhere around
   here, so the tiles stack. They keep their identical shape, which is the part that
   carries equal standing — side by side is how it is READ, not what makes it equal. */
/* ★ THE TILES STACK ON A PHONE NOW, AND THE BREAKPOINT MOVED FROM 380 TO 640. ★ Each
   tile carries a full sentence about the reader's day rather than a four-word label, and
   a sentence in a 112px column is a word a line. Stacked, they keep the identical shape
   that carries the equal standing — side by side is how they are READ on a wide screen,
   not what makes them equal. */
@media (max-width: 640px){
  .bc-tri{ grid-template-columns:1fr; }
  .bc-tile-go{ margin-top:.15rem; }
}

/* ── The day summary, above the body row. Wider than a tile: it is one thing, not many. */
.day-card{ padding:.6rem .8rem;
  background:#fefae0 url("/static/images/paper.png") repeat;
  border:1px solid rgba(0,0,0,.12); border-radius:.6rem;
  box-shadow:0 3px 8px rgba(0,0,0,.16), 0 0 6px rgba(255,183,3,.14); }
.dc-head{ display:flex; align-items:baseline; gap:.5rem; flex-wrap:wrap; }
.dc-label{ text-transform:uppercase; letter-spacing:.08em; color:#6b6350; }
/* The token, not the number: 1.5rem IS --t-h1, but writing the literal puts this size
   outside every check scripts/type_scale.py can make and outside the per-breakpoint
   retune, which moves the heading steps and would silently leave this one behind. */
.dc-score{ font-weight:700; font-size:var(--t-h1); border-radius:.4rem; padding:0 .45rem; }
.dc-band{ font-weight:700; }
.dc-cov{ margin-left:auto; color:#6b6350; border:1px dotted rgba(0,0,0,.25);
  border-radius:9999px; padding:.05rem .45rem; }
.dc-note{ margin:.35rem 0 0; }
.dc-agree{ margin:.3rem 0 0; color:#6b6350; }
/* daily_outlook's five band words, which this card now shares with the stars above it and
   with "Today, section by section" below. The old bright/favourable/even/hard names are
   kept as defensive duplicates: a stored reading may still carry one. */
.dc-golden{ background:#ffd08a; } .dc-steady{ background:#e5f0d8; }
.dc-mixed{ background:#ece8df; } .dc-demanding{ background:#e3e6ec; }
.dc-intense{ background:#dfe1e6; }
.dc-bright{ background:#ffd08a; } .dc-favourable{ background:#e5f0d8; }
.dc-even{ background:#ece8df; } .dc-hard{ background:#dfe1e6; }
/* The rank sits under the band word: it is the number that makes the card's number. */
.bc-tile-rank{ color:#a45c1a; font-weight:600; }

/* ── The 1-3 stars, the same scale daily_outlook prints everywhere else. Gold on the
   filled slots; the empty slot and the half keep the shape constant so a reader always
   sees three positions and reads the FILL rather than counting glyphs. */
/* The stars take row 1 of the third column and the number sits under them in row 2 —
   NOT `1 / span 2`, which the score chip used when it was alone there and which would
   put the two on top of each other. */
/* ★ WCAG 1.4.3 AA, MEASURED NOT EYEBALLED (2026-09-02). ★ These were #a2652d and #bf6b1e,
   which give 3.01:1 and 3.74:1 against the paper ground — under the 4.5:1 that normal-size
   text needs. The stars scraped past 3:1, which is the bar for a GRAPHIC, and it would have
   been convenient to call them one: they are aria-hidden, so a screen reader takes the
   spoken form from the button's aria-label instead. But they are the primary VISUAL signal
   on the card, read by sighted users with low vision, and text made of ★ characters is
   text. Darkened along their own hue until they clear 4.5 against all three grounds they
   actually sit on — paper, the tile ground, and white — with margin rather than exactly.
   Solved arithmetically; see the journal for the before/after table. */
/* Row 3, spanning to the edge and aligned left under the name and the sign, so the three
   lines read as one block rather than as a column of centred fragments. */
.bc-stars{ grid-row:3; grid-column:2 / -1; font-size:var(--t-body); letter-spacing:.02em;
  color:#9c612c; line-height:1.1; text-align:left; }
/* ★ "SCOOT THE NUMERIC SCORE OVER TO BE CLOSER TO THE SIGN." ★ Row 2, column 3 — the same
   line as the sign rather than a column of its own under the stars. It is the smallest
   thing on the face and it now sits where the sentence it belongs to already is. */
.bc-score-sm{ grid-row:2; grid-column:3; color:#6b6350; text-align:right;
  padding-left:.35rem; white-space:nowrap; }
.bc-tile-stars{ font-size:var(--t-small); color:#9c612c; line-height:1.1; }
.dc-stars{ font-size:var(--t-body); color:#9c612c; line-height:1; letter-spacing:.02em; }
/* A tradition with no reading for this body keeps its column and explains itself. */
.bc-tile--absent{ background:#f6f3ec; border-style:dashed; box-shadow:none; }
.bc-tile-none{ color:#6b6350; font-style:italic; }
/* The one same-object comparison the card can make: given its own weight, not a footnote. */
.bc-pop-station{ border-top:1px solid rgba(201,128,62,.35); color:#4a4636; }

/* ── ★ THE SEARCH ANSWER PANEL (owner, 2026-09-06). ★ ────────────────────────────────
   "Let's give them a popup that tells them so much and then they can click into any one
   item to learn more, but even without clicking into an item they already know so much."

   It sits INSIDE the results list as the first row, so one keyboard model governs the
   whole dropdown and the panel cannot be scrolled away from its own results. It is
   role="presentation" so arrow-key navigation still walks destinations only — a reader
   pressing Down wants the next place to go, not to land on a paragraph.

   Colours are the paper palette the rest of the site uses; nothing new is introduced. */
.sitesearch__answer-wrap { list-style: none; padding: 0; }
.sitesearch__answer {
  background: #fdfaf1;
  border-left: 3px solid #c08a2e;
  border-bottom: 1px solid #e6dfcb;
  padding: 10px 12px;
}
.sitesearch__ans-head { margin: 0 0 6px; font-size: var(--t-small); color: #023047; }
.sitesearch__ans-sub {
  margin: 8px 0 2px; font-size: var(--t-micro); font-weight: 700;
  text-transform: uppercase; letter-spacing: .04em; color: #787266;
}
.sitesearch__ans-line { margin: 4px 0; font-size: var(--t-small); color: #3b3527; line-height: 1.5; }
.sitesearch__ans-list { margin: 2px 0 0; padding-left: 18px; }
.sitesearch__ans-list li {
  font-size: var(--t-small); color: #3b3527; line-height: 1.5; margin: 2px 0;
}
/* The tradition notes are the long ones and the least urgent, so they read as footnotes
   rather than as more of the answer. */
.sitesearch__ans-trad {
  margin: 8px 0 0; font-size: var(--t-micro); color: #6b6350; line-height: 1.5;
}
/* ── The how-to walkthrough ──────────────────────────────────────────────────────────
   Numbered because the ORDER is the content: these are steps to take in sequence, not a
   list of facts, and an unordered list would say the wrong thing about them. On the type
   scale like everything else — --t-small is the floor for secondary text, and a step a
   reader is meant to follow is not secondary. */
.sitesearch__ans-steps {
  margin: 6px 0 0; padding-left: 20px;
}
.sitesearch__ans-steps li {
  font-size: var(--t-small); color: #3b3527; line-height: 1.55; margin: 6px 0;
}
/* What the task costs, said before the reader spends a click on it. */
.sitesearch__ans-needs {
  margin: 6px 0 0; font-size: var(--t-micro); color: #6b6350;
  background: #f6efdd; border-radius: 4px; padding: 4px 8px; display: inline-block;
}
/* The classical-vs-extended note. Sits with the list it qualifies, quieter than the
   figures themselves but louder than the tradition footnotes, because it is the sentence
   that keeps the count above it honest. */
.sitesearch__ans-note {
  margin: 6px 0 0; font-size: var(--t-micro); color: #6b6350; line-height: 1.5;
  font-style: italic;
}
/* The offer to be walked through it, under the numbered steps it accelerates. */
.sitesearch__ans-walk {
  margin-top: 10px; font-family: inherit; font-size: var(--t-small); font-weight: 600;
  padding: 6px 14px; border-radius: 999px; border: 1px solid #023047;
  background: #023047; color: #fdfaf1; cursor: pointer;
}
.sitesearch__ans-walk:hover { background: #04507a; border-color: #04507a; }
.sitesearch__ans-walk:focus-visible { outline: 2px solid #fb8500; outline-offset: 2px; }
.sitesearch__ans-link { color: #023047; font-weight: 600; text-decoration: underline; }
.sitesearch__ans-link:hover { color: #fb8500; }

/* ═══════════════════════════════════════════════════════════════════════════════════
   /ask — Aunt Starling as a conversation
   ═══════════════════════════════════════════════════════════════════════════════════
   ★ THE ANSWER IS THE WIDEST, QUIETEST THING ON THE PAGE. ★ It is prose meant to be
   read, so it gets a measure (~62ch) rather than the full page width — a paragraph run
   edge to edge on a desktop is measurably harder to read, and this is the one surface
   where the reading IS the product. Everything on the shared type scale; nothing here
   goes below --t-small, because there is nothing on this page a reader is not meant to
   read. */
.ask-page { max-width: 760px; margin: 0 auto; padding: 8px 16px 56px; }
.ask-head { margin: 8px 0 18px; }
.ask-sub { color: #6b6350; margin: 6px 0 0; max-width: 62ch; line-height: 1.55; }

.ask-form { display: flex; gap: 8px; margin: 0 0 14px; }
.ask-input {
  flex: 1 1 auto; min-width: 0;
  font-family: inherit; font-size: var(--t-body);
  padding: 10px 14px; border-radius: 999px;
  border: 1px solid #cbd5e1; background: #fff; color: #023047;
}
.ask-input:focus-visible { outline: 2px solid #fb8500; outline-offset: 1px; }
.ask-send {
  font-family: inherit; font-size: var(--t-body); font-weight: 600;
  padding: 10px 22px; border-radius: 999px;
  border: 1px solid #023047; background: #023047; color: #fdfaf1; cursor: pointer;
}
.ask-send:hover { background: #04507a; border-color: #04507a; }
.ask-send:focus-visible { outline: 2px solid #fb8500; outline-offset: 2px; }

.ask-note { margin: 0 0 16px; color: #6b6350; }

/* ★ .paper_box CARRIES THE LOOK; THESE ONLY ADD SPACING AND THE GOLD EDGE. ★ The first
   version painted its own background, border and radius, which meant /ask would drift
   away from every other reading page the moment the paper treatment was retuned — and it
   already read as a different product. The card keeps the left edge because it marks
   "Aunt Starling is speaking here", the same mark the search answer panel uses. */
.ask-card {
  border-left: 4px solid #c08a2e;
  padding: 16px 18px; margin: 0 0 20px;
}
.ask-working, .ask-history, .ask-ideas { padding: 16px 18px; margin: 0 0 20px; }
.ask-working > :first-child, .ask-history > :first-child,
.ask-ideas > :first-child { margin-top: 0; }
/* The steps of a how-to answered on this page. Numbered because the order is content. */
.ask-steps { margin: 8px 0 0; padding-left: 22px; }
.ask-steps li { margin: 8px 0; line-height: 1.6; color: #3b3527; }
.ask-walk { margin-top: 12px; }
.ask-q { margin: 0 0 10px; color: #6b6350; }
.ask-answer p { margin: 0 0 12px; line-height: 1.62; color: #3b3527; max-width: 62ch; }
.ask-answer p:last-child { margin-bottom: 0; }
.ask-dim { color: #6b6350; }
.ask-card .ask-dim { margin: 12px 0 0; }


.ask-sub-h { margin: 14px 0 4px; }
.ask-list { margin: 6px 0 0; padding-left: 20px; }
.ask-list li { margin: 5px 0; line-height: 1.55; color: #3b3527; }
.ask-trad { color: #6b6350; line-height: 1.55; max-width: 62ch; margin: 10px 0 0; }



.ask-chips { list-style: none; margin: 8px 0 0; padding: 0;
             display: flex; flex-wrap: wrap; gap: 8px; }
.ask-chip {
  display: inline-block; font-size: var(--t-small);
  padding: 7px 14px; border-radius: 999px;
  border: 1px solid #cbd5e1; background: #fff; color: #023047; text-decoration: none;
}
.ask-chip:hover { border-color: #8aa1ab; background: #f6efdd; }

/* The offer in the search dropdown that points here. */
.sitesearch__ans-ask { font-weight: 700; }

@media (max-width: 480px) {
  /* The button keeps its tap target and the field keeps its width by stacking, rather
     than the field shrinking to nothing beside it. */
  .ask-form { flex-wrap: wrap; }
  .ask-input { flex: 1 1 100%; }
  .ask-send { flex: 0 0 auto; }
}

/* ── The daily reading's citations ───────────────────────────────────────────────────
   ★ SUBTEXT, NOT A FOOTNOTE THE READER HAS TO CHASE. ★ Owner, 2026-09-07: "add the
   astrological lingo as subtext under the paragraph - almost like citations in a very
   personalized research paper." So it sits directly under the sentence it belongs to,
   visible without a click — the no-fishing contract relocated rather than weakened.
   Quieter than the prose and on the type scale's floor for dense data, never below it. */
/* ★ THE CITATIONS FOLD (owner, 2026-09-07). ★ See the long note in dashboard.html for
   why collapsing a definition is safe on THIS card and nowhere else: the paragraph above
   is forbidden to contain jargon at all, so these lines are depth rather than the meaning
   of a term used above them.

   The marker is drawn here rather than left to the browser: the default disclosure
   triangle is a different shape, size and colour in every engine, and this sits inside a
   letter. list-style:none on the summary kills Safari's, ::-webkit-details-marker kills
   the older WebKit one. */
/* ══════════════════════════════════════════════════════════════════════════════════
   ★ THE ONE DELIBERATE EXCEPTION TO THE PROSE FLOOR (owner, 2026-09-07, asked twice). ★
   "Make the font size on Your day, read together for the narrative sections smaller."
   Then, after the first pass left the sentences alone: "The font size on Your day, read
   together is too big again."

   I held the line the first time and said so, because --t-body is the scale's floor for
   prose. Asked a second time, that is a decision, and it is recorded here rather than
   applied quietly — so the next type audit meets a ruling instead of a stray value.

   ★ AND I OVERSTATED THE RISK THE FIRST TIME, WHICH IS WORTH CORRECTING IN WRITING. ★ I
   said 16px was required on WCAG 1.4.4 (Resize Text) grounds. That is not what 1.4.4
   says: it requires text to survive 200% zoom without loss of content or function, which
   is a question about UNITS, not about size. --t-small is 0.875rem — still relative,
   still scaling with the reader's own browser font setting, still passing 1.4.4. WCAG 2.2
   AA sets no minimum font size at all. The 16px prose floor is OUR house rule, and the
   owner is entitled to spend it on their own letter.

   ONE STEP DOWN THE SCALE, not a number chosen to fit: --t-body -> --t-small, which is
   the step the standard already names for the rung below body copy. The letter's
   hierarchy stays legible — 18px headline, 16px card heading, 14px prose, 12px citations
   — and nothing here goes near the 12px absolute floor, which is not negotiable.

   Scoped to .nest-reading so it is exactly this card. Prose everywhere else on the site
   stays at the floor.

   ★ THE TEMPLATE CARRIES .t-small NOW, AND THIS RULE IS THE BELT TO ITS BRACES. ★ The
   first pass left the paragraphs marked .t-body and overrode them here, which rendered
   correctly and left the markup asserting 16px while the page showed 14 — the identical
   "class that lies" fault as the 23 dead sm:text-lg utilities removed the same day, and
   invisible for the same reason. dashboard.html says t-small; this stays because the
   reason lives here and a bare class in a template records none of it. */
.nest-reading p { font-size: var(--t-small); }

/* ★ AND A TYPE SELECTOR SILENTLY BEAT EVERY .t-micro IN THE CARD (2026-09-09). ★
   `.nest-reading p` scores (0,1,1); `.t-micro` scores (0,1,0). So a paragraph marked
   t-micro rendered at 14px, not 12 — the blanket rule above was written for PROSE and
   quietly claimed the card's dense lines too.

   ★ IT COST THREE ROUNDS WITH THE OWNER, EACH TIME "still too big". ★ Every check I ran
   read the CLASS and reported the floor; the browser was rendering two steps up. The
   `<summary>` toggles were right the whole time and that is the tell — they are not `p`,
   so the rule never reached them, and 12px beside 14px is exactly what "too big" looked
   like. The repo's own rule applies to its own CSS: MEASURE THE COMPUTED SIZE, never the
   class that asks for it.

   An explicit step from the scale must win over a blanket prose default — that is what
   the .t-* classes are FOR. Restoring the intent rather than adding a new step. */
.nest-reading p.t-micro { font-size: var(--t-micro); }
.nest-reading p.t-small { font-size: var(--t-small); }
.nest-reading p.t-body  { font-size: var(--t-body); }

/* ── Where a tradition stands against the other two (2026-09-07) ──────────────────
   Deliberately quiet: this is context around the card's own reading, not a second
   verdict competing with it. Left rule rather than a box, the same device the weather
   alert and the citations use, so the Nest keeps one vocabulary for "related, secondary".
   Every size is a token; the tags are --t-micro, the scale's absolute floor. */
.tstand { margin: .5rem 0 .25rem; padding-left: 10px; border-left: 2px solid #e6dfcb; }
.tstand__head { color: #787266; margin: 0 0 .25rem; line-height: 1.5; }
.tstand__rank { display: inline-block; font-weight: 700; color: var(--ember);
  background: rgba(192,138,46,.13); border-radius: 999px; padding: 1px 7px;
  margin-right: .35em; white-space: nowrap; }
.tstand__verdict { margin: 0 0 .3rem; color: #3b3527; line-height: 1.55; }
.tstand__rel { margin: .25rem 0 0; color: #3b3527; line-height: 1.55; }
/* ★ THE TAG IS A WORD, NOT A COLOUR (WCAG 2.2 SC 1.4.1). ★ agrees / parts / differs are
   three different facts, and a reader who cannot distinguish the hues still gets all
   three from the text. The colour is the pleasure; the word is the information. */
.tstand__tag { display: inline-block; font-size: var(--t-micro); font-weight: 700;
  text-transform: uppercase; letter-spacing: .06em; border-radius: 3px;
  padding: 1px 5px; margin-right: .4em; vertical-align: .08em; }
.tstand__rel--concord   .tstand__tag { background: #e5f0d8; color: #3f5a28; }
.tstand__rel--discord   .tstand__tag { background: #f7e3cf; color: #8a5a12; }
.tstand__rel--divergent .tstand__tag { background: #e3e6ec; color: #4a5568; }

/* ★ THE PER-PERSON RATING, PRINTED FROM OUR ROW (2026-09-08). ★ Gold rather than the
   prose colour because it is a heading for that person's section, not a sentence in it —
   and the same gold the star bands use everywhere else on the Nest, so a reader meets one
   vocabulary for "this is a rating". Marked aria-hidden in the template: the band word
   beside it says the same thing in English, and counting glyphs for a screen reader is
   how ★★☆ ends up announced as "3 stars". */
.nest-reading__rating { color:#956b24; letter-spacing: 0.06em; }

.nest-reading__cites-wrap { margin: 4px 0 0; }
.nest-reading__cites-toggle {
  display: inline-flex; align-items: center; gap: 5px;
  cursor: pointer; list-style: none;
  color: #787266; padding: 2px 0;
  /* 24px is the target-size floor this codebase already holds itself to. */
  min-height: 24px;
}
.nest-reading__cites-toggle::-webkit-details-marker { display: none; }
.nest-reading__cites-toggle::before {
  /* ★ 1em, NOT 0.9em. ★ The toggle is already at --t-micro, the floor, so a relative
     shrink on its marker lands below it — measured 11.2px. A decorative glyph is still
     something a reader looks at, and the cheapest fix is not to shrink it. */
  content: "▸"; color:#956b24; font-size: 1em; line-height: 1;
  transition: transform 120ms ease;
}
.nest-reading__cites-wrap[open] > .nest-reading__cites-toggle::before {
  transform: rotate(90deg);
}
.nest-reading__cites-toggle:hover { color: #6b6350; }
.nest-reading__cites-toggle:focus-visible { outline: 2px solid #8aa1ab; outline-offset: 2px; }
/* The count, so the reader knows how much is behind the fold before they open it. */
.nest-reading__cites-n {
  color: #a89f8c; font-variant-numeric: tabular-nums;
}
.nest-reading__cites-n::before { content: "("; }
.nest-reading__cites-n::after  { content: ")"; }
/* A reader who asked for less motion gets none, and the ▸/▾ still carries the state. */
@media (prefers-reduced-motion: reduce) {
  .nest-reading__cites-toggle::before { transition: none; }
}

.nest-reading__cites {
  margin: 4px 0 0; padding: 0 0 0 14px;
  border-left: 2px solid #ece5d3;
  list-style: none;
}
.nest-reading__cites li {
  margin: 3px 0; color: #787266; line-height: 1.5;
}
.nest-reading__cites li::before {
  content: "↳ "; color:#956b24;
}

/* The invitation shown in place of a section this reader's plan does not include.
   Deliberately quiet: it is an offer, not an obstacle, and it sits where the content
   would have been so the reader can see exactly what they are being offered. */
.sitesearch__ans-locked {
  margin: 8px 0 0; font-size: var(--t-small); color: #6b6350; line-height: 1.5;
  background: #f6efdd; border-radius: 4px; padding: 8px 10px;
}

/* The daily reading's headline — the same sentence the morning email uses as its subject,
   so a reader who got the email recognises the day the moment the page opens. Sits between
   the card title and the provenance line, and reads as a statement rather than a heading. */
/* ★ THE DAY'S SUBJECT LINE AND THE PEOPLE HEADINGS ARE HEADINGS (owner, 2026-09-09). ★
   "I'd like to see this ... and these ... in Special Elite." Both are titles doing a
   heading's job in a <p> — the headline names the day, and each rating line names a
   person's section — so they take the face every other heading on this site uses.

   ★ THE ★ GLYPHS FALL BACK ON PURPOSE, AND THAT IS NOT A GAP. ★ Special Elite is a
   latin-plus-extended face; U+2605 is outside both unicode-ranges, so the browser draws
   the stars from the serif fallback per GLYPH while the words stay typewriter. That is
   the behaviour we want and the reason the star span needs no rule of its own.

   Weight is left as it was: the face ships at 400 only, so a bold heading here is
   synthesised exactly as it already is for every h1-h5 on the site. Consistent, not new. */
.nest-reading__headline,
.nest-reading__who {
  font-family: "Special Elite", Georgia, serif;
}

.nest-reading__headline {
  color: #023047; font-weight: 600; line-height: 1.35;
}

/* ── Click-to-load third-party imagery (/space-weather) ──────────────────────────────
   The reader's IP reaches NASA or NOAA only if they ask. The placeholder has to carry
   enough to make that a real choice — what the picture is, who serves it, and what they
   will see — so it is sized like content rather than like a cookie banner. */
.optin-img__ask {
  border: 1px dashed #cbd5e1; border-radius: 8px;
  padding: 14px 16px; background: #f9f7f0;
}
.optin-img__why { color: #6b6350; line-height: 1.5; margin: 6px 0 10px; max-width: 62ch; }
.optin-img__btn {
  font-family: inherit; font-size: var(--t-small); font-weight: 600;
  padding: 7px 16px; border-radius: 999px;
  border: 1px solid #023047; background: #023047; color: #fdfaf1; cursor: pointer;
}
.optin-img__btn:hover { background: #04507a; border-color: #04507a; }
.optin-img__btn:focus-visible { outline: 2px solid #fb8500; outline-offset: 2px; }
.optin-img__remember {
  display: block; margin-top: 8px; color: #6b6350;
}
.optin-img__remember input { margin-right: 6px; }


/* ── WCAG 2.2 AA CONTRAST, MEASURED (8C, 2026-09-07) ─────────────────────────────────
   axe-core found 23 failing nodes on /nest, all of them small grey or white-on-gold text
   at 12px — which is --t-micro, the scale's absolute floor, so the large-text exemption
   (3:1) does not apply and 4.5:1 is required.

   ★ THE REPLACEMENTS WERE COMPUTED, NOT PICKED. ★ Each was walked toward black along its
   own hue until it cleared 4.5:1 against #fffaf0 using the WCAG relative-luminance
   formula, so the colours keep their character and the ratio is a fact rather than a
   guess:

     .ei-tick / .ei-mins   #8a97a0 → #6b757c    2.88 → 4.52
     .ei-scope             #7a8b93 → #67767c    3.40 → 4.53
     .hb-flag              white on #c08a2e → white on #956b23   2.92 → 4.58

   ★ THE GOLD MOVED, NOT THE TEXT. ★ .hb-flag is white on our brand gold; darkening the
   TEXT would have destroyed the badge, so the background carries the change and the badge
   still reads as gold. Same reasoning as the type scale: the layout is wrong before the
   reader is. */

/* The wingmate card's two summary lines. The "what they may want" line is the one a
   reader acts on, so it reads as the card's own voice; the placement sentence under it
   is quieter, the same relationship the daily reading has with its citations. */
.wm-needs { color: #3b3527; }
.wm-why { color: #787266; }

/* ── Is this murmuration in the daily readings? (/account/murmurations) ──────────────
   ★ THE STATE IS SAID, NOT JUST COLOURED. ★ WCAG 2.2 SC 1.4.1 — colour may never be the
   only carrier of meaning — and the same rule the star bands already follow, where the
   band WORD sits beside the stars. The tint is a second signal, not the signal. */
.mm-daily {
  display: inline-block; padding: 2px 8px; border-radius: 999px;
  border: 1px solid transparent;
}
.mm-daily--in  { background: #eef4ec; border-color: #cfe0c9; color: #2f5d3a; }
.mm-daily--out { background: #f3f1ea; border-color: #ddd6c6; color: #6b6350; }
/* ── The same note, on each wingmate (/account) ──────────────────────────────────────
   ★ ONE PAIR OF COLOURS FOR ONE FACT, ACROSS TWO PAGES. ★ A murmuration and a wingmate
   are both "in or out of the daily readings", and giving each page its own tint is how a
   reader comes to think they are two different settings. Same rule as the badge above:
   the WORD carries the state and the tint is the second signal, never the only one. */
/* ── A day the reader typed vs one we derived (2026-09-07) ───────────────────────────
   The dashed left rule carries it visually; .ev-src carries it in WORDS, because colour
   and line-style may never be the only signal (WCAG 2.2 SC 1.4.1) and a dashed border is
   exactly the kind of difference that vanishes in high-contrast mode. Quiet on purpose:
   it is provenance, not a warning. */
.ev-src {
  /* #787266 was 3.33:1 on this chip at 12px (axe, 2026-09-17); walked darker along its hue to 4.61:1. */
  font-size: var(--t-micro); font-weight: 400; color: #726c61;
  background: #f3f1ea; border: 1px solid #e2ddcd; border-radius: 999px;
  padding: 1px 7px; margin-left: .35em; white-space: nowrap; vertical-align: .1em;
  cursor: help;
}

/* ── "Today, bit by bit" folds (2026-09-07) ──────────────────────────────────────────
   The heading is INSIDE the summary, so the closed state still names itself and carries
   the card count — a fold whose closed state says nothing is a box the reader has to open
   to find out whether it was worth opening.

   ★ THE MARKER IS DRAWN, NOT INHERITED. ★ The default disclosure triangle differs in
   shape, size and colour across engines, and this sits at the top of a letter. Same
   treatment the citation fold uses, so the two folds on this page read as one device. */
.bbb-fold { margin-top: .5rem; }
.bbb-fold__toggle {
  display: flex; align-items: baseline; gap: .5rem;
  cursor: pointer; list-style: none;
  /* ★ AN EXPLICIT FLOOR, NOT AN INHERITED ONE. ★ This set no font-size and its ▸ marker
     is sized in `em`, so both took whatever the surrounding card happened to be — which
     on /chart/vedic measured 11.2px, below the scale's floor. Chasing the ancestor that
     caused it fixes one page; stating the size here fixes every page this fold appears
     on, including the next one. */
  font-size: var(--t-micro);
  color: #023047; font-weight: 600;
  /* 24px is the target-size floor this codebase holds itself to. */
  min-height: 24px; padding: 4px 0;
}
.bbb-fold__toggle::-webkit-details-marker { display: none; }
.bbb-fold__toggle::before {
  content: "▸"; color:#956b24; font-size: 1em; line-height: 1;
  transition: transform 120ms ease;
}
.bbb-fold[open] > .bbb-fold__toggle::before { transform: rotate(90deg); }
.bbb-fold__toggle:hover { color: #fb8500; }
.bbb-fold__toggle:focus-visible { outline: 2px solid #8aa1ab; outline-offset: 2px; }
.bbb-fold__count { color: #787266; font-weight: 400; font-size: var(--t-micro);
  font-variant-numeric: tabular-nums; }
@media (prefers-reduced-motion: reduce) {
  .bbb-fold__toggle::before { transition: none; }
}

/* ── Reset my card layout (2026-09-07) ───────────────────────────────────────────────
   Quiet on purpose: it is a rare, corrective action at the foot of a long page, not
   something competing with the reading. Injected by nest_cards.js and only when there IS
   a saved arrangement to undo, so these rules describe something a reader who has never
   rearranged anything will never see. */
.nest-reset { margin: 1.25rem 0 .5rem; color: #787266; line-height: 1.5; }
.nest-reset__btn {
  font: inherit; font-size: var(--t-micro); font-weight: 600;
  color: #6b6350; background: transparent;
  border: 1px solid #d9d2c0; border-radius: 999px;
  /* 24px is the target-size floor this codebase holds itself to. */
  min-height: 24px; padding: 3px 10px; cursor: pointer;
}
.nest-reset__btn:hover:not(:disabled) { color: #3f3a2e; border-color: #b9ae94; }
.nest-reset__btn:disabled { opacity: .6; cursor: default; }
.nest-reset__btn:focus-visible { outline: 2px solid #8aa1ab; outline-offset: 2px; }
.nest-reset__note { color: #a89f8c; }

/* ── The two weathers, one card (2026-09-07) ─────────────────────────────────────────
   ★ THE 2026-08-19 SPLIT'S REASON SURVIVES AS STRUCTURE. ★ It said the world's magnetic
   field and the temperature outside the window "are different subjects with different
   scales, and only one of them is a verdict about a day". True, and it does not require
   two cards — it requires the two never to be read as one measurement. A ruled break and
   a named sub-heading do that inside one card, which is what the owner asked for twice.

   The rule is the same device the citations and the tradition standing already use, so
   the Nest keeps one vocabulary for "related, and a step down". */
.wx-split { margin-top: .75rem; padding-top: .6rem; border-top: 1px solid #e6dfcb; }
.wx-sub { color: #023047; margin-bottom: .15rem; }

.wingmate-row__daily { margin-top: 3px; }
.wingmate-daily {
  display: inline-block; font-size: var(--t-micro); line-height: 1.4;
  padding: 2px 8px; border-radius: 999px; border: 1px solid transparent;
}
.wingmate-daily--in  { background: #eef4ec; border-color: #cfe0c9; color: #2f5d3a; }
.wingmate-daily--out { background: #f3f1ea; border-color: #ddd6c6; color: #6b6350; }

.mm-daily-btn {
  font-family: inherit; font-size: var(--t-micro); margin-left: 6px;
  background: none; border: none; padding: 2px 4px; cursor: pointer;
  color: #023047; text-decoration: underline;
  /* WCAG 2.2 2.5.8: a control is at least 24px tall (measured 22 at 390px, 2026-09-16). */
  min-height: 24px;
}
.mm-daily-btn:hover { color: #fb8500; }
.mm-daily-btn:focus-visible { outline: 2px solid #fb8500; outline-offset: 2px; }

/* ── ★ JUMP LINKS BETWEEN THE NEST'S CARDS (owner, 2026-09-10) ────────────────────────
   "Add jump links at the top, bottom, and in between the cards on each tab so that Nibling
   can easily skip around to the card they next want to view."

   ★ IT REUSES .jump-chip's SHAPE ON PURPOSE, RATHER THAN ITS CLASS. ★ The chip nav on the
   primary-results pages is inside a paper box on a plain ground; these sit BETWEEN paper
   boxes, over the page's own paper texture, where that chip's translucent white reads as a
   smudge. Same size, same radius, same 44px target — the parts a reader has already learned
   — with a ground that works where these actually stand. Sharing the class would have meant
   one of the two surfaces looking wrong, which is how a shared class becomes two classes
   with an if in the middle.

   ★ 44px MINIMUM, AND NOT ONLY ON COARSE POINTERS. ★ The device review that produced
   _jump_links.html found sub-44pt targets in exactly this pattern. A jump list is used by
   somebody scanning quickly, which is when a small target hurts most.

   Type is --t-micro, the scale's designated step for chips and its absolute floor. */
.nest-jump { max-width:1040px; margin:.5rem auto; padding:0 2rem; }
.nest-jump-lede {
  margin:0 0 .4rem; color:#6b6350; font-size:var(--t-micro);
  text-transform:uppercase; letter-spacing:.06em;
}
/* ★ NO PILL (owner, 2026-09-10). ★ "Take the background button off of the jump links so
   they take up less space."

   The pill was borrowed from .jump-chip, which sits on the primary-results pages as a short
   list of six or seven. This list is FOURTEEN, and repeated between every card — so the
   padding and the border were spending two or three lines of the page on a control that is
   only ever glanced at. Plain links wrap far more per line, which is the space the owner
   was asking for.

   ★ THE TAP TARGET IS NOT SPENT ALONG WITH THE BACKGROUND, AND THAT IS THE WHOLE CARE OF
   IT. ★ Losing the pill loses its 44px height too, and the device review that produced
   _jump_links.html found sub-44pt targets in exactly this pattern. So the base is compact
   for a mouse, and `pointer: coarse` — a finger — puts the height back. Two different
   input devices genuinely want two different sizes here; a single number would be wrong
   for one of them, and the one it would be wrong for is the phone. */
.nest-jump-chips { display:flex; flex-wrap:wrap; gap:.15rem .9rem; }
.nest-jump-chip {
  display:inline-flex; align-items:center; min-height:26px;
  padding:0; border:0; background:none;
  /* --ember on the page's paper measures 6.87:1 — over the 4.5:1 that --t-micro
     requires, since nothing at 12px may claim the large-text exemption. (It was
     #a95a00 at 5.3:1, which also passed; the change was hue, not contrast.) */
  color:var(--ember); text-decoration:none;
  font-size:var(--t-micro); line-height:1.3;
}
.nest-jump-chip:hover, .nest-jump-chip:focus-visible { text-decoration:underline; }
@media (pointer: coarse) {
  .nest-jump-chip { min-height:44px; }
  .nest-jump-chips { gap:0 1rem; }
}

/* The slim bar between two cards: a disclosure and a way back to the top. */
.nest-jump--slim {
  display:flex; align-items:center; gap:1rem; flex-wrap:wrap;
  margin:.35rem auto;
}
.nest-jump-more { flex:1 1 auto; min-width:0; }
.nest-jump-more > summary {
  cursor:pointer; color:#6b6350; font-size:var(--t-micro);
  min-height:44px; display:inline-flex; align-items:center;
  /* A summary is a control and reads as a sentence, so it is NOT Title Case — the header
     policy exempts it by name. */
}
.nest-jump-more[open] > summary { color:var(--ember); }
.nest-jump-more > .nest-jump-chips { margin-top:.4rem; }
.nest-jump-top {
  color:var(--ember); text-decoration:none; font-size:var(--t-micro);
  min-height:44px; display:inline-flex; align-items:center; white-space:nowrap;
}
.nest-jump-top:hover, .nest-jump-top:focus-visible { text-decoration:underline; }

/* ★ THE CARDS ARE EDGE-TO-EDGE UNDER 640px, SO THE NAV MUST BE TOO. ★ .paper_box_wide
   drops its framing below that breakpoint (see the small-screens block above); a nav
   still carrying 2rem of side padding would sit visibly indented from every card it
   navigates. 16px keeps the standing gutter rule. */
@media (max-width: 640px) {
  .nest-jump { padding:0 16px; }
}

/* ── "Last used" on the sign-in page (owner, 2026-09-12) ─────────────────────────────
   Marks which door this browser came in by last. ★ NOT COLOUR ALONE. ★ It carries the
   WORDS "Last used", so it works for a reader who cannot distinguish the tint and for
   one reading it through a screen reader — WCAG 2.2 SC 1.4.1, the same rule the events
   list follows for its dashed-or-solid edge. 12px is the scale's floor and this is a
   chip, which is what that floor is for. */
.signin-last {
  font-size: var(--t-micro);
  font-weight: 600;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  padding: 2px 7px;
  border-radius: 999px;
  /* Computed against #fffaf0, not picked: 4.6:1, clear of the 4.5:1 required at 12px. */
  color: #6b5636;
  background: #f6e7c9;
  border: 1px solid #e6d2a8;
  white-space: nowrap;
}
/* On the black Apple button the same chip needs its own ground to stay legible. */
.bg-black .signin-last,
a.bg-black .signin-last {
  color: #1b1b1b;
  background: #f0e3c4;
  border-color: #f0e3c4;
}


/* ── .tap24: a text-styled control that stands on its own line still needs a 24px target
   (WCAG 2.2 AA, 2.5.8). Links and buttons inside running prose are exempt and do not take
   it. Added from the phone sweep, 2026-09-16 (scripts/phone_audit.py). ── */
.tap24 { display: inline-flex; align-items: center; min-height: 24px; }

/* ── Previous / back / next rows on detail pages (graha, bhava, house, palace). A flex nav's
   links stand alone, so each owes a 24px target (WCAG 2.2 AA, 2.5.8). Measured 18px at
   390px on /chart/vedic/graha and /chart/vedic/bhava, 2026-09-16. ── */
nav.flex > a, .chart-crumb a { display: inline-flex; align-items: center; min-height: 24px; }

/* ── Hook classes: named for scripts and tests to find, styled by the elements around them.
   Declared here so scripts/cross_file_identifiers.py can tell a hook from a typo (2026-09-17). */
.nest-reading__trad {}   /* dashboard.html: a tradition's line under a person's rating */
.rel-parents {}          /* account_wingmate_form.html: the parents disclosure */


/* ── ★ ON A PHONE THE PINNED HEADER IS THE COSMIC STRIP, NOT THE BANNER (owner, 2026-09-17:
   "Let's shrink to just the cosmic strip rather than the logo row"). ★ The banner (logo row and
   search) took ~120px of every phone screen while scrolling. It now scrolls away with the page
   and the cosmic strip (Sun, Moon, Schumann, space weather) stays pinned instead. The menu and
   search are one scroll-up away. base.html measures whichever element is pinned for the jump-link
   offset (--header-h). ── */
@media (max-width: 640px) {
  /* ★ ONE LEVEL ABOVE THE PINNED STRIP (owner, 2026-09-17: "on my iphone ... the main menu loads
     under the cosmic strip"). ★ Both were at --z-header, and the strip comes later in the page,
     so it painted over anything that spilled out of the banner: the open menu. The banner's own
     box never overlaps the strip, so raising it changes nothing else. */
  header.site-banner { position: relative; z-index: calc(var(--z-header) + 1) !important; }
  .cosmic-strip.cosmic-mobile {
    /* !important twice over: overrides.css forces z-index:auto on every .paper_box (the strip
       is one), which let the page paint OVER the pinned strip; and it must be opaque, or the
       page scrolls visibly underneath. */
    position: sticky; top: 0; z-index: var(--z-header) !important;
    background: #fefae0 url("/static/images/paper.png") repeat !important;
    padding-top: calc(4px + env(safe-area-inset-top, 0px)); padding-bottom: 4px;
    box-shadow: 0 2px 6px rgba(2, 48, 71, .12);
  }
  /* ★ A SHORT SCROLL UP BRINGS THE BANNER BACK (owner, 2026-09-17). ★ base.html adds
     .banner-peek once the reader scrolls up a little past the banner, and removes it when they
     scroll down again. Sticky rather than fixed, so the page underneath never jumps. The strip
     then pins BELOW it (--peek-h is the banner's measured height). */
  header.site-banner.banner-peek {
    position: sticky !important; top: 0; z-index: calc(var(--z-header) + 2) !important;
    box-shadow: 0 2px 8px rgba(2, 48, 71, .25);
    animation: banner-peek-in .18s ease-out;
  }
  body.banner-peeking .cosmic-strip.cosmic-mobile { top: var(--peek-h, 0px); }
  header.site-banner.banner-peek .banner-menu__panel { max-height: calc(100vh - var(--peek-h, 120px) - 16px); overflow-y: auto; }
}
@keyframes banner-peek-in { from { transform: translateY(-100%); } to { transform: none; } }
@media (prefers-reduced-motion: reduce) { header.site-banner.banner-peek { animation: none; } }


/* ── ORANGE TEXT THAT PASSES (owner, 2026-09-17: "Yes please" to darkening it). ── The brand
   orange #fb8500 is 2.4:1 on the paper and Tailwind's orange-600 3.4:1; small text needs 4.5:1
   (WCAG 2.2 AA 1.4.3). Text links now use #ae5c00 (4.6:1) and orange-600 becomes #c3490a
   (4.7:1), each the same hue darkened, computed rather than picked. The brand orange stays on
   navy (where it passes) and on decoration (✦, bullets, ticks), which needs only 3:1 as a
   graphic. Hover states keep the bright orange as a change the eye can see. */
.text-orange-600 { color: #c3490a; }


/* ── ★ KEYBOARD FOCUS ON CHART SHAPES, ONE RULE SITE-WIDE (2026-09-17, queue item 23). ★ ──
   Measured with scripts/keyboard_audit.py: the wheel's pattern marks, the Vedic bhavas, the Nest's
   star constellation and the three-traditions strands each took keyboard focus, and either showed
   nothing (the strands) or a 16-22% tint that is hard to see (WCAG 2.4.7, and 1.4.11 wants 3:1 for
   the indicator). SVG shapes ignore `outline` in some browsers, so the ring is a stroke, on the
   focused element and its shapes. #c2410c is 4.7:1 on the paper. Text is left alone. */
svg [tabindex]:focus-visible { outline: none; }
svg [tabindex]:focus-visible:is(circle, rect, path, polygon, ellipse, line),
svg [tabindex]:focus-visible > :is(circle, rect, path, polygon, ellipse, line) {
  stroke: #c2410c !important; stroke-width: 2.5px !important;
}
/* Something fixed at the foot of the screen (Report an Issue, the cookie notice) must not sit
   over the control that just took focus (WCAG 2.4.11): scrolling to a focused element keeps
   this much clear at the bottom, as scroll-padding-top already does under the pinned header. */
html { scroll-padding-bottom: calc(84px + var(--cookie-h, 0px) + env(safe-area-inset-bottom, 0px)); }


/* ── ★ "SHOW ME HOW THIS PAGE WORKS" (owner, 2026-09-20) ★ ──────────────────────────────
   The offer that sits at the top of every page until the reader dismisses it. Modelled on
   .tier-note, which solves the same problem one row lower: a full-width note above the
   content that has to read as part of the paper rather than as a browser chrome bar.

   ★ EVERY SIZE IS A TOKEN FROM THE SCALE. ★ --t-small for the sentence, --t-micro for the
   quiet third button. No raw font-size, so a reader who has chosen Larger gets this too —
   the text-size setting scales the root and the whole site follows.

   ★ AND THE COLOURS ARE COMPUTED. ★ Against this strip's own #fdf6e8: the sentence at
   #3b3527 is 11.32:1 and the micro button at #6b6350 is 5.54:1, both clear of the 4.5:1
   that --t-micro requires — it is the scale's absolute floor, so the large-text 3:1
   exemption cannot apply to it. Measured with the WCAG relative-luminance formula rather
   than chosen by eye, which is the rule this project set after three greys shipped at
   2.88:1. */
.page-help {
  max-width: 62ch; margin: 0.9rem auto 0;
  background: #fdf6e8; border: 1px solid #d9c49a; border-left-width: 4px;
  border-left-color: #956b23;
  border-radius: 0.5rem; padding: 0.6rem 0.85rem;
  font-family: var(--font-sans, system-ui, sans-serif);
}
.page-help__line {
  display: flex; align-items: baseline; gap: 0.5em;
  color: #3b3527; line-height: 1.5;
}
.page-help__icon { flex: 0 0 auto; color: #956b23; }
/* Wraps to one control a line on a phone. MEASURED at 390px: three buttons in a row put
   "Stop offering these" past the edge, which is the exact fault the cosmic strip and the
   owner-tools row each had before their own flex-wrap. */
.page-help__acts {
  display: flex; flex-wrap: wrap; align-items: center; gap: 8px;
  margin-top: 0.55rem;
}
/* A third choice has to look like a third choice. This is a text button on purpose: three
   equally weighted buttons make a reader stop and choose, and the only one that matters
   here is the first. */
.page-help__never {
  background: none; border: 0; padding: 0.2rem 0.1rem;
  color: #6b6350; font-size: var(--t-micro); text-decoration: underline;
  cursor: pointer; font-family: inherit;
}
.page-help__never:hover { color: #3b3527; }
/* The focus ring is not optional: this is an interactive control with no border of its
   own, so without this a keyboard reader cannot see where they are. */
.page-help__never:focus-visible { outline: 2px solid #956b23; outline-offset: 2px; }

/* ── ★ ADMIN TABLES STACK INSTEAD OF SCROLLING SIDEWAYS (owner, 2026-09-20) ★ ───────────
   "Can we update the Beta Members card on /invitations to accommodate the larger font size
   that I selected so that I don't have to scroll to see everything?"

   MEASURED at the owner's own setting (data-text-size="large", root 18px), seven columns:

       phone  390px   table 898px in a 273px wrapper   625px hidden
       tablet 768px                                    194px hidden
       laptop 1280px                                    90px hidden
       desktop 1440px                                   90px hidden

   The laptop and desktop numbers are the same because .paper_box caps at 800px whatever
   the screen is — a PROSE measure (the 62ch line this site keeps everywhere else) applied
   to a data table, which is the one thing it is wrong for. Those two are fixed by moving
   the card to .paper_box_wide, 1040px.

   ★ THE PHONE CANNOT BE FIXED BY WIDTH, SO THE LAYOUT CHANGES INSTEAD. ★ 898px of table
   will never fit 390px of screen, and the type standard forbids the obvious dodge: "Never
   shrink text to make a layout fit. If it doesn't fit, the layout is wrong." So below the
   point where the columns stop fitting, each row becomes a small block with its own
   labels — every field still on screen, nothing hidden, no sideways scroll.

   ★ THE LABELS COME FROM data-label, AND THAT IS A REAL DUPLICATION. ★ The <th> text is
   repeated into each cell. It is the standard responsive-table trick and the alternative
   (reading the header in CSS) does not exist — but it means a renamed column must be
   renamed twice, so the header and the data-label are checked against each other by
   test_admin_table_labels. */
@media (max-width: 860px) {
  table.admin-stack thead { display: none; }
  table.admin-stack, table.admin-stack tbody, table.admin-stack tr, table.admin-stack td {
    display: block; width: 100%;
  }
  table.admin-stack tr {
    border: 1px solid #e6dfca; border-radius: 0.6rem;
    padding: 0.6rem 0.75rem; margin: 0 0 0.75rem;
    background: rgba(255,255,255,0.35);
  }
  table.admin-stack td {
    padding: 0.15rem 0; border: 0;
    /* The label sits above its value rather than beside it: side by side, a long email
       and a long label fight for the same 390px and the email wraps to three lines. */
    display: grid; grid-template-columns: 1fr; gap: 0.1rem;
  }
  table.admin-stack td::before {
    content: attr(data-label);
    font-size: var(--t-micro); font-weight: 600; color: #6b6350;
    text-transform: none; letter-spacing: 0.01em;
  }
  /* An empty cell with a label is noise — "Paying:" above nothing. */
  table.admin-stack td:empty { display: none; }
  /* Long addresses break rather than push the card wide again. */
  table.admin-stack td { overflow-wrap: anywhere; }
}

/* ── A birth date typed in three parts (partials/_birth_date_fields.html) ──────────────
   ★ SIZED FOR THE READER WHO FINDS THIS HARDEST. ★ Owner, 2026-09-29: "make every field
   as easy as possible - and especially for our older users. Equal for everyone, but...
   it's usually easier for younger people to adapt to their accommodations rather than
   the other way around."

   16px is not a style choice here: below it, iOS zooms the page the moment the field is
   focused and the reader has to pinch back out. 44px is the tap target this project
   already holds itself to. Both are floors, so this block only ever grows the controls. */
.bd-row { display: flex; gap: 8px; align-items: flex-end; flex-wrap: wrap; }
.bd-part { display: flex; flex-direction: column; min-width: 0; }
.bd-part--month { flex: 2 1 10rem; }
.bd-part--day   { flex: 1 1 4.5rem; }
.bd-part--year  { flex: 1 1 6rem; }
.bd-label {
  font-size: var(--t-small);
  color: #6b6350;
  margin-bottom: 2px;
}
.bd-input {
  font-size: var(--t-body);      /* 16px floor: anything smaller zooms iOS on focus */
  min-height: 44px;              /* the tap target, not a coincidence */
  width: 100%;
  padding: 8px 10px;
  border: 1px solid #c8bda2;
  border-radius: 6px;
  background: #fff;
  color: #2b2b2b;
}
.bd-input:focus-visible { outline: 2px solid #8aa1ab; outline-offset: 2px; }
/* The year is the part people get wrong, so it is the part that gets the room. */
.bd-part--year .bd-input { letter-spacing: 0.04em; }

/* ── ★ THE APP SHOULD NOT FEEL LIKE A WEB PAGE IN A JACKET (2026-10-19) ★ ────────────
   Owner: "I want this app to feel truly world class and professionally made." These are
   the webview tells a reader notices without being able to name, and every one of them is
   scoped to `html.native` — set before first paint in base.html — so the website is
   byte-for-byte unchanged and none of this can regress a browser.

   ★ REMOVING THE TAP HIGHLIGHT WITHOUT REPLACING IT MAKES THINGS WORSE. ★ iOS paints a
   grey box over a tapped link, which no native control does, so it is the clearest tell of
   the three. But it is also the only touch feedback the page has, and deleting feedback is
   how a button starts feeling broken — the same shape as the gold star with no line. So it
   goes, and a fast opacity dip takes its place: present, immediate, and not a grey
   rectangle in the shape of the hit area. */
html.native {
  -webkit-tap-highlight-color: transparent;
}
html.native a:active,
html.native button:active,
html.native [role="button"]:active,
html.native summary:active,
html.native label:active {
  opacity: 0.6;
  transition: opacity 60ms ease-out;
}
/* A reader who has asked for less motion still gets the feedback; it simply does not
   animate into it. */
@media (prefers-reduced-motion: reduce) {
  html.native a:active,
  html.native button:active,
  html.native [role="button"]:active,
  html.native summary:active,
  html.native label:active { transition: none; }
}

/* ★ LONG-PRESS OFFERS "COPY LINK" ON THINGS THAT ARE NOT LINKS TO ANYWHERE. ★ The callout
   belongs on CONTENT — a reader copying a line of their own reading is doing something
   reasonable, and prose keeps it. On controls it is a web page's menu appearing out of a
   native-looking button, so controls lose it. Text selection goes with it on controls for
   the same reason: dragging a button and selecting its label is not a thing apps do. */
html.native a,
html.native button,
html.native [role="button"],
html.native summary,
html.native .btn-primary,
html.native nav {
  -webkit-touch-callout: none;
  -webkit-user-select: none;
  user-select: none;
}
/* ...but never at the cost of the reader's own words. Anything they typed, and anything we
   wrote for them to read, stays selectable and callout-able. */
html.native input,
html.native textarea,
html.native select,
html.native p,
html.native li,
html.native td,
html.native blockquote,
html.native .nest-reading {
  -webkit-touch-callout: default;
  -webkit-user-select: text;
  user-select: text;
}

/* ★ WHILE THE KEYBOARD IS UP, THE FEEDBACK BUTTON GETS OUT OF THE WAY (2026-10-19). ★
   `.fb-fab` is fixed to the bottom-left of every page. With a keyboard open on a phone
   there is roughly 350px of screen left and the pill sits in it, immediately above the
   keys — which is exactly where a thumb travels on its way to them. A reader correcting a
   birth time is reaching past a feedback button to get at the number pad.

   It goes away rather than moving up: there is nowhere above the keyboard that is not
   also the form somebody is filling in, and a button that follows the keyboard upward is
   a button that covers the next field instead of the last one. `native_keyboard.js` puts
   the class on, and only in the app — on the web the browser's own chrome already sits
   where this problem would be. */
html.keyboard-up .fb-fab { display: none; }

/* `--kb-h` carries the keyboard's height for anything that would rather move than hide.
   Nothing uses it yet; it is published because the measurement is free at the moment we
   already have it, and the next thing anchored to the bottom will want it. */
html.keyboard-up { scroll-padding-bottom: var(--kb-h, 0px); }

/* ── ★ TAP TARGETS: THE FEW THAT ARE BOTH SMALL AND IMPORTANT (2026-10-19) ★ ────────
   MEASURED in a browser at 390px across /account, /nest and /today. The raw number is
   alarming and almost entirely meaningless, so the reasoning matters more than the rule:

   ★ WCAG 2.2 AA (2.5.8) EXEMPTS A TARGET "IN A SENTENCE". ★ Most of what a naive scan
   flags here is a glossary term or a prose link inside a paragraph — 86 of them on /nest
   alone — and every one is exempt, by the standard's own words. An audit that counts them
   reports a page as failing when it passes. That exemption is the whole analysis.

   ★ AND GROWING A STACKED TARGET STEALS ITS NEIGHBOUR'S TAPS. ★ Padding a 19px link up to
   44px adds about 12px above and below. Where the next control is 4px away, the two hit
   areas overlap and the one later in the DOM wins — so a reader aiming at the first gets
   the second. A target you miss is better than a target that does the wrong thing, so a
   blanket min-height across the site would have made this worse, not better.

   What is left after both of those is small and specific: standalone controls, with room
   around them, that a thumb has a real chance of missing. Those are named here. Everything
   else is a layout question for a page at a time, not a stylesheet rule.

   Apple's HIG asks 44pt and WCAG asks 24px; these take the 44. */
.la-tab-link,                 /* a TAB. "This Week, in full" was 19px tall */
.house-full-link,             /* "Open the full house page", one per house on /today */
.wheel-action-link {          /* "↑ Back to top", measured at 17px, and isolated */
  display: inline-flex;
  align-items: center;
  min-height: 44px;
}

/* ══════════════════════════════════════════════════════════════════════════════════
   ★ DARK MODE — THE PAPER AND THE NAVY TRADE PLACES (owner, 2026-10-19) ★

   Owner: "can we create a new set of styles though? Like reverse the paper color (but
   keep the texture) and the navy color? I know dark mode is important to some people."

   ★ WHY A PARALLEL SET AND NOT A MEDIA QUERY OVER THE EXISTING ONE. ★ MEASURED first:
   this stylesheet holds 738 hardcoded hex values (238 distinct) against 17 tokens, and
   the templates hold 2,313 more, 1,274 of them inside `style="..."` attributes. An
   inline style BEATS a stylesheet rule, so a `prefers-color-scheme` override would have
   produced a dark page with light cards and navy text on navy: the half-dark failure,
   which is worse than no dark mode at all.

   What made it tractable was measuring WHERE those inline colours are. 797 of them are
   on the chart pages (western_wheel 325, vedic 211, chinese 117) and admin_insights,
   and those are SEMANTIC DATA COLOURS — a planet's colour, an aspect's colour, an
   element's colour. They no more invert at night than a graph's red line does. On the
   four surfaces dark mode is actually for (base, dashboard, today, account) there are
   **72** inline colour declarations, and three patterns cover 48 of them. Those are
   handled by name at the bottom of this block.

   ★ EVERY COLOUR BELOW WAS COMPUTED, NOT PICKED. ★ The 8C accessibility rule, applied to
   a new palette: each value was walked along its own hue until it cleared the WCAG
   relative-luminance ratio it needs, and the ratio is written beside it. Picking a dark
   palette by eye is how you ship 3:1 body text that looked fine on the machine it was
   chosen on.
   ══════════════════════════════════════════════════════════════════════════════════ */

:root[data-theme="dark"] {
  /* The surface is the brand ink walked 30% toward black: #023047 itself glares over a
     whole page at this size, and the darker surface buys contrast rather than spending it. */
  --d-surface:  #012232;   /* cream text on this: 15.64:1 */
  --d-card:     #022a3e;   /* cards sit ABOVE the page, so they are lighter, not darker */
  --d-ink:      #fefae0;   /* the paper colour, now the ink. 14.23:1 on a card */
  --d-muted:    #cdc9b2;   /*  8.97:1 on a card — cream walked toward the brand's warm
                               dark #3b3527, NOT toward the navy, so the greys stay WARM
                               the way they are on paper. Walking to navy gave a cool
                               green-grey that read as a different brand. */
  --d-faint:    #b0ab96;   /*  6.49:1 on a card */
  --d-border:   #344d55;   /* 1.83:1 against the surface: a border is not text, and a
                               border that clears 4.5:1 is a cage rather than a card */
  --d-rule:     rgba(138,161,171,.35);
  --d-ember:    #cc9970;   /*  6.55:1 — the raw ember #9a3800 is 2.3:1 on dark and
                               unreadable, so it is walked halfway to paper */
  --d-gold:     #fb8500;   /*  6.63:1 UNCHANGED — the brand gold survives the swap
                               untouched, which is the one piece of luck in here */
  --d-ochre:    #b4965c;   /*  5.84:1 — from #956b23, which was 3.45:1 */
  --d-danger:   #c88074;   /*  5.33:1 — from #9b1c1c, which was 2.02:1 */
  --card-border: var(--d-border);
  --card-rule:   var(--d-rule);
  --ember:       var(--d-ember);
}

/* ★ THE TEXTURE STAYS, WHICH WAS THE OWNER'S WHOLE POINT. ★ paper.png is a 9-colour
   dithered grain tile and it is the brand — so it is not removed, it is BLENDED.
   `multiply` over a dark surface keeps the grain's variation and cannot lighten the page,
   because multiplying by anything is darker than the thing itself. `soft-light` and
   `overlay` both lift the midtones and washed the surface toward grey. */
:root[data-theme="dark"] body,
:root[data-theme="dark"] body.paper-bg {
  background-color: var(--d-surface);
  background-blend-mode: multiply;
  color: var(--d-ink);
}
:root[data-theme="dark"] .paper_box,
:root[data-theme="dark"] .acct-card,
:root[data-theme="dark"] .chart-detail-card {
  background-color: var(--d-card);
  background-blend-mode: multiply;
  color: var(--d-ink);
  border-color: var(--d-border);
}
:root[data-theme="dark"] h1,
:root[data-theme="dark"] h2,
:root[data-theme="dark"] h3,
:root[data-theme="dark"] .fancy-header { color: var(--d-ink); }
:root[data-theme="dark"] .text-gray-600,
:root[data-theme="dark"] .text-gray-500,
:root[data-theme="dark"] .text-gray-700 { color: var(--d-muted) !important; }
:root[data-theme="dark"] a { color: var(--d-gold); }
:root[data-theme="dark"] .btn-primary { background-color: var(--d-gold); color: #012232; }
:root[data-theme="dark"] table th { color: var(--d-ink); }
:root[data-theme="dark"] th, :root[data-theme="dark"] td { border-color: var(--d-border); }
:root[data-theme="dark"] input,
:root[data-theme="dark"] textarea,
:root[data-theme="dark"] select {
  background-color: #011a27; color: var(--d-ink); border-color: var(--d-border);
}

/* ★ BOTH SPELLINGS, BECAUSE AN ATTRIBUTE SUBSTRING IS LITERAL (2026-10-19). ★ The
   colour rules below were written with and without the space after the colon; the
   BACKGROUND rules were not, and `background: #fefae0` appears five times in templates/.
   An attribute selector matches the bytes in the attribute, so one spelling silently
   covers some elements and not others — the same shape as the apostrophe that got past
   prompt_privacy in seven spellings. Whenever one of these is added, add its spaced twin
   in the same line. */
/* ★ THE INLINE STYLES, BY NAME, AND THIS IS THE UGLY PART ON PURPOSE. ★ An attribute
   substring selector is the only thing in CSS that can reach a `style="color:#023047"`,
   and it needs !important because an inline declaration outranks every rule. It is
   written out, value by value, rather than hidden behind a wildcard: a list somebody can
   read is a list somebody can shorten, and every line here is a template that would be
   better off using a token. MEASURED on base/dashboard/today/account: these are 48 of the
   72 inline colour declarations on those pages. */
/* ★ EXCEPT ON THE BRAND ORANGE (2026-11-10). ★ Navy on #fb8500 is a button, not text on a
   card, and it is already 5.58:1 in both themes; turning it cream made it 2.36:1 (axe
   color-contrast on the cookie notice's "Got it", and the two orange buttons on
   account_households). The :not() keeps those navy. */
/* And on the pale green and red notices (#dcfce7 / #fee2e2, the admin audit pages): like the
   status pills below, they keep their colour in dark because green means ok and red means
   failing in any theme, so their text stays navy. */
:root[data-theme="dark"] [style*="color:#023047"]:not([style*="#fb8500"]):not([style*="#dcfce7"]):not([style*="#fee2e2"]),
:root[data-theme="dark"] [style*="color: #023047"]:not([style*="#fb8500"]):not([style*="#dcfce7"]):not([style*="#fee2e2"]) { color: var(--d-ink) !important; }
:root[data-theme="dark"] [style*="color:#6b6350"],
:root[data-theme="dark"] [style*="color: #6b6350"] { color: var(--d-muted) !important; }
:root[data-theme="dark"] [style*="color:#3b3527"],
:root[data-theme="dark"] [style*="color: #3b3527"] { color: var(--d-muted) !important; }
:root[data-theme="dark"] [style*="color:#ae5c00"] { color: var(--d-ochre) !important; }
:root[data-theme="dark"] [style*="color:#9b1c1c"] { color: var(--d-danger) !important; }
:root[data-theme="dark"] [style*="color:#956b24"] { color: var(--d-ochre) !important; }
:root[data-theme="dark"] [style*="background:#fefae0"],
:root[data-theme="dark"] [style*="background: #fefae0"],
:root[data-theme="dark"] [style*="background-color:#fefae0"],
:root[data-theme="dark"] [style*="background-color: #fefae0"] {
  background-color: var(--d-card) !important; color: var(--d-ink) !important;
}
:root[data-theme="dark"] [style*="border-color:#d4cfc4"] { border-color: var(--d-border) !important; }

/* ★ THE THREE RULES THAT CARRY !important AND A HARDCODED COLOUR (found by MEASURING,
   2026-10-19). ★ A page-wide contrast scan in dark mode reported **228** text nodes below
   their required ratio, the worst at **1.00:1** — cream on cream, invisible. Every one of
   them was inside the sticky cosmic strip, whose background is `!important` because
   overrides.css forces z-index on every .paper_box and the strip must stay opaque while
   the page scrolls under it. An `!important` shorthand outranks any themed rule, so a
   theme has to answer it in kind.

   ★ THIS IS THE ARGUMENT FOR THE TOKEN LAYER, WRITTEN IN ITS OWN BLOOD. ★ One `!important`
   colour declaration cost 228 unreadable text nodes, and it took a whole-page measurement
   to find rather than a reading of the CSS. There are three of them. The next one somebody
   adds will do the same thing again. */
:root[data-theme="dark"] .cosmic-strip.cosmic-mobile,
:root[data-theme="dark"] .cosmic-strip {
  background-color: var(--d-card) !important;
  background-image: url("/static/images/paper.png") !important;
  background-blend-mode: multiply;
  color: var(--d-ink);
}
:root[data-theme="dark"] .cosmic-strip * { color: var(--d-ink); }
:root[data-theme="dark"] .cosmic-strip .zodiac-name,
:root[data-theme="dark"] .cosmic-strip .glossed { color: var(--d-ink); }

/* line 1641's `background-color: #fefae0 !important` — same treatment. */
:root[data-theme="dark"] .paper_box_wide { background-color: var(--d-card) !important; }

/* ★ AND THE CHARTS ARE LEFT ALONE, DELIBERATELY. ★ A planet's colour is data. Inverting
   it would not be dark mode, it would be a different chart.
   ★ THIS RULE USED TO SET EVERY SVG TEXT TO THE CREAM INK, WHICH IS THE SAME MISTAKE IN
   THE OTHER DIRECTION. ★ On a chart that keeps its paper backing, cream glyphs are just
   as invisible as navy ones on a dark backing. SVG text is themed only where the SVG sits
   on one of OUR dark surfaces, and never inside a wheel. */
:root[data-theme="dark"] .paper_box > svg text,
:root[data-theme="dark"] .acct-card svg text { fill: var(--d-ink); }
:root[data-theme="dark"] .wheel-wrap svg text { fill: #023047; }

/* ── ★ DARK MODE, ROUND TWO: WHAT THE SCANNER NAMED (2026-10-19) ★ ─────────────────
   `scripts/contrast_scan.py` grouped 850 failing nodes across /account, /nest and /today
   into **77 (background + text) combinations**, and the head of that list is short enough
   to answer rule by rule. Each block below is a line from that report, in its order of
   damage, with the node count it was responsible for.

   ★ THE LARGEST SINGLE CAUSE WAS A MISSED SIBLING. ★ Round one themed `.paper_box` and
   not `.paper_box_wide` — about 294 nodes, every one of them navy or warm-grey text on a
   cream card, at 1.08:1. The two classes are two lines apart in this file. That is what a
   hand-maintained theme costs, and the argument for the token layer in one sentence. */

/* 294 nodes — the wide card, which round one missed entirely. */
:root[data-theme="dark"] .paper_box_wide,
:root[data-theme="dark"] .ts-card {
  background-color: var(--d-card) !important;
  background-blend-mode: multiply;
  color: var(--d-ink);
  border-color: var(--d-border);
}

/* 89 nodes at 1.00:1 — cream on cream. Anything still painting the paper colour as a
   background gets the card colour, and its text gets the ink, together: splitting those
   two is how you get invisible text rather than merely wrong text. */
:root[data-theme="dark"] [class*="paper"],
:root[data-theme="dark"] .cosmic-strip,
:root[data-theme="dark"] .glossed {
  background-color: transparent;
}
:root[data-theme="dark"] body.paper-bg { background-color: var(--d-surface); }

/* 88 nodes — `.glossed` sets its own warm grey AND a cream background chip. */
:root[data-theme="dark"] .glossed { color: var(--d-muted); border-bottom-color: var(--d-faint); }

/* 51 nodes — the brand gold sat on a white panel at 2.48:1. It passes on OUR dark surface
   (6.63:1) and fails on white, so the fix is the panel, not the gold. */
:root[data-theme="dark"] [class*="bg-white"],
:root[data-theme="dark"] [style*="background:#fff"],
:root[data-theme="dark"] [style*="background: #fff"],
:root[data-theme="dark"] [style*="background-color:#fff"],
:root[data-theme="dark"] [style*="background-color: #fff"],
:root[data-theme="dark"] [style*="background:#ffffff"] {
  background-color: var(--d-card) !important;
  color: var(--d-ink);
}

/* 99 nodes — the type-scale classes carry no colour of their own, so they were inheriting
   a navy or warm grey from an ancestor that round one did not reach. Named explicitly
   because the scanner named them. */
:root[data-theme="dark"] .t-small,
:root[data-theme="dark"] .t-micro,
:root[data-theme="dark"] .nest-reading__trad,
:root[data-theme="dark"] .nest-reading__cites-toggle { color: var(--d-muted); }

/* ★ THE REMAINING WARM GREYS AND THE NAVY, BY VALUE. ★ These come from CSS rules rather
   than inline styles, so an attribute selector cannot reach them and there is no token to
   redefine — which is exactly the hole the token layer would fill. Until it exists, the
   values the scanner found are mapped here, and the list is deliberately readable so it
   can be deleted a line at a time as the tokens arrive.
   #023047 navy · #3d4f57 slate · #3b3527 #5a5140 #6b6350 #6b6153 #787266 warm greys */
:root[data-theme="dark"] .ts-card,
:root[data-theme="dark"] .ts-card *:not(svg):not(path):not([class*="elem-"]):not([class*="wuxing"]) {
  color: var(--d-ink);
}
:root[data-theme="dark"] .paper_box_wide *:not(svg):not(path):not(a):not(button):not([class*="elem-"]):not([class*="wuxing"]):not([class*="aspect-"]) {
  color: inherit;
}

/* ── ★ DARK MODE, ROUND THREE: THE SCANNER'S REMAINING LIST (2026-10-19) ★ ──────────
   547 nodes after round two, and asking the browser WHICH elements still paint a light
   background turned the guesswork into a list of eight class names. /account came back
   clean at this stage; everything below was found on /nest and the deeper pages.

   ★ AND THERE ARE TWO CREAMS, WHICH IS WHY ROUND TWO MISSED HALF OF THEM. ★ The paper is
   `#fefae0` and the cards are `#fffaf0`, one unit apart in green and sixteen in blue, and
   nothing in this stylesheet ever said so. Overrides written against one of them silently
   skipped every element using the other. */

/* 48 of them on /nest alone: the Bit by Bit tile faces, which are <button>s. */
:root[data-theme="dark"] .bc-face,
:root[data-theme="dark"] .day-card {
  background-color: var(--d-card) !important;
  color: var(--d-ink);
  border-color: var(--d-border);
}
:root[data-theme="dark"] .bc-face *,
:root[data-theme="dark"] .day-card * { color: var(--d-ink); }

/* The second cream, #fffaf0, and the eight rules that paint plain white. Grouped because
   they are all the same mistake in the same shape: a light surface named by a class. */
:root[data-theme="dark"] .ei-card,
:root[data-theme="dark"] .ei-detail,
:root[data-theme="dark"] .nest-card-toggle,
:root[data-theme="dark"] .ask-input,
:root[data-theme="dark"] .ask-chip,
:root[data-theme="dark"] .bd-input,
:root[data-theme="dark"] .jump-chip:hover,
:root[data-theme="dark"] .audit-answer:focus {
  background-color: var(--d-card) !important;
  color: var(--d-ink) !important;
  border-color: var(--d-border);
}
:root[data-theme="dark"] .ei-card *,
:root[data-theme="dark"] .ei-sub { color: var(--d-muted); }

/* The warm greys and the navy that the scanner still found sitting directly on the body:
   #6b6350 and #6b7280 at 2.76:1 and 2.18:1, #023047 at 1.19:1. These come from rules, not
   inline styles, so they are reached by the classes that carry them. */
:root[data-theme="dark"] body.paper-bg > *,
:root[data-theme="dark"] .text-gray-400,
:root[data-theme="dark"] .text-gray-800,
:root[data-theme="dark"] .text-gray-900,
:root[data-theme="dark"] .strong-navy,
:root[data-theme="dark"] .brand-sans { color: var(--d-ink); }

/* ── ★ DARK MODE, ROUND FOUR: THE TRANSLUCENT WHITES (2026-10-19) ★ ────────────────
   ★ THESE WERE INVISIBLE TO EVERY SEARCH FOR A WHITE BACKGROUND, AND THEY WERE THE
   LARGEST GROUP LEFT. ★ The scanner reported 139 nodes sitting on `rgb(255,255,255)`,
   and a direct hunt for white elements found 32. The difference was
   `background: rgba(255,255,255,.5)` and its cousins: a white VEIL, which is a nice
   frosted panel over paper and a grey smear over navy, and which no grep for `#fff`
   will ever turn up.

   Walking the veil to the ink's colour instead keeps exactly what it was for — lifting a
   control slightly off the surface it sits on — in a theme where "lighter" means
   something else. */
:root[data-theme="dark"] .cn-fsel,
:root[data-theme="dark"] .cn-freset,
:root[data-theme="dark"] .jump-chip,
:root[data-theme="dark"] .kin-return-read {
  background: rgba(254, 250, 224, .10);
  border-color: var(--d-border);
  color: var(--d-ink);
}
/* The card-reorder arrows: the only truly solid white left, 32 of them on /nest. */
:root[data-theme="dark"] .nest-card-up,
:root[data-theme="dark"] .nest-card-down {
  background-color: var(--d-card) !important;
  color: var(--d-ink);
  border-color: var(--d-border);
}

/* ── ★ DARK MODE, ROUND FIVE: THE ROWS THE COUNTS WERE HIDING IN (2026-10-19) ★ ─────
   ★ 139 FAILING NODES TURNED OUT TO BE 25 ELEMENTS. ★ The scanner counts TEXT NODES, and
   a wingmate row holds a name, a relationship, three glossed terms and two action links.
   Seventeen of those rows on /account is eighty-eight glossed terms and fifty-one gold
   links — which read as two enormous problems and were one small one. Worth remembering
   before the next count is taken at face value: a node count is a measure of damage, not
   of work. */
:root[data-theme="dark"] .wingmate-row,
:root[data-theme="dark"] .ei-flatnote,
:root[data-theme="dark"] .ei-under-label,
:root[data-theme="dark"] .sitesearch__field {
  background-color: var(--d-card) !important;
  color: var(--d-ink);
  border-color: var(--d-border);
}
:root[data-theme="dark"] .wingmate-row .glossed,
:root[data-theme="dark"] .ei-flatnote .glossed { color: var(--d-muted); }
/* The gold action links were 2.48:1 on that white row and are 6.63:1 on the card. */
:root[data-theme="dark"] .wingmate-row a,
:root[data-theme="dark"] .wingmate-action-link { color: var(--d-gold); }

/* ── ★ DARK MODE, ROUND SIX: THE TEXT, NOW THAT THE SURFACES ARE RIGHT ★ ───────────
   204 left, and their character has changed entirely: the backgrounds in the report are
   now OUR colours (#022a3e, the dark body), and what fails is text still carrying its
   light-mode value — navy #023047 at 1.08:1, the warm greys at 2.76:1, Tailwind's
   gray-500 at 3.10:1. Each comes from a CSS rule rather than an inline style, so there is
   no attribute to match and no token to redefine.

   ★ SO THE DEFAULT IS MADE TO INHERIT, AND THE EXCEPTIONS ARE NAMED. ★ Inside a themed
   surface, text with no colour of its own takes the ink. What must NOT be touched is any
   colour that means something — an element, a Chinese phase, an aspect kind, a dignity, a
   status — because those are data wearing a colour, and inverting them would be lying
   rather than theming. That list is the `:not()` chain, and it is the part to extend when
   a new semantic colour appears. */
:root[data-theme="dark"] .paper_box *:not(svg):not(path):not(circle):not(a):not(button):not(input):not(select):not(textarea):not([class*="elem-"]):not([class*="wuxing"]):not([class*="aspect-"]):not([class*="dignity"]):not([class*="status"]):not([class*="badge"]):not([class*="tier"]),
:root[data-theme="dark"] .acct-card *:not(svg):not(path):not(circle):not(a):not(button):not(input):not(select):not(textarea):not([class*="elem-"]):not([class*="wuxing"]):not([class*="aspect-"]):not([class*="dignity"]):not([class*="status"]):not([class*="badge"]):not([class*="tier"]) {
  color: inherit;
}
/* Tailwind's grey scale, which carries no meaning and is used purely for emphasis. */
:root[data-theme="dark"] .text-gray-500,
:root[data-theme="dark"] .text-gray-600,
:root[data-theme="dark"] .text-gray-700 { color: var(--d-muted) !important; }
/* And the body's own direct children, where the scanner found navy sitting on the page. */
:root[data-theme="dark"] body.paper-bg,
:root[data-theme="dark"] body.paper-bg p,
:root[data-theme="dark"] body.paper-bg li,
:root[data-theme="dark"] body.paper-bg span:not([class*="elem-"]):not([class*="wuxing"]) { color: var(--d-ink); }

/* ── ★ DARK MODE, ROUND SEVEN: INCLUDING THE DAMAGE ROUND SIX DID ★ ────────────────
   ★ TWENTY-THREE OF THE REMAINING FAILURES WERE MINE. ★ Round six made text inherit the
   ink inside any themed surface, and that reached INTO the status pills — the green
   "ALL WELL", the yellow "WORTH A LOOK", the red "SOMETHING IS FAILING" — which are
   `bg-green-100` and friends and are deliberately LIGHT. Cream on green-100 measured
   1.04:1. A broad rule that fixes 74 things and breaks 23 is still worth having, but only
   if the 23 are then found, and they were only found because the scanner was run again
   after the fix rather than before it.

   The pills keep their colour, because green means ok and red means failing in any
   theme, and take dark text instead. */
:root[data-theme="dark"] [class*="bg-green-1"],
:root[data-theme="dark"] [class*="bg-yellow-1"],
:root[data-theme="dark"] [class*="bg-red-1"],
:root[data-theme="dark"] [class*="bg-blue-1"],
:root[data-theme="dark"] [class*="bg-amber-1"],
:root[data-theme="dark"] [class*="bg-green-1"] *,
:root[data-theme="dark"] [class*="bg-yellow-1"] *,
:root[data-theme="dark"] [class*="bg-red-1"] *,
:root[data-theme="dark"] [class*="bg-blue-1"] *,
:root[data-theme="dark"] [class*="bg-amber-1"] * { color: #012232 !important; }

/* The calm-mode surface #f3f1ea, which is a third light background nobody had counted. */
:root[data-theme="dark"] [style*="background:#f3f1ea"],
:root[data-theme="dark"] [style*="background: #f3f1ea"],
:root[data-theme="dark"] [style*="background-color:#f3f1ea"],
:root[data-theme="dark"] [style*="background-color: #f3f1ea"],
:root[data-theme="dark"] html.calm body { background-color: var(--d-surface) !important; }

/* The warm grey #6b6350 still sitting directly on the page: 25 nodes at 2.76:1. It comes
   from rules on elements the body-child selectors did not reach, so it is taken at the
   top of the tree instead, where anything that wants its own colour still overrides. */
:root[data-theme="dark"] body.paper-bg * :not(svg):not(path) { }
:root[data-theme="dark"] .t-micro, :root[data-theme="dark"] .t-small,
:root[data-theme="dark"] .italic, :root[data-theme="dark"] em,
:root[data-theme="dark"] small, :root[data-theme="dark"] figcaption { color: var(--d-muted); }

/* ── ★ DARK MODE, ROUND EIGHT: THE LAST FOUR, NAMED RATHER THAN GUESSED ★ ──────────
   ★ ROUNDS FOUR AND SEVEN CHANGED THE NUMBER BY ZERO, AND BOTH TIMES FOR THE SAME
   REASON: I WROTE SELECTORS FROM A COLOUR AND NOT FROM THE PAGE. ★ The green pill was
   guessed as Tailwind's `bg-green-100`; it is `.wingmate-badge--ready`, with its colour
   in this stylesheet. The grey surface was guessed as calm mode; it is `.wingmate-daily`
   and `.ev-src`. Two whole rounds of careful work landed on nothing because the premise
   was a hex value rather than an element.

   Asking the browser WHICH elements, which took one query, named all four immediately —
   the same method that found the cosmic strip and the wingmate rows. It is the only
   method in this file that has worked every time. */
:root[data-theme="dark"] .wingmate-badge,
:root[data-theme="dark"] .wingmate-badge--ready {
  /* The badge keeps meaning green; only its text moves, because green on dark still
     reads as "good" and inverting it would be theming away a signal. */
  color: #012232 !important;
}
:root[data-theme="dark"] .wingmate-daily,
:root[data-theme="dark"] .wingmate-daily--out,
:root[data-theme="dark"] .ev-src {
  background-color: rgba(254, 250, 224, .10) !important;
  color: var(--d-muted) !important;
}
/* ★ THE WHEEL KEEPS ITS PAPER, AND I TOOK IT AWAY BY MISTAKE (owner, 2026-10-19). ★
   Round eight darkened `.wheel-wrap` to clear a cream background the scanner flagged on
   /today — and on /chart/western that backing is what every glyph is drawn to be legible
   against. Owner, within the hour: "the glyphs on the darker wheel aren't visible." The
   scanner never saw it, because an SVG glyph is not a text node it measures.

   ★ THE CHART IS A DIAGRAM, AND A DIAGRAM KEEPS ITS OWN SURFACE. ★ Which is what this
   file already said about planet and aspect colours two hundred lines up, and then did
   not do. A light chart sitting on a dark page is the same thing as a printed chart on a
   desk at night: deliberate, legible, and not a theming failure. Theming the wheel
   properly means re-colouring every glyph, ring, spoke and label against a dark ground
   while keeping the semantic colours intact — a real piece of work, and not one to do by
   accident at the tail of a CSS sweep. */
:root[data-theme="dark"] .wheel-wrap,
:root[data-theme="dark"] .wheel-wrap * {
  background-color: #fefae0;
  color: #023047;
}

/* ── ★ DARK MODE, ROUND NINE: THE LAST OF THE TEXT, NAMED ★ ───────────────────────
   83 left, and asking which ELEMENTS carry a light-mode colour (rather than which colours
   exist) named them in one query again: <summary>, <strong>, the constellation tabs in
   their "off" state, and a handful of type-scale paragraphs.

   ★ <summary> IS THE ONE WORTH NOTICING. ★ Seventeen of them, and they are the open/close
   controls on every card on the site — the single most-clicked thing on /account. A card
   you cannot read the title of is a card you cannot decide whether to open. */
:root[data-theme="dark"] summary,
:root[data-theme="dark"] summary *,
:root[data-theme="dark"] strong,
:root[data-theme="dark"] b { color: var(--d-ink); }
/* A tab that is OFF is deliberately quieter than one that is on; it stays quieter, at a
   ratio that is still readable, rather than being flattened to the same ink. */
:root[data-theme="dark"] .constellation-tab-off,
:root[data-theme="dark"] .constellation-subtab-off,
:root[data-theme="dark"] .nest-reset__btn { color: var(--d-faint); }
:root[data-theme="dark"] .t-micro.m-0,
:root[data-theme="dark"] .t-small.m-0 { color: var(--d-muted); }

/* ── ★ DARK MODE, ROUND TEN: THE POCKET LETTER'S ENVELOPE ★ ───────────────────────
   Owner, from the phone: "Nest Today Full Horoscope looks good in dark mode, but Pocket
   doesn't."

   ★ THE SCANNER SAID ZERO AND IT WAS RIGHT, WHICH IS THE LESSON. ★ Seventeen Nest
   surfaces, both tabs opened on all five spans: 0 failing nodes, worst 99:1. Nothing in
   the Pocket panel was unreadable and nothing ever had been. The fault was not contrast
   at all — it was a BRIGHT LETTER IN A DARK PAGE, which a contrast scan cannot see by
   construction, the same blind spot that hid the wheel's glyphs. A page can be fully
   legible and still obviously unfinished.

   ★ AND THE LETTER ITSELF WAS ALREADY DARK. ★ Measured inside .nest-pocket__body: 125
   text nodes at #fefae0 and 16 backgrounds at rgb(2,42,62) — which is --d-card exactly,
   so the theme had already reached the email's own content. What stayed cream was only
   OUR ENVELOPE around it: the subject strip and the container, both declared in
   _constellation.html as part of the preview frame rather than part of the email. So
   this finishes a job that was nine-tenths done, and it changes nothing the email
   renderer produces.

   ★ WHY DARKENING THE ENVELOPE BREAKS NO PROMISE, THOUGH IT IS WORTH KNOWING IT IS
   CLOSE. ★ The panel captions itself "the one that goes out by email, exactly as it
   lands", and a caption like that is a claim. The envelope is not the letter — the
   subject strip is our chrome, printed to say "this is an email", and no mail client
   draws it. The letter's own markup is untouched here. */
:root[data-theme="dark"] .nest-pocket__subject {
  background: var(--d-card);
  border-color: var(--d-border);
  color: var(--d-ink);
}
:root[data-theme="dark"] .nest-pocket__body {
  background: var(--d-surface);
  border-color: var(--d-border);
}
/* The card-order disclosure on /nest, the same miss one card over: a floral-white
   #fffaf0 panel that the light-patch sweep found sitting next to the letter. */
:root[data-theme="dark"] .nest-order-card {
  background: var(--d-card);
  border-color: var(--d-border);
}

/* ── ★ DARK MODE, ROUND TEN (b): THE LETTER'S ASIDE, AND WHY ALL FOUR AT ONCE ★ ──────
   One light block was left inside the Pocket letter after the envelope went dark: the
   "Something Else Aunt Starling Does For You" aside, built in daily_digest.py and
   cadence_send.py as a <p> with everything inline, because email clients strip classes.
   Inline outranks every rule, so it reaches the preview untouched while the rest of the
   letter does not — the whole reason the block above exists.

   ★ MEASURED, 300x190 ON THE PHONE: the single visible light rectangle in a dark page. ★

   ★ ALL FOUR DECLARATIONS MOVE TOGETHER, AND THAT IS THE POINT. ★ Darkening only the
   background would put #5c5545 body text and a #963700 header onto a dark card — the
   round-six fault exactly, where 74 nodes were fixed and 23 broken because a surface
   moved and the text on it did not. The contrast scan reads 0 on this panel TODAY
   precisely because that text is sitting on a light ground; take the ground away without
   the text and the scan would have gone backwards.

   Verified against --d-card #022a3e: muted 8.97:1, ochre 6.12:1. */
:root[data-theme="dark"] .nest-pocket__body [style*="background:#faf7f0"] {
  background-color: var(--d-card) !important;
  border-left-color: var(--d-border) !important;
}
:root[data-theme="dark"] .nest-pocket__body [style*="color:#5c5545"] { color: var(--d-muted) !important; }
:root[data-theme="dark"] .nest-pocket__body [style*="color:#963700"] { color: var(--d-ochre) !important; }

/* ── ★ DARK MODE, ROUND TEN (c): THE SELECTED SUBTAB WAS THE ODD ONE OUT ★ ───────────
   .constellation-tab-on marks itself with TRANSLUCENT gold — rgba(192,138,46,0.13) —
   which over a dark page composites dark and needed nothing. .constellation-subtab-on
   used SOLID #f3ead6 for the same job, so Full/Pocket kept a cream pill while the span
   tabs above it did not. The inconsistency was the bug; matching the strip above it is
   the fix, not a new colour.

   ★ AND THE TEXT COLOUR IS DELIBERATELY NOT TOUCHED HERE. ★ The first draft set it to
   --d-ochre and MEASURED 4.59:1 on the composited pill — over the 4.5 floor by four
   hundredths, at --t-micro, which is no margin at all. It was also redundant: the
   markup already says `color: var(--ember)`, and the dark block remaps --ember to
   --d-ember, so the label themes itself and lands at 5.96:1. Changing a token that is
   already doing its job is how a palette grows a second answer. */
:root[data-theme="dark"] .constellation-subtab-on {
  background: rgba(192, 138, 46, 0.18);
  border-color: #c08a2e;
}

/* ── ★ DARK MODE, ROUND ELEVEN: THE SEMANTIC CHIPS, AS A RAMP AND NOT A FLAT SET ★ ───
   The last light patches on /nest, 10 per span and all on the Full tab: the band scores
   (.bc-*), the day scores (.dc-*), the motion chips (.ts-motion-*) and the togetherness
   tags (.tstand__tag). Every one is a pale pastel carrying MEANING — the comments beside
   them say "colour supports the word, never carries it alone" — so they cannot be
   flattened to one grey without throwing that away.

   ★ THE FIRST ATTEMPT WALKED ALL SIX HUES TO THE SAME 5.5:1 AND MADE THINGS WORSE. ★
   Pinning every chip to one contrast pins it to one LIGHTNESS, so only hue is left to
   separate them, and two hues that sit near each other collapse. MEASURED: the loud band
   and the discord tag went from 96 apart in light to 24 apart in dark. Chasing it moved
   the collision to amber/warm (128 -> 59). That is the signature of over-constraining,
   not of a bad colour choice.

   ★ THE LIGHT PALETTE IS A RAMP, SO THE DARK ONE IS TOO. ★ #ffd08a is far brighter than
   #ece8df on purpose: loud is meant to shout. So the loud chip stays BRIGHT here with
   dark ink on it — the existing --d-gold, no new colour, 6.03:1 — and only the quieter
   chips become dark surfaces with cream. Separation stops being a problem the moment the
   chips are allowed to differ in lightness the way they always did.

   MEASURED against the light palette's own separations — two pairs improve, three are
   unchanged, none regress:
     strong/ordinary    22 -> 85      quiet/motion-building   27 -> 55
     loud/discord       96 -> 186+    quiet/intense           27 -> 22
   ★ quiet/intense is the one that does not improve, and it was already indistinguishable
   in light (#e3e6ec against #dfe1e6). The word is what tells them apart, in both themes,
   which is what the original comment promised. */
:root[data-theme="dark"] {
  --d-chip-green: #486d1b;   /* cream 5.74:1 */
  --d-chip-warm:  #756339;   /* cream 5.55:1 */
  --d-chip-cool:  #516389;   /* cream 5.71:1 */
  --d-chip-deep:  #5b657f;   /* cream 5.53:1 */
  --d-chip-slate: #236c89;   /* cream 5.58:1 */
  --d-chip-rust:  #9c4f03;   /* cream 5.65:1 */
}
/* Loud keeps its prominence: bright gold, dark ink, exactly the light-mode intent. */
:root[data-theme="dark"] .bc-loud,
:root[data-theme="dark"] .dc-golden,
:root[data-theme="dark"] .dc-bright { background: var(--d-gold); color: var(--d-card); }

:root[data-theme="dark"] .bc-strong,
:root[data-theme="dark"] .dc-steady,
:root[data-theme="dark"] .dc-favourable,
:root[data-theme="dark"] .tstand__rel--concord .tstand__tag {
  background: var(--d-chip-green); color: var(--d-ink); }

:root[data-theme="dark"] .bc-ordinary,
:root[data-theme="dark"] .dc-mixed,
:root[data-theme="dark"] .dc-even,
:root[data-theme="dark"] .ts-motion-easing {
  background: var(--d-chip-warm); color: var(--d-ink); }

:root[data-theme="dark"] .bc-quiet,
:root[data-theme="dark"] .dc-demanding,
:root[data-theme="dark"] .tstand__rel--divergent .tstand__tag {
  background: var(--d-chip-cool); color: var(--d-ink); }

:root[data-theme="dark"] .bc-very-quiet,
:root[data-theme="dark"] .dc-intense,
:root[data-theme="dark"] .dc-hard {
  background: var(--d-chip-deep); color: var(--d-ink); }

:root[data-theme="dark"] .ts-motion-building {
  background: var(--d-chip-slate); color: var(--d-ink); }

:root[data-theme="dark"] .tstand__rel--discord .tstand__tag {
  background: var(--d-chip-rust); color: var(--d-ink); }

/* An ABSENT tile is drawn as a dashed outline of nothing; on paper it is filled #f6f3ec
   to read as a gap, and on a dark page the gap IS the page. */
:root[data-theme="dark"] .bc-tile--absent {
  background: transparent; border-color: var(--d-border); }

/* ── ★ DARK MODE, ROUND TWELVE: THE SITE-WIDE SWEEP ★ ────────────────────────────────
   First run of scripts/unthemed_scan.py over the whole site — 89 reader pages, both
   themes, every parameterised route filled from the live test primary:

       contrast       0 failing   in BOTH themes, all 89 pages
       light patches  379 on 46 pages in dark, 0 in light

   ★ CONTRAST WAS ALREADY DONE AND NOBODY KNEW, BECAUSE WE HAD NO WAY TO ASK THE OTHER
   QUESTION. ★ Every remaining dark-mode fault on this site is a light patch, not an
   unreadable one. That is the whole case for the second instrument.

   ★ AND THE MECHANISM WAS MEASURED, NOT ASSUMED. ★ A probe asked each patch whether its
   background came from an inline `style` attribute or a stylesheet, because the two need
   completely different selectors and guessing would have fixed half of them silently.
   Roughly two thirds are inline, which is why the attribute-selector block further up
   keeps growing.

   ★ #ece8df IS TWO DIFFERENT THINGS AND MUST GO TWO DIFFERENT WAYS. ★ 133 of the 379
   patches are this one hex, and it is used both as a METER TRACK (the empty groove on
   the Vedic and Chinese strength bars, `…background:#ece8df;border-radius:4px;overflow
   :hidden`) and as a TEXT CHIP (`background:#ece8df;color:#1a2b3a`). A track must be
   DARKER than the card it sits on or it reads as a bar that is already full; a chip must
   be LIGHTER or it disappears. One replacement colour would have been wrong for one of
   them, and wrong in a way that looks plausible. They are told apart by the rest of
   their own inline declaration. */

/* ── inline backgrounds, both spellings, !important because inline outranks everything ── */
:root[data-theme="dark"] [style*="background:#ece8df"][style*="overflow:hidden"],
:root[data-theme="dark"] [style*="background: #ece8df"][style*="overflow:hidden"] {
  background-color: var(--d-surface) !important;   /* a groove, so darker than the card */
}
:root[data-theme="dark"] [style*="background:#ece8df"][style*="color:#1a2b3a"],
:root[data-theme="dark"] [style*="background: #ece8df"][style*="color:#1a2b3a"],
:root[data-theme="dark"] [style*="background:#f3ede0"],
:root[data-theme="dark"] [style*="background: #f3ede0"] {
  background-color: var(--d-chip-warm) !important; /* a chip, so lighter than the card */
}
:root[data-theme="dark"] [style*="background:#eef2f4"],
:root[data-theme="dark"] [style*="background: #eef2f4"] {
  background-color: var(--d-chip-slate) !important;
}
:root[data-theme="dark"] [style*="background:#faf4e8"],
:root[data-theme="dark"] [style*="background: #faf4e8"],
:root[data-theme="dark"] [style*="background:#f7f3e8"],
:root[data-theme="dark"] [style*="background: #f7f3e8"],
:root[data-theme="dark"] [style*="background:#faf7ef"],
:root[data-theme="dark"] [style*="background: #faf7ef"] {
  background-color: var(--d-card) !important;
  border-color: var(--d-border) !important;
}
/* The inline text colours that ride ON those chips, moved in the same rule set for the
   round-six reason: a ground that moves without its text is a new failure, not a fix. */
:root[data-theme="dark"] [style*="color:#1a2b3a"],
:root[data-theme="dark"] [style*="color: #1a2b3a"] { color: var(--d-ink) !important; }
:root[data-theme="dark"] [style*="color:#4b5563"],
:root[data-theme="dark"] [style*="color: #4b5563"] { color: var(--d-muted) !important; }

/* ★ THE PAPER TEXTURE, INLINE. ★ Cards written as
   `background: url("/static/images/paper.png") repeat rgb(254, 250, 224)`. Matched on the
   FILENAME rather than on the colour, because this one declaration is spelled several
   ways across the templates and the image is the only stable part of it. The grain is
   kept and multiplied over the dark card — the owner's ask was "reverse the paper colour
   but keep the texture", and blend-mode is how that is done without a second image. */
:root[data-theme="dark"] [style*="paper.png"] {
  background-color: var(--d-card) !important;
  background-blend-mode: multiply;
  border-color: var(--d-border) !important;
}

/* ── stylesheet-declared patches ────────────────────────────────────────────────────── */
/* The readiness badge: Tailwind's green-100 on 24 nodes, the commonest of these. */
:root[data-theme="dark"] .wingmate-badge--ready {
  background: var(--d-chip-green); color: var(--d-ink); border-color: var(--d-border);
}
:root[data-theme="dark"] .v-chip-str,
:root[data-theme="dark"] .transit-wheel-toggle {
  background: var(--d-chip-slate); color: var(--d-ink); border-color: var(--d-border);
}
:root[data-theme="dark"] .zw-prom-headline { background: var(--d-chip-warm); color: var(--d-ink); }
:root[data-theme="dark"] .zw-prom-quiet,
:root[data-theme="dark"] .zw-prom-moderate { background: var(--d-chip-deep); color: var(--d-ink); }
:root[data-theme="dark"] .page-help,
:root[data-theme="dark"] .summary-jump,
:root[data-theme="dark"] .vchart-card,
:root[data-theme="dark"] .mm-daily--out {
  background-color: var(--d-card); color: var(--d-ink); border-color: var(--d-border);
}
/* A form field: Tailwind's gray-50 ground with a gray-300 border. A text input must look
   INSET on a dark page, so it goes to the page surface rather than the card. */
:root[data-theme="dark"] .border-gray-300 {
  background-color: var(--d-surface); color: var(--d-ink); border-color: var(--d-border);
}

/* ── ★ DARK MODE, ROUND THIRTEEN: THE TAIL, AND TWO HUES WE DID NOT HAVE ★ ───────────
   Round twelve took the site-wide count from 379 light patches to 86. Ten of those 86
   are the natal wheel keeping its paper DELIBERATELY — 113 glyphs are drawn against it
   and darkening it is what blanked them on 2026-10-19 — so the real tail is 76, and this
   is it. Probed for mechanism again rather than assumed: most are inline, each with its
   own text colour, and those move in the same rule as their ground.

   ★ #ece7df IS NOT #ece8df. ★ One digit apart, and the second one is ANOTHER meter
   track, on the murmuration reading page. A fix written from memory of the first would
   have missed it in silence, which is the whole argument for re-measuring rather than
   re-reading.

   ★ TWO NEW CHIP HUES, AND THE FIRST ATTEMPT AT THEM WAS GARISH. ★ The Zi Wei Dou Shu
   transformation callouts need a red and a violet. Walking the source pastels down with
   a saturation MULTIPLIER produced #cd0000 and #9200f3 — pure red and electric violet,
   nothing like this brand, because the sources are nearly white so any multiplier
   overshoots. Fixing saturation at 0.34, inside the band the existing chips already
   occupy, gives muted versions that belong. Red sits 77 from rust, which sounds tight
   until you measure the same pair in LIGHT mode: 44. Dark separates them better. */
:root[data-theme="dark"] {
  --d-chip-red:    #9d4d4d;   /* cream 5.54:1 */
  --d-chip-violet: #804fa1;   /* cream 5.62:1 */
}

/* ── inline: the pastel callouts, each with the text that sits on it ────────────────── */
:root[data-theme="dark"] [style*="background:#ece7df"][style*="overflow:hidden"],
:root[data-theme="dark"] [style*="background: #ece7df"][style*="overflow:hidden"] {
  background-color: var(--d-surface) !important;      /* a track, so darker than its card */
}
:root[data-theme="dark"] [style*="background:#f6e9da"],
:root[data-theme="dark"] [style*="background: #f6e9da"],
:root[data-theme="dark"] [style*="background:#efe9db"],
:root[data-theme="dark"] [style*="background: #efe9db"],
:root[data-theme="dark"] [style*="background:#eae3d1"],
:root[data-theme="dark"] [style*="background: #eae3d1"] {
  background-color: var(--d-chip-warm) !important;
}
:root[data-theme="dark"] [style*="background:#e9f1e2"],
:root[data-theme="dark"] [style*="background: #e9f1e2"] {
  background-color: var(--d-chip-green) !important;
}
:root[data-theme="dark"] [style*="background:#e3edf5"],
:root[data-theme="dark"] [style*="background: #e3edf5"] {
  background-color: var(--d-chip-slate) !important;
}
:root[data-theme="dark"] [style*="background:#f3f0ea"],
:root[data-theme="dark"] [style*="background: #f3f0ea"],
:root[data-theme="dark"] [style*="background:#f8f5ec"],
:root[data-theme="dark"] [style*="background: #f8f5ec"],
:root[data-theme="dark"] [style*="background:#fdfbf4"],
:root[data-theme="dark"] [style*="background: #fdfbf4"],
/* Two more pale creams, found by the 2026-11-10 sweep: the event and recurrence detail
   panels (#fff7ec) and share details (#fffdf7). Their navy text turned cream while they
   stayed pale, so the words all but vanished. */
:root[data-theme="dark"] [style*="background:#fff7ec"],
:root[data-theme="dark"] [style*="background: #fff7ec"],
:root[data-theme="dark"] [style*="background:#fffdf7"],
:root[data-theme="dark"] [style*="background: #fffdf7"] {
  background-color: var(--d-card) !important;
  border-color: var(--d-border) !important;
}
:root[data-theme="dark"] [style*="background-color: #e5e7eb"],
:root[data-theme="dark"] [style*="background-color:#e5e7eb"] {
  background-color: var(--d-chip-deep) !important;
}
/* Translucent white: written as rgba so it picks up whatever is behind it, which on a
   dark page is exactly wrong — it lightens the surface it was meant to tint. */
:root[data-theme="dark"] [style*="rgba(255,255,255,0.7)"],
:root[data-theme="dark"] [style*="rgba(255, 255, 255, 0.7)"],
:root[data-theme="dark"] [style*="rgba(255,255,255,0.55)"],
:root[data-theme="dark"] [style*="rgba(255, 255, 255, 0.55)"] {
  background-color: rgba(254, 250, 224, 0.10) !important;
}
/* The text that rides on those grounds. Moved HERE, with them, never afterwards. */
:root[data-theme="dark"] [style*="color:#8a5a12"],
:root[data-theme="dark"] [style*="color: #8a5a12"],
:root[data-theme="dark"] [style*="color:#5b5346"],
:root[data-theme="dark"] [style*="color: #5b5346"],
:root[data-theme="dark"] [style*="color:#3f5a28"],
:root[data-theme="dark"] [style*="color: #3f5a28"],
:root[data-theme="dark"] [style*="color:#1f4d6b"],
:root[data-theme="dark"] [style*="color: #1f4d6b"],
:root[data-theme="dark"] [style*="color:#4a4a4a"],
:root[data-theme="dark"] [style*="color: #4a4a4a"] { color: var(--d-ink) !important; }

/* ── stylesheet: the Zi Wei callouts, the opt-in placeholder, the Tailwind utilities ── */
:root[data-theme="dark"] .zw-hua-lu,
:root[data-theme="dark"] .mm-daily--in { background: var(--d-chip-green); color: var(--d-ink); }
:root[data-theme="dark"] .zw-hua-quan { background: var(--d-chip-slate); color: var(--d-ink); }
:root[data-theme="dark"] .zw-hua-ji   { background: var(--d-chip-red); color: var(--d-ink); }
:root[data-theme="dark"] .zw-hua-ke   { background: var(--d-chip-violet); color: var(--d-ink); }
:root[data-theme="dark"] .zw-prom-strong { background: var(--d-chip-warm); color: var(--d-ink); }
:root[data-theme="dark"] .zw-ord,
:root[data-theme="dark"] .notice-card {
  background-color: var(--d-card); color: var(--d-ink); border-color: var(--d-border);
}
/* ★ The click-to-load placeholder for NOAA and NASA images. ★ It is the control that
   tells a reader a third party will see their IP before they choose — so it must be
   legible in both themes, and a cream card on a dark page is the version of it nobody
   reads. */
:root[data-theme="dark"] .optin-img__ask {
  background-color: var(--d-card); color: var(--d-ink); border-color: var(--d-border);
}
/* Tailwind utilities, by the class rather than by the colour they resolve to, so a
   Tailwind upgrade that shifts a palette step cannot quietly strand these. */
:root[data-theme="dark"] .bg-white,
:root[data-theme="dark"] .bg-gray-50,
:root[data-theme="dark"] .bg-gray-100,
:root[data-theme="dark"] [class*="bg-white/"] {
  background-color: var(--d-card) !important; color: var(--d-ink);
}
:root[data-theme="dark"] .bg-amber-50,
:root[data-theme="dark"] .bg-yellow-50,
:root[data-theme="dark"] .bg-yellow-100,
:root[data-theme="dark"] [class*="bg-amber-50/"] {
  background-color: var(--d-chip-warm) !important; color: var(--d-ink);
}
:root[data-theme="dark"] .bg-red-50,
:root[data-theme="dark"] .bg-red-100,
:root[data-theme="dark"] [class*="bg-red-50/"] {
  background-color: var(--d-chip-red) !important; color: var(--d-ink);
}
:root[data-theme="dark"] .bg-green-100 {
  background-color: var(--d-chip-green) !important; color: var(--d-ink);
}

/* ── ★ DARK MODE, ROUND FOURTEEN: THE LAST TEN ★ ─────────────────────────────────────
   Round thirteen took the site-wide count to 20, of which ten are the natal wheel by
   design. These are the other ten, and they are all one thing: Tailwind utility and
   arbitrary-value classes that no earlier round named.

   ★ AND THE 20 WAS REPORTED AS 20 BECAUSE OF A SLIP IN MY OWN HARNESS. ★ The wheel
   allowlist was added to scripts/unthemed_scan.py and "mirrored" into the sweep script
   with a bare str.replace() whose pattern did not match — then printed a success line
   unconditionally. A measurement tool that reports work it did not do is worse than one
   that crashes, and this project has the same lesson written about `generate_character
   _readings` discarding its return value. The mirror asserts now. */
:root[data-theme="dark"] .wingmate-edit-tip,
:root[data-theme="dark"] .bg-amber-100,
:root[data-theme="dark"] .bg-yellow-100,
:root[data-theme="dark"] [class*="bg-amber-50/"],
:root[data-theme="dark"] .problem-chip--warning,
:root[data-theme="dark"] .problem-chip--resource,
:root[data-theme="dark"] .src-assumed {
  background-color: var(--d-chip-warm) !important;
  color: var(--d-ink) !important;
  border-color: var(--d-border) !important;
}
:root[data-theme="dark"] .wingmate-edit-tip strong { color: var(--d-ink) !important; }

:root[data-theme="dark"] .bg-green-50,
:root[data-theme="dark"] .bg-emerald-50 {
  background-color: var(--d-chip-green) !important; color: var(--d-ink) !important;
}
:root[data-theme="dark"] [class*="bg-gray-50/"],
:root[data-theme="dark"] [class*="bg-gray-100/"] {
  background-color: var(--d-card) !important; color: var(--d-ink);
}
/* Tailwind ARBITRARY values. The generated class name literally contains the hex, so the
   selector has to escape the brackets and the hash — `.bg-\[\#faf4e8\]`. Written out
   rather than reached with a substring matcher, because an attribute selector here would
   also catch `bg-[#023047]`, which is already dark and must be left alone. */
:root[data-theme="dark"] .bg-\[\#faf4e8\] {
  background-color: var(--d-card) !important; color: var(--d-ink) !important;
}
:root[data-theme="dark"] .bg-\[\#e8eef0\] {
  background-color: var(--d-chip-slate) !important; color: var(--d-ink) !important;
}

/* ── ★ DARK MODE, ROUND FIFTEEN: THE LAST ONE ★ ──────────────────────────────────────
   One node across 89 pages: `.brand-selected`, the chosen plan on /subscribe. Its light
   form is deliberate — "navy edge + the paper-yellow wash already used for emphasis
   site-wide", replacing a borrowed Tailwind purple. So the dark form inverts the same
   intent rather than flattening it: the brand GOLD as the edge, and the warm chip as
   the wash. The emphasis survives the theme, which is the point of it.
   `.wingmate-badge--default` shares the #fff8e1 wash and goes with it. */
:root[data-theme="dark"] .brand-selected {
  border-color: var(--d-gold);
  background-color: var(--d-chip-warm);
  color: var(--d-ink);
}
:root[data-theme="dark"] .wingmate-badge--default {
  background: var(--d-chip-warm); color: var(--d-ink); border-color: var(--d-border);
}
/* The cadence chips share that decision and so share its dark form: gold edge on the
   chosen one, the warm chip as its wash, the quiet ones on the slate chip. */
/* ★ THIS SHIPPED BROKEN FOR ONE RENDER, AND IT IS WORTH THE NOTE (2026-10-23). ★ The
   first version of these two rules was written as
   `:root:not([data-theme="light"]) .renewal-disclosure` with NO @media wrapper, copied
   from the generic theme-aware pattern. Dark here is OPT-IN, chosen in our own footer —
   279 blocks in this file key on `:root[data-theme="dark"]` and two on anything else —
   so `:not([data-theme="light"])` is true for almost every reader, and a LIGHT page
   rendered the disclosure in dark-theme ink (#e8e3d7) on its own cream panel. Measured in
   a browser: computed colour rgb(232,227,215), all but invisible.
   That is the worst element on the site to make faint: California asks for this one to be
   "clear and conspicuous", and an unreadable consent is arguably no consent. Caught by
   LOOKING at the page rather than by reading the stylesheet. */
:root[data-theme="dark"] .renewal-disclosure {
  background: var(--d-chip-warm); color: var(--d-ink); border-color: var(--d-border);
}
:root[data-theme="dark"] .terms-consent,
:root[data-theme="dark"] .renewal-consent { color: var(--d-ink); }
:root[data-theme="dark"] .terms-consent input[type="checkbox"]:not(:checked),
:root[data-theme="dark"] .renewal-consent input[type="checkbox"]:not(:checked) {
  background-color: transparent; border-color: var(--d-gold);
}
/* ★ AND THE SAME TRAP IS ONE LINE AWAY FROM THIS ONE. ★ These two rules key on
   `[data-theme="dark"]` and nothing else, for the reason the block above records: dark
   is opt-in here, so `:not([data-theme="light"])` would paint a LIGHT page in dark ink. */
:root[data-theme="dark"] .billing-country { color: var(--d-ink); }
:root[data-theme="dark"] .billing-country select {
  color: var(--d-ink); background-color: var(--d-chip-slate);
  border-color: var(--d-gold);
}

:root[data-theme="dark"] .cadence-chip {
  background: var(--d-chip-slate); color: var(--d-ink); border-color: var(--d-border);
}
:root[data-theme="dark"] .cadence-chip:hover { background: var(--d-chip-warm); }
:root[data-theme="dark"] .cadence-chip[aria-current] {
  border-color: var(--d-gold); background: var(--d-chip-warm); color: var(--d-ink);
}

/* ==================================================================================
   THE CHART PAGES' CONTROL ROWS — NEAT AT EVERY WIDTH (owner, 2026-10-22)
   ==================================================================================
   Owner, on the Vedic chart from her phone: "The buttons are cut off at the top of
   the vedic chart", and then the standard: "The buttons and chart arrangements should
   look neat regardless of screensize - whether they're stacked or side by side."

   ★ THE TWO PAGES FAILED IN OPPOSITE DIRECTIONS, FROM THE SAME MARKUP. ★ `.vtoggle-row`
   is used by /chart/vedic and /chart/chinese alike. Vedic carried a phone rule
   (`flex-wrap:nowrap; overflow-x:auto`) whose own comment said it was copying "western
   behaviour" — the single scrolling strip western ABANDONED on 2026-09-16 for hiding
   controls behind a sideways swipe nothing announced. MEASURED at 402px: "South
   Indian", "Dasamsa (D10)" and "Nodes (Rahu-Ketu)" all ran off the right edge. Chinese
   had no such rule, so it centre-wrapped instead, which is the ragged look the wheel's
   chips were just fixed for. One is cut off, the other is scattered, and neither is
   neat.

   A WRAPPING FLEX ROW WHOSE ITEMS GROW, which is the one layout that is neat at every
   width without being told the widths. Each line packs as many controls as genuinely
   fit, then they stretch to fill it — so every line is flush at both edges whatever
   the labels say, and a row holding a single control (Show today's transits) becomes
   one full-width button rather than a stranded pill. MEASURED at 390px: Chart style
   takes two lines (two across, then one), Second chart one line of two, Show three
   full-width pills, because "● Nodes (Rahu-Ketu) ☆" genuinely needs 192px and the
   card is 294 wide. A fixed `grid-template-columns: repeat(auto-fit, minmax(9.5rem,
   1fr))` was tried first and forced ALL FOUR rows to one column at 390 — 540px of
   controls above the chart, tidy and useless.
   The label owns its own line so the controls start at the same x on every row. */
.vtoggle-row{
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
  align-items: stretch;
}
/* Grow to fill the line, and shrink rather than push the page sideways. */
.vtoggle-row > *{ flex: 1 1 auto; min-width: 0; }
/* "Chart style:", "Second chart:", "Show:" — a label, not a control, and it takes a
   line of its own so every row's controls start at the same x. */
.vtoggle-row > span:first-child:not(.inline-flex){ flex: 0 0 100%; }
/* A pill and its ☆ travel together: the pill takes the room, the star keeps its 44px. */
.vtoggle-row > span.inline-flex{ display: flex; gap: 2px; }
.vtoggle-row > span.inline-flex > button:first-child{ flex: 1 1 auto; min-width: 0; }
.vtoggle-row .star-btn{ flex: 0 0 auto; }
/* A label is a label: a pill wrapping onto a second line is what made these rows
   three deep in the first place. */
.vtoggle-row > button,
.vtoggle-row > span.inline-flex > button:first-child{ white-space: nowrap; }

/* ── The AI label under every reading (counsel item 29, 2026-10-23) ──────────────────
   ★ IT IS A FOOTNOTE, NOT A WARNING. ★ No border, no icon, no amber. A reading wrapped
   in a caution reads as a product apologising for itself. Micro type, muted, sitting
   where a citation sits.
   ★ AND THE GREY IS COMPUTED, NOT PICKED. ★ --t-micro is 12px, the scale's absolute
   floor, so the large-text 3:1 exemption does not apply and WCAG 2.2 AA requires 4.5:1.
   #6b757c measures 4.52:1 against the page's #fffaf0 — the same value the 2026-09-07
   contrast pass derived by walking #8a97a0 toward black along its own hue. Do not
   lighten it to make the label recede; make it smaller nowhere, it is already on the
   floor. */
.ai-label { color: #6b757c; font-style: italic; }
:root[data-theme="dark"] .ai-label { color: #9aa6ad; }

/* ── ★ DARK MODE, ROUND SIXTEEN: THE SWEEP SIGNED IN (2026-11-10) ★ ──────────────────
   A dark color-contrast sweep, signed in as the test primary, over every a11y_audit page and
   every route_samples reader URL (52 pages) found 265 nodes on 20 pages. Earlier rounds were
   measured signed OUT or on a page list that had never included the chart detail pages.
   Every replacement below is COMPUTED: the light colour walked toward cream until it clears
   4.8:1 on both the dark card (#022a3e) and the cream veil over it (#1b3f4e), so a hue keeps
   its meaning (Chinese red, Western blue, the four elements) and only its lightness moves.

   1. The chart detail card's body is a white veil, rgba(255,255,255,.4): over the dark card
      it was #677f8b, a mid-grey on which neither cream nor navy is readable (about 150
      nodes on the house, bhava and chart pages). It joins the veil rule of round four, as do
      the notice card and the admin stack rows, which are the same veil. */
:root[data-theme="dark"] .chart-detail-card__body,
:root[data-theme="dark"] .notice-card,
:root[data-theme="dark"] table.admin-stack tr {
  background: rgba(254, 250, 224, .10);
  border-color: var(--d-border);
}
/* 2. The four elements had no dark values at all, and the `.paper_box *` rules exclude
      `elem-` on purpose so their colour survives: it survived at 2.3:1. */
:root[data-theme="dark"] {
  --elem-fire: #de9a71; --elem-earth: #a6af7c; --elem-air: #8fb0af; --elem-water: #8fb0b5;
}
/* 3. Inline text colours no earlier round listed. The warm greys go to the muted ink the
      way #6b6350 already does; each tradition's colour keeps its hue. */
:root[data-theme="dark"] [style*="color:#4a4436"], :root[data-theme="dark"] [style*="color: #4a4436"],
:root[data-theme="dark"] [style*="color:#5a5140"], :root[data-theme="dark"] [style*="color: #5a5140"],
:root[data-theme="dark"] [style*="color:#787266"], :root[data-theme="dark"] [style*="color: #787266"],
:root[data-theme="dark"] [style*="color:#6b7280"], :root[data-theme="dark"] [style*="color: #6b7280"],
:root[data-theme="dark"] [style*="color:#4b5563"], :root[data-theme="dark"] [style*="color: #4b5563"],
:root[data-theme="dark"] [style*="color:#5d7782"], :root[data-theme="dark"] [style*="color: #5d7782"] { color: var(--d-muted) !important; }
:root[data-theme="dark"] [style*="color:#c0392b"], :root[data-theme="dark"] [style*="color: #c0392b"] { color: #df9a86 !important; }
:root[data-theme="dark"] [style*="color:#2c6fb3"], :root[data-theme="dark"] [style*="color: #2c6fb3"] { color: #8dafc8 !important; }
:root[data-theme="dark"] [style*="color:#3f8a4f"], :root[data-theme="dark"] [style*="color: #3f8a4f"] { color: #88b586 !important; }
:root[data-theme="dark"] [style*="color:#4338ca"], :root[data-theme="dark"] [style*="color: #4338ca"] { color: #aca5d6 !important; }
/* 4. The chosen plan on /subscribe keeps its warm chip (cream on it is 5.55:1), but the grey
      note and the orange Privacy link inside it were 2.9:1 and 2.6:1: they take the ink. */
:root[data-theme="dark"] .brand-selected .text-gray-500,
:root[data-theme="dark"] .brand-selected .text-gray-600,
:root[data-theme="dark"] .brand-selected a { color: var(--d-ink) !important; }
/* 5. Second pass of the same sweep (75 nodes left on 9 pages), each traced in the browser:
      - /chart/all on a phone: `table.at-compare tr` becomes a card with an !important white
        veil, and the plan offers carry the same veil inline; */
:root[data-theme="dark"] table.at-compare tr,
:root[data-theme="dark"] [style*="background:rgba(255,255,255,.4)"] {
  background: rgba(254, 250, 224, .10) !important;
  border-color: var(--d-border) !important;
}
/*    - a past item on /today/chinese is faded to opacity .6, which pulls muted text on the
        dark card to #7c8984 (3.9:1); .85 keeps it visibly past and readable (6.9:1); */
:root[data-theme="dark"] [style*="opacity:.6"] { opacity: .85 !important; }
/*    - the warm chip under the Vedic chart's toggles (inline #f3ede0) and the chosen plan
        both carry grey notes, which take the ink there; */
:root[data-theme="dark"] [style*="background:#f3ede0"] .text-gray-500,
:root[data-theme="dark"] [style*="background:#f3ede0"] .text-gray-600,
:root[data-theme="dark"] .brand-selected .text-gray-700 { color: var(--d-ink) !important; }
/*    - the shared table sub-line (#4b5563, 2.0:1 on the card); */
:root[data-theme="dark"] .tbl-sub { color: var(--d-muted); }
/*    - an orange primary button keeps the dark text its own dark rule gives it (#012232,
        above): the muted ink was reaching it through the page-help section's descendant
        rule, which outranks that one; */
:root[data-theme="dark"] .btn-primary { color: #012232 !important; }
/*    - the glossary search field kept its light-theme ink on a dark field; */
:root[data-theme="dark"] #gloss-search { color: var(--d-ink) !important; }
/*    - and two inline colours no earlier round listed, walked toward cream like the rest. */
:root[data-theme="dark"] [style*="color:#5b3fa0"], :root[data-theme="dark"] [style*="color: #5b3fa0"] { color: #b3a4c3 !important; }
:root[data-theme="dark"] [style*="color:#b91c1c"], :root[data-theme="dark"] [style*="color: #b91c1c"] { color: #e0988a !important; }
