/* ==========================================================================
   Factbox reader (read.html) — the control rail, and the night ground.

   Loaded by read.html only, after css/app.css and css/audio-reader.css. It
   defines three things: the reader's palette, where the rail sits, and the
   right margin the card text gives up to clear it.

   WHY THERE IS A RAIL
   -------------------
   The reader had three controls at three heights: "← Stories" top-left, the
   Save bookmark beside it, and the sound button top-right. Save is the one a
   reader reaches for mid-story, and it was the furthest thing on the screen
   from the thumb holding the phone — up, and across. Every app this audience
   arrives from (TikTok, Instagram, Reels) puts the save action in a vertical
   rail at the foot of the right edge, and it is the one gesture they do not
   have to look for.

   So Save and Sound stack as one column, bottom-right. "← Stories" stays
   top-left: it is the way out of the story, not a control inside it.

   ORDER: sound on top, SAVE ON THE BOTTOM.
   The founder's ask was "the save button as a bookmark in the bottom right",
   and the bottom of the rail is literally that corner. It is also the slot a
   thumb reaches without the hand moving, which is the right place for the
   frequent action; sound is tapped once a session at most.

   THE FLOOR IS --bottom-safe, AND THAT IS NOT NEGOTIABLE
   -----------------------------------------------------
   --bottom-safe in app.css is max(13vh, inset + toolbar + 4px) because the
   Instagram and TikTok in-app browsers draw their own toolbar over the bottom
   of the viewport, and that is essentially all of our traffic. The rail's
   floor sits on that line with 6px of air, so it clears the toolbar at every
   screen height rather than at the one the last person tested on.
   ========================================================================== */

/* ==========================================================================
   THE READER IS NIGHT.

   css/app.css paints the site on paper: cream ground, navy type, blue fills.
   Every pane in here is a full-bleed museum painting with words laid over it,
   and cream behind a photograph is unreadable whatever colour the browse pages
   are. The end pane rides in the same swipe deck, so it has to match or the
   last swipe of every story is a white flash.

   WHY THE OVERRIDE LIVES IN THIS FILE, of all files.

   The obvious form is `html.reader{...}` in app.css. Nothing sets that class:
   read.html carries a bare <html lang="en">, and its <head> is not ours to
   edit. `body:has(> .deck)` would work in a current browser and silently do
   nothing in an older in-app webview — and ~all of this traffic is an in-app
   webview, so the failure mode is a cream page behind a painting for exactly
   the readers we cannot see.

   So the scope is the stylesheet itself. read.html is the only page that
   loads this file, and it loads it last, so a plain `:root` here outranks
   app.css's `:root` on the one document that should be dark and is invisible
   to every document that should not. Custom properties resolve where they are
   USED, so this repaints recommend.css, audio-reader.css and app.css's own
   reader rules alike, with nothing to keep in step.

   If you build another page that must be night, link this file from it.
   If you split the rail out of here, the palette goes with the reader, not
   with the rail.
   ========================================================================== */
:root{
  color-scheme:dark;

  /* ---- THE READER'S PALETTE, AND IT IS DERIVED ---------------------------
     Every value below is either a token out of css/app.css or one of the six
     literals in the two blocks marked LITERAL. That is the whole budget for
     this file, and it is spent where a token cannot reach: an opaque ground
     that is darker than --night, the two cream alphas the night ground needs
     for quiet type, and the four-stop scrim the plates are read through.
     Nothing else in the reader, in css/recommend.css or in css/audio-reader.css
     names a colour.

     WHAT CHANGED, AND WHY IT HAD TO
     This block used to repaint --coral to the old warm accent and --accent-ink
     with it. app.css is now painted in blue: --coral #3B9EF4 is the fill and
     --on-accent is what you may write on it, and both are correct on night
     without help. So the accent is NOT overridden any more — the reader gets
     the site's one accent, and the only thing night needs is a WRITEABLE blue,
     because --accent-ink (#1A5BA1, tuned for cream) is 1.6:1 on the ground.
     --accent-lift (#8FC4FA) is that colour: 10.1:1 on --night. --ring is
     var(--accent-ink) in app.css and resolves where it is used, so the focus
     ring follows this line with nothing to keep in step.

     MEASURED, on --ground below (#090C16):
       --parchment / --ink   #F4EFE6            17.8:1
       --dim      .88 cream                     15.3:1  (card prose)
       --dimmer   .62 cream                     10.0:1  (metadata, eyebrows)
       --accent-ink = --accent-lift #8FC4FA     10.1:1
       --on-accent #06182E on --coral #3B9EF4    6.3:1  (app.css's own pair)
     ---------------------------------------------------------------------- */

  /* Cream on night. The reading colour and the glass colour are the same
     colour, which is the point of --on-glass: the reader IS glass over
     artwork, from the first card to the last pane. */
  --parchment:var(--on-glass);
  --ink:var(--on-glass); --ink-soft:var(--on-glass-soft);

  /* The accent is app.css's. Only the writeable form is night's own. */
  --accent-ink:var(--accent-lift);

  /* LITERAL 1 — the ground.
     --night (#0F1420) is app.css's backstop BEHIND a plate: what shows for
     the milliseconds before a painting decodes. The pane the plate sits IN
     is a step darker so the plate reads as lit. Both are the same hue.

     --raise is the one lifted surface the reader has left (the plan sheet's
     options); it is --tint over the ground rather than a seventh literal. */
  --ground:#090C16;
  --raise:var(--tint);
  --ground-0:rgba(9,12,22,0);
  --raise-0:var(--glass-0);

  /* LITERAL 2 — the two cream alphas night needs and paper does not.
     app.css's --dim / --dimmer are navy at .82 / .74, which are invisible
     here; --on-glass-soft (.76) is the only cream token and it is one step,
     not two. These are the same #F4EFE6 at the two the reader needs: the
     alpha a card's prose is set in, and the one its metadata is. */
  --dim:rgba(244,239,230,.88);
  --dimmer:rgba(244,239,230,.62);
  --hair:rgba(244,239,230,.13);

  --tint:rgba(244,239,230,.07);
  --tint-2:rgba(244,239,230,.12);
  --tint-3:rgba(244,239,230,.16);
  --menu:#121724;

  --shadow-1:0 6px 20px rgba(0,0,0,.45);
  --shadow-2:0 14px 40px rgba(0,0,0,.55);

  /* LITERAL 3 — the scrim every plate in the reader is read through.

     Four stops, and they are the reason white type never sits on a bright
     sky: a deep foot the text block stands on, a clear middle where the
     painting is the whole point, and a light wash at the top so the back pill
     and the rail keep their contrast over a lit horizon. The colour is
     --glass's own (9,12,22) at four alphas; a custom property holds a whole
     gradient, so this is one declaration and every surface that needs it
     says var(--plate-scrim).

     Measured over the palest plate in the season (s12, mean luminance .71):
     --on-glass at the foot 12.9:1, at the 62% stop 5.4:1 — and no card sets
     type above the 62% stop. */
  --plate-scrim:linear-gradient(to top,
                  rgba(9,12,22,.94)   0%,
                  rgba(9,12,22,.78)  38%,
                  rgba(9,12,22,.34)  62%,
                  rgba(9,12,22,.38) 100%);
  /* The same colour, as a floor under a full-screen pane: the plate is
     atmosphere there rather than the subject, so it goes almost all the way
     down. */
  --pane-scrim:linear-gradient(to top,
                  rgba(9,12,22,.96)   0%,
                  rgba(9,12,22,.9)   44%,
                  rgba(9,12,22,.78) 100%);
  /* And as a BAND with words written across its foot — the next story's
     cover, and the wall's. Opaque at the bottom edge, so type that starts
     inside the plate starts on the ground rather than on the painting: at
     the eyebrow's line the scrim is .95 and a 62px overlap of the palest
     plate in the season measures 6.0:1 for --accent-ink and 8.4:1 for
     --parchment. A gradient that only reaches .94 measures 3.2:1 there,
     which is where this token came from. */
  --band-scrim:linear-gradient(to top,
                  rgba(9,12,22,1)     0%,
                  rgba(9,12,22,.9)   36%,
                  rgba(9,12,22,.4)  100%);
  /* And the other way up, for the one screen whose words are at the TOP.

     The interruption's paragraph fades out into the ground, and the element
     that does the fading is opaque --ground. Over a plate that is still
     showing at 6-7% that fade paints a visible rectangle — the edge of the
     box, drawn across the picture, which reads as a rendering fault rather
     than as an ending. So the plate is held at full ground for the top 44%,
     which is where the words are, and only swells below them. */
  --cut-scrim:linear-gradient(to bottom,
                  rgba(9,12,22,1)     0%,
                  rgba(9,12,22,1)    44%,
                  rgba(9,12,22,.62) 100%);
}

/* ==========================================================================
   THE CARD STACK.

   app.css owns the deck's geometry — full-height snap panes, the plate, the
   two rails, the credit line. What it cannot own is the reader's TYPE, which
   is the one thing that is different here: every other page on the site is
   DM Sans on cream, and a card is a headline laid over a museum painting.

   So the type is Newsreader, --display, at --w-display. read.html asks Google
   for it; a token cannot fetch a font, and a page that uses --display without
   adding the family to its own link gets Georgia, which is a real fallback
   and not the design.

   THE SIZE LADDER, AND WHY IT IS NOT A CLAMP
   app.css sets the headline with clamp(1.35rem,5.2vw,1.7rem) and the hook one
   step above it. A clamp on vw makes a 430px phone shout and a 375px one
   whisper at the same reading distance, and the deck is the same 100dvh pane
   at both. These are rem, so they track the reader's own text size and
   nothing else: the hook at 2.125rem, every other head at 1.5625rem, and a
   head that is really a long sentence one step down again — same face, never
   a different font.
   ========================================================================== */

.copy h2{
  font-family:var(--display);
  font-weight:var(--w-display);
  font-size:1.5625rem; line-height:1.18; letter-spacing:-.008em;
}
.beat.is-hook .copy h2{
  font-size:2.125rem; line-height:1.08; letter-spacing:-.012em;
}
.copy p{
  font-size:1rem; color:var(--dim);
}
.beat.is-hook .copy p{font-size:1.0625rem}

/* A few cards in the season are a single 150-200 character sentence with no
   body. The words are the author's, so the type gives rather than the copy:
   the same serif, one size down, set as prose. */
.beat.is-long .copy h2{
  font-family:var(--display); font-weight:var(--w-display);
  font-size:1.25rem; line-height:1.34; letter-spacing:-.004em;
  color:var(--parchment);
}
.beat.is-hook.is-long .copy h2{font-size:1.5625rem; line-height:1.24}

/* The scrim, from the token above rather than the near-black app.css carries
   for the paper build. Same four stops on every plate in the reader. */
.beat .art::after{background:var(--plate-scrim)}

/* The source line under a plate. app.css writes it at a fixed cream alpha;
   --on-glass-soft is the token for exactly this — a credit on glass over
   artwork — and it is two steps more legible at the same size. */
.plate-credit{color:var(--on-glass-soft)}
.plate-credit a{color:var(--on-glass)}

.fb-rail{
  position:fixed;
  right:12px;
  bottom:calc(var(--bottom-safe, max(13vh, 64px)) + 6px);
  /* 32, matching .fb-sound: one above .topbar (31) and the progress bar (30),
     so a card's own stacking context can never bury it. */
  z-index:32;

  display:flex; flex-direction:column; align-items:center;
  gap:10px;

  /* The gaps between the buttons are painting, not a target. Each child takes
     its own taps back. */
  pointer-events:none;

  transition:opacity .22s ease, transform .22s ease;
}
.fb-rail > *{pointer-events:auto}

/* Sound first, save last, whatever order they were appended in — the save
   button is mounted when the story renders and the sound button when the deck
   first has a scored card, and those two do not land in a fixed order. */
.fb-rail .fb-sound{position:static; top:auto; right:auto; order:1}
.fb-rail .fbs-save{order:2}

/* --------------------------------------------------------------------------
   Hidden on the end card.

   That pane is the way out of the story rather than part of it: it carries
   its own single button, and two floating controls land on its words. The
   class is toggled from read.html's scroll handler, which runs with and
   without an IntersectionObserver.
   -------------------------------------------------------------------------- */
.fb-rail.is-off{
  opacity:0;
  visibility:hidden;
  pointer-events:none;
  transform:translateY(8px);
  transition:opacity .22s ease, transform .22s ease, visibility 0s linear .22s;
}

/* --------------------------------------------------------------------------
   The card text gives up 62px on the right.

   .copy is padding:0 20px in app.css and the rail is 38px wide at right:12px,
   so a long line ran under the buttons instead of stopping before them. 62px
   is the rail plus its margin plus a hair. Only below 760px: above it the
   deck is wide, .copy is capped at 620px and centred, and the two never meet.
   -------------------------------------------------------------------------- */
@media (max-width:760px){
  .copy{padding-right:82px}
}

/* The sound control's note follows its button. It is one line of glass that
   says where the iOS silent switch is, shown once per device the moment sound
   starts — 700px above the button it belongs to, it is a message from
   nowhere. Only read.html loads this file, so the composed story pages keep
   the top-right note their own top-right button sits under. */
.fb-sound-note{
  top:auto;
  bottom:calc(var(--bottom-safe, max(13vh, 64px)) + 58px);
  transform:translateY(6px);
}
.fb-sound-note.on{transform:none}

@media (prefers-reduced-motion:reduce){
  .fb-rail{transition:none}
  .fb-sound-note{transform:none}
}
