/* airsideatc.com - the marketing site.
 *
 * SERVED BY CADDY AS A STATIC FILE, never by gunicorn (server/README.md, ops/Caddyfile).
 *
 * THE CONTENT SECURITY POLICY HERE IS THE SAME STRICT ONE THE PORTAL USES, deliberately: the two hosts
 * share a look and will share markup by copy and paste, so anything legal here gets pasted into the
 * portal, where it is not. So: no inline <style>, no style= attribute, anywhere, on any page.
 *
 * AND THIS IS THE ONLY STYLESHEET THE SITE MAY LINK. ops/edge_test.py asserts the page carries exactly
 * one <link rel="stylesheet">, and the three legal pages are asserted to link this exact path. So there
 * is no reset.css, no fonts.css and no scene.css: the whole design lives in this file, and its name and
 * location cannot change without changing the test that guards it.
 *
 * THE TYPEFACES ARE FILES, NOT BASE64. `font-src` falls back to `default-src 'self'`, which does not
 * include `data:`, so a base64 @font-face would be blocked and the page would render in the fallback
 * stack. The installer embeds its fonts as data URIs because it is a local WebView with no policy over
 * it; copying that pattern here would have reproduced the defect that shipped the portal in Times New
 * Roman. Files are written by tools/vendor_site_assets.py, which also asserts every one is referenced.
 *
 * SINGLE THEME, LIKE THE PRODUCT. The deck is dark because a controller works over a night scene and a
 * bright panel is a light source in the room. The site does not ask the visitor's system what it
 * prefers, for the same reason the installer does not: this is a place, and a place has a time of day.
 */

@font-face{font-family:'Barlow';font-style:normal;font-weight:300;font-display:optional;src:url(/assets/fonts/barlow-300.woff2) format('woff2')}
@font-face{font-family:'Barlow';font-style:normal;font-weight:400;font-display:optional;src:url(/assets/fonts/barlow-400.woff2) format('woff2')}
@font-face{font-family:'Barlow';font-style:normal;font-weight:600;font-display:optional;src:url(/assets/fonts/barlow-600.woff2) format('woff2')}
@font-face{font-family:'Barlow Condensed';font-style:normal;font-weight:500;font-display:optional;src:url(/assets/fonts/barlow-condensed-500.woff2) format('woff2')}
@font-face{font-family:'Barlow Condensed';font-style:normal;font-weight:600;font-display:optional;src:url(/assets/fonts/barlow-condensed-600.woff2) format('woff2')}
@font-face{font-family:'JetBrains Mono';font-style:normal;font-weight:400;font-display:optional;src:url(/assets/fonts/jetbrains-mono-400.woff2) format('woff2')}
@font-face{font-family:'JetBrains Mono';font-style:normal;font-weight:500;font-display:optional;src:url(/assets/fonts/jetbrains-mono-500.woff2) format('woff2')}

/* WHY `optional` AND NOT `swap`, AND WHY EVERY FACE IS PRELOADED IN EVERY PAGE'S HEAD.
 *
 * THE REPORT: "hard refresh atinca font tipi anlik birkac salise baska font gibi oluyor sonra bizim
 * font yukleniyor tak gecis yapiyo" (controller, 2026-09-07). On a hard refresh the page painted in
 * Arial and then snapped to Barlow a few hundredths of a second later, on all text and on the
 * wordmark.
 *
 * THAT IS EXACTLY WHAT `swap` IS SPECIFIED TO DO. It gives a zero length block period: paint the
 * fallback at once, then swap the moment the file lands. A hard refresh bypasses the cache, so the
 * files are fetched again and the swap becomes visible - which is why it showed there and nowhere
 * else. Nothing was broken; the wrong value was chosen.
 *
 * `optional` MAKES THE SWAP IMPOSSIBLE RATHER THAN UNLIKELY. The browser blocks for about 100ms and
 * then commits for the whole page load: the real font if it arrived, the fallback if it did not, and
 * NO change afterwards either way. Preloading alone would only have made the flash rare, and a defect
 * made rare is a defect that reappears on a bad connection in front of somebody who matters.
 *
 * THE PRELOADS ARE WHAT MAKE `optional` SAFE, and they are the other half of one decision. Without
 * them the fonts are not even requested until this stylesheet has been fetched and parsed, so the
 * 100ms is spent before the download starts and a cold first visit would render entirely in Arial.
 * With them the fetch begins as the head is parsed, in parallel with this file, and 22KB from the
 * same origin is in memory long before first paint.
 *
 * SO EVERY FACE DECLARED HERE IS PRELOADED IN EVERY PAGE THAT LINKS THIS FILE. All seven, not the
 * three that are obviously above the fold: under `optional`, a face that has not arrived is dropped
 * for the whole page view, so preloading some of them would give a page half in Barlow and half in
 * Arial, which is worse than the flash this replaces. ops/edge_test.py compares the two lists.
 *
 * THE COST, NAMED: about 170KB fetched eagerly on a cold visit, and on a genuinely slow connection a
 * first page view in the fallback stack. Both are accepted deliberately; the second is why the
 * fallback stacks below are real stacks and not a bare `sans-serif`.
 */

/* ---------------------------------------------------------------------------------------------------
 * TOKENS
 *
 * Carried over from src/Airside.Prototype/Ui/Theme.cs and client.html, never picked by eye. The site is
 * a third surface built from the two that exist: the deck's scope palette, the installer's typography.
 *
 * THE ONE THING TO KNOW ABOUT THIS PALETTE: near black plus a single acid green is one of the most
 * recognisable signatures of generated design, and this is not that. Four colours do four different
 * jobs. Green is the ink the controller's picture is drawn in. Yellow is the paint that is really on
 * the ground. Orange is spent once, on a clearance. And the TEXT IS NEVER GREEN - it is warm paper,
 * which is what stops the page reading as a terminal.
 */
:root{
  --ground:#0D0F12;      /* Theme.BgDeep, and the brand's ground */
  --scope:#070D0B;       /* Theme.MapBg, the scope well */
  --asphalt:#1B1F26;
  --panel:#15181D;       /* Theme.Panel */
  --panel-hi:#1D222A;    /* Theme.PanelHi */
  --line:#232830;
  --line-soft:#1A1F26;

  --paper:#F2EFE3;       /* text. Warm, like the deck's Data brush */
  --mute:#9AA3AC;        /* Theme.TextMute */
  --dim:#6B7480;         /* Theme.TextDim, lifted slightly for a web contrast floor */

  --phosphor:#34E39A;    /* Theme.Phosphor, the scope's return */
  --phos-dim:#1C7A57;    /* Theme.PhosphorDim, range rings and leaders */
  --paint:#F2C230;       /* the marking as it is on the ground */
  --orange:#FF6B1A;      /* Theme.Orange. SPENT ONCE: the clearance, and the buy */
  --bad:#F0524B;

  --ui:"Barlow",-apple-system,"Segoe UI",Roboto,Helvetica,Arial,sans-serif;
  --sign:"Barlow Condensed","Barlow",-apple-system,"Segoe UI",sans-serif;
  --mono:"JetBrains Mono","Cascadia Mono",ui-monospace,Consolas,monospace;

  /* Theme.cs S1..S5, extended upward for a page rather than a panel. */
  --s1:4px; --s2:8px; --s3:12px; --s4:20px; --s5:32px; --s6:52px; --s7:84px;

  /* One easing curve for the whole product, the same one Theme.cs hand implements for WPF so the two
   * halves trace the same path. */
  --ease:cubic-bezier(.16,1,.3,1);
  --quick:140ms;
  --smooth:320ms;
}

/* ---------------------------------------------------------------------------------------------------
 * RESET AND GROUND
 */
*,*::before,*::after{box-sizing:border-box}
html{-webkit-text-size-adjust:100%}
body{
  margin:0;
  background:var(--ground);
  color:var(--paper);
  font:400 16px/1.6 var(--ui);
  -webkit-font-smoothing:antialiased;
  overflow-x:hidden;
}
img,svg{max-width:100%;height:auto;display:block}
a{color:inherit}
h1,h2,h3,p,ul,ol,dl,dd,figure{margin:0}
ul{padding:0;list-style:none}
:focus-visible{outline:2px solid var(--orange);outline-offset:3px}
/* AFTER the element rules and with !important, because a component that sets its own display wins
 * over the user agent's [hidden] rule. The lightbox is display:flex, so it shipped OPEN over the
 * whole page until this line existed. Any element hidden by an attribute is hidden. */
[hidden]{display:none !important}

/* Every digit that sits in a column, everywhere. A ticking or changing number that is not tabular
 * changes width and shoves whatever is beside it. */
.mono,.fig,.strip-val,.count{font-family:var(--mono);font-variant-numeric:tabular-nums}

/* ---------------------------------------------------------------------------------------------------
 * THE SCENE
 *
 * A fixed canvas behind everything, with the page scrolling over it. The sky is a CSS gradient and the
 * canvas is transparent over it, so no skydome is ever rendered - the same trick the installer uses.
 */
.scene{position:fixed;inset:0;z-index:0;pointer-events:none}
/* THE AIRFIELD ARRIVES RATHER THAN APPEARING. The canvas is created and its first frame drawn a few
 * hundred milliseconds after the page paints, and with no transition that read as a second layout
 * dropping onto the first. It starts transparent and site.js adds the class on the first frame, so
 * the sky gradient underneath is what the reader sees until there is something to show. */
.scene canvas{display:block;width:100%;height:100%;opacity:0;transition:opacity 620ms var(--ease)}
.scene canvas.up{opacity:1}
@media (prefers-reduced-motion:reduce){
  .scene canvas{transition:none}
}
.sky{
  position:fixed;inset:0;z-index:-1;
  background:
    radial-gradient(150% 84% at 52% 104%, #1F2E44 0%, #16202F 34%, #10141B 64%, var(--ground) 88%),
    var(--ground);
}

/* Two gradients over the render so type never fights it: one from the left, because the copy column is
 * left aligned, and one from the bottom. */
.veil{
  position:fixed;inset:0;z-index:1;pointer-events:none;
  background:
    linear-gradient(to right, rgba(13,15,18,.88) 0%, rgba(13,15,18,.44) 26%, rgba(13,15,18,0) 56%),
    linear-gradient(to top, rgba(13,15,18,.80) 0%, rgba(13,15,18,.16) 14%, rgba(13,15,18,0) 38%);
}

/* The scope overlay: range rings and aircraft labels drawn as DOM over the render, so the type is real
 * type in the real face rather than a texture. JS positions these; it does not style them. */
.scope-layer{position:fixed;inset:0;z-index:2;pointer-events:none;opacity:1;transition:opacity 600ms var(--ease)}
.scope-layer.off{opacity:0}
.tag{
  position:absolute;transform:translate(14px,-50%);
  font-family:var(--mono);font-size:11px;font-weight:500;letter-spacing:.04em;
  color:var(--phosphor);white-space:nowrap;
  text-shadow:0 0 12px rgba(52,227,154,.45);
}
.tag i{display:block;font-style:normal;font-size:10px;color:var(--phos-dim)}
.tag::before{
  content:"";position:absolute;right:100%;top:50%;width:11px;height:1px;
  background:var(--phos-dim);opacity:.8;
}
.leader{position:absolute;height:1px;background:var(--phos-dim);transform-origin:0 50%;opacity:.55}

/* ---------------------------------------------------------------------------------------------------
 * MASTHEAD
 *
 * IT CONTAINS NO NESTED <div>, AND THE REASON IS NOT THE ONE THIS COMMENT USED TO GIVE. It said the
 * gate for Paddle's "reachable from navigation" requirement parsed THIS element, reading from the
 * literal pattern <div class="top"> to the first closing </div>. That stopped being true on
 * 2026-09-06, when the legal links moved to the footer and ops/edge_test.py moved with them; the arm
 * now reads <footer>...</footer> and the masthead is not load bearing for it at all. Corrected
 * 2026-09-07 rather than left standing, because a constraint nobody can find the reason for is a
 * constraint the next person works around or, worse, preserves without knowing why.
 *
 * The rule is kept anyway: it costs nothing, the masthead has never needed a wrapper, and the brand
 * lockup added below is an <a>, which is the right element for it in any case.
 */
.top{
  position:fixed;top:0;left:0;right:0;z-index:20;
  display:flex;align-items:center;gap:var(--s3);
  padding:var(--s3) clamp(var(--s4),4vw,var(--s6));
  background:linear-gradient(to bottom, rgba(13,15,18,.92), rgba(13,15,18,0));
}
/* THE MARK AND THE WORDMARK ARE ONE OBJECT AND NOW ONE LINK (controller, 2026-09-07). The gap that
 * used to come from .top's own flex has to move in here, because what used to be two children of
 * the masthead is now one. No underline and no link colour: a brand lockup is not a sentence with a
 * word in it, and the pointer plus the focus ring already say it is a control. */
.top .brand{display:flex;align-items:center;gap:var(--s3);text-decoration:none;color:inherit}

.mark{width:18px;height:18px;flex:none}
.wordmark{
  font-family:var(--sign);font-weight:600;font-size:17px;
  letter-spacing:.20em;text-transform:uppercase;color:var(--paper);
}
/* A PAGE CONTROL, NOT A DESTINATION, so it is set apart from the links rather than among them: the
 * nav is places you can go, and this changes what the page is doing. It sits at the right hand end of
 * the masthead, sized under the nav's own type so it reads as an aside instead of competing.
 * IT IS NOT HIDDEN ON A PHONE, which was the first thing tried and is exactly backwards: the weakest
 * graphics anybody will open this page on are in a phone, so the narrow layout is where the control
 * earns its place. The masthead already wraps its nav, and this wraps with it. */
.bgstop{
  position:relative;
  /* ITS OWN GAP, WIDER THAN THE MASTHEAD'S. The masthead's flex gap is 12px, which is the right
   * distance INSIDE the brand lockup - the mark and the wordmark are one object. Between the
   * navigation and this control they are two different kinds of thing, a bare text destination and a
   * bordered page control, and 12px put the link's letters hard against the button's border. The
   * separation belongs to the control rather than to the row, because the row is spacing a lockup. */
  margin-left:var(--s4);
  font-family:var(--sign);font-weight:500;font-size:11px;
  letter-spacing:.10em;text-transform:uppercase;
  color:var(--dim);background:transparent;
  border:1px solid var(--line);border-radius:2px;
  padding:3px var(--s2);cursor:pointer;
  transition:color var(--quick) var(--ease),border-color var(--quick) var(--ease);
}
.bgstop:hover,.bgstop:focus-visible{color:var(--paper);border-color:var(--paper)}
/* The shared footnote bubble, re-anchored: this one hangs BELOW its control and off its right edge,
 * because the control is in a fixed masthead at the very top right of the window and the default
 * placement would put the explanation off the screen on two sides at once. */
.bgstop .tip{top:calc(100% + 8px);bottom:auto;left:auto;right:0;transform:none;text-align:left}
/* The reveal is written per owner rather than on .tip itself, so a bubble can never be shown by an
 * ancestor that merely happens to be hovered. Keyboard gets it on focus, as the figures do. */
.bgstop:hover .tip,.bgstop:focus-visible .tip{opacity:1;visibility:visible}

.top nav{margin-left:auto;display:flex;flex-wrap:wrap;gap:var(--s3) var(--s4);align-items:center}
.top nav a{
  font-family:var(--sign);font-weight:500;font-size:13px;
  letter-spacing:.10em;text-transform:uppercase;color:var(--dim);
  text-decoration:none;transition:color var(--quick) var(--ease);
}
.top nav a:hover{color:var(--paper)}
.top nav a.now{color:var(--paper)}

/* ---------------------------------------------------------------------------------------------------
 * STAGES
 *
 * Each beat is a section tall enough for the camera to move through it, with its copy held still by
 * position: sticky. Sticky rather than a scroll library: the browser does the pinning, so it cannot
 * desynchronise from the scrollbar, and it costs nothing.
 */
main{position:relative;z-index:3}
.stage{position:relative;min-height:100vh}
.stage.tall{min-height:230vh}
.stage-in{
  position:sticky;top:0;min-height:100vh;
  display:flex;flex-direction:column;justify-content:center;
  padding:var(--s7) clamp(var(--s4),5vw,var(--s7));
  max-width:min(46rem,92vw);
}
.stage-in.wide{max-width:min(64rem,94vw)}

/* A BAND IS A SECTION THAT STOPS THE FILM. The scroll driven part of this page is the top of it:
 * three stages where a camera moves behind the words. Everything below is read rather than watched,
 * and photographs in particular need a ground of their own, so a band paints over the scene and lets
 * the page behave like a page. It is also the honest structure: the drawing sells the idea, the
 * photographs are the evidence, and the two should not be competing in the same frame.
 */
.band{position:relative;z-index:3;background:var(--ground);border-top:1px solid var(--line-soft)}
.band-in{
  max-width:min(72rem,94vw);margin:0 auto;
  padding:var(--s7) clamp(var(--s4),5vw,var(--s6));
}

/* ---------------------------------------------------------------------------------------------------
 * GALLERY
 *
 * A RAIL RATHER THAN A GRID, and small. A wall of full width screenshots at the top of a page is a
 * demand; a strip at the bottom is an offer. Native overflow with scroll snapping, so it works with a
 * trackpad, a touch screen and a scrollbar without any of that being written, and the two buttons are
 * for the case where none of those is obvious.
 */
.gallery-band{border-top:1px solid var(--line)}
.rail-head{display:flex;align-items:flex-end;justify-content:space-between;gap:var(--s4);flex-wrap:wrap}
.rail-nav{display:flex;gap:var(--s2)}
.rail-btn{
  width:2.6rem;height:2.6rem;line-height:1;
  font-family:var(--ui);font-size:1.1rem;
  color:var(--paper);background:transparent;
  border:1px solid var(--line);border-radius:2px;cursor:pointer;
  transition:border-color var(--quick) var(--ease),color var(--quick) var(--ease);
}
.rail-btn:hover{border-color:var(--paper)}
.rail-btn:disabled{color:var(--dim);border-color:var(--line-soft);cursor:default}

.rail{
  display:flex;gap:var(--s4);margin-top:var(--s5);
  overflow-x:auto;overscroll-behavior-x:contain;
  scroll-snap-type:x mandatory;
  scrollbar-width:none;
  padding-bottom:var(--s2);
}
.rail::-webkit-scrollbar{display:none}
.shot{margin:0;flex:0 0 clamp(15rem,26vw,20rem);scroll-snap-align:start}
.shot-open{
  display:block;width:100%;padding:0;border:1px solid var(--line);border-radius:2px;
  background:var(--panel);cursor:zoom-in;overflow:hidden;
  transition:border-color var(--quick) var(--ease);
}
.shot-open:hover{border-color:var(--phos-dim)}
/* ONE BOX, WHATEVER THE SOURCE WAS. The rail now carries frames from two capture sessions and the older ones
   are a hair wider than 16 by 9. With height:auto alone each card took its own height and the strip ran ragged
   along the bottom, which reads as a broken layout rather than as a gallery. The frame is fixed here and the
   picture fills it; the full size image behind the click is untouched. */
.shot-open img{width:100%;height:auto;display:block;aspect-ratio:16/9;object-fit:cover}
/* THE PLAY MARK, DRAWN RATHER THAN FETCHED. A triangle in a ring, in the site's own ink, so the one
   card that is a film says so without a second image to load and without borrowing another product's
   button. position:relative goes on the button so the mark sits over the picture. */
.shot-open.is-video{position:relative}
.shot-open.is-video .play{
  position:absolute;left:50%;top:50%;transform:translate(-50%,-50%);
  width:3.2rem;height:3.2rem;border-radius:50%;
  border:1px solid var(--paper);background:rgba(13,15,18,.55);
  display:flex;align-items:center;justify-content:center;
  transition:background var(--quick) var(--ease),border-color var(--quick) var(--ease);
}
.shot-open.is-video .play::before{
  content:"";display:block;
  /* the triangle, nudged right by a hair because an optically centred play mark is not a
     mathematically centred one: the shape's mass sits behind its point */
  margin-left:.22rem;
  border-style:solid;border-width:.5rem 0 .5rem .82rem;
  border-color:transparent transparent transparent var(--paper);
}
.shot-open.is-video:hover .play{background:rgba(13,15,18,.78);border-color:var(--phos-dim)}

.shot figcaption{
  margin-top:var(--s2);color:var(--dim);
  font-family:var(--sign);font-weight:500;font-size:.82rem;letter-spacing:.06em;
}

.lightbox{
  position:fixed;inset:0;z-index:40;
  display:flex;flex-direction:column;align-items:center;justify-content:center;gap:var(--s3);
  padding:clamp(var(--s4),4vw,var(--s6));
  background:rgba(6,8,10,.94);
}
.lightbox img{max-width:min(112rem,96vw);max-height:80vh;width:auto;border:1px solid var(--line)}
/* THE PLAYER GETS THE SAME FRAME THE PHOTOGRAPHS GET, and its size is bounded the same two ways, by
   the viewport's width and by 80vh, so a wide screen does not put the controls below the fold. The
   ratio is held by the box rather than by the iframe: an iframe has no intrinsic size, so without
   this it collapses to the html default of 300 by 150. */
.lightbox-video{
  width:min(112rem,96vw);max-width:calc(80vh * 16 / 9);
  aspect-ratio:16/9;border:1px solid var(--line);background:#000;
}
.lightbox-video iframe{width:100%;height:100%;display:block;border:0}
.lightbox-cap{max-width:56ch;text-align:center;color:var(--mute);font-size:.94rem}
.lightbox-close{
  position:absolute;top:var(--s4);right:var(--s4);
  font-family:var(--sign);font-weight:600;font-size:.8rem;letter-spacing:.14em;text-transform:uppercase;
  color:var(--paper);background:transparent;border:1px solid var(--line);border-radius:2px;
  padding:var(--s2) var(--s4);cursor:pointer;
}
.lightbox-close:hover{border-color:var(--paper)}
body.locked{overflow:hidden}

/* ---------------------------------------------------------------------------------------------------
 * THE PURCHASE WINDOW
 *
 * BLURRED RATHER THAN BLACKED OUT, which is a decision about what is behind it: the airfield is still
 * moving under this panel and it should stay visible, because the thing being bought is the thing on
 * the screen. The lightbox above goes nearly opaque for the opposite reason, a photograph needs the
 * page out of the way.
 *
 * THE FALLBACK IS NOT OPTIONAL. Where backdrop-filter is unavailable the background colour below is
 * all that separates this panel from the scene, so it carries enough alpha to be readable on its own;
 * the blur then sharpens a wash that already worked.
 */
.buybox{
  position:fixed;inset:0;z-index:50;
  display:flex;align-items:center;justify-content:center;
  padding:clamp(var(--s3),3vw,var(--s5));
  background:rgba(6,8,10,.72);
  backdrop-filter:blur(14px) saturate(.8);
  overflow-y:auto;
}
.buybox-in{
  position:relative;width:min(60rem,100%);
  background:var(--panel);border:1px solid var(--line);border-radius:2px;
  padding:clamp(var(--s4),3vw,var(--s6));
}
.buybox-in .eyebrow{margin-bottom:var(--s2)}
.buybox-close{
  position:absolute;top:var(--s3);right:var(--s3);
  font-family:var(--sign);font-weight:600;font-size:.72rem;letter-spacing:.14em;text-transform:uppercase;
  color:var(--mute);background:transparent;border:1px solid var(--line);border-radius:2px;
  padding:var(--s1) var(--s3);cursor:pointer;
}
.buybox-close:hover{color:var(--paper);border-color:var(--paper)}
.buybox-note{color:var(--mute);margin-top:var(--s3)}
/* THE DISCOUNT CODE, AND IT IS THE ONE PLACE ORANGE IS SPENT IN THIS BOX. The palette's rule is that
 * orange is a clearance and gets used once per page; here the page is asking for one thing and this
 * is the sentence that changes what somebody pays, so it earns the paint. Mono because the code is
 * something to be COPIED CHARACTER BY CHARACTER into another site's form, which is the same reason
 * every licence key and hash on this product is set in it. */
.coupon{color:var(--orange);font-family:var(--mono);font-size:.92rem;letter-spacing:.01em}
.buybox-small{color:var(--dim);font-size:.86rem;margin-top:var(--s3);max-width:64ch}
.buybox-small a{color:var(--mute)}
.buybox-small a:hover{color:var(--paper)}

/* Two columns that collapse to one, and they are deliberately the SAME weight: a highlighted tier is a
 * recommendation, and the recommendation here depends on whether somebody wants a hand in the roadmap,
 * which is not a thing this page can know. */
.tiers{display:grid;grid-template-columns:repeat(auto-fit,minmax(17rem,1fr));gap:var(--s4);margin-top:var(--s5)}
.tier{
  display:flex;flex-direction:column;
  background:var(--ground);border:1px solid var(--line);border-radius:2px;
  padding:var(--s4);
}
.tier-name{
  font-family:var(--sign);font-weight:600;font-size:.8rem;
  letter-spacing:.14em;text-transform:uppercase;color:var(--dim);
}
.tier-price{
  font-family:var(--mono);font-size:2rem;font-weight:500;color:var(--paper);
  line-height:1;margin-top:var(--s2);
}
.tier-say{color:var(--mute);margin-top:var(--s3)}
/* THE CARD WITH NO LIST HOLDS ITS ONE SENTENCE AS A STATEMENT. Taking the four bullets off the base
 * edition left a column of dead air, because .tier-go pins both buttons to the bottom so they line
 * up - correct, and it makes an empty card look like a card that is missing something. So the
 * sentence is set larger and in paper rather than mute, and the free space is shared above and below
 * it instead of all falling underneath. What was a hole reads as room. */
.tier.plain .tier-say{
  margin-top:auto;margin-bottom:auto;
  font-size:1.2rem;color:var(--paper);max-width:20ch;line-height:1.35;
}
.tier-list{margin:var(--s3) 0 0;padding-left:1.1em;color:var(--mute);display:grid;gap:var(--s2)}
.tier-list li{font-size:.92rem}
/* Pushed to the bottom by the auto margin, so two columns of unequal text still line their buttons up. */
.tier-go{margin-top:auto;padding-top:var(--s4)}
.tier-go .button{margin-top:0;width:100%;text-align:center;padding-left:var(--s4);padding-right:var(--s4)}
.tier.supporter .button{background:transparent;color:var(--paper);border-color:var(--line)}
.tier.supporter .button:hover{border-color:var(--paper)}
/* A button that leads nowhere yet says so, instead of looking pressable and doing nothing. */
.button[disabled]{opacity:.5;cursor:not-allowed;filter:none}
.button[disabled]:hover{filter:none}

/* ---------------------------------------------------------------------------------------------------
 * TYPE
 */
.eyebrow{
  font-family:var(--sign);font-weight:600;font-size:.78rem;
  letter-spacing:.16em;text-transform:uppercase;color:var(--phos-dim);
  margin-bottom:var(--s4);
}
h1{
  font-weight:300;font-size:clamp(2.5rem,6vw,5.2rem);line-height:.98;
  letter-spacing:-.03em;text-wrap:balance;
}
h1 b{font-weight:600}
h2{
  font-weight:300;font-size:clamp(1.9rem,3.6vw,3.1rem);line-height:1.04;
  letter-spacing:-.026em;text-wrap:balance;
}
h2 b{font-weight:600}
h3{font-family:var(--sign);font-weight:600;font-size:1.05rem;letter-spacing:.02em}
.lede{
  font-weight:300;font-size:clamp(1.1rem,1.5vw,1.4rem);line-height:1.45;
  color:var(--mute);max-width:34ch;margin-top:var(--s4);
}
p+p{margin-top:var(--s4)}
.say{color:var(--mute);max-width:60ch;margin-top:var(--s4)}
/* The note that says the scene is a drawing. Quiet, because it is a fact rather than a disclaimer,
 * and it sits with the copy rather than being pushed into a corner where nobody reads it. */
.note{
  margin-top:var(--s5);max-width:48ch;color:var(--dim);font-size:.86rem;line-height:1.55;
  border-left:1px solid var(--line);padding-left:var(--s3);
}
.tight{max-width:46ch}

/* ---------------------------------------------------------------------------------------------------
 * THE PRODUCT'S OWN COMPONENTS
 *
 * A flight progress strip as the deck draws it (Ui/StripCard.cs): a five pixel holder edge carrying the
 * phase, a monospace callsign in the warm data white, and a printed row of boxes. Not a decoration of
 * one: the same anatomy, so a visitor who buys recognises what they saw.
 */
/* THE STRIP HAD NO TOP MARGIN AT ALL and sat against the paragraph above it (controller, 2026-09-06:
 * "bu yazı ile strip kutucuğu çok dipdibe"). Everything else in a section is spaced by its own top
 * margin, and this card was the one element that had none, so it read as part of the sentence rather
 * than as the thing the sentence is about. */
.strip{
  margin-top:var(--s6);
  display:grid;grid-template-columns:5px 1fr;
  background:var(--panel);border:1px solid var(--line);border-radius:2px;
  overflow:hidden;transition:background var(--quick) var(--ease),border-color var(--quick) var(--ease);
}
.strip.sel{background:var(--panel-hi);border-color:var(--orange)}
.strip-edge{background:var(--phos-dim)}
.strip.dep .strip-edge{background:#38ADD8}   /* Theme.DepEdge, cool for departures */
.strip.arr .strip-edge{background:#E79A3A}   /* Theme.ArrEdge, warm for arrivals */
.strip-body{padding:var(--s3) var(--s4)}
.strip-head{display:flex;align-items:baseline;gap:var(--s3);flex-wrap:wrap}
.strip-call{font-family:var(--mono);font-weight:500;font-size:1.06rem;color:#E8E2D2}
.strip-type{font-family:var(--mono);font-size:.8rem;color:var(--dim)}
.strip-phase{
  margin-left:auto;font-family:var(--sign);font-weight:600;font-size:.74rem;
  letter-spacing:.13em;text-transform:uppercase;color:var(--phosphor);
}
.strip-row{display:flex;margin-top:var(--s3);border-top:1px solid var(--line-soft);padding-top:var(--s2)}
.strip-box{padding-right:var(--s4);margin-right:var(--s4);border-right:1px solid var(--line-soft)}
.strip-box:last-child{border-right:0;margin-right:0;padding-right:0}
.strip-key{
  display:block;font-family:var(--sign);font-weight:500;font-size:.64rem;
  letter-spacing:.13em;text-transform:uppercase;color:var(--dim);
}
.strip-val{font-size:.86rem;color:#E8E2D2}
.strip-wait{
  margin-top:var(--s3);font-family:var(--sign);font-weight:600;font-size:.76rem;
  letter-spacing:.12em;text-transform:uppercase;color:var(--orange);
}

/* The radio feed. Outbound is what you said, inbound is what the crew said back. */
.radio{margin-top:var(--s4);font-family:var(--mono);font-size:.86rem;line-height:1.65}
.radio li{display:grid;grid-template-columns:1.6em 1fr;gap:var(--s2);color:var(--mute)}
.radio li+li{margin-top:var(--s2)}
.radio li::before{content:"";}
.radio .out{color:#E8E2D2}
.radio .out::before{content:"TX";font-family:var(--sign);font-size:.66rem;letter-spacing:.1em;color:var(--orange)}
.radio .in::before{content:"RX";font-family:var(--sign);font-size:.66rem;letter-spacing:.1em;color:var(--phos-dim)}

/* The clearance button, as the deck draws it: an outlined pill that inverts on hover. This is the one
 * place orange is spent, and it is spent on the verb the whole product is about. */
.cmd{
  display:inline-block;margin-top:var(--s4);
  padding:var(--s3) var(--s5);
  font-family:var(--sign);font-weight:600;font-size:.86rem;
  letter-spacing:.14em;text-transform:uppercase;
  color:var(--orange);background:transparent;
  border:1px solid var(--orange);border-radius:2px;
  cursor:pointer;
  transition:background var(--quick) var(--ease),color var(--quick) var(--ease);
}
.cmd:hover:not(:disabled){background:var(--orange);color:var(--ground)}
.cmd:disabled{color:var(--dim);border-color:var(--line);cursor:default}

/* ---------------------------------------------------------------------------------------------------
 * LISTS, FIGURES, FACTS
 */
.layers{display:grid;gap:var(--s5);margin-top:var(--s6)}
/* TWO COLUMNS WHERE THERE IS ROOM, for the FAQ only. A single 52ch column inside a 72rem band leaves
 * half the width empty and makes eight questions read as a very long page; auto-fit collapses it to
 * one column on its own below about 54rem, so there is no second breakpoint to keep in step. */
.layers.two{grid-template-columns:repeat(auto-fit,minmax(24rem,1fr));gap:var(--s5) var(--s6)}
.layer h3{color:var(--paper)}
.layer p{color:var(--mute);margin-top:var(--s2);max-width:52ch}

/* ---------------------------------------------------------------- the FAQ accordion -------------
   NO JAVASCRIPT AND NOTHING TO LOAD. <details> carries the whole behaviour: click, Enter, focus and
   the open state are the browser's, so this block cannot break the way a hand written accordion
   breaks, and the answers stay in the DOM for a crawler that runs no scripts.

   THE HEIGHT IS NOT ANIMATED, on purpose. Doing that properly still needs either script or
   ::details-content, which one engine has; a half animation that janks is worse than an honest
   instant one. The marker rotates, which is the part the eye actually follows. */
.qa{margin-top:var(--s6);border-top:1px solid var(--line);max-width:60rem}
.qa details{border-bottom:1px solid var(--line)}

/* list-style AND the webkit marker: Safari still uses the second one, and leaving it out puts a
   black triangle in front of every question on one browser only. */
.qa summary{display:flex;align-items:flex-start;gap:var(--s4);padding:var(--s4) 0;cursor:pointer;list-style:none}
.qa summary::-webkit-details-marker{display:none}
.qa summary h3{flex:1;margin:0;color:var(--paper)}

/* The chevron is drawn from two borders rather than shipped as a file: one fewer request, and it
   takes its colour from the same tokens as everything around it. Down when shut, up when open. */
.qa summary::after{
  content:"";flex:none;width:10px;height:10px;margin-top:.5em;
  border-right:1.5px solid var(--dim);border-bottom:1.5px solid var(--dim);
  transform:rotate(45deg);transform-origin:center;
  transition:transform .28s var(--ease),border-color .18s var(--ease);
}
.qa summary:hover::after,
.qa summary:focus-visible::after{border-color:var(--paper)}
.qa details[open] summary::after{transform:rotate(-135deg);border-color:var(--paper)}
.qa details[open] summary h3{color:var(--paper)}
.qa p{color:var(--mute);margin:0 0 var(--s4);max-width:64ch;padding-right:var(--s5)}

.figures{
  display:grid;grid-template-columns:repeat(auto-fit,minmax(13rem,1fr));
  gap:1px;margin-top:var(--s6);background:var(--line-soft);
  border:1px solid var(--line-soft);
}
.figure{background:var(--ground);padding:var(--s4)}
.fig{display:block;font-size:clamp(1.6rem,3.2vw,2.4rem);font-weight:400;color:var(--paper);line-height:1.1}
.fig.word{font-family:var(--ui);font-weight:300;letter-spacing:-.02em}

/* A footnote you can reach without leaving the page. No library and no script: the mark is focusable,
 * so it opens on hover AND on keyboard focus, which is the half that usually gets left out. */
.info{
  display:inline-flex;align-items:center;justify-content:center;
  width:1.15rem;height:1.15rem;margin-top:.45rem;
  font-family:var(--ui);font-style:italic;font-size:.78rem;line-height:1;
  color:var(--dim);border:1px solid var(--line);border-radius:50%;
  cursor:help;position:relative;
}
.info:hover,.info:focus-visible{color:var(--paper);border-color:var(--paper)}
.tip{
  position:absolute;bottom:calc(100% + 8px);left:0;
  width:min(22rem,70vw);transform:translateX(-35%);
  padding:var(--s3);
  background:var(--panel-hi);border:1px solid var(--line);border-radius:2px;
  font-family:var(--ui);font-style:normal;font-size:.82rem;font-weight:400;letter-spacing:0;
  line-height:1.5;color:var(--mute);text-transform:none;
  opacity:0;visibility:hidden;transition:opacity var(--quick) var(--ease);
  z-index:10;
}
.info:hover .tip,.info:focus-visible .tip{opacity:1;visibility:visible}

/* THE SAME FOOTNOTE, SET IN A LINE OF TEXT RATHER THAN BESIDE A BIG NUMBER. The margin above is there to
 * drop the mark onto the baseline of a 3rem figure and would sink it below a body line; and the tooltip
 * opens leftward here because this mark sits at the END of a long row, where opening rightward would take
 * it off the edge of a narrow window. */
.fact .info{margin-top:0;vertical-align:middle}
.fact .tip{left:auto;right:0;transform:none}

.fig-row{display:flex;align-items:flex-start;gap:.35em;margin:0}
.figure > span{
  display:block;margin-top:var(--s2);
  font-family:var(--sign);font-weight:500;font-size:.74rem;
  letter-spacing:.12em;text-transform:uppercase;color:var(--dim);
}

.facts{display:grid;gap:1px;margin-top:var(--s5);background:var(--line-soft);border:1px solid var(--line-soft)}
.fact{
  background:var(--ground);padding:var(--s3) var(--s4);
  display:grid;grid-template-columns:minmax(9rem,14rem) 1fr;gap:var(--s4);align-items:baseline;
}
.fact dt{font-family:var(--sign);font-weight:500;font-size:.78rem;letter-spacing:.11em;text-transform:uppercase;color:var(--dim)}
.fact dd{margin:0;color:var(--paper)}
.fact dd.warn{color:var(--paint)}

/* The state chips that used to sit beside each limit are GONE, along with their three rules. They read
 * Closed / Deferred / Being looked at, which is our engineering vocabulary rather than the reader's, and
 * the strongest of them told somebody deciding whether to buy that we had stopped work on a thing we had
 * not. What is left is two sentences, which is what a reader takes in at this point on the page. */
/* `.limits` and `.limit` went with the pre-purchase block they laid out (controller, 2026-09-06).
 * Named rather than deleted silently, like button.danger on the portal: a rule nothing uses is a
 * rule the next reader takes for load bearing. The list itself is published in full on
 * known-limits.html, which the footer links from every page. */

/* ---------------------------------------------------------------------------------------------------
 * PRICE
 */
.buy{margin-top:var(--s6);border-top:1px solid var(--line);padding-top:var(--s5)}
.price{font-family:var(--mono);font-size:clamp(2rem,4vw,3rem);font-weight:500;color:var(--paper);line-height:1}
.price-note{color:var(--mute);margin-top:var(--s3);max-width:44ch}
/* The day count reads as a clock, not as a discount badge: yellow is the paint on the ground everywhere
 * else on this page, and it is deliberately NOT the orange that means "act now" on the button. Tabular
 * figures so the number does not shuffle its neighbours as it falls from 30 to 9. */
.count{color:var(--paint);font-family:var(--mono);font-weight:500;font-variant-numeric:tabular-nums}
/* The one promise in this block, given its own line and its own weight. It used to be the tail of a
 * sentence about what the price is not, which is the easiest place on a page to lose something. */
.price-keep{color:var(--mute);margin-top:var(--s3);max-width:46ch}
.price-keep b{color:var(--phosphor);font-weight:600}
/* The hero's only control. It sits under a heading that already has its own bottom margin, so the
 * button's default top margin would double it. */
.hero-buy{margin-top:var(--s4)}
.hero-buy .button{margin-top:0}
.button{
  display:inline-block;margin-top:var(--s5);
  padding:var(--s3) var(--s6);
  font-family:var(--sign);font-weight:600;font-size:.95rem;
  letter-spacing:.14em;text-transform:uppercase;text-decoration:none;
  color:var(--ground);background:var(--orange);
  border:1px solid var(--orange);border-radius:2px;
  /* A <button> DOES NOT GET THE HAND BY ITSELF, and half the things wearing this class are buttons
   * rather than links: the purchase window's two tiers and the hero's own Buy control are <button>
   * elements, because they open a panel instead of going to a URL. An anchor gets the pointer from
   * being an anchor, so the same class looked pressable in one place and dead in another. */
  cursor:pointer;
  transition:filter var(--quick) var(--ease);
}
.button:hover{filter:brightness(1.08)}
.button.ghost{background:transparent;color:var(--paper);border-color:var(--line)}
.button.ghost:hover{border-color:var(--paper)}
.buttons{display:flex;flex-wrap:wrap;gap:var(--s3);align-items:center}

/* ---------------------------------------------------------------------------------------------------
 * PROSE, for the legal pages, the press kit and the limits page
 *
 * These are read rather than scanned, so the measure is narrow, the leading is open, and there is no
 * scene behind them: a long document over a moving render is unreadable and the render is not doing any
 * work on a page whose job is to be quoted back at us.
 */
.doc{position:relative;z-index:3;background:var(--ground)}
.wrap{max-width:52rem;margin:0 auto;padding:0 clamp(var(--s4),4vw,var(--s5))}

/* ---------------------------------------------------------------- the blog ----------------------
   These pages are rendered by Django on the marketing host, so they load THIS stylesheet rather than
   one of their own. That is the whole reason the blog is not on the portal: one identity, one file,
   nothing to keep in step. Every token below is the site's. */
.posts{display:grid;gap:0;padding-bottom:var(--s7);border-top:1px solid var(--line)}
.post-card{
  display:grid;grid-template-columns:14rem 1fr;gap:var(--s5);align-items:start;
  padding:var(--s5) 0;border-bottom:1px solid var(--line);
  text-decoration:none;color:inherit;
}
/* A post with no image is one column, not one column with a hole where the image would be. */
.post-card.no-shot{grid-template-columns:1fr}
.post-text{display:block;min-width:0}
.post-shot{display:block;overflow:hidden;border-radius:2px;background:var(--panel);aspect-ratio:16/9}
.post-shot img{width:100%;height:100%;object-fit:cover;display:block;transition:transform .5s var(--ease)}
.post-card:hover .post-shot img{transform:scale(1.03)}

/* The label row is its own line rather than a prefix on the title, so a release note and a piece of
   news are told apart before either title is read. */
.post-meta{display:flex;flex-wrap:wrap;gap:var(--s3);align-items:baseline;margin:0}
/* THE SAME TREATMENT AS .eyebrow, which is this site's established label: same family, same weight,
   same tracking, same phosphor. A second look for the same job is how two pages stop matching. */
.post-kind{
  font-family:var(--sign);font-weight:600;font-size:.74rem;
  letter-spacing:.16em;text-transform:uppercase;color:var(--phos-dim);
}
.post-date{font-size:.82rem;color:var(--dim)}
.post-title{
  display:block;margin-top:var(--s2);color:var(--paper);
  font-size:1.28rem;line-height:1.28;letter-spacing:-.01em;text-wrap:balance;
  transition:color .18s var(--ease);
}
.post-card:hover .post-title{text-decoration:underline;text-underline-offset:4px;text-decoration-color:var(--phos-dim)}
.post-sum{display:block;margin-top:var(--s2);color:var(--mute);font-size:.95rem;line-height:1.6}

.post-hero{margin:0 0 var(--s6);border-radius:2px;overflow:hidden;background:var(--panel)}
.post-hero img{width:100%;height:auto;display:block}

/* The body is .prose as well, so headings, lists, links and tables are already the site's. Only the
   leading changes: .prose opens a page, this one continues one. */
/* THE HEADER AND THE BODY ARE ONE ARTICLE. .prose is written to OPEN a page, so it carries a full
   var(--s7) below it; on a post that lands between the summary and the first heading, and the
   heading brings its own var(--s6) on top of it. Measured at 136px of nothing. The header keeps its
   opening space and gives up its closing one, so the gap becomes the heading's own margin. */
.prose.post-head{padding-bottom:0}

/* .prose SETS `padding` AS A SHORTHAND and it is declared after this block, so `.post-body` alone
   lost every time and the post opened with 84px of dead space under its summary. MEASURED in the
   browser. Two class names rather than moving the rule, so the fix does not depend on file order. */
.prose.post-body{padding-top:0;padding-bottom:0}

/* A PICTURE IN THE BODY IS AS WIDE AS THE HERO, AND UNTIL 2026-09-07 IT WAS NOT.
 *
 * MEASURED in a browser at 1440px on the first post that ever had images in it: the hero ran the
 * full 784px of .wrap and every figure in the body ran 608px, because .prose sets a 38rem reading
 * measure and the hero is not inside one. Two widths on one page, neither of them wrong on its own,
 * and the result reads as a mistake rather than as a choice: the second picture looks like it failed
 * to load properly.
 *
 * A READING MEASURE IS FOR READING. 38rem is about sixty five characters and it is the right width
 * for a paragraph; it is not a constraint a photograph has any reason to obey. So the body gives up
 * its own max-width and hands it to the things that are read, which leaves figures free to take the
 * page. No negative margins and nothing to keep in step with .wrap.
 */
.prose.post-body{max-width:none}
.post-body > p,
.post-body > h2,
.post-body > h3,
.post-body > ul,
.post-body > ol,
.post-body > blockquote,
.post-body > .post-code,
.post-body > table{max-width:38rem}

.post-fig{margin:var(--s5) 0 0}
/* The panel fill sits behind the IMAGE and not behind the whole figure. It is there so a picture
 * that has not arrived yet is a dark rectangle rather than a flash of nothing; with it on the figure
 * the caption underneath sat on a panel coloured band with no inset, which reads as an unfinished
 * block rather than as a line of text. */
.post-fig img{width:100%;height:auto;display:block;border-radius:2px;background:var(--panel)}
.post-fig figcaption{margin-top:var(--s3);color:var(--dim);font-size:.85rem;max-width:38rem}
.post-code{
  margin-top:var(--s4);padding:var(--s4);overflow-x:auto;
  background:var(--panel);border:1px solid var(--line);border-radius:2px;
  font:400 .86rem/1.62 var(--mono);color:var(--mute);
}
.post-body blockquote{
  margin:var(--s4) 0 0;padding-left:var(--s4);border-left:2px solid var(--phos-dim);
  color:var(--mute);
}
/* THE WAY BACK TO THE INDEX, AND THE HOLE THAT WAS IN FRONT OF IT. .prose opens a page, so it
 * carries calc(s7 + s5) on top; on the trailing block of a post that landed AFTER the body's own
 * closing padding, and the two together measured about 168px of nothing between the last sentence
 * and one link. The body now closes at zero and this block owns the whole gap, which is one number
 * somebody can change rather than two that add up. */
.prose.post-foot{padding:var(--s6) 0 var(--s7)}

/* WHERE AIRSIDE IS. Four marks in the footer, quiet by default and brightening only on the ones
 * that are actually links: an <a> with no href yet is drawn at the same weight but does not respond
 * to a pointer, so the row never promises a destination it does not have.
 *
 * <img> RATHER THAN INLINE SVG, and the reason is in assets/social/README.txt: the footer is
 * repeated across seven static pages and the blog template, and an SVG loaded through <img> cannot
 * run script at all. The fill is baked into each file because an <img> cannot inherit currentColor,
 * so the dimming is opacity, which is also the one property worth animating here.
 */
.social{display:flex;gap:var(--s4);align-items:center;margin-top:var(--s4)}
.social-link{display:inline-flex;line-height:0}
.social-link img{width:20px;height:20px;opacity:.45;transition:opacity var(--quick) var(--ease)}
.social-link[href]:hover img{opacity:1}
.social-link:not([href]){cursor:default}

.wrap-in{padding:0 0 var(--s7)}
.gap-lg{margin-top:var(--s6)}

@media (max-width:44rem){
  .post-card{grid-template-columns:1fr;gap:var(--s4)}
}
.prose{max-width:38rem;padding:calc(var(--s7) + var(--s5)) 0 var(--s7)}
.prose h1{font-size:clamp(2rem,4.4vw,3.2rem);margin-bottom:var(--s3)}
.prose h2{
  font-family:var(--sign);font-weight:600;font-size:.82rem;letter-spacing:.15em;text-transform:uppercase;
  color:var(--phos-dim);margin:var(--s6) 0 var(--s3);
}
.prose h3{margin:var(--s5) 0 var(--s2)}
.prose p{margin-top:var(--s4);color:var(--paper);font-size:1.02rem;line-height:1.72}
.prose p.muted,.muted{color:var(--dim);font-size:.92rem}
.prose ul{margin-top:var(--s4);display:grid;gap:var(--s2)}
.prose ul li{position:relative;padding-left:var(--s4);color:var(--paper);line-height:1.66}
.prose ul li::before{content:"";position:absolute;left:0;top:.68em;width:7px;height:1px;background:var(--phos-dim)}
.prose a{color:var(--paper);text-decoration:underline;text-underline-offset:3px;text-decoration-color:var(--phos-dim)}
.prose a:hover{text-decoration-color:var(--paper)}
.prose table{width:100%;border-collapse:collapse;margin-top:var(--s4);font-size:.95rem}
.prose th{
  text-align:left;font-family:var(--sign);font-weight:600;font-size:.74rem;
  letter-spacing:.12em;text-transform:uppercase;color:var(--dim);
  border-bottom:1px solid var(--line);padding:var(--s2) var(--s3) var(--s2) 0;
}
.prose td{border-bottom:1px solid var(--line-soft);padding:var(--s3) var(--s3) var(--s3) 0;vertical-align:top;color:var(--paper)}
.prose td:first-child{color:var(--mute);width:38%}
.scroll{overflow-x:auto}

/* ---------------------------------------------------------------------------------------------------
 * FOOTER
 */
footer{
  position:relative;z-index:3;background:var(--ground);
  border-top:1px solid var(--line);
  padding:var(--s6) clamp(var(--s4),5vw,var(--s7));
}
footer nav{display:flex;flex-wrap:wrap;gap:var(--s3) var(--s5)}
footer nav a{
  font-family:var(--sign);font-weight:500;font-size:.8rem;letter-spacing:.1em;text-transform:uppercase;
  color:var(--dim);text-decoration:none;
}
footer nav a:hover{color:var(--paper)}
footer p{margin-top:var(--s5);max-width:52ch}

/* ---------------------------------------------------------------------------------------------------
 * WHEN THERE IS NO RENDER
 *
 * Two ways that happens and both are ordinary: a machine or browser with WebGL unavailable, and a
 * visitor who has asked their system for less motion. The page is written so that neither is a
 * degraded experience with a hole in it - every word is in the DOM, and the scene was always the
 * illustration rather than the content. site.js sets .no-scene on <html> when it cannot draw.
 */
.no-scene .scene{display:none}
.no-scene .scope-layer{display:none}
.no-scene .veil{
  background:
    radial-gradient(70% 55% at 78% 34%, rgba(52,227,154,.05), transparent 60%),
    linear-gradient(to right, rgba(13,15,18,.9) 0%, rgba(13,15,18,.5) 46%, rgba(13,15,18,.2) 100%);
}
.no-scene .stage{min-height:auto}
.no-scene .stage.tall{min-height:auto}
.no-scene .stage-in{min-height:auto;position:static;padding-top:var(--s6);padding-bottom:var(--s6)}
.no-scene .band{border-top-color:var(--line)}

@media (prefers-reduced-motion:reduce){
  *{animation-duration:.001ms !important;transition-duration:.001ms !important;scroll-behavior:auto !important}
  /* The declaration above reaches every animated ELEMENT and reaches nothing inside a canvas. site.js
   * reads the same query and draws one frame without arming the loop, which is the half that matters. */
  .stage.tall{min-height:120vh}
}

/* ---------------------------------------------------------------------------------------------------
 * NARROW
 *
 * The scene stays, simplified by site.js rather than removed, because a phone visitor is being sold the
 * same thing. What changes here is the copy column: full width, and the veil stops pulling from the
 * left because there is no room for a margin.
 */
@media (max-width:44rem){
  body{font-size:15px}
  .veil{background:linear-gradient(to top, rgba(13,15,18,.95) 0%, rgba(13,15,18,.62) 30%, rgba(13,15,18,.18) 70%)}
  .stage-in{padding-left:var(--s4);padding-right:var(--s4);max-width:none}
  .fact{grid-template-columns:1fr;gap:var(--s1)}
  .top nav{gap:var(--s2) var(--s3)}
  .top nav a{font-size:12px}
  .prose td:first-child{width:auto}
  .band-in{padding-left:var(--s4);padding-right:var(--s4)}
  .shot{flex:0 0 78vw}
  .lightbox img{max-height:64vh}
}
