/* =============================================================
   barakahgames.com — page composition

   Composes from tokens.css only. No hex value lives in this file
   and no token is redefined here: tokens.css is duplicated verbatim
   into barakahgames-site and osc-invite-site, so editing it in one
   place silently diverges the others.

   Ported from barakahgames-site/public/assets/site.css. Everything
   that describes a component came across unchanged. What changed is
   only the page shell: the static site was five one-screen pages,
   this is a scrolling site with a header and a footer.

   The @font-face blocks do NOT live here. Shopify serves assets
   through asset_url, which needs Liquid, so they are inlined in
   layout/theme.liquid. Only the family names travel between repos.
   ============================================================= */

/* ---------- Ground ---------- */

html {
  min-height: 100%;
}

body {
  min-height: 100vh;
  box-sizing: border-box;
  /* The homepage's horizontal gutter. Wider than --bg-space-md, which
     the document pages use, because the homepage runs full bleed. */
  --gutter: clamp(1.25rem, 5vw, 3rem);

  /* One measure for the whole page. Every section that caps its width
     uses this, so they all share a left edge — the hero's cards, the
     film's frame, the deck row and the ritual's curve all start in the
     same place. They were at 82, 82, 74 and 76rem, which is why the
     ritual's headline read as sitting outside the page. */
  --wrap: 82rem;

  /* The wordmark's height, and the masthead's composed from it —
     padding, mark, rule. The hero subtracts the masthead to fill the
     first screen exactly, so the two must never be set independently.
     Change --mark-h and everything downstream follows. */
  --mark-h: 2.5rem;
  --header-h: calc(var(--bg-space-md) * 2 + var(--mark-h) + var(--bg-hairline-width));
}

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

/* The gutter. Sections own their vertical rhythm — a global body
   padding would fight every full-bleed section on the homepage —
   so the horizontal gutter is the only thing set globally. */
.gutter {
  padding-inline: var(--bg-space-md);
}

.measure {
  width: 100%;
  max-width: var(--bg-measure);
  margin-inline: auto;
}

/* The one-screen pages — /app and 404. The document pages never
   centre vertically: a policy that does jumps on load. */
body.screen {
  display: flex;
  flex-direction: column;
  padding: var(--bg-space-xl) var(--bg-space-md);
}

body.screen main,
body.screen > section,
body.screen > .footer {
  width: 100%;
  max-width: var(--bg-measure);
  margin-inline: auto;
}


@supports not (height: 100svh) {
  .hero { min-height: calc(100vh - var(--header-h)); }
}

/* Every page that is not .screen. Footer pinned to the bottom on
   short pages without a fixed position. */
body.site {
  display: flex;
  flex-direction: column;
}

body.site > main {
  flex: 1;
}

/* Skip link. There are no fills, so it is a hairline row that
   arrives on focus rather than a coloured bar. */
.skip {
  position: absolute;
  left: -9999px;
  top: 0;
  z-index: 10;
  padding: var(--bg-space-sm) var(--bg-space-md);
  background: var(--p-ground);
  border-bottom: var(--bg-hairline-width) solid var(--p-hairline);
  color: var(--p-text);
  text-decoration: none;
}

.skip:focus {
  left: 0;
}

/* ---------- The terracotta line ----------
   The same curve as components/TerracottaLine.tsx, not a redraw: the
   app's Catmull-Rom control points converted to cubic Béziers, which
   is exact for tension 0.5. Taken from the invite site, which did the
   conversion. preserveAspectRatio="none" reproduces the app's
   independent width/height scaling, and non-scaling-stroke keeps the
   weight at the app's 1.6 regardless of the box.

   Product-level, not brand-level. It belongs to One Step Closer, so
   it appears on the homepage and the product pages and never in the
   header or the footer. */

.line {
  display: block;
  width: 100%;
  height: clamp(2.25rem, 11vw, 3.5rem);
  margin: 0 auto var(--bg-space-lg);
  overflow: visible;
}

.line path {
  fill: none;
  stroke: var(--p-accent);
  stroke-width: 1.6;
  stroke-linecap: round;
  stroke-linejoin: round;
  vector-effect: non-scaling-stroke;
  /* pathLength="1" normalises the dash maths, so the draw-on needs
     no measured length. */
  stroke-dasharray: 1;
  stroke-dashoffset: 1;
  animation: line-draw calc(var(--bg-motion-duration) * 2.5)
    var(--bg-motion-ease) both;
}

@keyframes line-draw {
  to {
    stroke-dashoffset: 0;
  }
}

/* A line used as a section divider is quieter than the hero's and
   does not redraw on every entry. Sparingly — it loses its power
   if it is everywhere. */
.line--divider {
  height: clamp(1.5rem, 6vw, 2.25rem);
  opacity: 0.7;
  margin-block: var(--bg-space-xl);
}

.line--divider path {
  animation: none;
  stroke-dashoffset: 0;
}

/* ---------- Copy ---------- */

.eyebrow {
  margin: 0 0 var(--bg-space-md);
}

.lede {
  color: var(--p-text-body);
  font-size: var(--bg-size-body-lg);
}

.hint {
  color: var(--p-text-hint);
  font-size: var(--bg-size-small);
}

/* ---------- Entry motion ----------
   Fade and rise. The stagger is the only sequencing on any page;
   tokens.css already collapses the duration under reduced-motion,
   so the delays below become inaudible rather than needing a
   second media query. */

.enter-1 { animation-delay: 0ms; }
.enter-2 { animation-delay: 90ms; }
.enter-3 { animation-delay: 180ms; }
.enter-4 { animation-delay: 270ms; }
.enter-5 { animation-delay: 360ms; }
.enter-6 { animation-delay: 450ms; }

@media (prefers-reduced-motion: reduce) {
  .enter-1, .enter-2, .enter-3,
  .enter-4, .enter-5, .enter-6 {
    animation-delay: 0ms;
  }
}

/* ---------- Fields ----------
   tokens.css supplies .field itself — transparent, one underline,
   terracotta on focus. What it cannot supply is the pairing with a
   permanent label above, which is the part the app is strict about. */

.field-group {
  margin: 0 0 var(--bg-space-lg);
}

.field-label {
  display: block;
  margin: 0 0 var(--bg-space-xs);
  font-family: var(--bg-font-body);
  font-size: var(--bg-size-label);
  font-weight: var(--bg-weight-secondary);
  letter-spacing: var(--bg-tracking-label);
  text-transform: uppercase;
  /* Deliberately NOT .label — these are not the gold ones. Gold is
     rationed to the eyebrow above a headline and to Ramadan. */
  color: var(--p-text-label);
}

.field {
  display: block;
  width: 100%;
  font-size: var(--bg-size-body);
}

textarea.field {
  min-height: 7rem;
  resize: vertical;
}

/* Autofill repaints the field white in WebKit and Blink, which is
   the one thing the brand cannot have. There is no way to set the
   background directly, so the fill is pushed outside the paint box
   by a long inset shadow and the text is drawn by the caret colour. */
.field:-webkit-autofill,
.field:-webkit-autofill:hover,
.field:-webkit-autofill:focus {
  -webkit-text-fill-color: var(--p-text);
  -webkit-box-shadow: 0 0 0 100px var(--p-ground) inset;
  caret-color: var(--p-text);
}

/* ---------- The action ----------
   A letterspaced uppercase label beside a circular outlined arrow.
   No fills anywhere: --bg-fill-permitted is 0 and stays 0 until the
   buy-action question in CLAUDE.md is settled. */

.action {
  display: inline-flex;
  align-items: center;
  gap: 14px;
  padding: 0;
  border: 0;
  background: none;
  border-radius: var(--bg-radius-none);
  text-decoration: none;
  color: var(--p-text);
  cursor: pointer;
  transition: opacity var(--bg-motion-duration-fast) var(--bg-motion-ease);
}

.action:hover,
.action:active {
  opacity: 0.75;
}

.action[disabled] {
  opacity: 0.45;
  cursor: default;
}

.action__label {
  font-family: var(--bg-font-body);
  font-weight: var(--bg-weight-body);
  font-size: 0.625rem;
  letter-spacing: 0.3em;
  /* The tracking adds a trailing space after the last letter;
     pulling it back keeps the optical gap to the circle at 14. */
  margin-right: -0.3em;
  text-transform: uppercase;
  white-space: nowrap;
}

.action__circle {
  flex: none;
  display: grid;
  place-items: center;
  width: 44px;
  height: 44px;
  border: var(--bg-hairline-width) solid
    rgb(from var(--p-ink) r g b / 0.35);
  border-radius: var(--bg-radius-full);
}

.action__circle svg {
  display: block;
  width: 18px;
  height: 18px;
  fill: none;
  stroke: var(--p-ink);
  stroke-width: 2;
  stroke-linecap: round;
  stroke-linejoin: round;
}

@supports not (color: rgb(from #fff r g b / 50%)) {
  .action__circle {
    border-color: var(--p-hairline);
  }
}

/* ---------- Form states ----------
   Three states, one at a time. The success state replaces the form
   rather than sitting beneath it — the app never stacks a result
   under the control that produced it. */

.form-state[hidden] {
  display: none;
}

.form-error {
  margin: var(--bg-space-md) 0 0;
  color: var(--p-accent);
  font-size: var(--bg-size-small);
}

/* The honeypot. Off-screen rather than display:none, which some
   autofillers skip and some bots detect. */
.trap {
  position: absolute;
  width: 1px;
  height: 1px;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
}

/* ---------- Documents ----------
   /privacy and /terms. Long-form, read once, usually by a reviewer
   or by someone who is worried. Generous leading, hairline rules
   instead of boxes, and no rectangle anywhere. */

.document {
  max-width: 42rem;
  margin-inline: auto;
  padding-block: var(--bg-space-xl);
}

.document h1 {
  margin-bottom: var(--bg-space-lg);
}

.document h2 {
  font-size: var(--bg-size-display-3);
  margin-top: var(--bg-space-xl);
  padding-top: var(--bg-space-md);
  border-top: var(--bg-hairline-width) solid var(--p-hairline);
}

.document h3 {
  font-size: var(--bg-size-body-lg);
  font-family: var(--bg-font-body);
  font-weight: var(--bg-weight-body);
  margin-top: var(--bg-space-lg);
  margin-bottom: var(--bg-space-xs);
  letter-spacing: 0;
}

.document p,
.document li {
  color: var(--p-text-body);
}

.document ul {
  margin: 0 0 var(--bg-space-md);
  padding-left: 1.1rem;
  max-width: var(--bg-measure);
}

.document li {
  margin-bottom: var(--bg-space-xs);
}

.document li::marker {
  color: var(--p-text-hint);
}

.document strong {
  color: var(--p-text);
  font-weight: var(--bg-weight-body);
}

.document a {
  color: var(--p-text);
  text-decoration-color: var(--p-hairline);
  text-underline-offset: 0.2em;
}

.document a:hover {
  text-decoration-color: var(--p-accent);
}

.updated {
  color: var(--p-text-hint);
  font-size: var(--bg-size-small);
  margin-bottom: var(--bg-space-xl);
}

/* Tables — the subprocessor list. Hairline rules only, and it has to
   survive a phone without a horizontal scrollbar on the page itself. */
.table-scroll {
  overflow-x: auto;
  margin: 0 0 var(--bg-space-md);
}

.document table {
  border-collapse: collapse;
  width: 100%;
  min-width: 30rem;
  font-size: var(--bg-size-small);
}

.document th,
.document td {
  text-align: left;
  vertical-align: top;
  padding: var(--bg-space-sm) var(--bg-space-md) var(--bg-space-sm) 0;
  border-bottom: var(--bg-hairline-width) solid var(--p-hairline);
  color: var(--p-text-body);
}

.document th {
  color: var(--p-text-label);
  font-weight: var(--bg-weight-secondary);
  font-size: var(--bg-size-label);
  letter-spacing: var(--bg-tracking-label);
  text-transform: uppercase;
  white-space: nowrap;
}

/* ---------- Choice rows ----------
   A native <select> renders OS chrome, which on most platforms means
   a white sheet — the one thing the brand cannot have. The app uses
   hairline-ruled rows for every choice list, so the form does too.
   tokens.css supplies .row; this is the radio pairing. */

/* A fieldset carries a default border, padding and min-width that all
   have to go. min-width:0 is the one that is easy to miss — without
   it a fieldset refuses to shrink below its content on narrow
   screens. */
.choices {
  border: 0;
  padding: 0;
  margin: 0 0 var(--bg-space-lg);
  min-width: 0;
}

/* A legend is not a normal box — it renders into the fieldset border
   in most engines and ignores much of what is set on it. Making it a
   plain block is the reliable way to flow it like the field labels
   above the other inputs, which is what it has to match. */
.choices legend {
  display: block;
  float: none;
  width: 100%;
  padding: 0;
  margin: 0 0 var(--bg-space-xs);
  font-family: var(--bg-font-body);
  font-size: var(--bg-size-label);
  font-weight: var(--bg-weight-secondary);
  letter-spacing: var(--bg-tracking-label);
  text-transform: uppercase;
  color: var(--p-text-label);
}

.choices__set {
  border-top: var(--bg-hairline-width) solid var(--p-hairline);
}

.choice {
  /* The visually-hidden input is positioned against this, so the row
     has to establish the containing block. Without it the input
     escapes to the page. */
  position: relative;
  display: flex;
  align-items: center;
  gap: var(--bg-space-sm);
  cursor: pointer;
  color: var(--p-text-body);
  font-size: var(--bg-size-body);
}

.choice input {
  /* The native control is removed rather than restyled — appearance
     is unreliable across engines and a partly-styled radio is worse
     than a drawn one. It stays in the accessibility tree. */
  position: absolute;
  left: 0;
  top: 0;
  opacity: 0;
  width: 1px;
  height: 1px;
  margin: 0;
  pointer-events: none;
}

.choice__mark {
  flex: none;
  width: 18px;
  height: 18px;
  border: var(--bg-hairline-width) solid var(--p-hairline);
  border-radius: var(--bg-radius-full);
  display: grid;
  place-items: center;
}

/* The selected dot. This is a mark rather than a control, which is
   the same exemption the camera shutter and the seal have in the app. */
.choice__mark::after {
  content: "";
  width: 8px;
  height: 8px;
  border-radius: var(--bg-radius-full);
  background: var(--p-accent);
  opacity: 0;
  transition: opacity var(--bg-motion-duration-fast) var(--bg-motion-ease);
}

.choice input:checked + .choice__mark::after {
  opacity: 1;
}

.choice input:checked + .choice__mark {
  border-color: var(--p-accent);
}

.choice input:focus-visible + .choice__mark {
  outline: 2px solid var(--p-accent);
  outline-offset: 3px;
}

/* =============================================================
   The chrome. New here — the static site had no header and its
   footer was one hairline row on a one-screen page.

   The Barakah Games wordmark lives here. The terracotta line does
   not: it is product-level, and it would need surgical removal the
   day a second product exists.
   ============================================================= */

.masthead {
  border-bottom: var(--bg-hairline-width) solid var(--p-hairline);
}

.masthead__inner {
  display: flex;
  align-items: center;
  gap: var(--bg-space-lg);
  max-width: 72rem;
  margin-inline: auto;
  padding-block: var(--bg-space-md);
}

/* The wordmark. The mark is inline SVG filled with currentColor, so
   the colour it takes is the ink token and there is no hex anywhere
   in the chain. Sized by height, like type on a baseline, never by
   width — the aspect ratio is fixed by the viewBox. */
.wordmark {
  display: inline-flex;
  color: var(--p-text);
  text-decoration: none;
}

.wordmark .mark {
  display: block;
  height: var(--mark-h);
  width: auto;
  /* 529.37 / 162.82, the measured bounds of the supplied vector.
     Declared so the mark reserves its space before paint rather than
     collapsing and shifting the header. */
  aspect-ratio: 529.37 / 162.82;
}

/* 2.5rem is a floor, not a preference. GAMES is a letterspaced line
   occupying about 15% of the lockup's height, so at the 1.75rem this
   started at it renders around four pixels tall and reads as a smudge.
   Cream on forest makes it worse: light strokes on a dark ground
   optically thin, and this face is high-contrast to begin with.
   Anything smaller needs a different lockup from the designer, not a
   smaller version of this one. */
.wordmark--small .mark {
  height: 2.25rem;
}

/* The link is the interactive thing, so the focus ring belongs on it
   rather than on the mark inside it. */
.wordmark:focus-visible {
  outline: 2px solid var(--p-accent);
  outline-offset: 4px;
}

.nav {
  margin-left: auto;
  display: flex;
  flex-wrap: wrap;
  gap: var(--bg-space-lg);
}

/* Uppercase and letterspaced, matching the labels used everywhere else
   on the site. Mixed sentence case across three links — "One Step
   Closer" beside "The app" — read as three different decisions. */
.nav a {
  color: var(--p-text-body);
  text-decoration: none;
  font-size: 0.625rem;
  letter-spacing: 0.18em;
  text-transform: uppercase;
  white-space: nowrap;
  transition: color var(--bg-motion-duration-fast) var(--bg-motion-ease);
}

.nav a:hover,
.nav a[aria-current="page"] {
  color: var(--p-text);
}

/* ---------- the toggle ----------
   Hairlines, at the same weight as every rule on the site, at unequal
   widths so the closed state is not three identical bars. They square
   up and cross when the panel opens. */

.burger {
  display: none;
  appearance: none;
  border: 0;
  background: none;
  cursor: pointer;
  margin-left: auto;
  position: relative;
  padding: 0;
  /* The box matches the wordmark's height so the masthead row is set
     by the mark, not by the control. A 44px-tall button here would add
     eight pixels the --header-h calculation knows nothing about, and
     the hero would overshoot the fold by exactly that much. */
  width: 2.75rem;
  height: var(--mark-h);
}

/* The touch target, expanded past the box rather than by growing it.
   Reaches 44px in both directions without touching layout. */
.burger::after {
  content: "";
  position: absolute;
  inset: -0.3rem -0.125rem;
}

.burger__line {
  position: absolute;
  left: 50%;
  top: 50%;
  margin-left: -11px;
  height: var(--bg-hairline-width);
  background: var(--p-ink);
  transition:
    width var(--bg-motion-duration-fast) var(--bg-motion-ease),
    transform var(--bg-motion-duration) var(--bg-motion-ease),
    opacity var(--bg-motion-duration-fast) var(--bg-motion-ease);
}

/* Unequal widths, so the closed state is not three identical bars. */
.burger__line:nth-child(1) { width: 22px; transform: translateY(-7px); }
.burger__line:nth-child(2) { width: 15px; transform: translateY(0); }
.burger__line:nth-child(3) { width: 19px; transform: translateY(7px); }

.burger:hover .burger__line { width: 22px; }

/* Open: the outer two square up, meet in the middle and cross; the
   inner one goes. */
.burger[aria-expanded="true"] .burger__line:nth-child(1) {
  width: 22px;
  transform: translateY(0) rotate(45deg);
}

.burger[aria-expanded="true"] .burger__line:nth-child(2) {
  width: 22px;
  opacity: 0;
}

.burger[aria-expanded="true"] .burger__line:nth-child(3) {
  width: 22px;
  transform: translateY(0) rotate(-45deg);
}

.burger:focus-visible {
  outline: 2px solid var(--p-accent);
  outline-offset: 2px;
  border-radius: var(--bg-radius-full);
}

@media (prefers-reduced-motion: reduce) {
  .burger__line { transition: none; }
}

@media (max-width: 46rem) {
  /* Ten per cent down on a phone, where the mark had the screen to
     itself and read large. The masthead follows automatically. */
  body { --mark-h: 2.25rem; }

  .burger { display: block; }

  /* The panel. Fixed rather than absolute so it covers the page
     regardless of where the header sits, and it starts display:none so
     its links are out of the tab order while closed. */
  /* visibility and opacity rather than display. display cannot be
     transitioned, so closing was a cut while opening was a fade.
     visibility still takes the links out of the tab order, and its
     change is delayed until the fade finishes so the panel is not
     pulled out from under its own animation. */
  .nav {
    position: fixed;
    inset: 0;
    z-index: 20;
    display: flex;
    flex-direction: column;
    justify-content: center;
    align-items: flex-start;
    gap: var(--bg-space-lg);
    margin: 0;
    padding: var(--bg-space-2xl) var(--gutter, var(--bg-space-md));
    background: var(--p-ground);
    visibility: hidden;
    opacity: 0;
    transition:
      opacity var(--bg-motion-duration) var(--bg-motion-ease),
      visibility 0s linear var(--bg-motion-duration);
  }

  .nav.is-open {
    visibility: visible;
    opacity: 1;
    transition:
      opacity var(--bg-motion-duration) var(--bg-motion-ease),
      visibility 0s;
  }

  /* The panel takes focus when it opens, not the first link. Focusing
     a link paints a focus ring on it, which after a tap reads as a
     stray outline around one menu item rather than as the keyboard
     affordance it is. The panel is a programmatic target, never
     tabbed to, so it needs no ring of its own. */
  .nav:focus { outline: none; }

  /* Large in the panel — this is the whole screen, not a strip. */
  .nav a {
    font-size: var(--bg-size-display-3);
    font-family: var(--bg-font-display);
    letter-spacing: var(--bg-tracking-display);
    text-transform: none;
    color: var(--p-text);
    opacity: 0;
  }

  /* The stagger hangs off .is-open so it replays on every open. Left
     on .nav it would run once at page load and never again, since the
     panel is no longer added and removed from the layout. */
  .nav.is-open a { animation: bg-enter var(--bg-motion-duration) var(--bg-motion-ease) forwards; }
  .nav.is-open a:nth-child(1) { animation-delay: 40ms; }
  .nav.is-open a:nth-child(2) { animation-delay: 110ms; }
  .nav.is-open a:nth-child(3) { animation-delay: 180ms; }
  .nav.is-open a:nth-child(4) { animation-delay: 250ms; }

  /* The toggle and the wordmark both sit above the panel. Leaving the
     wordmark under it makes the whole header look like it vanished,
     and there is then nothing to press to get home. */
  .burger,
  .masthead .wordmark { position: relative; z-index: 21; }
}

/* ---------- Footer ----------
   One hairline row on the static site. Here it carries navigation,
   the policies and the wordmark, so it is a column set that
   collapses to a stack. */

.footer {
  width: 100%;
  max-width: var(--bg-measure);
  margin: var(--bg-space-2xl) auto 0;
  padding-top: var(--bg-space-md);
  border-top: var(--bg-hairline-width) solid var(--p-hairline);
  display: flex;
  flex-wrap: wrap;
  gap: var(--bg-space-md);
  font-size: var(--bg-size-small);
}

.footer a {
  color: var(--p-text-hint);
  text-decoration: none;
}

.footer a:hover {
  color: var(--p-text-body);
}

.footer__mark {
  margin-left: auto;
  color: var(--p-text-hint);
}

/* The site footer. Wider than the one-screen .footer above, which
   /app and 404 keep using. */
.colophon {
  border-top: var(--bg-hairline-width) solid var(--p-hairline);
  margin-top: var(--bg-space-2xl);
}

.colophon__inner {
  max-width: 72rem;
  margin-inline: auto;
  padding-block: var(--bg-space-xl);
  display: grid;
  gap: var(--bg-space-xl);
  grid-template-columns: repeat(auto-fit, minmax(11rem, 1fr));
}

.colophon h2 {
  font-family: var(--bg-font-body);
  font-size: var(--bg-size-label);
  font-weight: var(--bg-weight-secondary);
  letter-spacing: var(--bg-tracking-label);
  text-transform: uppercase;
  color: var(--p-text-label);
  margin: 0 0 var(--bg-space-md);
}

.colophon ul {
  list-style: none;
  margin: 0;
  padding: 0;
  display: grid;
  gap: var(--bg-space-sm);
}

.colophon a {
  color: var(--p-text-body);
  text-decoration: none;
  font-size: var(--bg-size-small);
}

.colophon a:hover {
  color: var(--p-text);
}

.colophon__mark {
  grid-column: 1 / -1;
  padding-top: var(--bg-space-md);
  border-top: var(--bg-hairline-width) solid var(--p-hairline);
  display: flex;
  flex-wrap: wrap;
  gap: var(--bg-space-md);
  /* centre, not baseline. An inline SVG has no baseline of its own, so
     baseline alignment drops the neighbouring links to the bottom of
     the lockup instead of beside it. */
  align-items: center;
}

.colophon__legal {
  margin-left: auto;
  color: var(--p-text-hint);
  font-size: var(--bg-size-small);
}

/* ---------- /app, below the fold ----------
   The app's story, for people who did not arrive from the box.
   Hairline rows, no fills, no cards. */



/* ---------- Shopify's policy pages ----------
   /policies/privacy-policy and /policies/terms-of-service.

   These are the one set of pages the theme does not template. Shopify
   renders them itself and wraps them in its own markup — neither Dawn
   nor Horizon ships a templates/policy.liquid, and there is no `policy`
   object in any template scope. What the theme does control is the
   layout they render inside and the CSS, so the rules below map
   Shopify's containers onto the same .document treatment the rest of
   the site uses.

   The brand's own <h1> lives in the policy body, so Shopify's title —
   which is the fixed record name, "Privacy policy" rather than
   "Privacy." — is hidden rather than restyled. Headlines here are
   sentence case with a full stop like everywhere else. */

.shopify-policy__container {
  max-width: 42rem;
  margin-inline: auto;
  padding: var(--bg-space-xl) var(--bg-space-md);
}

.shopify-policy__title {
  display: none;
}

.shopify-policy__body h1 {
  margin-bottom: var(--bg-space-lg);
}

.shopify-policy__body h2 {
  font-size: var(--bg-size-display-3);
  margin-top: var(--bg-space-xl);
  padding-top: var(--bg-space-md);
  border-top: var(--bg-hairline-width) solid var(--p-hairline);
}

.shopify-policy__body h3 {
  font-size: var(--bg-size-body-lg);
  font-family: var(--bg-font-body);
  font-weight: var(--bg-weight-body);
  margin-top: var(--bg-space-lg);
  margin-bottom: var(--bg-space-xs);
  letter-spacing: 0;
}

.shopify-policy__body p,
.shopify-policy__body li {
  color: var(--p-text-body);
}

.shopify-policy__body ul {
  margin: 0 0 var(--bg-space-md);
  padding-left: 1.1rem;
  max-width: var(--bg-measure);
}

.shopify-policy__body li {
  margin-bottom: var(--bg-space-xs);
}

.shopify-policy__body li::marker {
  color: var(--p-text-hint);
}

.shopify-policy__body strong {
  color: var(--p-text);
  font-weight: var(--bg-weight-body);
}

.shopify-policy__body a {
  color: var(--p-text);
  text-decoration-color: var(--p-hairline);
  text-underline-offset: 0.2em;
}

.shopify-policy__body a:hover {
  text-decoration-color: var(--p-accent);
}

.shopify-policy__body .updated {
  color: var(--p-text-hint);
  font-size: var(--bg-size-small);
  margin-bottom: var(--bg-space-xl);
}

/* The subprocessor table. Same treatment as .document — hairline rules
   only, and it has to survive a phone without making the page itself
   scroll sideways. */
.shopify-policy__body .table-scroll {
  overflow-x: auto;
  margin: 0 0 var(--bg-space-md);
}

.shopify-policy__body table {
  border-collapse: collapse;
  width: 100%;
  min-width: 30rem;
  font-size: var(--bg-size-small);
}

.shopify-policy__body th,
.shopify-policy__body td {
  text-align: left;
  vertical-align: top;
  padding: var(--bg-space-sm) var(--bg-space-md) var(--bg-space-sm) 0;
  border-bottom: var(--bg-hairline-width) solid var(--p-hairline);
  color: var(--p-text-body);
}

.shopify-policy__body th {
  color: var(--p-text-label);
  font-weight: var(--bg-weight-secondary);
  font-size: var(--bg-size-label);
  letter-spacing: var(--bg-tracking-label);
  text-transform: uppercase;
  white-space: nowrap;
}

/* =============================================================
   The homepage.

   This is a different design language from the pages above it. The
   documents, /app and /support are the ported static site — hairline
   rules, no fills, cream on forest and nothing else. The homepage was
   rebuilt after that read as an unbroken column of text: fills are
   permitted here, the type is far louder, and the deck colours do the
   work. See CLAUDE.md, "The homepage is not the documents".

   Two rules hold across every section:

     Cards are never clipped by a section edge. The lower pair in the
     hero finishes over the film's top padding, so that padding is
     structural and not decoration.

     A deck accent never colours text. Measured on the forest ground
     the three land at 2.22, 2.35 and 3.10 to one, all beneath the 4.5
     that text this size needs. Colour goes into a mark instead.
   ============================================================= */



.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 0.7rem;
  font-family: var(--bg-font-body);
  font-weight: 500;
  font-size: 0.6875rem;
  letter-spacing: 0.2em;
  text-transform: uppercase;
  text-decoration: none;
  padding: 1.05rem 1.9rem;
  border: 0;
  border-radius: var(--bg-radius-full);
  /* Cream on the bright terracotta, chosen deliberately. It measures
     2.95 to one, under the 4.5 that AA asks of text this size and
     under the 3:1 large-text floor as well. Two alternatives both
     pass if this ever needs revisiting: dark ink on this same fill at
     5.13, or cream on --p-open-book-deep at 5.99. */
  background: var(--p-accent);
  color: var(--p-ink);
  cursor: pointer;
  transition:
    transform var(--bg-motion-duration-fast) var(--bg-motion-ease),
    filter var(--bg-motion-duration-fast) var(--bg-motion-ease),
    background-color var(--bg-motion-duration-fast) var(--bg-motion-ease),
    box-shadow var(--bg-motion-duration-fast) var(--bg-motion-ease);
}

.btn:hover {
  /* brightness rather than a second hex: it lifts the fill without
     adding a value nobody can trace, and cream still measures above
     five to one against the result. */
  filter: brightness(1.12);
  transform: translateY(-2px);
  box-shadow: 0 10px 24px rgba(15, 30, 22, 0.35);
}

.btn:active {
  transform: translateY(0);
  filter: brightness(0.96);
  box-shadow: none;
}

.btn--ghost {
  background: transparent;
  color: var(--p-ink);
  box-shadow: inset 0 0 0 1px rgb(from var(--p-ink) r g b / 0.45);
}

.btn--ghost:hover {
  filter: none;
  background: rgb(from var(--p-ink) r g b / 0.08);
  box-shadow: inset 0 0 0 1px rgb(from var(--p-ink) r g b / 0.75);
}

.btn--ghost:active { background: rgb(from var(--p-ink) r g b / 0.04); }

@supports not (color: rgb(from #fff r g b / 50%)) {
  .btn--ghost:hover {
    background: rgba(248, 245, 240, 0.08);
    box-shadow: inset 0 0 0 1px rgba(248, 245, 240, 0.75);
  }
}

@media (prefers-reduced-motion: reduce) {
  .btn:hover, .btn:active { transform: none; }
}

@supports not (color: rgb(from #fff r g b / 50%)) {
  .btn--ghost { box-shadow: inset 0 0 0 1px rgba(248, 245, 240, 0.45); }
}

.btn:focus-visible {
  outline: 2px solid var(--p-ink);
  outline-offset: 3px;
}

.actions {
  display: flex;
  gap: 0.9rem;
  flex-wrap: wrap;
  align-items: center;
  justify-content: center;
}

@media (max-width: 26rem) {
  .btn { width: 100%; }
}

/* ---------- hero ----------
   Three columns: card channel, text, card channel. The channels carry
   a real minimum width, so a card can never reach the headline however
   the window is sized. An earlier version positioned cards from the
   viewport edge and pushed them further out at every width to dodge a
   collision, which is precisely how they ended up as slivers. Here the
   text column yields first and the cards stay whole. */

.hero {
  position: relative;
  z-index: 1;
  /* Fill what is left of the first screen. svh rather than vh so the
     mobile address bar appearing does not resize the hero mid-scroll. */
  min-height: calc(100svh - var(--header-h));
  display: grid;
  /* An explicit 1fr track. Without it the single child sits in an auto
     track, auto sizes to max-content, and the grid inside collapses to
     the sum of its minimums — 1051px inside a 1440px window, with the
     card channels stuck at their 10.5rem floor and max-width: 82rem
     never applying. minmax(0,1fr) rather than 1fr so a long unbroken
     word cannot push the track wider than the screen. */
  grid-template-columns: minmax(0, 1fr);
  /* Even top and bottom. Uneven padding pushes the centred copy up by
     half the difference, which is what made it read as sitting high. */
  padding: clamp(1.5rem, 4vw, 3rem) var(--gutter);
  /* clip, not hidden: hidden forces the other axis to auto and traps
     the cards inside the section, which is the one thing this layout
     cannot have. */
  overflow-x: clip;
  overflow-y: visible;
}

.hero__grid {
  display: grid;
  /* stretch, and no align-content. The hero is a grid and this is its
     only child, so the row fills the height and the copy — align-self
     centre — centres against the screen. Centring the ROW instead
     collapses it to its content, the side columns stop reaching the
     hero's bottom edge, and the cards they anchor lose their hang. */
  align-items: stretch;
  grid-template-columns: minmax(10.5rem, 1fr) minmax(0, 42rem) minmax(10.5rem, 1fr);
  gap: clamp(0.5rem, 1.5vw, 1.5rem);
  /* width first, then the cap. An auto inline margin on a grid item
     switches off stretch alignment, so without an explicit width the
     box shrinks to its content and max-width never comes into play —
     which is what kept the channels at their 10.5rem floor. */
  width: 100%;
  max-width: var(--wrap);
  margin-inline: auto;
}

.hero__side { position: relative; min-height: 24rem; }

.hero__copy {
  text-align: center;
  align-self: center;
  position: relative;
  z-index: 2;
}

.hero h1 { font-size: clamp(2.1rem, 4.7vw, 3.9rem); }
.hero h1 em { font-style: italic; color: var(--p-label); }

/* ---------- the hero form ----------
   The app's input is transparent with a single underline, which is
   right inside a product that has already been paid for and wrong for
   the one thing this page asks a stranger to do. Here the field and
   its action are a single object at the button's own radius, centred,
   so it reads as a control rather than a rule with a button beside it.

   The underline treatment is still what the snippet renders without
   `pill: true`, and still what the deeper pages use. */
.hero__form {
  max-width: 30rem;
  margin-inline: auto;
}

.pill {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  width: 100%;
  padding: 0.4rem 0.4rem 0.4rem 1.35rem;
  border-radius: var(--bg-radius-full);
  background: rgb(from var(--p-ink) r g b / 0.06);
  box-shadow: inset 0 0 0 1px rgb(from var(--p-ink) r g b / 0.28);
  transition:
    box-shadow var(--bg-motion-duration-fast) var(--bg-motion-ease),
    background-color var(--bg-motion-duration-fast) var(--bg-motion-ease);
}

/* The whole control takes the focus ring, not just the input inside
   it — the pill is what reads as the control. */
.pill:focus-within {
  background: rgb(from var(--p-ink) r g b / 0.09);
  box-shadow: inset 0 0 0 1px var(--p-accent);
}

@supports not (color: rgb(from #fff r g b / 50%)) {
  .pill { background: rgba(248, 245, 240, 0.06); box-shadow: inset 0 0 0 1px rgba(248, 245, 240, 0.28); }
  .pill:focus-within { background: rgba(248, 245, 240, 0.09); }
}

.pill__input {
  flex: 1;
  min-width: 0;
  border: 0;
  background: none;
  color: var(--p-text);
  font: inherit;
  font-size: var(--bg-size-body);
  padding: 0.6rem 0;
  outline: none;
}

.pill__input::placeholder { color: var(--p-text-hint); }

.pill .btn { flex: none; padding: 0.95rem 1.5rem; }

/* Below 26rem the two will not sit side by side without the button
   crushing the field, so the pill becomes a rounded block and they
   stack — still one object, still centred. */
@media (max-width: 26rem) {
  .pill {
    flex-direction: column;
    gap: 0.75rem;
    padding: 1rem;
    border-radius: 1.5rem;
  }
  .pill__input { width: 100%; text-align: center; padding: 0.4rem 0; }
  .pill .btn { width: 100%; }
}

/* A placeholder is not a label — it vanishes the moment someone types
   and leaves a screen reader with an unnamed field. The label is
   present and hidden, not absent. */
.visually-hidden {
  position: absolute;
  width: 1px;
  height: 1px;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
}

.hero__lede {
  max-width: 31rem;
  margin: 1.5rem auto 2.2rem;
  font-size: var(--bg-size-body-lg);
  color: var(--p-text-body);
}

/* ---------- the countdown ----------
   Replaces a flat "Ships January", which read as a stray line under
   the buttons. Numerals here rather than words: this is a quantity you
   scan, which is the exception the voice rule allows.

   The markup ships the plain note as its content, and the script
   replaces it only once it has a date to count to. With JavaScript off
   the line still reads. */
.countdown {
  margin-top: 1.4rem;
  display: inline-flex;
  align-items: flex-start;
  gap: clamp(1rem, 2.5vw, 1.75rem);
  color: var(--p-text-body);
}

.countdown__unit {
  display: grid;
  justify-items: center;
  gap: 0.3rem;
  min-width: 3ch;
}

.countdown__n {
  font-family: var(--bg-font-display);
  font-size: clamp(1.5rem, 3vw, 2.1rem);
  line-height: 1;
  color: var(--p-text);
  /* Digits must not jitter as the count changes. */
  font-variant-numeric: tabular-nums;
}

.countdown__l {
  font-size: 0.5rem;
  letter-spacing: 0.34em;
  text-transform: uppercase;
  color: var(--p-text-hint);
}

.hero__note {
  margin-top: 1rem;
  font-size: var(--bg-size-small);
  color: var(--p-text-hint);
}

.hero__card {
  position: absolute;
  width: 100%;
  /* Not optional. The img carries width and height attributes so the
     browser can reserve the box before the file loads, but with width
     overridden by CSS and height left alone the attribute height wins
     and the card is squashed — 192x270 where the artwork is 192x288,
     about seven percent short. height:auto hands the ratio back to
     the image. */
  height: auto;
  border-radius: 0.8rem;
  box-shadow: 0 26px 60px rgba(15, 30, 22, 0.5);
}

/* Scattered, not arranged — but scattered by position and angle only.
   Four different sizes reads as inconsistent rather than casual, so
   every card is the same generous width and the irregularity comes
   from height, inset and rotation. No two rotations are the negative
   of each other, and the heights are staggered so the four do not
   occupy four corners, which is what made an earlier pass read as a
   rectangle around the headline.

   12rem is set against the channel, not chosen freely: the side
   columns run about 296px at 1440, and a 12rem card turned 17 degrees
   has a footprint of roughly 268px. Wider or more angled and it
   reaches the headline. */
.hero__card--l1 {
  top: 2%; left: 2%; max-width: 12rem; transform: rotate(-17deg);
}
/* Deliberately far below its opposite number. Two cards near the top
   and two near the bottom occupy four corners and read as a shape no
   matter how the angles vary — staggering the heights is what breaks
   it. */
.hero__card--r1 {
  top: 18%; right: 2%; max-width: 12rem; transform: rotate(9deg);
}
/* The lower pair clears the hero's own bottom padding before it buys
   any hang at all. Under that the card stops inside the section and
   the crossing never happens. They hang by different amounts on
   purpose — a matched pair is the thing that reads as arranged. */
.hero__card--l2 {
  bottom: -5rem; left: 10%; max-width: 12rem; transform: rotate(12deg);
}
.hero__card--r2 {
  bottom: -9rem; right: 6%; max-width: 12rem; transform: rotate(-7deg);
}

@media (max-width: 62rem) {
  /* Even padding, so the copy centres against the screen rather than
     sitting high above a reserved band. The band is gone: the cards
     now start below the fold and are met by scrolling, which is a
     better use of that space than holding it empty above them. */
  .hero__grid { grid-template-columns: 1fr; gap: 0; }
  .hero__side { position: static; min-height: 0; }
  .hero__card--l1, .hero__card--r1 { display: none; }
  /* Positioned to break the hero's lower edge rather than to sit
     inside it: their tops show at the fold and the rest arrives on
     scroll. A card cut by the fold resolves itself; a card cut by a
     section boundary never does. */
  .hero__card--l2 {
    width: 9.5rem; max-width: none; left: 5%; bottom: -8rem;
    transform: rotate(13deg);
  }
  .hero__card--r2 {
    width: 9.5rem; max-width: none; right: 6%; bottom: -10.5rem;
    transform: rotate(-7deg);
  }
}

@media (max-width: 30rem) {
  /* A rotated card's footprint is wider than the card, so the insets
     grow as the screen narrows rather than the cards shrinking. */
  .hero__card--l2 { width: 8rem; left: 8%; transform: rotate(11deg); }
  .hero__card--r2 { width: 8rem; right: 7%; }
}

/* ---------- the film ----------
   Inset and aligned to the hero grid rather than to a percentage: the
   frame's edges land where the outer cards sit, so the margin is a
   relationship instead of a guess. The forest runs around it and the
   page stays continuous instead of banded.

   The top padding is structural. The hero's lowest cards hang into it,
   and above 62rem it is deliberately shorter than that hang so a card
   comes to rest on the frame's top edge — the same crossing the hero
   makes, one section later. Trim it and the frame covers them. */

.film {
  position: relative;
  z-index: 0;
  /* Clearance for the hero's cards, which now hang up to 9rem past it
     on a phone. Above 62rem the pad is deliberately shorter than the
     hang so a card rests on the frame's top edge — that reads as
     intentional against an inset, rounded frame. Here the frame runs
     edge to edge with no corner to rest on, so the cards clear it.

     11.5rem, not the 9rem the offsets suggest: a rotated card's
     bounding box reaches lower than its geometric edge, and the
     furthest card lands 175px past the hero rather than the 144 the
     arithmetic gives. Measured, not derived. */
  padding-top: 11.5rem;
  padding-bottom: clamp(3rem, 7vw, 5rem);
}

@media (min-width: 62rem) { .film { padding-top: 4rem; } }

.film__frame {
  position: relative;
  overflow: hidden;
  width: calc(100% - var(--gutter) * 2);
  max-width: var(--wrap);
  margin-inline: auto;
  border: var(--bg-hairline-width) solid var(--p-hairline);
  border-radius: 1.5rem;
  aspect-ratio: 16 / 9;
  background:
    radial-gradient(120% 100% at 62% 30%, rgb(from var(--p-deen-talk) r g b / 0.3), transparent 62%),
    linear-gradient(155deg, var(--p-ground), var(--p-ground-deep));
}

@supports not (color: rgb(from #fff r g b / 50%)) {
  .film__frame { background: linear-gradient(155deg, var(--p-ground), var(--p-ground-deep)); }
}

/* Landscape footage letterboxed on a phone makes the two people in it
   thumbnail-sized. The shoot is briefed for both crops already. */
@media (max-width: 48rem) { .film__frame { aspect-ratio: 4 / 5; } }

/* Edge to edge on a phone. An inset only earns its margins when they
   line up with something, and at this width there is nothing to line
   up with. */
@media (max-width: 30rem) {
  .film__frame { width: 100%; border-radius: 0; border-inline: 0; }
}

.film__video {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
}

.film__slot {
  position: absolute;
  inset: 0;
  display: grid;
  place-content: center;
  justify-items: center;
  gap: 1rem;
  text-align: center;
  padding: var(--gutter);
}

.film__ring {
  width: clamp(4rem, 9vw, 5.5rem);
  aspect-ratio: 1;
  border-radius: var(--bg-radius-full);
  border: var(--bg-hairline-width) solid rgb(from var(--p-ink) r g b / 0.45);
  display: grid;
  place-items: center;
  color: var(--p-ink);
}

@supports not (color: rgb(from #fff r g b / 50%)) {
  .film__ring { border-color: rgba(248, 245, 240, 0.45); }
}

.film__ring span {
  font-size: 0.5rem;
  letter-spacing: 0.3em;
  text-transform: uppercase;
}

.film__cap {
  font-size: 0.625rem;
  letter-spacing: 0.24em;
  text-transform: uppercase;
  color: var(--p-text-hint);
  max-width: 22rem;
  margin: 0;
}

/* ---------- the decks ----------
   Both faces are real artwork, so the flip is the card turning over
   rather than a graphic imitating one. */

.decks { position: relative; z-index: 1; }

.decks__intro {
  max-width: 44rem;
  margin-inline: auto;
  padding: clamp(3rem, 7vw, 5rem) var(--gutter) clamp(2rem, 4vw, 3rem);
  text-align: center;
}

.decks__intro h2 { font-size: clamp(1.9rem, 4.6vw, 3.1rem); }
.decks__intro p { margin-inline: auto; color: var(--p-text-body); }

.decks__hint {
  font-size: var(--bg-size-label);
  letter-spacing: var(--bg-tracking-label);
  text-transform: uppercase;
  color: var(--p-text-hint);
  margin-top: 1.4rem;
}

.decks__row {
  max-width: var(--wrap);
  margin-inline: auto;
  padding: 0 var(--gutter) clamp(4rem, 9vw, 7rem);
  display: grid;
  gap: clamp(1.75rem, 3.5vw, 2.75rem);
  grid-template-columns: 1fr;
}

@media (min-width: 44rem) {
  .decks__row { grid-template-columns: repeat(3, 1fr); }
}

/* align-content:start, or a cell whose description runs to one line
   where its neighbours run to two spreads the slack into its card row
   and that card sits lower than the others. */
.decks__cell {
  display: grid;
  gap: 1rem;
  justify-items: center;
  text-align: center;
  align-content: start;
}

/* Cream, never the deck accent — see the note at the top of this
   block. The colour moves to the mark, which is decorative and owes
   nothing to contrast. */
.decks__name {
  margin: 0;
  font-size: 0.625rem;
  letter-spacing: 0.34em;
  text-transform: uppercase;
  color: var(--p-text);
  display: inline-flex;
  align-items: center;
  gap: 0.6rem;
}

.decks__name::before {
  content: "";
  width: 9px;
  height: 9px;
  border-radius: var(--bg-radius-full);
  background: var(--deck-accent, var(--p-accent));
  flex: none;
}

.decks__line {
  margin: 0;
  font-size: var(--bg-size-small);
  color: var(--p-text-body);
  max-width: 24ch;
}

.flip {
  appearance: none;
  border: 0;
  background: none;
  padding: 0;
  cursor: pointer;
  display: block;
  width: 100%;
  max-width: 15rem;
  margin-inline: auto;
  perspective: 1400px;
}

.flip__in {
  position: relative;
  display: block;
  width: 100%;
  aspect-ratio: 2 / 3;
  transform-style: preserve-3d;
  transition: transform calc(var(--bg-motion-duration) * 1.4) var(--bg-motion-ease);
}

.flip[aria-pressed="true"] .flip__in { transform: rotateY(180deg); }

.flip__face {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  display: block;
  backface-visibility: hidden;
  border-radius: 0.85rem;
  box-shadow: 0 26px 60px rgba(12, 20, 15, 0.45);
}

.flip__front { transform: rotateY(180deg); }

.flip:focus-visible {
  outline: 2px solid var(--p-accent);
  outline-offset: 6px;
  border-radius: 1rem;
}

/* ---------- the invitation ----------
   An affordance rather than an instruction. Telling someone to tap a
   card needs a line of small type that nobody reads; showing the card
   move says the same thing and says it about the object.

   On a pointer device the card lifts and turns a few degrees toward
   its other side — enough to reveal there is one. */
@media (hover: hover) {
  .flip:hover .flip__in { transform: translateY(-7px) rotateY(-13deg); }
  .flip[aria-pressed="true"]:hover .flip__in { transform: translateY(-7px) rotateY(193deg); }
}

/* And once, when the row first comes into view, each card turns
   slightly and settles back — staggered, so it reads as a deck rather
   than three separate animations. The class is removed when it ends,
   which hands the transform back to the rules above; leaving it would
   pin the cards and the flip would never fire. */
@keyframes deck-peek {
  0%, 100% { transform: none; }
  52% { transform: translateY(-6px) rotateY(-17deg); }
}

.decks__cell.is-inviting .flip__in {
  animation: deck-peek 1.6s var(--bg-motion-ease) both;
}

.decks__cell.is-inviting:nth-child(2) .flip__in { animation-delay: 0.16s; }
.decks__cell.is-inviting:nth-child(3) .flip__in { animation-delay: 0.32s; }

@media (prefers-reduced-motion: reduce) {
  .decks__cell.is-inviting .flip__in { animation: none; }
  .flip:hover .flip__in { transform: none; }
  .flip[aria-pressed="true"]:hover .flip__in { transform: rotateY(180deg); }
}

/* ---------- the nightly ritual ----------
   One sequence, not a layout: the die rolls in, lands, and the line
   draws out from where it stopped, revealing each beat as it passes.

   The resting state below IS the finished state. ritual.js adds
   .can-animate before hiding anything, so with no script, reduced
   motion, or a run that never fires, the section is simply readable.
   Nothing here is load-bearing for the content. */

.ritual {
  position: relative;
  z-index: 1;
  padding: clamp(3.5rem, 8vw, 6rem) var(--gutter);
}

/* The head shares the track's container so its left edge lines up
   with the curve's, rather than starting at the page gutter — the
   track is capped at 76rem and centred, so at a wide window the
   headline was sitting a long way outside it. */
.ritual__head {
  max-width: var(--wrap);
  margin-inline: auto;
  margin-bottom: clamp(2rem, 5vw, 3rem);
}

.ritual__head > * { max-width: 34rem; }

.ritual__head h2 { margin: 0; font-size: clamp(2rem, 5vw, 3.4rem); }
.ritual__head .label { margin: 0 0 0.9rem; }

.ritual__track {
  position: relative;
  max-width: var(--wrap);
  margin-inline: auto;
  min-height: 22rem;
}

/* ---- the die ---- */

.ritual__stage {
  position: absolute;
  left: 0;
  top: 0;
  width: 7rem;
  height: 7rem;
  perspective: 1100px;
  z-index: 3;
}

.die {
  /* Half the cube's edge. The faces sit on its surface, so this MUST
     track the width — a fixed value takes the cube apart at any other
     size, which is exactly what happened at the phone scale. */
  --half: 3.5rem;
  width: 7rem;
  height: 7rem;
  position: relative;
  transform-style: preserve-3d;
  transform: rotateX(-22deg) rotateY(24deg);
}

/* One body colour with the icons printed on it. inset:-1px so the
   faces overlap at the seams, and a small radius so the corners do not
   open up — at a larger radius the ground shows through every edge and
   the die reads as six loose tiles rather than one object. */
.die__face {
  position: absolute;
  inset: -1px;
  border-radius: 0.55rem;
  display: grid;
  place-items: center;
  padding: 22%;
  background: #F5EFE6;
  box-shadow: 0 14px 30px rgba(12, 20, 15, 0.35);
}

.die__face svg { width: 100%; height: 100%; display: block; }

.die__face--1 { transform: translateZ(var(--half)); }
.die__face--2 { transform: rotateY(90deg) translateZ(var(--half)); }
.die__face--3 { transform: rotateY(180deg) translateZ(var(--half)); }
.die__face--4 { transform: rotateY(-90deg) translateZ(var(--half)); }
.die__face--5 { transform: rotateX(90deg) translateZ(var(--half)); }
.die__face--6 { transform: rotateX(-90deg) translateZ(var(--half)); }

/* Tied to the die's height by script, so it tightens as the die drops
   and spreads as it rises. */
.die__shadow {
  position: absolute;
  left: 0;
  top: 0;
  width: 7rem;
  height: 1.6rem;
  border-radius: 50%;
  background: rgba(12, 20, 15, 0.38);
  filter: blur(9px);
  z-index: 2;
  pointer-events: none;
  opacity: 0;
}

/* ---- the line ---- */

.ritual__line {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  overflow: visible;
  z-index: 1;
}

.ritual__line path {
  fill: none;
  stroke: var(--p-accent);
  stroke-width: 1.6;
  stroke-linecap: round;
  vector-effect: non-scaling-stroke;
}

/* The phone version is the same curve transposed, not a second
   drawing — the mark turned on its side. */
.ritual__line--v { display: none; }

/* ---- the beats ---- */

.beats { list-style: none; margin: 0; padding: 0; }

.beat {
  position: absolute;
  width: 15rem;
  z-index: 2;
  transform: translateY(-100%);
  padding-bottom: 1.1rem;
}

.beat__dot {
  position: absolute;
  left: 0;
  bottom: 0;
  width: 13px;
  height: 13px;
  border-radius: var(--bg-radius-full);
  background: var(--p-accent);
  transform: translate(-50%, 50%);
  box-shadow: 0 0 0 5px var(--p-ground);
}

.beat h3 {
  margin: 0.3rem 0 0.35rem;
  font-family: var(--bg-font-display);
  font-weight: var(--bg-weight-display);
  font-size: clamp(1.15rem, 2.2vw, 1.5rem);
  line-height: 1.15;
  color: var(--p-text);
}

.beat p {
  margin: 0;
  color: var(--p-text-body);
  font-size: var(--bg-size-small);
  max-width: 24ch;
}

/* ---- the run ----
   Hidden only once the script has confirmed it can bring them back.
   Without .can-animate the line and beats simply show, so the failure
   mode is "no animation", never "no content". */

.ritual.can-animate .ritual__line { clip-path: inset(0 100% 0 0); }
.ritual.can-animate .beat { opacity: 0; }
.ritual.can-animate .die,
.ritual.can-animate .die__shadow { opacity: 0; }
.ritual.is-rolling .die { opacity: 1; }

/* A clip sweep, not a dash offset. pathLength normalises a dash, but
   this path is stretched non-uniformly AND carries non-scaling-stroke,
   and together those leave the dash computed in a different space from
   the rendered path — the tail never gets covered. The path is
   monotonic, so a clip looks identical with no maths to get wrong. */
@keyframes ritual-line {
  from { clip-path: inset(0 100% 0 0); }
  to   { clip-path: inset(0 0 0 0); }
}

@keyframes ritual-line-v {
  from { clip-path: inset(0 0 100% 0); }
  to   { clip-path: inset(0 0 0 0); }
}

@keyframes ritual-beat {
  from { opacity: 0; transform: translateY(calc(-100% + 10px)); }
  to   { opacity: 1; transform: translateY(-100%); }
}

.ritual.is-playing .ritual__line {
  animation: ritual-line var(--line-dur, 2.2s) cubic-bezier(0.4, 0, 0.2, 1) forwards;
}

.ritual.is-playing .beat {
  animation: ritual-beat 0.55s var(--bg-motion-ease) var(--in) forwards;
}

@media (prefers-reduced-motion: reduce) {
  .ritual.can-animate .ritual__line,
  .ritual.can-animate .ritual__line--v,
  .ritual.can-animate .beat,
  .ritual.can-animate .die,
  .ritual.can-animate .die__shadow { clip-path: none; opacity: 1; }
  .ritual.is-playing .ritual__line,
  .ritual.is-playing .ritual__line--v,
  .ritual.is-playing .beat { animation: none; }
}

/* ---- phones ----
   The idea survives, turned ninety degrees: the die still rolls, the
   line still carries the sequence, the beats still arrive as it
   reaches them. It just runs down the screen. Dropping all of it here
   would throw the section away for most of the traffic. */
@media (max-width: 52rem) {
  /* The die rests at the curve's start, which is the very top of the
     track, so the track needs headroom or it sits under the headline. */
  .ritual__track { min-height: 34rem; margin-top: 3.5rem; }

  .ritual__line { display: none; }
  .ritual__line--v { display: block; left: 0; width: 5rem; }

  .ritual__stage { width: 5rem; height: 5rem; }
  .die { --half: 2.5rem; width: 5rem; height: 5rem; }
  .die__face { padding: 20%; }
  .die__shadow { display: block; width: 5rem; height: 1.1rem; }

  .beat {
    width: auto;
    right: 0;
    left: 4.5rem !important;
    transform: none;
    padding-bottom: 0;
  }

  /* left comes from the script: the text column is fixed but the curve
     wanders, so each dot is measured onto it. Its 0.5rem drop keeps it
     level with the heading, and the script shifts the whole beat up by
     that much so the dot still lands on the curve — on a steep stretch
     even fourteen pixels of drop reads as a horizontal miss. */
  .beat__dot { bottom: auto; top: 0.5rem; transform: translate(-50%, 0); }
  .beat p { max-width: none; }

  .ritual.can-animate .ritual__line--v { clip-path: inset(0 0 100% 0); }
  .ritual.is-playing .ritual__line--v {
    animation: ritual-line-v var(--line-dur, 2.2s) cubic-bezier(0.4, 0, 0.2, 1) forwards;
  }

  @keyframes ritual-beat {
    from { opacity: 0; transform: translateY(10px); }
    to   { opacity: 1; transform: none; }
  }
}

/* =============================================================
   Home 5 · The app.

   THE ROWS carry every fact. The frame beside them carries none.
   Nothing may move out of the rows and into the phone.

   THE FRAME has two states and one appearance: with no video it
   draws the year screen, and with a video that same screen is the
   poster. Reduced motion, a cold cache and a file that never
   uploads therefore all land on the right image, and adding the
   footage later is a settings change rather than a deploy.

   It never gets controls or a play button. The film section above
   is a player; this is a phone left running. If both read as video
   embeds the second one reads as filler.
   ============================================================= */

/* No ground of its own. Every other section on the page sits on
   --p-ground and a single deeper band read as a panel dropped into the
   page rather than as depth within it. The frame's interior stays
   --p-ground because that is the app's actual screen colour, so the
   phone is now held by its hairline alone — which is the site's own
   language anyway. */
.app {
  padding-block: clamp(3.5rem, 8vw, 6rem);
  padding-inline: var(--gutter);
}

.app__in {
  max-width: var(--wrap);
  margin-inline: auto;
  display: grid;
  gap: clamp(2.5rem, 6vw, 4rem);
  grid-template-columns: minmax(0, 1fr);
  align-items: center;
}

@media (min-width: 60rem) {
  .app__in { grid-template-columns: minmax(0, 1fr) minmax(0, 20rem); }
}

.app__say h2 { margin: 0; max-width: 18ch; }

.app__lede {
  margin-top: var(--bg-space-md);
  max-width: 34ch;
  color: var(--p-text-body);
  font-size: var(--bg-size-body-lg);
}

.app__lede p { margin: 0; }

.app__rows {
  list-style: none;
  margin: var(--bg-space-lg) 0 0;
  padding: 0;
  border-top: var(--bg-hairline-width) solid var(--p-hairline);
}

.app__rows li {
  padding: 1.15rem 0;
  border-bottom: var(--bg-hairline-width) solid var(--p-hairline);
  display: grid;
  grid-template-columns: auto minmax(0, 1fr);
  gap: 1.1rem;
  align-items: baseline;
}

.app__rows b {
  font-family: var(--bg-font-display);
  font-weight: 400;
  font-size: 1.05rem;
  color: var(--p-text);
}

.app__rows span { color: var(--p-text-body); font-size: 0.9375rem; }

.app__side { display: grid; gap: 1.4rem; justify-items: center; }

.app__note {
  margin: 0;
  max-width: 22ch;
  text-align: center;
  font-size: 0.8125rem;
  line-height: 1.5;
  color: var(--p-text-hint);
}

/* ---- the frame ----
   No notch, no buttons, no chrome beyond a hairline and a radius. A
   drawn device would be the largest rectangle on a site that has
   almost none of them. */
.dev {
  position: relative;
  width: 100%;
  max-width: 17rem;
  aspect-ratio: 9 / 19.5;
  /* The hairline is the device edge, not part of the screen. As a border
     it would shrink the content box and every percentage inside — the
     grid lands at 80.65% of the frame instead of the app's 81.25%. */
  box-shadow: inset 0 0 0 var(--bg-hairline-width) var(--p-hairline);
  border-radius: 2.25rem;
  overflow: hidden;
  background: var(--p-ground);
  display: grid;
  container-type: inline-size;
}

.dev > * { grid-area: 1 / 1; }
.dev__video { width: 100%; height: 100%; object-fit: cover; display: block; }

/* ---- the year screen ----
   Every size is the app's own afs(n) = n x W / 320, so the screen is
   a container query rather than a set of fixed pixels: it stays exact
   at any frame width instead of at one. The fallback is the rule
   solved at the frame's own 17rem, which is its width nearly always. */
.scr {
  --afs: 0.85px;
  --scr-empty: var(--p-hairline);
  --scr-ring: var(--p-text-hint);
  padding: calc(var(--afs) * 34) 9.375% 0;
  overflow: hidden;
  font-family: var(--bg-font-body);
  font-weight: 400;
}

@supports (width: 1cqw) { .scr { --afs: 0.3125cqw; } }

@supports (color: color-mix(in srgb, red, blue)) {
  .scr {
    --scr-empty: color-mix(in srgb, var(--p-ink) 18%, transparent);
    --scr-ring:  color-mix(in srgb, var(--p-ink) 42%, transparent);
  }
}

/* One class of specificity, exactly. Higher and it outranks .scr__head
   and .scr__ms and eats their margins; at zero it loses to the plain
   `p` rule further up this file, whose margin-bottom then collapses
   against them and wins on size. The spec's vertical rhythm depends on
   this landing between the two. */
.scr :where(p, h3, ul) { margin: 0; }

.scr__eyebrow {
  font-size: calc(var(--afs) * 9);
  letter-spacing: 0.42em;
  text-transform: uppercase;
  color: var(--p-label);
  line-height: 1.2;
}

.scr__eyebrow--b { margin-top: calc(var(--afs) * 34); }

.scr__head {
  font-family: var(--bg-font-display);
  font-weight: 400;
  color: var(--p-text);
  font-size: calc(var(--afs) * 28);
  line-height: calc(var(--afs) * 33);
  margin-top: calc(var(--afs) * 14);
}

.scr__stats {
  margin-top: calc(var(--afs) * 28);
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  align-items: end;
}

.scr__stats > * { display: grid; gap: calc(var(--afs) * 7); }
.scr__stats > *:nth-child(2) { justify-items: center; }
.scr__stats > *:nth-child(3) { justify-items: end; }

.scr__v {
  font-family: var(--bg-font-display);
  font-size: calc(var(--afs) * 30);
  line-height: 1.2;
  color: var(--p-text);
  font-variant-numeric: tabular-nums;
}

/* Only KEPT carries a denominator, and it is a smaller, dimmer span —
   not the same size as the number in front of it. */
.scr__v em { font-style: normal; font-size: calc(var(--afs) * 17); opacity: 0.45; }

.scr__l {
  font-size: calc(var(--afs) * 8.5);
  letter-spacing: 0.306em;
  text-transform: uppercase;
  color: var(--p-text);
  opacity: 0.5;
  line-height: 1.2;
}

/* Tracking adds a trailing space after the last letter, which reads as
   a misalignment on anything set flush right. */
.scr__stats > *:nth-child(3) .scr__l { margin-right: -0.306em; }

.scr__rule { display: block; height: var(--bg-hairline-width); background: var(--p-hairline); }
.scr__rule--a { margin-top: calc(var(--afs) * 26); }
.scr__rule--b { margin-top: calc(var(--afs) * 28); }

/* ---- the grid ----
   Nineteen columns, no gutter, the dot at 49% of a square cell. A small
   circle plus a gap is a different shape and it shows at the edges. */
.scr__grid {
  margin-top: calc(var(--afs) * 30);
  display: grid;
  grid-template-columns: repeat(19, 1fr);
}

.scr__grid span { aspect-ratio: 1; display: grid; place-items: center; position: relative; }
.scr__grid i { width: 49%; aspect-ratio: 1; border-radius: 999px; display: block; opacity: 0.95; }

.scr__grid .d-o i { background: var(--p-open-book); }
.scr__grid .d-d i { background: var(--p-deen-talk); }
.scr__grid .d-q i { background: var(--p-quick-connect); }
.scr__grid .d-r i { background: var(--p-ramadan); }

.scr__grid .is-empty i {
  border: var(--bg-hairline-width) solid var(--scr-empty);
  opacity: 1;
}

/* The ring is drawn only where there is one to draw — a pseudo-element
   on all 365 cells is 365 boxes to lay out for the eight that show. */
.scr__grid .is-ms::after,
.scr__grid .is-today::after {
  content: "";
  position: absolute;
  width: 92%;
  aspect-ratio: 1;
  border-radius: 999px;
  border: var(--bg-hairline-width) solid var(--scr-ring);
}

.scr__grid .is-today::after {
  border-color: var(--p-accent);
  animation: scr-pulse 2.4s ease-in-out infinite;
}

@keyframes scr-pulse { 0%, 100% { opacity: 0.4; } 50% { opacity: 0.9; } }

.scr__legend {
  margin-top: calc(var(--afs) * 26);
  display: flex;
  justify-content: center;
  gap: calc(var(--afs) * 26);
}

.scr__legend span {
  display: flex;
  align-items: center;
  gap: calc(var(--afs) * 6);
  font-size: calc(var(--afs) * 12);
  font-weight: 300;
  color: var(--p-text);
  opacity: 0.7;
  font-variant-numeric: tabular-nums;
}

.scr__legend i {
  width: calc(var(--afs) * 12);
  height: calc(var(--afs) * 12);
  border-radius: 999px;
  border: calc(var(--afs) * 1.3) solid currentColor;
  display: block;
  flex: none;
}

.scr__legend .l-o i { color: var(--p-open-book); }
.scr__legend .l-d i { color: var(--p-deen-talk); }
.scr__legend .l-q i { color: var(--p-quick-connect); }
.scr__legend .l-r i { color: var(--p-ramadan); }

.scr__ms { list-style: none; padding: 0; margin-top: calc(var(--afs) * 14); }

.scr__ms li {
  border-top: var(--bg-hairline-width) solid var(--p-hairline);
  padding: calc(var(--afs) * 13) 0;
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto;
  align-items: center;
  gap: calc(var(--afs) * 10);
}

.scr__ms b {
  font-family: var(--bg-font-display);
  font-weight: 400;
  font-size: calc(var(--afs) * 15.5);
  color: var(--p-text);
  display: block;
}

.scr__ms .day {
  display: block;
  margin-top: calc(var(--afs) * 3);
  font-size: calc(var(--afs) * 8.5);
  letter-spacing: 0.306em;
  text-transform: uppercase;
  color: var(--p-text);
  opacity: 0.42;
}

.scr__ms .st {
  font-size: calc(var(--afs) * 10.5);
  font-weight: 300;
  color: var(--p-text);
  opacity: 0.55;
}

@media (prefers-reduced-motion: reduce) {
  .scr__grid .is-today::after { animation: none; opacity: 0.9; }
}

/* ---- the loop ----
   Three screens, one frame: the year, tonight's card, the night
   sealing, and back to the year one dot fuller.

   The resting state is the year screen alone and finished. Everything
   else is display:none until assets/app.js has proved it can cycle and
   added .can-cycle, so no script, reduced motion, a frame that never
   scrolls into view, or a run that dies halfway all leave a still of
   the same screen that shipped before the loop existed. Nothing here
   is allowed to hide content it cannot bring back. */
.dev .scr--play,
.dev .scr--seal { display: none; }

.dev.can-cycle .scr {
  display: block;
  opacity: 0;
  transform: translateY(var(--bg-motion-rise));
  transition: opacity var(--bg-motion-duration) var(--bg-motion-ease),
              transform var(--bg-motion-duration) var(--bg-motion-ease);
}

.dev.can-cycle .scr.is-on { opacity: 1; transform: none; }

/* ---- screen two · the nightly card ----
   One centred block, one action. Ground only.

   The block centres against the frame, not against the space left over
   above the action — as a 1fr row it lands half the action's height too
   high, which on a screen this sparse is the only thing to look at. */
.scr--play {
  position: relative;
  padding-bottom: calc(var(--afs) * 34);
}

.dev.can-cycle .scr--play { display: grid; place-content: center; }

.play__act {
  position: absolute;
  right: 9.375%;
  bottom: calc(var(--afs) * 34);
}

.play__head {
  font-family: var(--bg-font-display);
  font-weight: 400;
  color: var(--p-text);
  font-size: calc(var(--afs) * 29);
  line-height: 1.18;
  margin-top: calc(var(--afs) * 22);
}

.play__body {
  font-size: calc(var(--afs) * 13);
  font-weight: 300;
  line-height: 1.5;
  color: var(--p-text);
  opacity: 0.68;
  margin-top: calc(var(--afs) * 18);
}

.play__act {
  display: flex;
  align-items: center;
  gap: calc(var(--afs) * 10);
}

.play__act span {
  font-size: calc(var(--afs) * 8.5);
  letter-spacing: 0.306em;
  text-transform: uppercase;
  color: var(--p-text);
  opacity: 0.62;
}

.play__act i {
  width: calc(var(--afs) * 30);
  height: calc(var(--afs) * 30);
  border-radius: 999px;
  border: var(--bg-hairline-width) solid var(--p-hairline);
  display: grid;
  place-items: center;
  flex: none;
  color: var(--p-text);
}

.play__act svg { width: 52%; height: 52%; display: block; }

/* ---- screen three · the seal ----
   The base screen pads 34 at the top and nothing at the bottom, which
   is right for a screen that fills downward and puts a centred disc
   half the padding low. */
.scr--seal { padding-bottom: calc(var(--afs) * 34); }
.dev.can-cycle .scr--seal { display: grid; place-content: center; }

.seal {
  width: calc(var(--afs) * 88);
  height: calc(var(--afs) * 88);
  border-radius: 999px;
  background: var(--p-open-book);
  display: grid;
  place-items: center;
}

/* 0.38 of the seal, per the spec — the glyph is struck into the disc,
   not centred artwork sitting on it. */
.seal svg { width: 38%; height: 38%; display: block; }

/* The disc lands and the glyph settles a fraction behind it, so it
   presses rather than appears. No overshoot on either: the app has a
   little, and nothing on this site bounces. */
.dev.can-cycle .scr--seal.is-on .seal {
  animation: seal-press var(--bg-motion-duration) var(--bg-motion-ease) both;
}

.dev.can-cycle .scr--seal.is-on .seal svg {
  animation: seal-mark var(--bg-motion-duration) 90ms var(--bg-motion-ease) both;
}

@keyframes seal-press { from { transform: scale(0.55); } to { transform: none; } }
@keyframes seal-mark  { from { transform: scale(1.18); opacity: 0; } to { transform: none; opacity: 1; } }

@media (prefers-reduced-motion: reduce) {
  .dev.can-cycle .scr { transition: none; }
  .dev.can-cycle .scr--seal.is-on .seal,
  .dev.can-cycle .scr--seal.is-on .seal svg { animation: none; }
}

/* =============================================================
   Home 6 · The close.

   The second ask on the page, and the last thing on it.
   ============================================================= */

.close {
  padding-block: clamp(3.5rem, 8vw, 5.5rem) clamp(3rem, 7vw, 5rem);
  padding-inline: var(--gutter);
}

.close__in { max-width: var(--wrap); margin-inline: auto; text-align: center; }

.close__in h2 {
  margin: 0 auto;
  max-width: 14ch;
  font-size: clamp(2.1rem, 5.2vw, 3.6rem);
}

/* The wrap is 82rem so sections share a left edge, but a curve run to
   that width sits over a 30rem column of text and reads as a stray
   rule rather than a mark belonging to it. Measured dead centre either
   way — this is about what it relates to, not where it sits. */
.close__line {
  max-width: 34rem;
  margin-bottom: clamp(1.5rem, 4vw, 2.5rem);
}

/* The resting state is drawn. close.js rewinds it only once it has
   proved it can play it forward — the animation on the shared .line
   fires at load, which at the foot of the page nobody is present for. */
.close__line path { animation: none; stroke-dashoffset: 0; }

.close.can-draw .close__line path { stroke-dashoffset: 1; }

.close.is-drawn .close__line path {
  animation: line-draw calc(var(--bg-motion-duration) * 2.5)
    var(--bg-motion-ease) both;
}

.close__lede {
  margin: 1.4rem auto 2.2rem;
  max-width: 34ch;
  color: var(--p-text-body);
  font-size: var(--bg-size-body-lg);
}

.close__lede p { margin: 0; }

/* The wrap is 82rem so the line can run the width of the page. The
   pill must not: unconstrained it becomes a 1312px slot for an email
   address, which reads as a search bar rather than the one thing the
   page is asking for. */
.close .pill { max-width: 30rem; margin-inline: auto; }

/* margin-inline auto, not 0. Every p inherits max-width: 34rem from
   the measure, so inside an 82rem wrap the box sits flush left and
   text-align only centres the words inside it — the note was 768px
   off, which is what made it look uncentred.

   No optical compensation for the trailing letter-space. The usual
   fix is a padding-left of one tracking unit; measured, the glyph run
   is already dead centre without it and the padding pushes it 1.8px
   right. */
.close__note {
  margin: 1.6rem auto 0;
  font-size: var(--bg-size-label);
  letter-spacing: var(--bg-tracking-label);
  text-transform: uppercase;
  color: var(--p-text-hint);
}

/* =============================================================
   /app — the QR destination, and the page the nav points at.

   Two visitors with opposite needs. The scanner has just opened a box
   they paid for and is thirty seconds from either starting their year
   or giving up; they need the download and never scroll. The browser
   came from the nav, owns nothing, and wants the story. The top of the
   page is the download and everything below it is the story, so
   neither is compromised for the other.

   Ported from the approved mockup: its type scale, spacing and
   breakpoints, reading colour from the token layer rather than the
   inline block the mockup carried so it could run standalone.

   Everything is prefixed .ap- because this page runs a wider measure
   and a larger display scale than the homepage, and the two must not
   leak into each other.
   ============================================================= */

.ap {
  padding-block: clamp(4rem, 9vw, 7.5rem);
}

.ap-wrap {
  width: 100%;
  max-width: 70rem;
  margin-inline: auto;
  padding-inline: clamp(1.25rem, 4vw, 2.5rem);
}

.ap-narrow { max-width: 40rem; margin-inline: auto; }
.ap-center { text-align: center; }
.ap-stack { display: flex; flex-direction: column; gap: 1.25rem; }
.ap-stack--sm { display: flex; flex-direction: column; gap: 0.625rem; }

/* This page's display scale is larger than the homepage's — the
   headline is the whole hero here, where the homepage's shares the
   frame with cards. */
.ap h1,
.ap h2,
.ap h3 {
  font-family: var(--bg-font-display);
  font-weight: var(--bg-weight-display);
  line-height: 1.14;
  letter-spacing: -0.012em;
  color: var(--p-text);
  margin: 0;
  text-wrap: balance;
}

.ap h1 { font-size: clamp(2.5rem, 6vw, 4.25rem); }
.ap h2 { font-size: clamp(1.875rem, 4.2vw, 3rem); }
.ap h3 { font-size: clamp(1.125rem, 2vw, 1.5rem); }

/* Gold italic on the second half of a headline. There is no italic
   Playfair in assets/ — the browser synthesises the oblique, which is
   the same compromise the homepage hero already makes. */
.ap em { font-style: italic; color: var(--p-label); }

/* margin-block, not margin. tokens.css caps every p at the measure and
   its shorthand zeroes the inline margins along with the bottom one —
   so a capped paragraph inside a centred block sits flush left and
   text-align only centres the words inside the box. Resetting the
   block margins alone leaves the inline pair free for the rule below,
   which is what actually centres them. Both rules are one class deep,
   so a shorthand here silently wins on source order.

   :where() so the reset carries no specificity of its own. As a plain
   descendant selector it outranks every per-paragraph rule further
   down this file and quietly ate their margins — the film's closing
   line and the privacy link both lost theirs to it. It still beats the
   bare p in tokens.css, which is all it needs to do. */
.ap :where(p) { margin-block: 0; }

/* Centred blocks, and the two paragraphs that carry .ap-center
   themselves rather than containing it. */
.ap-center p,
p.ap-center { margin-inline: auto; }

/* The measures the mockup set inline on each lede. Without them every
   paragraph falls back to the 34rem global cap, which is close enough
   to look deliberate and wrong enough to change where lines break. */
.ap-pairing__lede p,
.ap-features__lede p,
.ap-screens__lede p { max-width: 32rem; }
.ap-year__lede p { max-width: 30rem; }
.ap-year__caption { max-width: 28rem; }
.ap-film__lede p,
.ap-privacy__body p { max-width: 34rem; }
.ap .lede { font-size: clamp(1rem, 1.5vw, 1.1875rem); color: var(--p-text-body); }
.ap .hint { color: var(--p-text-hint); font-size: 0.9375rem; }

/* ---------- the download ---------- */

.ap-hero {
  position: relative;
  overflow: hidden;
  padding-block: clamp(2rem, 5vw, 4rem) clamp(3.5rem, 7vw, 5.5rem);
}

.ap-hero__in {
  position: relative;
  z-index: 2;
  max-width: 34rem;
  margin-inline: auto;
  text-align: center;
  display: flex;
  flex-direction: column;
  gap: 1.375rem;
  align-items: center;
}

/* The ring, framed two ways from one drawing.

   Below 960 it is cropped to a shallow arc above the headline — a full
   ring fights the text at phone widths. At 960 and up the same drawing
   becomes a halo with the content inside it. */
.ap-ring {
  position: relative;
  height: clamp(74px, 20vw, 120px);
  overflow: hidden;
  margin-bottom: clamp(1.5rem, 4vw, 2.25rem);
}

.ap-ring .yr__svg {
  position: absolute;
  left: 50%;
  top: 0;
  width: min(210vw, 1100px);
  transform: translateX(-50%);
  display: block;
}

.yr__empty {
  fill: none;
  stroke: var(--ap-hollow, rgba(248, 245, 240, 0.2));
  stroke-width: 1;
}

.yr__kept { stroke: none; opacity: 0.85; }
.yr__kept--open-book { fill: var(--p-open-book); }
.yr__kept--deen-talk { fill: var(--p-deen-talk); }
.yr__kept--quick-connect { fill: var(--p-quick-connect); }
.yr__kept--ramadan { fill: var(--p-ramadan); }

@supports (color: color-mix(in srgb, red, blue)) {
  .ap-hero { --ap-hollow: color-mix(in srgb, var(--p-ink) 20%, transparent); }
}

@media (min-width: 960px) {
  .ap-hero {
    min-height: min(84vh, 860px);
    display: grid;
    place-items: center;
  }

  .ap-ring {
    position: absolute;
    left: 50%;
    top: 50%;
    width: min(78vh, 820px);
    height: auto;
    aspect-ratio: 1;
    transform: translate(-50%, -50%);
    margin: 0;
    overflow: visible;
    z-index: 0;
    pointer-events: none;
  }

  .ap-ring .yr__svg {
    position: static;
    width: 100%;
    height: 100%;
    transform: none;
  }

  .ap-hero__in { max-width: 30rem; }
}

.ap-actions {
  display: flex;
  gap: 0.75rem;
  flex-wrap: wrap;
  justify-content: center;
}

/* The quiet twin of the primary action, for the second store on
   desktop. Same geometry, no fill. */
.ap-btn--quiet {
  background: none;
  color: var(--p-text);
  box-shadow: inset 0 0 0 var(--bg-hairline-width) var(--p-hairline);
}

.ap-btn--quiet:hover {
  box-shadow: inset 0 0 0 var(--bg-hairline-width) var(--p-text-hint);
  filter: none;
}

/* Universal Links do not fire inside the Instagram and Gmail in-app
   browsers, so a real share of visitors land here with the app already
   installed. One recessive line rescues them. */
.ap-recessive {
  font-size: 0.9375rem;
  color: var(--p-text-hint);
  text-decoration: none;
  border-bottom: var(--bg-hairline-width) solid var(--p-hairline);
  padding-bottom: 2px;
}

.ap-recessive:hover { color: var(--p-text-body); border-bottom-color: var(--p-text-hint); }

.ap-hero .waitlist { width: 100%; }
.ap-hero .pill { max-width: 26rem; margin-inline: auto; }
.ap-note { max-width: 28rem; }

/* ---------- pairing ---------- */

.ap-three { display: grid; gap: 2rem; grid-template-columns: 1fr; }

@media (min-width: 760px) {
  .ap-three { grid-template-columns: repeat(3, 1fr); gap: 2.5rem; }
}

/* Hairline-topped steps, not a numbered wizard — a wizard reads as
   instructions for a difficult thing, and this is not difficult. */
.ap-step {
  display: flex;
  flex-direction: column;
  gap: 0.625rem;
  padding-top: 1.25rem;
  border-top: var(--bg-hairline-width) solid var(--p-hairline);
}

.ap-step .ap-n {
  font-family: var(--bg-font-display);
  font-style: italic;
  color: var(--p-label);
  font-size: 1.125rem;
}

.ap-step p { font-size: 0.9375rem; }

/* ---------- what it does ---------- */

.ap-defs { display: grid; gap: 0 3rem; grid-template-columns: 1fr; }

@media (min-width: 820px) {
  .ap-defs { grid-template-columns: 1fr 1fr; }
}

.ap-def {
  display: flex;
  flex-direction: column;
  gap: 0.25rem;
  padding-block: 1.125rem;
  border-bottom: var(--bg-hairline-width) solid var(--p-hairline);
}

.ap-def dt { font-family: var(--bg-font-display); font-size: 1.0625rem; color: var(--p-text); }
.ap-def dd { margin: 0; font-size: 0.9375rem; color: var(--p-text-body); }

/* ---------- the year ---------- */

.ap-split { display: grid; gap: 2.5rem; grid-template-columns: 1fr; align-items: start; }

@media (min-width: 900px) {
  .ap-split { grid-template-columns: 1fr 1fr; gap: 4rem; }
}

.ap-panel {
  border: var(--bg-hairline-width) solid var(--p-hairline);
  border-radius: 22px;
  background: var(--p-ground-deep);
  padding: clamp(1.25rem, 2.5vw, 1.875rem);
  display: flex;
  flex-direction: column;
  gap: 1.5rem;
}

.ap-stats { display: flex; gap: 1rem; justify-content: space-between; flex-wrap: wrap; }

.ap-stat b {
  font-family: var(--bg-font-display);
  font-weight: var(--bg-weight-display);
  font-size: clamp(1.5rem, 2.6vw, 1.875rem);
  color: var(--p-text);
  font-variant-numeric: tabular-nums;
  line-height: 1;
  display: block;
}

.ap-stat b i { font-style: normal; font-size: 0.9375rem; color: var(--p-text-hint); }
.ap-stat .label { font-size: 0.5rem; margin-top: 0.375rem; }

.ap-rule { height: var(--bg-hairline-width); background: var(--p-hairline); border: 0; margin: 0; }

.ap-grid { display: grid; grid-template-columns: repeat(19, 1fr); gap: clamp(3px, 0.55vw, 6px); }
.ap-dot { width: 100%; aspect-ratio: 1; border-radius: var(--bg-radius-full); }
.ap-dot--o { background: var(--p-open-book); }
.ap-dot--d { background: var(--p-deen-talk); }
.ap-dot--q { background: var(--p-quick-connect); }
.ap-dot--r { background: var(--p-ramadan); }
.ap-dot--empty { background: none; border: var(--bg-hairline-width) solid var(--ap-hollow, rgba(248, 245, 240, 0.15)); }

.ap-legend { display: flex; gap: 1.125rem; flex-wrap: wrap; }

.ap-leg {
  display: flex;
  align-items: center;
  gap: 0.4375rem;
  font-size: 0.8125rem;
  color: var(--p-text-body);
  font-variant-numeric: tabular-nums;
}

.ap-leg i { width: 9px; height: 9px; border-radius: var(--bg-radius-full); display: block; }

@supports (color: color-mix(in srgb, red, blue)) {
  .ap-panel { --ap-hollow: color-mix(in srgb, var(--p-ink) 15%, transparent); }
}

/* ---------- the film ---------- */

.ap-film {
  background: var(--p-ground-deep);
  border-block: var(--bg-hairline-width) solid var(--p-hairline);
}

/* Sealed frames as discs, one of them opening. Masked at both edges so
   the reel reads as a strip continuing past the page rather than a row
   of twenty-five things. */
.ap-reel {
  display: flex;
  gap: 0.5rem;
  justify-content: center;
  flex-wrap: nowrap;
  overflow: hidden;
  margin-block: clamp(1.5rem, 4vw, 2.25rem);
  -webkit-mask-image: linear-gradient(90deg, transparent, #000 14%, #000 86%, transparent);
  mask-image: linear-gradient(90deg, transparent, #000 14%, #000 86%, transparent);
}

.ap-frame {
  width: clamp(20px, 3.2vw, 34px);
  aspect-ratio: 1;
  border-radius: var(--bg-radius-full);
  flex: 0 0 auto;
  opacity: 0.9;
}

.ap-frame--open { outline: 2px solid var(--p-accent); outline-offset: 3px; opacity: 1; }

.ap-stage {
  border: var(--bg-hairline-width) solid var(--p-hairline);
  border-radius: 24px;
  background: rgba(0, 0, 0, 0.1);
  aspect-ratio: 16 / 9;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 1.125rem;
  text-align: center;
  padding: 1.5rem;
  max-width: 56rem;
  margin-inline: auto;
  position: relative;
  overflow: hidden;
}

.ap-stage__video {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
}

.ap-mediaring {
  width: 64px;
  height: 64px;
  border-radius: var(--bg-radius-full);
  border: var(--bg-hairline-width) solid var(--p-hairline);
  display: grid;
  place-items: center;
}

.ap-mediaring span {
  font-size: 0.5rem;
  letter-spacing: 0.28em;
  color: var(--p-text-hint);
  text-transform: uppercase;
}

.ap-stage p,
.ap-slot p {
  font-size: 0.6875rem;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--p-text-hint);
  line-height: 1.9;
}

/* ---------- screens ---------- */

.ap-slots { display: grid; gap: 1.25rem; grid-template-columns: 1fr; }

@media (min-width: 640px) {
  .ap-slots { grid-template-columns: repeat(3, 1fr); gap: 1.5rem; }
}

.ap-slot {
  border: var(--bg-hairline-width) solid var(--p-hairline);
  border-radius: 20px;
  background: var(--p-ground-deep);
  aspect-ratio: 9 / 16;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 1rem;
  text-align: center;
  padding: 1.5rem;
  overflow: hidden;
}

.ap-slot img { width: 100%; height: 100%; object-fit: cover; display: block; margin: -1.5rem; }

/* ---------- the vertical rhythm the mockup set inline ----------
   Each of these was a style attribute in the mockup. Porting the
   markup into sections turned them into class names, and the classes
   were never written — so a subhead sat hard against the rows under
   it, and the section that was meant to tuck under privacy carried a
   full section's worth of top padding instead of none. */

/* A centred head over the block it introduces. Pairing gets more room
   than the others: three columns need more separation from the words
   above them than a list of rows does. */
.ap-pairing__head { margin-bottom: clamp(2.5rem, 5vw, 3.5rem); }
.ap-features__head,
.ap-screens__head { margin-bottom: clamp(2rem, 4vw, 3rem); }

/* Sits under the stage rather than in the stack above it. */
.ap-film__closing { margin-top: 1.5rem; }

/* The curve needs air above the label, and the link needs a beat after
   the paragraph so it does not read as another line of it. */
.ap-privacy__label { margin-top: 1rem; }
.ap-privacy__link { margin-top: 0.5rem; }

/* .line--divider carries 3.5rem block margins for the document pages.
   Here the stack's own gap does that work and the divider's margins
   double it. Kept for the rest of the variant: it does not redraw. */
.ap-line { margin-block: 0; }

/* Follows privacy closely rather than opening a new section. */
.ap-help { padding-block: 0 clamp(3rem, 7vw, 5rem); }

/* ---------- privacy ---------- */

.ap-line { display: block; width: min(100%, 22rem); height: auto; margin-inline: auto; }
.ap-line path { fill: none; stroke: var(--p-accent); stroke-width: 1.8; stroke-linecap: round; }

/* The hidden attribute has to win.

   It sets display:none from the UA stylesheet at the lowest possible
   specificity, so any author display rule beats it — and .ap-actions
   is display:flex. Without this, hiding the store row does nothing and
   an Android phone is shown an App Store button it cannot use. That is
   the one failure this page must not have. */
[hidden] { display: none !important; }

/* ---------- entry ----------
   The hero only, and it fires on load rather than on an observer.
   Nothing on this page may sit at opacity 0 waiting to be scrolled to:
   the one thing above the fold is the action, and an action that has
   not appeared yet is an action nobody takes.

   No fill mode, deliberately. With backwards or both, an animation
   whose clock never advances — a throttled tab, a renderer that has
   stopped compositing — holds the opening frame, and the opening frame
   of a fade is an invisible download button. With no fill the element
   falls back to its own style, which is the finished one. */
@keyframes ap-rise {
  from { opacity: 0.001; transform: translateY(var(--bg-motion-rise)); }
  to   { opacity: 1; transform: none; }
}

.ap-enter { animation: ap-rise 600ms var(--bg-motion-ease); }

@media (prefers-reduced-motion: reduce) {
  .ap-enter { animation: none; }
}

/* =============================================================
   /one-step-closer — the product page.

   A marketing page with a waitlist today, the Shopify product page at
   launch. Ported from the approved mockup: its type scale, spacing,
   breakpoints and section order, reading colour from the token layer
   rather than the inline block the mockup carried to run standalone.

   Prefixed .pd- throughout. This page runs a wider measure and a
   different display scale from the homepage, and .decks and .flip
   already belong to the homepage's own card flip.
   ============================================================= */

.pd { padding-block: clamp(3.5rem, 8vw, 6.5rem); }

/* One sentence between the product and the testimonials. It does not
   need a full section's room, and the reviews open flush against it. */
.pd--tight { padding-block: clamp(2.5rem, 6vw, 4.5rem); }
.pd--flush-top { padding-block: 0 clamp(3.5rem, 8vw, 6rem); }

.pd-wrap {
  width: 100%;
  max-width: 70rem;
  margin-inline: auto;
  padding-inline: clamp(1.25rem, 4vw, 2.5rem);
}

.pd-narrow { max-width: 40rem; margin-inline: auto; }
.pd-center { text-align: center; }
.pd-stack { display: flex; flex-direction: column; gap: 1.25rem; }

/* Same trap as /app: tokens.css caps every p at the measure and its
   shorthand zeroes the inline margins with it, so a capped paragraph
   in a centred block sits flush left. :where() keeps this reset out of
   the way of the per-element rules below. */
.pd-wrap :where(p) { margin-block: 0; }
.pd-center p,
p.pd-center { margin-inline: auto; }

/* Scoped to .pd-wrap, not .pd. The product hero is a bare wrap with
   no section around it — exactly as the mockup has it — so anything
   hung on .pd missed the one h1 on the page and the global heading
   rule sized it instead. */
.pd-wrap h1,
.pd-wrap h2,
.pd-wrap h3 {
  font-family: var(--bg-font-display);
  font-weight: var(--bg-weight-display);
  line-height: 1.15;
  letter-spacing: -0.012em;
  color: var(--p-text);
  margin: 0;
  text-wrap: balance;
}

.pd-wrap h1 { font-size: clamp(2.25rem, 4.6vw, 3.25rem); }
.pd-wrap h2 { font-size: clamp(1.75rem, 3.8vw, 2.75rem); }
.pd-wrap em { font-style: italic; color: var(--p-label); }
.pd-wrap .lede { font-size: clamp(1rem, 1.4vw, 1.1875rem); color: var(--p-text-body); }
.pd-wrap .hint { color: var(--p-text-hint); font-size: 0.9375rem; }

/* The head over a block, and the measures the mockup set inline. */
.pd-head { margin-bottom: clamp(2rem, 4vw, 3rem); }
.pd-head--close { margin-bottom: clamp(1.5rem, 3vw, 2rem); }
.pd-head__lede p,
.pd-buy__lede p { max-width: 34rem; }
.pd-head__lede p { margin-inline: auto; }

.pd-recessive {
  font-size: 0.9375rem;
  color: var(--p-text-hint);
  text-decoration: none;
  border-bottom: var(--bg-hairline-width) solid var(--p-hairline);
  padding-bottom: 2px;
}

.pd-recessive:hover { color: var(--p-text-body); border-bottom-color: var(--p-text-hint); }
.pd-app__link { margin-top: 0.5rem; }

/* ---------- the product ---------- */

.pd-product {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  gap: 2.5rem;
  padding-block: clamp(2rem, 5vw, 3.5rem) clamp(3rem, 6vw, 4.5rem);
}

@media (min-width: 900px) {
  .pd-product { grid-template-columns: 1.05fr 0.95fr; gap: 3.5rem; align-items: start; }
  .pd-buycol { position: sticky; top: 5rem; }
}

/* A grid child defaults to min-width:auto, so one wide photograph
   would push the column past its track instead of scaling into it. */
.pd-galleries { min-width: 0; }
.pd-gallery { display: flex; flex-direction: column; gap: 0.75rem; }

/* Designed for empty. A ring and the name of the shot it is waiting
   for — never a grey box. */
.pd-shot {
  border: var(--bg-hairline-width) solid var(--p-hairline);
  border-radius: 20px;
  background: var(--p-ground-deep);
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 0.875rem;
  text-align: center;
  padding: 1.5rem;
  overflow: hidden;
}

.pd-shot--main { aspect-ratio: 4 / 5; }
.pd-shot__img { width: 100%; height: 100%; object-fit: cover; display: block; margin: -1.5rem; }

.pd-ring {
  width: 56px;
  height: 56px;
  border-radius: var(--bg-radius-full);
  border: var(--bg-hairline-width) solid var(--p-hairline);
  display: grid;
  place-items: center;
  flex: none;
}

.pd-ring span {
  font-size: 0.5rem;
  letter-spacing: 0.28em;
  color: var(--p-text-hint);
  text-transform: uppercase;
}

.pd-shot p {
  font-size: 0.6875rem;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--p-text-hint);
  line-height: 1.9;
}

.pd-thumbs { display: grid; grid-template-columns: repeat(4, 1fr); gap: 0.75rem; }
.pd-thumbs .pd-shot { aspect-ratio: 1; padding: 0.75rem; gap: 0.5rem; border-radius: 14px; }
.pd-thumbs .pd-shot__img { margin: -0.75rem; }
.pd-thumbs .pd-ring { width: 34px; height: 34px; }
.pd-thumbs .pd-ring span { font-size: 0.4rem; letter-spacing: 0.2em; }
.pd-thumbs p { font-size: 0.5rem; letter-spacing: 0.1em; }

/* ---------- the buy column ---------- */

.pd-buy { display: flex; flex-direction: column; gap: 1.25rem; }
.pd-price { font-family: var(--bg-font-display); font-size: 1.75rem; color: var(--p-text); }

.pd-variants {
  border: 0;
  padding: 0;
  margin: 0;
  display: flex;
  flex-direction: column;
  gap: 0;
  border-top: var(--bg-hairline-width) solid var(--p-hairline);
}

.pd-variants legend { padding: 0; margin-bottom: 0.75rem; }

.pd-var {
  display: flex;
  gap: 0.875rem;
  align-items: flex-start;
  padding: 1rem 0;
  border-bottom: var(--bg-hairline-width) solid var(--p-hairline);
  cursor: pointer;
}

/* The radio stays in the accessibility tree and keeps its keyboard
   behaviour; the tick beside it is what gets drawn. */
.pd-var input { position: absolute; opacity: 0; pointer-events: none; }

.pd-tick {
  width: 22px;
  height: 22px;
  border-radius: var(--bg-radius-full);
  border: var(--bg-hairline-width) solid var(--p-hairline);
  flex: 0 0 auto;
  display: grid;
  place-items: center;
  margin-top: 0.125rem;
  transition: border-color var(--bg-motion-duration-fast) var(--bg-motion-ease);
}

.pd-tick::after {
  content: "";
  width: 10px;
  height: 10px;
  border-radius: var(--bg-radius-full);
  background: var(--p-accent);
  transform: scale(0);
  transition: transform var(--bg-motion-duration-fast) var(--bg-motion-ease);
}

.pd-var input:checked + .pd-tick { border-color: var(--p-accent); }
.pd-var input:checked + .pd-tick::after { transform: scale(1); }
.pd-var input:focus-visible + .pd-tick { outline: 2px solid var(--p-accent); outline-offset: 4px; }

.pd-var__body { display: flex; flex-direction: column; gap: 0.1875rem; flex: 1; }
.pd-var__body b { font-family: var(--bg-font-display); font-weight: 400; color: var(--p-text); font-size: 1.0625rem; }
.pd-var__body span { font-size: 0.875rem; color: var(--p-text-hint); }
.pd-caveat { color: var(--p-text-hint); }

.pd-var__price {
  font-family: var(--bg-font-display);
  color: var(--p-text);
  flex: 0 0 auto;
  font-variant-numeric: tabular-nums;
}

.pd-cart { margin: 0; }
.pd-btn { width: 100%; }

.pd-btn[disabled] {
  background: none;
  color: var(--p-text-hint);
  box-shadow: inset 0 0 0 var(--bg-hairline-width) var(--p-hairline);
  cursor: not-allowed;
  filter: none;
}

.pd-buy .pill { width: 100%; }
.pd-meta { display: flex; flex-direction: column; gap: 0.5rem; }
.pd-meta div { display: flex; gap: 0.625rem; font-size: 0.875rem; color: var(--p-text-body); }
.pd-meta i { color: var(--p-label); font-style: normal; }

/* ---------- testimonials ---------- */

.pd-revs { display: grid; gap: 1.25rem; grid-template-columns: minmax(0, 1fr); }

@media (min-width: 820px) {
  .pd-revs { grid-template-columns: repeat(3, minmax(0, 1fr)); }
}

.pd-rev {
  border: var(--bg-hairline-width) solid var(--p-hairline);
  border-radius: 20px;
  padding: 1.5rem;
  display: flex;
  flex-direction: column;
  gap: 0.875rem;
  background: var(--p-ground-deep);
  margin: 0;
}

.pd-rev q { font-family: var(--bg-font-display); font-size: 1.0625rem; line-height: 1.45; color: var(--p-text); quotes: none; }
.pd-rev figcaption { font-size: 0.8125rem; color: var(--p-text-hint); }

/* Dashed, accent, and deliberately not quiet. It is what stands
   between this page and invented reviews going public. */
.pd-flag {
  border: var(--bg-hairline-width) dashed var(--pd-flag-edge, rgba(201, 123, 90, 0.6));
  border-radius: 14px;
  padding: 0.75rem 1rem;
  font-size: 0.8125rem;
  color: var(--p-text-hint);
  margin-bottom: 1.5rem;
}

@supports (color: color-mix(in srgb, red, blue)) {
  .pd-flag { --pd-flag-edge: color-mix(in srgb, var(--p-accent) 60%, transparent); }
}

/* ---------- inside the box ---------- */

.pd-contents { display: grid; gap: 0 3rem; grid-template-columns: minmax(0, 1fr); }

@media (min-width: 820px) {
  .pd-contents { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}

.pd-row {
  display: flex;
  gap: 1rem;
  align-items: baseline;
  padding-block: 1.0625rem;
  border-bottom: var(--bg-hairline-width) solid var(--p-hairline);
}

.pd-row b {
  font-family: var(--bg-font-display);
  font-weight: 400;
  color: var(--p-text);
  font-size: 1.0625rem;
  min-width: 3.25rem;
  font-variant-numeric: tabular-nums;
}

.pd-row p { font-size: 0.9375rem; }

/* ---------- the decks ---------- */

.pd-decks { display: grid; gap: 1.5rem; grid-template-columns: repeat(2, minmax(0, 1fr)); }

@media (min-width: 820px) {
  .pd-decks { grid-template-columns: repeat(4, minmax(0, 1fr)); gap: 1.25rem; }
}

.pd-deck { display: flex; flex-direction: column; gap: 0.875rem; align-items: center; text-align: center; }

/* The cards are 2:3 and the artwork fills the turning element exactly,
   so nothing is letterboxed or cropped. The ratio sits on .pd-flip__in
   rather than the button, because that is the element the faces are
   absolutely positioned against. */
.pd-flip {
  appearance: none;
  width: 100%;
  background: none;
  border: 0;
  padding: 0;
  cursor: pointer;
  display: block;
  perspective: 1200px;
}

.pd-flip__in {
  position: relative;
  display: block;
  width: 100%;
  aspect-ratio: 2 / 3;
  transition: transform 700ms var(--bg-motion-ease);
  transform-style: preserve-3d;
}

.pd-flip[aria-pressed="true"] .pd-flip__in { transform: rotateY(180deg); }

.pd-flip:focus-visible { outline: 2px solid var(--p-accent); outline-offset: 6px; border-radius: 1rem; }

/* Both faces are the real card artwork — the same files the homepage
   turns over — so the printed tint, the border and the wash all come
   from the card rather than from CSS imitating it. */
.pd-face {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  display: block;
  backface-visibility: hidden;
  border-radius: 0.85rem;
  box-shadow: 0 16px 34px rgba(0, 0, 0, 0.16);
}

.pd-face--front { transform: rotateY(180deg); }

/* A deck with no front supplied: the back, in flow, not turning. */
.pd-face--still {
  position: static;
  width: 100%;
  height: auto;
  aspect-ratio: 2 / 3;
  border-radius: 0.85rem;
}

.pd-deck__name {
  display: flex;
  align-items: center;
  gap: 0.4375rem;
  font-size: 0.6875rem;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--p-text-label);
}

/* The accent lives in the mark, where it owes nothing. */
.pd-deck__name i { width: 8px; height: 8px; border-radius: var(--bg-radius-full); display: block; flex: none; }
.pd-deck--open-book .pd-deck__name i { background: var(--p-open-book); }
.pd-deck--deen-talk .pd-deck__name i { background: var(--p-deen-talk); }
.pd-deck--quick-connect .pd-deck__name i { background: var(--p-quick-connect); }
.pd-deck--ramadan .pd-deck__name i { background: var(--p-ramadan); }

.pd-deck small { font-size: 0.875rem; color: var(--p-text-body); }

/* ---------- how to play ---------- */

.pd-steps {
  display: grid;
  gap: 1.75rem;
  grid-template-columns: minmax(0, 1fr);
  list-style: none;
  margin: 0;
  padding: 0;
}

@media (min-width: 760px) {
  .pd-steps { grid-template-columns: repeat(5, minmax(0, 1fr)); gap: 1.5rem; }
}

.pd-step {
  display: flex;
  flex-direction: column;
  gap: 0.5rem;
  padding-top: 1.125rem;
  border-top: var(--bg-hairline-width) solid var(--p-hairline);
}

.pd-step__n { font-family: var(--bg-font-display); font-style: italic; color: var(--p-label); font-size: 1.0625rem; }
.pd-step p { font-size: 0.9375rem; }

.pd-panel {
  border: var(--bg-hairline-width) solid var(--p-hairline);
  border-radius: 22px;
  background: var(--p-ground-deep);
  padding: clamp(1.5rem, 3vw, 2rem);
  display: flex;
  flex-direction: column;
  gap: 0.75rem;
}

.pd-panel--ramadan { margin: 2.75rem auto 0; max-width: 38rem; align-items: center; }

/* ---------- questions ---------- */

.pd-faq { border-top: var(--bg-hairline-width) solid var(--p-hairline); }
.pd-faq details { border-bottom: var(--bg-hairline-width) solid var(--p-hairline); }

.pd-faq summary {
  list-style: none;
  cursor: pointer;
  padding-block: 1.125rem;
  display: flex;
  justify-content: space-between;
  gap: 1rem;
  align-items: center;
  font-family: var(--bg-font-display);
  font-size: 1.0625rem;
  color: var(--p-text);
}

.pd-faq summary::-webkit-details-marker { display: none; }

.pd-faq summary::after {
  content: "+";
  color: var(--p-label);
  font-family: var(--bg-font-body);
  font-weight: 300;
  flex: 0 0 auto;
}

.pd-faq details[open] summary::after { content: "\2013"; }
.pd-faq p { padding-bottom: 1.125rem; font-size: 0.9375rem; max-width: 44rem; }

/* ---------- the blessing ---------- */

.pd-blessing {
  font-family: var(--bg-font-display);
  font-style: italic;
  font-size: clamp(1.25rem, 2.6vw, 1.75rem);
  color: var(--p-text);
  line-height: 1.4;
  text-wrap: balance;
}

.pd-blessing__source { margin-top: 0.25rem; }
.pd-close__form { margin-top: 1.5rem; display: flex; justify-content: center; }
.pd-close__form .waitlist { width: 100%; max-width: 26rem; }

@media (prefers-reduced-motion: reduce) {
  .pd-flip__in { transition: none; }
}

/* =============================================================
   /about — the only page where Barakah Games leads.

   THE DELIBERATE ABSENCE. None of One Step Closer's signature marks
   appear here: no terracotta line, no card artwork, no deck colours,
   almost no accent at all. Gold labels, cream type, forest ground.
   That is the two-layer token system doing visible work — if /about
   looked like the product pages the layering would be theoretical.

   Ported from the approved mockup. Note the measure: 64rem, narrower
   than the 70rem the product and app pages run, because this page is
   almost entirely prose.
   ============================================================= */

.ab { padding-block: clamp(4rem, 9vw, 7rem); }

.ab-wrap {
  width: 100%;
  max-width: 64rem;
  margin-inline: auto;
  padding-inline: clamp(1.25rem, 4vw, 2.5rem);
}

.ab-mid { max-width: 44rem; margin-inline: auto; text-align: center; }
.ab-stack { display: flex; flex-direction: column; gap: 1.5rem; }

/* Same trap as the other two pages: tokens.css caps every p at the
   measure and its shorthand zeroes the inline margins with it, so a
   capped paragraph in a centred block sits flush left. :where() keeps
   the reset out of the way of the per-element rules below. */
.ab-wrap :where(p) { margin-block: 0; }
/* max-width: none, not just auto margins. tokens.css caps every p at
   the measure, so inside a 44rem centred block the box is narrower
   than its parent and — with the shorthand zeroing the inline margins
   — pinned to the left of it. The turn's two paragraphs were 96px off,
   and it is the centre of the page.

   Removing the cap rather than only centring the box also restores the
   mockup's line breaks: these blocks set their own measure, 44rem for
   .ab-mid and 40rem for the beat, and the cap was overriding both. */
.ab-mid p,
.ab-beat p { max-width: none; margin-inline: auto; }

.ab-wrap h1,
.ab-wrap h2,
.ab-wrap h3 {
  font-family: var(--bg-font-display);
  font-weight: var(--bg-weight-display);
  line-height: 1.1;
  letter-spacing: -0.015em;
  color: var(--p-text);
  margin: 0;
  text-wrap: balance;
}

.ab-wrap em { font-style: italic; color: var(--p-label); }
.ab-wrap .hint { color: var(--p-text-hint); font-size: 0.9375rem; }

/* Every section heading below the hero shares one size — the mockup
   set it inline on all four. */
.ab-h2 { font-size: clamp(1.875rem, 4.4vw, 3rem); }

/* The two heads that introduce a block beneath them. */
.ab-mid--wide { margin-bottom: clamp(2rem, 5vw, 3.5rem); }
.ab-mid--catalogue { margin-bottom: clamp(2rem, 4vw, 3rem); }

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

.ab-hero { padding-block: clamp(4.5rem, 13vw, 9rem) clamp(3.5rem, 8vw, 6rem); text-align: center; }
.ab-hero h1 { font-size: clamp(2.75rem, 7.4vw, 5.5rem); margin-block: 1.25rem 1.75rem; }

.ab-hero__sub p {
  font-size: clamp(1.0625rem, 1.7vw, 1.375rem);
  color: var(--p-text-body);
  max-width: 36rem;
  margin-inline: auto;
}

/* ---------- the turn ----------
   The centre of the page, on the darker ground, at the largest type on
   the site. */
.ab-turn {
  background: var(--p-ground-deep);
  border-block: var(--bg-hairline-width) solid var(--p-hairline);
}

.ab-beat { max-width: 40rem; margin-inline: auto; text-align: center; }

.ab-beat p {
  font-family: var(--bg-font-display);
  font-size: clamp(1.375rem, 3.2vw, 2.125rem);
  line-height: 1.34;
  color: var(--p-text);
  text-wrap: balance;
}

/* The before is quieter than the after. The rule between them is the
   turn itself. */
.ab-beat__before p { color: var(--p-text-hint); }

.ab-beat__after {
  margin-top: 2.25rem;
  padding-top: 2.25rem;
  border-top: var(--bg-hairline-width) solid var(--p-hairline);
}

/* ---------- who ----------
   The page's one motif. Circles are brand-level language and this is
   the only place they appear — used once it reads as a mark, repeated
   it reads as a system nobody committed to. */
.ab-seats {
  display: flex;
  gap: clamp(2rem, 7vw, 4.5rem);
  justify-content: center;
  flex-wrap: wrap;
  margin-top: 2.5rem;
}

.ab-seat { display: flex; flex-direction: column; align-items: center; gap: 0.875rem; }
.ab-pips { display: flex; gap: 0.5rem; }

.ab-pips i {
  width: clamp(12px, 2.4vw, 17px);
  aspect-ratio: 1;
  border-radius: var(--bg-radius-full);
  background: var(--p-label);
  display: block;
  opacity: 0.9;
}

.ab-seat span {
  font-size: 0.6875rem;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--p-text-label);
}

/* ---------- beliefs ---------- */

.ab-belief {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  gap: 0.75rem;
  padding-block: clamp(2rem, 4vw, 2.75rem);
  border-bottom: var(--bg-hairline-width) solid var(--p-hairline);
}

.ab-belief:first-child { border-top: var(--bg-hairline-width) solid var(--p-hairline); }
.ab-belief h3 { font-size: clamp(1.5rem, 3vw, 2.125rem); }
.ab-belief p { max-width: 34rem; font-size: 1.0625rem; }

@media (min-width: 820px) {
  .ab-belief { grid-template-columns: minmax(0, 1fr) minmax(0, 1fr); gap: 3rem; align-items: start; }
}

/* ---------- the catalogue ----------
   A list that grows by a row. Nothing here assumes there is one. */
.ab-cat { border-top: var(--bg-hairline-width) solid var(--p-hairline); }

.ab-item {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  gap: 0.625rem;
  padding-block: 1.75rem;
  border-bottom: var(--bg-hairline-width) solid var(--p-hairline);
}

@media (min-width: 760px) {
  .ab-item { grid-template-columns: 6rem minmax(0, 1fr) auto; gap: 2rem; align-items: baseline; }
}

.ab-item__yr { font-family: var(--bg-font-display); color: var(--p-label); font-variant-numeric: tabular-nums; }
.ab-item h3 { font-size: 1.5rem; }
.ab-item p { font-size: 0.9375rem; margin-top: 0.375rem; max-width: 36rem; }

.ab-item a {
  font-size: 0.6875rem;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--p-text-label);
  text-decoration: none;
  white-space: nowrap;
}

.ab-item a:hover { color: var(--p-text); }
.ab-next { color: var(--p-text-hint); font-size: 0.9375rem; padding-block: 1.75rem; display: block; }

/* ---------- the founder note ---------- */

.ab-founder { display: grid; gap: 2.5rem; grid-template-columns: minmax(0, 1fr); align-items: start; }

@media (min-width: 860px) {
  .ab-founder { grid-template-columns: 0.8fr 1.2fr; gap: 3.5rem; }
}

.ab-portrait {
  border: var(--bg-hairline-width) solid var(--p-hairline);
  border-radius: 20px;
  background: var(--p-ground-deep);
  aspect-ratio: 4 / 5;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 0.875rem;
  text-align: center;
  padding: 1.5rem;
  overflow: hidden;
}

.ab-portrait__img { width: 100%; height: 100%; object-fit: cover; display: block; margin: -1.5rem; }

.ab-ring {
  width: 56px;
  height: 56px;
  border-radius: var(--bg-radius-full);
  border: var(--bg-hairline-width) solid var(--p-hairline);
  display: grid;
  place-items: center;
  flex: none;
}

.ab-ring span { font-size: 0.5rem; letter-spacing: 0.28em; color: var(--p-text-hint); text-transform: uppercase; }
.ab-portrait p { font-size: 0.6875rem; letter-spacing: 0.14em; text-transform: uppercase; color: var(--p-text-hint); line-height: 1.9; }

.ab-note__label { margin-bottom: 1.25rem; }

.ab-note__lead {
  font-size: 1.1875rem;
  color: var(--p-text);
  font-family: var(--bg-font-display);
  line-height: 1.45;
}

.ab-note p + p { margin-top: 1.125rem; }

/* The fills. Gold and dashed so they cannot be mistaken for finished
   copy, and so nobody has to remember which passages are unwritten. */
.ab-fill { color: var(--p-label); border-bottom: 1px dashed var(--ab-fill-edge, rgba(201, 168, 120, 0.5)); }

@supports (color: color-mix(in srgb, red, blue)) {
  .ab-note { --ab-fill-edge: color-mix(in srgb, var(--p-label) 50%, transparent); }
}

/* Rendered only while a fill remains, and it takes itself down when
   the last one is replaced. */
.ab-warning {
  border: var(--bg-hairline-width) dashed var(--ab-fill-edge, rgba(201, 168, 120, 0.5));
  border-radius: 14px;
  padding: 0.75rem 1rem;
  font-size: 0.8125rem;
  color: var(--p-text-hint);
  margin-bottom: 1.5rem;
}

.ab-sig { font-family: var(--bg-font-display); font-style: italic; font-size: 1.125rem; color: var(--p-text); margin-top: 1.5rem; }

/* ---------- find us ---------- */

.ab-links { display: flex; flex-wrap: wrap; gap: 1.75rem; justify-content: center; margin-top: 0.75rem; }

.ab-links a {
  font-size: 0.9375rem;
  color: var(--p-text-body);
  text-decoration: none;
  border-bottom: var(--bg-hairline-width) solid var(--p-hairline);
  padding-bottom: 2px;
}

.ab-links a:hover { color: var(--p-text); border-bottom-color: var(--p-text-hint); }

.ab-signup { margin-top: 2.5rem; }
.ab-signup__note { margin-bottom: 0.875rem; }
.ab-contact .pill { max-width: 26rem; margin-inline: auto; }
