/* Signalyze site styles: one design system, eight pages, eleven locales.
 *
 * Palette comes entirely from brand/tokens.css, which is generated from the logo
 * by tools/brand_assets.py. Nothing here invents a brand colour. The two
 * exceptions are the win and loss colours below, which are not brand colours at
 * all -- they are the trading convention, and a result card that renders a loss
 * in gold would be unreadable in the only sense that matters. Their contrast is
 * measured in tests/test_brand.py like every other pairing.
 *
 * Three constraints shape the whole sheet:
 *
 * 1. Gold is 1.94:1 against white, so on light surfaces it is a fill, a rule and
 *    a border but never text: the text role belongs to --sz-accent-text, a
 *    darkened gold measuring 4.63:1.
 * 2. The Content-Security-Policy is `style-src 'self'` with no `unsafe-inline`,
 *    so there is no `style=` attribute in any document. Every state is a class.
 * 3. Every layout rule is written so no element can exceed the viewport width.
 *    A single horizontal scrollbar on a phone reads as broken, and the site is
 *    checked at 360px.
 *
 * System fonts, deliberately. A webfont would be a render-blocking request on the
 * critical path for a document whose entire value is text a crawler can read, and
 * the system UI stack is what the reader's own device already renders best.
 *
 * --- The breakpoints -------------------------------------------------------
 *
 * Four, and no others. They are listed here because a fifth invented at the
 * point of need is how a design system stops being one, and because two of the
 * four used to disagree with each other: the gutter widened at 64em while every
 * layout in the sheet switched at 62em, so between 992px and 1024px the page
 * changed shape and its margins did not.
 *
 *   20.5em   328px   the brand wordmark no longer fits beside the controls
 *   30em     480px   two things fit side by side where one did before
 *   48em     768px   tablet: card grids open up, the gutter widens
 *   62em     992px   desktop: the header takes its full form, the sticky
 *                    mobile call to action retires, the council seats unstack
 *
 * `max-width` rules are written at the breakpoint minus 0.01em so a viewport
 * exactly on a boundary matches one side only.
 */

/* --- The container system -------------------------------------------------
 *
 * Three widths, not one. The site used to set every section to 68rem, which made
 * a legal paragraph 1088px wide -- roughly 150 characters a line, about twice a
 * comfortable measure -- and simultaneously left a four-card pricing row cramped.
 *
 * `--wrap` is a custom property rather than a class per width, so a band widens
 * its own contents by redefining one value. `.wrap` reads it wherever it lands,
 * which means a section changes width without its markup changing at all.
 */
:root {
  /* Measured against the category at 1440px: standard containers cluster at
   * 1160-1248 (WunderTrading 1160, 3Commas 1170, Stripe 1232, Cryptohopper
   * 1248), wide treatments at 1344-1360 (Linear 1344, Cornix 1360), and text
   * columns at 640-680 (Linear 648, 3Commas 650, Cornix 660). The site used to
   * set every section to 1088px, which was narrower than all of them and made a
   * legal paragraph about 150 characters a line. */
  --wrap: 75rem;          /* 1200px: cards, grids, the default */
  --wrap-text: 45rem;     /*  720px: prose, legal, FAQ answers */
  --gutter: 1.25rem;

  --radius: 14px;
  --radius-sm: 10px;

  /* The card edge. `--sz-border` is right on a light surface and heavy on a
   * dark one: #333333 is roughly 20% white, which reads as a drawn box rather
   * than an edge. Dark surfaces get a wash instead, which is what makes a panel
   * on #141414 look lit rather than outlined. */
  --card-edge: var(--sz-border);
  --card-lift: none;

  --band: clamp(3.5rem, 2.5rem + 4vw, 6rem);
  --band-tight: clamp(2.25rem, 1.75rem + 1.6vw, 3.25rem);

  /* Trading convention, not brand. Measured against every surface it is drawn
   * on; see tests/test_brand.py.
   *
   * The two are also separated in *luminance*, not only in hue. The obvious
   * pair -- a mid green and a mid red -- measured 1.00:1 against each other,
   * which is the one relationship a red-green colour blindness cannot resolve
   * at all: the two numbers would have looked identical. The sign in front of
   * every figure is the primary signal and the darker red is the second one. */
  --sz-win: #15704F;      /* 5.76:1 on surface */
  --sz-loss: #9A2018;     /* 6.93:1 on surface, 1.33:1 against the green */
}
@media (min-width: 48em) { :root { --gutter: 2rem; } }
/* 62em, not 64em. The wide gutter used to arrive two rem-steps after the layout
 * it belongs to, which put a 32px margin on a page that had already taken its
 * desktop shape. */
@media (min-width: 62em) { :root { --gutter: 3rem; } }
:root[data-theme="dark"] {
  --sz-win: #4ED6A1;      /* 10.05:1 on surface */
  --sz-loss: #FF7A6B;     /*  7.24:1 on surface */
  --card-edge: color-mix(in srgb, #FFFFFF 9%, transparent);
  --card-lift: inset 0 1px 0 rgb(255 255 255 / 0.05);
}
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) {
    --sz-win: #4ED6A1;
    --sz-loss: #FF7A6B;
    --card-edge: color-mix(in srgb, #FFFFFF 9%, transparent);
    --card-lift: inset 0 1px 0 rgb(255 255 255 / 0.05);
  }
}

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

/* The user-agent rule for `hidden` is `display: none`, which any later
 * `display` declaration wins against -- so a component whose own rule sets
 * `display: grid` is still drawn when script hides it. That is not theoretical:
 * the result panels are `display: grid`, so all six rendered at once inside the
 * laptop the moment the tabs were wired. `app.css` has carried this rule since
 * it was written; the site did not. */
[hidden] { display: none !important; }

html {
  -webkit-text-size-adjust: 100%;
  scroll-behavior: smooth;
}
/* The global guard, and it is deliberately belt as well as braces: every
 * decorative primitive below is *also* declared opt-in inside
 * `prefers-reduced-motion: no-preference`, so a reader who has asked for less
 * motion never has the moving state applied in the first place. This block is
 * what covers rules nobody has written yet.
 *
 * `animation-iteration-count` is the part that was missing and it matters more
 * than the duration: collapsing an *infinite* animation to 0.01ms does not stop
 * it, it runs it at the frame rate forever. `animation-timeline` is reset for
 * the same reason -- a scroll-driven animation is not driven by duration at all,
 * so shortening the duration does nothing to it. */
@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }
  *, *::before, *::after {
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
    animation-timeline: none !important;
    transition-duration: 0.01ms !important;
    scroll-timeline: none !important;
  }
}

body {
  margin: 0;
  background: var(--sz-surface);
  color: var(--sz-text);
  font-family: system-ui, -apple-system, "Segoe UI", Roboto, "Helvetica Neue", Arial,
    "Noto Sans", "Noto Sans Arabic", "Noto Sans Devanagari", sans-serif;
  /* 17px, and it does not shrink on a phone. Linear holds 15/24 at every
   * breakpoint and Cryptohopper 16/24; dropping body text below 16 on mobile is
   * the most common way a site that looks premium on a desktop stops being one
   * in the hand. */
  font-size: 1.0625rem;
  line-height: 1.6;
  /* Belt and braces against a wide child escaping its container on a phone. */
  overflow-x: hidden;
}

img, svg, video { max-width: 100%; height: auto; }

h1, h2, h3 {
  line-height: 1.15;
  letter-spacing: -0.02em;
  margin: 0 0 0.5em;
  font-weight: 650;
  text-wrap: balance;
  /* A single word longer than the line breaks rather than overflowing it.
   * `text-wrap: balance` and `max-width` both operate on where lines *may*
   * break, and a word with no break opportunity has none -- so the Russian
   * "конфиденциальности" made the /ru/privacy/ heading 396px wide in a 390px
   * viewport and the whole page scrolled sideways. */
  overflow-wrap: break-word;
}
/* Tracking is the single highest-leverage typographic setting on a page like
 * this, and none was set before. The rule the category converges on: negative
 * at display sizes, none at reading sizes, positive only on an uppercase
 * eyebrow. Linear runs -0.022em at 64px; Stripe -0.025em at 56px. */
h1 { font-size: clamp(2.125rem, 1.2rem + 3.7vw, 3.75rem); letter-spacing: -0.022em; }
h2 { font-size: clamp(1.625rem, 1.15rem + 1.9vw, 2.5rem); letter-spacing: -0.02em; }
h3 { font-size: clamp(1.125rem, 1.05rem + 0.35vw, 1.3125rem); font-weight: 620; }
p { margin: 0 0 1rem; text-wrap: pretty; overflow-wrap: break-word; }
p:last-child { margin-bottom: 0; }

a { color: var(--sz-accent-text); text-underline-offset: 0.2em; }
a:hover { color: var(--sz-text); }

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

/* An icon that points has to point the way the reader reads. Only the icons that
 * actually carry a direction are marked: an arrow meaning "go there" and the
 * paper plane meaning "sent". The rest of the set is deliberately excluded --
 * mirroring a shield, a clock face or a balance produces a subtly wrong drawing
 * of a familiar object, and mirroring the price tape would reverse the direction
 * time runs in. */
[dir="rtl"] .icon-directional { transform: scaleX(-1); }

.wrap {
  width: min(100% - (var(--gutter) * 2), var(--wrap));
  margin-inline: auto;
}

/* The eyebrow above a section heading: small, spaced, quiet, and never the only
 * thing carrying the meaning. */
.eyebrow {
  display: inline-flex;
  align-items: center;
  gap: 0.45rem;
  margin-bottom: 0.85rem;
  font-size: 0.75rem;
  font-weight: 700;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--sz-accent-text);
}

/* `--wrap-text`, not a 46rem near-miss of it. Two ceilings 16px apart are two
 * ceilings, and the second one had no argument behind it. */
.section-head { max-width: var(--wrap-text); margin-bottom: clamp(1.75rem, 1rem + 2vw, 2.75rem); }
.section-head h2 { margin-bottom: 0.4rem; }
/* A value this service computed at render time, inside a sentence a translator
 * wrote once. The distinction is worth marking: "June 2022" and the current
 * month are the only parts of that sentence that will be different tomorrow, and
 * a reader who cannot tell which numbers are live has to take the whole line on
 * trust. Colour and weight only -- no wash, no border, no pill: this sits inside
 * running prose and a chip would read as a control. */
.live { color: var(--sz-value-live); font-weight: 650; }
/* The dates are Latin numerals in every locale, including the RTL one, so the
 * isolate keeps them from reordering against the text beside them. */
.live > bdi { unicode-bidi: isolate; }

.section-lede {
  font-size: clamp(1rem, 0.96rem + 0.25vw, 1.125rem);
  color: var(--sz-text-muted);
  margin: 0;
  max-width: 54ch;
}

/* Skip link: present for keyboard users, out of the way for everyone else.
 *
 * Clipped rather than pushed off-screen. `left: -9999px` is the classic recipe
 * and it is a direction bug: in a right-to-left document the overflow axis is
 * mirrored, so the element that was harmlessly off to the left becomes 9999px of
 * scrollable width and every Arabic page gains a horizontal scrollbar. */
.skip {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
  z-index: 100;
  background: var(--sz-surface-raised);
  color: var(--sz-text);
  border: 2px solid var(--sz-accent);
}
.skip:focus {
  width: auto;
  height: auto;
  margin: 0;
  padding: 0.75rem 1rem;
  overflow: visible;
  clip-path: none;
  inset-inline-start: 0.5rem;
  top: 0.5rem;
}

/* Present to assistive technology, absent from the page. Clipped rather than
 * `display: none`, which would take it out of the tab order. */
.visually-hidden {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
}

/* --- Icons ----------------------------------------------------------------
 * One weight, one grid, always `currentColor`, so an icon cannot drift from the
 * colour of the text it sits beside. */
.icon { flex: none; display: block; }

/* --- Buttons ------------------------------------------------------------- */

.button {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 0.5rem;
  min-height: 3rem;              /* 52px at desktop below; never under 44px */
  padding: 0.7rem 1.5rem;
  background: transparent;
  border: 1px solid transparent;
  border-radius: 10px;
  font: inherit;
  color: inherit;
  font-weight: 600;
  text-decoration: none;
  cursor: pointer;
  transition: background-color 120ms ease, border-color 120ms ease, color 120ms ease;
}
.button-primary {
  background: var(--sz-accent);
  color: var(--sz-on-accent);
  border-color: var(--sz-accent);
}
.button-primary:hover {
  background: var(--sz-gold-600);
  border-color: var(--sz-gold-600);
  color: var(--sz-on-accent);
}
@media (min-width: 48em) {
  .button { min-height: 3.25rem; padding: 0.75rem 1.75rem; }
}
/* The header action is deliberately smaller than the hero action, so the two do
 * not compete in the same viewport. Linear and 3Commas both hold their header
 * button at 32px against a 56px hero button. */
.header-cta {
  min-height: 2.5rem;
  padding: 0.4rem 1.1rem;
  border-radius: 999px;
  font-size: 0.875rem;
}
@media (min-width: 48em) { .header-cta { min-height: 2.5rem; padding: 0.4rem 1.1rem; } }
.button-quiet {
  background: transparent;
  color: var(--sz-text);
  border-color: var(--sz-border);
}
.button-quiet:hover { border-color: var(--sz-accent); color: var(--sz-text); }

/* Anything an in-page link or a `#hash` can land on clears the sticky header.
   Without this the header covers the heading the reader was sent to, which
   reads as the link having gone to the wrong place. */
:target,
h1[id], h2[id], h3[id], section[id], .band[id] { scroll-margin-top: 5.5rem; }

/* --- Header -------------------------------------------------------------- */

.site-header {
  position: sticky;
  top: 0;
  z-index: 40;
  background: color-mix(in srgb, var(--sz-surface) 92%, transparent);
  backdrop-filter: blur(8px);
  border-bottom: 1px solid var(--sz-border);
}
.header-inner {
  display: flex;
  align-items: center;
  gap: 0.75rem;
  min-height: 4.25rem;
  flex-wrap: wrap;
}
.brand {
  display: inline-flex;
  align-items: center;
  gap: 0.55rem;
  color: var(--sz-text);
  text-decoration: none;
  font-weight: 700;
  letter-spacing: 0.01em;
  flex: none;
}
.brand-mark { display: block; width: 1.9rem; height: auto; }
.brand-word { font-size: 1.05rem; }

.site-nav { margin-inline-start: auto; min-width: 0; }
.site-nav ul {
  display: flex;
  /* Wrapping rather than a breakpoint chosen per language. Russian and Turkish
   * labels are markedly longer than English, and a fixed width at which the
   * horizontal bar is "safe" is a guess that a future locale breaks. */
  flex-wrap: wrap;
  gap: 0.4rem 1.15rem;
  list-style: none;
  margin: 0;
  padding: 0;
  min-width: 0;
}
.site-nav a {
  color: var(--sz-text-muted);
  text-decoration: none;
  font-size: 0.9375rem;
  font-weight: 550;
  white-space: nowrap;
}
.site-nav a:hover { color: var(--sz-text); }
.site-nav a[aria-current="page"] {
  color: var(--sz-text);
  box-shadow: inset 0 -2px 0 var(--sz-accent);
}

.header-end {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  flex: none;
  margin-inline-start: 0.25rem;
}

/* --- The language control -------------------------------------------------
 *
 * A `<details>`, so it opens with a keyboard, announces its own expanded state
 * and needs no script -- which matters twice here: the CSP forbids inline
 * script, and this is the one control a reader who cannot read the page has to
 * be able to reach before anything else has loaded.
 *
 * In the header on every page. It used to be eleven links at the bottom of the
 * footer, which is where a reader in the wrong language never looks. */
.lang { position: relative; flex: none; }
.lang-button {
  display: inline-flex;
  align-items: center;
  gap: 0.3rem;
  min-height: 2.5rem;
  padding: 0.35rem 0.6rem;
  border: 1px solid var(--sz-border);
  border-radius: 10px;
  color: var(--sz-text-muted);
  cursor: pointer;
  list-style: none;
  user-select: none;
}
.lang-button::-webkit-details-marker { display: none; }
.lang-button:hover { color: var(--sz-text); border-color: var(--sz-accent); }
.lang-now { font-size: 0.8125rem; font-weight: 650; letter-spacing: 0.02em; }
.lang-caret { transition: transform 140ms ease; }
.lang[open] > .lang-button { color: var(--sz-text); border-color: var(--sz-accent); }
.lang[open] > .lang-button .lang-caret { transform: rotate(180deg); }

.lang-menu {
  position: absolute;
  top: calc(100% + 0.5rem);
  inset-inline-end: 0;
  z-index: 60;
  margin: 0;
  padding: 0.4rem;
  list-style: none;
  width: max-content;
  min-width: 12rem;
  max-width: min(17rem, calc(100vw - 2rem));
  max-height: min(70vh, 26rem);
  overflow-y: auto;
  background: var(--sz-surface-raised);
  border: 1px solid var(--sz-border);
  border-radius: var(--radius-sm);
  box-shadow: 0 18px 40px rgb(0 0 0 / 0.18);
}
.lang-menu a {
  display: flex;
  align-items: center;
  gap: 0.6rem;
  padding: 0.5rem 0.6rem;
  border-radius: 8px;
  color: var(--sz-text);
  text-decoration: none;
  font-size: 0.9375rem;
}
.lang-menu a:hover { background: var(--sz-surface-sunken); }
.lang-menu a[aria-current="true"] { color: var(--sz-accent-text); font-weight: 650; }
/* The vendored flags in the picker (signalyze.web.flags). Sized in rem rather
 * than by the tag's own width/height so they scale with the reader's text size:
 * a language picker is the one control somebody with large type set has to be
 * able to hit. The width/height attributes stay on the tag regardless, to
 * reserve the row before the image arrives.
 *
 * `aspect-ratio` rather than `height: auto` so the reserved box is correct even
 * for the moment before a lazily-loaded flag has been fetched, and `flex: none`
 * because a flag beside a long endonym ("Bahasa Indonesia") is the item flexbox
 * would otherwise choose to shrink to nothing. */
.flag {
  flex: none;
  width: 1.25rem;
  height: auto;
  aspect-ratio: 4 / 3;
  border-radius: 3px;
  /* Several of these are a plain colour field with no light of their own. A
   * hairline keeps a pale flag -- the white bands of ID, RU or IN -- from
   * bleeding into the surface it sits on, in either theme. */
  box-shadow: 0 0 0 1px rgb(0 0 0 / 0.12);
}
.lang-button .flag { width: 1.125rem; }

/* The mobile menu is a checkbox and a label: no script, so it cannot arrive
 * late, and it keeps working if site.js fails to load. Both are absent above the
 * breakpoint, where the navigation is simply visible. */
.menu-toggle, .menu-button { display: none; }

/* --- Hero ---------------------------------------------------------------- */

.hero {
  padding: clamp(3rem, 1.5rem + 6vw, 5.75rem) 0 clamp(2.5rem, 1.5rem + 4vw, 4.25rem);
  background: var(--sz-surface);
  position: relative;
  isolation: isolate;
  border-bottom: 1px solid var(--sz-border);
}
/* `.hero-wide` used to promote `--wrap` to `--wrap-wide` here, and the two
   council/plans bands did the same below. That gave one page a 1360px content
   column and the next one 1200px, so a reader moving from /how-it-works/ to
   /pricing/ watched the heading jump 80px left at a 1440px viewport -- on pages
   whose whole claim is that they came from one design system. The wide bands
   are the ones with a product visual or four price cards, which is exactly the
   content a reader compares *across* pages, so they were the worst possible
   candidates for a different measure.

   One width now. `.hero-wide` still exists and still marks the heroes that
   carry a visual, because `.hero-grid` reads it for its column split. */
.hero-grid {
  display: grid;
  /* `minmax(0, 1fr)` and not `1fr`: a grid track's automatic minimum is its
   * content's min-content width, so a heading containing one long word makes
   * the track wider than the container and the whole page scrolls sideways. */
  grid-template-columns: minmax(0, 1fr);
  gap: clamp(2rem, 1rem + 3vw, 3.5rem);
  align-items: center;
}
.hero-copy { min-width: 0; }
.hero h1 { max-width: 22ch; }
/* Beside a visual the grid track is already the constraint, and a second cap
 * broke the headline into four short lines. */
.hero-wide .hero-copy h1 { max-width: none; }
.lede {
  font-size: clamp(1.0625rem, 1rem + 0.45vw, 1.3125rem);
  color: var(--sz-text-muted);
  max-width: 54ch;
  margin-bottom: 1.75rem;
}
/* The two hero actions, equal width below the breakpoint.
 *
 * They were shrink-to-fit, so their width was whatever their translated label
 * happened to be: "Open the Mini App" rendered noticeably wider than "See
 * pricing", and the pair read as two unrelated controls that had been placed by
 * accident rather than as a primary and its alternative. It is worse in the
 * locales with longer strings, and worst in the ones where the *secondary* is
 * the longer of the two, which inverts the visual hierarchy outright.
 *
 * Stacked rather than sharing one row. Sharing gave each about 170px at 390px,
 * which wrapped "Open Signalyze in Telegram" onto three lines -- equal widths,
 * but a worse object than the ragged pair they replaced. A full-width row each
 * is the standard mobile pattern and the only one where every locale's label
 * fits on one line. Above 30em they return to their natural widths, where there
 * is room for the primary to be the larger object on purpose.
 */
.hero-actions { display: flex; flex-wrap: wrap; gap: 0.75rem; }
.hero-actions .button { flex: 1 1 100%; min-width: 0; }
@media (min-width: 30em) {
  .hero-actions .button { flex: 0 0 auto; }
}

.venue-lead {
  margin: 1.75rem 0 0.5rem;
  font-size: 0.75rem;
  font-weight: 650;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--sz-text-muted);
}
.venue-strip {
  display: flex;
  flex-wrap: wrap;
  gap: 0.5rem;
  margin: 0;
  padding: 0;
  list-style: none;
}
.venue-strip li {
  padding: 0;
  border: 1px solid var(--sz-border);
  border-radius: 999px;
  background: var(--sz-surface-raised);
  font-size: 0.8125rem;
  font-weight: 600;
  color: var(--sz-text);
}
/* The pill's padding lives on the link, not the list item, so the whole pill is
 * the hit target rather than only the text inside it -- 36px tall and about
 * 110px wide as measured, comfortably past WCAG 2.2 SC 2.5.8's 24px minimum at
 * AA. It is deliberately not stretched to the 44px of SC 2.5.5 (AAA): these sit
 * under the hero as supporting context, and three 44px pills would compete with
 * the call to action directly above them. */
.venue-link {
  display: flex;
  align-items: center;
  gap: 0.45rem;
  min-height: 2.25rem;
  padding: 0.3rem 0.8rem;
  border-radius: inherit;
  color: inherit;
  text-decoration: none;
}
/* The publisher's own artwork, at a shared height so three very different grids
 * read as one row. No `color` rule: these are images now, in the colours their
 * owners published, which is the whole point of the change -- a `filter` or a
 * `color` here would be the monochrome treatment coming back by another door.
 *
 * The height is set here and not left to the element's own `height` attribute,
 * because `img, svg, video { height: auto }` at the top of this file is a
 * presentational reset that outranks it: without this rule Binance rendered at
 * its intrinsic 150px and Bybit's logotype collapsed to nothing. Height rather
 * than width for the reason the inline marks used it -- a diamond, a wide
 * logotype and a squat mountain only read as a set when their heights match. */
.venue-link .venue-art { flex: none; width: auto; height: 1.125rem; }
a.venue-link:hover { background: var(--sz-surface-sunken); }
.venue-name { white-space: nowrap; }
/* The affiliate disclosure. Deliberately legible rather than hidden: a referral
 * link a reader cannot tell is one is the thing worth designing against. */
.venue-note {
  margin: 0.6rem 0 0;
  font-size: 0.75rem;
  color: var(--sz-text-muted);
}

/* The product, as one object, beside the headline. */
.hero-visual { min-width: 0; display: flex; justify-content: center; }
.signal-card {
  width: min(100%, 26rem);
  padding: 1.25rem;
  background: var(--sz-surface-raised);
  border: 1px solid var(--card-edge);
  border-radius: var(--radius);
  box-shadow: 0 24px 60px rgb(0 0 0 / 0.12), var(--card-lift);
}
/* The card in the hero is a mocked trade, and at that size beside a headline it
 * is the most screenshot-like thing on the site. The label says so. Quiet, but
 * never hidden: a disclaimer that only appears on hover is not a disclaimer. */
.signal-example {
  margin: 0 0 0.6rem;
  font-size: 0.6875rem;
  font-weight: 700;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--sz-text-muted);
}
.tag {
  padding: 0.1rem 0.45rem;
  border-radius: 6px;
  font-size: 0.6875rem;
  font-weight: 700;
  letter-spacing: 0.06em;
}
.tag-long { background: color-mix(in srgb, var(--sz-win) 16%, transparent); color: var(--sz-win); }
.tag-short { background: color-mix(in srgb, var(--sz-loss) 16%, transparent); color: var(--sz-loss); }

.result-rows { margin: 0; }
.result-rows > div {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 1rem;
  padding: 0.55rem 0;
  border-bottom: 1px solid var(--sz-border);
}
.result-rows > div:last-child { border-bottom: 0; }
.result-rows dt {
  font-size: 0.8125rem;
  color: var(--sz-text-muted);
  flex: none;
}
.result-rows dd {
  margin: 0;
  font-variant-numeric: tabular-nums;
  font-weight: 600;
  font-size: 0.9375rem;
  text-align: end;
  overflow-wrap: anywhere;
}
.signal-foot {
  display: flex;
  align-items: center;
  gap: 0.45rem;
  margin: 0.85rem 0 0;
  padding-top: 0.85rem;
  border-top: 1px solid var(--sz-border);
  font-size: 0.8125rem;
  font-weight: 600;
  color: var(--sz-win);
}

/* --- The example signals --------------------------------------------------
 *
 * Three tabs over three panels, in the shape the Telegram message has: a pair,
 * a direction, the metadata rows, then one labelled group of bullets per
 * section. Every panel is in the document and only the inactive ones carry
 * `hidden`, so this reads as three examples with no script at all.
 *
 * The prices are `tabular-nums` and `dir="ltr"` inside a `<bdi>`, so a column
 * of levels lines up and reads left to right on the Arabic page, where the
 * surrounding text does not. */
.sx-tabs {
  display: flex;
  gap: 0.3rem;
  margin-bottom: 0.9rem;
  padding: 0.25rem;
  background: var(--sz-surface-sunken);
  border-radius: var(--radius-sm);
}
.sx-tab {
  flex: 1 1 0;
  min-width: 0;
  padding: 0.45rem 0.4rem;
  border: 0;
  border-radius: calc(var(--radius-sm) - 3px);
  background: transparent;
  color: var(--sz-text-muted);
  font: inherit;
  font-size: 0.75rem;
  font-weight: 650;
  line-height: 1.25;
  cursor: pointer;
}
.sx-tab[aria-selected="true"] {
  background: var(--sz-surface-raised);
  color: var(--sz-text);
  box-shadow: var(--card-lift);
}
.sx-head {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  flex-wrap: wrap;
  margin: 0 0 0.75rem;
}
.sx-pair { font-weight: 700; letter-spacing: 0.01em; }
.sx-metas {
  margin: 0 0 0.9rem;
  padding-bottom: 0.75rem;
  border-bottom: 1px solid var(--sz-border);
}
.sx-meta {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 1rem;
  padding: 0.2rem 0;
}
.sx-meta dt { font-size: 0.8125rem; color: var(--sz-text-muted); flex: none; }
.sx-meta dd {
  margin: 0;
  font-size: 0.875rem;
  font-weight: 600;
  text-align: end;
  overflow-wrap: anywhere;
}
.sx-meta code {
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  font-size: 0.8125rem;
}
.sx-block { margin-bottom: 0.8rem; }
.sx-label {
  margin: 0 0 0.3rem;
  font-size: 0.6875rem;
  font-weight: 700;
  letter-spacing: 0.07em;
  text-transform: uppercase;
  color: var(--sz-text-muted);
}
.sx-block ul { list-style: none; margin: 0; padding: 0; }
/* A real bullet, because the message has one and because a list of levels with
 * no marker reads as a paragraph that happens to wrap. */
.sx-block li {
  position: relative;
  padding-inline-start: 0.85rem;
  padding-block: 0.1rem;
  font-variant-numeric: tabular-nums;
  font-weight: 600;
  font-size: 0.9375rem;
}
.sx-block li::before {
  content: "";
  position: absolute;
  inset-inline-start: 0;
  top: 0.62em;
  width: 0.3rem;
  height: 0.3rem;
  border-radius: 999px;
  background: var(--sz-accent);
}
.sx-news {
  display: flex;
  align-items: baseline;
  flex-wrap: wrap;
  gap: 0.4rem;
  margin: 0.9rem 0 0;
  padding-top: 0.75rem;
  border-top: 1px solid var(--sz-border);
  font-size: 0.8125rem;
}
.sx-news-label { font-weight: 650; color: var(--sz-text-muted); }
.sx-news-score { font-weight: 700; font-variant-numeric: tabular-nums; }
.sx-news-band {
  padding: 0.05rem 0.4rem;
  border-radius: 6px;
  font-size: 0.6875rem;
  font-weight: 700;
  letter-spacing: 0.04em;
}
.sx-band-bullish { background: color-mix(in srgb, var(--sz-win) 16%, transparent); color: var(--sz-win); }
.sx-band-bearish { background: color-mix(in srgb, var(--sz-loss) 16%, transparent); color: var(--sz-loss); }
.sx-band-mixed { background: var(--sz-surface-sunken); color: var(--sz-text-muted); }
.sx-news-items { width: 100%; color: var(--sz-text-muted); }
.sx-note {
  margin: 0.85rem 0 0;
  font-size: 0.75rem;
  line-height: 1.5;
  color: var(--sz-text-muted);
}

/* --- Bands --------------------------------------------------------------- */

.band { padding: var(--band) 0; }
.band-tight { padding: var(--band-tight) 0; }
.band-alt { background: var(--sz-surface-sunken); }
/* The council and the pricing row were promoted to a wider container from here,
 * outside `.wrap` -- a custom property inherits downwards, so declaring it on
 * the section itself did nothing and both rows had silently shipped at 1200px.
 * The promotion is gone rather than fixed: see `.hero-wide` above. Every band
 * on every page now resolves the same `--wrap`, which is what makes "one design
 * system" a property of the stylesheet rather than a claim about it. */
.band-cta {
  background: var(--sz-surface-sunken);
  border-block: 1px solid var(--sz-border);
  text-align: center;
  padding: var(--band) 0;
}
.band-cta { position: relative; isolation: isolate; }
.hero::before,
.band-cta::before {
  content: "";
  position: absolute;
  inset: 0;
  z-index: -1;
  pointer-events: none;
}
.hero::before {
  background: radial-gradient(64rem 30rem at 50% -10rem,
    color-mix(in srgb, var(--sz-accent) 14%, transparent), transparent 70%);
}
.band-cta::before {
  background: radial-gradient(48rem 22rem at 50% 120%,
    color-mix(in srgb, var(--sz-accent) 10%, transparent), transparent 70%);
}

.band-cta p { max-width: 52ch; margin-inline: auto; }
.band-cta h2 { margin-bottom: 0.5rem; }

/* --- Cards --------------------------------------------------------------- */

.cards {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(100%, 17rem), 1fr));
  gap: 1.25rem;
}
/* Exactly four peers go two-by-two rather than three-and-an-orphan. */
.cards:has(> :nth-child(4):last-child) { grid-template-columns: repeat(2, minmax(0, 1fr)); }
@media (max-width: 47.99em) {
  .cards:has(> :nth-child(4):last-child) { grid-template-columns: 1fr; }
}
.card {
  padding: clamp(1.25rem, 1rem + 0.8vw, 1.75rem);
  background: var(--sz-surface-raised);
  border: 1px solid var(--card-edge);
  border-radius: var(--radius);
  box-shadow: var(--card-lift);
  transition: border-color 160ms cubic-bezier(.25,.46,.45,.94),
    background-color 160ms cubic-bezier(.25,.46,.45,.94),
    transform 160ms cubic-bezier(.25,.46,.45,.94);
}
.card:hover {
  border-color: color-mix(in srgb, var(--sz-accent) 45%, transparent);
  transform: translateY(-2px);
}
.card h3 { color: var(--sz-text); }
.card p { color: var(--sz-text-muted); margin: 0; }
.card-icon {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 2.75rem;
  height: 2.75rem;
  margin-bottom: 1rem;
  border-radius: 12px;
  background: color-mix(in srgb, var(--sz-accent) 14%, transparent);
  border: 1px solid color-mix(in srgb, var(--sz-accent) 30%, transparent);
  color: var(--sz-accent-text);
}

/* --- Steps --------------------------------------------------------------- */


/* --- The council ---------------------------------------------------------
 *
 * Three seats that have to read as one body. The rail is a single line drawn
 * behind the row on desktop: three cards floating in a grid would illustrate
 * three unrelated opinions, which is the opposite of the claim. It disappears
 * when the cards stack, where a horizontal connector would connect nothing. */
.council {
  position: relative;
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  gap: 1.25rem;
}
.council-rail { display: none; }
.seat {
  position: relative;
  display: flex;
  flex-direction: column;
  padding: 1.6rem;
  background: var(--sz-surface-raised);
  border: 1px solid var(--card-edge);
  border-radius: var(--radius);
}
/* One accent line along the top of each seat, in three steps of the same gold
 * ramp, so the three are visibly a set and visibly distinguishable. */
.seat::before {
  content: "";
  position: absolute;
  inset-inline: 1.6rem;
  top: -1px;
  height: 2px;
  border-radius: 2px;
}
.seat-guardian::before { background: var(--sz-gold-700); }
.seat-maverick::before { background: var(--sz-gold-500); }
.seat-arbiter::before { background: var(--sz-gold-300); }
/* The persona emblem. Bigger than the interface icon it replaced and with no
 * disc behind it: these are rendered gold objects on a transparent ground, and
 * a tinted circle under one reads as a button rather than as a character.
 *
 * Height is set here rather than left to the `height` attribute because the
 * shared `img, svg, video { height: auto }` rule outranks the attribute -- the
 * same trap the exchange logotypes hit. The three emblems are drawn to one
 * canvas at one coverage, so a single height keeps them optically equal even
 * though the balance is wider than the shield is. */
.seat-emblem {
  width: auto;
  height: 5rem;
  margin-bottom: 0.9rem;
  align-self: flex-start;
}
.seat h3 { margin-bottom: 0.15rem; font-size: 1.1875rem; }
.seat-role {
  margin: 0 0 1rem;
  font-size: 0.8125rem;
  font-weight: 650;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: var(--sz-accent-text);
}
.seat-quote {
  margin: 0 0 1.1rem;
  padding-inline-start: 0.9rem;
  border-inline-start: 2px solid color-mix(in srgb, var(--sz-accent) 45%, transparent);
}
.seat-quote p {
  margin: 0;
  color: var(--sz-text);
  font-size: 0.9375rem;
  line-height: 1.6;
  font-style: italic;
}
.seat-traits {
  display: flex;
  flex-wrap: wrap;
  gap: 0.4rem;
  margin: auto 0 0;
  padding: 0;
  list-style: none;
}
.seat-traits li {
  padding: 0.25rem 0.6rem;
  border: 1px solid var(--sz-border);
  border-radius: 999px;
  background: var(--sz-surface-sunken);
  font-size: 0.75rem;
  font-weight: 600;
  color: var(--sz-text-muted);
}

@media (min-width: 62em) {
  .council { grid-template-columns: repeat(3, minmax(0, 1fr)); }
  .council-rail {
    display: block;
    position: absolute;
    top: 3.75rem;
    inset-inline: 12%;
    height: 1px;
    background: linear-gradient(90deg,
      transparent,
      color-mix(in srgb, var(--sz-accent) 55%, transparent) 20%,
      color-mix(in srgb, var(--sz-accent) 55%, transparent) 80%,
      transparent);
  }
  .seat { z-index: 1; }
}

/* --- Infrastructure ------------------------------------------------------ */

.band-infra {
  background:
    radial-gradient(48rem 24rem at 15% 0%,
      color-mix(in srgb, var(--sz-accent) 10%, transparent), transparent 70%),
    var(--sz-surface);
  border-block: 1px solid var(--sz-border);
}
.infra {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  gap: clamp(1.75rem, 1rem + 2.5vw, 3rem);
  align-items: center;
}
.infra-copy h2 { margin-bottom: 0.6rem; }
.infra-stats {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(100%, 12rem), 1fr));
  gap: 1rem;
  margin: 0;
  padding: 0;
  list-style: none;
}
.infra-stats li {
  padding: 1.25rem;
  background: var(--sz-surface-raised);
  border: 1px solid var(--card-edge);
  border-radius: var(--radius);
}
.infra-stats h3 { margin-bottom: 0.3rem; font-size: 1.0625rem; }
.infra-stats p { margin: 0; font-size: 0.875rem; color: var(--sz-text-muted); }
.stat-icon {
  display: inline-flex;
  margin-bottom: 0.75rem;
  color: var(--sz-accent-text);
}

/* --- Result cards -------------------------------------------------------- */
/*
 * A tab row over a laptop, and the laptop's screen holds the PnL card this
 * service actually sends. The frame is drawn here rather than shipped as an
 * image: a device mock-up is four rectangles and a radius, and a photograph of a
 * laptop would be somebody else's artwork, a large download, and a fixed screen
 * aspect our card would have to be letterboxed into anyway.
 *
 * The tab row is a real `role="tablist"` and `site.js` drives it. Before that
 * file runs, every panel is visible and the frame simply scrolls -- a reader
 * with no JavaScript sees six results rather than one result and five dead
 * buttons.
 */

.showcase { display: grid; gap: 1rem; }

/* The six selectors are a *deck*: overlapped, each tilted a degree or two off
 * true, with the chosen one lifted square to the front. Six pill buttons in a
 * row read as a filter bar, which is what they were; six overlapping cards read
 * as a hand of results, which is what they are.
 *
 * Built entirely out of the tab row that was already here, and deliberately so.
 * The panels keep `hidden` and keep `role="tabpanel"`, so the accessibility
 * contract -- one panel in the tree, arrow keys moving between tabs -- is
 * untouched; a deck that mounted all six panels at once to fan them would put
 * five hidden results into the reading order to buy a visual effect. Nothing
 * here is a new image either: the fan is the buttons that already carried the
 * pair and the move.
 *
 * `padding-block` on the row absorbs the rotation. A rotated box is wider and
 * taller than its layout box, and without the padding the top card's corner
 * clips against the section above -- and the extra width is exactly what the
 * browser sweep's right-edge guard exists to catch.
 */
.shot-tabs {
  display: flex;
  flex-wrap: wrap;
  gap: 0.5rem;
  padding-block: 0.35rem;
}
.shot-tab {
  display: inline-flex;
  align-items: baseline;
  gap: 0.5rem;
  padding: 0.45rem 0.85rem;
  border: 1px solid var(--sz-border);
  border-radius: 10px;
  background: var(--sz-surface-raised);
  color: var(--sz-text-muted);
  font: inherit;
  font-size: 0.8125rem;
  font-weight: 650;
  cursor: pointer;
  position: relative;
  /* Later cards sit under earlier ones by default, which is what makes the
   * overlap read as a stack rather than as a mistake. */
  z-index: 1;
  transition:
    border-color 160ms ease,
    color 160ms ease,
    background 160ms ease,
    transform 200ms var(--ease-out),
    box-shadow 200ms var(--ease-out);
}
/* The fan. Odd and even cards tilt opposite ways and the overlap is a negative
 * margin rather than a translate, so the row's own width shrinks with it and a
 * narrow viewport still wraps instead of overflowing. */
.shot-tab:nth-child(odd) { transform: rotate(-1.4deg); }
.shot-tab:nth-child(even) { transform: rotate(1.1deg); }
.shot-tab + .shot-tab { margin-inline-start: -0.75rem; }
.shot-tab:hover,
.shot-tab:focus-visible {
  color: var(--sz-text);
  z-index: 3;
}
/* Click to front: the chosen card straightens, lifts, and stops being one of
 * the fan. The z-index is what makes "front" literal rather than implied. */
.shot-tab[aria-selected="true"] {
  color: var(--sz-text);
  border-color: var(--sz-accent);
  background: color-mix(in srgb, var(--sz-accent) 12%, var(--sz-surface-raised));
  z-index: 4;
  box-shadow: 0 6px 18px -10px rgb(0 0 0 / 45%);
}
.shot-tab[aria-selected="true"]:nth-child(odd),
.shot-tab[aria-selected="true"]:nth-child(even) {
  transform: rotate(0deg) translateY(-3px);
}
/* Reduced motion keeps the *shape* and drops the movement. A static rotation is
 * not animation, and flattening the deck would remove the thing the section is
 * for; what a reader who asked for less motion must not get is the lift and the
 * transition, and the global guard at the top of this file already zeroes every
 * transition-duration. The transform is restated here so the chosen card is
 * still square-on rather than only differing by a shadow. */
@media (prefers-reduced-motion: reduce) {
  .shot-tab[aria-selected="true"]:nth-child(odd),
  .shot-tab[aria-selected="true"]:nth-child(even) {
    transform: rotate(0deg);
  }
}
.shot-pair { font-variant-numeric: tabular-nums; }
/* The move on the tab is the same figure the panel prints, in the same colour,
 * so the row reads as a set of outcomes rather than a set of tickers. */
.shot-move { font-variant-numeric: tabular-nums; font-weight: 700; }
.shot-win { color: var(--sz-win); }
.shot-loss { color: var(--sz-loss); }

/* The frame. `--lid` is the bezel, and the base below it is what makes the
 * silhouette read as a laptop rather than as a bordered box. */
.laptop { display: grid; justify-items: center; }
.laptop-lid {
  position: relative;
  width: 100%;
  padding: 1.25rem 1.25rem 1.5rem;
  background: linear-gradient(180deg, #2A2A2A, #171717);
  border: 1px solid #3A3A3A;
  border-radius: 18px 18px 6px 6px;
  box-shadow: 0 24px 60px rgb(0 0 0 / 0.28);
}
.laptop-cam {
  position: absolute;
  top: 0.55rem;
  inset-inline: 0;
  margin-inline: auto;
  width: 6px;
  height: 6px;
  border-radius: 50%;
  background: #4A4A4A;
}
/* The foot: the full width of the lid and much shallower, which is the whole of
 * the shape. Deliberately not wider than the lid, however much a real laptop is:
 * the wrap is already at the viewport edge on a phone, and an element that
 * overhangs it is the one that gives the page a horizontal scrollbar. */
.laptop-foot {
  width: 100%;
  height: 12px;
  background: linear-gradient(180deg, #3A3A3A, #202020);
  border-radius: 0 0 12px 12px;
  box-shadow: 0 10px 24px rgb(0 0 0 / 0.25);
}
.laptop-screen {
  display: grid;
  justify-items: center;
  gap: 1.25rem;
  padding: 1.25rem;
  background: var(--sz-ink-900);
  border-radius: 10px;
  overflow: hidden;
}
/* Once `site.js` has taken over, one panel is mounted at a time and it fades in
 * rather than snapping. The animation is defined here so the reduced-motion
 * block at the top of this sheet already governs it. */
.laptop-screen > .result { animation: shot-in 220ms ease both; }
@keyframes shot-in { from { opacity: 0; transform: translateY(6px); } }

.result {
  display: grid;
  gap: 1.25rem;
  align-items: center;
  grid-template-columns: minmax(0, 1fr);
}
@media (min-width: 48em) {
  /* The card is the evidence, so it takes the larger share of the screen and
   * the caption is capped at a reading measure rather than stretched across
   * whatever is left. */
  .result { grid-template-columns: minmax(0, 22rem) minmax(0, 32rem); gap: 2rem; }
}
/* No aspect ratio declared, because the card no longer has one this sheet can
 * name: these are the exchanges' own share-position cards, and the three
 * artworks are 992x579, 960x1320 and 1005x1178. The `width` and `height`
 * attributes on the element carry the real ones, so the browser reserves the
 * right box before the image arrives without this having to know which
 * exchange it was drawn for. */
.result-shot {
  width: 100%;
  height: auto;
  border-radius: 12px;
  border: 1px solid #2E2E2E;
}
/* The caption sits on the dark screen with the card, so its text tokens are the
 * dark-theme ones regardless of the reader's scheme. */
.result-meta { color: #F5F3EF; min-width: 0; }
/* The card's provenance line: what this is, and when it happened.
 *
 * In normal flow rather than absolutely positioned in the corner. It used to
 * hold one short word, so a corner badge worked and `.result-head` reserved 5rem
 * for it. It now carries a label *and* a date, which is wider than that reserve
 * in English and wider still in the locales whose label is a whole word, and it
 * overlapped the leverage badge underneath it. A row that shares the card's full
 * width cannot collide with anything and needs no magic number kept in sync.
 */
.result-tag {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 0.5rem;
  margin: 0 0 0.75rem;
  font-size: 0.625rem;
  font-weight: 700;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--sz-text-muted);
}
.result-label {
  padding: 0.1rem 0.45rem;
  border: 1px solid var(--sz-border);
  border-radius: 6px;
}
/* Tabular so the six dates form a column rather than a ragged edge, and
 * `ltr` because a date is read left to right even on an Arabic page. */
.result-date {
  font-variant-numeric: tabular-nums;
  letter-spacing: 0.04em;
  direction: ltr;
}
.result-head {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  flex-wrap: wrap;
}
.result-pair { font-weight: 700; }
.result-lev { font-size: 0.8125rem; font-weight: 650; color: var(--sz-text-muted); }
.result-change {
  margin: 0.5rem 0 0.9rem;
  font-size: clamp(1.75rem, 1.4rem + 1.4vw, 2.25rem);
  font-weight: 700;
  letter-spacing: -0.03em;
  font-variant-numeric: tabular-nums;
  line-height: 1.1;
}
/* Fixed rather than themed, for the same reason `.result-meta` is: these two
 * sit on the laptop's own dark screen in both schemes, and the light-theme pair
 * (#15704F / #9A2018) is unreadable on #141414. These are the dark-theme values
 * from `tokens.css`, which `tests/test_brand.py` already measures against it. */
.result-win .result-change { color: #4ED6A1; }
.result-loss .result-change { color: #FF7A6B; }
.result-meta .result-tag,
.result-meta .result-lev { color: #A8A29A; }
.result-meta .result-label { border-color: #3A3A3A; }
.result-meta .result-rows dt { color: #A8A29A; }
.result-meta .result-rows > div { border-bottom-color: #2E2E2E; }
.result-meta .tag-long { background: color-mix(in srgb, #4ED6A1 16%, transparent); color: #4ED6A1; }
.result-meta .tag-short { background: color-mix(in srgb, #FF7A6B 16%, transparent); color: #FF7A6B; }

/* --- Pricing ------------------------------------------------------------- */

.plans {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(100%, 15rem), 1fr));
  gap: 1.25rem;
  align-items: stretch;
}
.plan {
  position: relative;
  display: flex;
  flex-direction: column;
  padding: 1.6rem;
  background: var(--sz-surface-raised);
  border: 1px solid var(--card-edge);
  border-radius: var(--radius);
}
/* The recommended plan is emphasised by weight and border, not by size: a card
 * that is physically taller than its neighbours breaks the row on every
 * viewport where the grid wraps. */
/* Bordered and washed, not filled. A 1360px row with one card flooded in
 * #DFAE45 would out-shout the logo and every other gold element on the page,
 * and gold is the brand rather than the emphasis. */
.plan-featured {
  border-color: var(--sz-accent);
  background-image: radial-gradient(120% 80% at 50% 0%,
    color-mix(in srgb, var(--sz-accent) 12%, transparent), transparent 70%);
  box-shadow: 0 0 0 1px color-mix(in srgb, var(--sz-accent) 30%, transparent);
}
.plan-flag {
  position: absolute;
  top: -0.75rem;
  inset-inline-start: 1.6rem;
  margin: 0;
  padding: 0.15rem 0.6rem;
  border-radius: 999px;
  background: var(--sz-accent);
  color: var(--sz-on-accent);
  font-size: 0.6875rem;
  font-weight: 700;
  letter-spacing: 0.06em;
  text-transform: uppercase;
}
/* The plan's package art: the same file the Mini App's own pricing screen draws.
 *
 * Sized as a thumbnail rather than the full-width square the app gives it. Four
 * of these sit in a row here and one fills a phone there, so the same asset has
 * to be a header in one place and an object in the other. Height rather than
 * width, because the four renders differ in how much of their square they fill
 * and a shared height is what makes them a set.
 *
 * `height` in CSS, not left to the element's attribute: `img, svg, video
 * { height: auto }` at the top of this file outranks the attribute, which is
 * the same reset that flattened the venue marks. */
.plan-art {
  display: block;
  width: auto;
  height: 6.5rem;
  margin: 0 auto 0.75rem;
  object-fit: contain;
}
.plan-name { margin-bottom: 0.5rem; font-size: 1.0625rem; }
.plan-price {
  display: flex;
  align-items: baseline;
  flex-wrap: wrap;
  gap: 0.4rem;
  margin-bottom: 0.85rem;
}
.plan-amount {
  font-size: 2.25rem;
  font-weight: 700;
  letter-spacing: -0.035em;
  font-variant-numeric: tabular-nums;
  line-height: 1;
  color: var(--sz-text);
}
.plan-period { font-size: 0.875rem; color: var(--sz-text-muted); }
.plan-body {
  color: var(--sz-text-muted);
  font-size: 0.9375rem;
  margin-bottom: 1.35rem;
}
.plan .button { margin-top: auto; width: 100%; }
.plans-note {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 0.5rem;
  margin: 1.5rem 0 0;
  color: var(--sz-text-muted);
  font-size: 0.9375rem;
  text-align: center;
}
.plans-note .icon { color: var(--sz-accent-text); }

/* --- Referral -------------------------------------------------------------
 * Three steps and three rungs. The steps are an ordered list because they are
 * one: a reader who lands on this section with a screen reader should hear
 * "1 of 3" rather than three unrelated headings.
 *
 * The medallions are the Earn screen's own, which is the point -- a reader who
 * follows the button finds the same three objects on the other side. */
.ref-steps {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(100%, 16rem), 1fr));
  gap: 1.25rem;
  margin: 2rem 0 0;
  padding: 0;
  list-style: none;
  counter-reset: none;
}
.ref-step { display: flex; align-items: flex-start; gap: 0.9rem; }
.ref-num {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  flex: none;
  width: 2rem;
  height: 2rem;
  border-radius: 50%;
  background: color-mix(in srgb, var(--sz-accent) 16%, transparent);
  border: 1px solid color-mix(in srgb, var(--sz-accent) 34%, transparent);
  color: var(--sz-accent-text);
  font-weight: 700;
  font-variant-numeric: tabular-nums;
}
.ref-step-text { display: flex; flex-direction: column; gap: 0.2rem; min-width: 0; }
.ref-step-title { font-weight: 650; }
.ref-step-body { color: var(--sz-text-muted); font-size: 0.9375rem; line-height: 1.45; }

.ref-eyebrow {
  margin: 2.25rem 0 0.75rem;
  font-size: 0.6875rem;
  font-weight: 700;
  letter-spacing: 0.09em;
  text-transform: uppercase;
  color: var(--sz-text-muted);
}
.ref-ladder {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(100%, 12rem), 1fr));
  gap: 1rem;
  margin: 0;
  padding: 0;
  list-style: none;
}
.ref-rung {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 0.15rem;
  padding: 1.1rem 1rem 1.25rem;
  background: var(--sz-surface-raised);
  border: 1px solid var(--card-edge);
  border-radius: var(--radius);
  text-align: center;
}
/* Height in CSS for the reason the venue marks needed it: the reset at the top
 * of this file sets `height: auto` on every image and outranks the attribute. */
.ref-medal { display: block; width: auto; height: 3.25rem; margin-bottom: 0.4rem; }
.ref-rung-name { font-weight: 650; }
.ref-rate {
  font-size: 1.75rem;
  font-weight: 700;
  letter-spacing: -0.03em;
  font-variant-numeric: tabular-nums;
  color: var(--sz-text);
  line-height: 1.1;
}
.ref-rung-from { color: var(--sz-text-muted); font-size: 0.8125rem; }
.ref-note { margin: 1rem 0 0; color: var(--sz-text-muted); font-size: 0.9375rem; }
.ref-action { margin: 1.5rem 0 0; }

/* --- Community ----------------------------------------------------------- */

.channels {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(100%, 17rem), 1fr));
  gap: 0.9rem;
  margin: 0;
  padding: 0;
  list-style: none;
}
.channel {
  display: flex;
  align-items: center;
  gap: 0.9rem;
  min-height: 4.25rem;
  padding: 0.9rem 1.1rem;
  background: var(--sz-surface-raised);
  border: 1px solid var(--card-edge);
  border-radius: var(--radius);
  color: var(--sz-text);
  text-decoration: none;
  transition: border-color 140ms ease, background-color 140ms ease;
}
.channel:hover {
  border-color: var(--sz-accent);
  background: color-mix(in srgb, var(--sz-accent) 7%, var(--sz-surface-raised));
  color: var(--sz-text);
}
.channel-icon {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 2.5rem;
  height: 2.5rem;
  flex: none;
  border-radius: 12px;
  background: var(--sz-surface-sunken);
  border: 1px solid var(--sz-border);
  color: var(--sz-text);
}
.channel-text { display: grid; min-width: 0; }
.channel-name { font-weight: 650; font-size: 0.9375rem; }
.channel-body {
  font-size: 0.8125rem;
  color: var(--sz-text-muted);
  overflow-wrap: anywhere;
}
.channel-go { margin-inline-start: auto; color: var(--sz-text-muted); flex: none; }
.channel:hover .channel-go { color: var(--sz-accent-text); }

/* --- Prose, FAQ, notices ------------------------------------------------- */

/* The measure is capped, the container is not.
 *
 * Narrowing `--wrap` to the text width centred the whole block, so a prose
 * section started 95px to the right of the section above it and the page had two
 * left edges. On /contact/ that put "Message us on Telegram" visibly out of line
 * with the community grid over it; on /terms/ every heading sat indented from
 * the `<h1>`. Capping the children instead keeps one left edge on every page
 * while a paragraph still stops at a readable 720px, which is what
 * `.section-head` already does everywhere else. */
/* Prose elements, not every child. `_faq_section` puts `<div class="faq">` --
 * a column of disclosure cards -- directly inside `.wrap.prose`, and the
 * universal selector capped that whole structural block at the text measure
 * too. At 1440px the FAQ page's only content section therefore ended 600px
 * short of the hero and CTA bands bracketing it. A row of cards is structure
 * and takes `--wrap`; a paragraph is a measure and takes `--wrap-text`. */
.prose > p,
.prose > ul,
.prose > ol,
.prose > .section-head { max-width: var(--wrap-text); }
/* The measure still applies to the answer text, one level further in, so the
 * card border reaches the structural edge while the prose inside it does not. */
.qa > p { max-width: var(--wrap-text); }
.prose p { color: var(--sz-text-muted); }
.prose h2 { color: var(--sz-text); }

/* A legal page is a run of short prose sections, and a full band of padding on
 * each side of every one put 192px between a three-line paragraph and the next
 * heading -- a page of terms that is mostly whitespace, and three times longer
 * than the text in it. Consecutive prose bands share one gap instead of
 * stacking two. */
.band:has(> .prose) { padding-block: var(--band-tight); }
.band:has(> .prose) + .band:has(> .prose) { padding-top: 0; }

/* Both of these carried a 3px gold rule down one edge. It is the single most
 * recognisable tell of a generated interface, and it was doing a job something
 * else already does better: the notice has an accent icon and the question has
 * a heading, so the edge was decoration claiming to be emphasis. One hairline
 * all the way round, and the accent stays where it means something. */
/* Disclosure cards. The marker is drawn by us rather than by the UA, because
   the native triangle differs on every platform and sits outside the padding. */
.faq { display: grid; gap: 0.75rem; margin-top: 2rem; }
.qa {
  background: var(--sz-surface-raised);
  border: 1px solid var(--card-edge);
  border-radius: var(--radius-sm);
  box-shadow: var(--card-lift);
  transition: border-color 140ms ease, box-shadow 140ms ease;
}
.qa[open] { border-color: color-mix(in srgb, var(--sz-accent) 40%, var(--card-edge)); }
.qa:hover { border-color: color-mix(in srgb, var(--sz-accent) 28%, var(--card-edge)); }
.qa > summary {
  display: flex;
  align-items: flex-start;
  gap: 0.75rem;
  padding: 1.1rem 1.25rem;
  cursor: pointer;
  list-style: none;
  /* The whole row is the control, so the focus ring has to be on the row. */
  border-radius: var(--radius-sm);
}
.qa > summary::-webkit-details-marker { display: none; }
.qa > summary:focus-visible {
  outline: 2px solid var(--sz-accent);
  outline-offset: 2px;
}
.qa > summary h3 {
  color: var(--sz-text);
  margin: 0;
  font-size: 1.0625rem;
  flex: 1;
}
/* A chevron that turns, drawn in CSS so it needs no icon and no script. */
.qa > summary::after {
  content: "";
  flex: none;
  width: 0.55rem;
  height: 0.55rem;
  margin-top: 0.4rem;
  border-right: 2px solid var(--sz-text-muted);
  border-bottom: 2px solid var(--sz-text-muted);
  transform: rotate(45deg);
  transition: transform 140ms ease;
}
.qa[open] > summary::after { transform: rotate(-135deg); }
.qa > p { margin: 0; padding: 0 1.25rem 1.15rem; color: var(--sz-text-muted); }
[dir="rtl"] .qa > summary::after { transform: rotate(135deg); }
[dir="rtl"] .qa[open] > summary::after { transform: rotate(-135deg); }

.notice {
  display: flex;
  gap: 0.9rem;
  padding: 1.35rem;
  background: var(--sz-surface-raised);
  border: 1px solid var(--card-edge);
  border-radius: var(--radius-sm);
  box-shadow: var(--card-lift);
}
.notice-icon { color: var(--sz-accent-text); flex: none; padding-top: 0.15rem; }
.notice h2 { font-size: 1.0625rem; margin-bottom: 0.4rem; }
/* The card is structure and reaches `--wrap` like the grids above and below it;
 * the prose inside it keeps a measure, the same way `.qa > p` does. Capping the
 * card itself was the defect: `max-width: 56rem` matched neither `--wrap` (1200)
 * nor `--wrap-text` (720), so at 1440px a notice ended 424px short of the
 * section above it and the page showed three different right edges. */
.notice p { color: var(--sz-text-muted); margin-bottom: 0.6rem; font-size: 0.9375rem; }
.notice-body { min-width: 0; }
.notice-body > p { max-width: var(--wrap-text); }

/* --- Footer -------------------------------------------------------------- */

.site-footer {
  padding: clamp(2.75rem, 2rem + 2vw, 4rem) 0 6.5rem;  /* clears the sticky CTA */
  background: var(--sz-surface-sunken);
  border-top: 1px solid var(--sz-border);
  font-size: 0.9375rem;
}
.footer-cols {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(100%, 12rem), 1fr));
  gap: 2rem;
}
.footer-brand { max-width: 24rem; }
.footer-tagline { color: var(--sz-text-muted); margin-top: 0.75rem; }
.footer-since {
  display: inline-flex;
  align-items: center;
  gap: 0.4rem;
  margin: 0.9rem 0 0;
  padding: 0.25rem 0.7rem;
  border: 1px solid var(--sz-border);
  border-radius: 999px;
  background: var(--sz-surface-raised);
  font-size: 0.8125rem;
  font-weight: 600;
  color: var(--sz-text-muted);
}
.footer-since .icon { color: var(--sz-accent-text); }
.site-footer .footer-social {
  display: flex;
  flex-wrap: wrap;
  gap: 0.45rem;
  margin: 1.1rem 0 0;
  padding: 0;
  list-style: none;
}
.footer-social a {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 2.25rem;
  height: 2.25rem;
  border: 1px solid var(--sz-border);
  border-radius: 10px;
  background: var(--sz-surface-raised);
  color: var(--sz-text-muted);
}
.footer-social a:hover { color: var(--sz-text); border-color: var(--sz-accent); }
.site-footer h2 {
  font-size: 0.75rem;
  text-transform: uppercase;
  letter-spacing: 0.1em;
  color: var(--sz-text-muted);
  margin-bottom: 0.75rem;
}
.site-footer ul { list-style: none; margin: 0; padding: 0; display: grid; gap: 0.5rem; }
.site-footer .footer-group a { color: var(--sz-text); text-decoration: none; }
.site-footer .footer-group a:hover { text-decoration: underline; }

.footer-base {
  margin-top: clamp(2rem, 1.5rem + 1.5vw, 2.75rem);
  padding-top: 1.5rem;
  border-top: 1px solid var(--sz-border);
}
.footer-disclaimer { color: var(--sz-text-muted); margin-bottom: 1rem; }
.footer-legal {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: 0.75rem 1.5rem;
}
.footer-rights { color: var(--sz-text-muted); margin: 0; font-variant-numeric: tabular-nums; }

/* The cookie control: one quiet button beside the copyright, where a settings
 * link belongs. It used to be a headed block with a live status line sitting
 * permanently at the bottom of every page -- a settings panel pretending to be
 * content. The status and the withdrawal moved into the dialog it opens. */
.footer-consent {
  display: inline-flex;
  align-items: center;
  gap: 0.4rem;
  padding: 0.35rem 0.7rem;
  background: transparent;
  border: 1px solid var(--sz-border);
  border-radius: 8px;
  font: inherit;
  font-size: 0.8125rem;
  color: var(--sz-text-muted);
  cursor: pointer;
}
.footer-consent:hover { color: var(--sz-text); border-color: var(--sz-accent); }

/* --- Sticky mobile call to action ---------------------------------------- */
/* Media query rather than script, so it cannot flash in late on a slow line. */

.sticky-cta { display: none; }
@media (max-width: 61.99em) {
  .sticky-cta {
    display: block;
    position: fixed;
    inset-inline: 0;
    bottom: 0;
    z-index: 45;
    padding: 0.75rem 1rem calc(0.75rem + env(safe-area-inset-bottom));
    background: color-mix(in srgb, var(--sz-surface) 94%, transparent);
    backdrop-filter: blur(10px);
    border-top: 1px solid var(--sz-border);
  }
  .sticky-cta .button { width: 100%; }
}

/* --- Consent ------------------------------------------------------------- */

.consent {
  position: fixed;
  inset-inline: 0;
  bottom: 0;
  z-index: 60;
  background: var(--sz-surface-raised);
  border-top: 1px solid var(--sz-border);
  box-shadow: 0 -8px 32px rgb(0 0 0 / 0.18);
}
.consent[hidden] { display: none; }
.consent-inner {
  padding: 0.875rem 0;
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.75rem 1.5rem;
}
.consent-copy { flex: 1 1 20rem; min-width: 0; }
.consent h2 { font-size: 0.9375rem; margin-bottom: 0.15rem; }
.consent p { color: var(--sz-text-muted); font-size: 0.875rem; margin: 0; }
.consent-state { margin-top: 0.35rem !important; font-weight: 600; }
.consent-state:empty { display: none; }
/* Equal in size, weight, placement and styling, because rejecting may not be
 * harder than accepting. The emphasised gold fill used to sit on Accept alone,
 * which is exactly the asymmetry the EDPB names; neither has it now. */
.consent-actions { display: flex; gap: 0.5rem; flex: 1 1 16rem; }
.consent-choice { flex: 1 1 0; min-height: 2.75rem; padding: 0.5rem 0.75rem; }
/* Nothing to withdraw until something was chosen; `site.js` reveals it. */
.consent-withdraw {
  display: none;
  flex: 1 1 100%;
  padding: 0.25rem 0;
  background: none;
  border: 0;
  font: inherit;
  font-size: 0.8125rem;
  color: var(--sz-text-muted);
  text-decoration: underline;
  cursor: pointer;
}
.consent-chosen .consent-withdraw { display: block; }
/* When the consent bar is up it sits above the sticky CTA, which would
 * otherwise cover its buttons on a phone. */
body:has(.consent:not([hidden])) .sticky-cta { display: none; }

/* --- Error documents ----------------------------------------------------- */

body.centred {
  min-height: 100dvh;
  display: grid;
  place-items: center;
  text-align: center;
  padding: 2rem 0;
}
body.centred .brand { justify-content: center; margin-bottom: 1.5rem; }
/* The error document is the one place a capped measure should be centred rather
 * than left-aligned: the whole page is centred, and a text column pinned left
 * inside it would sit off to one side of its own heading. */
body.centred .prose > * { margin-inline: auto; }

/* --- Wide layouts -------------------------------------------------------- */

@media (min-width: 62em) {
  .hero-wide .hero-grid { grid-template-columns: minmax(0, 1.1fr) minmax(0, 0.9fr); }
  /* Three across, twice. `auto-fit` gave four and left an orphan pair. */
  .channels { grid-template-columns: repeat(3, minmax(0, 1fr)); }
  .infra { grid-template-columns: minmax(0, 1fr) minmax(0, 1fr); }
  .plans { grid-template-columns: repeat(4, minmax(0, 1fr)); }
}

/* --- Narrow layouts ------------------------------------------------------ */

@media (max-width: 61.99em) {
  .header-cta { display: none; }        /* the sticky bar carries it instead */

  /* The checkbox is the control: it is what receives focus and what a screen
   * reader announces, named by its label, with its checked state saying whether
   * the menu is open. It is clipped rather than hidden, because `display: none`
   * and the `hidden` attribute both remove it from the tab order -- which is how
   * the entire primary navigation became unreachable by keyboard below this
   * breakpoint. */
  .menu-toggle {
    display: block;
    position: absolute;
    width: 1px;
    height: 1px;
    margin: -1px;
    padding: 0;
    overflow: hidden;
    clip-path: inset(50%);
    white-space: nowrap;
  }

  .menu-button {
    display: block;
    width: 2.75rem;
    height: 2.75rem;
    border: 1px solid var(--sz-border);
    border-radius: 10px;
    cursor: pointer;
    position: relative;
    flex: none;
  }
  /* The focus ring belongs on the thing that is visible, not on the 1px box. */
  .menu-toggle:focus-visible + .menu-button {
    outline: 3px solid var(--sz-focus);
    outline-offset: 2px;
  }
  .menu-bars,
  .menu-bars::before,
  .menu-bars::after {
    content: "";
    position: absolute;
    left: 50%;
    width: 1.125rem;
    height: 2px;
    background: var(--sz-text);
    transform: translateX(-50%);
  }
  .menu-bars { top: 50%; margin-top: -1px; }
  .menu-bars::before { top: -6px; left: 0; transform: none; }
  .menu-bars::after { top: 6px; left: 0; transform: none; }

  /* The navigation moves below the bar. `:has()` rather than a sibling
   * combinator because the toggle now lives beside the language control in
   * `.header-end`, which is not the navigation's sibling -- and the language
   * control is deliberately outside the collapsible menu, since a reader who
   * cannot read the page cannot be asked to find a button labelled in it. */
  /* `order` here is purely about paint. The toggle and the label are emitted
   * before the navigation so that a keyboard reaches the menu immediately after
   * opening it; these three rules put them back where they belong on screen,
   * with the navigation on its own row underneath. Changing the DOM order to
   * match the painted order would reintroduce the focus trap. */
  .site-nav {
    display: none;
    order: 3;
    flex-basis: 100%;
    margin-inline-start: 0;
    padding-bottom: 0.75rem;
  }
  .header-end { order: 1; margin-inline-start: auto; }
  .menu-button { order: 2; }
  .header-inner:has(.menu-toggle:checked) .site-nav { display: block; }
  .site-nav ul { flex-direction: column; gap: 0.25rem; }
  .site-nav a {
    display: block;
    padding: 0.7rem 0.25rem;
    font-size: 1rem;
  }
}

@media (max-width: 20.5em) {
  /* Measured: at 360px the mark, the wordmark, the language control and the
   * menu button come to about 288px inside a 320px content box, so they fit and
   * the brand stays on the bar. Below 328px they do not, and the mark alone
   * still identifies the site. */
  .brand-word { display: none; }
}
@media (max-width: 29.99em) {
  .lang-menu { max-width: calc(100vw - 1.5rem); }
}

/* An exchange's own logotype in the slot a card icon would occupy. The marks
 * have very different aspect ratios -- a diamond, a wide wordmark, a squat glyph
 * -- so the slot sizes to the mark rather than the mark to the slot, and they are
 * matched on height instead. Muted rather than gold: these are somebody else's
 * marks and should not read as this product's accent. */
/* A logotype is not an icon and must not sit in the icon chip: the chip is a
 * fixed 44px square with a tinted ground, and a wide wordmark either overflows it
 * or gets squeezed into it. These opt out of the chip entirely -- no ground, no
 * border, no fixed width -- and are matched to the others on height alone.
 *
 * Muted rather than gold, because gold is this product's accent and these are
 * somebody else's marks. */
.card-mark {
  display: block;
  width: auto;
  height: 26px;
  margin-bottom: 1rem;
  padding: 0;
  border: 0;
  border-radius: 0;
  background: none;
}
.card-mark .venue-art { display: block; width: auto; height: 100%; }

/* An exchange card that links out. Same object as `.card`, plus the affordance:
 * a hover lift and a corner arrow, so a reader can tell which of the cards on a
 * page are destinations before clicking one. */
a.card-link {
  position: relative;
  display: block;
  color: inherit;
  text-decoration: none;
  transition: border-color 160ms ease, transform 160ms ease;
}
a.card-link:hover {
  border-color: var(--sz-accent);
  transform: translateY(-2px);
}
.card-out {
  position: absolute;
  inset-block-start: 1rem;
  inset-inline-end: 1rem;
  color: var(--sz-text-muted);
  line-height: 0;
}
a.card-link:hover .card-out { color: var(--sz-accent-text); }
@media (prefers-reduced-motion: reduce) {
  a.card-link { transition: border-color 160ms ease; }
  a.card-link:hover { transform: none; }
}

/* --- Motion -------------------------------------------------------------
 *
 * The site had one @keyframes and ten transitions, all of them hover-driven, so
 * nothing on the page acknowledged a reader scrolling down it. What follows is
 * the smallest set of primitives that fixes that without becoming decoration:
 * a reveal, an ambient drift, and a glow. Every one of them animates `transform`
 * or `opacity` and nothing else, because those are the two properties the
 * compositor can drive without a paint or a layout.
 *
 * Three rules hold the whole thing together.
 *
 * **The resting state is the finished state.** `.reveal` on its own does
 * nothing at all. Only `html.js-motion .reveal:not(.is-in)` sets the hidden
 * from-state, and `js-motion` is added by site.js after it has confirmed both
 * that it is running and that the reader has not asked for reduced motion. So
 * the pre-JS render, the JS-disabled render, the JS-404 render and the
 * reduced-motion render are all the same render: everything visible, in place.
 * Hiding in CSS and revealing in JS is the inverse of this and loses the page
 * whenever the script does not arrive, which for a site whose job is copy is
 * not a trade worth making.
 *
 * **Opt in, not opt out.** Every block is inside
 * `prefers-reduced-motion: no-preference`. The global `reduce` guard above is
 * the second line, not the first.
 *
 * **Nothing loops where it cannot be seen.** The ambient primitives are paused
 * by `.is-idle`, which site.js toggles with an IntersectionObserver, so an
 * off-screen glow is not still compositing.
 */

:root {
  --ease-out: cubic-bezier(0.2, 0.8, 0.2, 1);
  --rise: 14px;
  --reveal-ms: 520ms;
}

@media (prefers-reduced-motion: no-preference) {
  html.js-motion .reveal:not(.is-in) {
    opacity: 0;
    transform: translateY(var(--rise));
  }
  html.js-motion .reveal {
    transition:
      opacity var(--reveal-ms) var(--ease-out),
      transform var(--reveal-ms) var(--ease-out);
  }
  /* A short stagger between siblings, expressed in CSS rather than written onto
   * each element by script: an inline style would need `style-src-attr`, and
   * the policy is `style-src 'self'` with no inline anything. Four steps is
   * enough to read as a sequence and short enough that the last card is not
   * still arriving when a fast reader has moved on. */
  html.js-motion .reveal:nth-child(2) { transition-delay: 60ms; }
  html.js-motion .reveal:nth-child(3) { transition-delay: 120ms; }
  html.js-motion .reveal:nth-child(4) { transition-delay: 180ms; }

  /* Ambient: a slow vertical drift and a slow brightening, both on layers that
   * exist only to carry them. The hero already paints a radial gradient; this
   * moves that same layer rather than animating the gradient itself, because a
   * gradient is repainted every frame and a transform is not. */
  @keyframes sz-drift {
    from { transform: translate3d(0, -0.6rem, 0); }
    to   { transform: translate3d(0, 0.6rem, 0); }
  }
  @keyframes sz-glow {
    from { opacity: 0.45; }
    to   { opacity: 0.85; }
  }
  .hero::before,
  .band-cta::before {
    animation: sz-drift 14s var(--ease-out) infinite alternate;
    will-change: transform;
  }
  .hero::before { animation-name: sz-drift, sz-glow; animation-duration: 14s, 9s; }
  .hero.is-idle::before,
  .band-cta.is-idle::before { animation-play-state: paused; }
}

/* --- The market tape -----------------------------------------------------
 *
 * A duplicated track translated by exactly -50%. The percentage resolves
 * against the element's own border box, so the seam is exact at every width in
 * every locale with nothing measured in script -- and because both copies are
 * server-rendered, it is exact before any script runs.
 *
 * `animation-play-state` rather than removing the animation, so pausing keeps
 * the strip exactly where it was instead of snapping it home.
 */
.tape {
  border-bottom: 1px solid var(--sz-border);
  background: var(--sz-surface-sunken);
  display: flex;
  align-items: center;
  gap: 0.75rem;
  padding-inline: var(--gutter);
  overflow: hidden;
}
.tape-empty { justify-content: center; }
.tape-empty p {
  margin: 0;
  padding-block: 0.5rem;
  font-size: 0.8125rem;
  color: var(--sz-text-muted);
}
.tape-viewport { flex: 1; overflow: hidden; }
.tape-track { display: flex; width: max-content; }
.tape-item {
  display: inline-flex;
  align-items: baseline;
  gap: 0.4rem;
  padding: 0.5rem 1rem;
  font-size: 0.8125rem;
  white-space: nowrap;
}
.tape-sym { font-weight: 650; letter-spacing: 0.02em; }
.tape-px { color: var(--sz-text-muted); font-variant-numeric: tabular-nums; }
.tape-chg { font-variant-numeric: tabular-nums; font-weight: 600; }
/* Direction is carried by a glyph as well as a colour: roughly one reader in
 * twelve cannot use the colour, and a signed number beside an arrow costs
 * nothing to anybody else. The sign is already in the text. */
.tape-item[data-dir="up"] .tape-chg { color: var(--sz-win); }
.tape-item[data-dir="down"] .tape-chg { color: var(--sz-loss); }
.tape-item[data-dir="up"] .tape-chg::after { content: " \2191"; }
.tape-item[data-dir="down"] .tape-chg::after { content: " \2193"; }
.tape-toggle {
  flex: none;
  border: 1px solid var(--sz-border);
  background: var(--sz-surface-raised);
  color: var(--sz-text-muted);
  border-radius: 999px;
  padding: 0.2rem 0.7rem;
  font: inherit;
  font-size: 0.6875rem;
  cursor: pointer;
}
.tape-toggle:hover { color: var(--sz-text); }

@media (prefers-reduced-motion: no-preference) {
  @keyframes tape-scroll {
    from { transform: translateX(0); }
    to   { transform: translateX(-50%); }
  }
  .tape-track {
    animation: tape-scroll 90s linear infinite;
    will-change: transform;
  }
  /* Mirrored rather than re-authored: the track is the same, the direction the
   * eye reads it in is not. */
  [dir="rtl"] .tape-track { animation-direction: reverse; }
  .tape.is-paused .tape-track { animation-play-state: paused; }
  .tape:hover .tape-track { animation-play-state: paused; }
}

/* With motion off the strip does not move at all, so it becomes an ordinary
 * horizontally scrollable row -- still complete, still readable, and reachable
 * with a keyboard because a scroll container is focusable. */
@media (prefers-reduced-motion: reduce) {
  .tape-viewport { overflow-x: auto; }
  .tape-track { width: auto; }
  /* The second copy exists only to make a seam invisible while scrolling. With
   * nothing scrolling it is duplicate content, so it is not rendered. */
  .tape-track > .tape-item:nth-child(n + 13) { display: none; }
  .tape-toggle { display: none; }
}
