* { box-sizing:border-box; }
  body { margin:0; background:var(--cream); color:var(--ink);
         font-family:var(--font-sans); font-size:15px; line-height:1.6; }
  /* Grid and flex children default to min-width:auto, which lets a wide table
     push the whole page sideways. This is the rule the signed-in mockup was
     missing on all seventeen routes. */
  .l-wrap > *, .l-sec > * { min-width:0; }
  .l-wrap { max-width:var(--wrap); margin:0 auto; padding:0 16px; }
  .l-sec { padding:38px 0; }
  .l-alt { background:var(--surface-2); border-block:1px solid var(--line-soft); }
  .l-sub { font-size:var(--text-base); line-height:1.62; color:var(--ink-2);
           margin:0; max-width:66ch; }
  /* A link in a lede had no colour and came out in the browser's own
     -webkit-link — rgb(158,158,255) in dark, #0000EE in light. .l-sub is on
     every page, so this was one page today and any page that adds a link to a
     lede tomorrow. Set here rather than on the page that happened to have
     one. */
  .l-sub a { color:var(--gold-dk); text-underline-offset:2px; }
  /* A CENTRED SECTION, for pages whose head is centred too. Lives here rather
     than in a page's stylesheet because two clusters use it now — it was
     .ai-c in ai_astrologer.py, and pages/dasha.py picked up the class without
     the rule, so its ledes rendered left under centred heads and looked like
     an oversight. margin-inline is not optional: .l-sub caps at 66ch, so
     centring the text without centring the box leaves a centred column
     sitting on the left of the wrap.
     Only the lede. Lists, tables and cards inside a .l-ctr stay left —
     centred body copy is ragged on both edges and a centred bulleted list
     stops forming a column to scan. */
  .l-ctr .l-sub { text-align:center; margin-inline:auto; }
  /* TWO LEDES GO SIDE BY SIDE. One is fine stacked: 66ch keeps a line
     readable and the grid or panel under it carries the width. Two of them
     stacked is not — they fill the left half of the band, the right half is
     empty, and under a full-width heading with a full-width panel below it
     the pair reads as a column that has been cut off rather than as air.
     Measured at 1280: 582px of a 1180px section, 49% of it, twice over.

     A track each, so both keep the SAME measure they had — this is a layout
     fix, not a wider line. Stretching one paragraph across 1180px would give
     150 characters to a line that is already at 74.

     auto-fit rather than two fixed columns, because the home page has a pair
     inside a half-width column; there they stack again rather than becoming
     two forty-character ribbons.

     .l-sub KEEPS ITS 66ch. Each track comes out at 552px against a 582px
     cap, so the track is what governs in two columns and the cap is what
     governs when it falls back to one — which is the width it has today.
     Overriding the cap to none looked tidier and gave a 736px tablet a
     93-character line, which is the fault this rule exists to avoid.

     min(400px, 100%), NOT 400px. A minimum track wider than the container
     does not shrink — it overflows it, and at 320px the whole page scrolled
     sideways by 26px. min() caps the floor at whatever room there is. */
  .l-lede { display:grid; gap:10px 44px; margin-bottom:20px;
            grid-template-columns:repeat(auto-fit, minmax(min(400px, 100%), 1fr)); }
  .l-mb { margin-bottom:20px; }
  .l-mt { margin-top:22px; }
  .l-signin, .l-signup { text-decoration:none; white-space:nowrap; }
  .l-act-u, .l-act-a { display:flex; align-items:center; gap:8px; }
  /* The drawer carries Sign in and Sign up and nothing else now — the theme
     toggle moved to top_nav's `bar`, which is the header at every width. Its
     own display and gap belong to the component, in ui.py, so the breakpoint
     that hides it there is not overruled from here. */
  .c-drawer-a .l-signin, .c-drawer-a .l-signup {
      flex:1; min-height:44px; padding:0 15px; justify-content:center; }
  /* Date and time are one question, asked once. Both values are short enough
     to sit side by side even at 360px, and pairing them stops the form
     reading as five separate chores. */
  /* Date and time side by side, and STACKED on the narrowest phones. The two
     were always a 1fr 1fr grid; what changed is that the time trigger now
     reads 09:15 PM as four flex spans rather than one string, so its
     min-content is wider than half of a 288px card and the card grew to 311 —
     7px of sideways scroll on the page the whole site links to. minmax(0,1fr)
     alone would have kept the row and clipped the readout instead. */
  .l-dt { display:grid; grid-template-columns:1fr; gap:10px; }
  @media (min-width:360px) {
    .l-dt { grid-template-columns:minmax(0,1fr) minmax(0,1fr); }
  }
  /* Part of the hero's left column, not a band of its own. It anchors the
     bottom of that column so it finishes level with the form card. */
  /* Inside the form card the list sits under the button, separated by a rule
     rather than by more whitespace — it is what pressing the button gets you,
     so it should read as part of the button's promise. */
  .l-cardsub { margin:2px 0 14px; }
  .l-cardinc { margin-top:14px; padding-top:14px;
               border-top:1px solid var(--line-soft); }
  .l-inc { list-style:none; margin:0; padding:0; display:grid; gap:9px; }
  .l-inc li { display:flex; align-items:flex-start; gap:9px; }
  .l-inc svg { flex:none; margin-top:2px; color:var(--gold-dk); }
  .l-inc span { font-size:14px; line-height:1.45; color:var(--ink-2); }

  .l-samp { display:flex; align-items:center; gap:13px; padding:14px 16px;
            text-decoration:none; color:var(--ink); border:1px solid var(--line);
            border-radius:var(--r-card); background:var(--surface); }
  .l-samp:hover { border-color:var(--gold); background:var(--gold-bg); }
  /* Between the two-column breakpoint and 1024 the left column runs 9px past
     the card, because the headline takes an extra line before the columns are
     wide enough. Four pixels off the padding and four off the stack gap close
     it without anyone being able to tell the difference. */

  .l-samp > svg:first-child { flex:none; color:var(--gold-dk); }
  .l-samp > svg:last-child { flex:none; color:var(--ink-3); margin-left:auto; }
  .l-samp span { min-width:0; }
  .l-samp b { display:block; font-size:14.5px; font-weight:700; }
  .l-samp i { display:block; font-style:normal; font-size:12.5px;
              color:var(--ink-3); margin-top:2px; line-height:1.45; }
  .l-dt .c-pf { margin-bottom:0; }
  .l-dth { margin:6px 0 var(--space-3); }
  .l-mb2 { margin-bottom:16px; }

  .l-mark { display:block; width:34px; height:34px; flex:none; }
  .l-mark img { display:block; width:100%; height:100%; border-radius:19%; }

  /* proof row */
  .l-proof { background:var(--gold-bg); border-block:1px solid var(--line); }
  .l-pfr { display:grid; grid-template-columns:repeat(2,1fr); gap:0;
           padding:4px 0; }
  .l-pf { padding:18px 4px 18px 18px; }
  /* Hairlines, not a colour change, do the separating — and only between
     cells that are actually side by side, which changes at each breakpoint. */
  .l-pf:nth-child(2n) { border-left:1px solid var(--line); }
  .l-pf:nth-child(n+3) { border-top:1px solid var(--line); }
  .l-pf:first-child { padding-left:0; }
  .l-pf b { display:block; font-family:var(--font-serif); font-size:29px;
            line-height:1.05; font-weight:600; color:var(--gold-dk);
            letter-spacing:-.015em; }
  .l-pf span { display:block; font-size:13.5px; font-weight:650; margin-top:5px;
               color:var(--ink); }
  .l-pf i { display:block; font-style:normal; font-size:12.5px; color:var(--ink-3);
            margin-top:2px; }
  @media (min-width:760px) {
    .l-pfr { grid-template-columns:repeat(4,1fr); }
    .l-pf:nth-child(n+3) { border-top:0; }
    .l-pf:nth-child(n+2) { border-left:1px solid var(--line); }
  }

  /* panchang */
  .l-panch { display:grid; gap:8px; grid-template-columns:repeat(2,1fr); }
  .l-pc { padding:13px 14px; border:1px solid var(--line); border-radius:var(--r-in);
          background:var(--surface); }
  .l-pc span { display:block; font-family:var(--font-mono); font-size:9px;
               letter-spacing:.13em; color:var(--ink-3); }
  .l-pc b { display:block; font-size:15px; margin-top:5px; }
  .l-pc i { display:block; font-style:normal; font-size:12px; color:var(--ink-3); }
  .l-pc.c-t-good b { color:var(--good); } .l-pc.c-t-bad b { color:var(--bad); }
  @media (min-width:700px) { .l-panch { grid-template-columns:repeat(3,1fr); } }
  @media (min-width:1000px) { .l-panch { grid-template-columns:repeat(6,1fr); } }

  /* rashifal */
  .l-rsg { display:grid; gap:7px; grid-template-columns:repeat(3,1fr); }
  .l-rs { display:flex; flex-direction:column; align-items:center; gap:1px;
          min-height:98px; justify-content:center; padding:12px 5px;
          border:1px solid var(--line); border-radius:var(--r-in);
          background:var(--surface); text-decoration:none; color:var(--ink);
          text-align:center; }
  .l-rs:hover { border-color:var(--gold); background:var(--gold-bg); }
  .l-rs .c-sym { color:var(--gold-dk); margin-bottom:4px; }
  .l-rs b { font-size:12.5px; font-weight:650; }
  .l-rs i { font-style:normal; font-size:10.5px; color:var(--ink-3); }
  .l-rs u { text-decoration:none; font-size:12px; color:var(--gold-dk); }
  @media (min-width:560px) { .l-rsg { grid-template-columns:repeat(4,1fr); } }
  @media (min-width:900px) { .l-rsg { grid-template-columns:repeat(6,1fr); } }

  /* tools */
  .l-tools { display:grid; gap:12px; }
  .l-ti { display:grid; place-items:center; width:40px; height:40px;
          border-radius:11px; color:var(--gold-dk); background:var(--gold-bg);
          margin-bottom:11px; }
  .l-tl { display:flex; flex-wrap:wrap; gap:6px; margin-top:13px; }
  .l-tl a { display:inline-flex; align-items:center; min-height:44px;
            font-size:12.5px; font-weight:600; padding:0 14px;
            border-radius:var(--r-pill); color:var(--ink-2);
            border:1px solid var(--line); background:var(--surface);
            text-decoration:none; }
  .l-tl a:hover { color:var(--gold-dk); border-color:var(--gold);
                  background:var(--gold-bg); }
  @media (min-width:720px) { .l-tools { grid-template-columns:repeat(2,1fr); } }

  /* astrologers */
  /* .h-astro in home.py sets display on top of this and is the only caller,
     so anything layout-ish added here is dead. The phone rail lives there. */
  .l-astro { display:grid; gap:11px; grid-template-columns:repeat(1,1fr); }
  .l-ah { display:flex; align-items:center; gap:11px; }
  .l-an { display:block; font-size:15px; font-weight:700; }
  .l-as { display:block; font-size:12.5px; color:var(--ink-3); }
  .l-am { display:flex; gap:5px; margin-top:12px; }
  @media (min-width:600px) { .l-astro { grid-template-columns:repeat(2,1fr); } }
  @media (min-width:1000px) { .l-astro { grid-template-columns:repeat(4,1fr); } }

  /* The unknown-birth-time escape hatch belongs inside the time picker, where
     someone reaches it at the exact moment they realise they cannot answer. */
  .l-unk { margin-top:14px; padding-top:12px; border-top:1px solid var(--line-soft); }
  .l-faq { margin-top:16px; }


/* ── page head ────────────────────────────────────────── */
.s-crumb { padding-top:16px; }
.s-top { padding:22px 0 6px; }
.s-head { max-width:64ch; }
/* With a form beside it the header is the full width and the two columns
   share it, so the reading underneath is not squeezed into a rail. */
.s-head-2 { max-width:none; display:grid; gap:26px;
            grid-template-columns:minmax(0,1fr); align-items:start; }
.s-head-2 > * { min-width:0; }
@media (min-width:960px) {
  .s-head-2 { grid-template-columns:minmax(0,1fr) minmax(0,380px); }
}
.s-head-t { max-width:64ch; }
.s-eyebrow { display:inline-block; font-family:var(--font-mono); font-size:10px;
             letter-spacing:.14em; text-transform:uppercase; color:var(--gold-dk);
             padding:5px 10px; border-radius:var(--r-pill);
             background:var(--gold-bg); margin-bottom:14px; }
.s-h1 { font-family:var(--font-serif); font-size:clamp(27px,5.2vw,42px);
        line-height:1.08; letter-spacing:-.021em; font-weight:600;
        margin:0 0 13px; text-wrap:balance; }
.s-h1 em { font-style:normal; color:var(--gold-dk); }
.s-dek { font-size:var(--text-md); line-height:1.62; color:var(--ink-2);
         margin:0; max-width:58ch; }
/* What the page gives back, named. See page_head() for why it is only on the
   fourteen with a full birth-details form. */
.s-ret { margin-top:18px; max-width:58ch; }
.s-ret ul { margin:8px 0 0; padding:0; list-style:none; display:grid;
            gap:5px; }
.s-ret li { position:relative; padding-left:18px; font-size:14px;
            line-height:1.55; color:var(--ink-2); }
.s-ret li::before { content:''; position:absolute; left:2px; top:9px;
                    width:5px; height:5px; border-radius:50%;
                    background:var(--gold-solid); }
@media (min-width:960px) {
  .s-head-fill { align-items:stretch; }
  .s-head-fill > .s-head-t { display:flex; flex-direction:column; }
  .s-head-fill .s-meta { margin-top:auto; padding-top:16px; }
}
/* The facts a dek would otherwise carry as adjectives. Mono, so they read as
   data rather than as more sentence. */
/* THE CENTRED HEAD, for pages that are not asking for a birth time. The
   default sits beside a form card and runs to 64ch down the left; a page
   whose top is a choice rather than a form leaves that whole right-hand
   column empty, and the text reads as the caption of something that failed
   to load. Two pages want this — the tarot hub, whose top half is six
   readings, and the astrologer pages, whose top half is a roster — so it
   lives here rather than being written twice. */
.s-head-c { max-width:700px; margin-inline:auto; text-align:center; }
.s-head-c .s-eyebrow { font-size:11.5px; letter-spacing:.09em; font-weight:700;
          border:1px solid var(--gold-lt); padding:5px 13px; margin-bottom:16px; }
.s-head-c .s-h1 { font-size:clamp(30px,5.6vw,46px); }
.s-head-c .s-dek { margin-inline:auto; max-width:62ch; }
.s-head-c .s-meta { justify-content:center; }

.s-meta { display:flex; flex-wrap:wrap; gap:8px 18px; margin-top:16px;
          font-family:var(--font-mono); font-size:11px; letter-spacing:.04em;
          color:var(--ink-3); }
.s-meta span { display:flex; align-items:center; gap:7px; }
.s-meta span + span::before { content:''; width:3px; height:3px; flex:none;
          border-radius:50%; background:var(--line); margin-left:-11px; }

/* ── related ──────────────────────────────────────────── */
.s-rel { display:grid; gap:10px; grid-template-columns:1fr; }
@media (min-width:620px) { .s-rel { grid-template-columns:repeat(2,1fr); } }
@media (min-width:960px) { .s-rel { grid-template-columns:repeat(3,1fr); } }
.s-rel > * { min-width:0; }
.s-rel-i { display:flex; align-items:flex-start; gap:12px; padding:14px 16px;
           min-height:44px; text-decoration:none; color:var(--ink);
           background:var(--surface); border:1px solid var(--line);
           border-radius:var(--r-card); }
.s-rel-i:hover { border-color:var(--gold); background:var(--gold-bg); }
.s-rel-k { flex:none; display:grid; place-items:center; width:32px; height:32px;
           border-radius:9px; background:var(--gold-bg); color:var(--gold-dk); }
.s-rel-x { min-width:0; display:flex; flex-direction:column; line-height:1.4; }
.s-rel-x b { font-size:14px; font-weight:650; }
.s-rel-x i { font-style:normal; font-size:12.5px; color:var(--ink-3);
             margin-top:2px; }

/* ── closing CTA ──────────────────────────────────────── */
.s-cta { display:grid; gap:20px; align-items:center; padding:28px;
         border-radius:var(--r-card); border:1px solid var(--gold-lt);
         background:
           radial-gradient(120% 140% at 88% 0%, var(--gold-bg) 0%, transparent 64%),
           var(--surface); }
@media (min-width:820px) {
  .s-cta { grid-template-columns:1fr auto; gap:34px; padding:32px 36px; } }
.s-cta > * { min-width:0; }
.s-cta h2 { font-family:var(--font-serif); font-size:clamp(21px,3.4vw,28px);
            line-height:1.16; letter-spacing:-.018em; font-weight:600;
            margin:0 0 9px; text-wrap:balance; }
.s-cta p { font-size:var(--text-base); line-height:1.6; color:var(--ink-2);
           margin:0; max-width:52ch; }
.s-cta-b { display:flex; flex-wrap:wrap; gap:10px; }
.s-cta-b > * { flex:1 1 auto; }
.s-cta-alt { display:inline-flex; align-items:center; justify-content:center;
             gap:8px; text-decoration:none; white-space:nowrap; }
.s-cta-alt svg { color:var(--gold-dk); }

  /* ── the welcome gift ── */
  .d-bon { position:fixed; inset:0; z-index:60; display:flex;
           align-items:center; justify-content:center; padding:20px; }
  .d-bon[hidden] { display:none; }
  .d-bon-x { position:absolute; inset:0; background:rgba(0,0,0,.5); }
  .d-bon-c { position:relative; width:100%; max-width:390px; text-align:center;
             padding:26px 26px 20px; border-radius:18px;
             border:1px solid var(--line); background:var(--surface);
             box-shadow:0 18px 50px rgba(0,0,0,.22);
             max-height:calc(100dvh - 40px); overflow-y:auto; }
  .d-bon-g { display:inline-flex; align-items:center; justify-content:center;
             width:62px; height:62px; border-radius:50%;
             background:var(--gold-bg); color:var(--gold-dk);
             margin-bottom:13px; }
  .d-bon-c h2 { font-family:var(--font-serif); font-size:23px; font-weight:600;
                margin:0; color:var(--ink); letter-spacing:-.01em; }
  .d-bon-s { margin:5px 0 16px; font-size:13.5px; color:var(--ink-2); }
  .d-bon-amt { padding:15px; border-radius:var(--r-card);
               background:var(--gold-bg); margin-bottom:16px; }
  .d-bon-amt span { display:block; font-family:var(--font-mono);
                    font-size:10px; letter-spacing:.14em; color:var(--ink-3);
                    text-transform:uppercase; }
  .d-bon-amt b { display:block; margin:5px 0 4px; font-family:var(--font-serif);
                 font-size:38px; font-weight:600; line-height:1;
                 color:var(--gold-dk); }
  .d-bon-amt i { font-style:normal; font-size:12px; color:var(--ink-2); }
  .d-bon-l { list-style:none; margin:0 0 18px; padding:0; display:grid;
             gap:8px; text-align:left; }
  .d-bon-l li { position:relative; padding-left:20px; font-size:13px;
                color:var(--ink-2); }
  .d-bon-l li::before { content:''; position:absolute; left:4px; top:.5em;
                width:6px; height:6px; border-radius:50%;
                background:var(--gold-dk); }
  .d-bon-go { position:relative; width:100%; }
  .d-bon-go .d-bon-lb { transition:opacity .12s linear; }
  .d-bon-go[aria-busy="true"] .d-bon-lb { opacity:0; }
  .d-bon-go[aria-busy="true"] { pointer-events:none; }
  .d-bon-sp { position:absolute; top:50%; left:50%; width:20px; height:20px;
              margin:-10px 0 0 -10px; opacity:0; pointer-events:none; }
  .d-bon-go[aria-busy="true"] .d-bon-sp { opacity:1; }
  .d-bon-no { display:block; width:100%; margin-top:9px; font:inherit;
              font-size:12.5px; padding:7px; border:0; background:none;
              color:var(--ink-3); cursor:pointer; }
  .d-bon-no:hover { color:var(--ink-2); text-decoration:underline; }
  .d-bon-e { margin:9px 0 0; font-size:12.5px; color:var(--bad); }
  .d-bon-e[hidden] { display:none; }

  /* The row for whoever closed it. Money nobody has taken should not be
     invisible because a modal was dismissed once. */
  .d-bonr { display:flex; align-items:center; gap:13px; flex-wrap:wrap;
            padding:13px 16px; margin:0 0 18px;
            border:1px solid var(--gold); border-radius:var(--r-card);
            background:var(--gold-bg); }
  .d-bonr[hidden] { display:none; }
  .d-bonr-g { flex:none; color:var(--gold-dk); display:flex; }
  .d-bonr-t { flex:1 1 210px; min-width:0; font-size:13.5px; color:var(--ink); }
  .d-bonr-t b { font-weight:650; }
  .d-bonr-x { flex:none; font:inherit; font-size:12.5px; border:0;
              background:none; color:var(--ink-3); cursor:pointer;
              padding:6px 8px; }
  .d-bonr-x:hover { color:var(--ink-2); text-decoration:underline; }

  /* ── shell ── */
  .d-shell { display:grid; grid-template-columns:248px minmax(0,1fr);
             min-height:100vh; background:var(--cream); }
  .d-col2 { min-width:0; }
  .d-rail { background:var(--rail); border-right:1px solid var(--line);
            padding:16px 12px 12px; display:flex; flex-direction:column;
            gap:3px; position:sticky; top:0; height:100vh; overflow-y:auto; }
  .d-brand { display:flex; align-items:center; gap:10px; padding:4px 8px 16px;
             color:inherit; text-decoration:none;
             font-family:var(--font-serif); font-size:16px; font-weight:600; }
  .d-grp { margin-top:14px; padding:0 8px 6px; }
  .d-grp b { display:block; font-family:var(--font-mono); font-size:9.5px;
             letter-spacing:.14em; text-transform:uppercase; font-weight:600;
             color:var(--ink-3); }
  .d-grp i { display:block; font-family:var(--font-serif); font-style:italic;
             font-size:11.5px; color:var(--ink-3); }
  .d-nav { display:flex; align-items:center; gap:10px; padding:9px 10px;
           border-radius:var(--r-in); font-size:13.5px; font-weight:500;
           color:var(--ink-2); text-decoration:none; min-height:40px; }
  .d-nav:hover { background:var(--surface); color:var(--ink); }
  .d-nav .c-ico { width:18px; height:18px; flex:none; }
  .d-nl { flex:1; min-width:0; }
  .d-on { background:var(--pill-ink); color:var(--pill-text); font-weight:650; }
  .d-tail { font-family:var(--font-mono); font-size:10px; padding:1px 5px;
            border-radius:5px; background:var(--gold-bg); color:var(--gold-dk); }
  .d-on .d-tail { background:var(--surface); color:var(--ink); }
  .d-acct { position:relative; margin-top:auto; padding-top:12px;
            border-top:1px solid var(--line); }
  .d-acct-c { display:flex; align-items:center; gap:9px; padding:9px;
              border:1px solid var(--line); border-radius:var(--r-in);
              background:var(--surface); text-decoration:none;
              cursor:pointer; }
  .d-acct-c:hover { border-color:var(--gold-lt); }
  .d-acct-c .c-ico { flex:none; color:var(--ink-3);
                     transition:transform .16s; }
  #d-ac-o:checked ~ .d-acct-c .c-ico { transform:rotate(180deg); }
  /* UPWARD. It is the last thing in the rail with the viewport floor under
     it, so a menu hanging below it would open off the bottom of the screen.
     Same checkbox the profile switcher uses rather than the prototype's
     :hover, which on a touch screen is a menu that opens and will not close. */
  .d-ac-m { display:none; position:absolute; left:0; right:0;
            bottom:calc(100% + 7px); z-index:40; padding:6px;
            border-radius:var(--r-card); background:var(--surface);
            border:1px solid var(--line);
            box-shadow:0 18px 44px -18px rgba(0,0,0,.5); }
  #d-ac-o:checked ~ .d-ac-m { display:block; }
  .d-ac-x { display:none; }
  #d-ac-o:checked ~ .d-ac-x { display:block; position:fixed; inset:0;
                              z-index:35; }
  .d-ac-i { display:flex; align-items:center; gap:11px; width:100%;
            padding:9px 10px; border:0; border-radius:var(--r-in);
            background:none; cursor:pointer; text-align:left; font:inherit;
            font-size:13px; color:var(--ink); text-decoration:none; }
  .d-ac-i:hover { background:var(--surface-2); }
  .d-ac-i .c-ico { flex:none; color:var(--ink-3); }
  .d-ac-sep { height:1px; background:var(--line); margin:5px 8px; }
  /* No icon, and the only red thing in the rail: it is the one row here that
     does something rather than going somewhere. */
  .d-ac-out { padding-left:37px; color:var(--bad); font-weight:600; }
  @media (prefers-reduced-motion:reduce) {
    .d-acct-c .c-ico { transition:none; }
  }
  .d-av { width:30px; height:30px; border-radius:50%; flex:none;
          display:grid; place-items:center; font-size:12px; font-weight:700;
          background:var(--gold-solid); color:var(--gold-solid-text); }
  .d-who { min-width:0; flex:1; }
  .d-who b { display:block; font-size:13px; color:var(--ink); }
  .d-who i { display:block; font-style:normal; font-size:11.5px;
             color:var(--ink-3); }

  /* ── top bar ── */
  .d-top { display:flex; align-items:center; gap:12px; padding:11px 22px;
           border-bottom:1px solid var(--line); background:var(--cream);
           position:sticky; top:0; z-index:5; }
  .d-crumb { font-size:13px; color:var(--ink-3); min-width:0; }
  .d-crumb b { color:var(--ink); font-weight:600; }
  .d-tb { margin-left:auto; display:flex; align-items:center; gap:8px; }
  .d-chip { display:flex; align-items:center; gap:6px; font-size:12.5px;
            padding:6px 11px; border:1px solid var(--line);
            border-radius:var(--r-pill); background:var(--surface); }
  .d-chip i { font-style:normal; font-family:var(--font-mono); font-size:9.5px;
              letter-spacing:.05em; padding:1px 5px; border-radius:4px;
              background:var(--gold-bg); color:var(--gold-dk); }
  .d-wallet { display:flex; align-items:center; gap:7px; font-size:13px;
              font-weight:700; padding:6px 7px 6px 12px; text-decoration:none;
              border-radius:var(--r-pill); border:1px solid var(--gold);
              background:var(--gold-bg); color:var(--gold-dk); }
  .d-plus { display:grid; place-items:center; width:18px; height:18px;
            border-radius:50%; background:var(--gold-solid);
            color:var(--gold-solid-text); font-size:12px; line-height:1; }
  .d-burger { display:none; }
  .d-scrim { display:none; }

  /* ── main ── */
  .d-main { padding:26px 22px 56px; max-width:var(--wrap); }
  .d-h1 { font-family:var(--font-serif); font-size:clamp(27px,3.2vw,42px);
          line-height:1.08; letter-spacing:-.015em; font-weight:600;
          margin:0 0 9px; text-wrap:balance; }
  .d-h1 em { font-style:italic; color:var(--gold-dk); }
  .d-sub { display:flex; align-items:baseline; gap:8px; margin:0;
           color:var(--ink-2); font-size:14.5px; line-height:1.6;
           max-width:640px; }
  .d-sub b { color:var(--ink); font-weight:650; }
  /* NOT .d-dot. This CSS used to live inside /home/ where a page owned its
     own class names; moving it into lib/ made every name here global, and
     pages/views/biorhythm.py has been drawing its chart's data points with
     a .d-dot for months. The shell is the newcomer, so the shell renames. */
  /* Prose links. Scoped to <p> on purpose: every signed-in page puts the
     odd link in a caption or an empty-state sentence, and without this they
     render browser-default blue in the middle of a gold page. Anchors that
     are structure rather than prose — rail items, rashi tiles, roster cards,
     the profile menu — are never inside a paragraph, so they keep their own
     styling and this cannot reach them. */
  .d-main p a { color:var(--gold-dk); text-underline-offset:2px; }
  .d-main p a:hover { color:var(--gold); }

  .d-live { width:7px; height:7px; border-radius:50%; background:var(--good);
           flex:none; }
  .d-mk { display:flex; align-items:baseline; gap:10px; margin:32px 0 13px; }
  .d-mk0 { margin:0 0 13px; }
  .d-mkn { font-family:var(--font-mono); font-size:12px; color:var(--gold-dk); }
  /* An h2/h3 now, so the browser's own heading margin and 1.5em size have
     to go — the row is a flex baseline and a UA margin would shove it. */
  .d-mkt { font-family:var(--font-mono); font-size:12px; letter-spacing:.13em;
           text-transform:uppercase; font-weight:600; margin:0;
           color:inherit; }
  .d-mkd { font-family:var(--font-serif); font-style:italic; font-size:13px;
           color:var(--ink-3); }
  .d-card { background:var(--surface); border:1px solid var(--line);
            border-radius:var(--r-card); padding:20px; }
  .d-row { display:grid; gap:15px; }
  /* THE LEFT CARD SETS THE HEIGHT AND BEST HOURS FITS ITSELF INTO IT.
     Stretching the pair to their tallest is not the same thing: the tallest
     is whichever has more to say, so the void simply moves to the other
     column — which is exactly what happened when Best hours grew from three
     rows to seven. The slot is an empty positioned box, so it contributes
     nothing to the row; the card fills the slot absolutely and gives up
     rows to its scroller instead of giving up its shape.

     min-height so the pair cannot collapse when the left card is in one of
     its short states — no birth details, or a chart that would not load. */
  @media (min-width:980px) {
    .d-2 { grid-template-columns:1.5fr 1fr; }
    .d-hslot { position:relative; min-height:280px; }
    .d-hslot > .d-hrs { position:absolute; inset:0; }
  }
  /* On one column there is nothing to match, so the card is its own height
     and the whole list is simply there. */
  @media (max-width:979.98px) { .d-hslot { display:contents; } }
  .d-read { font-size:14.5px; line-height:1.65; color:var(--ink-2); margin:0; }
  .d-read b { color:var(--ink); font-weight:650; }

  /* ── profile switcher ── */
  .d-pf { position:relative; }
  .d-pf-t { display:flex; align-items:center; gap:9px; cursor:pointer;
            padding:5px 9px 5px 6px; border:1px solid var(--line);
            border-radius:var(--r-pill); background:var(--surface);
            max-width:230px; }
  .d-pf-t:hover { border-color:var(--gold-lt); }
  .d-pf-n { min-width:0; text-align:left; }
  .d-pf-n b { display:block; font-size:12.5px; font-weight:650; color:var(--ink);
              white-space:nowrap; overflow:hidden; text-overflow:ellipsis; }
  .d-pf-n i { display:block; font-style:normal; font-family:var(--font-mono);
              font-size:10px; color:var(--ink-3); }
  .d-pf-t .c-ico { flex:none; color:var(--ink-3); }
  .d-pf-m { display:none; position:absolute; top:calc(100% + 7px); left:0;
            z-index:40; min-width:250px; padding:6px; border-radius:var(--r-card);
            background:var(--surface); border:1px solid var(--line);
            box-shadow:0 18px 44px -18px rgba(0,0,0,.5); }
  /* The input sits INSIDE .d-top, so the menu and the catcher are its
     siblings there — not descendants of .d-shell, which is what the
     first version reached for and never matched. */
  #d-pf-o:checked ~ .d-pf .d-pf-m { display:block; }
  /* A full-screen catcher so the next click anywhere closes it — without one
     the menu stays open behind whatever you clicked next. */
  .d-pf-x { display:none; }
  #d-pf-o:checked ~ .d-pf-x { display:block; position:fixed;
            inset:0; z-index:35; }
  .d-pf-i { display:flex; align-items:center; gap:9px; width:100%;
            padding:8px 9px; border:0; border-radius:var(--r-in);
            background:none; cursor:pointer; text-align:left;
            text-decoration:none; font:inherit; }
  .d-pf-i:hover { background:var(--surface-2); }
  .d-pf-on { background:var(--gold-bg); }
  .d-pf-on .d-pf-n b { color:var(--gold-dk); }
  .d-pf-add { border-top:1px solid var(--line-soft); border-radius:0 0 9px 9px;
              margin-top:4px; padding-top:11px; }
  .d-pf-add .d-av { background:var(--surface-2); color:var(--ink-2); }
  /* max-width:150px used to sit here too and had never once applied: the
     phone block further down sets max-width:none at the same specificity and
     later in the file, so it won every time. The date of birth is what this
     rule is actually for. */
  @media (max-width:560px) { .d-pf-n i { display:none; } }

  /* ── waiting ── */
  /* A grey block the shape of the answer, not a spinner in the middle of an
     empty page: the layout is already correct, so nothing jumps when the real
     values land. */
  /* A SWEEP, NOT A PULSE. The old rule faded --line-soft in and out, and
     --line-soft sits 1.07:1 against the card it lies on in dark — so the bar
     was invisible at its brightest and more invisible at its dimmest, which
     reads as a page that has finished loading and has nothing to say. The
     band travelling left to right is the part that says "still coming"; the
     bar underneath it is what says "and it will be about this big". */
  .d-sk { display:block; min-height:1em; width:100%; border-radius:6px;
          /* The &nbsp; that gives the span its height must not be seen. */
          color:transparent; user-select:none;
          background-color:var(--sk);
          background-image:linear-gradient(100deg, rgba(0,0,0,0) 34%,
                           var(--sk-hi) 50%, rgba(0,0,0,0) 66%);
          background-size:280% 100%; background-repeat:no-repeat;
          animation:d-sheen 1.45s linear infinite; }
  .d-sk + .d-sk { margin-top:7px; }
  @keyframes d-sheen { from { background-position:170% 0; }
                       to { background-position:-70% 0; } }
  /* Stationary, not still-but-invisible: reduced motion keeps the bar and
     drops only the travel. */
  @media (prefers-reduced-motion:reduce) {
    .d-sk { animation:none; background-image:none; }
  }

  /* IN A FLEX PARENT A SKELETON COLLAPSES. .d-sub is a flex row, so a
     `<span class="d-sk">&nbsp;</span>` shrink-wrapped its one space and came
     out 3px wide — five screens opened with a crumb of grey under the
     headline where a line of type was about to appear. Width here, and a
     shorter one for the sub, because a full-column bar promises a paragraph
     the line will not deliver. */
  .d-sub .d-sk { flex:0 0 auto; width:min(26ch, 100%); }

  /* Seen only when a live reading could not be fetched and the page is
     showing what was built into it. It is a caveat on real data, not an
     error — amber, in the subtitle, not a banner across the page. It lives
     here because every signed-in page that reads a dated snapshot needs the
     same sentence, and three copies would drift. */
  .d-stale { color:var(--mid); font-size:13px; }
  .d-stale b { color:var(--mid); font-weight:650; }
  /* Same collapse, in the top bar. The profile chip and the wallet pill both
     size to their contents, so a width:100% child resolves against nothing —
     3px and 13px respectively. These are the widths a name and a balance
     actually take, so the chrome does not resize when they arrive. */
  .d-pf-n .d-sk { width:9ch; }
  .d-wallet .d-sk { width:4.5ch; }

  /* ── the rail becomes a drawer ── */
  @media (max-width:900px) {
    .d-shell { grid-template-columns:minmax(0,1fr); }
    .d-burger { display:grid; place-items:center; width:44px; height:44px;
                flex:none; cursor:pointer; border:1px solid var(--line);
                border-radius:10px; background:var(--surface);
                color:var(--ink-2); }
    .d-rail { position:fixed; top:0; left:0; bottom:0; width:264px; z-index:40;
              transform:translateX(-100%); transition:transform .2s ease; }
    #d-menu:checked ~ .d-shell .d-rail { transform:none; }
    #d-menu:checked ~ .d-shell .d-scrim { display:block; position:fixed;
              inset:0; z-index:30; background:rgba(0,0,0,.45); }
    .d-main { padding:20px 16px 48px; }
    .d-top { padding:10px 16px; }
  }
  /* ── ONE ROW ON A PHONE ──
     The bar holds a burger, the profile chip, a breadcrumb, a date, a wallet
     and the theme toggle: 473px of controls in 358px at 390. Letting it wrap
     kept everything on screen and made the header 116px of a 844px phone —
     two rows, the second one shoved to the right by margin-left:auto with
     124px of dead space beside it. It read as a header that had come apart,
     not one that had been designed.
     Two things go instead of wrapping. The breadcrumb, because it names the
     page a reader is looking at. And the date chip, because it is the only
     thing on the bar that is not a control — every page that turns on the
     date says so in its own heading, and what a phone needs up here is the
     way out (burger), who this is cast for (profile), and the money.
     What is left is 191px of fixed controls, so the profile takes the rest
     and truncates. That is why .d-pf gets min-width:0: it is a flex item, its
     min-width defaults to its content, and without this the name would push
     the wallet off the edge rather than ellipsing — the same trap that gave
     the roster card a 326px floor. */
  @media (max-width:620px) {
    .d-top { flex-wrap:nowrap; gap:8px; }
    .d-crumb { display:none; }
    .d-chip { display:none; }
    .d-tb { margin-left:auto; flex:none; gap:8px; }
    /* FITS ITS NAME, and shrinks only when the row runs out. flex defaults to
       0 1 auto, so the chip is as wide as its contents and no wider; the
       max-width is the ceiling a long name truncates at, and min-width:0 is
       what lets it give way at 320 rather than widening the page.
       It used to be flex:1 1 auto with width:100%, which stretched it across
       whatever the bar had spare — a chip holding "Rohit Singh" sat 167px
       wide with the name floating in the left third of it. */
    .d-pf { min-width:0; }
    .d-pf-t { max-width:190px; }
  }
  @media (prefers-reduced-motion:reduce) { .d-rail { transition:none; } }
  /* ── 11px IS THE FLOOR ON A PHONE, EVERYWHERE INSIDE THE SHELL ──
     Thirty-six rules across four files set text between 8.5px and 10.5px on
     these pages: axis labels, table headings, row labels, the segment names
     inside the dasha bars, date pills, the notification-time caption. Each was
     a fair call at 1280px, where the same component has half again the room.
     On a phone they are collectively most of the words on the screen, and
     8.5px is not a size anybody reads on a bus.
     ONE BLOCK, NOT THIRTY-SIX EDITS, because scattering the same override
     through four files is how the thirty-seventh gets written at 9px and
     nobody notices. .d-main prefixes every selector because page CSS is
     emitted AFTER the shell's, so this needs specificity rather than position
     to win — and from here it wins wherever the base rule lives.
     Anything that genuinely cannot hold 11px gets room made for it instead of
     being allowed back under the floor.
     THREE OF THESE WERE INVISIBLE LOCALLY. .d-fx (the dashboard's tithi /
     nakshatra / yoga / karana row) and .c-tl-now (the "now" marker on the day
     timeline) only render when there is live panchang data, and a local build
     runs with COSMIQE_STALE_OK=1. They were found by running the check
     against production after deploying, which is now the only way to see
     them — worth remembering before trusting a local pass on these pages. */
  @media (max-width:640px) {
    .d-main .c-lb-i i,
              .d-main .c-pf-l,
              .d-main .c-scl-l,
              .d-main .c-scl-t,
              .d-main .c-scl-tag,
              .d-main .c-scl-v,
              .d-main .c-table th,
              .d-main .c-tl-c b,
              .d-main .c-tl-lbl,
              .d-main .c-tl-night i,
              .d-main .c-tl-now i,
              .d-main .d-fx s,
              .d-main .d-fx i,
              .d-main .c-tl-t i,
              .d-main .d-flc s,
              .d-main .d-lk s,
              .d-main .d-ovl,
              .d-main .d-rate,
              .d-main .d-scl,
              .d-main .dl-lab i,
              .d-main .dl-lab s,
              .d-main .dl-life-t,
              .d-main .dl-ls b,
              .d-main .dl-ls i,
              .d-main .dl-seg,
              .d-main .dl-sg-h s,
              .d-main .dl-sg-r s,
              /* .dl-tbl th was here. The dasha table stacks on a phone and
                 hides its heads, so there is no width at which this rendered
                 — see the block at the end of dasha_app.py. */
              .d-main .h-dl-r .h-dl-pc,
              .d-main .h-f-b u,
              .d-main .h-f-bn,
              .d-main .h-f-hint,
              .d-main .h-f-l,
              .d-main .h-f-luck-t,
              .d-main .h-f-tile i,
              .d-main .h-trend,
              /* ── THE THREE THAT MIX THEIR OWN TEXT WITH A CHILD ──
                 Missed for a reason in the checks rather than in the page:
                 shell-mobile.mjs only measured elements with no element
                 children at all, so anything shaped like
                 <text>Finance<tspan>87</tspan></text> or
                 <span><svg/>Tithi<i>…</i></span> was never looked at, on five
                 horoscope pages and panchang. The eyebrow is 10.5, the anga
                 heading 10, and the radar label 8.5 in an SVG that renders it
                 at 9.0 on a 320 screen. */
              .d-main .h-f-eyebrow,
              .d-main .h-radar-l,
              .d-main .p-anga-h,
              .d-main .p-ch-h,
              .d-main .p-per-h,
              .d-main .p-sum i,
              .d-main .st-pill-off i { font-size:11px; }
  }

/* WHAT AN ASTROLOGER'S ANSWER LOOKS LIKE.
 *
 * Everything here is sized to sit INSIDE a chat bubble, not on a page. The
 * bubble is 14px/1.62 and about 660px at its widest, so a heading that would
 * be right in an article is a shout in here: the three levels land at 15,
 * 14.5 and 14px and separate themselves by weight and space rather than size.
 *
 * Colours are tokens, never the app's literals. MessageComponents.tsx paints
 * bold #111827 and blockquote #8B5CF6, which are a light-theme phone's
 * colours — dropped into this site's dark theme they are near-black text on
 * near-black ground and a purple that belongs to no palette here.
 */
.c-md > :first-child { margin-top:0; }
.c-md > :last-child { margin-bottom:0; }
.c-md p { margin:0 0 9px; }
/* The one thing the agents are told to do on EVERY answer: bold the planets,
   signs, nakshatras and dashas. It carries most of the scanability, so it is
   a real weight step and a colour step, not one or the other. */
.c-md strong { font-weight:700; color:var(--ink); }
.c-md em { font-style:italic; }
.c-md code { font-family:var(--font-mono); font-size:.92em;
             padding:1px 5px; border-radius:5px;
             background:var(--surface-2); color:var(--ink-2); }

/* NOT <h3>. These come out of a language model, one turn at a time, and real
   headings would let generated text rewrite the page's outline — a screen
   reader would hear a document whose structure changes with every answer.
   Styled as headings, absent from the outline, which is also what the app
   does: MessageComponents.tsx renders them as styled <Text>. */
.c-md b.c-md-h { display:block; font-weight:700; color:var(--ink);
                 line-height:1.4; margin:14px 0 6px; }
.c-md b.c-md-h1 { font-size:15px; }
.c-md b.c-md-h2 { font-size:14.5px; }
.c-md b.c-md-h3 { font-size:14px; letter-spacing:.01em; }

.c-md ul, .c-md ol { margin:0 0 9px; padding-left:20px; }
.c-md li { margin:0 0 4px; }
.c-md li:last-child { margin-bottom:0; }
.c-md ul { list-style:none; padding-left:16px; }
/* A marker drawn rather than a disc, so it can take the accent and sit on the
   text's first line at this line-height. */
.c-md ul > li { position:relative; padding-left:14px; }
.c-md ul > li::before { content:''; position:absolute; left:0; top:.62em;
                        width:5px; height:5px; border-radius:50%;
                        background:var(--gold-dk); }

/* Reserved by the prompt for Sade Sati, Mangal Dosha and the like — the
   sentences somebody most needs to not skim past. */
/* --gold-dk on the rule, not --gold: against this block's own tinted ground
   --gold measures 2.47:1 in the light theme — under the 3:1 a non-text
   element carrying meaning needs, and it reads as a bar that is barely
   there. --gold-dk is 4.88 light and 9.92 dark. */
.c-md blockquote { margin:9px 0; padding:8px 13px;
                   border-left:3px solid var(--gold-dk);
                   background:var(--gold-bg); border-radius:0 6px 6px 0;
                   color:var(--ink-2); }
.c-md blockquote p:last-child { margin-bottom:0; }
.c-md hr { height:1px; border:0; margin:13px 0;
           background:var(--line-soft); }

  /* Own prefix cv-. */

  /* The intro line as designed: one plain line under the heading — no live
     dot, and not held to the shell's 640px, which wrapped it in two. */
  .cv-main .d-sub { max-width:none; }
  .cv-main .d-sub .d-live { display:none; }

  /* ── picking an astrologer ── */
  .cv-grid { display:grid; gap:10px;
             grid-template-columns:repeat(auto-fill,minmax(240px,1fr)); }
  .cv-card { font:inherit; text-align:left; cursor:pointer; width:100%;
             display:grid; gap:1px 11px; align-items:center;
             grid-template-columns:auto minmax(0,1fr) auto;
             padding:10px 12px; border:1px solid var(--line);
             border-radius:12px; background:var(--surface); color:var(--ink);
             transition:border-color .16s; }
  .cv-card:hover { border-color:var(--gold-lt); }
  .cv-card:focus-visible { outline:2px solid var(--gold); outline-offset:2px; }
  .cv-card img, .cv-card .cv-noav { grid-area:1 / 1 / span 2 / 2;
             width:38px; height:38px; border-radius:50%; object-fit:cover;
             background:var(--surface-2); }
  /* WHOLE NAMES AND WHOLE SPECIALISMS, AT EVERY WIDTH. The four-column grid
     cut "Acharya Aryabh…" and "Advanced Numerolo…" on a laptop, and the
     specialism is how somebody picks. They wrap instead. */
  .cv-card b { grid-area:1 / 2; font-family:var(--font-serif); font-size:15px;
               font-weight:600; line-height:1.3; }
  .cv-card i { grid-area:2 / 2; font-style:normal; font-size:12.5px;
               line-height:1.4; color:var(--ink-2); }
  /* THE SUGGESTED ONE, marked on the card's edge rather than beside the
     name — beside it, it ran into "Astro Prem" on a laptop-width card. */
  .cv-card { position:relative; }
  .cv-card.is-picked { border-color:var(--gold-lt); }
  .cv-card .cv-pickme { position:absolute; top:-9px; left:14px;
             padding:1px 8px; border-radius:var(--r-pill);
             border:1px solid var(--gold-lt); background:var(--gold-bg); }

  /* ── Not sure who to ask? ── one row on a wide screen, as designed */
  .cv-ask { margin:0 0 22px; padding:16px 18px; border-radius:16px;
            border:1px solid var(--gold-lt); background:var(--gold-bg);
            display:flex; align-items:center; gap:18px; }
  .cv-ask-h { flex:0 0 auto; }
  .cv-ask-h b { display:block; font-family:var(--font-serif); font-size:18px;
                font-weight:600; }
  .cv-ask-h span { font-size:13px; color:var(--ink-2); }
  .cv-ask input { flex:1 1 auto; min-width:0; font:inherit; font-size:15px;
             height:44px; box-sizing:border-box; padding:0 16px;
             border-radius:22px; border:1px solid var(--line);
             background:var(--surface); color:var(--ink); }
  .cv-ask input:focus { outline:2px solid var(--gold); outline-offset:1px; }
  .cv-ask-to { flex:0 0 auto; font-size:14px; color:var(--ink-2); }
  .cv-ask-out { flex:0 0 auto; }
  .cv-ask-out[hidden] { display:none; }
  .cv-ask-pick { font:inherit; cursor:pointer; display:flex;
             align-items:center; gap:10px; min-height:44px;
             padding:6px 14px 6px 6px; border-radius:var(--r-pill);
             border:1px solid var(--line); background:var(--surface);
             color:var(--ink); font-size:14px; text-align:left; }
  .cv-ask-pick:hover { border-color:var(--gold-lt); }
  .cv-ask-pick:focus-visible { outline:2px solid var(--gold); outline-offset:2px; }
  .cv-ask-pick img, .cv-ask-pick .cv-noav { width:32px; height:32px;
             border-radius:50%; object-fit:cover; flex:0 0 auto; }
  .cv-ask-none { font-size:13px; color:var(--ink-2); }
  /* A phone: the row stacks — title, the question, the suggestion. */
  @media (max-width:760px) {
    .cv-ask { flex-direction:column; align-items:stretch; gap:12px; }
    .cv-ask input { font-size:16px; }
    .cv-ask-to { display:none; }
    .cv-ask-pick { width:100%; }
  }
  .cv-pickme { font-family:var(--font-mono); font-size:10.5px;
               letter-spacing:.06em; text-transform:uppercase;
               color:var(--gold-dk); }

  /* ── the roster by life area ── */
  /* FOUR ACROSS AT 1440, as designed — the design itself wraps the long
     names ("Acharya / Chanakya") beside the price, and that is accepted.
     Three on a laptop, one on a phone. */
  .cv-groups { display:grid; gap:22px 20px;
               grid-template-columns:repeat(auto-fill,minmax(min(100%,250px),1fr)); }
  .cv-group { display:flex; flex-direction:column; gap:8px; }
  .cv-group h3 { margin:0; font-family:var(--font-serif); font-size:16px;
                 font-weight:600; }
  .cv-price { grid-area:1 / 3 / span 3 / 4; font-family:var(--font-mono);
             font-size:11px; padding:5px 8px; border-radius:var(--r-pill);
             background:var(--gold-bg); color:var(--gold-dk);
             white-space:nowrap; }

  /* ── the room ───────────────────────────────────────────────────────
     THE VIEWPORT IS THE FRAME. --cv-off is the shell's sticky top bar plus
     this main's own padding, measured in JS rather than guessed at, because
     the bar wraps to two rows on a narrow window and a hardcoded number
     would put the composer off the bottom of the screen exactly there.
     100dvh, not 100vh: on iOS the address bar is part of vh and the
     composer spends the whole session hidden underneath it. */
  .cv-main.is-live { padding-bottom:18px; }
  .cv-main.is-live > .d-h1, .cv-main.is-live > .d-sub { display:none; }
  /* ── ONE WINDOW, NOT THREE CARDS ──
     The bar, the transcript and the composer were three separately bordered
     boxes floating 12px apart on the page background, so the middle — the
     part that IS the conversation — had no edges at all and read as a hole
     between two cards. They are one box now: the room carries the border and
     the radius, the three parts divide it with hairlines, and overflow:hidden
     lets the corners clip whatever runs to the edge.
     The transcript is the recessed tone and the bubbles stay on --surface, so
     they lift off it — the same pairing /home/chats/ already uses for the
     conversation it reads back. */
  .cv-room { display:flex; flex-direction:column;
             height:var(--cv-room-h, calc(100dvh - var(--cv-off,150px)));
             /* The room follows the visible window down when iOS pans it.
                A translate rather than a top offset, so it costs no layout
                and cannot disturb the sticky bar above it — which stays
                visible on purpose: it carries the wallet and the way out,
                and hiding it mid-charge would be its own surprise. */
             transform:translateY(var(--cv-room-y, 0px));
             min-height:430px;
             position:relative; overflow:hidden;
             border:1px solid var(--line); border-radius:16px;
             background:var(--surface); }
  .cv-room[hidden] { display:none; }

  /* ── the bar: who, and what it costs, on one line ── */
  .cv-bar { flex:0 0 auto; display:flex; align-items:center; gap:12px;
            flex-wrap:wrap; padding:11px 14px;
            border-bottom:1px solid var(--line); background:var(--surface); }
  .cv-who { display:flex; align-items:center; gap:12px; flex:1 1 240px;
            min-width:0; }
  .cv-who img, .cv-who .cv-noav { width:40px; height:40px; border-radius:50%;
            object-fit:cover; background:var(--surface-2); flex:0 0 auto; }
  .cv-who-b { flex:1 1 auto; min-width:0; }
  .cv-who-b b { display:block; font-family:var(--font-serif); font-size:15.5px;
                font-weight:600; overflow:hidden; text-overflow:ellipsis;
                white-space:nowrap; }
  .cv-who-b i { display:block; font-style:normal; font-size:12.5px;
                color:var(--ink-2); overflow:hidden; text-overflow:ellipsis;
                white-space:nowrap; }
  /* THE PRICE LEADS THE LINE, so whatever the width cuts, it is not the rate. */
  .cv-who-b i em { font-style:normal; font-weight:650; color:var(--gold-dk); }
  /* ── WHO IS ANSWERING, WHOLE ──
     Both lines are nowrap+ellipsis so the bar can also hold the meter, and at
     390 that cut the specialism and at 320 the astrologer's own name — on the
     one screen where knowing which of the twenty-four you are paying is the
     entire point. The bar wraps instead, which it has room for on a phone
     once the meter sits under it rather than beside it.
     AFTER the two rules it overrides. Written above them, as it was first,
     the media query loses at equal specificity and does nothing — which is
     how the specialism stayed clipped through a passing measurement. */
  @media (max-width:640px) {
    .cv-bar { flex-wrap:wrap; row-gap:10px; }
    .cv-who { flex:1 1 100%; }
    .cv-who-b b, .cv-who-b i { white-space:normal; overflow:visible; }
  }
  .cv-swap { font:inherit; cursor:pointer; text-decoration:none;
             font-size:12.5px; font-weight:600;
             padding:7px 13px; border-radius:var(--r-pill);
             border:1px solid var(--line); background:var(--surface-2);
             color:var(--ink); flex:0 0 auto;
             display:inline-flex; align-items:center; gap:6px; }
  .cv-swap svg { flex:0 0 auto; }

  /* ── the meter, inside the bar ── */
  .cv-meter { display:flex; align-items:center; gap:11px; flex:0 0 auto;
              padding:7px 11px; border:1px solid var(--gold-lt);
              border-radius:var(--r-pill); background:var(--gold-bg);
              font-size:12.5px; }
  .cv-meter[hidden] { display:none; }
  .cv-dot-live { width:7px; height:7px; border-radius:50%; flex:0 0 auto;
                 background:var(--gold-solid); animation:cvlive 1.6s
                 ease-in-out infinite; }
  @keyframes cvlive { 0%,100% { opacity:.3; } 50% { opacity:1; } }
  .cv-meter-l { display:flex; align-items:baseline; gap:7px; }
  .cv-meter-l b { font-family:var(--font-mono); font-size:13.5px;
                  color:var(--gold-dk); font-variant-numeric:tabular-nums; }
  .cv-meter-l i { display:none; }   /* the rate: already in the header */
  .cv-meter-r { display:flex; align-items:baseline; gap:7px; }
  .cv-meter-r b { font-weight:700; color:var(--ink);
                  font-variant-numeric:tabular-nums; }
  .cv-meter-r i { font-style:normal; font-size:11.5px; color:var(--ink-3); }
  .cv-stop { font:inherit; cursor:pointer; font-size:12px; font-weight:600;
             padding:5px 11px; border-radius:var(--r-pill);
             border:1px solid var(--gold-lt); background:var(--surface);
             color:var(--gold-dk); }

  /* ── the transcript: the only thing that scrolls ── */
  .cv-thread { flex:1 1 auto; min-height:0; overflow-y:auto;
               overscroll-behavior:contain; display:flex;
               flex-direction:column; gap:12px; padding:16px;
               background:var(--surface-2); }
  /* THREE MESSAGES BELONG AT THE BOTTOM, next to the box they were typed in
     — not stranded at the top with the empty half of the window under them.
     margin-top:auto on the first child rather than justify-content:flex-end,
     which clips the top of an overflowing column in every browser. */
  .cv-thread > :first-child { margin-top:auto; }
  /* A READABLE COLUMN, as designed: on a wide screen the conversation sits in
     760px down the middle instead of spreading bubble-to-bubble across the
     whole room. Left/right only — margin-top:auto above keeps its job. */
  .cv-thread > * { width:100%; max-width:760px; margin-left:auto;
                   margin-right:auto; box-sizing:border-box; }
  /* A run from one speaker is one block: the follow-ups tuck up under the
     first and drop the avatar, which is what stops a back-and-forth reading
     as a list of separately-addressed notes. */
  .cv-msg + .cv-msg.cv-same { margin-top:-6px; }
  .cv-msg.cv-same .cv-mav { visibility:hidden; }
  .cv-thread:focus-visible { outline:2px solid var(--gold);
                             outline-offset:3px; border-radius:10px; }
  /* ONE ASTROLOGER, NAMED ONCE. The room's bar says who is answering, so
     every answer carrying the name in capitals and the face beside it was a
     line and a column spent on a phone saying what the bar already says. A
     message is the bubble and, under it, its time — and on an answer, Copy
     and Share. */
  .cv-msg { display:flex; flex-direction:column; align-items:flex-start;
            gap:3px; max-width:100%; }
  .cv-msg-me { align-items:flex-end; }
  /* In the 760px column too: .cv-msg's own max-width:100% came later and
     outranked the column rule, so bubbles still spanned the room. */
  .cv-thread > .cv-msg { max-width:760px; }
  .cv-meta { display:flex; align-items:center; gap:2px; font-size:11.5px;
             color:var(--ink-2); padding:0 2px; }
  .cv-meta time { padding:0 4px; font-variant-numeric:tabular-nums; }
  .cv-act { font:inherit; font-size:12px; cursor:pointer; display:inline-flex;
            align-items:center; gap:5px; min-height:30px; padding:0 8px;
            border:0; border-radius:8px; background:none; color:var(--ink-2); }
  .cv-act:hover { background:var(--surface); color:var(--ink); }
  .cv-act:focus-visible { outline:2px solid var(--gold); outline-offset:1px; }
  @media (pointer:coarse) { .cv-act { min-height:44px; } }
  /* The day, between the days — a conversation carried on from yesterday
     reads as one block otherwise. */
  .cv-day { display:flex; align-items:center; gap:10px; font-size:12px;
            color:var(--ink-2); }
  .cv-day::before, .cv-day::after { content:''; flex:1 1 auto; height:1px;
                                     background:var(--line); }
  /* ── ASK NEXT, after the last answer ──
     Suggested questions used to exist only before the first one, so every
     later turn started from a blank box. Tapping one fills the box — it does
     not send — because sending is what spends. */
  .cv-next { display:flex; flex-direction:column; gap:8px; margin-top:2px; }
  .cv-next-l { font-family:var(--font-mono); font-size:11px;
               letter-spacing:.08em; text-transform:uppercase;
               color:var(--gold-dk); }
  .cv-next-qs { display:flex; flex-wrap:wrap; gap:8px; }
  .cv-chip { font:inherit; cursor:pointer; font-size:13.5px; line-height:1.4;
             text-align:left; padding:8px 13px; min-height:38px;
             border-radius:var(--r-pill); border:1px solid var(--gold-lt);
             background:var(--surface); color:var(--ink); }
  .cv-q1.cv-chip { border-radius:var(--r-pill); border-color:var(--gold-lt);
                   width:auto; font-size:13.5px; }
  .cv-chip:hover { background:var(--gold-bg); }
  .cv-chip:focus-visible { outline:2px solid var(--gold); outline-offset:2px; }
  @media (pointer:coarse) { .cv-chip { min-height:44px; } }
  .cv-mav { width:26px; height:26px; border-radius:50%; object-fit:cover;
            background:var(--surface-2); flex:0 0 auto; margin-bottom:2px; }
  .ch-b { max-width:min(660px,84%); padding:11px 15px; border-radius:16px;
          font-size:14px; line-height:1.62; border:1px solid var(--line);
          background:var(--surface); color:var(--ink);
          border-bottom-left-radius:5px; }
  .ch-b-me { background:var(--gold-bg); border-color:var(--gold-lt);
             border-bottom-left-radius:16px; border-bottom-right-radius:5px; }
  .ch-b s { display:block; text-decoration:none; margin-bottom:4px;
            font-family:var(--font-mono); font-size:9.5px; letter-spacing:.1em;
            text-transform:uppercase; color:var(--ink-3); }
  /* Who said it, at a size somebody can read it. */
  @media (max-width:640px) { .ch-b s { font-size:11px; } }
  .ch-b p { margin:0 0 9px; }
  .ch-b p:last-child { margin-bottom:0; }

  /* ── the opener, which is where a starter question belongs ──
     An empty transcript used to be a grey slab 180px tall with one grey
     sentence floating in the middle of it. It is the first thing anybody
     sees on this page, so it now says who is listening and offers three
     things to ask — from /chat/agent-questions/, which the backend has
     served all along and no client had ever called. */
  .cv-open { margin:auto; display:flex; flex-direction:column;
             align-items:center; text-align:center; gap:13px; padding:20px 8px;
             max-width:44ch; }
  .cv-open img, .cv-open .cv-noav { width:60px; height:60px; border-radius:50%;
             object-fit:cover; background:var(--surface-2); }
  .cv-open p { margin:0; font-size:13.5px; line-height:1.62;
               color:var(--ink-2); }
  .cv-open p b { color:var(--ink); font-weight:600; }
  .cv-qs { display:flex; flex-direction:column; gap:7px; width:100%; }
  .cv-foryou { align-self:flex-start; }
  .cv-foryou[hidden] { display:none; }
  .cv-q1 { font:inherit; cursor:pointer; font-size:13px; line-height:1.5;
           text-align:left; padding:10px 14px; border-radius:12px;
           border:1px solid var(--line); background:var(--surface);
           color:var(--ink); transition:border-color .16s; }
  .cv-q1:hover { border-color:var(--gold-lt); }
  .cv-q1:focus-visible { outline:2px solid var(--gold); outline-offset:2px; }

  /* THE WAIT IS A MESSAGE, not a spinner in a corner. It sits where the
     answer will sit, so the eye is already in the right place. */
  .cv-wait { display:flex; flex-direction:column; gap:6px; }
  .cv-wait-h { display:flex; align-items:flex-start; gap:9px; }
  .cv-wait-h .cv-dot { margin-top:.6em; }
  .cv-wait-t { font-size:12px; color:var(--ink-2); }
  .cv-wait-t b { font-family:var(--font-mono); color:var(--gold-dk);
                 font-weight:600; font-variant-numeric:tabular-nums; }
  .cv-dot { width:7px; height:7px; border-radius:50%;
            background:var(--gold-solid); animation:cvp 1.1s ease-in-out
            infinite; flex:0 0 auto; }
  @keyframes cvp { 0%,100% { opacity:.25; } 50% { opacity:1; } }

  .cv-jump { position:absolute; left:50%; transform:translateX(-50%);
             bottom:126px; z-index:2; font:inherit; cursor:pointer;
             font-size:12px; font-weight:600; padding:7px 15px;
             border-radius:var(--r-pill); border:1px solid var(--gold-lt);
             background:var(--surface); color:var(--gold-dk);
             box-shadow:0 3px 12px rgba(0,0,0,.09); }
  .cv-jump[hidden] { display:none; }
  /* Read aloud, never seen — the site's one way of doing that (ui.py). */
  /* The price's shimmer: the width of "$0.20/min", inline in its line. */
  .cv-price-sk { display:inline-block; width:7.5ch; min-height:1em;
                 vertical-align:-0.15em; }
  .cv-sr { position:absolute; width:1px; height:1px; overflow:hidden;
           clip-path:inset(50%); white-space:nowrap; margin:0; }

  /* ── composer, pinned ── */
  .cv-foot { flex:0 0 auto; padding:10px 12px;
             border-top:1px solid var(--line); background:var(--surface); }
  /* align-items:flex-end, so a question that grows to four lines pushes the
     top of the box up and leaves Send sitting on the last line — where the
     cursor is — rather than floating in the middle of the block. */
  .cv-comp { display:flex; align-items:flex-end; gap:10px;
             border:1px solid var(--line); border-radius:14px;
             background:var(--surface); padding:8px 8px 8px 14px; }
  /* min-width:0 or the textarea's intrinsic width wins the flex negotiation
     and pushes Send off the right edge on a narrow screen. */
  .cv-comp textarea { flex:1 1 auto; min-width:0; font:inherit;
             font-size:14.5px; line-height:1.6; border:0; background:none;
             color:var(--ink); resize:none; max-height:132px; display:block;
             padding:8px 0; }
  .cv-comp textarea:focus { outline:none; }
  /* 16px OR IOS ZOOMS THE PAGE WHEN YOU TAP IT. Safari enlarges the whole
     document on focus for any control under 16px, and it does not zoom back
     out — so the one box a paying reader types into would leave them at 120%
     with the meter running. mobile.mjs asserts this floor on five public
     pages; nothing was asserting it on the signed-in side, which is where the
     typing actually happens. */
  @media (max-width:640px) {
    .cv-comp textarea { font-size:16px; }
  }
  .cv-comp:focus-within { border-color:var(--gold-lt); }
  /* Only rendered near the limit, so it costs nothing until it means
     something. flex:0 0 auto and self-end keeps it on Send's line. */
  .cv-count { align-self:flex-end; flex:0 0 auto; padding-bottom:11px;
              font-size:11.5px; color:var(--ink-3);
              font-variant-numeric:tabular-nums; }
  .cv-count[hidden] { display:none; }
  /* THE ARROW CIRCLE AT EVERY WIDTH, as designed — it was the phone's only.
     The word stays in the accessibility tree (see #cv-send-t below). */
  .cv-send { font:inherit; cursor:pointer; flex:0 0 auto; width:44px;
             height:44px; padding:0; border-radius:50%; border:0;
             display:grid; place-items:center; background:var(--gold-solid);
             color:var(--gold-solid-text); }
  #cv-send-t { position:absolute; width:1px; height:1px; overflow:hidden;
               clip-path:inset(50%); white-space:nowrap; }
  .cv-send::after { content:'\2192'; font-size:20px; line-height:1;
                    font-family:var(--font-sans); }
  .cv-send[disabled] { opacity:.45; cursor:not-allowed; }
  .cv-mic { font:inherit; cursor:pointer; flex:0 0 auto; width:38px;
            height:38px; border-radius:50%; border:1px solid var(--line);
            background:var(--surface); color:var(--ink); display:grid;
            place-items:center; padding:0; }
  .cv-mic[hidden] { display:none; }
  .cv-mic:focus-visible { outline:2px solid var(--gold); outline-offset:2px; }
  .cv-mic.is-on { border-color:var(--gold); background:var(--gold-bg);
                  color:var(--gold-dk); animation:cvlive 1.4s ease-in-out infinite; }
  @media (pointer:coarse) { .cv-mic { width:44px; height:44px; } }
  @media (prefers-reduced-motion:reduce) { .cv-mic.is-on { animation:none; } }
  /* NOT BELOW 360. At 320 the mic took the 44px the placeholder was budgeted
     against, and "Ask your question" wrapped inside a one-line box. */
  @media (max-width:359px) { .cv-mic { display:none; } }
  /* ── THUMB SIZES ON THE SCREEN THAT COSTS MONEY ──
     Send at 38, "Someone else" at 36, a suggested question at 42. Send is the
     one that spends: a miss there with the meter running is a mis-tap the
     reader pays for, and it sits 10px from the edge of the composer. */
  @media (pointer:coarse) {
    .cv-send { height:44px; }
    .cv-swap { min-height:44px; display:inline-flex; align-items:center; }
    .cv-q1 { min-height:44px; }
  }
  .cv-fine { font-size:11.5px; line-height:1.55; color:var(--ink-3);
             margin:8px 2px 0; max-width:78ch; }

  /* ── THE VERDICT: a centred dialog; on a phone, a bottom sheet ──
     (design "Chat Rating Popup"). The dialog box itself is transparent and
     unpadded so a click that lands on it is a click on the backdrop; the
     panel inside is what is drawn. */
  .cv-rp { border:0; padding:0; margin:auto; background:transparent;
           color:var(--ink); width:min(460px, calc(100vw - 32px));
           max-width:none; max-height:none; overflow:visible; }
  .cv-rp::backdrop { background:rgba(20,15,8,.52); }
  .cv-rp-p { position:relative; box-sizing:border-box; display:flex;
             flex-direction:column; gap:16px; padding:28px 28px 24px;
             border-radius:20px; background:var(--surface);
             box-shadow:0 24px 60px rgba(20,15,8,.3);
             max-height:calc(100dvh - 32px); overflow-y:auto;
             overscroll-behavior:contain; }
  .cv-rp-s { display:flex; flex-direction:column; gap:16px; }
  .cv-rp-s[hidden] { display:none; }
  .cv-rp-g { display:none; }
  .cv-rp-h { display:flex; flex-direction:column; align-items:center;
             gap:10px; text-align:center; padding:0 30px; }
  .cv-rp-h img { width:56px; height:56px; border-radius:50%;
                 object-fit:cover; background:var(--surface-2); }
  .cv-rp h2 { margin:0; font-family:var(--font-serif); font-size:22px;
              font-weight:600; line-height:1.22; color:var(--ink); }
  .cv-rp h2 b { font-weight:inherit; }
  .cv-rp-rc { align-self:center; margin:0; display:inline-flex;
              align-items:center; gap:7px; padding:6px 12px;
              border-radius:var(--r-pill); background:var(--gold-bg);
              border:1px solid var(--gold-lt); font-size:12.5px;
              color:var(--ink-2); }
  .cv-rp-rc[hidden] { display:none; }
  .cv-rp-rc b { color:var(--ink); }
  .cv-rp-rc b:first-child { font-family:var(--font-mono); color:var(--gold-dk); }
  .cv-stars { display:flex; justify-content:center; gap:4px; }
  .cv-star { width:48px; height:48px; padding:0; border:0; background:none;
             display:grid; place-items:center; cursor:pointer;
             color:var(--ink-3); border-radius:50%; }
  .cv-star svg { width:36px; height:36px; fill:none; stroke:currentColor;
                 stroke-width:1.6; stroke-linejoin:round; }
  .cv-star.is-lit { color:var(--gold); }
  .cv-star.is-lit svg { fill:currentColor; }
  .cv-star:focus-visible { outline:2px solid var(--gold); outline-offset:1px; }
  /* Everything up to the one hovered lights — on a pointer that hovers. */
  @media (hover:hover) {
    .cv-stars:hover .cv-star { color:var(--gold); }
    .cv-stars:hover .cv-star svg { fill:currentColor; }
    .cv-stars .cv-star:hover ~ .cv-star { color:var(--ink-3); }
    .cv-stars .cv-star:hover ~ .cv-star svg { fill:none; }
  }
  .cv-rp-lab { margin:-6px 0 0; text-align:center; font-size:14px;
               font-weight:650; color:var(--gold-dk); }
  .cv-rp-why { margin:0; text-align:center; font-size:13px; color:var(--ink-2); }
  .cv-rp-why[hidden], .cv-rp-no[hidden] { display:none; }
  .cv-rp-go, .cv-rp-no { font:inherit; font-size:15px; font-weight:650;
             min-height:46px; padding:0 22px; border-radius:var(--r-pill);
             display:inline-flex; align-items:center; justify-content:center;
             text-decoration:none; cursor:pointer; box-sizing:border-box; }
  .cv-rp-go { border:0; background:var(--gold-solid); color:var(--gold-solid-text); }
  .cv-rp-no { border:1px solid var(--line); background:var(--surface); color:var(--ink); }
  .cv-rp-go:focus-visible, .cv-rp-no:focus-visible {
             outline:2px solid var(--gold); outline-offset:2px; }
  .cv-rate-w { display:flex; flex-direction:column; gap:12px; }
  .cv-rate-w[hidden] { display:none; }
  .cv-rp-tl { font-size:13px; color:var(--ink-2); }
  .cv-rp-tl span, .cv-rate-l span { color:var(--ink-3); }
  .cv-rp-tags { display:flex; flex-wrap:wrap; gap:8px; margin-top:-4px; }
  .cv-tag { font:inherit; font-size:13.5px; min-height:38px; padding:7px 13px;
            border-radius:var(--r-pill); border:1px solid var(--line);
            background:var(--surface); color:var(--ink); cursor:pointer; }
  .cv-tag[aria-pressed="true"] { background:var(--gold-bg);
            border-color:var(--gold); font-weight:650; }
  .cv-tag[aria-pressed="true"]::before { content:'\2713\00a0'; color:var(--gold-dk); }
  .cv-tag:focus-visible { outline:2px solid var(--gold); outline-offset:2px; }
  @media (pointer:coarse) { .cv-tag { min-height:44px; } }
  .cv-rate-l { font-size:13px; color:var(--ink-2); margin-bottom:-6px; }
  .cv-rate-w textarea { width:100%; box-sizing:border-box; font:inherit;
             font-size:15px; line-height:1.5; padding:10px 12px;
             border-radius:12px; border:1px solid var(--line);
             background:var(--surface); color:var(--ink); resize:none; }
  .cv-rate-w textarea:focus { outline:2px solid var(--gold); outline-offset:1px; }
  .cv-rate-b { display:flex; flex-direction:row-reverse; gap:10px; }
  .cv-rate-b .cv-rp-go { flex:1 1 auto; }
  .cv-rate-t { margin:0; font-size:13px; color:var(--ink-2); }
  .cv-rate-t[hidden] { display:none; }
  .cv-rp-x { position:absolute; top:10px; right:10px; width:44px; height:44px;
             padding:0; border:0; border-radius:50%; background:none;
             color:var(--ink-2); display:grid; place-items:center; cursor:pointer; }
  .cv-rp-x svg { width:20px; height:20px; fill:none; stroke:currentColor;
                 stroke-width:2; stroke-linecap:round; }
  .cv-rp-x:focus-visible { outline:2px solid var(--gold); outline-offset:-2px; }
  .cv-rp-done { align-items:center; text-align:center; padding-top:6px; }
  .cv-rp-ok { width:56px; height:56px; border-radius:50%; display:grid;
              place-items:center; background:var(--gold-bg);
              border:1px solid var(--gold-lt); }
  .cv-rp-ok svg { width:26px; height:26px; fill:none; stroke:var(--gold-dk);
                  stroke-width:2.6; stroke-linecap:round; stroke-linejoin:round; }
  .cv-rp-done p { margin:0; font-size:14.5px; line-height:1.55; color:var(--ink-2); }
  .cv-rp-acts { display:flex; flex-direction:column; gap:10px; width:100%; margin-top:4px; }

  /* A PHONE: a sheet from the bottom edge, in reach of a thumb. */
  @media (max-width:640px) {
    .cv-rp { width:100%; max-width:100%; margin:0; position:fixed;
             inset:auto 0 0 0; }
    .cv-rp-p { border-radius:22px 22px 0 0; max-height:92dvh;
               padding:10px 20px calc(22px + env(safe-area-inset-bottom)); }
    .cv-rp-g { display:block; align-self:center; width:40px; height:5px;
               border-radius:3px; background:var(--line); }
    .cv-rp-x { top:14px; }
    .cv-rp h2 { font-size:20px; }
    .cv-rate-b { flex-direction:column; }
    .cv-rate-w textarea { font-size:16px; }   /* no zoom on focus in iOS */
    .cv-rp[open] .cv-rp-p { animation:cv-rp-up .22s ease-out; }
  }
  @keyframes cv-rp-up { from { transform:translateY(40px); opacity:.6; } }
  @media (prefers-reduced-motion:reduce) {
    .cv-rp[open] .cv-rp-p { animation:none; }
  }

  /* ── what went wrong ── */
  .cv-say { flex:0 0 auto; padding:11px 14px; border-radius:12px;
            border:1px solid var(--line); background:var(--surface);
            font-size:13.5px; line-height:1.6; color:var(--ink-2); }
  .cv-say[hidden] { display:none; }
  .cv-say b { color:var(--ink); }
  .cv-say a { color:var(--gold-dk); }

  @media (max-width:560px) {
    .cv-bar { gap:9px; padding:10px 12px; }
    .cv-meter { width:100%; justify-content:space-between; }
    .ch-b { max-width:88%; }
  }

  /* ══ THE ROOM ON A PHONE, AND THIS BLOCK GOES LAST ═══════════════════════
     A media query adds no specificity, so every rule here has to follow the
     one it overrides — including the two phone blocks higher up this file,
     which is why this is at the bottom rather than beside them.

     WHAT WAS WRONG. Measured on the built page with a conversation open and
     the meter running, on the ~700px of room a phone browser actually leaves:

       app top bar            65px    9%
       astrologer card       170px   24%      <- two rows, and it grew to two
       transcript            280px   40%         the moment billing started
       composer               62px    9%
       footnote               71px   10%

     Sixty per cent furniture on the one screen that is charged for by the
     minute. Everything below is about giving that 24 + 10 back. */
  @media (max-width:640px) {

    /* ── 01 · THE CARD BECOMES ONE ROW ──
       The block above wraps the bar deliberately, so a 320 screen keeps the
       astrologer's NAME — right, and it paid for it with a whole second row.
       The specialty is what goes instead: "Advanced Numerology" is why you
       picked them, read once, on their page and on the roster, and it is not
       a thing anybody re-reads while the clock runs. The name stays, full
       width, first in the row. 170px -> 52px. */
    .cv-bar { flex-wrap:nowrap; row-gap:0; gap:9px; padding:7px 11px;
              min-height:52px; }
    .cv-who { flex:1 1 auto; min-width:0; gap:9px; }
    .cv-who img, .cv-who .cv-noav { width:30px; height:30px; }
    /* THE RATE STAYS ON A PHONE. The specialism line was dropped here to
       keep the bar one row, and the per-minute price went with it — so on
       the screen that charges by the minute, a reader who arrived by a link
       never saw what it cost. The line comes back, one row high, price
       first; the specialism is what the ellipsis takes. */
    /* THE PRICE ALONE. The line wrapped ("Love &" / "Relationship
       Expert", a three-line bar) and, held to one line, was cut mid-word
       ("\u20b910/min \u00b7 Adva…"). The specialism was why they picked; the
       price is what the meter is running at. */
    .cv-who-b i { font-size:12px; white-space:nowrap; }
    .cv-spec { display:none; }
    .cv-who-b b { white-space:nowrap; overflow:hidden;
                  text-overflow:ellipsis; }
    /* THE WAY TO SOMEONE ELSE, AS AN ICON. It was hidden here, and the note
       that was meant to carry it is hidden on a phone too — so there was no
       way out of the room but the menu. */
    .cv-swap { width:40px; height:40px; padding:0; justify-content:center; }
    /* ...EXCEPT WHILE THE CLOCK RUNS. With the meter in the row as well, the
       icon took the room the name needs — "Acharya Parash" at 390 — and the
       name of who is charging is the one thing this bar cannot lose. End is
       right there; the icon comes back when the session does end. */
    .cv-bar:has(.cv-meter:not([hidden])) .cv-swap { display:none; }
    .cv-swap-t { position:absolute; width:1px; height:1px; overflow:hidden;
                 clip-path:inset(50%); white-space:nowrap; }

    /* ── 02 · NOTHING MOVES WHEN THE METER STARTS ──
       .cv-meter is hidden until money is moving, and on a one-row bar its
       arrival used to make the row taller — pushing the transcript down
       mid-read, on the one screen where the reader is paying for the seconds
       they spend finding their place again. min-height on the bar reserves
       it, so the row is the same height before and after. */
    /* width:auto UNDOES THE BLOCK THIS ONE REPLACES. The earlier phone rules
       set .cv-meter { width:100% } because on a wrapped bar the meter had a
       row to itself and filled it. On one row that makes it take everything
       and leaves .cv-who at ZERO — the astrologer's name drawn in a box of no
       width, which is worse than the two rows it was meant to save. It was
       invisible for the same reason the whole state was: the meter only
       renders while the clock is running, and nothing had ever run it. */
    .cv-meter { width:auto; flex:0 0 auto; justify-content:flex-start;
                padding:4px 4px 4px 9px; gap:7px; font-size:11px; }
    .cv-meter-l i, .cv-meter-r i { display:none; }
    .cv-meter-l b { font-size:12.5px; }
    /* THE CONTROL THAT STOPS THE CHARGING, at 33px. It has been that size
       since it was written and no run had measured it, because no run had
       ever rendered the bar it lives in. */
    .cv-stop { padding:6px 12px; min-height:var(--tap-min); }

    /* ── 03 · SEND IS A CIRCLE, AND THE FIELD GETS THE WIDTH ──
       The word costs about 55px of a 390px row and the arrow costs 44. On a
       row whose whole job is holding the question, that is the difference
       between a placeholder that fits and one that does not. It keeps its
       aria-label, so it is still called Send by anything that reads it. */
    .cv-send { width:44px; height:44px; padding:0; border-radius:50%;
               display:grid; place-items:center; }
    /* THE WORD IS STILL THERE, IT IS JUST NOT DRAWN. font-size:0 was the
       first attempt and shell-mobile.mjs caught it immediately — a 0px string
       is a string under the floor, and the rule is right to say so. The
       site's visually-hidden pattern takes it out of the drawing while
       leaving it in the accessibility tree, so a screen reader still reads
       "Send" and the arrow is drawn on the button itself. */
    #cv-send-t { position:absolute; width:1px; height:1px; overflow:hidden;
                 clip-path:inset(50%); white-space:nowrap; }
    .cv-send::after { content:'\2192'; font-size:20px; line-height:1;
                      font-family:var(--font-sans); }
    .cv-comp { padding:7px 7px 7px 13px; }

    /* ── 04 · NO FINE PRINT UNDER THE COMPOSER ──
       The paragraph is the laptop's. On a phone the meter in the bar already
       says, in figures, the thing the prose said in four lines. */
    .cv-fine { display:none; }

    /* ── 05 · THE COMPOSER CLEARS THE HOME INDICATOR ──
       .cv-room is already 100dvh minus a measured offset — dvh rather than
       vh, which is the thing that actually breaks a pinned composer on iOS.
       What it did not account for is the inset below the last pixel of the
       viewport, so on a notched phone the fine print sat under the bar. */
    .cv-foot { padding:9px 10px
               calc(9px + var(--cv-safe-b, env(safe-area-inset-bottom, 0px))); }
    /* ── min-height WOULD DEFEAT THE WHOLE THING ──
       430px is a floor for a room on a small window. With a keyboard up, the
       room has to be allowed to become short — on a 667px handset the usable
       height drops to about 250 — and a 430px floor would push the composer
       straight back under the keyboard, which is the bug being fixed. */
    body.cv-kb .cv-room { min-height:0; }
    /* The other half of the same thing: the offset above stops RESERVING the
       bottom padding, and this stops the element DRAWING it. */
    body.cv-kb .d-main.cv-main { padding-bottom:0; }
    /* And nothing under the composer while the keyboard is up. */
    body.cv-kb .cv-fine { display:none; }

    /* ── THE HEADER STEPS ASIDE WHILE THE KEYBOARD IS UP ──
       With a 336px keyboard on a 700px handset the room gets a 364px window
       and 79 of that is the composer. The other fixed costs were the shell
       header (65), this main's top padding (26) and the room's own bar (69),
       which left the CONVERSATION 123px — about two lines of an answer being
       paid for by the minute. That is the "unusable" complaint, measured.

       Of the three, the bar is the one that cannot go: it carries the meter
       and the End button, and hiding the money mid-charge would be its own
       surprise. The header can, and precisely BECAUSE the meter is there —
       .d-wallet duplicates what .cv-meter is already showing (spent, and
       what is left), and .cv-stop is a nearer way out than the menu. So
       while a keyboard is up on a touch screen the header stands down, the
       top padding goes with it, and the transcript takes the 91px.

       WHAT THIS DELIBERATELY IS NOT: the room pinned at its full height and
       scrolled under the header. That leaves a page scroll nested inside the
       transcript's own — dragging the thread at its top chains to the page
       and pushes the composer back under the keys — and half of .cv-thread's
       viewport sits off-screen while the element still believes it is 441px
       tall, so `pinned` and the scroll-to-bottom below would be working in
       pixels nobody can see. Resizing keeps one scroller and one truth. */
    body.cv-kb .d-top { display:none; }
    body.cv-kb .d-main.cv-main { padding-top:0; }

    /* Dragging the transcript scrolls the transcript, not the page behind
       it — the reason a chat thread on a phone feels like a window rather
       than a section of a document. */
    .cv-thread { overscroll-behavior:contain; }
  }

  /* ── AND BELOW 360 THE BAR WRAPS AGAIN, ON PURPOSE ──
     One row costs the specialty, which is a fair price. Below 360 it costs
     the NAME as well, and that is not: measured on a 320 screen the row can
     give the name 95px and "Acharya Parashara" needs 137, at any size worth
     reading. There is nothing left to take — the meter is already down to a
     dot, a clock and End.
     So the trade reverses at the width where it stops paying. The original
     block wrapped the bar for exactly this reason and was right about it; it
     was only wrong to do it at every width, which cost 375, 390, 393 and 414
     a row they had the space for. A 320 screen pays a row and keeps the name
     of the person it is being charged by. */
  @media (max-width:359px) {
    .cv-bar { flex-wrap:wrap; row-gap:8px; min-height:0; }
    .cv-who { flex:1 1 100%; }
    .cv-who-b b { white-space:normal; overflow:visible; }
    .cv-meter { width:100%; justify-content:space-between; }
  }


  @media (prefers-reduced-motion:reduce) {
    .cv-card, .cv-q1 { transition:none; }
    .cv-dot, .cv-dot-live { animation:none; opacity:.7; }
  }