/**
 * The one page anybody reads before deciding whether to trust this.
 *
 * Three decisions carry it:
 *
 *   The frame is the first object and the widest, and it is running the
 *   shipping layout rule over real photographs. Everything else on the page
 *   is a caption to it.
 *
 *   The page has one light source, and it is that screen. The background wash
 *   is centred on it, and the sections furthest from it are set quietest —
 *   except the one that qualifies the promise, which is set in full ink,
 *   because a caveat harder to read than the pitch is a caveat in bad faith.
 *
 *   The palette, the type and the overlay geometry are app.css's and
 *   frame.css's, unchanged. Signing in should feel like walking further into
 *   the same room.
 */

:root {
  --void:   #0b0b0d;
  --lift:   #191920;
  /*
   * --edge draws hairlines and may be faint. --edge-strong draws the visible
   * boundary of a control, which WCAG wants at 3:1 against the background,
   * and both schemes are set to clear it. They are not the same job and were
   * getting the same weight.
   */
  --edge:   rgba(255, 255, 255, .12);
  --edge-strong: rgba(255, 255, 255, .34);
  --rule:   rgba(255, 255, 255, .09);

  --glow: #8ab4f8;
  --glow-hover: #a8c8ff;
  --on-glow: #10131a;

  /* 16.9:1, 7.3:1 and 4.8:1 on --void. */
  --paper: rgba(255, 255, 255, .93);
  --muted: rgba(255, 255, 255, .62);
  --faint: rgba(255, 255, 255, .48);

  --mono: ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, monospace;

  /* Five sizes. Anything that wants a sixth is trying to be a new thing. */
  --t-display: clamp(2.4rem, 5.2vw, 3.6rem);
  --t-lead:    1.12rem;
  --t-body:    .97rem;
  --t-small:   .84rem;
  --t-label:   .70rem;

  --s1: .75rem;
  --s2: 1.5rem;
  --s3: clamp(2rem, 5vh, 3rem);
  --s4: clamp(3.5rem, 10vh, 7rem);

  --measure: 74rem;
  --prose: 34rem;     /* every text block. no exceptions. */
  --spine: 13rem;
  --rill: clamp(1.5rem, 4vw, 3.5rem);
  --radius: .85rem;
}

/*
 * Daylight. Most of the traffic this page will ever get is a phone in
 * sunlight following a link, so it follows the device, with the same toggle
 * and the same stored key as the app. Tokens are app.css's verbatim.
 *
 * The frame itself stays dark glass in both schemes: a display on a white
 * wall is still a dark rectangle. In light mode it becomes the only dark
 * object on a pale page, which makes it dominate harder, not less.
 */
:root[data-theme="light"] {
  --void:   #eef0f4;
  --lift:   #fdfdfe;
  /*
   * Higher alphas than the dark scheme, not mirrored ones. Dark ink on a
   * light ground loses contrast much faster as alpha drops than white ink on
   * a dark ground does, so copying the dark values across is exactly what
   * left this scheme bleached — the ghost button measured 1.7:1 and the
   * quiet type 2.6:1, both well under the floor.
   */
  --edge:   rgba(18, 20, 26, .18);
  --edge-strong: rgba(18, 20, 26, .46);
  --rule:   rgba(18, 20, 26, .14);

  --glow: #2d64c8;
  --glow-hover: #24549e;
  --on-glow: #ffffff;

  /* 14:1, 6.1:1 and 4.6:1 on --void. */
  --paper: rgba(14, 16, 21, .92);
  --muted: rgba(14, 16, 21, .68);
  --faint: rgba(14, 16, 21, .60);
}

:root[data-theme="dark"]  { color-scheme: dark; }
:root[data-theme="light"] { color-scheme: light; }

* { margin: 0; padding: 0; box-sizing: border-box; }

html {
  background: var(--void);
  /* The glow of the screen in the hero, which is high and central. */
  background-image:
    radial-gradient(80% 48% at 50% 4%, var(--lift) 0%, transparent 64%),
    radial-gradient(120% 62% at 50% 0%, #121218 0%, var(--void) 60%);
  background-repeat: no-repeat;
  min-height: 100dvh;
}

/*
 * Flat top-down in daylight. The dark scheme's off-centre radials read as a
 * screen lighting the wall behind it; the same shape on white reads as a
 * spotlight cone with visible banding, which is a lighting effect nobody
 * asked for. Light mode gets a plain wash and lets the frame be the object.
 */
/* `html[data-theme=…]`, not `:root[data-theme=…] html` — :root *is* html, so
   the descendant form asks for an <html> inside an <html> and matches
   nothing. That typo left the dark scheme's radials painting under the light
   palette, which showed up as a spotlight cone behind the headline. */
html[data-theme="light"] {
  background-image: linear-gradient(180deg, #ffffff 0%, var(--void) 42%);
}

body {
  font-family: "Google Sans", "Product Sans", Roboto, -apple-system,
               "Segoe UI", Ubuntu, "Helvetica Neue", sans-serif;
  color: var(--paper);
  font-size: var(--t-body);
  line-height: 1.6;
}

/* Smoothing thins type, which reads as crisp on black and anaemic on white. */
:root[data-theme="dark"] body { -webkit-font-smoothing: antialiased; }

p { text-wrap: pretty; }

:where(a, button):focus-visible {
  outline: 2px solid var(--glow);
  outline-offset: 3px;
  border-radius: .3rem;
}

/* ============================================================
   The sky
   ============================================================ */

/*
 * Four fixed layers behind everything, drifting up at different rates as the
 * page scrolls. Dark scheme only: specks on white read as a dirty screen
 * rather than a night sky, which is the joke, not the effect.
 *
 * The brief was "dust particles on a screen", and that is the ceiling on all
 * of it. Nothing here is meant to be looked at; the test is that you notice
 * the page has depth without being able to say why. So the brightest star on
 * the page is 39% white on near-black, the galaxies are 5% alpha, and the
 * whole field is washed out under the screen glow at the top of the page for
 * free — the same alpha over a lighter ground is simply less visible, which
 * is what light pollution does anyway.
 *
 * Each layer is one element painting a tiled sheet of 1px radial gradients.
 * Tile sizes are coprime-ish across layers (1500, 1180, 960) so the three
 * repeats never line up into a visible grid. Positions are random, not
 * hand-placed: an even scatter is the one thing a real sky never is.
 */

.sky {
  position: fixed;
  /* Taller than the viewport by more than the largest drift, so the field
     never runs out from under itself at either edge. */
  inset: -34vh 0;
  z-index: -1;
  pointer-events: none;
  overflow: hidden;
}

/* The frame is dark glass in both schemes, but the page around it is not. */
:root[data-theme="light"] .sky { display: none; }

.sky > * { position: absolute; inset: 0; }

/*
 * Parallax without a scroll listener. The layer's own translate is the
 * keyframe; the scroll position is the clock. Where scroll timelines are
 * missing the layers simply sit still, which is a starfield, just a flatter
 * one — so this is an enhancement and never a dependency.
 */
@supports (animation-timeline: scroll()) {
  @media (prefers-reduced-motion: no-preference) {
    /* :not(.shoot) — the streaks are children of .sky too, and they have an
       animation of their own to run. */
    .sky > :not(.shoot) {
      animation: drift linear both;
      animation-timeline: scroll(root block);
      will-change: transform;
    }
  }
}

@keyframes drift { to { transform: translate3d(0, var(--drift), 0); } }

/*
 * Far-off light: three ellipses and a band across the corner. At these
 * alphas they are colour rather than shape — you cannot find their edges,
 * you only notice the black isn't quite even.
 */
.sky-deep {
  --drift: -2vh;
  background-image:
    radial-gradient(38rem 22rem at 76% 14%, rgba(126, 150, 220, .055), transparent 70%),
    radial-gradient(30rem 19rem at 14% 58%, rgba(168, 138, 205, .045), transparent 70%),
    radial-gradient(26rem 15rem at 62% 86%, rgba(118, 168, 200, .04), transparent 70%),
    linear-gradient(112deg, transparent 38%, rgba(150, 168, 215, .035) 52%, transparent 66%);
  background-repeat: no-repeat;
}

.sky-far {
  --drift: -6vh;
  background-size: 1500px 1100px;
  background-image:
    radial-gradient(1.1px 1.1px at 1339px 496px, rgba(255,255,255,0.109), rgba(255,255,255,0)),
    radial-gradient(1px 1px at 275px 991px, rgba(255,255,255,0.073), rgba(255,255,255,0)),
    radial-gradient(1.1px 1.1px at 376px 441px, rgba(255,255,255,0.061), rgba(255,255,255,0)),
    radial-gradient(0.9px 0.9px at 1479px 69px, rgba(255,255,255,0.061), rgba(255,255,255,0)),
    radial-gradient(1px 1px at 696px 534px, rgba(255,255,255,0.062), rgba(255,255,255,0)),
    radial-gradient(0.9px 0.9px at 674px 659px, rgba(255,255,255,0.101), rgba(255,255,255,0)),
    radial-gradient(0.9px 0.9px at 1442px 748px, rgba(255,255,255,0.132), rgba(255,255,255,0)),
    radial-gradient(1px 1px at 129px 201px, rgba(255,255,255,0.139), rgba(255,255,255,0)),
    radial-gradient(0.8px 0.8px at 758px 300px, rgba(255,255,255,0.15), rgba(255,255,255,0)),
    radial-gradient(0.9px 0.9px at 641px 224px, rgba(255,255,255,0.13), rgba(255,255,255,0)),
    radial-gradient(1.1px 1.1px at 1335px 1026px, rgba(255,255,255,0.145), rgba(255,255,255,0)),
    radial-gradient(0.9px 0.9px at 939px 670px, rgba(255,255,255,0.133), rgba(255,255,255,0)),
    radial-gradient(0.8px 0.8px at 1427px 916px, rgba(255,255,255,0.136), rgba(255,255,255,0)),
    radial-gradient(0.8px 0.8px at 562px 897px, rgba(255,255,255,0.154), rgba(255,255,255,0)),
    radial-gradient(1px 1px at 1333px 928px, rgba(255,255,255,0.141), rgba(255,255,255,0)),
    radial-gradient(0.8px 0.8px at 199px 746px, rgba(255,255,255,0.157), rgba(255,255,255,0)),
    radial-gradient(0.9px 0.9px at 1348px 860px, rgba(255,255,255,0.131), rgba(255,255,255,0)),
    radial-gradient(0.9px 0.9px at 998px 575px, rgba(255,255,255,0.12), rgba(255,255,255,0)),
    radial-gradient(1.2px 1.2px at 188px 799px, rgba(255,255,255,0.144), rgba(255,255,255,0)),
    radial-gradient(0.8px 0.8px at 1353px 266px, rgba(255,255,255,0.155), rgba(255,255,255,0)),
    radial-gradient(1.1px 1.1px at 1375px 432px, rgba(255,255,255,0.157), rgba(255,255,255,0)),
    radial-gradient(1px 1px at 147px 503px, rgba(255,255,255,0.082), rgba(255,255,255,0)),
    radial-gradient(0.9px 0.9px at 599px 636px, rgba(255,255,255,0.111), rgba(255,255,255,0)),
    radial-gradient(0.9px 0.9px at 473px 806px, rgba(255,255,255,0.158), rgba(255,255,255,0)),
    radial-gradient(1.1px 1.1px at 542px 723px, rgba(255,255,255,0.095), rgba(255,255,255,0)),
    radial-gradient(1px 1px at 317px 951px, rgba(255,255,255,0.062), rgba(255,255,255,0)),
    radial-gradient(1px 1px at 677px 166px, rgba(255,255,255,0.089), rgba(255,255,255,0)),
    radial-gradient(1px 1px at 220px 246px, rgba(255,255,255,0.14), rgba(255,255,255,0)),
    radial-gradient(1.1px 1.1px at 1030px 386px, rgba(255,255,255,0.15), rgba(255,255,255,0)),
    radial-gradient(1.1px 1.1px at 894px 180px, rgba(255,255,255,0.102), rgba(255,255,255,0)),
    radial-gradient(0.9px 0.9px at 416px 10px, rgba(255,255,255,0.075), rgba(255,255,255,0)),
    radial-gradient(0.9px 0.9px at 1321px 541px, rgba(255,255,255,0.075), rgba(255,255,255,0)),
    radial-gradient(1.2px 1.2px at 1138px 101px, rgba(255,255,255,0.1), rgba(255,255,255,0)),
    radial-gradient(1.1px 1.1px at 310px 5px, rgba(255,255,255,0.119), rgba(255,255,255,0)),
    radial-gradient(1.1px 1.1px at 661px 651px, rgba(255,255,255,0.069), rgba(255,255,255,0)),
    radial-gradient(1.1px 1.1px at 31px 89px, rgba(255,255,255,0.09), rgba(255,255,255,0)),
    radial-gradient(1.2px 1.2px at 249px 448px, rgba(255,255,255,0.155), rgba(255,255,255,0)),
    radial-gradient(1px 1px at 1011px 375px, rgba(255,255,255,0.067), rgba(255,255,255,0)),
    radial-gradient(1.1px 1.1px at 577px 307px, rgba(255,255,255,0.133), rgba(255,255,255,0)),
    radial-gradient(1.1px 1.1px at 606px 401px, rgba(255,255,255,0.128), rgba(255,255,255,0)),
    radial-gradient(0.9px 0.9px at 802px 874px, rgba(255,255,255,0.116), rgba(255,255,255,0)),
    radial-gradient(0.9px 0.9px at 930px 530px, rgba(255,255,255,0.089), rgba(255,255,255,0)),
    radial-gradient(1.1px 1.1px at 1117px 495px, rgba(255,255,255,0.143), rgba(255,255,255,0)),
    radial-gradient(1.2px 1.2px at 769px 669px, rgba(255,255,255,0.096), rgba(255,255,255,0)),
    radial-gradient(1.2px 1.2px at 1258px 859px, rgba(255,255,255,0.125), rgba(255,255,255,0)),
    radial-gradient(0.8px 0.8px at 1449px 177px, rgba(255,255,255,0.073), rgba(255,255,255,0)),
    radial-gradient(1px 1px at 992px 603px, rgba(255,255,255,0.148), rgba(255,255,255,0)),
    radial-gradient(1px 1px at 1182px 31px, rgba(255,255,255,0.128), rgba(255,255,255,0)),
    radial-gradient(1.2px 1.2px at 1068px 1089px, rgba(255,255,255,0.129), rgba(255,255,255,0)),
    radial-gradient(0.8px 0.8px at 374px 659px, rgba(255,255,255,0.112), rgba(255,255,255,0)),
    radial-gradient(0.9px 0.9px at 139px 155px, rgba(255,255,255,0.129), rgba(255,255,255,0)),
    radial-gradient(0.9px 0.9px at 1410px 948px, rgba(255,255,255,0.137), rgba(255,255,255,0)),
    radial-gradient(1.1px 1.1px at 1147px 223px, rgba(255,255,255,0.128), rgba(255,255,255,0)),
    radial-gradient(1.2px 1.2px at 1399px 619px, rgba(255,255,255,0.154), rgba(255,255,255,0)),
    radial-gradient(1px 1px at 188px 1087px, rgba(255,255,255,0.148), rgba(255,255,255,0)),
    radial-gradient(0.8px 0.8px at 395px 466px, rgba(255,255,255,0.074), rgba(255,255,255,0)),
    radial-gradient(1.1px 1.1px at 1336px 461px, rgba(255,255,255,0.139), rgba(255,255,255,0)),
    radial-gradient(1px 1px at 1080px 472px, rgba(255,255,255,0.064), rgba(255,255,255,0)),
    radial-gradient(0.9px 0.9px at 437px 106px, rgba(255,255,255,0.112), rgba(255,255,255,0)),
    radial-gradient(0.9px 0.9px at 1421px 186px, rgba(255,255,255,0.083), rgba(255,255,255,0)),
    radial-gradient(0.9px 0.9px at 876px 1014px, rgba(255,255,255,0.145), rgba(255,255,255,0)),
    radial-gradient(0.9px 0.9px at 1297px 273px, rgba(255,255,255,0.08), rgba(255,255,255,0)),
    radial-gradient(0.9px 0.9px at 1311px 868px, rgba(255,255,255,0.124), rgba(255,255,255,0)),
    radial-gradient(0.9px 0.9px at 585px 834px, rgba(255,255,255,0.155), rgba(255,255,255,0));
}

/* A handful carry colour. Stars are not white, and two tints at 15% alpha is
   as far as that fact is worth pushing on a page about photographs. */
.sky-mid {
  --drift: -14vh;
  background-size: 1180px 940px;
  background-image:
    radial-gradient(1.1px 1.1px at 293px 318px, rgba(255,255,255,0.258), rgba(255,255,255,0)),
    radial-gradient(1px 1px at 952px 618px, rgba(255,255,255,0.152), rgba(255,255,255,0)),
    radial-gradient(1.3px 1.3px at 481px 766px, rgba(255,255,255,0.136), rgba(255,255,255,0)),
    radial-gradient(1.4px 1.4px at 355px 483px, rgba(255,232,208,0.146), rgba(255,232,208,0)),
    radial-gradient(1px 1px at 811px 648px, rgba(200,216,255,0.131), rgba(200,216,255,0)),
    radial-gradient(1.3px 1.3px at 844px 52px, rgba(255,255,255,0.17), rgba(255,255,255,0)),
    radial-gradient(1.3px 1.3px at 640px 486px, rgba(255,255,255,0.199), rgba(255,255,255,0)),
    radial-gradient(1px 1px at 394px 350px, rgba(255,255,255,0.24), rgba(255,255,255,0)),
    radial-gradient(1.5px 1.5px at 198px 644px, rgba(255,255,255,0.199), rgba(255,255,255,0)),
    radial-gradient(1.4px 1.4px at 551px 328px, rgba(255,255,255,0.123), rgba(255,255,255,0)),
    radial-gradient(1.1px 1.1px at 1088px 106px, rgba(255,255,255,0.255), rgba(255,255,255,0)),
    radial-gradient(1.1px 1.1px at 370px 206px, rgba(255,255,255,0.186), rgba(255,255,255,0)),
    radial-gradient(1.1px 1.1px at 325px 557px, rgba(255,255,255,0.154), rgba(255,255,255,0)),
    radial-gradient(1.2px 1.2px at 1113px 807px, rgba(200,216,255,0.214), rgba(200,216,255,0)),
    radial-gradient(1.5px 1.5px at 122px 820px, rgba(255,255,255,0.236), rgba(255,255,255,0)),
    radial-gradient(1.2px 1.2px at 160px 80px, rgba(255,255,255,0.131), rgba(255,255,255,0)),
    radial-gradient(1.3px 1.3px at 569px 721px, rgba(255,255,255,0.239), rgba(255,255,255,0)),
    radial-gradient(1.4px 1.4px at 1128px 463px, rgba(200,216,255,0.15), rgba(200,216,255,0)),
    radial-gradient(1.4px 1.4px at 239px 608px, rgba(255,232,208,0.165), rgba(255,232,208,0)),
    radial-gradient(1.2px 1.2px at 606px 67px, rgba(255,255,255,0.179), rgba(255,255,255,0)),
    radial-gradient(1.4px 1.4px at 883px 127px, rgba(255,255,255,0.144), rgba(255,255,255,0)),
    radial-gradient(1.4px 1.4px at 331px 383px, rgba(255,255,255,0.229), rgba(255,255,255,0)),
    radial-gradient(1.3px 1.3px at 224px 16px, rgba(200,216,255,0.122), rgba(200,216,255,0)),
    radial-gradient(1.5px 1.5px at 1119px 745px, rgba(255,255,255,0.131), rgba(255,255,255,0)),
    radial-gradient(1.1px 1.1px at 976px 460px, rgba(255,255,255,0.13), rgba(255,255,255,0)),
    radial-gradient(1.2px 1.2px at 540px 527px, rgba(255,255,255,0.206), rgba(255,255,255,0)),
    radial-gradient(1.4px 1.4px at 909px 47px, rgba(200,216,255,0.208), rgba(200,216,255,0)),
    radial-gradient(1.5px 1.5px at 904px 539px, rgba(200,216,255,0.225), rgba(200,216,255,0)),
    radial-gradient(1.4px 1.4px at 121px 137px, rgba(255,255,255,0.14), rgba(255,255,255,0)),
    radial-gradient(1.3px 1.3px at 1118px 677px, rgba(255,255,255,0.166), rgba(255,255,255,0));
}

/* Thirteen of them in a tile, which is the whole depth cue: the layer that
   moves most is the one you can count. */
.sky-near {
  --drift: -26vh;
  background-size: 960px 820px;
  background-image:
    radial-gradient(1.7px 1.7px at 285px 374px, rgba(255,255,255,0.278), rgba(255,255,255,0)),
    radial-gradient(1.9px 1.9px at 127px 486px, rgba(255,255,255,0.392), rgba(255,255,255,0)),
    radial-gradient(2px 2px at 386px 440px, rgba(255,255,255,0.377), rgba(255,255,255,0)),
    radial-gradient(1.8px 1.8px at 707px 585px, rgba(255,255,255,0.381), rgba(255,255,255,0)),
    radial-gradient(1.6px 1.6px at 128px 208px, rgba(255,255,255,0.252), rgba(255,255,255,0)),
    radial-gradient(1.9px 1.9px at 253px 460px, rgba(255,255,255,0.31), rgba(255,255,255,0)),
    radial-gradient(1.5px 1.5px at 461px 451px, rgba(255,255,255,0.36), rgba(255,255,255,0)),
    radial-gradient(1.9px 1.9px at 413px 617px, rgba(255,255,255,0.226), rgba(255,255,255,0)),
    radial-gradient(1.9px 1.9px at 249px 513px, rgba(255,255,255,0.354), rgba(255,255,255,0)),
    radial-gradient(2px 2px at 887px 287px, rgba(255,255,255,0.346), rgba(255,255,255,0)),
    radial-gradient(1.9px 1.9px at 412px 325px, rgba(255,255,255,0.232), rgba(255,255,255,0)),
    radial-gradient(2px 2px at 823px 693px, rgba(255,255,255,0.32), rgba(255,255,255,0)),
    radial-gradient(1.7px 1.7px at 203px 366px, rgba(255,255,255,0.323), rgba(255,255,255,0));
}

/*
 * The occasional one. Visible for under a second in a minute-long cycle, at
 * two different periods so the pair never falls into step — the failure mode
 * of a decoration like this is becoming a metronome you start waiting for.
 * The travel is packed into the visible window; the rest of the cycle is the
 * streak parked offstage at zero opacity, costing nothing.
 */
/* `.sky > .shoot`, not `.shoot`: the `.sky > *` rule above carries a
   universal selector and would otherwise win the inset. */
.sky > .shoot {
  /* --top is where it belongs in the viewport; the calc pays back the
     overscan .sky is inset by, which otherwise starts every streak a third
     of a screen above the top of the page. */
  top: calc(34vh + var(--top));
  left: var(--left);
  right: auto;
  bottom: auto;
  width: 2px;
  height: 2px;
  border-radius: 50%;
  background: rgba(255, 255, 255, .9);
  box-shadow: 0 0 5px 1px rgba(196, 216, 255, .45);
  opacity: 0;
  animation: shoot var(--every) var(--delay) linear infinite;
}

/* The tail, drawn behind the head along the direction of travel. */
.shoot::before {
  content: "";
  position: absolute;
  right: 1px;
  top: 50%;
  width: 5rem;
  height: 1px;
  transform: translateY(-50%);
  background: linear-gradient(to left, rgba(255, 255, 255, .5), rgba(255, 255, 255, 0));
}

.shoot--a { --top: 14vh; --left: 68vw; --angle: 24deg;  --travel: 26rem; --every: 41s; --delay: -8s; }
.shoot--b { --top: 46vh; --left: 22vw; --angle: 15deg;  --travel: 19rem; --every: 67s; --delay: -39s; }

@keyframes shoot {
  0%, 4%   { transform: rotate(var(--angle)) translateX(0); opacity: 0; }
  4.7%     { opacity: .9; }
  6.5%     { transform: rotate(var(--angle)) translateX(var(--travel)); opacity: 0; }
  100%     { transform: rotate(var(--angle)) translateX(var(--travel)); opacity: 0; }
}

/* ============================================================
   Chrome
   ============================================================ */

.bar {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 1rem;
  width: min(100%, var(--measure));
  margin: 0 auto;
  padding: 1.1rem clamp(1.25rem, 4vw, 2rem);
}

.bar-right { display: flex; align-items: center; gap: 1rem; }

.wordmark {
  font-size: .82rem;
  font-weight: 500;
  letter-spacing: .16em;
  text-transform: uppercase;
  color: var(--paper);
}

/*
 * The way back into the app is a control, not a footnote. As a bare text
 * link it was the only actionable thing in the bar that did not look
 * actionable, sitting next to a theme button that did.
 */
.bar-link {
  display: inline-flex;
  align-items: center;
  text-decoration: none;
  white-space: nowrap;
}

/* ============================================================
   Buttons
   ============================================================ */

/*
 * Blue means light on this page: the frame's spill, the live dot, the focus
 * ring. So the hero's call to action is a ghost, and the single filled button
 * is at the very bottom, after the caveats, where it is a door rather than a
 * funnel.
 */
.cta {
  display: inline-block;
  padding: .72em 1.5em;
  font-size: var(--t-lead);
  font-weight: 500;
  border-radius: .55rem;
  border: 1px solid var(--edge-strong);
  color: var(--paper);
  text-decoration: none;
  transition: border-color 140ms ease, background 140ms ease, transform 130ms ease;
}
.cta:hover { border-color: var(--paper); }
.cta:active { transform: translateY(1px); }

.cta--solid {
  background: var(--glow);
  border-color: transparent;
  color: var(--on-glow);
}
.cta--solid:hover { background: var(--glow-hover); border-color: transparent; }

.ghost {
  font: inherit;
  font-size: var(--t-small);
  color: var(--paper);
  background: none;
  border: 1px solid var(--edge-strong);
  border-radius: .45rem;
  padding: .35em .8em;
  cursor: pointer;
  transition: color 130ms ease, border-color 130ms ease;
}
.ghost:hover { color: var(--paper); border-color: var(--paper); }

/* The icon variant is chrome rather than an action, so it keeps the quieter
   ink and no border of its own. */
.ghost--icon {
  padding: .35em;
  border-color: transparent;
  line-height: 0;
  color: var(--muted);
}
.ghost--icon svg { display: block; width: 1.1rem; height: 1.1rem; }
.ghost--icon:hover { border-color: transparent; color: var(--paper); }

/* ============================================================
   Layout
   ============================================================ */

main {
  width: min(100%, var(--measure));
  margin: 0 auto;
  padding: clamp(1rem, 3vh, 2rem) clamp(1.25rem, 4vw, 2rem) var(--s4);
}

.band {
  display: grid;
  grid-template-columns: var(--spine) minmax(0, 1fr);
  column-gap: var(--rill);
  align-items: start;
  margin-top: var(--s4);
  padding-top: var(--s3);
  border-top: 1px solid var(--rule);
}

/* The label holds the spine; everything else is content, however many pieces
   a section turns out to have. Without this a third child auto-places itself
   back under the label. */
.band > :not(.label) { grid-column: 2; }

/* A closing note after a list — the fact that did not want to be a step. */
.after {
  margin-top: var(--s2);
  color: var(--muted);
  max-width: var(--prose);
}

/*
 * Section names are labels for machinery, the way they are in the app: mono,
 * letterspaced, small, hung in the left column. They name a region; they are
 * not headings in prose.
 */
.label {
  font-family: var(--mono);
  font-size: var(--t-label);
  font-weight: 500;
  letter-spacing: .18em;
  text-transform: uppercase;
  color: var(--faint);
  padding-top: .35rem;
}

/* ============================================================
   Hero
   ============================================================ */

.hero { margin-top: clamp(1rem, 4vh, 2.5rem); }

h1 {
  font-size: var(--t-display);
  font-weight: 300;
  letter-spacing: -.028em;
  line-height: 1.04;
  text-wrap: balance;
  max-width: 20ch;
}

.stage { margin-top: var(--s3); }

.say { margin-top: var(--s3); max-width: 40rem; }

.lead {
  font-size: var(--t-lead);
  font-weight: 300;
  color: var(--muted);
}

/*
 * The three facts that decide it. They were a fineprint line at 38% opacity
 * under a button, which is where a page puts something it hopes nobody reads
 * — and these are the reasons to keep reading. Full ink, above the button.
 */
.keys {
  display: flex;
  flex-wrap: wrap;
  gap: .45em 1.5em;
  list-style: none;
  margin-top: var(--s2);
  color: var(--paper);
}
.keys li { display: flex; align-items: center; gap: .55em; }
.keys li::before {
  content: "";
  flex: none;
  width: .32rem;
  height: .32rem;
  border-radius: 50%;
  background: var(--glow);
}

.act { margin-top: var(--s2); }

.fineprint {
  margin-top: var(--s2);
  font-size: var(--t-small);
  color: var(--muted);
}

/* ============================================================
   The frame — the signature
   ============================================================ */

/*
 * A container, so everything inside can be sized in fractions of the frame
 * exactly as frame.css sizes it in fractions of the screen. The clock is
 * 9.2 of the display's height there and 9.2cqh here: the same number, so the
 * miniature is the real overlay rather than an approximation of it.
 *
 * The width cap keeps a 16:9 box from running off the bottom of a laptop
 * viewport, which is the one thing that would stop it being the whole first
 * impression.
 */
.frame {
  container-type: size;
  position: relative;
  aspect-ratio: 16 / 9;
  width: min(100%, calc((100svh - 19rem) * 16 / 9));
  margin-inline: auto;
  border-radius: clamp(.6rem, 1vw, 1.1rem);
  border: 1px solid var(--edge);
  background: #050507;
  overflow: hidden;
  /* Contained, the way a screen lights the wall just behind it. Literal blue
     in both schemes: this is screen light, not the text accent. */
  box-shadow:
    0 0 0 1px rgba(138, 180, 248, .18),
    0 1.2rem 3.5rem -1.2rem rgba(138, 180, 248, .45),
    0 2.5rem 7rem -2.5rem rgba(138, 180, 248, .30);
  animation: power-on 700ms 120ms cubic-bezier(.2, .7, .3, 1) backwards;
}

:root[data-theme="light"] .frame {
  box-shadow:
    0 0 0 1px rgba(18, 20, 26, .10),
    0 1.6rem 3.5rem -1.2rem rgba(20, 26, 40, .30),
    0 .4rem 1rem -.6rem rgba(20, 26, 40, .18);
}

@keyframes power-on { from { opacity: 0 } to { opacity: 1 } }

.frame-stage { position: absolute; inset: 0; }

.slide {
  position: absolute;
  inset: 0;
  display: flex;
  opacity: 0;
  transition: opacity 2000ms cubic-bezier(.4, 0, .2, 1);
  will-change: opacity;
}
.slide.on { opacity: 1; }

/* Two plates tile the frame with no gap and nothing behind them — which is
   what the slideshow does with "fill the screen" on, the default. */
.plate { position: relative; height: 100%; flex: 0 0 auto; overflow: hidden; }

.plate-img {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  opacity: 0;
  transition: opacity 500ms ease,
              transform var(--kb, 14000ms) linear;
  transform: translate3d(var(--x0, 0), var(--y0, 0), 0) scale(var(--s0, 1));
  will-change: transform, opacity;
  backface-visibility: hidden;
}
.plate.ready .plate-img { opacity: 1; }
.slide.go .plate-img {
  transform: translate3d(var(--x1, 0), var(--y1, 0), 0) scale(var(--s1, 1.06));
}

/*
 * What sits under a photograph until it has decoded, tinted to that photo's
 * own hue so the fade-up is a warming rather than a cut.
 *
 * It used to be four radials pretending to be a photograph, back when the
 * page had none and the frame had to imply one. That job is gone, and a
 * convincing fake under a real photo is worse than nothing: a bright blurred
 * blob crossfading over a picture is the ugliest thing this page can do. So
 * it is quiet now, and reads as a screen warming up.
 */
.plate-light {
  position: absolute;
  inset: 0;
  background: linear-gradient(165deg,
    hsl(var(--hue) 22% 16%),
    hsl(var(--hue) 26% 8%) 68%,
    hsl(var(--hue) 28% 5%));
}

/* frame.css's readability scrim, to scale. */
.frame-scrim {
  position: absolute;
  inset: 0;
  pointer-events: none;
  background:
    linear-gradient(to top, rgba(0,0,0,.62) 0%, rgba(0,0,0,.26) 14%, rgba(0,0,0,0) 34%),
    radial-gradient(120% 100% at 50% 45%, rgba(0,0,0,0) 55%, rgba(0,0,0,.34) 100%);
}

.frame-overlay {
  position: absolute;
  inset: 0;
  pointer-events: none;
  color: rgba(255, 255, 255, .96);
  text-shadow: 0 2px 22px rgba(0,0,0,.55), 0 1px 3px rgba(0,0,0,.4);
}

/*
 * The overlay's positions are the frame's exactly — 4.4 across, 5.2 up, as
 * fractions of the display. Its type is not: a television is read from four
 * metres away and this is a card on a laptop, so the sizes keep the frame's
 * proportions but sit on a floor in rem.
 */
.frame-clock { position: absolute; left: 4.4cqw; bottom: 5.2cqh; }
.frame-time {
  font-size: max(9.2cqh, 1.75rem);
  line-height: .95;
  font-weight: 200;
  letter-spacing: -.018em;
  font-variant-numeric: tabular-nums;
}
.frame-date {
  margin-top: 1.5cqh;
  font-size: max(2.35cqh, .78rem);
  font-weight: 300;
  letter-spacing: .012em;
  color: rgba(255, 255, 255, .72);
}

/* The album credit's slot, carrying the layout decision instead. */
.frame-credit { position: absolute; right: 4.4cqw; bottom: 5.8cqh; text-align: right; }
.frame-verdict {
  font-family: var(--mono);
  font-size: max(1.5cqh, .62rem);
  font-weight: 400;
  letter-spacing: .14em;
  text-transform: uppercase;
  color: rgba(255, 255, 255, .58);
}

/* One quiet line under the frame: what it is, and what it is doing. */
.show-note {
  margin-top: var(--s1);
  font-size: var(--t-small);
  color: var(--faint);
}

.live {
  display: inline-flex;
  align-items: center;
  gap: .4em;
  margin-right: .5em;
  font-family: var(--mono);
  font-size: .68rem;
  letter-spacing: .14em;
  text-transform: uppercase;
  color: var(--muted);
}
.live::before {
  content: "";
  width: .4rem;
  height: .4rem;
  border-radius: 50%;
  background: var(--glow);
  box-shadow: 0 0 .5rem rgba(138, 180, 248, .9);
  animation: pulse 2.4s ease-in-out infinite;
}
@keyframes pulse { 0%, 100% { opacity: 1 } 50% { opacity: .35 } }

/* ============================================================
   Short answers, and the same shape reused at the bottom
   ============================================================ */

/*
 * Term over answer, not beside it.
 *
 * A second column inside the content column gave the page a fourth left
 * edge: the section label at one x, most prose at another, the answers at a
 * third, and the figures at a fourth. Stacking costs a line per row and buys
 * back the single edge the layout keeps claiming to have — and it lets a
 * term be as long as it needs to be, which an 11rem column did not.
 */
.answers { display: grid; }
.answers dt {
  font-weight: 500;
  color: var(--paper);
  padding-top: 1.05rem;
  border-top: 1px solid var(--rule);
}
.answers dd {
  color: var(--muted);
  padding-bottom: 1.05rem;
  max-width: var(--prose);
}
.answers dt:first-of-type { border-top: 0; padding-top: 0; }

/*
 * Links in prose. They keep the colour of the sentence around them and are
 * marked by a rule that firms up on hover — a page with one accent, spent on
 * the frame's own light, has none left over to make a link blue.
 */
/* :not(.cta) because the closing button lives inside .plain, and a button
   that underlines its own label reads as a broken link. */
.answers a, .plain a:not(.cta), .steps a {
  color: inherit;
  text-decoration: underline;
  text-decoration-color: var(--edge-strong);
  text-underline-offset: .18em;
  transition: text-decoration-color 130ms ease;
}
.answers a:hover, .plain a:not(.cta):hover, .steps a:hover {
  text-decoration-color: currentColor;
}

/* ============================================================
   How it works
   ============================================================ */

.steps {
  list-style: none;
  counter-reset: step;
  display: grid;
  gap: var(--s2);
  max-width: var(--prose);
}
.steps li {
  counter-increment: step;
  position: relative;
  padding-left: 2.4rem;
  color: var(--muted);
}
.steps li::before {
  content: counter(step);
  position: absolute;
  left: 0;
  top: 0;
  font-family: var(--mono);
  font-size: var(--t-small);
  font-variant-numeric: tabular-nums;
  color: var(--faint);
}
.steps strong { color: var(--paper); font-weight: 500; }

/* ============================================================
   The two hard parts
   ============================================================ */

/* ============================================================
   Tall photos — the same picture, both ways
   ============================================================ */

/*
 * This section used to claim the benefit in prose, next to a figure (60%)
 * that nobody can picture. Showing the crop costs two screens' worth of
 * markup and no new assets, and it argues better than the sentence did: the
 * left frame is missing the top of the building, and you can see that it is.
 */
.compare-say { color: var(--muted); max-width: var(--prose); }

.compare {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: clamp(.9rem, 3vw, 2rem);
  max-width: 44rem;
  margin-top: var(--s2);
}

.mini {
  display: flex;
  aspect-ratio: 16 / 9;
  border-radius: .5rem;
  border: 1px solid var(--edge);
  background: #050507;
  overflow: hidden;
}
.mini img {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: cover;
}
/* Two plates tiling one screen, the way the slideshow does it. */
.mini--pair img { width: 50%; }

.compare figcaption {
  margin-top: .6rem;
  font-size: var(--t-small);
  color: var(--faint);
}

/* ============================================================
   The limit, who runs it, and the door
   ============================================================ */

/*
 * No spine here, and a column narrower than the prose measure everywhere
 * else. This is the part of the page that has to be read rather than
 * scanned, so it is set apart by geometry and full ink rather than by a
 * coloured box or a warning icon.
 */
.close {
  display: grid;
  grid-template-columns: var(--spine) minmax(0, 1fr);
  column-gap: var(--rill);
  align-items: start;
  row-gap: var(--s4);
  margin-top: var(--s4);
  padding-top: var(--s3);
  border-top: 1px solid var(--rule);
}
.close .label { grid-column: 1; }
.close .plain { grid-column: 2; }

.plain { max-width: 32rem; color: var(--paper); }
.plain p + p { margin-top: 1em; }

/* Same stacked shape as Short answers; only the ink is heavier here. */
.plain .answers dd { color: var(--paper); }

.door { margin-top: var(--s4); }
.door .act { margin-top: 0; }

/* ============================================================
   Footer
   ============================================================ */

footer {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: var(--s2);
  flex-wrap: wrap;
  margin-top: var(--s4);
  padding-top: var(--s3);
  border-top: 1px solid var(--rule);
  font-size: var(--t-small);
  color: var(--faint);
}
footer p + p { margin-top: .4em; }
footer a { color: var(--muted); }
footer a:hover { color: var(--paper); }

/*
 * The one saturated colour on the page that is not the frame's light, and it
 * is borrowed rather than chosen: Buy Me a Coffee's yellow, so the button is
 * recognised before it is read. It earns the exception by being the only
 * thing here asking for anything, and by sitting at the very bottom where
 * the page has already said everything it needs to.
 *
 * Fixed colours, not tokens. A brand mark does not change with the theme.
 */
.bmc {
  display: inline-flex;
  align-items: center;
  gap: .55em;
  flex: none;
  padding: .62em 1.05em;
  border-radius: .5rem;
  background: #ffdd00;
  color: #0d0c0b;
  font-size: var(--t-small);
  font-weight: 600;
  white-space: nowrap;
  text-decoration: none;
  transition: filter 130ms ease, transform 130ms ease;
}
footer .bmc { color: #0d0c0b; }
.bmc:hover { filter: brightness(1.05); }
.bmc:active { transform: translateY(1px); }
.bmc svg { display: block; width: 1.15em; height: 1.15em; }

/* ============================================================
   Narrow
   ============================================================ */

@media (max-width: 54rem) {
  .band { grid-template-columns: minmax(0, 1fr); row-gap: var(--s2); }
  .band > :not(.label) { grid-column: 1; }
  .label { padding-top: 0; }

  .close { grid-template-columns: minmax(0, 1fr); row-gap: var(--s3); }
  .close .label, .close .plain { grid-column: 1; }

}

@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: 1ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 1ms !important;
  }
  /* The Ken Burns drift is the exception the frame itself makes: it is the
     product, held still, and the demo already collapses to one composition. */
  .plate-img { transition: opacity 300ms ease !important; transform: none !important; }

  /* The blanket rule above would compress a shooting star into a 1ms flash
     rather than removing it, which is worse than either. The field itself
     stays: a still starfield is not motion, it is a background. */
  .shoot { display: none; }
}
