/* ===================================================================
   STILLS — the whole site, one stylesheet
   ===================================================================

   FOUR COLOURS, AND THEY ARE GIVEN. There is no fifth, and mixing one is
   how this stops looking like itself.

       --bg       #0D0D0D   the ground.
       --white    #D9D9D9   type on the ground, and the menu's own ground.
       --black    #1E1E1E   type on the white, and any raised surface.
       --orange   #EB5E28   used about twice per screen. never as a fill
                            behind body copy, never as a heading colour.

   Everything else in :root is one of those four at an alpha, which is why
   there are no other hex literals below this block. If you are about to
   write one, you want an alpha of an existing token instead.

   TWO FACES.
       Young Serif   display only — the hero, page titles, section heads,
                     the two buttons, the word "menu". Lowercase where it
                     can be. Weight 300.
       Heming        everything else — body, metadata, the menu index,
                     forms, the footer. 300–700.

   THE HOMEPAGE DOES NOT SCROLL AND THE OTHER PAGES DO. That split is
   deliberate and it is the one structural rule here:

       body.home     height:100svh, overflow:hidden. One composition.
       body          normal document flow. Long pages are allowed.

   So `.home` owns `.stage` (a two-column grid that fills the viewport
   exactly once) and everything else owns `.sheet` (a scrolling column).
   The bar and the menu are identical on both.

   GLYPH WARNING (see fonts.css): the codepoints for middot, copyright,
   section, ellipsis, bullet, degree and one-half render as BLANK SPACE in
   Heming and the browser will not fall back. Never type them. Every
   separator here is a drawn box, a rule, or an em dash.

   CAREFUL WITH BARE `span` RULES. kern.js wraps every capital D in the
   document in <span class="dk"> at DOMContentLoaded. A rule like
   `.foo span{display:block}` lands on one and splits the word it is
   inside. Use a child combinator or a class.
   =================================================================== */

:root{
  --bg:#0D0D0D;
  --white:#D9D9D9;
  --black:#1E1E1E;
  --orange:#EB5E28;

  /* The white, stepped down. EVERY STEP IS SET BY A MEASURED CONTRAST
     RATIO, not by eye — these are the numbers against #0D0D0D:

       --white-70   7.1:1   body copy
       --white-52   5.4:1   labels and small caps  (was .52 = 4.3, under AA)
       --white-38   4.7:1   fine print             (was .38 = 2.9)

     The names are kept so nothing has to be renamed; the alphas moved.
     If you lighten the ground, re-run these before trusting them. */
  --white-70:rgba(217,217,217,.70);
  --white-52:rgba(217,217,217,.60);
  --white-38:rgba(217,217,217,.55);
  --rule:rgba(217,217,217,.15);
  --rule-soft:rgba(217,217,217,.08);

  /* The menu turns the palette over: white as ground, black as type. */
  /* And the same on the sheet, against #D9D9D9:

       --black-64   5.5:1   body copy   (was .64 = 4.4, under the 4.5 AA floor
                                         — this is the one that mattered, it is
                                         every paragraph on every reading page)
       --black-40   4.9:1   labels and fine print  (was .40 = 2.3)

     The two dim steps end up close to the body value, and that is the
     point: 11px small caps have to clear 4.5:1 like anything else, so
     the hierarchy is carried by size, case and tracking rather than by
     fading the text until it stops being readable. */
  --black-64:rgba(30,30,30,.72);
  --black-40:rgba(30,30,30,.68);
  --black-rule:rgba(30,30,30,.16);

  /* SAFE AREAS. Every page is laid out edge to edge under
     viewport-fit=cover, so on a notched iPhone the bar sits beneath the
     status bar and the fine print beneath the home indicator unless these
     are added. env() is 0 everywhere else, so it costs nothing. */
  --safe-t:env(safe-area-inset-top,0px);
  --safe-b:env(safe-area-inset-bottom,0px);
  --safe-l:env(safe-area-inset-left,0px);
  --safe-r:env(safe-area-inset-right,0px);

  --pad:clamp(22px,6.6vw,118px);
  --edge:clamp(12px,2.2vw,38px);
  /* The reading column. Title and body share its left edge, which is what
     stops a centred column under a left-bleeding masthead reading as an
     accident. */
  --measure:680px;
  --round:clamp(14px,1.6vw,26px);
  --bar:clamp(64px,7.2vw,104px);

  /* One curve, everywhere. Long tail, no overshoot — this site never
     bounces, it settles. */
  --ease:cubic-bezier(.16,.84,.28,1);
  --ease-panel:cubic-bezier(.62,.02,.16,1);
}

*{box-sizing:border-box}
/* iOS paints a translucent blue box over anything tappable. On a page this
   dark it reads as a rendering fault rather than as feedback. */
a,button,summary,label,input,select,textarea{-webkit-tap-highlight-color:transparent}
/* SMOOTH SCROLL, AND THE PADDING THAT MAKES IT LAND RIGHT. `.bar` is
   fixed to the top, so a jump to #main or to any in-page anchor would
   otherwise put the first line of the target underneath it. scroll-padding
   reserves the bar's own height plus the notch inset, so the thing you
   asked to see arrives below the furniture rather than behind it.

   Turned off again under prefers-reduced-motion at the foot of this file:
   an animated scroll is motion, and that setting is the reader saying no
   to motion. */
html{
  background:var(--bg);-webkit-text-size-adjust:100%;
  scroll-behavior:smooth;
  scroll-padding-top:calc(var(--bar) + var(--safe-t) + 12px);
}
body{
  margin:0;background:var(--bg);color:var(--white);
  font-family:var(--heming);font-weight:400;font-size:16px;
  -webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale;
}
/* The homepage, and only the homepage. */
body.home{height:100svh;overflow:hidden}

a{color:inherit;text-decoration:none}
img,video{display:block;max-width:100%}
button,input,textarea,select{font:inherit;color:inherit}
h1,h2,h3,h4,p,ul,ol,dl,figure,blockquote,fieldset{margin:0;padding:0}
ul,ol{list-style:none}
::selection{background:var(--orange);color:var(--white)}

.skip{position:fixed;z-index:200;left:20px;top:-80px;background:var(--white);color:var(--black);padding:15px 22px;font-size:13px}
.skip:focus{top:20px}
.sr-only{position:absolute;width:1px;height:1px;overflow:hidden;clip:rect(0,0,0,0);white-space:nowrap}
a:focus-visible,button:focus-visible,input:focus-visible,textarea:focus-visible,select:focus-visible,summary:focus-visible{
  outline:2px solid var(--orange);outline-offset:5px;border-radius:1px;
}

/* ===================================================================
   THE BAR — wordmark centred, one control at the right. Identical on
   every page. Fixed, and it never grows a background: on the scrolling
   pages the type simply passes behind it, which is the intent.
   =================================================================== */

.bar{
  position:fixed;z-index:70;top:0;left:0;right:0;
  height:calc(var(--bar) + var(--safe-t));padding-top:var(--safe-t);
  display:flex;align-items:center;justify-content:center;
  padding-left:calc(var(--pad) + var(--safe-l));
  padding-right:calc(var(--pad) + var(--safe-r));
  pointer-events:none;
}
.bar>*{pointer-events:auto}

/* The wordmark is a greeting, not a permanent fixture. stills.js adds
   `gone` once the page has moved; the trigger stays, because it is the
   only route to the rest of the site. */
.mark{transition:opacity .4s var(--ease)}
.bar.gone .mark{opacity:0;pointer-events:none}

.mark{position:relative;width:clamp(84px,6.6vw,116px);line-height:0}
.mark img{width:100%;height:auto}
/* Two wordmarks, one shown at a time. Swapping `src` in script flashes
   the old bitmap while the new one decodes; both being in the document
   means the change is a repaint, not a fetch. */
.mark .m-dark{position:absolute;inset:0;opacity:0;transition:opacity .3s ease}

/* THE WORDMARK IS 21 PIXELS TALL and it is the only way home from every
   page on the site. As a thumb target that is a quarter of what it needs
   to be, and on a phone the two links in the bar are the whole of the
   navigation.

   The hit area is grown with a pseudo-element rather than with padding,
   because `.m-dark` is positioned `inset:0` against this box — padding
   would stretch the black cut to the padded height while the white one
   kept its own, and the two wordmarks would no longer sit on top of each
   other. This adds 44px of reachable height, centred, and changes no
   layout at all.

   It inherits `pointer-events:none` from `.bar.gone .mark`, so once the
   mark has faded on scroll the invisible target goes with it rather than
   leaving a dead spot at the top of the page. */
.mark:after{
  content:"";position:absolute;left:0;right:0;top:50%;
  transform:translateY(-50%);height:44px;
}
.bar.on-light .m-dark{opacity:1}
.bar.on-light .m-light{opacity:0}
.mark .m-light{transition:opacity .3s ease}
.bar.on-light .burger{color:var(--black)}

.burger{
  position:absolute;right:var(--pad);top:50%;translate:0 -50%;
  width:38px;height:38px;display:grid;align-content:center;gap:7px;
  border:0;background:none;padding:0;cursor:pointer;color:var(--white);
  transition:color .3s ease;
}
.burger>i{display:block;height:2px;background:currentColor}

/* ===================================================================
   THE MENU — six items, right-aligned, on paper.

   It opens WITHOUT JAVASCRIPT. The trigger is an <a href="#site-menu">
   and `:target` opens the panel; stills.js then upgrades it to a button
   so the URL does not collect a hash. Do not turn the trigger into a
   plain <button> in the markup — that is what breaks the no-JS path.
   =================================================================== */

.scrim{
  position:fixed;z-index:78;inset:0;background:rgba(19,18,17,.62);
  opacity:0;visibility:hidden;transition:opacity .6s var(--ease),visibility .6s;
}
.menu{
  position:fixed;z-index:80;inset:0 0 0 auto;
  width:min(38vw,620px);min-width:440px;
  background:var(--white);color:var(--black);
  display:flex;flex-direction:column;
  padding:var(--safe-t) calc(clamp(28px,3vw,52px) + var(--safe-r)) calc(clamp(24px,2.4vw,40px) + var(--safe-b)) clamp(28px,3vw,52px);
  transform:translateX(100%);transition:transform .78s var(--ease-panel);
  overflow:auto;overscroll-behavior:contain;
}
#site-menu:target,.menu-open .menu{transform:translateX(0)}
#site-menu:target~.scrim,.menu-open .scrim{opacity:1;visibility:visible}
.menu-open{overflow:hidden}

.menu-top{height:var(--bar);flex:none;display:flex;align-items:center;justify-content:space-between}
.menu-word{font-family:var(--young);font-weight:300;font-size:clamp(24px,2vw,34px);line-height:1}
/* The panel takes focus when it opens (see openMenu). It is not a
   control, so it must not look like one — and a UA default ring on a
   tabindex="-1" container would put the very outline back that focusing
   the panel exists to avoid. */
.menu:focus{outline:none}
.menu-x{
  display:block;width:38px;height:38px;border:0;background:none;padding:0;cursor:pointer;
  position:relative;color:var(--black);flex:none;
}
.menu-x:before,.menu-x:after{content:"";position:absolute;left:6px;right:6px;top:18px;height:2px;background:currentColor;transition:transform .45s var(--ease)}
.menu-x:before{transform:rotate(45deg)}
.menu-x:after{transform:rotate(-45deg)}

.menu-index{margin:auto 0;display:flex;flex-direction:column;align-items:flex-end;padding:clamp(30px,5vh,64px) 0}
.menu-index a{
  font-family:var(--heming);font-weight:300;
  font-size:clamp(36px,4.9vw,86px);line-height:1.2;letter-spacing:-.045em;
  text-transform:uppercase;text-align:right;
  transition:color .3s ease;
}
.menu-index a:hover{color:var(--orange)}
.menu-x:hover{color:var(--orange)}
.burger:hover{color:var(--orange)}
.menu-index a[aria-current="page"]{color:var(--orange)}

.menu-foot{
  flex:none;display:flex;justify-content:space-between;align-items:flex-end;gap:24px;
  padding-top:clamp(16px,1.6vw,24px);border-top:1px solid var(--black-rule);
}
.menu-foot p{font-size:11px;line-height:1.6;letter-spacing:.09em;text-transform:uppercase;color:var(--black-64)}
.menu-social{display:flex;gap:clamp(12px,1.4vw,22px)}
.menu-social a{font-size:11px;letter-spacing:.09em;text-transform:uppercase;color:var(--black-64);transition:color .3s ease}
.menu-social a:hover{color:var(--orange)}

/* Once the panel has slid most of the way in, the index arrives. Six
   items, 45ms apart — enough to read as a sequence, not enough to wait
   through. */
.menu-open .menu-index a,#site-menu:target .menu-index a{animation:rise .8s var(--ease) both}
.menu-open .menu-index a:nth-child(1),#site-menu:target .menu-index a:nth-child(1){animation-delay:.22s}
.menu-open .menu-index a:nth-child(2),#site-menu:target .menu-index a:nth-child(2){animation-delay:.265s}
.menu-open .menu-index a:nth-child(3),#site-menu:target .menu-index a:nth-child(3){animation-delay:.31s}
.menu-open .menu-index a:nth-child(4),#site-menu:target .menu-index a:nth-child(4){animation-delay:.355s}
.menu-open .menu-index a:nth-child(5),#site-menu:target .menu-index a:nth-child(5){animation-delay:.4s}
.menu-open .menu-index a:nth-child(6),#site-menu:target .menu-index a:nth-child(6){animation-delay:.445s}

/* ===================================================================
   THE HOMEPAGE — one composition, exactly one viewport tall.
   =================================================================== */

.stage{
  position:relative;z-index:2;height:100svh;
  display:grid;grid-template-columns:minmax(0,1.02fr) minmax(0,1fr);
  align-items:center;gap:clamp(16px,3vw,48px);
  background:var(--bg);
  padding:calc(var(--bar) + var(--safe-t)) calc(var(--pad) + var(--safe-r)) clamp(46px,5.4vh,68px) calc(var(--pad) + var(--safe-l));
}

.hero{max-width:none;margin-bottom:20vh}
.hero h1{
  font-family:var(--young);font-weight:300;
  /* Sized so the WIDEST word in the cycle still fits on one line. At
     6.1vw "summer" ran five pixels past the column and the headline
     dropped to two lines every time it came round — a reflow you cannot
     unsee. `nowrap` below is the backstop if someone adds a longer word;
     check the fit at 1440 and 1024 if you do. */
  font-size:clamp(44px,5.9vw,112px);line-height:.94;letter-spacing:-.028em;
  color:var(--white);
}
@media (min-width:901px){ .hero h1{white-space:nowrap}}

/* THE CYCLING WORD. Its width is set in pixels by script and transitions,
   so "camera" slides across rather than snapping between five different
   line lengths — and the swap happens while the word is invisible, so
   nothing is ever caught mid-resize.

   No `overflow:hidden`: "memory" and "feeling" both have descenders and a
   clipped y is worse than anything this rule was going to fix. */
.cycle{
  display:inline-block;vertical-align:baseline;white-space:nowrap;
  transition:width .3s var(--ease),opacity .26s ease,transform .26s var(--ease);
}
.cycle.swap{opacity:0;transform:translateY(-.1em)}
/* The measurer. Same font, same tracking, out of flow and out of the
   accessibility tree — it exists only to be measured. */
.cycle-ghost{
  position:absolute;visibility:hidden;pointer-events:none;
  white-space:nowrap;left:-9999px;top:0;
}
.hero .said{
  margin-top:clamp(14px,1.7vw,26px);
  font-weight:300;font-size:clamp(17px,1.62vw,29px);line-height:1.28;
  letter-spacing:-.012em;color:var(--white-70);max-width:42ch;
}
.hero .said b{font-weight:300;color:var(--orange)}

/* A short account of the thing, under the headline. Four facts on one
   line, not a feature list — and the paragraph above it is the
   definitional sentence the search result wants. */
.hero .def{
  margin-top:clamp(14px,1.5vw,22px);max-width:46ch;
  font-size:clamp(13px,1.02vw,16px);line-height:1.62;color:var(--white-52);
}
.facts-row{
  display:flex;flex-wrap:wrap;align-items:center;gap:.55em clamp(10px,1vw,16px);
  margin-top:clamp(14px,1.6vw,24px);
  font-size:clamp(10px,.78vw,12px);letter-spacing:.15em;text-transform:uppercase;color:var(--white-38);
}
/* A drawn box, not a bullet — the bullet codepoint is blank in Heming. */
.facts-row li+li{display:flex;align-items:center;gap:clamp(10px,1vw,16px)}
.facts-row li+li:before{content:"";width:3px;height:3px;background:var(--orange);flex:none}

/* Bottom corner of the one screen: the year and the two legal links, and
   nothing else. */
.basement{
  position:fixed;z-index:6;left:0;right:0;bottom:0;
  display:flex;gap:clamp(12px,1.4vw,22px);
  padding:0 calc(var(--pad) + var(--safe-r)) calc(clamp(14px,1.5vw,22px) + var(--safe-b)) calc(var(--pad) + var(--safe-l));
  font-size:10px;letter-spacing:.13em;text-transform:uppercase;color:var(--white-38);
  opacity:0;animation:fade 1.1s var(--ease) 1s both;
}
/* THE LOOP RUNS UNDER THIS LINE, so the line needs its own ground.
   `.develop` deliberately bleeds off the bottom edge of the viewport (see
   the mobile `.stage` rule), and `.basement` is fixed to that same edge —
   so at 375x667 the year and the two legal links were painting directly
   on top of the phone's screen, 24.5px of overlap, measured. Reserving
   padding for them would kill the bleed, which is the better half of the
   composition.

   This is a scrim, NOT a blur: a gradient from transparent to the page's
   own background colour. Over the page it is invisible, because it fades
   to exactly the colour already there. Over the loop it gives the text a
   floor. It cannot be a backdrop-filter — those were removed from this
   site on purpose. */
.basement:before{
  content:"";position:absolute;z-index:-1;left:0;right:0;bottom:0;
  height:calc(100% + clamp(34px,7vh,64px));
  background:linear-gradient(to top,var(--bg) 42%,rgba(13,13,13,0));
  pointer-events:none;
}
.basement a{transition:color .3s ease}
.basement a:hover{color:var(--orange)}

.acts{display:flex;flex-wrap:wrap;gap:clamp(9px,.85vw,14px);margin-top:clamp(30px,4.6vw,66px)}
.btn{
  display:inline-flex;align-items:center;gap:.55em;
  font-family:var(--young);font-weight:300;
  font-size:clamp(16px,1.62vw,28px);line-height:1;
  padding:clamp(14px,1.45vw,24px) clamp(18px,2.2vw,38px);
  border-radius:clamp(8px,.85vw,14px);
  background:var(--white);color:var(--black);border:1px solid var(--white);
  cursor:pointer;transition:background .4s var(--ease),color .4s var(--ease),transform .5s var(--ease);
}
.btn.ghost{background:none;color:var(--white);border-color:var(--rule)}
.btn:hover{background:var(--orange);border-color:var(--orange);color:var(--white)}
.btn.ghost:hover{border-color:var(--white);background:transparent;color:var(--white)}

/* --- the developing screen -------------------------------------------
   The 12-second loop rendered for the teaser: a phone in a dark room
   with a roll coming up in it, cropped to the phone so it plays at its
   own resolution instead of being blown up to fill a 16:9 box.

   THE CROP IS MEASURED, NOT EYEBALLED. The lit phone occupies
   x 801..1134, y 225..885 of the 1920x1080 source across all 360 frames,
   so the frame is 474x752 centred on (967,555). The first attempt was
   centred by eye and put the phone at 55% of the frame width — which is
   invisible on the two-column desktop layout and plainly wrong the
   moment the loop is centred on a tablet or a phone.

   `mix-blend-mode:screen` is what makes it sit ON the page rather than
   IN a rectangle. Screen leaves anything black exactly as the backdrop
   already was, so the render's #000 surround becomes the ground itself
   and there is no edge anywhere.

   THE BACKDROP IT BLENDS AGAINST IS `.stage`, which paints the ground.
   It used to be an opaque background on `.develop` itself, and at 1024
   the box is wider than its own grid column — so that rectangle painted
   #0D0D0D straight over the last word of the headline. A backdrop on the
   container cannot overlap anything, which is why it lives there now.
   Nothing else may ever be painted behind this element.

   AND IT ONLY WORKS IF THE SURROUND IS EXACTLY ZERO. Screen never
   darkens: a surround of rgb(3,3,3) — which is what the loop encoded to
   on the first pass — lifts #0D0D0D to #101010 and draws a pale
   rectangle across a third of the homepage. The file in assets/ is
   encoded through `colorlevels=rimin=0.035` for exactly this reason. If
   you ever re-cut it from teaser/out/, keep that filter, and check the
   corner pixels are (0,0,0) before believing the page. */
.develop{
  position:relative;justify-self:center;align-self:center;
  height:min(72svh,620px);width:auto;max-width:100%;aspect-ratio:474/752;
}
.develop img,.develop video{
  position:absolute;inset:0;
  width:100%;height:100%;object-fit:contain;
  mix-blend-mode:screen;

  /* THE FLOAT LIVES HERE, NOT ON .develop. A transform on the container
     makes it a stacking context, which isolates this blend from the
     stage behind it — and the moment that happens the render's black
     surround stops resolving to the ground and a pale rectangle appears
     across a third of the homepage. On the blended element itself the
     transform is harmless: an element still blends with its parent
     stacking context's backdrop. */
  opacity:0;animation:fade 2.2s var(--ease) .5s both, float 7s ease-in-out infinite;
}
/* The still is for the reduced-motion case ONLY, and it is display:none
   rather than stacked under the video on purpose. Two screened layers do
   not hide one another — they add — so leaving the poster visible put a
   second, half-second-later copy of the same phone on top of the first
   and the roll appeared to develop twice. The <video poster> attribute
   already covers the moment before the first frame decodes. */
.develop img{display:none}

/* ===================================================================
   THE SCROLLING PAGES

   Two surfaces and nothing between them. The ground is the ground; the
   words sit on a sheet of white laid over it, edge to edge. The page
   title is set across the join, in the same white as the sheet, so its
   lower third disappears into it — the letters are not clipped by
   anything, they simply stop being distinguishable from what is behind
   them, which is why the title and the panel must stay the SAME token.
   Give the panel its own off-white and the effect reads as a rendering
   bug instead of as a decision.

   Page titles are one word wherever a word will do. They are set at
   about 10vw, which means a title of more than a dozen characters is a
   layout problem rather than a copy problem — the short name in the
   menu is the name of the page.
   =================================================================== */

.sheet{
  position:relative;z-index:2;
  /* Full bleed up to 1600, then it stops growing and the ground frames it.
     Past that width a sheet of white spanning the whole monitor with a
     70ch column in its top-left corner reads as unfinished rather than
     airy, and the masthead and the body both sit against this same edge
     so they stay aligned when it starts centring. */
  max-width:1600px;margin:0 auto;
  min-height:100svh;display:flex;flex-direction:column;
  padding-top:calc(var(--bar) + var(--safe-t));
}
.sheet>main{display:flex;flex-direction:column;flex:1}

/* THE MASTHEAD NO LONGER SINKS INTO THE SHEET. It used to carry a
   -0.32em bottom margin so the sheet swallowed its lower third — which
   reads as a deliberate effect in a comp and as a rendering fault on a
   real page, which is how it was read. It sits clear of the sheet now.

   `max-width:var(--measure)` with auto margins does not clip the title;
   the text overflows it to the right. What it does is put the title's
   LEFT edge exactly where the body column's left edge is, so the two
   line up however wide the window gets. */
.title{
  /* Hard left, on the sheet's own edge. Aligning it with the centred
     reading column instead left it sitting a quarter of the way in,
     which reads as neither one thing nor the other. */
  width:100%;padding:0 var(--edge);
  margin:clamp(18px,3.4vw,52px) 0 clamp(20px,2.8vw,44px);
  font-family:var(--heming);font-weight:300;
  font-size:clamp(40px,8.4vw,150px);line-height:.9;letter-spacing:-.035em;
  text-transform:uppercase;color:var(--white);
}

/* The sheet of white. Inset from the ground and rounded, so it reads as
   a card laid on the page rather than as a band across it. `flex:1` is
   what fills the rest of a short page — a legal page and a 404 both have
   to reach the bottom of the window. */
.panel{
  position:relative;flex:1;
  margin:0 var(--edge) var(--edge);
  border-radius:var(--round);
  background:var(--white);color:var(--black);
  padding:clamp(30px,4.2vw,68px) var(--edge) clamp(22px,2.6vw,38px);
  display:flex;flex-direction:column;
}
/* Every block inside the sheet is the same centred column. */
.panel>*{width:100%;max-width:var(--measure);margin-left:auto;margin-right:auto}
.news{width:100%;max-width:none}
.panel>.foot{max-width:none}
/* Pictures are allowed wider than the words. */
.panel>.strip,.panel>.article-cover{max-width:min(1040px,100%)}
.panel .lede{
  font-weight:300;font-size:clamp(18px,1.7vw,27px);line-height:1.4;
  letter-spacing:-.01em;color:var(--black);
}
.panel .lede a{border-bottom:1px solid var(--orange)}
.panel .stamp,.panel .eyebrow{margin-top:20px;font-size:11px;letter-spacing:.14em;text-transform:uppercase;color:var(--black-40)}
.panel .stamp+.prose,.panel .eyebrow+.prose,.panel .stamp+.qa{margin-top:clamp(22px,2.6vw,38px)}

.prose{margin-top:clamp(28px,3.4vw,50px)}
.prose>*+*{margin-top:18px}
.prose .lead{
  font-weight:400;font-size:clamp(16px,1.25vw,20px);line-height:1.6;color:var(--black);
}
.prose h2{
  font-family:var(--young);font-weight:300;
  font-size:clamp(23px,2.1vw,34px);line-height:1.14;letter-spacing:-.022em;color:var(--black);
  margin-top:clamp(42px,4.6vw,70px);padding-top:22px;border-top:1px solid var(--black-rule);
}
.prose h3{
  font-size:12px;letter-spacing:.13em;text-transform:uppercase;color:var(--black-40);
  margin-top:clamp(26px,2.8vw,38px);
}
.prose h2+*,.prose h3+*{margin-top:12px}
/* The first section head already has the whole column's top margin above
   it; its own opened a second gap the height of a paragraph. */
.prose>h2:first-child{margin-top:0;padding-top:0;border-top:0}
.prose p,.prose li{font-size:clamp(14px,1.02vw,16px);line-height:1.75;color:var(--black-64)}
.prose b,.prose strong{font-weight:600;color:var(--black)}
.prose a{color:var(--black);border-bottom:1px solid var(--orange);transition:color .3s ease}
.prose a:hover{color:var(--orange)}
.prose ul{margin-top:16px}
.prose li{position:relative;padding-left:22px;margin-top:10px}
/* A drawn box, not a bullet — the bullet codepoint is blank in Heming. */
.prose li:before{content:"";position:absolute;left:2px;top:.7em;width:4px;height:4px;background:var(--orange)}
.prose ol{counter-reset:n;margin-top:16px}
.prose ol li{counter-increment:n}
.prose ol li:before{content:counter(n);background:none;color:var(--orange);top:0;left:0;width:auto;height:auto;font-size:12px;letter-spacing:.1em}
.prose blockquote{border-left:2px solid var(--orange);padding:2px 0 2px 20px;margin-top:20px}
.prose blockquote p{color:var(--black)}
.prose figure{margin-top:28px}
.panel>.cap{margin:calc(-1 * clamp(14px,1.6vw,24px)) auto clamp(26px,3.2vw,46px);text-align:right;max-width:min(1040px,100%)}
.prose figcaption,.cap{margin-top:10px;font-size:11px;letter-spacing:.1em;text-transform:uppercase;color:var(--black-40)}
.prose hr{border:0;height:1px;background:var(--black-rule);margin:clamp(34px,4vw,60px) 0}
.callout{border-left:2px solid var(--orange);padding:2px 0 2px 20px;margin-top:24px}

.strip{
  display:grid;grid-auto-flow:column;grid-auto-columns:minmax(0,1fr);gap:4px;
  margin:clamp(30px,4vw,56px) auto;
}
/* One shape for all of them, whatever was uploaded — for PHOTOGRAPHS.
   Phone screenshots must not be cropped: a press kit that lops the top
   and bottom off the App Store shots is worse than no strip at all, so
   `.strip.shots` keeps their real proportions. */
.strip img{width:100%;height:100%;aspect-ratio:4/5;object-fit:cover;border-radius:10px}
.strip.shots img{height:auto;aspect-ratio:auto;object-fit:contain;border-radius:8px}

/* The loupe on /film. `background-size` is a multiple of the rendered
   frame, so the magnification is of the file's own pixels — which is the
   only reason the sentence beside it is worth writing. */
.strip.loupe{position:relative}
.strip.loupe img{cursor:crosshair}
.lens{
  position:absolute;z-index:3;pointer-events:none;
  width:190px;height:190px;margin:-95px 0 0 -95px;border-radius:50%;
  background-repeat:no-repeat;background-size:320% auto;
  box-shadow:0 0 0 1px rgba(30,30,30,.35),0 18px 40px rgba(0,0,0,.35);
}

.facts{margin-top:clamp(26px,3vw,40px)}
.facts>div{display:grid;grid-template-columns:minmax(120px,26%) 1fr;gap:20px;padding:14px 0;border-top:1px solid var(--black-rule)}
.facts dt{font-size:11px;letter-spacing:.13em;text-transform:uppercase;color:var(--black-40)}
.facts dd{font-size:15px;line-height:1.5;color:var(--black-64)}
.facts dd a{color:var(--black);border-bottom:1px solid var(--orange)}

.qa{margin-top:clamp(28px,3.4vw,50px)}
.qa>div{padding:clamp(20px,2.2vw,30px) 0;border-top:1px solid var(--black-rule)}
.qa h2{font-family:var(--young);font-weight:300;font-size:clamp(20px,1.8vw,29px);line-height:1.18;letter-spacing:-.018em;color:var(--black)}
.qa p{margin-top:10px;font-size:clamp(14px,1.02vw,16px);line-height:1.7;color:var(--black-64)}
.qa p+p{margin-top:9px}
.qa a{color:var(--black);border-bottom:1px solid var(--orange)}

/* Signposts to the pages that are not in the six-item menu. */
.onward{
  display:flex;flex-wrap:wrap;gap:clamp(10px,1.2vw,18px);
  margin-top:clamp(40px,5vw,76px);
}
.onward a{
  position:relative;display:block;padding-right:clamp(44px,4vw,60px);padding:clamp(16px,1.6vw,24px) clamp(18px,2vw,30px);
  border:1px solid var(--black-rule);border-radius:14px;
  transition:background .4s var(--ease),border-color .4s var(--ease);
}
.onward a:hover{background:var(--black);border-color:var(--black)}
.onward a:hover .t{color:var(--white)}
.onward a:hover .k{color:var(--white)}
.onward a:hover .i-go{color:var(--white);transform:translateX(4px)}
/* ORANGE IS A TEXT COLOUR ON THE GROUND AND A DECORATION COLOUR ON THE
   SHEET. #EB5E28 on #D9D9D9 is 2.37:1 — it cannot pass at any size, so on
   the white it is only ever a rule, a bullet or an underline. On #0D0D0D
   the same orange is 5.7:1 and can carry words. */
.onward .k{display:block;font-size:10px;letter-spacing:.17em;text-transform:uppercase;color:var(--black-40)}
.onward .i-go{
  position:absolute;right:clamp(16px,1.8vw,26px);top:50%;translate:0 -50%;
  width:15px;height:15px;color:var(--black-40);
  transition:color .3s ease,transform .4s var(--ease);
}
.onward .t{display:block;margin-top:7px;font-family:var(--young);font-weight:300;font-size:clamp(18px,1.6vw,25px);line-height:1.1;letter-spacing:-.02em;color:var(--black)}

/* The whole footer. One line, bottom right, and that is all a page that
   carries a six-item menu on every screen actually needs. */
.foot{margin-top:auto;padding-top:clamp(32px,4vw,60px);text-align:right}
.foot p{font-size:12px;letter-spacing:.02em;color:var(--black-40)}
.panel .foot p{color:var(--black-40)}
.sheet>.foot{padding:clamp(32px,4vw,60px) var(--edge) clamp(24px,3vw,40px)}
.sheet>.foot p{color:var(--white-38)}
.cards{
  display:grid;grid-template-columns:repeat(auto-fill,minmax(clamp(258px,22vw,380px),1fr));
  gap:clamp(14px,1.6vw,24px);
}
.card{
  display:block;background:var(--white);color:var(--black);
  border-radius:18px;padding:clamp(12px,1vw,16px);
  align-self:start;transition:box-shadow .35s var(--ease);
}
/* THE ASPECT RATIO IS NOT DECORATION. Covers come from the newsroom's own
   uploader, so their shape is whatever somebody happened to upload — the
   first test used a 620x1344 App Store screenshot and each card came out
   thirteen hundred pixels tall. Fixing the frame and cropping into it is
   what keeps a row of cards a row. */
.card-cover{display:block;line-height:0;overflow:hidden;border-radius:10px;background:rgba(30,30,30,.08);aspect-ratio:3/2}
.card-cover img{width:100%;height:100%;object-fit:cover;transition:transform .7s var(--ease)}
.card:hover{box-shadow:0 0 0 2px var(--orange)}
.card:hover .card-cover img{transform:scale(1.03)}
.card-blank{display:block;aspect-ratio:3/2;border-radius:10px;background:rgba(30,30,30,.08)}
.card-body{display:block;padding:clamp(12px,1.1vw,18px) clamp(4px,.5vw,8px) clamp(6px,.6vw,10px)}
/* The comp had this line in orange, and orange on the white card is
   2.41:1 — unreadable for anyone with moderate vision loss, on every
   card, at 13px. Same rule as the signposts: orange carries words on the
   ground and decorates on the sheet. The card keeps its orange in the
   hover outline. */
.card-meta{display:block;font-size:13px;letter-spacing:.01em;text-transform:lowercase;color:var(--black-40)}
.card-meta b{font-weight:400;margin-right:12px}
.card-title{
  display:block;margin-top:8px;
  font-family:var(--young);font-weight:300;font-size:clamp(20px,1.7vw,27px);
  line-height:1.12;letter-spacing:-.02em;color:var(--black);
}
.card-dek{display:block;margin-top:8px;font-size:13px;line-height:1.5;color:var(--black-64)}
.empty{max-width:44ch;color:var(--white-52);font-size:15px;line-height:1.7}
.empty-mark{margin-bottom:18px;line-height:0;opacity:.5}
.preview-flag{
  grid-column:1/-1;margin-bottom:4px;padding:10px 14px;border-left:2px solid var(--orange);
  font-size:11px;letter-spacing:.1em;text-transform:uppercase;color:var(--white-70);
}
.article-cover{margin:0 0 clamp(26px,3vw,44px);line-height:0}
.article-cover img{width:100%;height:auto;border-radius:12px}
.body-figure{margin-top:28px}
.body-figure img{width:100%;height:auto;border-radius:10px}

/* The article template's title block comes from _worker.js as a bare
   <h1> plus .eyebrow, .sub and .stamp. Those four names are that contract. */
.page-head{padding:0 var(--edge);margin-top:clamp(20px,4.4vw,64px)}
.page-head .eyebrow{font-size:11px;letter-spacing:.18em;text-transform:uppercase;color:var(--orange)}
.page-head h1{
  margin-top:14px;max-width:20ch;
  font-family:var(--heming);font-weight:300;
  font-size:clamp(34px,5.4vw,86px);line-height:.94;letter-spacing:-.03em;
  text-transform:uppercase;color:var(--white);
}
.page-head .sub{margin-top:clamp(14px,1.6vw,24px);max-width:46ch;font-weight:300;font-size:clamp(16px,1.3vw,22px);line-height:1.4;color:var(--white-70)}
.page-head .stamp{margin-top:18px;font-size:11px;letter-spacing:.14em;text-transform:uppercase;color:var(--white-38)}
.back{
  display:inline-block;margin-bottom:clamp(20px,3vw,38px);
  font-size:11px;letter-spacing:.15em;text-transform:uppercase;color:var(--white-38);
  transition:color .3s ease;
}
.back:hover{color:var(--orange)}

/* ===================================================================
   FORMS — support and account. The ids and names in the markup are a
   contract with support.js and account.js. Styling only, below.
   =================================================================== */

.ticket,.acct{margin-top:clamp(26px,3vw,40px)}
.tf{margin-top:24px}
.tf-row{display:grid;grid-template-columns:1fr 1fr;gap:20px}
.tf label{display:block;font-size:11px;letter-spacing:.15em;text-transform:uppercase;color:var(--black-64)}
/* "YOUR NAME OPTIONAL" read as one phrase, because the only thing
   separating the qualifier from the label was two points of lightness —
   --black-64 is 5.4:1 and --black-40 is 4.9:1, and after both were raised
   to clear AA there is no room left to make it quieter. Anything dimmer
   fails the contrast floor for small text.

   So it is separated by SHAPE instead of by tone. Parentheses in CSS
   rather than in the markup: five labels here, more on the account page,
   and the brackets are presentation. Not a middot — Heming maps that
   codepoint to an empty glyph and the browser will not fall back, so it
   would have shipped as "YOUR NAME  OPTIONAL" with a hole in it. */
.tf label>i{font-style:normal;color:var(--black-40);letter-spacing:.1em}
label>i:before{content:"("}
label>i:after{content:")"}
.tf input,.tf textarea,.tf select,.acct input{
  width:100%;margin-top:9px;border:0;border-bottom:1px solid var(--black-rule);border-radius:0;
  /* 16px, NOT 15. Safari on iOS zooms the whole page when a focused field's
     text is under 16px, and it does not zoom back out. */
  background:none;padding:13px 0;font-size:16px;transition:border-color .4s var(--ease);
}
.tf input:focus,.tf textarea:focus,.tf select:focus,.acct input:focus{border-color:var(--black);outline:none}
.tf input::placeholder,.tf textarea::placeholder{color:var(--black-40)}
.tf textarea{resize:vertical;line-height:1.6}
.tf select{appearance:none;cursor:pointer}
.tf select option{background:var(--white);color:var(--black)}
.tf-hint{margin-top:9px;font-size:11px;line-height:1.55;color:var(--black-40)}
.tf-error{margin-top:16px;font-size:13px;color:var(--orange)}
.tf-error:empty{display:none}
.tf-drop{
  display:block;margin-top:9px;padding:26px 20px;border:1px dashed var(--black-rule);
  cursor:pointer;text-align:center;font-size:13px;line-height:1.6;color:var(--black-40);
  transition:border-color .4s var(--ease),color .4s var(--ease);
}
/* The drop zone IS a <label>, so `.tf label{text-transform:uppercase}` was
   shouting a whole sentence of instructions at the reader. Source order
   does not settle this — `.tf label` is (0,1,1) and `.tf-drop` is (0,1,0),
   so the type selector wins wherever it sits. The class has to be
   qualified to outrank it. */
.tf label.tf-drop{text-transform:none;letter-spacing:0}
.tf-drop:hover,.tf-drop.over{border-color:var(--orange);color:var(--black-64)}
.tf-drop input[type=file]{position:absolute;width:1px;height:1px;opacity:0}
.tf-files{display:flex;flex-wrap:wrap;gap:10px;margin-top:12px}
.tf-files img{width:64px;height:64px;object-fit:cover}

.btn.wide{margin-top:32px;text-align:center;width:100%;max-width:320px}
/* On the white sheet the button has to turn over — a white pill on a white
   sheet is an invisible submit control. */
.panel .btn{background:var(--black);color:var(--white);border-color:var(--black)}
.panel .btn.ghost{background:none;color:var(--black);border-color:var(--black-rule)}
.panel .btn.ghost:hover{border-color:var(--black);background:rgba(30,30,30,.05)}
.panel .btn.danger{background:none;border-color:var(--orange);color:var(--orange)}
.panel .btn.danger:hover{background:var(--orange);color:var(--white)}
.btn:disabled{opacity:.5;cursor:default;transform:none}
.i-apple{width:.82em;height:1.02em;flex:none;margin-top:-.14em}
.btn.danger{background:none;border-color:var(--orange);color:var(--orange)}
.btn.danger:hover{background:var(--orange);color:var(--white)}

.ticket-done{margin-top:clamp(26px,3vw,40px);border-left:2px solid var(--orange);padding-left:24px}
.ticket-done h3{color:var(--black);font-family:var(--young);font-weight:300;font-size:clamp(20px,1.9vw,29px);line-height:1.15;margin-top:12px}
.ticket-done p{margin-top:14px;font-size:15px;line-height:1.7;color:var(--black-64);max-width:56ch}
.ticket-done a{color:var(--black);border-bottom:1px solid var(--orange)}
.ticket-done .btn{margin-top:24px}

.contact{margin-bottom:clamp(32px,4vw,56px)}
.contact h2{border-top:0;padding-top:0;margin-top:0}
.mailto{
  display:inline-block;margin-top:16px;color:var(--black);
  font-family:var(--young);font-weight:300;font-size:clamp(19px,1.7vw,27px);
  border-bottom:1px solid var(--orange);padding-bottom:4px;
}

/* The account page. Every id and class here is read by account.js —
   `.hidden` in particular is how it swaps the three panels, so it has to
   stay a plain display:none and stay more specific than nothing. */

.acard{border-top:1px solid var(--black-rule);padding:clamp(24px,2.8vw,38px) 0}
/* The first panel has nothing above it to be separated from. */
.acct>.acard:first-child{border-top:0;padding-top:0}
.acard>label{display:block;font-size:11px;letter-spacing:.15em;text-transform:uppercase;color:var(--black-64)}
/* 14px of padding, not 11, so the field clears 44px. At 11 it measured
   39 tall — under the touch minimum, on the one control that stands
   between somebody and their own account. The font size was already
   right; it was only ever the box that was short. */
.acard input{
  width:100%;margin-top:10px;border:0;border-bottom:1px solid var(--black-rule);background:none;
  padding:14px 0;font-size:16px;transition:border-color .4s var(--ease);
}
.acard input:focus{border-color:var(--black);outline:none}
.acard input::placeholder{color:var(--black-40)}
.acard input.code{font-size:26px;letter-spacing:.42em}
.acard .btn{margin-top:22px}
.anote{margin-top:12px;font-size:12px;line-height:1.6;color:var(--black-40);max-width:46ch}
.who{margin-top:10px;color:var(--black);font-family:var(--young);font-weight:300;font-size:clamp(21px,2vw,30px);line-height:1.2}
.acct .row{margin-top:clamp(26px,3vw,40px);padding-top:clamp(22px,2.4vw,32px);border-top:1px solid var(--black-rule)}
.acct .row>label{display:block;font-size:11px;letter-spacing:.15em;text-transform:uppercase;color:var(--black-64)}
.error{margin-top:14px;font-size:13px;line-height:1.6;color:var(--orange)}
.hidden{display:none}

/* ===================================================================
   MOTION — everything on this site arrives the same way: it rises a
   short distance and settles. Nothing bounces, nothing pulses, nothing
   arrives late enough to be waited for.
   =================================================================== */

.reveal{opacity:0;transform:translateY(18px);transition:opacity .8s var(--ease),transform .8s var(--ease)}
.reveal.seen{opacity:1;transform:none}

/* THE NEWSROOM GRID IS NOT ONE THING. Revealed as a block, a row of
   cards slid up as a single slab — which reads as a panel loading rather
   than as a set of separate stories arriving. So the container itself
   stops moving and hands the movement to its children, sixty
   milliseconds apart.

   Capped at twelve. Past that the delay would be longer than the
   animation, and a thirteenth card that simply appears is better than
   one the reader is kept waiting for. Cards beyond twelve inherit no
   delay and land with the twelfth.

   All of this lives under `.reveal`, which the script only ever adds
   when motion is allowed — so with reduced motion the cards were never
   hidden and none of these rules apply. */
.cards.reveal{opacity:1;transform:none}
.cards.reveal>.card{opacity:0;transform:translateY(16px);transition:opacity .7s var(--ease),transform .7s var(--ease)}
.cards.seen>.card{opacity:1;transform:none}
.cards.seen>.card:nth-child(1){transition-delay:0ms}
.cards.seen>.card:nth-child(2){transition-delay:60ms}
.cards.seen>.card:nth-child(3){transition-delay:120ms}
.cards.seen>.card:nth-child(4){transition-delay:180ms}
.cards.seen>.card:nth-child(5){transition-delay:240ms}
.cards.seen>.card:nth-child(6){transition-delay:300ms}
.cards.seen>.card:nth-child(7){transition-delay:360ms}
.cards.seen>.card:nth-child(8){transition-delay:420ms}
.cards.seen>.card:nth-child(9){transition-delay:480ms}
.cards.seen>.card:nth-child(10){transition-delay:540ms}
.cards.seen>.card:nth-child(11){transition-delay:600ms}
.cards.seen>.card:nth-child(12){transition-delay:660ms}
/* A block that was never scrolled to has never been revealed, and print
   does not scroll. Without this the legal pages — the ones people
   actually print — come out of the printer with holes in them. */
@media print{.reveal{opacity:1!important;transform:none!important}}

@keyframes fade{from{opacity:0}to{opacity:1}}
/* Seven seconds, twelve pixels, no rotation. Enough that the phone reads
   as held rather than pasted; not enough to notice it moving. */
@keyframes float{0%,100%{transform:translate3d(0,-6px,0)}50%{transform:translate3d(0,6px,0)}}
@keyframes rise{from{opacity:0;transform:translateY(14px)}to{opacity:1;transform:none}}
@keyframes riseHero{from{opacity:0;transform:translateY(26px)}to{opacity:1;transform:none}}

/* The hero, in the order it is read. The h1 is masked rather than faded
   so it comes up out of the ground instead of appearing on top of it. */
.hero>*{opacity:0;animation:riseHero 1.15s var(--ease) both}

/* HELD UNTIL THE TYPEFACE HAS LANDED. stills.js adds `hold-type` to
   <html> before it measures anything and takes it off once Young Serif is
   ready — with a 1.2s timeout so a font that never arrives cannot hold
   the hero hostage. Without this the headline painted in the fallback
   serif, at the smaller size that face measures to, and then cut to
   Young Serif and re-flowed: the load read as type appearing, changing
   face, and moving.

   `animation-play-state`, not `visibility`: the entrance is already
   `both`-filled from opacity 0, so pausing it holds exactly the frame it
   would have started on and releasing it plays the entrance from the
   top instead of snapping in half-way through.

   Only `.hero` — the developing loop beside it is a video and owes the
   typeface nothing, so it carries on arriving on its own schedule. */
html.hold-type .hero>*{animation-play-state:paused}
.hero .said{animation-delay:.2s}
.hero .acts{animation-delay:.3s}

/* Reading pages: the title block only. Body copy is not animated —
   a paragraph that fades in as you reach it is a paragraph you cannot
   scan, and these pages are reference. */
.title,.page-head>*{opacity:0;animation:rise 1s var(--ease) both}
.page-head>*:nth-child(1){animation-delay:.04s}
.page-head>*:nth-child(2){animation-delay:.12s}
.page-head>*:nth-child(3){animation-delay:.2s}
.page-head>*:nth-child(4){animation-delay:.28s}
/* THE SHEET DOES NOT ANIMATE IN, AND THAT IS A STABILITY DECISION.
   It used to reveal with `clip-path: inset(100% 0 0 0)` and
   `animation-fill-mode: both`, which means the entire page body is
   clipped to ZERO HEIGHT until that animation runs. Anything that stops
   it running — a suspended tab, a browser that skips the animation, a
   capture harness — leaves a page with no content on it, and the clip
   removes the sheet from hit-testing too, so scripts reading what is
   under the cursor get the wrong answer as well.

   A decorative flourish must never be the thing that decides whether the
   page is visible. The title and the menu still animate; they are
   decoration on top of content that is already there. */

@media (prefers-reduced-motion:reduce){
  *,*:before,*:after{animation-duration:.01ms!important;animation-iteration-count:1!important;transition-duration:.01ms!important}
  html{scroll-behavior:auto}
  .develop video{display:none}
  .develop img{display:block}}
/* The still frame is only for the reduced-motion case and the moment
   before the video is decoded; the video is stacked over it. */
.develop video{z-index:2}

/* ===================================================================
   NARROWER
   =================================================================== */

@media (max-width:1180px){
  .stage{grid-template-columns:minmax(0,1fr) minmax(0,1fr)}
  .hero h1{font-size:clamp(40px,5.4vw,84px)}
  .menu{width:min(52vw,560px)}}

@media (max-width:900px){
  .menu{width:100%;min-width:0}
  /* 44px is the smallest thing a thumb hits reliably. The trigger and the
     close were 38 and drawn from thin rules, which is smaller still. */
  .burger,.menu-x{width:46px;height:46px}
  .burger{gap:8px}
  .burger>i{height:2.5px}
  .menu-index a{padding:.06em 0}
  .menu-index{align-items:flex-start;text-align:left}
  .menu-foot{flex-direction:column;align-items:flex-start;gap:14px}

  /* No bottom padding, on purpose. The loop runs to the bottom edge of the
     viewport, and a photograph that continues past the frame reads larger
     than one that stops inside it. */
  .stage{
    grid-template-columns:1fr;grid-template-rows:auto minmax(0,1fr);
    align-content:start;gap:clamp(10px,2vh,22px);
    padding:calc(var(--bar) + clamp(8px,2vh,24px)) var(--pad) 0;
  }
  .hero{min-height:0;margin-bottom:0}
  /* "camera" drops to its own line so the line COUNT never changes as the
     word cycles. Fifteen words of different lengths would otherwise reflow
     the headline every three seconds on a page that cannot scroll. */
  .tail{display:block}
  .hero h1{font-size:clamp(42px,13.4vw,92px);max-width:none;line-height:.9}
  .hero .said{font-size:clamp(17px,4.2vw,28px);max-width:22ch}
  /* `max-width:none` here is deliberate, and it is the opposite of the
     desktop rule above. In one column there is nothing beside the loop to
     collide with, and everything in the render outside the phone is pure
     black that screens away to nothing — so letting the box run wider than
     the column costs nothing visible and buys a phone about a third
     larger than a width-constrained box would give. On two columns the
     cap stays, because there the headline is next to it. */
  .develop{height:auto;width:auto;max-width:none;min-height:0;align-self:stretch;justify-self:center;aspect-ratio:474/752}

  /* ROUNDER ON A PHONE, on purpose. A 14px radius that reads as "card" on
     a 1440 monitor reads as "barely rounded rectangle" at 375, because the
     eye judges a corner against the width of the thing it belongs to.

     It has to be written as a RULE. A bare `--round:26px` sitting directly
     inside is a parse error, not a declaration, and it is dropped
     without a word. */
  :root{--round:26px}
  .prose,.qa,.facts{max-width:none}
  .tf-row{grid-template-columns:1fr}
  .facts>div{grid-template-columns:1fr;gap:6px}
  .strip{grid-auto-flow:row;grid-auto-columns:auto}
  .cards{grid-template-columns:repeat(auto-fill,minmax(230px,1fr))}
  .onward a{flex:1 1 100%}}

/* The iPhone SE 3 is 375x667, it runs iOS 26, and it is not in Xcode's
   default simulator set — which is exactly why things overflow there
   unnoticed. The homepage must still be one screen at that size. */
@media (max-height:700px) and (max-width:900px){
  .hero h1{font-size:clamp(34px,9.4vw,52px)}
  .hero .said{font-size:15px;margin-top:10px}
  .acts{margin-top:18px;gap:9px}
  .btn{padding:12px 18px;font-size:16px}}

@media (max-width:520px){
  :root{--round:24px}
  .title{font-size:clamp(40px,13vw,72px)}
  .cards{grid-template-columns:1fr}
  /* Full-width targets on the narrowest screens: two pills side by side at
     375 leaves each of them about 150px wide with 18px of padding. */
  .acts{gap:10px}
  .acts .btn{flex:1 1 100%;justify-content:center;text-align:center}
  .panel{padding-left:clamp(18px,5vw,26px);padding-right:clamp(18px,5vw,26px)}
  .foot{text-align:left}}

/* A PHONE HELD SIDEWAYS, which the single-column rule above gets wrong.

   The mobile `.stage` is `grid-template-rows:auto minmax(0,1fr)` — hero
   on top, loop underneath. That is right at 393x852 and nonsense at
   852x393: the hero takes the whole of a 393px-tall viewport and the
   loop is handed what is left, which is nothing. The developing phone
   disappeared completely and the right half of the screen was empty
   black. The homepage cannot scroll, so there was no way to discover
   that anything was missing.

   Landscape gets the two-column composition back — which is what the
   desktop layout already is, and it fits a wide short viewport for the
   same reason it fits a wide tall one. `.tail` stays block so "camera"
   keeps its own line and the headline's line COUNT still never changes
   as the word cycles.

   Keyed on orientation rather than on a width, because the thing that
   breaks it is the shape, not the size. */
@media (max-width:900px) and (orientation:landscape){
  .stage{
    grid-template-columns:minmax(0,1.06fr) minmax(0,1fr);
    grid-template-rows:minmax(0,1fr);
    align-items:center;align-content:center;
    gap:clamp(12px,2.4vw,30px);
    padding:calc(var(--bar) + var(--safe-t)) calc(var(--pad) + var(--safe-r)) clamp(30px,7vh,48px) calc(var(--pad) + var(--safe-l));
  }
  .hero{margin-bottom:0}
  .hero h1{font-size:clamp(30px,5.4vw,56px)}
  /* Back inside the column: in two columns the loop sits beside the
     headline, so the `max-width:none` that mobile grants it would put a
     phone through the text. */
  .develop{
    align-self:center;justify-self:center;
    height:min(74svh,340px);width:auto;max-width:100%;
  }}

/* --- print ------------------------------------------------------------
   Nobody wants a solid black A4. The ground goes white, the sheet stops
   being a sheet, and the furniture that only makes sense on screen —
   the bar, the menu, the developing loop — comes off. */
@media print{
  html,body{background:#fff;color:#000}
  .bar,.menu,.scrim,.basement,.develop,.onward,.back{display:none!important}
  .sheet{max-width:none;padding:0}
  .panel{margin:0;padding:0;border-radius:0;background:#fff;color:#000}
  .title{color:#000;font-size:32pt;padding:0;margin:0 0 12pt}
  .panel .lede,.prose p,.prose li,.qa p{color:#000}
  .prose a,.qa a,.panel .lede a{color:#000;border-bottom:1px solid #999}
  .prose h2{border-top:1pt solid #ccc;color:#000}
  .foot{border:0;color:#555}}
.menu-more{display:flex;flex-wrap:wrap;gap:4px 22px;padding-top:20px;border-top:1px solid var(--black-rule)}
.menu-more a{font-size:13px;min-height:44px;display:inline-flex;align-items:center}
.menu-more a:hover{text-decoration:underline;text-underline-offset:5px}
.panel .btn:hover{background:var(--bg);color:var(--white);border-color:var(--bg)}
.btn:active{transform:translateY(0)}
.prose a:hover,.qa a:hover{color:var(--black);border-bottom-width:2px}
.strip.shots{grid-template-columns:repeat(3,minmax(0,1fr));grid-auto-flow:row;gap:20px;max-width:1040px}
.foot-links{display:flex;flex-wrap:wrap;gap:4px 20px;text-align:left;max-width:470px}
.foot-links a{font-size:12px;display:flex;align-items:center;min-height:44px;text-decoration:underline;text-decoration-color:var(--black-rule);text-underline-offset:5px}
.foot>p{font-size:11px;line-height:1.8;max-width:310px}
.sheet>.foot a{text-decoration-color:var(--rule)}
.news-intro{padding:0 var(--edge);font-size:17px;line-height:1.65;max-width:600px;color:var(--white-70)}
.empty{max-width:none;border:1px solid var(--rule);border-radius:var(--round);padding:clamp(26px,5vw,70px)}
.empty h2{font-family:var(--young);font-size:clamp(26px,3vw,44px);font-weight:300;color:var(--white);line-height:1.2;margin-bottom:20px}
.empty p{max-width:48ch}.empty a{text-decoration:underline;text-underline-offset:4px}
.article-body{margin-top:0}
.tf select{padding-right:42px;background-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='10' viewBox='0 0 16 10'%3E%3Cpath d='m2 2 6 6 6-6' fill='none' stroke='%231E1E1E' stroke-width='1.5'/%3E%3C/svg%3E");background-repeat:no-repeat;background-position:right 16px center}
.tf input,.tf textarea,.tf select,.acard input{border-radius:8px;padding:14px 16px;transition:border-color .2s ease,background .2s ease}
.tf input:focus,.tf textarea:focus,.tf select:focus,.acard input:focus{outline:2px solid var(--black);outline-offset:2px}
.tf-error,.error{color:var(--black);font-weight:500}
.tf-error:not(:empty),.error:not(.hidden){border-left:2px solid var(--orange);padding-left:12px}
.tf-drop:focus-within{outline:2px solid var(--black);outline-offset:4px}
.tf-drop{border-radius:10px}
.panel .btn.danger{color:var(--black);border-color:var(--black)}
.panel .btn.danger:hover{color:var(--white);background:var(--black)}
.recovery-actions{display:flex;flex-wrap:wrap;gap:12px;margin-top:30px}
@media(max-width:900px){
 .menu-index{align-items:flex-start}.menu-index a{font-size:clamp(34px,9vw,60px)}
 .hero{margin-bottom:0}
 .foot{align-items:flex-start;flex-direction:column;gap:16px;text-align:left}
 .foot-links{gap:0 22px}
 .strip.shots{grid-template-columns:repeat(2,minmax(0,1fr));gap:12px}
 .prose h2{margin-top:38px}}
@media(max-height:650px), (max-width:360px){
 body.home{height:auto;min-height:100svh;overflow:auto}}

.tf-thumb{position:relative}.tf-thumb img{border-radius:8px}
.tf-remove{position:absolute;right:-9px;top:-9px;width:28px;height:28px;border:1px solid var(--white);border-radius:50%;background:var(--black);color:var(--white);font-family:Arial,sans-serif;cursor:pointer}
@media(max-height:760px) and (max-width:900px){
 body.home{height:auto;min-height:100svh;overflow:auto}}
/* Off-canvas links must also leave the keyboard order in the no-script path. */
.menu{visibility:hidden;transition:transform .45s var(--ease-panel),visibility .45s}
#site-menu:target,.menu-open .menu{visibility:visible}