/* bean2brain — the reader's front page.
 *
 * Separate from the console's stylesheet on purpose. That one is a control
 * panel and should look like instrumentation; this one is a magazine and
 * should look like it was art-directed. Nothing here is shared.
 *
 * The "wow" is not one feature, it is an accumulation of small correctnesses:
 * a display face with real optical sizing, headlines that wrap in balanced
 * lines, a grain that stops flat colour looking like a screenshot, easing
 * curves that decelerate the way physical objects do, and images that move a
 * little slower than the page. None of it announces itself.
 */

:root{
  /* Roast palette. Espresso ground, cream paper, one warm accent that does
     all the work, one cool green for origin. */
  --ink:#1b1410;
  --ink-soft:#4a3a30;
  --paper:#faf6f0;
  --paper-2:#f2ebe1;
  --line:#e3d8c9;
  --line-2:#cdbda9;
  --amber:#c06a2e;
  --amber-lit:#e08b46;
  --leaf:#2f5d50;
  --mute:#8a786a;

  --shadow-sm:0 1px 2px rgba(27,20,16,.06), 0 4px 14px rgba(27,20,16,.05);
  --shadow-md:0 8px 30px rgba(27,20,16,.13);
  --shadow-lg:0 24px 70px rgba(27,20,16,.22);

  /* Deceleration, not linear. Physical objects arrive, they do not stop. */
  --ease:cubic-bezier(.22,.68,.24,1);
  --ease-out:cubic-bezier(.16,1,.3,1);

  --wrap:min(1200px, 92vw);
  --gap:clamp(1rem, 2.2vw, 2rem);
}

@media (prefers-color-scheme: dark){
  :root{
    --ink:#f3ece3;
    --ink-soft:#c9bcae;
    --paper:#15100c;
    --paper-2:#1e1712;
    --line:#2e241c;
    --line-2:#41332a;
    --amber:#e0904f;
    --amber-lit:#f0a768;
    --leaf:#7fbaa4;
    --mute:#9b8879;
    --shadow-sm:0 1px 2px rgba(0,0,0,.4), 0 4px 14px rgba(0,0,0,.3);
    --shadow-md:0 8px 30px rgba(0,0,0,.5);
    --shadow-lg:0 24px 70px rgba(0,0,0,.65);
  }
}

*{box-sizing:border-box}
html{-webkit-text-size-adjust:100%}
body{
  margin:0; background:var(--paper); color:var(--ink);
  font:400 17px/1.65 Inter, ui-sans-serif, system-ui, -apple-system, "Segoe UI", sans-serif;
  font-feature-settings:"kern" 1, "liga" 1;
  -webkit-font-smoothing:antialiased;
  overflow-x:clip;
}
img{max-width:100%; display:block}
a{color:inherit; text-decoration:none}
button{font:inherit; color:inherit; cursor:pointer}

::selection{background:var(--amber); color:#fff}
:focus-visible{outline:2px solid var(--amber); outline-offset:3px; border-radius:4px}

/* Paper is never perfectly flat. This is the single cheapest thing that
   stops a screen looking like a screen. */
.grain{
  position:fixed; inset:-50%; z-index:1; pointer-events:none; opacity:.32;
  background-image:url("data:image/svg+xml;utf8,\
<svg xmlns='http://www.w3.org/2000/svg' width='160' height='160'>\
<filter id='n'><feTurbulence type='fractalNoise' baseFrequency='.82' numOctaves='3'/>\
<feColorMatrix type='saturate' values='0'/></filter>\
<rect width='160' height='160' filter='url(%23n)' opacity='.5'/></svg>");
  mix-blend-mode:multiply;
}
@media (prefers-color-scheme: dark){ .grain{mix-blend-mode:screen; opacity:.16} }

/* ============================================================ masthead */
.masthead{
  position:sticky; top:0; z-index:20;
  display:flex; align-items:center; gap:var(--gap);
  width:100%; padding:.5rem max(4vw, calc((100vw - var(--wrap)) / 2));
  background:color-mix(in srgb, var(--paper) 82%, transparent);
  backdrop-filter:saturate(1.5) blur(14px);
  -webkit-backdrop-filter:saturate(1.5) blur(14px);
  border-bottom:1px solid transparent;
  transition:border-color .4s var(--ease), background .4s var(--ease);
}
.masthead.stuck{border-bottom-color:var(--line)}

.brand{display:flex; align-items:center; gap:.55rem}
/* The mark is black line art on a TRANSPARENT background, so it would
   vanish entirely in dark mode. The cream disc is not decoration -- it is
   what keeps the logo legible on both grounds, and it reads as a paper
   coaster, which suits a coffee-ring mark. */
/* MEASURED, not eyeballed. The artwork occupies only 79.4% x 71.7% of
   its own PNG and sits OFF-CENTRE inside it (left margin 12.9%, right
   7.7%; top 17.1%, bottom 11.2%). Dropping it in at natural size left
   the coffee ring floating in the middle of the coaster with a wide
   gap all round.

   So the disc is a CLIP and the image is oversized inside it: the
   coaster stays 72px while the ring grows to nearly touch its edge.
   translate() first re-centres the artwork (its centre is at 52.6%,
   53.0% of the file), then scale() fills the disc -- 0.96 / 0.794 =
   1.21, so 1.22 puts the ring at ~97% of the coaster. */
.mark{
  width:72px; height:72px; flex:none; border-radius:50%;
  background:#fbf7f1; overflow:hidden; display:block;
  box-shadow:0 1px 4px rgba(27,20,16,.2);
  transition:transform .5s var(--ease-out);
}
.mark img{
  width:100%; height:100%; object-fit:contain;
  transform:scale(1.22) translate(-2.6%, -3%);
}
.brand:hover .mark{transform:rotate(-8deg) scale(1.05)}
.wordmark{
  font-family:Fraunces, Georgia, serif; font-weight:600;
  font-size:clamp(1.4rem, 2.1vw, 1.78rem);
  letter-spacing:-.022em; font-variation-settings:"opsz" 72, "SOFT" 20;
  white-space:nowrap;
}
.wordmark em{font-style:normal; color:var(--amber)}

/* The dateline belongs to the paper, not to a control. Under the wordmark it
   reads as a masthead dateline and stops competing for room in the middle of
   the bar, which is what made the bar feel crammed. */
.brand-text{display:flex; flex-direction:column; align-items:flex-start;
  gap:.1rem; min-width:0}
.dateline{font-weight:500; color:var(--mute); font-size:.72rem;
  letter-spacing:.08em; text-transform:uppercase; white-space:nowrap}

.masthead-mid{
  display:flex; align-items:center; gap:1rem; margin-inline:auto;
  flex:none;
}
.pulse{color:var(--mute); font-size:.79rem; letter-spacing:.06em;
  text-transform:uppercase}

/* --- search ------------------------------------------------------------
   THE BOX IS A FIXED WIDTH AND THE FIELD GROWS INSIDE IT. That is the whole
   trick, and the first version got it wrong: a field that grows by widening
   its own box pushes everything after it along, so focusing the search
   shoved `Saved` sideways. Here the row is a fixed width with its contents
   pinned right, so growth is absorbed on the LEFT and nothing else moves. */
.find-row{
  display:flex; align-items:center; justify-content:flex-end; gap:.4rem;
  /* Widened from clamp(15rem, 26vw, 21rem) when the account button joined
     Saved: the search pill at rest plus two buttons need the room, and the
     row must not shrink the pill to make it. */
  flex:none; width:clamp(21rem, 34vw, 27rem);
}
.find-box{
  /* GROWS ONLY WHEN THE INPUT HAS FOCUS -- `:has(.find:focus)`, not
     `:focus-within`. This was the × bug, and it took three tries to see:
     mousing DOWN on the × gave the × focus, `:focus-within` fired, the box
     began growing leftward, the chip at its left edge slid out from under
     the cursor, and mouse-UP landed on the chip text -- so the click
     resolved to "reopen the field", not "clear". A precise click on the ×
     produced an open field still holding the terms.

     It could not be reproduced in the in-app browser pane, whose CSS
     transitions are frozen while it is hidden -- nothing moved there, so
     the click always landed. That froze measurement also produced a wrong
     diagnosis ("rem to % will not interpolate") that is recorded in
     PROGRESS so nobody chases it again.

     flex-grow rather than width so growth takes only the free space beside
     Saved and can never overlap it. */
  position:relative; display:flex; align-items:center;
  flex:0 0 8.5rem; min-width:0;
  height:2rem; padding:0 .2rem 0 .7rem;
  border:1px solid var(--line); border-radius:999px;
  background:transparent; cursor:text;
  transition:flex-grow .3s var(--ease), border-color .25s var(--ease);
}
.find-box:hover{border-color:var(--line-2)}
.find-box:has(> .find:focus){flex-grow:1; border-color:var(--amber)}
/* Holding a search, the box is back at its REST width with the chip inside
   and the terms clipped -- his rule. A `[data-has]` rule that widened it sat
   here for one version and was the bug he saw: the field never shrank back
   after Enter. There is deliberately no such rule now; only focus widens. */
.find{
  flex:1; min-width:0; background:transparent; color:var(--ink);
  border:0; padding:0; font:inherit; font-size:.8rem; outline:none;
}
.find::placeholder{color:var(--mute)}
.find::-webkit-search-cancel-button{display:none}

/* The terms, INSIDE the field. The × is inline at the chip's left rather
   than overlapping its corner: a corner badge cannot be drawn whole inside
   a rounded, bordered box -- it either clips against the border or hangs
   outside it, and the first version clipped. Inline shows all of it.
   The chip and the × share a hairline so they read as ONE object. */
.chip-term{
  display:inline-flex; align-items:center; gap:.3rem;
  min-width:0; max-width:100%;
  border:1px solid var(--line); border-radius:999px;
  padding:.1rem .5rem .1rem .18rem;
  font-size:.74rem; color:var(--mute);
  cursor:pointer;
  transition:color .2s var(--ease), border-color .2s var(--ease);
}
.chip-term > span{overflow:hidden; text-overflow:ellipsis; white-space:nowrap}
.chip-term:hover{color:var(--ink); border-color:var(--line-2)}
.chip-x{
  /* THE HIT AREA IS NOT THE CIRCLE. The circle is 15px; a control that
     small beside a 60px chip gets missed, and a miss lands on the chip,
     which -- by spec -- reopens the field with the terms still in it. That
     read as "clear does nothing". So the button is a 1.7rem square and the
     circle is drawn inside it; the visible mark did not change, the target
     did. */
  display:grid; place-items:center; flex:none;
  width:1.7rem; height:1.7rem; margin:-.4rem 0 -.4rem -.35rem;
  padding:0; border:0; background:transparent; color:var(--mute);
  cursor:pointer;
}
.chip-x > span{
  display:grid; place-items:center; width:.95rem; height:.95rem;
  border:1px solid var(--line); border-radius:50%;
  font-size:.68rem; line-height:1;
  transition:color .2s var(--ease), border-color .2s var(--ease);
}
.chip-x:hover > span{border-color:var(--amber); color:var(--amber)}
/* The supply chain, running. Every verb here is something that has to
   happen before a cup exists -- and "brewing" is deliberately NOT among
   them, because that is the step the reader performs by pressing Fresh
   brew. The chain ends at their hand. */
.pulse{display:inline-flex; align-items:center; gap:.45rem;
  min-width:10.5rem}
.tick-word{display:inline-block; will-change:transform, opacity}
.tick-word.out{opacity:0; transform:translateY(-6px);
  transition:opacity .28s var(--ease), transform .28s var(--ease)}
.tick-word.in{animation:tickIn .42s var(--ease-out)}
@keyframes tickIn{
  from{opacity:0; transform:translateY(7px)}
  to{opacity:1; transform:none}
}
.pulse i{
  width:7px; height:7px; border-radius:50%; background:var(--leaf);
  box-shadow:0 0 0 0 color-mix(in srgb, var(--leaf) 60%, transparent);
  animation:beat 2.6s var(--ease) infinite;
}
@keyframes beat{
  0%{box-shadow:0 0 0 0 color-mix(in srgb, var(--leaf) 55%, transparent)}
  70%{box-shadow:0 0 0 9px transparent}
  100%{box-shadow:0 0 0 0 transparent}
}

.masthead-actions{display:flex; gap:.4rem; flex:none}
.ghost-btn{
  display:inline-flex; align-items:center; gap:.45rem;
  background:transparent; border:1px solid var(--line-2); color:var(--ink-soft);
  padding:.42rem .8rem; border-radius:999px; font-size:.84rem; font-weight:500;
  transition:border-color .3s var(--ease), color .3s var(--ease),
             background .3s var(--ease), transform .3s var(--ease);
}
.ghost-btn:hover{border-color:var(--amber); color:var(--amber); transform:translateY(-1px)}
.ghost-btn[aria-pressed="true"]{background:var(--amber); border-color:var(--amber); color:#fff}
.ghost-btn b{font-variant-numeric:tabular-nums}
.ghost-btn .ico{font-size:1em; line-height:1}
.ghost-btn.spin .ico{animation:spin .9s var(--ease-out)}
@keyframes spin{to{transform:rotate(360deg)}}

main{position:relative; z-index:2}

/* ================================================================ lead */
.lead{
  position:relative; width:100%; min-height:min(86vh, 760px);
  display:flex; align-items:flex-end; overflow:hidden;
  background:#120d09; isolation:isolate;
}
.lead-media{position:absolute; inset:0; z-index:-2; overflow:hidden}
.lead-media img{
  width:100%; height:120%; object-fit:cover;
  /* Taller than the box so it can drift without exposing an edge. */
  transform:translate3d(0, var(--par, 0px), 0) scale(1.06);
  will-change:transform;
}
.lead::after{
  content:""; position:absolute; inset:0; z-index:-1;
  background:
    linear-gradient(to top, rgba(10,7,5,.94) 0%, rgba(10,7,5,.72) 32%,
                    rgba(10,7,5,.25) 62%, rgba(10,7,5,.45) 100%),
    radial-gradient(120% 80% at 20% 100%, rgba(192,106,46,.22), transparent 60%);
}
.lead-body{
  width:var(--wrap); margin:0 auto; padding:clamp(2rem,6vh,5rem) 0 clamp(2.2rem,7vh,4.5rem);
  color:#fdf9f4; position:relative;
}
.lead h1{
  font-family:Fraunces, Georgia, serif;
  font-size:clamp(2.3rem, 6.1vw, 5rem); line-height:1.02;
  letter-spacing:-.032em; font-weight:600; margin:.55rem 0 .8rem;
  font-variation-settings:"opsz" 144, "SOFT" 30;
  text-wrap:balance; max-width:19ch;
  text-shadow:0 2px 40px rgba(0,0,0,.4);
}
.lead .dek{
  font-size:clamp(1rem, 1.5vw, 1.22rem); line-height:1.55;
  max-width:56ch; color:rgba(255,250,244,.9); margin:0 0 1.15rem;
}
.lead .meta{color:rgba(255,250,244,.62)}
.lead .kicker{color:var(--amber-lit)}
.lead .save{color:rgba(255,250,244,.75); border-color:rgba(255,255,255,.3)}
.lead .save:hover{color:#fff; border-color:#fff}

.kicker{
  font-size:.74rem; font-weight:600; letter-spacing:.15em;
  text-transform:uppercase; color:var(--amber);
}
.meta{
  display:flex; flex-wrap:wrap; align-items:center; gap:.5rem;
  font-size:.82rem; color:var(--mute);
}
.meta .dot{opacity:.45}
.meta .src{font-weight:600; color:var(--ink-soft)}
.lead .meta .src{color:rgba(255,250,244,.92)}

/* ============================================================ sections */
.features-wrap, .river-wrap{width:var(--wrap); margin:0 auto}
.features-wrap{padding:clamp(2.5rem,6vh,4.5rem) 0 0}
.river-wrap{padding:clamp(2rem,5vh,3.5rem) 0 4rem}

.rule{
  display:flex; align-items:center; gap:1rem; margin:0 0 1.6rem;
  font:600 .78rem/1 Inter, sans-serif; letter-spacing:.16em;
  text-transform:uppercase; color:var(--mute);
}
.rule::after{content:""; flex:1; height:1px; background:var(--line)}
.rule span{flex:none}

/* ============================================================ features */
.features{
  display:grid; gap:var(--gap);
  grid-template-columns:repeat(auto-fit, minmax(min(100%, 270px), 1fr));
}
.features .card-media{
  aspect-ratio:16/10; border-radius:12px; overflow:hidden; display:block;
  background:var(--paper-2); box-shadow:var(--shadow-sm);
}
.features .card-media img{width:100%; height:100%; object-fit:cover;
  transition:transform .9s var(--ease-out), filter .5s var(--ease)}
.features .card:hover .card-media img{transform:scale(1.055)}
.features h3{
  font-family:Fraunces, Georgia, serif; font-weight:600;
  font-size:clamp(1.15rem, 1.7vw, 1.45rem); line-height:1.16;
  letter-spacing:-.018em; margin:.7rem 0 .35rem; text-wrap:balance;
  font-variation-settings:"opsz" 60;
}
.features .dek{
  margin:0 0 .55rem; color:var(--ink-soft); font-size:.93rem; line-height:1.55;
  /* No line clamp. His rule (2026-09-13): no display ends in "...". The
     teaser is one or two sentences by the prompt; the card grows to it. */
}

/* =============================================================== river */
.river{list-style:none; margin:0; padding:0}
.river-nav{display:flex; gap:.3rem; justify-content:center; align-items:center;
  padding:1.6rem 0 0; flex-wrap:wrap}
.river-nav button{
  min-width:2.2rem; height:2.2rem; padding:0 .6rem; border-radius:999px;
  background:transparent; border:1px solid var(--line-2); color:var(--ink-soft);
  font:inherit; font-size:.86rem; cursor:pointer;
  transition:border-color .25s var(--ease), color .25s var(--ease);
}
.river-nav button:hover{border-color:var(--amber); color:var(--amber)}
.river-nav button[aria-current="page"]{background:var(--amber); border-color:var(--amber); color:#fff}
.river-nav button:disabled{opacity:.35; cursor:default}
.river .card{
  display:grid; grid-template-columns:132px 1fr auto; gap:1.1rem;
  align-items:start; padding:1.15rem 0; border-top:1px solid var(--line);
}
.river li:first-child .card{border-top:0}
.river .card-media{
  width:132px; aspect-ratio:4/3; border-radius:9px; overflow:hidden;
  background:var(--paper-2);
}
.river .card-media img{width:100%; height:100%; object-fit:cover;
  transition:transform .8s var(--ease-out)}
.river .card:hover .card-media img{transform:scale(1.06)}
.river h3{
  font-family:Fraunces, Georgia, serif; font-weight:600; font-size:1.16rem;
  line-height:1.24; letter-spacing:-.014em; margin:0 0 .3rem;
  text-wrap:balance; font-variation-settings:"opsz" 40;
}
.river .dek{
  margin:0 0 .45rem; color:var(--ink-soft); font-size:.9rem;
  /* No line clamp -- see .features .dek. */
}

.card{position:relative}
.card h3 a{
  background-image:linear-gradient(var(--amber), var(--amber));
  background-size:0% 1.5px; background-position:0 92%;
  background-repeat:no-repeat;
  transition:background-size .45s var(--ease-out), color .3s var(--ease);
}
.card:hover h3 a{background-size:100% 1.5px}

/* Read stories recede. The page should look different once you have used it
   -- that is what makes coming back feel like progress rather than repetition. */
.card.read{opacity:.58}
.card.read:hover{opacity:.85}
.card.read .kicker::after{content:" · read"; color:var(--mute)}

.save{
  background:none; border:1px solid var(--line-2); color:var(--mute);
  width:32px; height:32px; border-radius:50%; line-height:1; flex:none;
  display:grid; place-items:center; font-size:.95rem;
  transition:all .3s var(--ease);
}
.save:hover{border-color:var(--amber); color:var(--amber); transform:scale(1.08)}
.save[aria-pressed="true"]{background:var(--amber); border-color:var(--amber); color:#fff}
.features .save, .lead .save{position:absolute; top:.7rem; right:.7rem;
  background:color-mix(in srgb, var(--paper) 80%, transparent);
  backdrop-filter:blur(8px)}
.lead .save{position:static; margin-top:.2rem}

/* ====================================================== scroll reveal */
.reveal{opacity:0; transform:translateY(22px)}
.reveal.in{opacity:1; transform:none;
  transition:opacity .75s var(--ease-out), transform .75s var(--ease-out)}

.empty-note{color:var(--mute); padding:2.5rem 0; text-align:center}
.foot{
  width:var(--wrap); margin:0 auto; padding:2.5rem 0 4rem;
  border-top:1px solid var(--line); color:var(--mute); font-size:.88rem;
}
.foot strong{font-family:Fraunces, Georgia, serif; color:var(--ink)}
.small{font-size:.8rem}

/* ====================================================== reveal in place */
/* Three slots, three shapes. Collapsed they are all headline-and-picture;
   open, each uses the room it actually has. Nothing covers the page and no
   button ever appears. */

.card{cursor:pointer}
.card.open{cursor:default}

.card-teaser{display:none}
.card.open .card-teaser{display:block}
.teaser{margin:0; color:var(--ink-soft); font-size:.95rem; line-height:1.62;
  animation:teaserIn .55s var(--ease-out) both}
@keyframes teaserIn{
  from{opacity:0; transform:translateY(10px)}
  to{opacity:1; transform:none}
}

/* ------------------------------------------------- the source IS the link */
/* There was never a need for a "Read at ..." button. The publisher's name is
   already on every card; collapsed it reads as attribution, open it becomes
   the way out -- larger, amber, and clickable. Same element, two states. */
.src{
  font-weight:600; color:var(--ink-soft); text-decoration:none;
  pointer-events:none;
  transition:font-size .4s var(--ease), color .3s var(--ease);
}
.card.open .src, .lead.open .src{
  pointer-events:auto; color:var(--amber); font-size:1.12rem; font-weight:700;
}
.card.open .src::after, .lead.open .src::after{content:" \2197"}
.card.open .src:hover, .lead.open .src:hover{
  color:var(--amber-lit); text-decoration:underline; text-underline-offset:4px;
}
.lead .src{color:rgba(255,250,244,.92)}
.lead.open .src{color:var(--amber-lit); font-size:1.24rem}

/* ------------------------------------------------------------ the river */
/* Open: the picture grows by about two thirds -- not to half the row -- the
   teaser sits beside it centred on the picture's height, and the text drops
   below both. The width is animated rather than the grid track, because a
   width transition is smooth everywhere and a track transition is not. */
.river .card{
  display:grid; gap:1.1rem; align-items:start;
  grid-template-columns:auto 1fr auto;
  grid-template-areas:"media text save";
}
.river .card-media{grid-area:media; width:132px;
  transition:width .5s var(--ease-out)}
.river .card-text{grid-area:text}
.river .save{grid-area:save}
.river .card-teaser{grid-area:teaser}

.river .card.open{
  grid-template-areas:
    "media teaser save"
    "text  text   text";
  padding-bottom:1.5rem;
}
.river .card.open .card-media{width:220px}       /* 132 -> 220 = +67% */
.river .card.open .card-teaser{align-self:center}
.river .card.open h3{font-size:1.34rem}
/* The text does not teleport from column two to row two -- it arrives. */
.river .card.open .card-text{animation:dropIn .5s var(--ease-out) both}
@keyframes dropIn{
  from{opacity:0; transform:translateY(-10px)}
  to{opacity:1; transform:none}
}

/* ---------------------------------------------------------- also today */
/* The teaser lies over the FOOT OF THE PICTURE. It shares the picture's grid
   cell and aligns to its end -- the previous version positioned it against
   the card, so bottom:0 put it under the text it was meant to sit above. */
.features .card{
  display:grid; grid-template-areas:"media" "text"; align-content:start;
  position:relative;
}
.features .card-media{grid-area:media}
.features .card-text{grid-area:text}
.features .card-teaser{grid-area:media; align-self:end; z-index:2;
  padding:2.6rem .9rem .85rem; border-radius:0 0 12px 12px;
  background:linear-gradient(to top, rgba(10,7,5,.95) 40%, transparent);}
.features .card.open .teaser{color:rgba(255,250,244,.96); font-size:.9rem}
.features .card.open .card-media img{transform:scale(1.04)}

/* --------------------------------------------------------------- lead */
.lead .card-teaser{display:none}
.lead.open .card-teaser{display:block; margin:.1rem 0 .7rem}
.lead .teaser{color:rgba(255,250,244,.94);
  font-size:clamp(1rem,1.5vw,1.18rem); max-width:58ch}
.lead .meta{color:rgba(255,250,244,.62)}

@media (max-width:720px){
  .river .card, .river .card.open{
    grid-template-columns:96px 1fr;
    grid-template-areas:"media text" "teaser teaser";
  }
  .river .card.open .card-media{width:96px}
  .river .card.open h3{font-size:1.16rem}
  .river .save{display:none}
}

@media (max-width:720px){
  .river .card, .river .card.open{
    grid-template-columns:96px 1fr;
    grid-template-areas:"media text" "teaser teaser";
  }
  .river .card.open h3{font-size:1.2rem}
  .river .save{display:none}
}

@media (max-width:720px){
  .river .card.open .card-media{width:96px}
}

@media (max-width:720px){
  .masthead-mid{display:none}
  .river .card{grid-template-columns:96px 1fr; grid-template-areas:"m t" "m t"}
  .river .card-media{width:96px; grid-area:m}
  .river .save{position:absolute; right:0; bottom:1.15rem}
  .lead{min-height:74vh}
}

/* Motion is a courtesy, not a requirement. */
@media (prefers-reduced-motion: reduce){
  *,*::before,*::after{
    animation-duration:.001ms !important; animation-iteration-count:1 !important;
    transition-duration:.001ms !important; scroll-behavior:auto !important;
  }
  .reveal{opacity:1; transform:none}
  .lead-media img{transform:none !important}
}

/* ======================================================== topic drawer */
/* A TAB HANGING FROM THE MASTHEAD, like an inverted folder tab. It is a
   child of the bar, positioned against the bar's bottom edge, so it is
   always there because the bar is always there, and it never lands on the
   photograph as a foreign object -- it belongs to the chrome above it.

   Its left edge is the CONTENT COLUMN'S left edge: the same padding the
   masthead itself uses, which is the line the logo, every card and the river
   all start on. Not the window edge, not centred. */
.drawer{
  position:absolute; left:max(4vw, calc((100vw - var(--wrap)) / 2));
  top:100%; margin-top:-1px;       /* sits ON the bar's bottom rule */
  z-index:1;
}
.drawer-tab{
  position:relative; z-index:2;
  display:flex; align-items:center; gap:.5rem;
  width:12.5rem;                   /* CONSTANT, and the PANEL is the same
                                      width: opening changes the height of
                                      the object, never its outline */
  height:2.2rem; padding:0 .95rem 0 .6rem;
  /* THE MASTHEAD'S OWN GROUND, not a darker one. A different fill made the
     tab read as a thing attached to the bar; the same fill makes it read
     as part of the bar, which it is. The hairline carries the shape. */
  background:color-mix(in srgb, var(--paper) 82%, transparent);
  backdrop-filter:saturate(1.5) blur(14px);
  -webkit-backdrop-filter:saturate(1.5) blur(14px);
  border:1px solid var(--line-2); border-top:0;
  border-radius:0 0 9px 9px;
  color:var(--ink-soft); font:inherit; font-size:.86rem; font-weight:500;
  letter-spacing:.04em; cursor:pointer; text-align:left;
  transition:color .2s var(--ease), border-color .2s var(--ease),
             background .2s var(--ease);
}
.drawer-tab:hover{color:var(--ink); border-color:var(--amber)}
.drawer[data-topic]:not([data-topic="all"]) .drawer-tab{
  color:var(--ink); border-color:var(--amber)}
/* The ×'s slot is held whether or not it holds one, so the label starts at
   the same x with or without a topic selected. */
.tab-slot{display:grid; place-items:center; width:1rem; height:1rem; flex:none}
.tab-x{
  display:grid; place-items:center; flex:none;
  width:1.6rem; height:1.6rem; margin:-.3rem; padding:0;
  border:0; background:transparent; color:var(--mute); cursor:pointer;
}
.tab-x > span{
  display:grid; place-items:center; width:1rem; height:1rem;
  border:1px solid var(--line-2); border-radius:50%;
  background:var(--paper); font-size:.7rem; line-height:1;
  transition:color .2s var(--ease), border-color .2s var(--ease);
}
.tab-x:hover > span{border-color:var(--amber); color:var(--amber)}
.tab-label{white-space:nowrap; overflow:hidden; text-overflow:ellipsis;
  min-width:0}

/* OPEN, THE TAB AND THE PANEL ARE ONE SHAPE. The tab loses its bottom
   corners and its bottom border takes the panel's colour, so the eye sees a
   single object that has been pulled down -- the way a browser's active tab
   merges with its page -- rather than a tab with a menu floating under it. */
.drawer[data-open="true"] .drawer-tab{
  border-radius:0; background:var(--paper);
  border-bottom-color:var(--paper); box-shadow:none;
}
.drawer-panel{
  position:absolute; left:0; top:100%; margin-top:-1px;
  display:flex; flex-direction:column; gap:.1rem;
  width:12.5rem; padding:.5rem 0 .55rem;
  background:var(--paper);
  border:1px solid var(--line-2); border-top:0; border-radius:0 0 9px 9px;
  box-shadow:var(--shadow-md);
  animation:drawerOut .18s var(--ease-out);
}
@keyframes drawerOut{
  from{opacity:0; transform:translateY(-4px)}
  to{opacity:1; transform:none}
}
/* The About drawer: Topics mirrored across the page. Same object, pinned
   to the right line of the content column; its panel opens under it,
   right-aligned, and its entries are links styled as the topic rows. */
.drawer-right{left:auto; right:max(4vw, calc((100vw - var(--wrap)) / 2))}
.drawer-right .drawer-tab{width:8rem; padding:0 .95rem}
.drawer-right .drawer-panel{left:auto; right:0; width:8rem}
.drawer-right .topic{padding-left:.85rem; text-decoration:none}
.acct-link{color:var(--amber); text-decoration:underline; text-underline-offset:2px}
.acct-link:hover{color:var(--amber-lit)}

/* ---------------------------------------------------------- text pages */
/* About and Contact: the masthead's brand and one column of prose in the
   site's own type. Nothing else -- a text page is not the front page. */
.page{width:min(64ch, 92vw); margin:0 auto; padding:clamp(2rem,5vh,3.5rem) 0 4rem}
.page h1{
  font-family:Fraunces, Georgia, serif; font-weight:600;
  font-size:clamp(1.9rem, 3.4vw, 2.6rem); line-height:1.1; letter-spacing:-.02em;
  margin:0 0 1rem; text-wrap:balance; font-variation-settings:"opsz" 96;
}
.page h2{
  font-family:Fraunces, Georgia, serif; font-weight:600; font-size:1.25rem;
  margin:2rem 0 .5rem; letter-spacing:-.01em;
}
.page p{margin:0 0 1rem; color:var(--ink-soft); line-height:1.7}
.page p strong{color:var(--ink)}
.page .callout{
  margin:1.6rem 0; padding:1rem 1.2rem; border-left:3px solid var(--amber);
  background:var(--paper-2); border-radius:0 10px 10px 0;
}
.page .callout p{margin:0 0 .6rem}
.page .callout p:last-child{margin:0}
.page a{color:var(--amber); text-decoration:underline; text-underline-offset:2px}
.page .back{display:inline-block; margin-top:2rem; font-size:.9rem}
.page .muted{color:var(--mute)}
.contact{display:flex; flex-direction:column; gap:.9rem; margin:1.2rem 0 1.6rem}
.cf-field{display:flex; flex-direction:column; gap:.3rem; font-size:.84rem; color:var(--mute)}
.cf-field input, .cf-field textarea{
  font:inherit; font-size:.95rem; color:var(--ink); background:transparent;
  border:1px solid var(--line-2); border-radius:12px; padding:.6rem .8rem; outline:none;
  transition:border-color .25s var(--ease);
}
.cf-field textarea{resize:vertical; min-height:9rem; line-height:1.55}
.cf-field input:focus, .cf-field textarea:focus{border-color:var(--amber)}
.cf-trap{position:absolute; left:-10000px; width:1px; height:1px; overflow:hidden}
.cf-err{margin:0; font-size:.86rem; color:#a8442f}
.cf-row{display:flex; align-items:center; gap:.8rem}
.topic{
  display:block; width:100%; text-align:left; white-space:nowrap;
  background:transparent; border:0; border-radius:0;
  padding:.44rem .95rem .44rem 2.1rem; cursor:pointer;
  color:var(--ink-soft); font:inherit; font-size:.92rem;
  transition:background .18s var(--ease), color .18s var(--ease);
}
.topic:hover{background:color-mix(in srgb, var(--amber) 10%, transparent);
  color:var(--ink)}
.topic[aria-current="true"]{color:var(--amber); font-weight:600}

/* ========================================================= nothing found */
.nothing{padding:3.5rem 1rem 4rem; text-align:center}
.nothing-head{
  font-family:Fraunces, Georgia, serif; font-weight:600;
  font-size:clamp(1.3rem, 3vw, 1.75rem); margin:0 0 .5rem; color:var(--ink);
}
.nothing-sub{color:var(--ink-soft); margin:0 0 1.6rem; font-size:.94rem}
.nothing-sub span{color:var(--ink)}
.nothing-topics{display:flex; gap:.4rem; justify-content:center;
  flex-wrap:wrap; margin-bottom:1.4rem}
.linky{
  background:none; border:0; border-bottom:1px solid var(--line-2);
  color:var(--mute); font:inherit; font-size:.82rem; padding:0 0 1px;
  cursor:pointer; transition:color .2s var(--ease)
}
.linky:hover{color:var(--amber); border-color:var(--amber)}

.api-banner{position:sticky; top:0; z-index:30; background:#a8442f; color:#fff;
  padding:.6rem 1rem; font-size:.88rem; text-align:center}

/* --- account ---------------------------------------------------------
   The card hangs from the masthead's bottom edge under the account button,
   the mirror of the topic drawer on the left: same paper, same hairline,
   same arrival. Two faces, one shown at a time. The form's inputs borrow
   the search field's pill so the chrome has one vocabulary. */
.acct-btn .ico{font-size:1.05em}
.acct-btn[aria-pressed="true"]{background:transparent; border-color:var(--amber); color:var(--amber)}
.acct{
  position:absolute; right:max(4vw, calc((100vw - var(--wrap)) / 2)); top:100%;
  margin-top:-1px; width:17rem; padding:.9rem 1rem 1rem;
  background:var(--paper); border:1px solid var(--line-2); border-top:0;
  border-radius:0 0 9px 9px; box-shadow:var(--shadow-md);
  animation:drawerOut .18s var(--ease-out); z-index:25;
}
.acct-face{display:flex; flex-direction:column; gap:.6rem}
.acct-why{margin:0; font-size:.84rem; color:var(--amber); font-weight:500}
.acct-field{display:flex; flex-direction:column; gap:.25rem; font-size:.74rem; color:var(--mute)}
.acct-field input{
  height:2rem; padding:0 .7rem; font:inherit; font-size:.84rem; color:var(--ink);
  background:transparent; border:1px solid var(--line); border-radius:999px; outline:none;
  transition:border-color .25s var(--ease);
}
.acct-field input:hover{border-color:var(--line-2)}
.acct-field input:focus{border-color:var(--amber)}
.acct-err{margin:0; font-size:.8rem; color:#a8442f}
.acct-row{display:flex; align-items:center; gap:.7rem; flex-wrap:wrap}
.acct-note{margin:0; font-size:.74rem; color:var(--mute)}
.acct-name{margin:0; font-size:.9rem; line-height:1.35}
.acct-name .small{font-size:.76rem; color:var(--mute)}
.acct-sub{display:flex; flex-direction:column; gap:.6rem; padding-top:.5rem; border-top:1px solid var(--line)}
.ghost-btn.solid{background:var(--amber); border-color:var(--amber); color:#fff}
.ghost-btn.solid:hover{background:var(--amber-lit); border-color:var(--amber-lit); color:#fff; transform:none}

/* ==========================================================================
   THE PHONE — a different application, not a narrow column.

   His design, 2026-09-13: one story a screen, the photograph taking
   everything between two bars, a swipe that locks onto the next story.

   TWO THINGS HERE ARE NOT STYLE CHOICES AND MUST NOT BE "TIDIED":

   1. `dvh`, NEVER `vh`. On iOS Safari `100vh` is the height the viewport
      would have IF the address bar were hidden — taller than what you can
      see — so a "full screen" panel is cut off at the bottom and every
      snap lands a bar's height out. `100dvh` is the visible height and
      follows the bar as it collapses. This is the single most common bug
      in this kind of layout.

   2. The snapping is the BROWSER'S (`scroll-snap-type`), not JavaScript
      imitating it. Real momentum, real rubber-banding, the platform's own
      curve — anything hand-written feels wrong on a phone and fights the
      OS. `overscroll-behavior:contain` stops a swipe at the end of the
      stack from pulling the whole page into a bounce.
   ========================================================================= */
.phone{display:none}

@media (max-width:720px){
  /* The desktop arrangement is not narrowed, it is ABSENT. */
  .masthead, #main, .foot{display:none !important}
  .phone{display:block; position:fixed; inset:0; background:var(--ink)}
  html, body{overflow:hidden; height:100%; overscroll-behavior:none}

  /* -- the stack ------------------------------------------------------- */
  .ph-stack{position:absolute; inset:0; overflow-y:auto; overscroll-behavior-y:contain;
    scroll-snap-type:y mandatory; -webkit-overflow-scrolling:touch;
    scrollbar-width:none}
  .ph-stack::-webkit-scrollbar{display:none}
  .ph-panel{position:relative; height:100dvh; scroll-snap-align:start;
    scroll-snap-stop:always;          /* never skip two stories on one flick */
    display:grid; overflow:hidden}
  .ph-panel img{position:absolute; inset:0; width:100%; height:100%;
    object-fit:cover; object-position:center}
  /* The photograph is the background of the type, so the type needs its own
     ground: a gradient, not a flat panel, or the picture stops at a line. */
  .ph-panel::after{content:""; position:absolute; inset:0; pointer-events:none;
    background:linear-gradient(to bottom, rgba(10,7,5,.55) 0, rgba(10,7,5,0) 22%,
      rgba(10,7,5,0) 38%, rgba(10,7,5,.72) 78%, rgba(10,7,5,.92) 100%)}
  .ph-panel.no-photo{background:linear-gradient(160deg,#2a1d14,#17110d)}

  /* -- the type over it ------------------------------------------------ */
  .ph-text{position:absolute; left:0; right:0;
    bottom:calc(var(--ph-bar) + env(safe-area-inset-bottom) + .3rem);
    padding:0 1.1rem; z-index:2; color:#fff}
  .ph-kicker{font-size:.68rem; letter-spacing:.14em; text-transform:uppercase;
    color:rgba(255,255,255,.78); margin:0 0 .4rem}
  .ph-title{font-family:var(--display,Fraunces),Georgia,serif; font-weight:600;
    font-size:clamp(1.45rem,5.6vw,2rem); line-height:1.16; margin:0;
    text-shadow:0 2px 18px rgba(0,0,0,.45)}
  .ph-meta{margin:.5rem 0 0; font-size:.76rem; color:rgba(255,255,255,.72)}
  /* Shut, the reveal takes no room and no time; open, it is what the tap
     was for. Height is not animated — `max-height` on text of unknown
     length either clips it or jumps — opacity and a small lift are. */
  .ph-more{margin:.7rem 0 0; opacity:0; transform:translateY(10px);
    transition:opacity .22s ease, transform .22s ease; pointer-events:none;
    max-height:0; overflow:hidden}
  /* Bounded at both ends: the teaser grows UPWARD from the title, so an
     unbounded one runs under the top bar. Past this it scrolls inside
     itself rather than off the screen. */
  .ph-panel.open .ph-more{opacity:1; transform:none; pointer-events:auto;
    max-height:min(42dvh, 26rem); overflow-y:auto}
  .ph-teaser{margin:0; font-size:.95rem; line-height:1.5;
    color:rgba(255,255,255,.93)}
  .ph-link{display:inline-flex; align-items:center; gap:.4rem; margin-top:.8rem;
    padding:.6rem .95rem; border-radius:999px; background:var(--amber,#b4632a);
    color:#fff; text-decoration:none; font-size:.85rem; font-weight:600}
  .ph-hint{margin:.55rem 0 0; font-size:.72rem; color:rgba(255,255,255,.6)}
  .ph-panel.open .ph-hint{display:none}

  /* -- the two bars ---------------------------------------------------- */
  /* They float over the photograph rather than boxing it in: the picture is
     meant to reach every edge. `env(safe-area-inset-*)` keeps them clear of
     the notch and the home indicator on an iPhone. */
  /* THE BAR'S REAL HEIGHT, and it must be real or the story's text hides
     behind it. The star is 54px, with .5rem of padding above and below:
     that is ~4.6rem, not the 3.2 this said when the controls were smaller.
     In Safari (not added to the Home Screen) the browser's own toolbar sits
     over the bottom too, and `env(safe-area-inset-bottom)` does NOT account
     for it -- hence the second value. His report, 2026-09-15: the link to
     the article was landing under the icons and could not be read. */
  :root{--ph-bar:4.6rem}
  @media (display-mode: browser){ :root{--ph-bar:6.1rem} }
  .ph-top, .ph-bottom{position:absolute; left:0; right:0; z-index:4;
    display:flex; align-items:center; justify-content:space-between;
    padding:.5rem .8rem; gap:.5rem}
  .ph-top{top:0; padding-top:calc(.5rem + env(safe-area-inset-top))}
  .ph-bottom{bottom:0; padding-bottom:calc(.5rem + env(safe-area-inset-bottom))}
  /* IN SAFARI, NOT ON THE HOME SCREEN, the browser's own toolbar sits over
     the bottom of the page and `safe-area-inset-bottom` does not account
     for it -- the star ends up pressed against the address bar (seen in his
     screenshot, 2026-09-13). Added to the Home Screen the toolbar is gone
     and this rule does not apply, which is how the layout is meant to be
     seen; it should still be usable in the browser. */
  @media (display-mode: browser){
    .ph-bottom{padding-bottom:calc(1.5rem + env(safe-area-inset-bottom))}
  }
  .ph-brand{display:flex; align-items:center; gap:.5rem; text-decoration:none;
    color:#fff; font-size:1rem; font-weight:700; letter-spacing:.01em;
    /* `flex:0 1 auto`, NOT `1 1 auto`. Growing, the link filled the whole
       middle of the bar, so every tap on what LOOKS like empty bar was a
       tap on the mark -- "tap the top to go back to the first story"
       opened Info instead. The handler was firing; the tap never reached
       the bar. (His report, 2026-09-13.) */
    text-shadow:0 1px 10px rgba(0,0,0,.5); min-width:0; flex:0 1 auto}
  /* The mark is black line art on a cream disc, and at the size the first
     build used it read as a smudge -- there is a floor under which a logo
     stops being a logo and becomes a dot. 40px, and the disc is what makes
     it legible over a photograph. */
  /* The bean from the browser tab, shadow and all -- no cream disc. The
     line-art logo it replaced was a smudge at this size. */
  .ph-brand img{width:36px; height:36px; flex:0 0 auto}
  .ph-brand span{white-space:nowrap; overflow:hidden; text-overflow:ellipsis}
  .ph-brand em{font-style:normal; color:var(--amber,#e08a42)}
  .ph-tools{display:flex; align-items:center; gap:.35rem; flex:0 0 auto}

  /* Every control is at least 48px and the GLYPHS ARE LARGE. His note,
     2026-09-13: the first build was legible but timid -- an icon sized for
     a mouse pointer is small for a thumb at arm's length, and a phone is
     held further from the eye than a laptop screen. */
  .ph-icon, .ph-badge, .ph-star{min-height:48px; min-width:48px; border:0;
    background:rgba(20,14,10,.42); backdrop-filter:blur(8px); color:#fff;
    border-radius:999px; line-height:1; cursor:pointer;
    display:grid; place-items:center}
  .ph-icon{font-size:1.5rem}
  /* ABSOLUTE SIZES, NEVER PERCENTAGES. An <svg> with no width/height
     attribute has NO INTRINSIC SIZE, so `width:58%` resolves against a
     parent that is itself being sized by its content -- the button grows to
     fit the SVG, the SVG grows to fit the button, and the browser settles
     it with the SVG's 300px default. The house came out the width of the
     screen (his screenshot, 2026-09-15). Rem sizes cannot loop.
     The buttons are also pinned so nothing inside them can stretch one. */
  .ph-icon, .ph-badge, .ph-star{flex:0 0 auto}
  .ph-icon svg, .ph-star svg, .ph-badge svg{fill:none; stroke:currentColor;
    stroke-width:2; stroke-linecap:round; stroke-linejoin:round}
  .ph-icon svg{width:1.55rem; height:1.55rem}
  .ph-star svg{width:1.85rem; height:1.85rem}
  /* The house reads smaller than a star at the same size -- it is a wider,
     emptier shape -- so it is given a little more. */
  .ph-home svg{width:2rem; height:2rem}
  .ph-badge svg.ph-person{width:1.75rem; height:1.75rem; stroke-width:1.9}
  .ph-badge{font-size:1.15rem; font-weight:700;
    border:2px solid rgba(255,255,255,.65)}
  .ph-badge.on{background:var(--amber,#b4632a); border-color:transparent}
  .ph-star{min-height:54px; min-width:54px; font-size:1.9rem}
  .ph-star.on{color:var(--amber-lit,#e6a765)}
  .ph-home{font-size:1.7rem}
  .ph-ico{font-size:1.2rem; opacity:.85; display:inline-flex;
    align-items:center}
  .ph-ico svg{width:1.25rem; height:1.25rem; fill:none; stroke:currentColor;
    stroke-width:2; stroke-linecap:round}

  /* -- the search, growing leftward ------------------------------------ */
  /* `flex:0 0 48px` and `min-width:0`, not a bare `width`. A flex item's
     automatic minimum size is its CONTENT, and the content here is a search
     input, whose intrinsic width in Safari is around 170px -- so a shut box
     can sit there holding the bar open and pushing the masthead's name out
     of its own bar. `overflow:hidden` usually zeroes that minimum; saying it
     outright costs nothing and does not depend on knowing it does. */
  .ph-find{display:flex; align-items:center; justify-content:flex-end;
    background:rgba(20,14,10,.42); backdrop-filter:blur(8px); border-radius:999px;
    transition:flex-basis .22s ease, width .22s ease;
    flex:0 0 48px; width:48px; min-width:0; overflow:hidden}
  .ph-find.open{flex-basis:min(64vw,340px)}
  .ph-find.open{width:min(64vw,340px); background:rgba(20,14,10,.72)}
  .ph-q{width:0; border:0; background:transparent; color:#fff;
    padding:0; min-width:0; outline:none}
  .ph-find.open .ph-q{width:100%; padding:0 .2rem 0 .9rem}
  .ph-q::placeholder{color:rgba(255,255,255,.55)}

  /* 16px, AND IT IS NOT A STYLE CHOICE. Safari on iOS zooms the whole page
     when a text field with a font under 16px takes focus, and it does NOT
     zoom back out afterwards -- the reader is left pinching to undo it. The
     other fix people reach for, `maximum-scale=1` on the viewport, works by
     disabling pinch-zoom entirely, which takes the feature away from anyone
     who needs it to read. Sizing the font is the fix with no victim.
     Reported by him on the first phone build, 2026-09-13. */
  .ph-q, .ph-intro-find input, .ph-field input{font-size:16px}

  /* -- the opening quarter --------------------------------------------- */
  /* Present on arrival, gone after the first swipe and not brought back.
     It is laid OVER the stack rather than pushing it: resizing the panels
     would move every snap point mid-scroll, which is how a snapping list
     starts landing between stories. */
  /* FULLY OPAQUE. The first build faded to transparent at its lower edge
     and the compact bar showed through it -- two mastheads at once, which
     reads as a rendering fault rather than a design. It is the top of the
     page: it should look like the top of the page. */
  .ph-intro{position:absolute; top:0; left:0; right:0; height:25dvh; z-index:6;
    display:flex; flex-direction:column; justify-content:center; gap:.55rem;
    padding:calc(.5rem + env(safe-area-inset-top)) 1rem .7rem;
    background:var(--ink,#17110d);
    transition:opacity .25s ease, transform .25s ease}
  .phone:not(.at-top) .ph-intro{opacity:0; transform:translateY(-14px);
    pointer-events:none}
  /* And the compact bar is not merely covered, it is OFF while the opening
     quarter has the screen: a control hidden under an opaque panel still
     takes taps meant for what is drawn on top of it. */
  .phone.at-top .ph-top{opacity:0; pointer-events:none}
  .ph-top{transition:opacity .2s ease}
  .ph-intro-row{display:flex; align-items:center; justify-content:space-between;
    gap:.8rem}
  .ph-intro-brand{display:flex; align-items:center; gap:.6rem; min-width:0;
    text-decoration:none; color:#fff}
  .ph-intro-lines{display:flex; flex-direction:column; gap:.15rem; min-width:0}
  .ph-intro-name{font-family:var(--display,Fraunces),Georgia,serif;
    font-size:1.28rem; font-weight:700; line-height:1.1}
  /* The bean carries its own shadow (bean.svg) -- no disc, nothing behind
     it. It is the mark in the browser tab, which is the one he recognises;
     `align-items:center` on the row centres it against BOTH lines. */
  .ph-intro-brand img{width:44px; height:44px; flex:0 0 auto}
  .ph-intro-brand em{font-style:normal; color:var(--amber-lit,#e08a42)}
  .ph-intro-acct{flex:0 0 auto; min-height:44px; padding:0 1rem; border-radius:999px;
    border:2px solid rgba(255,255,255,.6); background:rgba(20,14,10,.4);
    color:#fff; font:inherit; font-size:.92rem; font-weight:700; cursor:pointer;
    max-width:42vw; overflow:hidden; text-overflow:ellipsis; white-space:nowrap}
  .ph-intro-acct.on{background:var(--amber,#b4632a); border-color:transparent}
  /* The dateline, where a newspaper puts it: under the name it belongs to,
     not under the whole masthead. */
  .ph-intro-date{font-size:.74rem; letter-spacing:.09em;
    text-transform:uppercase; color:rgba(255,255,255,.62);
    white-space:nowrap; overflow:hidden; text-overflow:ellipsis}
  /* Skinnier: the field is one line of type, and the opening quarter has
     four things to fit into a quarter of a phone. */
  .ph-intro-find{display:flex; align-items:center; gap:.55rem;
    background:rgba(255,255,255,.13); border:1px solid rgba(255,255,255,.22);
    border-radius:999px; padding:.34rem .9rem}
  .ph-intro-find input{flex:1; min-width:0; border:0; background:transparent;
    color:#fff; outline:none}
  .ph-intro-find input::placeholder{color:rgba(255,255,255,.6)}
  /* The first story keeps the other three quarters: its photograph is
     pushed below the masthead rather than hidden behind it. */
  .phone.at-top .ph-stack .ph-panel:first-child img{top:25dvh; height:75dvh}
  .phone.at-top .ph-stack .ph-panel:first-child::after{top:25dvh}

  /* -- the topics band, always on -------------------------------------- */
  .ph-band{position:absolute; left:0; right:0; z-index:4;
    top:calc(3.6rem + env(safe-area-inset-top));
    display:flex; gap:.4rem; padding:.25rem .8rem;
    overflow-x:auto; scrollbar-width:none; -webkit-overflow-scrolling:touch;
    scroll-snap-type:x proximity; overscroll-behavior-x:contain;
    transition:transform .25s ease}
  .ph-band::-webkit-scrollbar{display:none}
  /* While the masthead has the top quarter, the band sits under IT. */
  .phone.at-top .ph-band{transform:translateY(calc(25dvh - 3.6rem
    - env(safe-area-inset-top)))}
  .ph-chip{flex:0 0 auto; scroll-snap-align:start; min-height:40px;
    padding:0 .95rem; border:0; border-radius:999px; cursor:pointer;
    background:rgba(20,14,10,.42); backdrop-filter:blur(8px); color:#fff;
    font:inherit; font-size:.92rem; font-weight:600; white-space:nowrap}
  /* WHITE, not amber. The band sits over a photograph and the amber
     chip read as an alert rather than as "you are here"; paper-white is
     the site's other colour and says the same thing quietly. */
  .ph-chip.on{background:rgba(255,255,255,.94); color:var(--ink,#17110d)}

  /* -- the sheets ------------------------------------------------------ */
  .ph-scrim{position:absolute; inset:0; z-index:5; background:rgba(8,5,3,.5);
    backdrop-filter:blur(2px)}
  .ph-sheet{position:absolute; left:0; right:0; bottom:0; z-index:6;
    background:var(--paper,#f7f1e8); color:var(--ink,#17110d);
    border-radius:18px 18px 0 0; padding:.5rem .9rem
      calc(1rem + env(safe-area-inset-bottom));
    max-height:76dvh; overflow-y:auto; box-shadow:0 -12px 40px rgba(0,0,0,.4)}
  .ph-grab{width:38px; height:4px; border-radius:999px; margin:.3rem auto .7rem;
    background:rgba(0,0,0,.18)}
  .ph-row{display:flex; align-items:center; justify-content:space-between;
    width:100%; min-height:48px; padding:.2rem .4rem; border:0; background:none;
    text-align:left; font:inherit; font-size:.98rem; color:inherit;
    text-decoration:none; border-bottom:1px solid rgba(0,0,0,.07); cursor:pointer}
  .ph-row:last-child{border-bottom:0}
  /* EVERY ROW IS ONE ROW. "Saved stories" was bolder than the rest, which
     made a menu of four equal choices look like one choice and three
     afterthoughts. Only the COUNT beside it is emphasised, because that is
     the part that changes. */
  .ph-row b{font-weight:700}
  .ph-row.on{color:var(--amber,#b4632a); font-weight:700}
  .ph-form{display:flex; flex-direction:column; gap:.6rem; padding:.3rem 0 .7rem}
  .ph-field{display:flex; flex-direction:column; gap:.25rem; font-size:.78rem;
    color:var(--mute,#7a6a58)}
  .ph-field input{font:inherit; font-size:1rem; padding:.6rem .7rem;
    border:1px solid rgba(0,0,0,.16); border-radius:10px; background:#fff;
    color:var(--ink,#17110d)}
  .ph-btn{min-height:46px; border:0; border-radius:999px; font:inherit;
    font-weight:700; background:var(--amber,#b4632a); color:#fff; cursor:pointer}
  .ph-err{margin:0; font-size:.82rem; color:#b3261e}
  .ph-note{margin:.2rem 0 .6rem; font-size:.82rem; color:var(--mute,#7a6a58)}
  /* The name is who the sheet belongs to, so it is the one thing in it that
     is bigger than a row. The address under it stays quiet. */
  .ph-who{margin:.1rem 0 .8rem; line-height:1.3}
  .ph-who b{font-family:var(--display,Fraunces),Georgia,serif;
    font-size:1.34rem; font-weight:700}
  .ph-who span{font-size:.84rem; color:var(--mute,#7a6a58)}
  .ph-empty{position:absolute; inset:0; display:grid; place-items:center;
    padding:2rem; text-align:center; color:rgba(255,255,255,.85); z-index:3}

  /* A phone that asks not to be animated gets no transitions, but the
     SNAPPING STAYS: it is how the layout works, not decoration. */
  @media (prefers-reduced-motion: reduce){
    .ph-find, .ph-more{transition:none}
  }
}

/* A quiet word at the foot of the page: "One moment…" when the server asks
   the reader to slow down. Opacity, not [hidden], so it can fade -- and
   pointer-events:none so it is never in the way of a click. */
.note{position:fixed; left:50%; bottom:1.4rem; transform:translateX(-50%) translateY(.4rem);
  background:var(--ink); color:var(--paper); padding:.5rem .9rem; border-radius:999px;
  font-size:.82rem; box-shadow:0 6px 24px rgba(0,0,0,.22); opacity:0;
  pointer-events:none; transition:opacity .18s ease, transform .18s ease; z-index:60}
.note.on{opacity:.94; transform:translateX(-50%) translateY(0)}
@media (prefers-reduced-motion: reduce){ .note{transition:none} }

/* MUST STAY LAST — the browser's own [hidden] is specificity (0,1,0) and any
   author rule of equal specificity that sets `display:` silently beats it,
   which stops el.hidden working with no error anywhere. Same trap as the
   console's stylesheet. */
[hidden]{display:none !important}
