/*
 * The public pages: a spot, a challenge, a player.
 *
 * Built to read like the picture you share from the app rather than like a web page about it
 * (besluit 2026-09-18): the same ground lit from above, the same mark at the top, the same number
 * between two hairlines with what it is over it and where it was done under it. Somebody who taps
 * a link in a group chat and somebody who opens the app a minute later should not feel they have
 * been to two places.
 *
 * Hand-written and not built, because `apps/api` has no npm step and adding one for three pages
 * would cost more than it saves (see 07-deploy.md). The values under `:root` are copies of
 * `packages/shared/tokens.json`; change them there first and bring them here, the same deal the
 * admin stylesheet already lives by.
 */
:root {
  --bg: #0a0b0c;
  --bg-elevated: #111315;
  --panel: #15181b;
  --border: rgba(255, 255, 255, 0.08);
  --border-strong: rgba(255, 255, 255, 0.16);
  --text: #f4f6f7;
  --text-muted: #9aa3ab;
  --text-faint: #5f6870;
  --accent: #1ad4ff;
  --accent-dim: rgba(26, 212, 255, 0.16);
  --on-accent: #061012;
  --gold: #e0a93a;
  --gold-dim: rgba(224, 169, 58, 0.18);
  /* The hairlines the subject sits between. Cyan, faint: a frame and not a border. */
  --rule: rgba(26, 212, 255, 0.28);
}

/*
 * The brand's display face, the same one the app sets a record in. Self-hosted from /fonts, which
 * the ITF Free Font License allows in so many words. Capitals only, which is the only way it is
 * drawn anywhere.
 */
@font-face {
  font-family: 'Nippo';
  src: url('/fonts/Nippo-Bold.ttf') format('truetype');
  font-weight: 700;
  font-display: swap;
}

@font-face {
  font-family: 'Atlas fitness-gym';
  src: url('/fonts/atlas/fitness-gym.ttf') format('truetype');
  font-display: block;
}

@font-face {
  font-family: 'Atlas yoga';
  src: url('/fonts/atlas/yoga.ttf') format('truetype');
  font-display: block;
}

@font-face {
  font-family: 'Atlas basic-ui';
  src: url('/fonts/atlas/basic-ui.ttf') format('truetype');
  font-display: block;
}

@font-face {
  font-family: 'Atlas marketing';
  src: url('/fonts/atlas/marketing.ttf') format('truetype');
  font-display: block;
}

@font-face {
  font-family: 'Atlas achievement';
  src: url('/fonts/atlas/achievement.ttf') format('truetype');
  font-display: block;
}

@font-face {
  font-family: 'Atlas goals';
  src: url('/fonts/atlas/goals.ttf') format('truetype');
  font-display: block;
}

* { box-sizing: border-box; }

body {
  margin: 0;
  background: var(--bg);
  color: var(--text);
  font: 400 16px/1.55 -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif;
  -webkit-font-smoothing: antialiased;
  min-height: 100vh;
}

/*
 * Everything except the bar pinned to the bottom of the window.
 *
 * The light behind a band is wider than the page on purpose and gets cut off by its edge, and
 * without this that cut becomes a sideways scroll on a phone. It is on a layer of its own rather
 * than on the body, because clipping the body clips what is fixed to the window as well and the ask
 * at the bottom would go with it. Full width, so whatever breaks out to the window still can.
 */
.page { overflow-x: hidden; overflow-x: clip; }

/*
 * One light above the page, falling off before it reaches the middle, over a floor that fades back
 * to the background. The app icon's finish, and the share card's.
 */
body::before {
  content: '';
  position: fixed;
  inset: 0;
  pointer-events: none;
  background:
    radial-gradient(132% 52% at 50% -8%, rgba(26, 212, 255, 0.2) 0%, rgba(26, 212, 255, 0.07) 32%, rgba(26, 212, 255, 0) 62%);
}

a { color: var(--accent); }

.wrap { position: relative; max-width: 560px; margin: 0 auto; padding: 28px 20px 56px; }

/*
 * The mark, centred, and solid white at full strength.
 *
 * It used to sit at 0.82 so the page could be about the place rather than about us. That is a fine
 * idea over a photograph and a bad one over the cyan light, which shines straight through the
 * letters and puts a gradient on them. White, opaque, everywhere. The chevron keeps its own cyan:
 * that path carries its colour inside the file (besluit 2026-09-19).
 */
.brand { display: block; width: 132px; margin: 0 auto 30px; color: #ffffff; }
.brand svg { width: 100%; height: auto; display: block; }

/* Capitals, tracked, in the brand face. The one thing on a page that is set the way the app sets it. */
.display { font-family: 'Nippo', system-ui, sans-serif; font-weight: 700; text-transform: uppercase; }

/*
 * The place, across the top of the page and melting into it.
 *
 * Behind the column rather than inside it, so it is the width of the window on a phone and on a
 * desktop alike. The fade goes opaque at the bottom so there is no seam, clears through the middle
 * where the picture actually is, and comes back a little at the top so the logo survives a bright
 * sky. The same three jobs the card's own fade does.
 */
.photo { position: absolute; top: 0; left: 0; right: 0; height: 46vh; max-height: 420px; overflow: hidden; }
.photo img { width: 100%; height: 100%; object-fit: cover; display: block; }
.photo .fade {
  position: absolute;
  inset: 0;
  background: linear-gradient(
    to top,
    var(--bg) 0%,
    rgba(10, 11, 12, 0.9) 22%,
    rgba(10, 11, 12, 0.4) 52%,
    rgba(10, 11, 12, 0.15) 76%,
    rgba(10, 11, 12, 0.5) 100%
  );
}
/* The column starts under the picture, with a little of it behind the first line so the two are
   one thing rather than a photo and then a page. */
body.has-photo .wrap { padding-top: 28px; }
body.has-photo .below-photo { margin-top: calc(min(46vh, 420px) - 150px); }

/*
 * Shorter, for a page whose subject has to be on screen without scrolling. A challenge page is a
 * number: making somebody scroll past a picture of a park to reach it is the wrong way round
 * (besluit 2026-09-18). The place is still there, behind and above it.
 */
body.tight-photo .photo { height: 30vh; max-height: 250px; }
body.tight-photo .fade { background: linear-gradient(to top, var(--bg) 0%, rgba(10, 11, 12, 0.94) 34%, rgba(10, 11, 12, 0.55) 70%, rgba(10, 11, 12, 0.45) 100%); }

h1 { font-family: 'Nippo', system-ui, sans-serif; font-weight: 700; text-transform: uppercase; font-size: 30px; line-height: 1.08; margin: 0; letter-spacing: 0.02em; }
.kicker { color: var(--text-muted); font-size: 12px; letter-spacing: 0.16em; text-transform: uppercase; margin: 0 0 8px; font-weight: 600; }
.lede { color: var(--text-muted); margin: 16px 0 0; }

/*
 * The subject: what it is, how many, where. The share card's centre block, on a page.
 */
.subject { text-align: center; margin: 8px 0 0; }
.subject .rule { height: 1px; width: 56%; margin: 0 auto; background: var(--rule); }
.subject .what { color: var(--accent); font-size: 17px; letter-spacing: 0.2em; margin: 24px 0 2px; }
.subject .figure { font-family: 'Nippo', system-ui, sans-serif; font-weight: 700; font-size: 92px; line-height: 1.02; letter-spacing: 0.01em; margin: 0; }
.subject .unit { color: var(--text-muted); font-size: 12px; letter-spacing: 0.32em; margin: 2px 0 24px; }
.subject .where { color: var(--text-muted); font-size: 13px; letter-spacing: 0.12em; margin: 20px 0 0; }
.subject .unclaimed { color: var(--accent); text-transform: uppercase; letter-spacing: 0.04em; }

.rows { margin: 26px 0 0; border: 1px solid var(--border); border-radius: 16px; overflow: hidden; background: var(--panel); }
.row { display: flex; align-items: center; gap: 14px; padding: 15px 16px; border-top: 1px solid var(--border); }
.row:first-child { border-top: 0; }
.row .grow { flex: 1; min-width: 0; }
/* A row that is a link is still a row: it takes the page's ink, not the accent, and the whole
   thing is the target rather than the words in it. */
a.row { text-decoration: none; color: inherit; }
a.row:hover { background: var(--bg-elevated); }
.row .name { font-family: 'Nippo', system-ui, sans-serif; font-weight: 700; text-transform: uppercase; letter-spacing: 0.08em; font-size: 15px; }
.row .sub { color: var(--text-muted); font-size: 13px; margin-top: 2px; }

/*
 * The exercise, in the circle the app draws it in: gold when somebody holds it, grey when nobody
 * does. The icon font is the app's own, copied to /fonts/atlas for the panel and used here for the
 * same reason (besluit 2026-09-18) — a row that names an exercise in words alone is a list, and
 * this is a scoreboard.
 */
.row .mark { width: 38px; height: 38px; border-radius: 999px; border: 1px solid var(--border-strong); background: var(--bg); display: flex; align-items: center; justify-content: center; flex: none; }
.row .mark i { font-style: normal; font-size: 17px; line-height: 1; color: var(--text-muted); }
.row.held .mark { border-color: rgba(224, 169, 58, 0.45); background: var(--gold-dim); }
.row.held .mark i { color: var(--gold); }

/* The record itself, which is what somebody came to this page to read. */
.row .record { text-align: right; flex: none; }
.row .record .n { font-family: 'Nippo', system-ui, sans-serif; font-weight: 700; font-size: 28px; line-height: 1; letter-spacing: 0.01em; font-variant-numeric: tabular-nums; }
.row.held .record .n { color: var(--gold); }
.row .record .u { color: var(--text-faint); font-size: 10px; letter-spacing: 0.22em; text-transform: uppercase; margin-top: 4px; font-weight: 600; }
.row .record .u.open { color: var(--accent); }
.row .value { font-family: 'Nippo', system-ui, sans-serif; font-weight: 700; font-size: 22px; letter-spacing: 0.02em; font-variant-numeric: tabular-nums; }
.row .value.held { color: var(--gold); }
.row .value.none { color: var(--text-faint); }

/* Three numbers side by side, the way the app's profile puts them. */
.tiles { display: grid; grid-template-columns: repeat(3, 1fr); gap: 10px; margin-top: 24px; }
.tile { border: 1px solid var(--border); background: var(--panel); border-radius: 14px; padding: 14px 10px; text-align: center; }
.tile .n { font-family: 'Nippo', system-ui, sans-serif; font-weight: 700; font-size: 26px; line-height: 1.1; }
.tile .n.gold { color: var(--gold); }
.tile .l { color: var(--text-faint); font-size: 11px; letter-spacing: 0.14em; text-transform: uppercase; margin-top: 4px; font-weight: 600; }

.face { display: block; width: 36px; height: 36px; border-radius: 999px; object-fit: cover; background: var(--bg-elevated); }
.face.lg { width: 88px; height: 88px; border: 2px solid var(--accent); }

.tags { display: flex; flex-wrap: wrap; gap: 8px; margin-top: 16px; justify-content: center; }
.tag { border: 1px solid var(--border); background: var(--panel); border-radius: 999px; padding: 6px 12px; font-size: 11px; color: var(--text-muted); letter-spacing: 0.12em; text-transform: uppercase; font-weight: 600; }
.tag.gold { border-color: rgba(224, 169, 58, 0.32); background: var(--gold-dim); color: var(--gold); }

.shots { display: grid; grid-template-columns: repeat(auto-fill, minmax(150px, 1fr)); gap: 10px; margin-top: 12px; }
.shots img { width: 100%; aspect-ratio: 4 / 3; object-fit: cover; border-radius: 12px; border: 1px solid var(--border); display: block; }

/*
 * The one thing this page wants you to do, and the only place it is asked. Lit like the badge tiles
 * in the app, so the ask reads as part of the same object rather than as an advertisement bolted on.
 */
.cta { margin-top: 34px; border: 1px solid rgba(26, 212, 255, 0.18); border-radius: 18px; padding: 24px 20px; text-align: center; background: var(--panel) radial-gradient(120% 90% at 50% -20%, rgba(26, 212, 255, 0.16) 0%, rgba(26, 212, 255, 0) 70%); }
.cta p { margin: 0 0 18px; color: var(--text-muted); }

/* Apple's badge and Google's, as they supply them. Sized by height, which is how both ask for it. */
.stores { display: flex; flex-wrap: wrap; gap: 12px; justify-content: center; align-items: center; }
.stores a, .stores .soon { display: block; line-height: 0; }
.stores img { height: 48px; width: auto; display: block; }
/* Not a link until there is something to link to. Dimmed, and it says why underneath. */
.stores .soon { opacity: 0.4; filter: grayscale(1); }
.stores .soon-label { display: block; margin-top: 6px; color: var(--text-faint); font-size: 10px; letter-spacing: 0.16em; text-transform: uppercase; text-align: center; font-weight: 600; line-height: 1.4; }

footer { margin-top: 44px; color: var(--text-faint); font-size: 12px; letter-spacing: 0.16em; text-transform: uppercase; text-align: center; font-weight: 600; }

/*
 * The one form on these pages: deleting an account (besluit 2026-09-19). Every other public page
 * is read-only, so this is the only place a field exists, and it is styled to look like the panels
 * around it rather than like a browser default in the middle of them.
 */
.form { margin-top: 26px; }
.field { display: block; }
.field .l { display: block; color: var(--text-faint); font-size: 11px; letter-spacing: 0.14em; text-transform: uppercase; font-weight: 600; margin-bottom: 8px; }
.field input {
  width: 100%;
  border: 1px solid var(--border-strong);
  background: var(--panel);
  color: var(--text);
  border-radius: 12px;
  padding: 14px 16px;
  font: inherit;
  font-size: 16px; /* Anything smaller and iOS zooms the page when the field is tapped. */
}
.field input:focus { outline: none; border-color: var(--accent); }
/* The code is six characters that have to be read back one at a time. */
.field input.code { font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace; letter-spacing: 0.32em; text-transform: uppercase; }

.go {
  display: block; width: 100%; margin-top: 16px;
  border: 0; border-radius: 12px; padding: 15px 20px;
  background: var(--accent); color: var(--on-accent);
  font-family: 'Nippo', system-ui, sans-serif; font-weight: 700; text-transform: uppercase;
  letter-spacing: 0.08em; font-size: 15px; cursor: pointer;
}
/* The last button is not cyan. Cyan is the colour of everything you are invited to do here. */
.go.danger { background: transparent; border: 1px solid rgba(255, 92, 92, 0.5); color: #ff5c5c; }
.fine { color: var(--text-faint); font-size: 12px; line-height: 1.5; margin: 14px 0 0; }
.bad { color: #ff5c5c; font-size: 13px; margin: 10px 0 0; }

/*
 * The pages that are mostly words: what RYVLS is, support, privacy, terms.
 *
 * Everything above this line is a scoreboard in a 560 column, because a shared link is about one
 * place and one number. These four are read instead of scanned, so they get a wider column and a
 * left edge to run along. Nothing else changes: the same ground, the same mark, the same hairlines.
 */
body.read .wrap { max-width: 680px; }

.page-head { margin-top: 6px; }
body.read .page-head h1 { font-size: 40px; }
.page-head .stamp { color: var(--text-muted); font-size: 11px; letter-spacing: 0.22em; text-transform: uppercase; font-weight: 600; margin: 16px 0 0; }
.page-head .lede { font-size: 18px; line-height: 1.75; }

/* A short cyan line of capitals above a block, which is how a section announces itself here. */
.kick { font-family: 'Nippo', system-ui, sans-serif; font-weight: 700; color: var(--accent); font-size: 11px; letter-spacing: 0.3em; text-transform: uppercase; margin: 0 0 18px; }

/*
 * Long text. Generous leading, a heading that is plainly a heading, and links that are visible
 * without shouting: underlined in a dim rule rather than set in the accent, which stays reserved
 * for the things you are invited to do.
 */
.prose { margin-top: 10px; }
.prose h2 { font-family: 'Nippo', system-ui, sans-serif; font-weight: 700; text-transform: uppercase; font-size: 24px; line-height: 1.16; letter-spacing: 0.03em; margin: 44px 0 14px; }
.prose p { color: var(--text-muted); font-size: 17px; line-height: 1.75; margin: 0 0 14px; }
.prose p:last-child { margin-bottom: 0; }
.prose ul { margin: 0; padding-left: 20px; }
.prose li { color: var(--text-muted); font-size: 17px; line-height: 1.7; margin: 0 0 10px; }
.prose a { text-decoration: underline; text-underline-offset: 3px; text-decoration-color: rgba(26, 212, 255, 0.4); }
/* Where reviewed wording still has to go. Visible on purpose: a gap you can see is a gap that gets
   filled, and one you cannot see is a gap that ships. */
.prose .todo { color: var(--text-faint); }

/* The jump list at the top of a long page, so nobody has to scroll to find out what is in it. */
.toc { display: flex; flex-wrap: wrap; gap: 14px 18px; margin-top: 30px; border: 1px solid var(--border); background: var(--panel); border-radius: 16px; padding: 20px 22px; }
.toc a { font-family: 'Nippo', system-ui, sans-serif; font-weight: 700; font-size: 12px; letter-spacing: 0.16em; text-transform: uppercase; text-decoration: none; }

/* One paragraph lit like the ask, for the single thing on a page that is worth stopping at. */
.callout { border: 1px solid rgba(26, 212, 255, 0.22); border-radius: 16px; padding: 22px 20px; background: var(--panel) radial-gradient(130% 120% at 30% -24%, rgba(26, 212, 255, 0.18) 0%, rgba(26, 212, 255, 0) 70%); margin: 0 0 14px; }
.callout p { color: var(--text); margin: 0; }

/* What is kept and why, side by side. It stacks on a phone, where two columns of this leave no
   room for either. */
.facts { border-bottom: 1px solid var(--border); }
.facts .f { display: flex; flex-wrap: wrap; gap: 8px 24px; padding: 16px 0; border-top: 1px solid var(--border); }
.facts .f .k { flex: none; width: 190px; font-family: 'Nippo', system-ui, sans-serif; font-weight: 700; text-transform: uppercase; letter-spacing: 0.06em; font-size: 14px; }
.facts .f .v { flex: 1; min-width: 0; color: var(--text-muted); font-size: 16px; line-height: 1.65; }

/*
 * What it does, and what it does not. The second half is the useful one: a product that will not
 * say what it skips gets sorted by whoever is guessing, and is then judged on somebody else's
 * terms.
 */
.does { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 12px; margin-top: 34px; }
.does .col { border: 1px solid var(--border); border-radius: 16px; padding: 22px 20px; }
.does .col.yes { border-color: rgba(26, 212, 255, 0.22); background: var(--panel); }
.does .kick { color: var(--text-muted); }
.does .col.yes .kick { color: var(--accent); }
.does ul { margin: 0; padding: 0; list-style: none; }
.does li { display: flex; gap: 12px; color: var(--text-muted); font-size: 15px; line-height: 1.6; margin: 0 0 14px; }
.does li:last-child { margin-bottom: 0; }
.does .col.yes li { color: var(--text); }
.does li::before { content: ''; flex: none; width: 6px; height: 6px; margin-top: 8px; background: rgba(255, 255, 255, 0.28); }
.does .col.yes li::before { background: var(--accent); }

/* A question and its answer, open. No accordion: an answer that is folded away is invisible to a
   search engine and to an assistant, and these pages exist to be landed on from a search. */
.group { margin-top: 44px; }
.qa { border-top: 1px solid var(--border); padding: 20px 0; }
.qa:last-child { border-bottom: 1px solid var(--border); }
.qa h3 { font-family: 'Nippo', system-ui, sans-serif; font-weight: 700; text-transform: uppercase; letter-spacing: 0.06em; font-size: 17px; line-height: 1.2; margin: 0 0 10px; }
.qa p { color: var(--text-muted); font-size: 17px; line-height: 1.7; margin: 0; }
.qa a { text-decoration: underline; text-underline-offset: 3px; text-decoration-color: rgba(26, 212, 255, 0.4); }

/* A slot with nothing in it yet, drawn so that it reads as deliberate rather than as forgotten. */
.slot { border: 1px dashed rgba(255, 255, 255, 0.18); border-radius: 16px; padding: 22px 20px; margin-top: 40px; }
.slot p { color: var(--text-faint); font-size: 17px; line-height: 1.75; margin: 0; }

/* Where to write when the page did not answer it. An address and not a form: a form is a wall
   between somebody with a problem and somebody who can fix it. */
.reach { border: 1px solid rgba(26, 212, 255, 0.22); border-radius: 18px; padding: 26px 22px; margin-top: 52px; background: var(--panel) radial-gradient(130% 110% at 20% -20%, rgba(26, 212, 255, 0.16) 0%, rgba(26, 212, 255, 0) 70%); }
.reach h2 { font-family: 'Nippo', system-ui, sans-serif; font-weight: 700; text-transform: uppercase; font-size: 24px; letter-spacing: 0.03em; margin: 0 0 12px; }
.reach p { color: var(--text-muted); font-size: 17px; line-height: 1.7; margin: 0 0 16px; }
.reach a { font-family: 'Nippo', system-ui, sans-serif; font-weight: 700; font-size: 15px; letter-spacing: 0.1em; text-decoration: none; }

/* The footer carries the four pages a store listing has to be able to reach, on every page. */
footer .links { display: flex; flex-wrap: wrap; gap: 10px 20px; justify-content: center; margin-top: 18px; }
footer .links a { color: var(--text-muted); text-decoration: underline; text-underline-offset: 4px; text-decoration-color: rgba(255, 255, 255, 0.2); }

@media (max-width: 620px) {
  body.read .page-head h1 { font-size: 32px; }
  .does { grid-template-columns: minmax(0, 1fr); }
  .facts .f .k { width: 100%; }
}


/*
 * The homepage.
 *
 * The only page here that is a pitch rather than a record, so it gets the wide column and a rhythm
 * of its own. Everything it is built from is already above: the same tokens, the same scoreboard
 * rows, the same ask at the bottom. What is new is the light, which walks down the page instead of
 * sitting still at the top, and the screens it walks past.
 */
body.home .wrap { max-width: 1040px; padding: 40px 20px 110px; }

/* The page light is fixed everywhere else, which is right for a page you barely scroll. Here it
   would follow you down five screens, so it is pinned to the top of the document and made big
   enough to sit behind the mark rather than behind the middle of the block. */
body.home::before {
  position: absolute;
  height: 1560px;
  background: radial-gradient(1100px 700px at 50% 90px, rgba(26, 212, 255, 0.34) 0%, rgba(26, 212, 255, 0.12) 38%, rgba(26, 212, 255, 0) 72%);
}

/* Concrete over that light, so the glow has a surface instead of being a wash. Value noise on a
   wrapping grid, generated rather than photographed: it tiles with no seam and carries no licence.
   Masked away downwards so it never reaches the bands. */
body.home::after {
  content: '';
  position: absolute;
  left: 0;
  top: 0;
  right: 0;
  height: 1560px;
  pointer-events: none;
  opacity: 0.11;
  background-image: url('/images/concrete.webp');
  background-size: 384px 384px;
  -webkit-mask-image: linear-gradient(to bottom, #000 0%, rgba(0, 0, 0, 0.85) 46%, transparent 100%);
  mask-image: linear-gradient(to bottom, #000 0%, rgba(0, 0, 0, 0.85) 46%, transparent 100%);
}

body.home .brand { width: 180px; margin-bottom: 0; }

.hero { position: relative; text-align: center; margin-top: 54px; }
.hero h1 { font-size: 82px; line-height: 1.04; }
.hero .lede { max-width: 640px; margin: 26px auto 0; font-size: 19px; line-height: 1.6; }
.hero .stores { margin-top: 50px; }
.hero .micro { font-family: 'Nippo', system-ui, sans-serif; font-weight: 700; color: var(--text-muted); font-size: 11px; letter-spacing: 0.24em; text-transform: uppercase; margin: 28px 0 0; }

/*
 * Three screens as one object. The counting shot in front because it is the picture that explains
 * the app, the map and the record behind it. The big one overlaps the inner edge of both, and the
 * bottoms nearly line up: exactly level is stiff, far apart is a hand of cards.
 */
.trio { position: relative; height: 685px; margin: 50px auto 0; max-width: 1040px; }
.trio.live { cursor: grab; touch-action: pan-y; }
.trio.live:active { cursor: grabbing; }
.trio.live:focus-visible { outline: 2px solid var(--accent); outline-offset: 20px; border-radius: 24px; }
/*
 * All three are the same object in the same place, and the slot decides where it stands. Scaled
 * from the middle, so the two behind share a centre line with the one in front rather than a floor,
 * and so the whole thing moves with one transition on one property.
 */
.trio img {
  position: absolute;
  display: block;
  height: auto;
  width: 340px;
  left: 50%;
  top: 0;
  margin-left: -170px;
  transform-origin: center center;
  transition: transform 0.45s cubic-bezier(0.22, 0.61, 0.36, 1), opacity 0.45s ease;
}
.trio img[data-slot='c'] { transform: translateX(0) scale(1); z-index: 3; }
.trio img[data-slot='l'] { transform: translateX(-215px) scale(0.77); z-index: 1; opacity: 0.9; }
.trio img[data-slot='r'] { transform: translateX(215px) scale(0.77); z-index: 1; opacity: 0.9; }
@media (prefers-reduced-motion: reduce) {
  .trio img { transition: none; }
}

/*
 * Real takeovers, edge to edge under the hero, faded off at both ends. It is the one thing on the
 * page that proves the city is alive before anybody has scrolled. It does not move: a strip that
 * scrolls by itself is a thing to wait for rather than to read, and the names on it are the point.
 */
.ticker { position: relative; margin: 96px calc(50% - 50vw) 0; height: 68px; overflow: hidden; border-top: 1px solid var(--border); border-bottom: 1px solid var(--border); }
/*
 * It runs, forever, one way.
 *
 * The list is written into the page twice and the track slides exactly half its own width, so the
 * moment it starts over the second copy is standing where the first one was and nothing jumps. Slow
 * enough to read a name as it goes: a strip you cannot read is a decoration, and this one is the
 * only proof on the page that anybody is playing.
 */
.ticker .run { position: absolute; left: 0; top: 0; bottom: 0; display: flex; align-items: center; gap: 18px; width: max-content; animation: ticker 60s linear infinite; }
.ticker:hover .run { animation-play-state: paused; }
@keyframes ticker { from { transform: translateX(0); } to { transform: translateX(-50%); } }
/* Somebody who asked their machine to stop moving things meant this too. */
@media (prefers-reduced-motion: reduce) {
  .ticker .run { animation: none; }
}
.ticker .who { font-family: 'Nippo', system-ui, sans-serif; font-weight: 700; font-size: 14px; letter-spacing: 0.18em; text-transform: uppercase; white-space: nowrap; }
.ticker .who em { font-style: normal; font-weight: 500; color: var(--text-muted); }
.ticker .when { color: var(--text-muted); font-size: 11px; letter-spacing: 0.18em; font-weight: 600; white-space: nowrap; }
.ticker .dot { color: var(--accent); font-size: 9px; line-height: 1; }
.ticker .edge { position: absolute; top: 0; bottom: 0; width: 170px; pointer-events: none; }
.ticker .edge.l { left: 0; background: linear-gradient(to right, var(--bg) 0%, rgba(10, 11, 12, 0) 100%); }
.ticker .edge.r { right: 0; background: linear-gradient(to left, var(--bg) 0%, rgba(10, 11, 12, 0) 100%); }
.ticker .glow { position: absolute; left: 50%; top: 50%; width: 1400px; height: 300px; margin: -150px 0 0 -700px; pointer-events: none; background: radial-gradient(closest-side, rgba(26, 212, 255, 0.26) 0%, rgba(26, 212, 255, 0.08) 42%, rgba(26, 212, 255, 0) 76%); }

/*
 * A band: words on one side, a screen on the other, and its own light on the side the screen is
 * on. The light is wider than the column and gets cut off by the edge of the page, which is what
 * makes it read as light rather than as a shape, and it hangs low so the hot middle lands on the
 * screen and not on a paragraph.
 */
.band { position: relative; display: flex; gap: 72px; align-items: center; margin-top: 150px; }
.band::before {
  content: '';
  position: absolute;
  top: 62%;
  width: 1320px;
  height: 1080px;
  margin: -540px 0 0 -660px;
  pointer-events: none;
  background: radial-gradient(closest-side, rgba(26, 212, 255, 0.26) 0%, rgba(26, 212, 255, 0.09) 40%, rgba(26, 212, 255, 0) 74%);
}
.band.lit-right::before { left: 86%; }
.band.lit-left::before { left: 14%; }
.band.lit-mid::before { left: 50%; top: 64%; width: 1620px; height: 1300px; margin: -650px 0 0 -810px; }
.band .col { position: relative; flex-grow: 1; }
.band h2 { font-family: 'Nippo', system-ui, sans-serif; font-weight: 700; text-transform: uppercase; font-size: 52px; line-height: 1.06; letter-spacing: 0.02em; margin: 0 0 22px; }
.band p { max-width: 480px; color: var(--text-muted); font-size: 18px; line-height: 1.7; margin: 0; }
.band .shot { position: relative; flex: none; width: 340px; }
.band.wide-strip { display: block; }
.band.wide-strip .col { margin-bottom: 44px; }
.band .shot img { display: block; width: 100%; height: auto; }
.band .shot::before {
  content: '';
  position: absolute;
  left: 50%;
  top: 52%;
  width: 483px;
  height: 483px;
  margin: -241px 0 0 -241px;
  pointer-events: none;
  background: radial-gradient(closest-side, rgba(26, 212, 255, 0.34) 0%, rgba(26, 212, 255, 0.1) 40%, rgba(26, 212, 255, 0) 74%);
}

/* One record, the way the app sets it: the number between two hairlines with what it is over it. */
.record-card { position: relative; border: 1px solid var(--border); background: var(--panel); border-radius: 20px; padding: 28px 24px 26px; text-align: center; max-width: 420px; margin-top: 30px; overflow: hidden; }
.record-card .lit { position: absolute; inset: 0; pointer-events: none; background: radial-gradient(130% 100% at 50% -12%, rgba(26, 212, 255, 0.34) 0%, rgba(26, 212, 255, 0.1) 34%, rgba(26, 212, 255, 0) 68%); }
.record-card .in { position: relative; }
.record-card .rule { height: 1px; width: 56%; margin: 0 auto; background: var(--rule); }
.record-card .what { font-family: 'Nippo', system-ui, sans-serif; font-weight: 700; color: var(--accent); font-size: 14px; letter-spacing: 0.2em; text-transform: uppercase; margin: 20px 0 0; }
.record-card .n { font-family: 'Nippo', system-ui, sans-serif; font-weight: 700; font-size: 108px; line-height: 1.02; margin: 2px 0 0; color: #ffffff; }
/* The number and its unit are the one thing on this card that is a fact, so they are white and
   nothing else is. */
.record-card .u { font-family: 'Nippo', system-ui, sans-serif; font-weight: 700; color: #ffffff; font-size: 11px; letter-spacing: 0.32em; text-transform: uppercase; margin: 2px 0 20px; }
.record-card .where { font-family: 'Nippo', system-ui, sans-serif; font-weight: 700; color: var(--text-muted); font-size: 12px; letter-spacing: 0.12em; text-transform: uppercase; margin: 16px 0 14px; }

/*
 * The three exercises, as a strip you push rather than tabs you click.
 *
 * `apps/api` has no npm step and this page is not worth starting one for, so the choice was between
 * markup pretending to be a control and a control the browser already has. Scroll snapping is the
 * second: it swipes on a phone, drags on a trackpad, and the screen after this one is visible at
 * the edge, which is the part that makes anybody push it at all. The push-up shot is on its side
 * because the app turns the phone for an exercise seen from the side.
 */
/*
 * Edge to edge, whatever the column is doing.
 *
 * A strip that stops where the text stops reads as a picture with a scrollbar. This one starts
 * where the words start and runs off the side of the window, which is the shape that says push me.
 * The padding on the left puts the first slide back under the column; the negative margins take it
 * out to the window on both sides.
 */
.strip { display: flex; align-items: center; gap: 32px; overflow-x: auto; scroll-snap-type: x mandatory; padding-bottom: 34px; scrollbar-width: none; margin-left: calc(50% - 50vw); margin-right: calc(50% - 50vw); padding-left: max(20px, calc(50vw - 50%)); padding-right: max(20px, calc(50vw - 50%)); scroll-padding-left: max(20px, calc(50vw - 50%)); }
.strip::-webkit-scrollbar { display: none; }
/* Every slide is the same width, whichever way up the screen inside it is. Slides of different
   widths give uneven snap points and a ragged right edge, and the strip stops reading as one
   control. The shot that is on its side is simply shorter, which is the true difference. */
.strip .slide { position: relative; scroll-snap-align: start; flex: none; width: 340px; }
.strip .slide img { display: block; width: 340px; height: auto; }

/*
 * Which exercise this is, on the picture instead of under it.
 *
 * It straddles the bottom edge of the phone, half on and half off, so it is the first thing you
 * read and it still covers nothing the screen itself is saying. The circle and the glyph are the
 * ones the app puts on that exercise, so the label and the badge inside the shot are visibly the
 * same object.
 */
.strip .slide .on-shot {
  position: absolute;
  left: 50%;
  bottom: -21px;
  transform: translateX(-50%);
  display: inline-flex;
  align-items: center;
  gap: 10px;
  padding: 7px 20px 7px 7px;
  border-radius: 999px;
  white-space: nowrap;
  background: rgba(10, 11, 12, 0.86);
  backdrop-filter: blur(10px);
  -webkit-backdrop-filter: blur(10px);
  border: 1px solid rgba(26, 212, 255, 0.28);
  font-family: 'Nippo', system-ui, sans-serif;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.12em;
  font-size: 15px;
  color: var(--text);
}
.strip .slide .on-shot i {
  flex: none;
  width: 30px;
  height: 30px;
  border-radius: 999px;
  border: 1px solid rgba(26, 212, 255, 0.4);
  background: var(--accent-dim);
  color: var(--accent);
  display: flex;
  align-items: center;
  justify-content: center;
  font-style: normal;
  font-size: 15px;
  line-height: 1;
}

@media (max-width: 900px) {
  .close { margin-top: 90px; padding: 80px 20px 100px; }
  .close h2 { font-size: 34px; }
  .close p { font-size: 16px; }
  body.home .wrap { padding-top: 30px; }
  body.home .brand { width: 150px; }
  body.home::before, body.home::after { height: 1240px; }
  .hero { margin-top: 44px; }
  .hero h1 { font-size: 38px; }
  .hero .lede { font-size: 16px; }
  /* Measured off the column rather than off the middle of the screen, so the outer two land inside
     the page instead of against its edge. The three still overlap; they just do it in less room. */
  .trio { height: 363px; max-width: 332px; }
  .trio img { width: 180px; margin-left: -90px; }
  .trio img[data-slot='l'] { transform: translateX(-104px) scale(0.69); }
  .trio img[data-slot='r'] { transform: translateX(104px) scale(0.69); }
  .ticker { margin-top: 72px; height: 56px; }
  .ticker .who { font-size: 12px; }
  .ticker .edge { width: 70px; }
  .band { display: block; margin-top: 90px; }
  .band::before { left: 50% !important; width: 880px; height: 1140px; margin: -570px 0 0 -440px; }
  .band h2 { font-size: 36px; }
  .band p { font-size: 16px; max-width: none; }
  .band .shot { width: 300px; margin: 32px auto 0; }
  .band .shot.first { margin-top: 0; margin-bottom: 32px; }
  .record-card { max-width: none; }
  .record-card .n { font-size: 92px; }
  .strip { gap: 20px; }
  .strip .slide, .strip .slide img { width: 250px; }
  .strip .slide .on-shot { font-size: 13px; padding: 6px 16px 6px 6px; bottom: -19px; }
  .strip .slide .on-shot i { width: 26px; height: 26px; font-size: 13px; }

}

/*
 * The pages about a place: a spot, and the city it sits in.
 *
 * Wider than a share card and narrower than the homepage, because these are lists you scan rather
 * than a number you read or a pitch you scroll. Everything on them is built from the rows and tiles
 * already above; what is new is the block that says where a thing is, and the small head that lets
 * a section carry a link on its right.
 */
body.wide .wrap { max-width: 880px; }
body.wide h1 { font-size: 56px; }
body.wide .page-head .lede { font-size: 17px; max-width: 640px; }

/* A section title with room for one link opposite it. */
.head { display: flex; align-items: baseline; justify-content: space-between; gap: 20px; margin: 0 0 18px; }
.head .kick { margin: 0; }
.head a { font-family: 'Nippo', system-ui, sans-serif; font-weight: 700; font-size: 11px; letter-spacing: 0.2em; text-transform: uppercase; text-decoration: none; white-space: nowrap; }
.sect { margin-top: 56px; }

/* Cities / Meppel / Calisthenics parks. The last one is where you are and is not a link. */
.crumbs { display: flex; flex-wrap: wrap; align-items: center; gap: 12px; margin: 0 0 26px; }
.crumbs a, .crumbs .here { color: var(--text-muted); font-size: 11px; letter-spacing: 0.2em; text-transform: uppercase; font-weight: 600; text-decoration: none; }
.crumbs .here { color: var(--text); }
.crumbs span:not(.here) { color: rgba(255, 255, 255, 0.22); font-size: 11px; }

/*
 * Where it is, for the half of the traffic that came to find that out.
 *
 * The aerial if there is one, because a roof seen from above tells you more about whether you can
 * train there than a name does. Otherwise a lit pin on a ruled field, which says "a point on a map"
 * without pretending to be one. The district, the town and the coordinates underneath are text on
 * purpose: that is the part a crawler can read and an assistant can answer with.
 */
.place { border: 1px solid var(--border); border-radius: 18px; overflow: hidden; background: var(--panel); }
.place .view { position: relative; height: 220px; background: #0d1114; overflow: hidden; }
.place .view img { width: 100%; height: 100%; object-fit: cover; display: block; }
.place .grid { position: absolute; inset: 0; background-image: linear-gradient(rgba(255, 255, 255, 0.04) 1px, transparent 1px), linear-gradient(90deg, rgba(255, 255, 255, 0.04) 1px, transparent 1px); background-size: 46px 46px; }
.place .halo { position: absolute; left: 50%; top: 50%; width: 260px; height: 260px; margin: -130px 0 0 -130px; background: radial-gradient(closest-side, rgba(26, 212, 255, 0.22) 0%, rgba(26, 212, 255, 0) 72%); }
.place .pin { position: absolute; left: 50%; top: 50%; width: 20px; height: 20px; margin: -10px 0 0 -10px; border-radius: 999px; background: var(--accent); box-shadow: 0 0 0 8px rgba(26, 212, 255, 0.18); }
.place .at { display: flex; flex-wrap: wrap; align-items: center; gap: 14px; padding: 16px 18px; border-top: 1px solid var(--border); }
.place .at .grow { flex: 1; min-width: 0; }
.place .at .n { font-family: 'Nippo', system-ui, sans-serif; font-weight: 700; text-transform: uppercase; letter-spacing: 0.08em; font-size: 14px; }
.place .at .c { color: var(--text-muted); font-size: 13px; margin-top: 3px; font-variant-numeric: tabular-nums; }
.place .at a { font-family: 'Nippo', system-ui, sans-serif; font-weight: 700; font-size: 11px; letter-spacing: 0.2em; text-transform: uppercase; text-decoration: none; white-space: nowrap; }
.place .at i { font-style: normal; font-size: 17px; color: var(--text-muted); flex: none; }

/* The city page leads with counts rather than a picture, so its tiles take four across. */
.tiles.four { grid-template-columns: repeat(4, 1fr); }

/* Every category is its own page, so the row that looks like a filter is a row of links. */
.pills { display: flex; flex-wrap: wrap; gap: 8px; margin-top: 30px; }
.pills a { display: inline-flex; align-items: center; gap: 8px; min-height: 40px; padding: 0 16px; border-radius: 999px; text-decoration: none; border: 1px solid var(--border); font-family: 'Nippo', system-ui, sans-serif; font-weight: 700; font-size: 11px; letter-spacing: 0.18em; text-transform: uppercase; color: var(--text-muted); }
.pills a.on { border-color: rgba(26, 212, 255, 0.45); background: var(--accent-dim); color: var(--accent); }
.pills a span { font-weight: 500; color: var(--text-muted); }

/* Districts, as cards you can open. */
.districts { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 10px; }
.districts a { display: block; text-decoration: none; color: inherit; border: 1px solid var(--border); background: var(--panel); border-radius: 14px; padding: 16px; }
.districts .n { display: flex; align-items: center; gap: 10px; font-family: 'Nippo', system-ui, sans-serif; font-weight: 700; text-transform: uppercase; letter-spacing: 0.08em; font-size: 14px; }
.districts .n i { font-style: normal; font-size: 15px; color: var(--text-muted); }
.districts .s { color: var(--text-muted); font-size: 12px; margin-top: 8px; }
.districts .s .gold { color: var(--gold); }
.districts .s .open { color: var(--accent); }

@media (max-width: 720px) {
  body.wide h1 { font-size: 36px; }
  .tiles.four { grid-template-columns: repeat(2, 1fr); }
  .districts { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .place .view { height: 170px; }
}

/*
 * The one thing this whole site is for, pinned to the bottom of the window.
 *
 * Somebody who has read three bands and is ready should not have to scroll back up or on to find
 * the buttons, and on a phone the bottom of the screen is where a thumb already is. It sits over
 * the page rather than in it, and the page grows a floor so the footer never ends up underneath it.
 */
.dock {
  position: fixed;
  left: 0;
  right: 0;
  bottom: 0;
  z-index: 40;
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: center;
  gap: 10px 20px;
  padding: 12px 20px;
  background: rgba(10, 11, 12, 0.82);
  backdrop-filter: blur(14px);
  -webkit-backdrop-filter: blur(14px);
  border-top: 1px solid rgba(26, 212, 255, 0.24);
}
.dock .line { font-family: 'Nippo', system-ui, sans-serif; font-weight: 700; color: var(--text-muted); font-size: 11px; letter-spacing: 0.22em; text-transform: uppercase; }
.dock .stores img { height: 40px; }
.dock .stores { gap: 10px; }
body.docked .wrap { padding-bottom: 150px; }

@media (max-width: 620px) {
  .dock { gap: 6px 14px; padding: 10px 16px; }
  .dock .line { display: none; }
  .dock .stores img { height: 36px; }
  body.docked .wrap { padding-bottom: 130px; }
}

/*
 * How the homepage ends.
 *
 * Not the panel every other page ends in. Those are narrow columns where the ask is a small thing
 * at the bottom and a box holds it together; this page is made of bands with light behind them, and
 * a bordered grey card among them was the one element that read as stuck on afterwards.
 *
 * So: no border, no fill, no corners. The same light as the rest of the page, coming up off the
 * floor this time, with the same concrete over it as the hero. The two ends of the page are then
 * the only two places with a surface, which is what makes them read as the two ends.
 */
.close { position: relative; margin: 150px calc(50% - 50vw) 0; padding: 120px 20px 150px; text-align: center; }
.close::before {
  content: '';
  position: absolute;
  left: 50%;
  bottom: -260px;
  width: 1500px;
  height: 860px;
  margin-left: -750px;
  pointer-events: none;
  background: radial-gradient(closest-side, rgba(26, 212, 255, 0.3) 0%, rgba(26, 212, 255, 0.09) 42%, rgba(26, 212, 255, 0) 74%);
}
.close::after {
  content: '';
  position: absolute;
  inset: 0;
  pointer-events: none;
  opacity: 0.1;
  background-image: url('/images/concrete.webp');
  background-size: 384px 384px;
  -webkit-mask-image: linear-gradient(to top, transparent 0%, rgba(0, 0, 0, 0.85) 26%, rgba(0, 0, 0, 0.7) 62%, transparent 100%);
  mask-image: linear-gradient(to top, transparent 0%, rgba(0, 0, 0, 0.85) 26%, rgba(0, 0, 0, 0.7) 62%, transparent 100%);
}
.close .in { position: relative; max-width: 1080px; margin: 0 auto; }
.close h2 { font-family: 'Nippo', system-ui, sans-serif; font-weight: 700; text-transform: uppercase; font-size: 58px; line-height: 1.06; letter-spacing: 0.015em; margin: 0; }
.close p { color: var(--text-muted); font-size: 19px; margin: 26px auto 0; max-width: 520px; }
.close .stores { margin-top: 40px; }
