/* ==========================================================================
   Railit landing page.

   ─────────────────────────────────────────────────────────────────────────
   WHY THIS FILE EXISTS INSTEAD OF LIVING IN styles.css

   Both sheets are dark, so the ORIGINAL reason for the split is gone: this
   file used to be black ink on white paper and could not have shared tokens
   with a dark one. The split stays for the reason underneath it. Four pages
   (contact, privacy, terms, refunds, and 404 makes five) share styles.css,
   and each of them carries its own <style> block with card fills typed
   straight in as literal hexes: #101010, #1F1F1F, #131313. Those numbers are
   not derived from anything and will not follow a token. Everything below IS
   derived, from one pair of values, and merging the two would mean either
   abandoning that or editing five pages this task does not own. So the
   landing page still loads THIS file and nothing else, and styles.css is
   still left byte-identical.

   ─────────────────────────────────────────────────────────────────────────
   THE PALETTE HAS NO COLOUR, WHICH IS THE POINT

   The reference this page follows carries one searing orange and hangs its
   whole hierarchy on it: the button, the eyebrow, the tinted surface, the
   active state. Railit has no hue at all (brand/palette.json argues why), so
   the accent is rebuilt out of the three things that are left.

     WEIGHT    a filled white button against an outlined one. The fill IS the
               accent, and there is exactly one filled control per view.
     CONTRAST  one thing at full ink, everything else stepped back. The
               headline is the ink, the sentence under it is 78% of it, the
               caption is 62%. Nothing is dimmed by opacity.
     SCALE     106px against 16px says more than any colour pair does.

   ─────────────────────────────────────────────────────────────────────────
   THE PAGE IS THE DARK HALF, AND THE LADDER IS THE SAME LADDER

   brand/palette.json states that light and dark are mirrors: `ink` is
   near-black on light and near-white on dark, `action` follows. <html>
   carries the `dark` class, so --brand-ink out of palette.css is already the
   near-white value; --paper is the neutral ink from the same file. Every step
   below is a percentage of ONE over the OTHER, so the entire ramp turned over
   without a single value being retyped, and the three-step hierarchy (full
   ink, 78%, 62%) is the same hierarchy pointing the other way.

   It was re-measured rather than assumed, and it came out stronger than it
   was on paper: 18.97:1, 11.49:1, 7.46:1 against this ground, where the white
   page gave 17.9, 9.7 and 5.4. Mixing toward black loses less contrast per
   percentage point than mixing toward white does. The tints move the same
   way: 3% of ink over this paper is 2.3 steps of L*, where 3% over white was
   1.7, so the faintest surface on the page is if anything a shade more
   present than it was, not less.

   Every value below is derived from --brand-ink and --wehi-ink, which are
   generated into palette.css from brand/palette.json, so a change there still
   moves this page. No brand hex is typed here.

   ─────────────────────────────────────────────────────────────────────────
   TYPE

   Geist, already served locally, at 400/450/500 and never heavier. Hierarchy
   is size, not weight: the display is 106px at 400, the caption is 12px at
   450, and a bold anything would be a fourth axis competing with the three
   above. Tracking is microscopically POSITIVE and grows as the size falls
   (0.01em display, 0.03em caption), which is what keeps a 106px line from
   reading as a logo and a 12px caption from reading as a smudge.
   ========================================================================== */

/* ------------------------------------------------------------------ fonts */

/* Local files, all of them. A landing page that fetches a font from a third
   party has handed that party every visitor's IP address and a render-blocking
   request. This site has never had one and does not gain one here. */

@font-face {
  font-family: 'Geist';
  src: url('fonts/geist-latin-wght-normal.woff2') format('woff2-variations'),
       url('fonts/geist-latin-wght-normal.woff2') format('woff2');
  font-weight: 100 900;
  font-style: normal;
  font-display: swap;
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA,
    U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215;
}
@font-face {
  font-family: 'Geist';
  src: url('fonts/geist-latin-ext-wght-normal.woff2') format('woff2-variations'),
       url('fonts/geist-latin-ext-wght-normal.woff2') format('woff2');
  font-weight: 100 900;
  font-style: normal;
  font-display: swap;
  unicode-range: U+0100-024F, U+0259, U+1E00-1EFF, U+2020, U+20A0-20AB,
    U+20AD-20CF, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
@font-face {
  font-family: 'Geist Mono';
  src: url('fonts/geist-mono-latin-wght-normal.woff2') format('woff2-variations'),
       url('fonts/geist-mono-latin-wght-normal.woff2') format('woff2');
  font-weight: 100 900;
  font-style: normal;
  font-display: swap;
}

/* Thmanyah is the Arabic DISPLAY face: it carries the headlines when the page
   is switched to Arabic. Light and Regular only, because the weight ceiling
   applies in both scripts. */
@font-face {
  font-family: 'Thmanyah';
  src: url('fonts/thmanyah-Light.woff2') format('woff2');
  font-weight: 300;
  font-style: normal;
  font-display: swap;
}
@font-face {
  font-family: 'Thmanyah';
  src: url('fonts/thmanyah-Regular.woff2') format('woff2');
  font-weight: 400;
  font-style: normal;
  font-display: swap;
}
@font-face {
  font-family: 'Thmanyah';
  src: url('fonts/thmanyah-Medium.woff2') format('woff2');
  font-weight: 500;
  font-style: normal;
  font-display: swap;
}
/* Noto Kufi carries Arabic RUNNING TEXT. Thmanyah is drawn for headlines and
   goes muddy at 16px over four lines; Kufi stays legible and is the face the
   product itself uses for body copy. */
@font-face {
  font-family: 'Noto Kufi Arabic';
  src: url('fonts/noto-kufi-arabic.woff2') format('woff2');
  font-weight: 300 600;
  font-style: normal;
  font-display: swap;
}

/* ----------------------------------------------------------------- tokens */

:root {
  /*
    PAPER AND INK, AND NEITHER IS A HEX.

    --brand-ink comes from palette.css, which is generated from
    brand/palette.json, and the `dark` class on <html> is what makes it the
    near-white of the pair rather than the near-black. --paper is that file's
    neutral ink. Everything below is mixed out of those two, which is why the
    page turned over without any of the percentages changing.
  */
  --paper: var(--wehi-ink);
  --ink: var(--brand-ink);

  /*
    THE INK LADDER.

    Four steps, each a percentage of the ink over paper, so the whole ramp
    moves if the brand ever does. Every step is declared twice: the first
    declaration is a plain grey of the same value for a browser without
    color-mix, the second wins everywhere that has it. Without the pair, an
    unsupported color-mix makes the declaration invalid and the text inherits
    whatever was above it.

    Contrast on this paper, measured: ink 18.97:1, ink-2 11.49:1, ink-3
    7.46:1. ink-4 is 4.36:1, a hair under the 4.5 body text needs, and is
    therefore still NEVER running text: rules, borders, and glyphs that sit
    beside a label saying the same thing.

    The fallbacks are each one step off the arithmetic mean on purpose. The
    exact 3% mix is the brand hex itself, and `npm run brand:check` hunts that
    string wherever it is not generated, correctly: a colour typed by hand
    does not follow palette.json. One value in 255, in a declaration only a
    browser without color-mix ever reads, is not worth being the exception.
  */
  --ink-2: #C6C6C6;
  --ink-2: color-mix(in srgb, var(--ink) 78%, var(--paper));
  --ink-3: #9F9F9F;
  --ink-3: color-mix(in srgb, var(--ink) 62%, var(--paper));
  --ink-4: #767676;
  --ink-4: color-mix(in srgb, var(--ink) 45%, var(--paper));

  /* THE TINTS. What the reference does with a wash of its accent, done with a
     wash of the ink. Three depths is the whole range: any more and they stop
     being distinguishable from each other. */
  --tint-1: #121212;
  --tint-1: color-mix(in srgb, var(--ink) 3%, var(--paper));
  --tint-2: #161616;
  --tint-2: color-mix(in srgb, var(--ink) 5%, var(--paper));
  --tint-3: #1D1D1D;
  --tint-3: color-mix(in srgb, var(--ink) 8%, var(--paper));

  --rule: #242424;
  --rule: color-mix(in srgb, var(--ink) 11%, var(--paper));
  --rule-strong: #3C3C3C;
  --rule-strong: color-mix(in srgb, var(--ink) 21%, var(--paper));

  --sans: 'Geist', ui-sans-serif, system-ui, -apple-system, 'Segoe UI', sans-serif;
  --mono: 'Geist Mono', ui-monospace, 'SF Mono', Menlo, Consolas, monospace;
  --ar-display: 'Thmanyah', 'Noto Kufi Arabic', 'Geist', sans-serif;
  --ar-text: 'Noto Kufi Arabic', 'Thmanyah', 'Geist', sans-serif;

  /*
    THE SCALE. Six sizes and nothing between them.

    Every one is a clamp, so the page has almost no width breakpoints: the
    display headline is 44px on a 320px phone and 106px on a desktop, and
    every value in between is arithmetic rather than a media query somebody
    has to remember to add. The only breakpoints left in this file are the
    ones where the LAYOUT changes shape, which is a different decision.
  */
  --t-caption: 0.75rem;                                     /* 12 */
  --t-small: 0.875rem;                                      /* 14 */
  --t-body: 1rem;                                           /* 16 */
  --t-lead: clamp(1.0625rem, 1rem + 0.35vw, 1.1875rem);     /* 17 → 19 */
  --t-sub: clamp(1.25rem, 1.05rem + 0.85vw, 1.4375rem);     /* 20 → 23 */
  --t-h3: clamp(1.375rem, 1.2rem + 0.75vw, 1.75rem);        /* 22 → 28 */
  --t-h2: clamp(2.0625rem, 1.45rem + 3vw, 2.75rem);         /* 33 → 44 */
  --t-display: clamp(2.75rem, 1.2rem + 7.4vw, 6.625rem);    /* 44 → 106 */

  /* SPACE IS THE SEPARATOR, not a change of background colour. A section is
     bounded by emptiness; the page keeps one surface almost throughout. */
  --gap-section: clamp(80px, 9vw, 144px);
  --gap-block: clamp(40px, 4.5vw, 72px);

  --wrap: 1200px;

  /* Rounded geometry, four values, applied by ROLE and never by taste. */
  --r-card: 16px;
  --r-button: 12px;
  --r-link: 8px;
  --r-pill: 999px;

  /*
    THE ONE ELEVATION ON THE PAGE, and it still comes from BELOW.

    A downward shadow says "this floats above the page", which on a flat sheet
    is a claim nothing here makes. Light from below lifts a card off the paper
    by a millimetre instead. It is used on exactly one element per section, and
    there is no second elevation level.

    On white it was a soft BLACK glow rising from under the card. That does not
    survive the swap: a black glow on near-black paper is not subtle, it is
    absent, and dropping it would have left two cards with nothing but a 3%
    tint to stand on. So the light source keeps its direction and its softness
    and changes what it is made of, which is the same thing every other value
    on the page just did. It lands as a hairline along the lit edge, plus a
    whisper of spill under it.

    Declared twice like the ladder, and for a sharper reason: one invalid
    colour anywhere in a box-shadow list drops the WHOLE declaration, not the
    one stop that could not be parsed.
  */
  --lift: inset 0 -1px 0 rgba(250, 250, 250, 0.1),
          0 -10px 24px -10px rgba(250, 250, 250, 0.07);
  --lift: inset 0 -1px 0 color-mix(in srgb, var(--ink) 10%, transparent),
          0 -10px 24px -10px color-mix(in srgb, var(--ink) 7%, transparent);
}

/* ------------------------------------------------------------------ reset */

*, *::before, *::after { box-sizing: border-box; }

html {
  /* Tells the browser to paint its own furniture dark too: the scrollbar, the
     <details> marker we replace, the form controls this page does not have
     yet. Without it a light scrollbar runs down the edge of a dark page. */
  color-scheme: dark;
  scroll-behavior: smooth;
  scroll-padding-top: 96px;
  -webkit-text-size-adjust: 100%;
}

body {
  margin: 0;
  background: var(--paper);
  color: var(--ink);
  font-family: var(--sans);
  font-size: var(--t-body);
  font-weight: 400;
  line-height: 1.65;
  letter-spacing: 0.01em;
  -webkit-font-smoothing: antialiased;
  /* The safety net, not the fix. Anything that overflows is a bug found at
     320px and repaired at source; this only stops one from taking the whole
     page sideways with it in the meantime. */
  overflow-x: clip;
}

h1, h2, h3, h4, p, ul, ol, figure, figcaption, blockquote { margin: 0; }
ul, ol { padding: 0; list-style: none; }
a { color: inherit; }
img, svg { display: block; max-width: 100%; }
button { font: inherit; color: inherit; }

::selection { background: var(--ink); color: var(--paper); }

:focus-visible {
  outline: 2px solid var(--ink);
  outline-offset: 3px;
  border-radius: var(--r-link);
}

@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }
}

/* ------------------------------------------------------- Arabic and RTL */

/*
  Two faces, two jobs. Arabic has no italic and no small caps, so the only
  hierarchy available to it is the same one the Latin side is using: size and
  a change of face. Line height goes UP, because Arabic ascenders and the dots
  under a ye need room the Latin leading does not leave.
*/
html[lang='ar'] body {
  font-family: var(--ar-text);
  line-height: 1.95;
  letter-spacing: 0;      /* tracking an Arabic word breaks its joins */
}
html[lang='ar'] .display,
html[lang='ar'] .h2,
html[lang='ar'] .h3 {
  font-family: var(--ar-display);
  letter-spacing: 0;
  line-height: 1.25;      /* 0.92 is a Latin value; it collides in Arabic */
}
/*
  THE MEASURE IS IN ch, AND ch IS NOT THE SAME UNIT IN BOTH SCRIPTS.

  `ch` is the advance of the digit zero in the current font, and Thmanyah's
  zero is wide next to the Arabic letters it sets. A 20ch heading that holds
  two lines of English came out as four lines of Arabic in a column half the
  width of the page. The measures are therefore widened for Arabic rather
  than shared, which is the same reason its line height is different: it is a
  different script, not the same script in another language.
*/
html[lang='ar'] .sect-head .h2 { max-width: 30ch; }
html[lang='ar'] .block-text .h2 { max-width: 24ch; }
html[lang='ar'] .lead { max-width: 58ch; }
html[lang='ar'] .limits { max-width: 58ch; }
html[lang='ar'] .closing .display { max-width: 26ch; }

/* The mono face has no Arabic. A run tagged as a figure stays in Geist Mono
   whichever way the page is reading. */
.mono {
  font-family: var(--mono);
  font-variant-numeric: tabular-nums;
}

/*
  ARROWS POINT ALONG THE READING DIRECTION.

  A glyph that means "onward" means the other way round in Arabic, so it is
  flipped rather than redrawn. Only marks tagged .arrow flip: an external-link
  glyph, a plus, a check are not directional and must not.
*/
[dir='rtl'] .arrow { transform: scaleX(-1); }

/* ------------------------------------------------------------- utilities */

.wrap {
  width: min(100% - 32px, var(--wrap));
  margin-inline: auto;
}
@media (min-width: 700px) {
  .wrap { width: min(100% - 64px, var(--wrap)); }
}

.skip-link {
  position: absolute;
  inset-block-start: -200px;
  inset-inline-start: 16px;
  z-index: 200;
  padding: 12px 18px;
  background: var(--ink);
  color: var(--paper);
  border-radius: var(--r-button);
  font-weight: 500;
  text-decoration: none;
}
.skip-link:focus { inset-block-start: 12px; }

/* A scroll target that occupies no space. It exists so a link written on
   another page keeps working without that page having to change. */
.anchor { display: block; height: 0; scroll-margin-top: 96px; }

/* The eyebrow: mono, uppercase, wide. It is the only tracked-out thing on the
   page, which is what makes it read as a label rather than as a sentence. */
.eyebrow {
  font-family: var(--mono);
  font-size: var(--t-caption);
  font-weight: 450;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--ink-3);
}
html[lang='ar'] .eyebrow { letter-spacing: 0.08em; }

.display {
  font-size: var(--t-display);
  font-weight: 400;
  line-height: 0.92;
  letter-spacing: 0.01em;
  text-wrap: balance;
}
.h2 {
  font-size: var(--t-h2);
  font-weight: 400;
  line-height: 1.0;
  letter-spacing: 0.012em;
  text-wrap: balance;
}
.h3 {
  font-size: var(--t-h3);
  font-weight: 450;
  line-height: 1.2;
  letter-spacing: 0.014em;
  text-wrap: balance;
}
/* The second half of a headline, stepped back. This is the page's only
   two-tone treatment and it replaces what a second colour would have done. */
.quiet { color: var(--ink-3); }

.lead {
  font-size: var(--t-lead);
  line-height: 1.65;
  color: var(--ink-2);
  max-width: 46ch;
  text-wrap: pretty;
}
.small {
  font-size: var(--t-small);
  line-height: 1.6;
  color: var(--ink-3);
}
.caption {
  font-family: var(--mono);
  font-size: var(--t-caption);
  letter-spacing: 0.03em;
  color: var(--ink-3);
}

.link {
  color: var(--ink);
  border-radius: var(--r-link);
  text-decoration: underline;
  text-decoration-color: var(--rule-strong);
  text-underline-offset: 3px;
  transition: text-decoration-color 0.15s ease;
}
.link:hover { text-decoration-color: var(--ink); }

/* --------------------------------------------------------------- buttons */

.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 8px;
  /* 48px, not 44: 44 is the floor a finger needs, and a floor is not a
     target. Every control on this page clears it, header included. */
  min-height: 48px;
  padding: 0 24px;
  border: 1px solid transparent;
  border-radius: var(--r-button);
  font-size: var(--t-small);
  font-weight: 500;
  letter-spacing: 0.01em;
  text-decoration: none;
  /* A two-word label is one word tall. "Start free" wrapping inside the
     header button at 320px made the bar 100px deep and the button a square. */
  white-space: nowrap;
  cursor: pointer;
  transition: background-color 0.18s ease, border-color 0.18s ease, color 0.18s ease;
}
/* THE ACCENT. Solid ink, paper label. One per section, no exceptions. */
.btn-fill {
  background: var(--ink);
  color: var(--paper);
}
.btn-fill:hover { background: var(--ink-2); }
.btn-line {
  background: transparent;
  border-color: var(--rule-strong);
  color: var(--ink);
}
.btn-line:hover { border-color: var(--ink); background: var(--tint-1); }

.btn svg { width: 15px; height: 15px; flex: none; }

/* ---------------------------------------------------------------- header */

.hdr {
  position: sticky;
  top: 0;
  z-index: 100;
  background: color-mix(in srgb, var(--paper) 88%, transparent);
  -webkit-backdrop-filter: blur(14px);
  backdrop-filter: blur(14px);
  border-bottom: 1px solid transparent;
  transition: border-color 0.25s ease;
}
@supports not (background: color-mix(in srgb, white 50%, transparent)) {
  .hdr { background: var(--paper); }
}
.hdr.scrolled { border-bottom-color: var(--rule); }

.hdr-row {
  display: flex;
  align-items: center;
  gap: 20px;
  min-height: 72px;
  padding-block: 10px;
}

/* THE LOGO LEADS. In the bar it is the largest thing in the bar; in the hero
   it is the largest thing on the screen. It is never a 20px afterthought in a
   corner. */
.logo {
  display: inline-flex;
  align-items: center;
  /* The mark is 30px and the lockup would be a 30px target. The link is given
     the full 44 so the thing a person taps to get home is the size a thumb
     is, in the header and in the footer both. */
  min-height: 44px;
  gap: 10px;
  color: var(--ink);
  text-decoration: none;
  border-radius: var(--r-link);
}
.logo-mark {
  width: 30px;
  height: auto;
  flex: none;
  /* The rim's opening at the lower left pulls the optical centre a hair above
     the bounding box centre. */
  transform: translateY(0.5px);
}
.logo-word {
  font-size: 1.375rem;
  font-weight: 500;
  letter-spacing: -0.005em;
  line-height: 1;
}

.hdr-nav {
  display: none;
  gap: 28px;
  margin-inline-start: 20px;
}
@media (min-width: 940px) { .hdr-nav { display: flex; } }
/* 44 in BOTH directions, which the height alone was not giving. "Limits" is a
   39px word and "Pricing" a 43px one, so two of the four nav targets were
   44 tall and under 44 wide, in the header and again in the footer. The word
   is centred in the box rather than padded on one side, so nothing moves but
   the hit area. */
.hdr-nav a {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: 44px;
  min-height: 44px;
  font-size: var(--t-small);
  color: var(--ink-2);
  text-decoration: none;
  border-radius: var(--r-link);
  transition: color 0.15s ease;
}
.hdr-nav a:hover { color: var(--ink); }

.hdr-acts {
  margin-inline-start: auto;
  display: flex;
  align-items: center;
  gap: 8px;
}
.hdr-plain {
  display: none;
  align-items: center;
  min-height: 44px;
  padding-inline: 10px;
  font-size: var(--t-small);
  color: var(--ink-2);
  text-decoration: none;
  border-radius: var(--r-link);
}
@media (min-width: 560px) { .hdr-plain { display: inline-flex; } }
.hdr-plain:hover { color: var(--ink); }

/* The language switch. A real button, 44px square minimum, and it says the
   language it switches TO in that language, which is the only label that
   needs no translation. */
.lang {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: 44px;
  min-height: 44px;
  padding-inline: 12px;
  border: 1px solid var(--rule);
  border-radius: var(--r-pill);
  background: transparent;
  font-size: var(--t-small);
  font-weight: 450;
  line-height: 1;
  cursor: pointer;
  transition: border-color 0.15s ease, background-color 0.15s ease;
}
.lang:hover { border-color: var(--ink); background: var(--tint-1); }
.lang[data-to='ar'] { font-family: var(--ar-text); }

.hdr .btn { min-height: 44px; padding: 0 18px; }
/* At 320px the bar carries a lockup, a language switch and a filled button on
   288px of usable width. Every one of them keeps its 44px height; only the
   horizontal padding gives. */
@media (max-width: 400px) {
  .hdr-row { gap: 10px; }
  .hdr .btn { padding: 0 13px; }
  .lang { padding-inline: 9px; }
  .logo-word { font-size: 1.25rem; }
}

/* ------------------------------------------------------------------ hero */

/*
  THE HERO IS A PHOTOGRAPH OF A RAILWAY, FULL BLEED, WITH THE HEADLINE ON IT.

  The mark is a track curving through a circle seen in perspective, and the
  product is called Railit. The railway is the name, not an ornament, so the
  first thing the page shows is a real one in black and white. A diagram would
  have been a picture OF the idea; this is the idea.

  ─────────────────────────────────────────────────────────────────────────
  HOW THE TYPE STAYS READABLE ON IT AT EVERY WIDTH

  Three rules, in this order:

  1. THE CROP MOVES, THE TYPE DOES NOT. `object-fit: cover` with an
     `object-position` that shifts by width. A 1600x1200 frame shown in a
     390x700 window is a different photograph unless somebody chooses which
     part survives, so the phone keeps the centre of the track and drops the
     sky, and the desktop keeps the whole sweep.
  2. A TONAL SCRIM, NOT A DIM. A gradient of pure black at increasing alpha
     from the bottom edge upward. It is the same monochrome the page is made
     of, so it darkens the picture rather than tinting it, and the guarantee
     it gives holds whatever the photograph turns out to contain.
  3. THE TYPE SITS AT THE BOTTOM, where the scrim is strongest, and never
     floats over the middle where the subject is.

  ─────────────────────────────────────────────────────────────────────────
  WHAT CHANGED WHEN THE PAGE BEHIND IT WENT DARK, AND WHY

  This frame is 45% near-white fog: its top half means 242 of 255, and the
  bottom half of ballast means 90. On white paper that top half was quieter
  than the page around it and needed nothing. On this paper it is brighter
  than anything else on the site, brighter than the ink the headlines are set
  in, and a full-bleed rectangle of it ending on a hard horizontal edge reads
  as a hole punched through the page rather than as a photograph on it.

  Two changes, and NEITHER of them is inverting the picture. A negative of a
  railway is a different photograph making a different claim, and the whole
  argument for this image is that it is a real one.

    THE CROP FAVOURS THE BALLAST. object-position moves down the frame on wide
    screens, so more of the dark track survives and less of the fog does. The
    train and the sky the mark stands in both stay: they are the subject and
    the quiet area, and a crop that loses either is a worse picture, not a
    darker one.

    THE PICTURE MEETS THE PAGE INSTEAD OF STOPPING AT IT. The scrim's last
    stop is --paper itself rather than 88% black, so the bottom of the frame
    IS the page colour and there is no seam to find. And its first stop no
    longer reaches transparent: on white there was a page above the top of the
    gradient for it to be transparent TO, and here there is not, so it holds a
    little over a quarter and the fog lands near 174 rather than 242. Bright,
    still the brightest thing on screen, and no longer a light leak.
*/
.hero {
  position: relative;
  isolation: isolate;
  display: flex;
  align-items: flex-end;
  /* Tall enough on a phone to be a photograph rather than a strip, short
     enough on a laptop that the first section is not below the fold. */
  min-height: min(94svh, 820px);
  padding-block: clamp(48px, 10vh, 96px);
  overflow: hidden;
  /* The page colour, not its opposite: this is what shows for the instant
     before the photograph decodes, and a white flash on a dark page is the
     one frame everybody notices. */
  background: var(--paper);
}
@media (max-width: 699px) {
  .hero { min-height: min(92svh, 760px); }
}

.hero-photo {
  position: absolute;
  inset: 0;
  z-index: -2;
  width: 100%;
  height: 100%;
  object-fit: cover;
  /*
    WHAT SURVIVES THE CROP, AT EACH SHAPE.

    A portrait phone shows the photograph's FULL HEIGHT and about a third of
    its width, so the horizontal position is the whole decision: the centre
    slice is the one holding the curve of the track and the train in the fog.
    There is no vertical choice to make there at all, which is why the phone
    value is unchanged by the page going dark.

    A landscape desktop shows the full width and crops the height, so the
    vertical position is the decision instead, and it is the one that moved:
    58% rather than 45%. The window keeps its top edge in the sky, so the
    mark still has its quiet area and the train is still in the frame, but
    roughly a seventh of the visible height changes hands from fog to ballast.
    That is as far as it can go. Past about 65% the sky above the train runs
    out and the mark has nowhere bright to stand.
  */
  object-position: 50% 50%;
}
@media (min-width: 700px) { .hero-photo { object-position: 50% 58%; } }

/*
  THE SCRIM DARKENS THE BOTTOM AND LEAVES THE SKY MOSTLY ALONE.

  The photograph is pale sky above and dark ballast below, which is the whole
  reason the hero is arranged the way it is: the headline goes where the
  picture is already dark, the mark goes where it is already light, and the
  wash only has to finish the job rather than do it.

  THE NUMBERS ARE NOT TASTE. Ink needs a 4.5:1 background for running copy and
  3:1 for the display size. The fog in the middle of this frame is near-white,
  so there is no honest way to put a caption up there. That is why the hero has
  no eyebrow: the one line of small text that wanted to sit on the bright part
  of the picture was removed instead of being dimmed until it looked
  deliberate.

  TWO STOPS MOVED WHEN THE PAGE WENT DARK, at opposite ends.

  At the BOTTOM the wash used to finish at 88% black, which on white paper was
  a photograph fading toward a colour the page did not have; the edge below it
  was the point where the picture stopped. It finishes at --paper now, so the
  last band of the frame is literally the page and the hero has no lower
  boundary to see.

  At the TOP it used to reach rgba(0,0,0,0) because there was a white page
  above it to be transparent to. There is not any more, and a gradient that
  runs to nothing over near-white fog is what makes the top of the hero read as
  a light leak. It holds at 0.26 to 0.28 instead, which takes the fog from 242
  to about 174: still the brightest thing on the page, and no longer brighter
  than the page has any use for.

  Pure black at varying alpha, never a colour: it darkens the photograph
  rather than tinting it, which is the same argument the palette makes.
*/
.hero-scrim {
  position: absolute;
  inset: 0;
  z-index: -1;
  /*
    A phone has no second column to hide the wash in, so this one carries the
    whole burden and is measured against the worst case: the display line lands
    at roughly 38 to 50 percent up, on the brightest fog the centre slice
    holds, and needs 3:1 there.
  */
  background: linear-gradient(
    to top,
    var(--paper) 0%,
    rgba(0, 0, 0, 0.9) 12%,
    rgba(0, 0, 0, 0.84) 24%,
    rgba(0, 0, 0, 0.76) 36%,
    rgba(0, 0, 0, 0.6) 50%,
    rgba(0, 0, 0, 0.42) 66%,
    rgba(0, 0, 0, 0.3) 84%,
    rgba(0, 0, 0, 0.28) 100%
  );
}
/*
  On a wide screen the text takes one column and the train has the other, so a
  second wash comes in from the reading edge. It buys the headline its contrast
  without darkening the half of the photograph that carries the subject.

  Logical direction: in Arabic it arrives from the right instead, because the
  text does. That is also what keeps the mark clear of it in both languages
  without a second rule. The mark sits at the inline END and the wash starts at
  the inline START, so whichever way the page reads, the wash has fallen to
  nothing by the time it reaches the corner the mark is in.
*/
@media (min-width: 900px) {
  .hero-scrim {
    background:
      linear-gradient(to top, var(--paper) 0%, rgba(0, 0, 0, 0.88) 12%,
        rgba(0, 0, 0, 0.76) 28%, rgba(0, 0, 0, 0.54) 46%,
        rgba(0, 0, 0, 0.34) 68%, rgba(0, 0, 0, 0.26) 100%),
      linear-gradient(to var(--scrim-to, right), rgba(0, 0, 0, 0.62) 0%,
        rgba(0, 0, 0, 0.52) 32%, rgba(0, 0, 0, 0.3) 52%, rgba(0, 0, 0, 0.1) 68%,
        rgba(0, 0, 0, 0) 82%);
  }
}
[dir='rtl'] .hero-scrim { --scrim-to: left; }

/*
  THE MARK SITS IN THE PHOTOGRAPH, AND THE PHOTOGRAPH CHOSE WHERE.

  This is the quiet area the picture actually has: the empty sky in the upper
  corner, above the fog bank and clear of the train. It is placed at the END
  of the reading direction rather than the start, for two reasons. It puts the
  mark diagonally opposite the headline, which is what holds the composition
  together; and it keeps it away from the small lockup in the sticky bar, so
  the page does not appear to show its own logo twice in the same corner.

  THE MARK IS DARK ON A PAGE WHOSE INK IS LIGHT, AND THAT IS NOT AN OVERSIGHT.

  Its colour belongs to the PHOTOGRAPH, not to the theme. The sky it stands in
  reads 238 of 255 across the whole box the mark occupies, at both the desktop
  and the phone placement, and about 174 once the wash is on it. So the legible
  value up there is the dark one, whichever token happens to be holding it: on
  the white page that was --ink, and here it is --paper. It was checked rather
  than flipped by reflex, which is exactly what flipping by reflex would have
  cost: a near-white mark on near-white fog.

  Measured on the sky as it now renders: 8.9:1. A logo is a graphic and 3:1
  would have done.

  The white halo stays, and stays white, for the reason it was drawn: insurance
  in case a crop at some width slides the train or the ballast underneath it.
  It is much weaker than it was, and that is the one place the fix for the fog
  had a side effect. At 0.75 over sky that was 242 the halo was invisible; over
  sky that is now 169 the same 0.75 painted a white disc and the mark read as a
  sticker applied to the photograph rather than as a sign standing in it. Wider
  and fainter is the same insurance without the badge.

  --mark-x / --mark-y are set on the element in the markup, because where the
  quiet area is belongs to the image and not to the stylesheet.
*/
.hero-mark {
  position: absolute;
  z-index: 1;
  inset-block-start: var(--mark-y, 9%);
  inset-inline-end: var(--mark-x, 7%);
  width: clamp(64px, 8vw, 116px);
  height: auto;
  color: var(--paper);
  filter: drop-shadow(0 0 26px rgba(255, 255, 255, 0.3));
}
@media (max-width: 699px) {
  /* A portrait crop is a different photograph. The sky is still at the top,
     so the mark stays there, tucked under the bar and inside the margin. */
  .hero-mark { inset-block-start: 22px; inset-inline-end: 18px; width: 62px; }
}

.hero-body { position: relative; z-index: 1; max-width: 40rem; }
.hero .display { max-width: 13ch; }
/* The stepped-back half of the headline. 0.84 rather than the 0.62 the page
   uses on paper: over a photograph the same step reads as a fade instead of
   as a second voice. These three stay in flat white alpha rather than being
   re-expressed over --ink, because they are values chosen against a
   PHOTOGRAPH and not against the page. Nothing in them moved when the page
   did, which is the evidence that they were about the picture all along. */
.hero .display .quiet { color: rgba(255, 255, 255, 0.84); }
.hero-lead {
  margin-top: clamp(20px, 2.6vw, 30px);
  color: rgba(255, 255, 255, 0.88);
  max-width: 38ch;
}
.hero .hero-fine { color: rgba(255, 255, 255, 0.72); }

/*
  THERE IS NO `.on-dark` ANY MORE, AND ITS ABSENCE IS THE MEASURE OF THE SWAP.

  That class held five rules whose whole job was to mirror the palette for one
  section of a page whose other surfaces were white: the filled button turned
  over, the outlined button's border turned over, the focus ring turned over.
  Every one of them now describes what the page already does, because the page
  is the dark half. Keeping them would have flipped the hero back to a black
  button on a black photograph.

  ONE rule survives, and it is not about the theme. The outlined button is the
  only control whose edge is drawn rather than filled, and --rule-strong is a
  value measured against the PAGE. Laid over a photograph containing both fog
  and ballast it disappears into one of them, so the border here is a fixed
  white alpha for the same reason the headline beside it is.
*/
.hero .btn-line { border-color: rgba(255, 255, 255, 0.42); }
.hero .btn-line:hover {
  border-color: var(--ink);
  background: rgba(255, 255, 255, 0.1);
}

.acts {
  margin-top: clamp(28px, 3.5vw, 44px);
  display: flex;
  flex-wrap: wrap;
  gap: 12px;
}

.hero-fine {
  margin-top: 20px;
  font-family: var(--mono);
  font-size: var(--t-caption);
  letter-spacing: 0.03em;
  color: var(--ink-3);
}

/* -------------------------------------------------------------- imagery */

/*
  Every image declares its intrinsic width and height in the markup, so the
  box exists before the bytes do and nothing under the reader's thumb moves
  when they land. The RATIO here overrides that box on purpose: the files are
  4:3 and a 1200px column at 4:3 is a 900px-tall wall.
*/
.figure { margin-top: clamp(40px, 5vw, 72px); }
.shot {
  width: 100%;
  height: auto;
  border-radius: var(--r-card);
  background: var(--tint-2);
  object-fit: cover;
  /*
    THE HAIRLINE IS NEW AND IT IS FOR THE DARK PAGE.

    On white every one of these images had an edge for free, because all of
    them contain something darker than the paper at their border. Here two do
    not: the photograph that ends in dark ballast, and the product capture,
    whose own chrome is near-black because the product is a dark application.
    Both simply stopped existing as objects. Drawn as a spread shadow rather
    than a border so it costs no pixel of the image and no change to the box.
  */
  box-shadow: 0 0 0 1px var(--rule);
}
.shot-hero { aspect-ratio: 4 / 3; }
@media (min-width: 700px) { .shot-hero { aspect-ratio: 16 / 9; } }
.shot-block { aspect-ratio: 4 / 3; }
.figure figcaption { margin-top: 14px; }

/* -------------------------------------------------------------- sections */

.sect { padding-block: var(--gap-section); }
/* Emptiness is the divider. A rule appears only where a list genuinely needs
   its rows separated, never to mark the end of a section. */
/*
  THE MEASURE BELONGS TO THE LINE, NOT TO THE BLOCK.

  A single max-width on the head puts the 44px heading and the 19px paragraph
  in the same column, and a column that gives the heading two lines gives the
  paragraph six. 20ch of display type and 46ch of running text are two
  different measures because they are two different sizes.
*/
.sect-head .h2 { max-width: 20ch; }
.sect-head .lead { margin-top: 24px; max-width: 46ch; }

/* ------------------------------------------------------- editorial block */

/*
  The rhythm the whole page is built on: headline, a short paragraph, evidence
  in the mono face, an image, then a great deal of nothing. Alternating the
  image side gives the eye a reason to travel down rather than scan.
*/
.block {
  display: grid;
  /*
    minmax(0, 1fr), NOT 1fr.

    A `1fr` track will not shrink below the intrinsic minimum width of what is
    in it, and one of these columns holds a ledger card whose account line is
    `white-space: nowrap`. At 320px the track grew to 323px inside a 288px
    container and took the whole page sideways with it. minmax(0, 1fr) is the
    same track with permission to be narrower than its contents, which is what
    lets the ellipsis in .erow .acct do the job it was written for.
  */
  grid-template-columns: minmax(0, 1fr);
  gap: clamp(32px, 4vw, 56px);
  padding-block: var(--gap-block);
  align-items: center;
}
@media (min-width: 900px) {
  .block { grid-template-columns: minmax(0, 5fr) minmax(0, 6fr); gap: clamp(56px, 6vw, 96px); }
  /* Logical, not left/right: in Arabic the whole alternation mirrors itself
     and the page still reads outward from the text. */
  .block-flip .block-text { order: 2; }
}
.block-num {
  display: block;
  margin-bottom: 20px;
}
.block-text .h2 { max-width: 16ch; }
.block-text .lead { margin-top: 24px; }

/* The evidence under a claim: a real question, a real document number, in the
   mono face so it reads as a machine's output rather than as more prose. */
.evidence {
  margin-top: 28px;
  border-top: 1px solid var(--rule);
}
.evidence li {
  padding: 12px 0;
  border-bottom: 1px solid var(--rule);
  font-family: var(--mono);
  font-size: var(--t-small);
  line-height: 1.6;
  color: var(--ink-3);
  /* An invoice number is one unbreakable run and a phone is 320px wide. */
  overflow-wrap: anywhere;
}
/*
  An Arabic sample line keeps its own direction inside a page reading the
  other way: the bidi run is untouched, only the block's alignment moves, so
  every row still starts where its neighbours start.

  It has to be said in PHYSICAL terms here, and this is the one place in the
  file where that is right. `text-align: start` is resolved against the
  ELEMENT's direction, and this element is dir="rtl" inside an LTR page, so
  `start` would put it against the far edge on its own while the English rows
  above it sat against the near one. The page's direction is what decides,
  and only a physical value can ask that question.
*/
.evidence li[lang='ar'] { font-family: var(--ar-text); }
[dir='ltr'] .evidence li[lang='ar'] { text-align: left; }
[dir='rtl'] .evidence li[dir='ltr'] { text-align: right; }

/* ------------------------------------------------------- the entry card */

/*
  The proposal, which is the product. It is the one place a surface is tinted
  rather than white, and the one place the lift is used in this section: the
  page's whole argument is that a change waits on a card until a person
  presses Apply, so the card is the thing that gets the millimetre.
*/
.entry {
  border-radius: var(--r-card);
  background: var(--tint-1);
  box-shadow: var(--lift);
  padding: clamp(20px, 3vw, 32px);
}
.entry-head {
  display: flex;
  flex-wrap: wrap;
  gap: 8px 16px;
  align-items: baseline;
  justify-content: space-between;
  margin-bottom: 20px;
}
.entry-rows {
  border-top: 1px solid var(--rule);
  border-bottom: 1px solid var(--rule);
  padding-block: 12px;
  font-family: var(--mono);
  font-size: var(--t-small);
  font-variant-numeric: tabular-nums;
}
.erow {
  display: flex;
  gap: 12px;
  align-items: baseline;
  padding-block: 5px;
}
.erow .side { flex: none; width: 2.2em; color: var(--ink-4); }
/* The account name gives way, never the figure: at 320px the long credit line
   ellipsizes instead of pushing the amount off the card. */
.erow .acct {
  flex: 1;
  min-width: 0;
  overflow: hidden;
  white-space: nowrap;
  text-overflow: ellipsis;
  color: var(--ink-2);
}
/* The account name is a Latin string and says so, for the same bidi reason as
   the evidence rows. Its alignment still follows the PAGE, so the column of
   accounts stays a column. */
[dir='rtl'] .erow .acct { text-align: right; }
.erow .amt { flex: none; color: var(--ink); }
.erow-cr { padding-inline-start: 2.2em; }
.entry-foot {
  margin-top: 20px;
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 12px;
}
.entry-foot .caption { flex: 1; min-width: 12ch; }

/* ------------------------------------------------------------- the limits */

/*
  A list where each row is a whole sentence at subheading size. No icons, no
  bullets: a rule between rows and nothing else, because the refusals are the
  most serious thing the page says and decoration undercuts them.
*/
.limits { margin-top: clamp(40px, 5vw, 64px); max-width: 34ch; }
@media (min-width: 900px) { .limits { max-width: 46ch; } }
.limits li {
  padding-block: clamp(20px, 2.4vw, 28px);
  border-top: 1px solid var(--rule);
  font-size: var(--t-sub);
  line-height: 1.45;
  letter-spacing: 0.012em;
  color: var(--ink);
  text-wrap: pretty;
}
.limits li:last-child { border-bottom: 1px solid var(--rule); }

/* --------------------------------------------------------------- pricing */

.plans {
  margin-top: clamp(40px, 5vw, 72px);
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  gap: 16px;
}
@media (min-width: 860px) {
  .plans { grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 20px; }
}

/*
  THREE CARDS, ONE PALETTE, AND THE MIDDLE ONE IS STILL OBVIOUS.

  The reference tells its plans apart with three pastel washes. With no hue
  available, the difference is DEPTH plus the one lift: the two outer cards sit
  on the lightest tint flat against the paper, and the recommended one sits a
  step deeper with the shadow under it. The button does the rest, since only
  one card on the row carries a filled control.
*/
.plan {
  display: flex;
  flex-direction: column;
  border-radius: var(--r-card);
  background: var(--tint-1);
  padding: clamp(24px, 3vw, 32px);
}
.plan-pick {
  background: var(--tint-3);
  box-shadow: var(--lift);
}
.plan-name {
  display: flex;
  flex-wrap: wrap;
  gap: 10px;
  align-items: center;
  margin-bottom: 24px;
}
.badge {
  padding: 4px 10px;
  border-radius: var(--r-pill);
  background: var(--ink);
  color: var(--paper);
  font-family: var(--mono);
  font-size: 0.6875rem;
  font-weight: 450;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  line-height: 1.4;
}
.price {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: 8px;
  margin-bottom: 24px;
}
.price .amount {
  font-size: clamp(2rem, 1.7rem + 1.1vw, 2.5rem);
  font-weight: 400;
  letter-spacing: 0.006em;
  line-height: 1;
  /* "USD 32" is ONE price. The currency code must never be left hanging on a
     line of its own, and site.js writes the whole string from the API so it
     cannot be held together in the markup. */
  white-space: nowrap;
  /* A monospaced space is as wide as a digit, so "USD 32" arrives looking
     like two facts. The gap is closed here rather than in the markup because
     site.js writes the whole string, currency and figure together, from the
     API. */
  word-spacing: -0.32em;
}
.plan-rows .mono { word-spacing: -0.28em; }
.plan-rows { flex: 1; margin-bottom: 28px; }
.plan-rows li {
  padding-block: 12px;
  border-top: 1px solid var(--rule);
  font-size: var(--t-small);
  line-height: 1.55;
  color: var(--ink-2);
}
.plan-rows li:first-child { border-top: 0; padding-top: 0; }
.plan .btn { width: 100%; }

.price-note { margin-top: clamp(28px, 3vw, 40px); max-width: 62ch; }

/* ------------------------------------------------------------------- faq */

/*
  <details>, so every answer is in the HTML whether or not anything runs. A
  reader with scripting off, a search engine, and a payment processor's
  reviewer all get the full text.
*/
.qa { margin-top: clamp(40px, 5vw, 64px); max-width: 68ch; }
.qa details { border-top: 1px solid var(--rule); }
.qa details:last-of-type { border-bottom: 1px solid var(--rule); }
.qa summary {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 24px;
  min-height: 44px;
  padding-block: 20px;
  font-size: var(--t-lead);
  font-weight: 450;
  line-height: 1.4;
  cursor: pointer;
  list-style: none;
}
.qa summary::-webkit-details-marker { display: none; }
/* A plus that becomes a minus, built from two rules rather than a glyph, so
   it is the same hairline weight as the borders around it at any zoom. */
.qa summary::after {
  content: '';
  flex: none;
  width: 12px;
  height: 12px;
  background:
    linear-gradient(var(--ink-3), var(--ink-3)) center / 12px 1px no-repeat,
    linear-gradient(var(--ink-3), var(--ink-3)) center / 1px 12px no-repeat;
  transition: opacity 0.2s ease;
}
.qa details[open] summary::after {
  background: linear-gradient(var(--ink), var(--ink)) center / 12px 1px no-repeat;
}
.qa .answer {
  padding-bottom: 24px;
  font-size: var(--t-small);
  line-height: 1.8;
  color: var(--ink-2);
  max-width: 62ch;
}

/* ------------------------------------------------------------- last stop */

/*
  The closing band is the only place the page changes surface, and even there
  it changes by 3%. The texture sits behind it at the file's own weight: it
  was drawn barely-there so that nothing here has to dim it.
*/
.closing {
  position: relative;
  isolation: isolate;
  overflow: hidden;
  border-radius: var(--r-card);
  background: var(--tint-1);
  padding: clamp(48px, 8vw, 120px) clamp(24px, 5vw, 80px);
  text-align: center;
}
/*
  MULTIPLY WAS REASONED FOR WHITE PAPER, AND ARITHMETIC SAYS IT DOES NOTHING
  HERE.

  The file is a near-white paper grain: 235 to 255 at its extremes, 246 to 253
  across the middle 90% of it, median 250. On white, multiply kept the tint
  underneath and let through only the part of the file darker than white, so
  the band ran 241 to 248 against a surface of 250. It darkened by 2 to 9
  steps, which is what "barely there" was supposed to mean.

  Multiply the same file over this tint and it runs 16.6 to 17.1 against a
  surface of 17.2. Half a step. That is not a subtler version of the effect, it
  is the effect being gone, and the band would have been a flat panel with a
  decorative image fetched, decoded and blended to no purpose.

  So the operation mirrors along with the palette, rather than being kept out
  of habit. Inverted, the grain is 2 to 9 where it was 246 to 253; screened
  over the tint, `screen` lets through only what is LIGHTER than black, which
  is the exact counterpart of what multiply let through on white. The band now
  runs 19.8 to 26.4 against a surface of 17.2: it lightens by 2.6 to 9.2, the
  same deviation the white page had, in the one direction a dark ground can
  show it. Measured off the rendered page afterwards: 17 to 24, median 20.

  This is a drawn grain and not a photograph. Nothing depicted is being turned
  inside out; a field of noise has no subject to misrepresent.
*/
.closing-texture {
  position: absolute;
  inset: 0;
  z-index: -1;
  width: 100%;
  height: 100%;
  object-fit: cover;
  filter: invert(1);
  mix-blend-mode: screen;
}
.closing .display { max-width: 18ch; margin-inline: auto; }
.closing .lead { margin-inline: auto; }
.closing .acts { justify-content: center; }

/* ---------------------------------------------------------------- footer */

.foot {
  margin-top: var(--gap-section);
  padding-block: clamp(48px, 6vw, 80px) 40px;
  border-top: 1px solid var(--rule);
}
.foot-grid {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  gap: 40px;
}
@media (min-width: 760px) {
  .foot-grid { grid-template-columns: minmax(0, 2fr) minmax(0, 1fr) minmax(0, 1fr); gap: 48px; }
}
.foot-tag { margin-top: 16px; max-width: 30ch; }
.foot-col { display: flex; flex-direction: column; align-items: flex-start; }
/* Same 44 in both directions as the header nav, and here the column is
   flex-start aligned so a wider box does not move the text at all. */
.foot-col a,
.foot-legal-nav a {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: 44px;
  min-height: 44px;
  font-size: var(--t-small);
  color: var(--ink-2);
  text-decoration: none;
  border-radius: var(--r-link);
  transition: color 0.15s ease;
}
.foot-col a:hover, .foot-legal-nav a:hover { color: var(--ink); }
.foot-col h2 { font-size: var(--t-caption); }

.foot-legal-nav {
  margin-top: clamp(32px, 4vw, 48px);
  padding-top: 8px;
  border-top: 1px solid var(--rule);
  display: flex;
  flex-wrap: wrap;
  gap: 0 24px;
}
.foot-legal {
  margin-top: 24px;
  font-family: var(--mono);
  font-size: 0.6875rem;
  letter-spacing: 0.03em;
  line-height: 1.9;
  color: var(--ink-3);
}
.social { display: inline-flex; align-items: center; gap: 8px; }
.social svg { width: 15px; height: 15px; flex: none; }

/* --------------------------------------------------------- scroll reveal */

/* The base state is VISIBLE. The class only removes an offset, so an observer
   that never fires costs the movement and never the content. */
.js .rise { opacity: 0; transform: translateY(16px); }
.js .rise.in {
  opacity: 1;
  transform: none;
  transition: opacity 0.6s cubic-bezier(0.22, 1, 0.36, 1),
              transform 0.6s cubic-bezier(0.22, 1, 0.36, 1);
  transition-delay: var(--d, 0s);
}
@media (prefers-reduced-motion: reduce) {
  .js .rise, .js .rise.in { opacity: 1; transform: none; transition: none; }
}
