/*
 * Cite, joincite.com
 *
 * The palette is the app's darkColors table, and specifically `surface`
 * (#141414) for the page rather than `background` (#000000): every screen in
 * the app renders on surface, so black made the site look like a different
 * product.
 */

/* Same face the app renders in (frontend/src/constants/fonts.ts, FONT_CHOICE
   'jakarta'): a tab and a home screen showing two different type feels read as
   two different products. Self-hosted from /vendor rather than Google's CDN,
   so it loads under the operator pages' `font-src 'self'` CSP with no rule to
   widen, and one variable file covers every weight this page uses (400 to
   700, plus the 650 nothing else could hit exactly) instead of four static
   ones. See web/vendor/README.md to update it. */
@font-face {
  font-family: 'Plus Jakarta Sans';
  font-style: normal;
  font-weight: 400 700;
  font-display: swap;
  src: url("/vendor/plus-jakarta-sans-variable.woff2") format('woff2');
}

:root {
  --surface: #141414;
  --raised: #1c1c1e;
  --high: #242424;
  --hairline: #232327;
  --hairline-soft: #1e1e21;
  --rule-strong: #3a3a3c;

  --text: #ffffff;
  --text-2: #aeaeb2;
  --text-3: #85858b;

  --accent: #3ddc91;
  --accent-dim: rgba(61, 220, 145, 0.12);

  --food: #ff6b35;
  --hazard: #ff9500;
  --safety: #ff3b30;
  --campus: #5856d6;
  --access: #007aff;
  --gold: #ffb800;

  --page: 1020px;
  --ease: cubic-bezier(0.23, 1, 0.32, 1);
}

* { box-sizing: border-box; }
html { scroll-behavior: smooth; -webkit-text-size-adjust: 100%; }

body {
  margin: 0;
  background: var(--surface);
  color: var(--text);
  font: 400 16px/1.55 'Plus Jakarta Sans', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif;
  -webkit-font-smoothing: antialiased;
}

a { color: inherit; text-decoration: none; }
svg { display: block; }

.wrap { width: 100%; max-width: var(--page); margin: 0 auto; padding: 0 28px; }

/* ── Type ───────────────────────────────────────────────────────────────── */

/* The app's field label: 11px, 700, wide tracking, muted. Reused here so the
   two share a vocabulary rather than merely a palette. */
.label {
  font-size: 11px;
  font-weight: 700;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--text-3);
  margin: 0;
}

h1 {
  text-wrap: balance;
  font-size: clamp(34px, 5.4vw, 54px);
  line-height: 1.06;
  letter-spacing: -0.035em;
  font-weight: 700;
  margin: 18px 0 0;
  max-width: 15ch;
}

h2 {
  text-wrap: balance;
  font-size: clamp(25px, 3.2vw, 33px);
  line-height: 1.14;
  letter-spacing: -0.028em;
  font-weight: 700;
  margin: 14px 0 0;
  max-width: 20ch;
}

p { margin: 0; }

.body {
  color: var(--text-2);
  font-size: 16px;
  line-height: 1.62;
  max-width: 52ch;
}

/* The gap after a heading and between paragraphs, owned here rather than by an
   inline margin on each block. One of them always forgets. */
h1 + .body, h2 + .body { margin-top: 18px; }
.body + .body { margin-top: 16px; }

/* Emphasis inside the copy is a lift in colour, not a slant: the page sets
   everything in one weight of one face, and an italic run reads as a quotation
   from somewhere else. */
.body em { color: var(--text); font-style: normal; font-weight: 500; }

.small { color: var(--text-3); font-size: 13.5px; line-height: 1.55; }

/* ── Nav ────────────────────────────────────────────────────────────────── */

.nav {
  position: sticky;
  top: 0;
  z-index: 40;
  background: rgba(20, 20, 20, 0.82);
  backdrop-filter: saturate(160%) blur(12px);
  -webkit-backdrop-filter: saturate(160%) blur(12px);
  border-bottom: 1px solid transparent;
  transition: border-color 220ms var(--ease), background 220ms var(--ease);
}

.nav[data-scrolled] { border-bottom-color: var(--hairline); }

.nav-in { display: flex; align-items: center; justify-content: space-between; height: 60px; }

.brand { display: flex; align-items: center; gap: 9px; font-size: 17px; font-weight: 700; letter-spacing: -0.02em; }

/* The links carry a rule that slides to whichever section you are actually in.
   One rule that travels rather than a new one lighting up, which is the gesture
   the app's own tab bar makes. `.nav-ink` is positioned by the script; without
   it the nav is a plain row of links and nothing looks broken. */
.nav-links { position: relative; display: flex; gap: 26px; font-size: 14px; color: var(--text-2); }
.nav-links a { padding: 4px 0; transition: color 150ms var(--ease); }
.nav-links a:hover, .nav-links a[data-current] { color: var(--text); }

.nav-ink {
  position: absolute;
  bottom: -2px;
  left: 0;
  height: 2px;
  width: 0;
  border-radius: 1px;
  background: var(--accent);
  opacity: 0;
  pointer-events: none;
  transition: transform 420ms var(--ease), width 420ms var(--ease), opacity 220ms var(--ease);
}

/* ── The Cite mark ──────────────────────────────────────────────────────── */

/* Same geometry as /icon-512.png, /bimi/logo.svg and the app icon. Carried as
   a mask rather than an <img> so it inherits currentColor like the <use> icons
   below, and stays sharp at any size.

   20px, where the old pin sat at 14: the ring is only 0.17 of the radius, so
   below about 20px its stroke drops under 1.5px and the ring, the disc and the
   three ticks silt up into one blob. The footer runs it at 18 against 13.5px
   type, which is the smallest that still holds. */
.mark {
  width: 20px; height: 20px; flex-shrink: 0;
  background: currentColor;
  -webkit-mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 100 100'%3E%3Cpath d='M62.69 1.64A50.00 50.00 0 1 0 62.69 98.36L60.55 90.21A41.57 41.57 0 1 1 60.55 9.79Z' fill='%23fff'/%3E%3Ccircle cx='50' cy='50' r='30.63' fill='%23fff'/%3E%3Cpolygon points='82.10,12.21 87.79,17.90 82.10,23.59 76.41,17.90' fill='%23fff'/%3E%3Cpolygon points='82.10,76.41 87.79,82.10 82.10,87.79 76.41,82.10' fill='%23fff'/%3E%3Crect x='91.08' y='45.68' width='8.64' height='8.64' fill='%23fff'/%3E%3C/svg%3E") center / contain no-repeat;
  mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 100 100'%3E%3Cpath d='M62.69 1.64A50.00 50.00 0 1 0 62.69 98.36L60.55 90.21A41.57 41.57 0 1 1 60.55 9.79Z' fill='%23fff'/%3E%3Ccircle cx='50' cy='50' r='30.63' fill='%23fff'/%3E%3Cpolygon points='82.10,12.21 87.79,17.90 82.10,23.59 76.41,17.90' fill='%23fff'/%3E%3Cpolygon points='82.10,76.41 87.79,82.10 82.10,87.79 76.41,82.10' fill='%23fff'/%3E%3Crect x='91.08' y='45.68' width='8.64' height='8.64' fill='%23fff'/%3E%3C/svg%3E") center / contain no-repeat;
}
.foot .mark { width: 18px; height: 18px; }

/* ── Buttons ────────────────────────────────────────────────────────────── */

.btn {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  height: 46px;
  padding: 0 22px;
  border-radius: 12px;
  font-size: 15px;
  font-weight: 600;
  letter-spacing: -0.01em;
  border: 1px solid transparent;
  transition: transform 140ms var(--ease), background 140ms var(--ease), border-color 140ms var(--ease);
}
.btn:active { transform: scale(0.975); }

.btn-fill { background: var(--text); color: #101010; }
.btn-fill:hover { background: #e6e6e6; }

.btn-line { background: transparent; border-color: var(--rule-strong); color: var(--text); }
.btn-line:hover { background: var(--raised); }

/* ── Layout ─────────────────────────────────────────────────────────────── */

section { padding: clamp(58px, 8.5vw, 100px) 0; }

.head { margin-bottom: 40px; }
.head .body { margin-top: 14px; }

/* Copy left, artefact right, with the artefact allowed to be the wider thing. */
.duo {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(0, 1.05fr);
  gap: clamp(36px, 6vw, 72px);
  align-items: center;
}

/* ── Hero ───────────────────────────────────────────────────────────────── */

.hero { padding-top: clamp(48px, 7vw, 84px); }
.hero .body { margin-top: 20px; }
.hero-cta { display: flex; flex-wrap: wrap; gap: 10px; margin-top: 30px; }
.hero-note { margin-top: 16px; }

/* The copy is two blocks rather than one so the phone can sit between them on a
   narrow screen. On a wide one the pair stacks back into a single column and
   the layout is the two-up it always was. */
.hero .duo {
  grid-template-areas: "lede phone" "more phone";
  row-gap: 0;
}
.hero-lede { grid-area: lede; }
.hero-more { grid-area: more; }
.hero .phone { grid-area: phone; }

/* Larger than body copy and narrower than it. This is the one sentence that has
   to land, so it is set at a size a person reads without deciding to. */
.lede {
  margin-top: 18px;
  max-width: 41ch;
  color: var(--text-2);
  font-size: clamp(17px, 1.6vw, 19.5px);
  line-height: 1.52;
  letter-spacing: -0.006em;
}

/* Free was a word inside a grey 13.5px line and readers were missing it. It is
   the strongest single fact about the product, so it gets the accent and a
   shape of its own. */
.tag {
  display: inline-block;
  margin-right: 8px;
  padding: 2.5px 8px;
  border-radius: 6px;
  background: var(--accent-dim);
  border: 1px solid rgba(61, 220, 145, 0.3);
  color: var(--accent);
  font-size: 10.5px;
  font-weight: 700;
  letter-spacing: 0.07em;
  text-transform: uppercase;
}

/* The three states carry the colour the map gives them. Painted in rather than
   arriving coloured: the headline lands white, reads as one sentence, and then
   the states light up in order, which is the only thing above the fold that
   moves. Plain inline spans, so the line still wraps wherever it needs to.

   The base colour is the final one, so killing the animation for a reader who
   asked for less motion leaves the headline correct rather than white. */
.hl { color: var(--c); }

.js .hl { animation: ink 620ms var(--ease) both; }
.js .hl:nth-of-type(1) { animation-delay: 460ms; }
.js .hl:nth-of-type(2) { animation-delay: 620ms; }
.js .hl:nth-of-type(3) { animation-delay: 780ms; }

@keyframes ink { from { color: var(--text); } }

/* ── The hero's proof strip ────────────────────────────────────────────── */

/* The byline gets its own line rather than a slot in the dot-separated list
   above it. Two people at one campus is a claim about the product, not a
   footnote to it, and buried in a run of facts it reads as the third fact. */
.credit { margin-top: 9px; }
.credit strong { color: var(--text-2); font-weight: 500; }

.proof {
  margin-top: 36px;
  padding-top: 26px;
  border-top: 1px solid var(--hairline-soft);
}

.chips { display: flex; flex-wrap: wrap; gap: 8px; margin-top: 14px; }

.chip {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  height: 31px;
  padding: 0 13px;
  border-radius: 9px;
  background: var(--raised);
  border: 1px solid var(--hairline);
  font-size: 12.5px;
  font-weight: 500;
  color: var(--text-2);
}

/* The same square swatch the lifespan table uses, so a colour means the same
   thing in both places. */
.chip::before {
  content: "";
  width: 6px; height: 6px;
  border-radius: 2px;
  background: var(--c);
  flex-shrink: 0;
}

/* ── Phone ──────────────────────────────────────────────────────────────── */

/* The frame is still drawn here rather than baked into the image: a screenshot
   with its own bezel would carry that bezel's colour and lighting into a page
   it knows nothing about, and would have to be re-exported to change. */
.phone {
  width: 292px;
  justify-self: center;
  border-radius: 40px;
  padding: 9px;
  background: #0b0b0b;
  border: 1px solid #303034;
  box-shadow: 0 30px 70px rgba(0, 0, 0, 0.6);
}

/* Sized from the screenshot's own ratio rather than a fixed height, so the
   image is never cropped and the frame follows if the width ever changes. */
.screen {
  position: relative;
  aspect-ratio: 923 / 2000;
  border-radius: 32px;
  overflow: hidden;
  background: #0f1519;
}

.screen img {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: cover;
}

/* ── Status ─────────────────────────────────────────────────────────────── */

/* The same card as the audience switch, and the same chips as the hero: these
   are the app's real words for a state, so they are set in the type the rest of
   the page already uses for the app's vocabulary. */
.states { background: var(--raised); border: 1px solid var(--hairline); border-radius: 20px; padding: 22px; }
.state-set + .state-set { margin-top: 22px; padding-top: 20px; border-top: 1px solid var(--hairline-soft); }
.states .chips { margin-top: 12px; }
.states .chip { background: var(--high); }

/* ── Campus ─────────────────────────────────────────────────────────────── */

.decoder {
  width: 100%;
  max-width: 440px;
  justify-self: center;
  background: var(--raised);
  border: 1px solid var(--hairline);
  border-radius: 20px;
  padding: 24px;
}

/* What was typed, before what it meant. Without it the card is a diagram of a
   string; with it, it is a search that answered. */
.query {
  display: flex;
  align-items: center;
  gap: 11px;
  margin-top: 14px;
  padding: 13px 15px;
  border-radius: 13px;
  background: var(--high);
  border: 1px solid var(--hairline);
  font-size: 16px;
  font-weight: 600;
  letter-spacing: 0.01em;
}
.query svg { flex-shrink: 0; color: var(--text-3); }

/* The card keeps the app's ground when the section around it goes light.
   The three segment colours are the map's own, tuned for #1c1c1e: on paper the
   green lands near 1.9 to 1 and the amber lower still, which is not a
   readability quibble, it is unreadable. The choice was to pick different
   colours here and break the rule that a colour means one thing everywhere on
   this page, or to let the card carry its own dark with it. It carries it.

   Re-declaring the tokens rather than restating every rule: everything inside
   resolves against these, so the card needs no light-mode variants of itself. */
.invert .decoder {
  --text: #ffffff;
  --text-2: #aeaeb2;
  --text-3: #85858b;
  --hairline: #232327;
  --hairline-soft: #1e1e21;
  --high: #242424;
  color: var(--text);
  box-shadow: 0 20px 46px rgba(20, 20, 20, 0.16);
}

/* The parse, shown rather than described. A code is one opaque token until
   somebody draws the line under each piece of it. */
.code { display: flex; gap: 10px; margin-top: 22px; }

.seg { flex: 1 1 0; min-width: 0; text-align: center; }

/* The rule goes under the digit, not under the whole column: it is what marks
   off the piece of the code, and the word below it is the answer. */
.seg-n {
  display: block;
  padding-bottom: 11px;
  border-bottom: 2px solid var(--c);
  font-size: clamp(28px, 4vw, 34px);
  font-weight: 700;
  letter-spacing: -0.03em;
  line-height: 1;
  color: var(--c);
}
.seg-l { display: block; margin-top: 10px; font-size: 10px; font-weight: 700; letter-spacing: 0.07em; text-transform: uppercase; color: var(--text-3); }

.decoded {
  margin-top: 22px;
  padding-top: 18px;
  border-top: 1px solid var(--hairline-soft);
  color: var(--text-2);
  font-size: 15px;
}

/* ── Inside the app ─────────────────────────────────────────────────────── */

.gallery { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); grid-template-rows: auto auto; gap: clamp(20px, 3vw, 32px); }

/* Subgrid so all three captions share one row and the phones start on the same
   line, whatever width the text happens to wrap at. Without it a caption that
   runs to three lines drops its phone below the other two. */
.shot { margin: 0; display: grid; grid-row: span 2; grid-template-rows: subgrid; row-gap: 0; }

/* The caption sits above the screen rather than under it. Three phones in a row
   are read across their top edge, and a title there lets the whole section be
   taken in one pass without stopping at any single one. */
.shot figcaption { margin-bottom: 18px; }
.shot-t { font-size: 16px; font-weight: 600; letter-spacing: -0.015em; }
.shot-d { margin-top: 5px; color: var(--text-3); font-size: 13.5px; line-height: 1.5; }

/* Same frame as the hero, lighter on its feet: one phone can carry a 70px
   shadow, three of them start to look like a shop window. */
.gallery .phone {
  width: 100%;
  max-width: 288px;
  margin: 0 auto;
  box-shadow: 0 18px 44px rgba(0, 0, 0, 0.5);
  transition: transform 340ms var(--ease), box-shadow 340ms var(--ease);
}

.gallery .phone:hover {
  transform: translateY(-5px);
  box-shadow: 0 30px 62px rgba(0, 0, 0, 0.58);
}

/* ── Numbered list ──────────────────────────────────────────────────────── */

/* Five rows of icon-then-text is the shape every feature list on the internet
   has. The number carries the type's own colour instead, which makes the five
   scannable as a set and gives the section something of its own. */
.list { border-top: 1px solid var(--hairline-soft); }

.item {
  position: relative;
  display: grid;
  grid-template-columns: 58px minmax(0, 1fr);
  gap: 20px;
  padding: 28px 0;
  border-bottom: 1px solid var(--hairline-soft);
  align-items: start;
}

/* The hairline under the row, redrawn in the row's colour, left to right. It
   only exists on hover: five permanent coloured rules would be a decoration
   the page has to carry all the way down. */
.item::after {
  content: "";
  position: absolute;
  left: 0;
  right: 0;
  bottom: -1px;
  height: 1px;
  background: var(--c);
  transform: scaleX(0);
  transform-origin: left center;
  transition: transform 480ms var(--ease);
}
.item:hover::after { transform: scaleX(1); }

.item-n {
  font-size: 25px;
  font-weight: 700;
  line-height: 1;
  letter-spacing: -0.04em;
  font-variant-numeric: tabular-nums;
  color: var(--c);
  opacity: 0.45;
  padding-top: 4px;
  transition: opacity 260ms var(--ease);
}
.item:hover .item-n { opacity: 1; }

.item-head { display: flex; align-items: center; gap: 10px; margin-bottom: 9px; }

.item-ico {
  width: 30px; height: 30px;
  border-radius: 9px;
  display: grid; place-items: center;
  flex-shrink: 0;
  color: var(--c);
  background: color-mix(in srgb, var(--c) 14%, transparent);
  transition: transform 260ms var(--ease);
}
.item:hover .item-ico { transform: scale(1.08); }

.item h3 { margin: 0; font-size: 17px; font-weight: 600; letter-spacing: -0.02em; }
.item p { color: var(--text-3); font-size: 14.5px; line-height: 1.6; max-width: 62ch; }

/* ── Who it's for ──────────────────────────────────────────────────────── */

/* A band, not a section: a little over half the vertical padding and no h2,
   because this is a way into the list below rather than a chapter of its own.
   The hairline above it is all that separates it from the hero, which is
   enough at this height and cheaper than another heading. */
.band {
  /* Asymmetric on purpose. The section below opens with its own full padding,
     so a matching bottom here stacks into a gap twice the size of any other
     on the page. */
  padding: clamp(38px, 5.2vw, 60px) 0 clamp(20px, 2.8vw, 32px);
  border-top: 1px solid var(--hairline-soft);
}

.uses {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: clamp(20px, 3vw, 34px);
  margin-top: 24px;
}

/* Same 14% tint the list rows give their icons, so a reader who has scrolled
   past this and reached Pins meets a shape they have already seen. */
.use-ico {
  width: 32px; height: 32px;
  border-radius: 10px;
  display: grid; place-items: center;
  color: var(--c);
  background: color-mix(in srgb, var(--c) 14%, transparent);
}

.use h3 { margin: 13px 0 0; font-size: 16px; font-weight: 600; letter-spacing: -0.02em; }
.use p { margin: 6px 0 0; color: var(--text-3); font-size: 14px; line-height: 1.6; }

/* ── The light chapter ─────────────────────────────────────────────────── */

/* One section leaves the dark. Everything on the page reads its colours from
   these five tokens, so inverting the section is a matter of redefining them
   rather than writing a second set of rules for every component inside it.

   Warm off-white rather than #fff: pure white against #141414 glares, and the
   app has no white surface to be faithful to anyway. */
.invert {
  --text: #101014;
  --text-2: #52525a;
  --text-3: #7a7a83;
  --hairline: #dcdbd6;
  --hairline-soft: #e4e3df;
  --high: #e6e5e0;

  background: #f4f3ef;
  color: var(--text);
  padding-top: clamp(70px, 10vw, 116px);
  padding-bottom: clamp(70px, 10vw, 116px);
}

/* ── Expiry table ───────────────────────────────────────────────────────── */

.ttl { width: 100%; border-collapse: collapse; }
.ttl caption { text-align: left; padding-bottom: 14px; }
.ttl th {
  text-align: left;
  font-size: 11px; font-weight: 700; letter-spacing: 0.08em; text-transform: uppercase;
  color: var(--text-3);
  padding: 0 0 10px;
  border-bottom: 1px solid var(--hairline-soft);
}
.ttl th:last-child, .ttl td:last-child { text-align: right; }
.ttl td { padding: 13px 0; border-bottom: 1px solid var(--hairline-soft); font-size: 15px; }
.ttl tr:last-child td { border-bottom: 0; }
.ttl .kind { display: flex; align-items: center; gap: 9px; font-weight: 500; }

/* The row carries its lifespan as a length as well as a number, and the length
   drains at a rate proportional to what it represents, so six hours visibly
   empties three times over while seven days is still going. That comparison is
   the section's whole claim and it is far easier to watch than to read. */
.ttl .life {
  display: block;
  height: 4px;
  width: var(--w, 100%);
  margin-top: 8px;
  border-radius: 2px;
  background: var(--high);
  overflow: hidden;
}
.ttl .life i {
  display: block;
  height: 100%;
  width: 100%;
  border-radius: 2px;
  background: currentColor;
  opacity: 0.8;
  transform-origin: left center;
}
.swatch { width: 7px; height: 7px; border-radius: 2px; flex-shrink: 0; }
.dur { font-variant-numeric: tabular-nums; color: var(--text-2); }

/* ── Audience switch (the real component) ───────────────────────────────── */

.aud { background: var(--raised); border: 1px solid var(--hairline); border-radius: 20px; padding: 16px; }
.aud-row {
  display: flex; align-items: center; gap: 12px;
  padding: 12px; border-radius: 14px;
  border: 1px solid transparent;
  background: var(--high);
  transition: background 320ms var(--ease), border-color 320ms var(--ease);
}
.aud-row + .aud-row { margin-top: 8px; }
.aud-row.on { background: var(--accent-dim); border-color: var(--accent); }

.aud-ico {
  width: 36px; height: 36px;
  border-radius: 11px;
  display: grid; place-items: center;
  flex-shrink: 0;
  background: var(--high);
  color: var(--text-2);
  transition: background 320ms var(--ease), color 320ms var(--ease);
}
.aud-row.on .aud-ico { background: var(--accent); color: #06180e; }

.aud-name { display: block; font-size: 14.5px; font-weight: 600; }
.aud-sub { display: block; font-size: 12px; color: var(--text-3); margin-top: 1px; }

.aud-tick {
  color: var(--accent);
  opacity: 0;
  transform: scale(0.6);
  transition: opacity 240ms var(--ease), transform 240ms var(--ease);
}
.aud-row.on .aud-tick { opacity: 1; transform: none; }

/* ── The close ─────────────────────────────────────────────────────────── */

/* Every section above this one reads left to right off a common margin. This
   one stops doing that, which is most of why it reads as an ending rather than
   a sixth thing to get through. */
#get { text-align: center; }
#get h2 { max-width: 26ch; margin-left: auto; margin-right: auto; }
#get .body { margin-left: auto; margin-right: auto; }
#get .hero-cta { justify-content: center; }

/* ── The Android half of the close ──────────────────────────────────────── */

/* iOS shipped on 2026-09-09 and Android has no date, so `#get` does two things
   now: a download for the people who can take one, and a form for the people
   who cannot. The rule above this is what keeps the pair from reading as one
   offer with a puzzling number of buttons. */
.get-alt {
  margin-top: clamp(44px, 6vw, 64px);
  padding-top: clamp(32px, 4.5vw, 44px);
  border-top: 1px solid var(--hairline);
}
.get-alt h3 { margin: 0; font-size: 19px; font-weight: 600; letter-spacing: -0.02em; }
/* `#get` centres a `.body` by margin, and `.body` caps itself at 52ch. This
   one is two lines under a short heading rather than a paragraph under a
   headline, so it wants the narrower measure to stay a block. */
.get-alt .body { margin-top: 11px; max-width: 46ch; }

/* ── The waitlist form ──────────────────────────────────────────────────── */

/* It sits centred like everything else in `#get`, and it takes its colours
   from the same six custom properties the rest of the page does, so it would
   survive being dropped into an `.invert` section without a light variant
   being written for it. */

/* Native controls, told which ground they are on. A browser paints a form
   control for a light document unless the document says otherwise, and this
   used to be load-bearing for the phone radios, which drew as white discs on a
   #141414 page without it. Those are gone, and it stays for the field: it is
   what keeps the caret, the autofill wash and Safari's own contact-fill button
   from arriving in light-mode colours. Scoped to the form rather than set on
   `:root`, because two sections of this page are deliberately light and have
   their own idea of the answer. */
.wait { margin-top: 30px; color-scheme: dark; }

/* The input and the button read as one control. `stretch` rather than a fixed
   height so the button always matches the field, including the one browser
   that adds a pixel of its own to an `<input>`. */
.wait-row {
  display: flex;
  align-items: stretch;
  justify-content: center;
  flex-wrap: wrap;
  gap: 10px;
}

.wait-email {
  flex: 0 1 320px;
  min-width: 0;
  height: 46px;
  padding: 0 15px;
  border-radius: 12px;
  border: 1px solid var(--rule-strong);
  background: var(--raised);
  color: var(--text);
  font: inherit;
  /* 16px, where the button beside it is 15px, and the one pixel is not an
     oversight. Safari zooms the whole page when it focuses an input set below
     16px, which on a phone throws the layout sideways at the exact moment
     somebody is trying to type. The alternative is `maximum-scale=1` in the
     viewport meta, which fixes it by taking pinch zoom away from everybody who
     needs it. */
  font-size: 16px;
  transition: border-color 140ms var(--ease), background 140ms var(--ease);
}
.wait-email::placeholder { color: var(--text-3); }
.wait-email:hover { border-color: var(--text-3); }
.wait-email:focus {
  outline: none;
  border-color: var(--accent);
  background: var(--high);
}
/* The browser marks a field invalid from the first character typed, which
   would paint this red while somebody is still typing the `@`. `:user-invalid`
   waits until they have left the field or tried to submit. */
.wait-email:user-invalid { border-color: var(--safety); }

.wait .btn { flex: 0 0 auto; }

/* The honeypot. Off the page rather than `display: none`: a bot worth catching
   skips a field it can see is not rendered, and this one is in the layout,
   focusable in principle, and simply nowhere a person will ever look. */
.wait-hp {
  position: absolute;
  left: -9999px;
  width: 1px;
  height: 1px;
  overflow: hidden;
}

/* The answer. `role="status"` announces it; this is the part that draws it. */
.wait-say {
  margin: 16px auto 0;
  max-width: 44ch;
  color: var(--accent);
  font-size: 14px;
  line-height: 1.55;
}
.wait-say.bad { color: var(--hazard); }

/* Once somebody is on the list the inputs go. `display: none` on the attribute
   alone would not have done it: `.wait-row` sets `display: flex`, which beats
   the browser's own `[hidden]` rule. */
.wait[data-done] .wait-row { display: none; }
.wait[data-done] .wait-say { margin-top: 0; }

/* Once the row has wrapped, a 320px field above a button sized to its own text
   reads as two unrelated things. Below this width they are one stacked control
   with one set of edges. */
@media (max-width: 520px) {
  .wait-email { flex-basis: 100%; }
  .wait .btn { width: 100%; justify-content: center; }
}

/* ── Footer ─────────────────────────────────────────────────────────────── */

footer { border-top: 1px solid var(--hairline-soft); padding: 34px 0 46px; margin-top: 20px; }
.foot { display: flex; flex-wrap: wrap; gap: 14px; align-items: center; justify-content: space-between; }
.foot a { color: var(--text-3); font-size: 13.5px; transition: color 150ms var(--ease); }
.foot a:hover { color: var(--text); }
/* Wrapping as whole links, not mid-phrase. Without the wrap the six items
   shrink instead, and "A vendor? Click here" breaks across two lines and runs
   into Contact beside it on a phone. */
.foot-links { display: flex; flex-wrap: wrap; gap: 10px 20px; }
.foot-links a { white-space: nowrap; }

/* ── Responsive ─────────────────────────────────────────────────────────── */

@media (max-width: 860px) {
  .duo { grid-template-columns: 1fr; gap: 40px; }

  /* The phone cannot lead here: at 609px tall on an 844px screen it would put
     the entire headline below the fold, and a visitor would meet a screenshot
     of an app nobody had named yet. But it cannot come last either, which is
     what it used to do, because that buried the product under every word on the
     page. It goes in the middle: headline, what it is, then the thing itself. */
  .hero .duo { grid-template-areas: "lede" "phone" "more"; row-gap: 0; }
  .hero .phone { margin-top: 34px; }

  /* An artefact that leads on a narrow screen is a widget with no caption. On
     a wide one these sit to the left of the copy that explains them; stacked,
     the copy has to come first or the reader meets an answer before they have
     been told the question. */
  #campus .decoder, #audience .aud { order: 2; }

  /* Three columns at this width is unreadable, but stacking icon over text
     three times spends three hundred pixels on three sentences. The row form
     costs a third of that and is the same shape .item takes here. */
  .uses { grid-template-columns: 1fr; gap: 22px; }
  .use {
    display: grid;
    grid-template-columns: 32px minmax(0, 1fr);
    column-gap: 14px;
    align-items: start;
  }
  .use-ico { grid-row: span 2; }
  .use h3 { margin: 0; }

  .nav-links { display: none; }
  .item { grid-template-columns: 38px minmax(0, 1fr); gap: 14px; padding: 24px 0; }
  .item-n { font-size: 20px; padding-top: 3px; }

  /* Stacked, the three screens would be nineteen hundred pixels of scrolling
     for one idea. A swipe strip is what a person expects of a set of screens
     and it needs no script: scroll snapping does the whole job. The negative
     margin lets it run to both edges of the phone, which is the difference
     between a strip that is meant to be swiped and one that looks cut off. */
  .gallery {
    grid-template-columns: none;
    grid-auto-flow: column;
    grid-auto-columns: 76%;
    overflow-x: auto;
    overscroll-behavior-x: contain;
    scroll-snap-type: x mandatory;
    scroll-padding-left: 28px;
    margin: 0 -28px;
    padding: 0 28px 26px;
    scrollbar-width: none;
    -webkit-overflow-scrolling: touch;
  }
  .gallery::-webkit-scrollbar { display: none; }
  .shot { scroll-snap-align: start; }
  .gallery .phone { max-width: none; }
}

@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }
  * { transition: none !important; }
  /* The headline's paint-in is an animation, not a transition, and the rule
     above does not reach it. Removing it leaves the final colour, which is
     what the base rule already says. */
  .hl { animation: none !important; }
}

/* ── Detail ─────────────────────────────────────────────────────────────── */

::selection { background: rgba(61, 220, 145, 0.24); color: #fff; }

/* Keyboard focus was invisible, which on a page that is mostly links matters
   more than it looks like it should. */
:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 3px;
  border-radius: 6px;
}
:focus:not(:focus-visible) { outline: none; }

/* ── Entrance ───────────────────────────────────────────────────────────── */

/* Seen once per visit, so a small amount is affordable. 10px and 420ms: enough
   to feel like the page is arriving, not enough to wait for.
   Scoped to .js, set by an inline script in <head>: unscoped, a reader with
   JavaScript off would get a blank page rather than an unanimated one. */
/* Copy rises; the artefacts come in from the side they live on, so eight
   blocks do not all perform the same gesture on the way past. */
.js [data-reveal] {
  opacity: 0;
  transform: translateY(10px);
  transition: opacity 420ms var(--ease), transform 420ms var(--ease);
}
.js [data-reveal="left"]  { transform: translateX(-16px); }
.js [data-reveal="right"] { transform: translateX(16px); }
.js [data-reveal="scale"] { transform: scale(0.97); }
.js [data-reveal].in { opacity: 1; transform: none; }

.js .proof[data-reveal] { opacity: 1; transform: none; transition: none; }
.js .proof .chip {
  opacity: 0;
  transform: translateY(8px);
  transition: opacity 400ms var(--ease), transform 400ms var(--ease);
}
.js .proof.in .chip { opacity: 1; transform: none; }
.js .proof.in .chip:nth-child(1) { transition-delay: 40ms; }
.js .proof.in .chip:nth-child(2) { transition-delay: 90ms; }
.js .proof.in .chip:nth-child(3) { transition-delay: 140ms; }
.js .proof.in .chip:nth-child(4) { transition-delay: 190ms; }
.js .proof.in .chip:nth-child(5) { transition-delay: 240ms; }
.js .proof.in .chip:nth-child(6) { transition-delay: 290ms; }
.js .proof.in .chip:nth-child(7) { transition-delay: 340ms; }

@media (prefers-reduced-motion: reduce) {
  .js [data-reveal] { opacity: 1; transform: none; transition: none; }
  .js .proof .chip { opacity: 1; transform: none; }
  /* The two loops are never started at all; the script checks the same query.
     What is left is every element sitting at the end of its animation. */
  .ttl .life i { transform: none; }
  .item::after { transform: scaleX(0); }
}

/* ── Legal pages ──────────────────────────────────────────────────────────
   Terms and Privacy are long-form reading rather than a landing page, so they
   get a narrower measure than `.wrap` and a plain vertical rhythm. Everything
   else, colours and nav and footer, is shared with the page above. */
.legal { max-width: 720px; margin: 0 auto; padding: 92px 28px 40px; }
.legal h1 { font-size: clamp(30px, 5vw, 42px); letter-spacing: -0.02em; margin-bottom: 10px; }
.legal .updated { color: var(--text-3); font-size: 13.5px; margin-bottom: 26px; }
.legal .intro {
  color: var(--text-2);
  font-size: 14.5px;
  line-height: 1.65;
  padding: 16px 18px;
  border: 1px solid var(--hairline);
  border-radius: 12px;
  margin-bottom: 40px;
}
.legal h2 {
  font-size: 19px;
  letter-spacing: -0.01em;
  margin: 38px 0 12px;
  padding-top: 22px;
  border-top: 1px solid var(--hairline-soft);
}
.legal h2:first-of-type { border-top: 0; padding-top: 0; }
.legal p { color: var(--text-2); font-size: 15px; line-height: 1.7; margin-bottom: 14px; }
.legal ul { margin: 0 0 16px; padding-left: 0; list-style: none; }
.legal li {
  position: relative;
  color: var(--text-2);
  font-size: 15px;
  line-height: 1.65;
  padding-left: 18px;
  margin-bottom: 10px;
}
.legal li::before {
  content: "";
  position: absolute;
  left: 2px;
  top: 9px;
  width: 5px;
  height: 5px;
  border-radius: 50%;
  background: var(--text-3);
}
.legal strong { color: var(--text); font-weight: 600; }
.legal a { color: var(--accent); }

/* ── Business page ────────────────────────────────────────────────────────
   /business is the one marketing page with something to sell rather than
   something to explain, so it adds exactly two components: a pair of prices,
   and the three shapes a larger organisation arrives in. Everything else on
   it — .duo, .list, .item, .states, .chip, .invert — is the home page's, on
   purpose. A shop owner who read the front page should not feel they have
   left the site. */

.pricing { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: clamp(16px, 2.4vw, 22px); }

/* The breakpoint lives with the component rather than up in the responsive
   block, which sits earlier in this file and would lose the cascade to the
   rule above it. Two prices side by side on a phone leaves neither figure room
   to be the large thing it is; stacked, they read as what they are — a plan,
   and the thing you can add to it. */
@media (max-width: 860px) {
  .pricing { grid-template-columns: 1fr; }
}

/* The card wears its colour on the top edge rather than as a fill, the way
   .item wears it on hover: two saturated panels side by side would compete
   with each other and with the states card further up the page. */
.price {
  position: relative;
  overflow: hidden;
  padding: clamp(24px, 3vw, 30px);
  background: var(--raised);
  border: 1px solid var(--hairline);
  border-radius: 20px;
}
.price::before {
  content: "";
  position: absolute;
  inset: 0 0 auto;
  height: 2px;
  background: var(--c);
}

.price-name { font-size: 13px; font-weight: 600; color: var(--text-2); }
/* Tabular figures, because the two prices sit one above the other on a phone
   and a proportional 1 makes $15 and $30 read as different type sizes. */
.price-amount {
  margin: 12px 0 0;
  font-size: clamp(32px, 4.4vw, 40px);
  font-weight: 700;
  line-height: 1;
  letter-spacing: -0.035em;
  font-variant-numeric: tabular-nums;
}
.price-per { font-size: 14.5px; font-weight: 500; letter-spacing: -0.01em; color: var(--text-3); }
.price-what { margin: 14px 0 0; color: var(--text-3); font-size: 14.5px; line-height: 1.6; }
.price-what em { color: var(--text-2); font-style: normal; font-weight: 500; }
/* The one card that is not something you buy: the fee on a ticketed night.
   Full width, so the plan and its add-on stay side by side above it. */
.price-wide { grid-column: 1 / -1; }

/* The audience switch's row, minus the state: nothing here is selected,
   because none of it is buyable yet. It lives in the light section, so the
   rows take --high for their ground — .invert redefines that one and
   deliberately leaves --raised alone. */
.org { border: 1px solid var(--hairline); border-radius: 20px; padding: 16px; }
.org-row { display: flex; align-items: center; gap: 12px; padding: 12px; border-radius: 14px; background: var(--high); }
.org-row + .org-row { margin-top: 8px; }
.org-ico {
  width: 32px; height: 32px;
  border-radius: 10px;
  display: grid; place-items: center;
  flex-shrink: 0;
  color: var(--c);
  background: color-mix(in srgb, var(--c) 16%, transparent);
}
.org-name { display: block; font-size: 14.5px; font-weight: 600; }
/* --text-2 rather than the --text-3 a subtitle would normally take. These rows
   sit on --high, which in the light section is a shade darker than the page,
   and 12.5px of --text-3 on it comes out at 3.4 to 1. */
.org-sub { display: block; margin-top: 2px; color: var(--text-2); font-size: 12.5px; line-height: 1.45; }

/* A link inside a sentence, which this site otherwise never has: everything
   else is a button or a nav item. It cannot be .legal's accent green, because
   two of the three places it appears are on the light section where green on
   #f4f3ef falls under two to one. Ink plus a rule works on either ground. */
.ext {
  color: var(--text);
  text-decoration: underline;
  text-decoration-color: var(--rule-strong);
  text-underline-offset: 3px;
}
.ext:hover { text-decoration-color: currentColor; }

/* ══ Operator tools ═════════════════════════════════════════════════════════
   Gone from here, and that is the end of a migration rather than a deletion.

   /vendor, /door, /console and /admin used to share the `op-` layer that sat
   at this point in the file. They left it one at a time - /vendor first, then
   /console and /admin - each on the argument that one shared component layer
   made four tools that answer different questions read as one screenshot with
   the label swapped. /door was the last, and took the remains with it to
   web/door.css.

   What replaced the sharing is narrower and deliberate: lib/theme.css holds
   the palette, because the theme is a per-person setting and four copies could
   not stay in step, and lib/shell.css holds the chrome, because three tools
   were then asked to look like the fourth. Neither is loaded by this file, and
   the marketing site keeps its own colours below and above.
   ═════════════════════════════════════════════════════════════════════════ */

/* ── Landing: a shared place, opened without the app (landing.js) ───── */

.landing {
  position: fixed;
  left: 50%;
  bottom: 20px;
  z-index: 60;
  width: min(420px, calc(100% - 32px));
  transform: translateX(-50%);
  padding: 20px 22px 18px;
  border: 1px solid var(--hairline);
  border-radius: 20px;
  background: var(--raised);
  box-shadow: 0 30px 60px -30px rgba(0, 0, 0, 0.9);
  color: var(--text);
}
.landing-lead { margin: 0; font-size: 13px; color: var(--text-3); }
.landing-name { margin: 2px 0 6px; font-size: 22px; font-weight: 700; letter-spacing: -0.02em; line-height: 1.2; }
.landing-hint { margin: 0 0 14px; font-size: 14px; line-height: 1.5; color: var(--text-2); }
.landing-go {
  display: block;
  padding: 12px 16px;
  border-radius: 12px;
  background: var(--accent);
  color: #05130c;
  font-weight: 600;
  text-align: center;
  text-decoration: none;
}
.landing-close {
  position: absolute;
  top: 10px;
  right: 12px;
  width: 32px;
  height: 32px;
  border: 0;
  border-radius: 50%;
  background: none;
  color: var(--text-3);
  font-size: 22px;
  line-height: 1;
  cursor: pointer;
}
