/* ============================================================================
   JT COMMON — v1.0.0
   The layer every page loads, before its own stylesheet.

   WHAT BELONGS HERE
   Anything a second page would otherwise copy: the palette and type tokens, the
   reset, the page-agnostic base, the display heading, the lede,
   the eyebrow, and the Button component. All of it was lifted
   verbatim out of landing.css, which had been the only page with a real design
   behind it and had therefore accumulated the shared parts.

   WHAT DOES NOT BELONG HERE
   Anything a page owns: its layout, its sections, its own components. A rule
   that only one page will ever use should live in that page's file even if it
   feels tidy to hoist it — this file is the site's vocabulary, not an attic.

   LOAD ORDER
     <link rel="stylesheet" href="/styles/common.css">
     <link rel="stylesheet" href="/styles/landing.css">   (or content.css)
   Common first, always. Page sheets override tokens and rules from here, and a
   handful deliberately do — each override is commented where it sits.

   WHERE THE VALUES COME FROM
   Figma "Landing" (38:2476) and the component sets it instantiates, chiefly
   Button (2:1280) and eyebrow (60:8168). Read from the node tree, not
   from a screenshot. Component states come from every variant of a set.

   NAMING
   The --jt- prefix belongs to the frozen modules (menu, footer, background) and
   must never be declared here. Nothing in this file may style anything inside
   .jt-menu or .jt-footer.
   ========================================================================== */

:root {
  /* --- palette -----------------------------------------------------------
     THESE ARE ROLE NAMES POINTING AT THE FIGMA TOKENS, not values.

     They used to be literals, copied from whatever node was measured that day.
     That is how the site ended up with headings at #f2f7fc while the design
     system had moved them to #cbddea: each rule agreed with itself, nothing
     compared them to Figma, and the drift was invisible until someone looked.

     `--text-*` comes from styles/tokens.css, generated from the Figma variable
     collections (see build/tokens.js). Changing a colour is now a change in
     Figma plus a regenerate — never an edit here.

     The role names describe each colour's purpose. Colours without a matching
     Figma token remain literals so adopting a nearby token cannot change them. */
  --ink:            var(--text-heading);          /* headings          #cbddea */
  --ink-soft:       var(--text-heading-bright);   /* bright headings   #ebf6fe */
  --body:           var(--text-paragraph);        /* running text      #bdcddb */
  --body-dim:       var(--text-paragraph-pale);   /* supporting text   #7a8ca0 */
  --cyan:           var(--text-blue);             /*                   #00d4ff */
  --cyan-bright:    var(--text-blue-pale);        /* eyebrow, in view  #40d1ff */
  --cyan-soft:      #7dd3fc;   /* no matching token */
  --cyan-deep:      #04121a;   /* text on a filled cyan button; no token */
  --amber:          var(--text-orange);           /* the Order's mark  #eb8b2d */
  --amber-deep:     var(--text-orange-dark);      /*                   #d06700 */
  --rule:           rgba(125, 211, 252, 0.16);

  --display: "Tektur", ui-monospace, "SFMono-Regular", Menlo, monospace;
  --text:    "Exo 2", system-ui, -apple-system, "Segoe UI", sans-serif;

  /* --- motion ------------------------------------------------------------- */
  --ease-out:  cubic-bezier(.16, 1, .3, 1);
  --ease-expo: cubic-bezier(.2, .8, .2, 1);

  /* The menu module is fixed at 60px (--jt-menu-height). Pages clear it from
     here rather than the module reaching out to push them, which would make
     every page depend on the module's internals. */
  --menu-height: 60px;

  /* --- button ------------------------------------------------------------
     How far the corner brackets travel outward on hover and focus, measured
     from the component set: rest at the button's corner, hover/focus at −5,−5
     / +5,−5 / −5,+5 / +5,+5. One value, applied on both axes with a per-corner
     sign, so the four move apart rather than in one direction.

     The duration is not in the component — variants hold positions, not time.
     180ms is what the motion spec gives for the button's hover, and the fade
     and the travel share it so they read as one gesture. */
  --btn-corner-shift: 5px;
  --btn-move:         180ms;
}

/* ---------------------------------------------------------------------------
   Base
   ------------------------------------------------------------------------- */

* { box-sizing: border-box; }

html {
  background: #05070f;
  scroll-behavior: smooth;
  scroll-padding-top: calc(var(--menu-height) + 24px);
}

@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }
}

/* Deliberately NOT set here: font-size, line-height and letter-spacing. The
   landing page sets 17/26 with +0.01em because that is what its comp measures;
   content pages set a fluid 16–18/1.7 because they are read, not scanned.
   Hoisting either would impose one page's typography on the other. */
body {
  margin: 0;
  min-width: 320px;
  /* clip, not hidden: `overflow: hidden` on body breaks position:sticky and
     scroll-padding, and creates a scroll container the fixed menu does not
     expect. */
  overflow-x: clip;
  /* The background module paints at z-index −2 and −1. isolation anchors that
     stacking context to the body so page content never has to out-index it. */
  isolation: isolate;
  color: var(--body);
  font-family: var(--text);
  font-weight: 300;
  text-rendering: optimizeLegibility;
  -webkit-font-smoothing: antialiased;
}

/* ---------------------------------------------------------------------------
   Utilities
   ------------------------------------------------------------------------- */

.visually-hidden {
  position: absolute; width: 1px; height: 1px;
  margin: -1px; padding: 0; border: 0;
  clip-path: inset(50%); overflow: hidden; white-space: nowrap;
}

/* The menu module owns the document's skip link, targeting <main id="main">. */

/* The Button's icon slots. `Icon left` and `Icon right` are boolean properties
   on set 2:1280; the glyph is 16×16 and takes the label's colour, so neither
   variant needs an icon rule of its own — the near-black on a filled primary
   and the pale blue on a ghost both arrive through `currentColor`.

   `flex: none` because a long translated label must eat the free space, never
   the glyph: "na stronę główną" is half again as long as "to the homepage". */
.btn__icon { flex: none; width: 16px; height: 16px; }

/* ---------------------------------------------------------------------------
   more — component set "more" (772:27004)

   The site's standard "there is more this way" link: a Body/Label Small word
   and an arrow-right glyph, 4px apart, centred on each other. Two variants,
   Default and Hover, and the ONLY difference between them is that Hover
   underlines the text — no colour change, no movement, no glow. Checked both
   variants rather than assumed, because a hover that does nothing visible is
   the kind of thing that gets "improved" into something the comp never drew.

   THE ARROW IS A GLYPH, NOT A CHARACTER. It used to be typed into the copy —
   `"cta": "Read →"` in the related module's strings — which put a piece of the
   design inside a translatable string, where every language had to remember to
   carry it and a screen reader read it out as "right arrow".

   `stroke: currentColor` so the icon takes the link's colour wherever it sits,
   including inside a card that recolours on hover.
   ------------------------------------------------------------------------- */
.more {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  color: var(--cyan);
  font-family: var(--display);
  font-size: var(--body-label-small-size);
  font-weight: var(--body-label-small-weight);
  letter-spacing: var(--body-label-small-tracking);
  line-height: normal;
  text-transform: uppercase;
  text-decoration: none;
}

.more__icon { flex: none; width: 16px; height: 16px; }

/* State=Hover: underline, and nothing else. */
.more:hover .more__label,
.more:focus-visible .more__label { text-decoration: underline; }

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

/* ---------------------------------------------------------------------------
   THE HEADING RAMP — one definition, every page

   h1 to h6 wear the design's Heading/H1 … Heading/H6 text styles. This is the
   only place the ramp is written; styles/tokens.css holds the measurements, and
   everything downstream reads the tag.

   It used to be written twice. content.css had it scoped to `:where(.prose)`,
   which meant it existed for article bodies and nowhere else, so the landing
   restated the H4 line by line in landing.css for its level titles — and drifted
   while restating it: 11px against 13, weight 500 against 400, and the title in
   --ink-soft (#ebf6fe) where the comp says --ink (#cbddea). Two copies of a type
   scale is one copy and one bug waiting.

   ELEMENT SELECTORS ON PURPOSE — (0,0,1), so ANY class beats them. That is what
   makes this safe to apply globally: the menu, the footer, the Hero module and
   the ladder all style their headings through their own classes and are
   untouched, exactly as the `:where()` container-default rule in CLAUDE.md
   requires. A component that WANTS the ramp simply says nothing.

   Margins are deliberately not here. Spacing between a heading and what follows
   it is a property of the context — an article body, a card, a section band —
   not of the type style, and content.css keeps its own for .prose.
   ------------------------------------------------------------------------- */
h1, h2, h3, h4, h5, h6 {
  margin: 0;
  color: var(--ink);
  font-family: var(--display);
  line-height: normal;
}

h1 { font-size: var(--h1-size); font-weight: var(--h1-weight); letter-spacing: var(--h1-tracking); }
h2 { font-size: var(--h2-size); font-weight: var(--h2-weight); letter-spacing: var(--h2-tracking); }
h3 { font-size: var(--h3-size); font-weight: var(--h3-weight); letter-spacing: var(--h3-tracking); }
h4 { font-size: var(--h4-size); font-weight: var(--h4-weight); letter-spacing: var(--h4-tracking); }
h5 { font-size: var(--h5-size); font-weight: var(--h5-weight); letter-spacing: var(--h5-tracking); }
h6 { font-size: var(--h6-size); font-weight: var(--h6-weight); letter-spacing: var(--h6-tracking); }

/* ---------------------------------------------------------------------------
   Type
   The lede. Content pages have their own spacing ramp for .page__title and
   .prose headings — a different job, and they keep it.
   ------------------------------------------------------------------------- */
/* THE .heading AND .lede CLASSES ARE GONE.

   They were the landing page's display heading and standfirst, and the landing
   page was their only consumer — the content pages have their own ramp for
   .page__title and .prose. When every section of the landing became a Hero
   instance (build/landing.js, sectionCopy), the module's .jt-hero__title and
   .jt-hero__description took over and these two stopped matching anything on
   any page in any language.

   Removed rather than left: a class that nothing renders is a second, silently
   diverging definition of type the Hero module already owns, and the next
   person to reach for `class="heading"` would get a heading that looks nearly
   but not quite like the component. The values themselves are not lost — they
   are modules/hero/hero.css, measured from the same Figma text styles.

   `.lede--wide` went with them; it was a commented-out max-width. */

/* ---------------------------------------------------------------------------
   Eyebrow — component set "eyebrow" (60:8168)

   ONE eyebrow for the whole site, in ONE appearance. Content pages used to have
   a second, plainer `.page__eyebrow` with no component behind it; it has been
   merged into this, so the category line above an article title and the label
   on the landing page are the same thing rendered by the same markup.

   One property, matching the component set's one:

     Index   `--no-index` drops the leading number. Nothing else changes.

   The divider is always "//". The label is always cyan.

   THE GREY STATE IS GONE. The set used to carry `State = default | in view`,
   grey #7a8ca0 warming to cyan #40d1ff as a section arrived. There is no grey
   eyebrow any more, so there is no `is-in-view` rule here and no colour
   transition. If you are looking for one because a label came up grey, the
   cause is a stale build, not a missing state.

   `is-in-view` still exists on the RANK PAGE, where it latches per-rank opacity
   as you scroll. Same class name, unrelated mechanism — see styles/ranks.css.

   The 1px rule is gone too. It had been hidden in every variant of the set for
   some time and only the landing CTA still rendered one.

   `--eyebrow-ink` repaints the number, the slashes and the label in one colour.
   Only the rank system page uses it, for the sphere spectrum — see its comment
   in build/components.js for why that is a property and not seven variants.

   Type is Tektur 13/2px uppercase at the font's own line height (Figma AUTO).

   The markup comes from build/components.js so the two generators cannot drift.
   ------------------------------------------------------------------------- */
.eyebrow {
  display: flex;
  align-items: center;
  gap: 16px;
  margin: 0;
  font-family: var(--display);
  font-size: 13px;
  font-weight: 400;
  letter-spacing: 2px;
  text-transform: uppercase;
  line-height: normal;
}

/* The index and the slashes are the base cyan; the label is the brighter one.
   That difference is the component's, not an accident — see the set. */
.eyebrow__idx,
.eyebrow__slash { color: var(--eyebrow-ink, var(--cyan)); }

.eyebrow__text { color: var(--eyebrow-ink, var(--cyan-bright)); }

/* THE PALE EYEBROW. The Sayings comp draws its "// quote of the day" in
   Text/Paragraph - pale — BOTH the slashes and the label at #7a8ca0 — where
   every other eyebrow on the site is cyan. Bound to the variable in Figma, so
   it is a deliberate choice rather than a one-off fill someone nudged.

   It rides in on `--eyebrow-ink`, the same property the rank page uses for its
   sphere spectrum: that property repaints number, slashes and label together,
   which is exactly right here because the comp gives the two parts the SAME
   colour rather than the usual base/bright pair.

   (An `.eyebrow--amber` modifier used to sit here. It went when :::note stopped
   using the eyebrow and became `.note__label--amber`; its explanatory comment
   outlived it by a few hours and is gone with this edit.) */
.eyebrow--pale { --eyebrow-ink: var(--body-dim); }

/* THE UNIFORM BLUE EYEBROW. The 404 comp binds BOTH the "//" and the label to
   Text/Blue, where the component's default gives the label the brighter
   #40d1ff. Same mechanism as `--pale`: one ink for the whole eyebrow.

   Three inks now exist — the default pair, this, and pale — and each is a comp
   binding rather than a preference. If a fourth appears it is probably time for
   the eyebrow to take a real `Ink` variant in Figma instead. */
.eyebrow--blue { --eyebrow-ink: var(--cyan); }

/* ---------------------------------------------------------------------------
   Button — component set "Button" (2:1280)

   Variants used here: primary and secondary. Every value below is read from a
   variant of the set — not invented for the default and guessed for the rest.

   THE CORNER BRACKETS
   Four 15×15 boxes, each drawing only the two borders that meet at its corner,
   with a 3px radius on the outer corner. The set gives them two positions:

     rest              flush with the button's corner, opacity 0
     hover / focus     shifted 5px outward on BOTH axes, opacity 1
     active            back to flush, opacity 0

   They travel and fade at the same time. The movement is done with `transform`
   rather than by animating `top`/`left` — offsets are laid out once and the
   transform is composited, so the brackets can't nudge the layout or stutter.
   Each corner carries its own --cx/--cy sign, which is what lets one rule move
   all four outward instead of four rules moving them the same way.
   ------------------------------------------------------------------------- */
.btn {
  position: relative;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  /* The comp's Button is a HORIZONTAL auto-layout with gap 10. It read as
     nothing while every instance was label-only; the 404 is the first to fill
     an icon slot, and without this its glyph sat against the word. */
  gap: 10px;
  min-height: 48px;
  padding: 0 32px;
  border: 1px solid transparent;   /* every variant has a 1px stroke */
  border-radius: 2px;
  font-family: var(--display);
  font-size: 13px;
  letter-spacing: 2px;
  text-transform: uppercase;
  text-decoration: none;
  white-space: nowrap;
  /* `background`, not `background-color`: primary's resting fill is a gradient
     — a background IMAGE — and a transition naming only the colour would leave
     the ramp painted over the flat hover fill. Gradients do not interpolate,
     so this particular change lands as a cut; the border and glow beside it
     still ease, which is what keeps the state read as one movement. */
  transition: background var(--btn-move) var(--ease-out),
              border-color var(--btn-move) var(--ease-out),
              box-shadow var(--btn-move) var(--ease-out),
              color var(--btn-move) var(--ease-out),
              transform var(--btn-move) var(--ease-out);
}

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

.btn__corner {
  position: absolute;
  width: 15px;
  height: 15px;
  opacity: 0;
  pointer-events: none;
  /* Sits on the button's outer edge, over its own 1px stroke, exactly as the
     rest variant places it. */
  transform: translate(0, 0);
  transition: opacity var(--btn-move) var(--ease-out),
              transform var(--btn-move) var(--ease-out),
              border-color var(--btn-move) var(--ease-out);
}

.btn__corner--top-left {
  top: -1px; left: -1px;
  --cx: -1; --cy: -1;
  border-top: 1px solid; border-left: 1px solid;
  border-top-left-radius: 3px;
}
.btn__corner--top-right {
  top: -1px; right: -1px;
  --cx: 1; --cy: -1;
  border-top: 1px solid; border-right: 1px solid;
  border-top-right-radius: 3px;
}
.btn__corner--bottom-left {
  bottom: -1px; left: -1px;
  --cx: -1; --cy: 1;
  border-bottom: 1px solid; border-left: 1px solid;
  border-bottom-left-radius: 3px;
}
.btn__corner--bottom-right {
  bottom: -1px; right: -1px;
  --cx: 1; --cy: 1;
  border-bottom: 1px solid; border-right: 1px solid;
  border-bottom-right-radius: 3px;
}

/* Appear and travel outward together. */
.btn:hover .btn__corner,
.btn:focus-visible .btn__corner {
  opacity: 1;
  transform: translate(calc(var(--cx) * var(--btn-corner-shift)),
                       calc(var(--cy) * var(--btn-corner-shift)));
}

/* The active variant pulls them back in and hides them again. Listed after the
   hover rule because a press happens while hovering. */
.btn:active .btn__corner {
  opacity: 0;
  transform: translate(0, 0);
}

/* --- primary -------------------------------------------------------------
   RE-READ after the set was retuned. What changed is the RESTING state only:

     State=default  was flat #00d4ff with a matching stroke.
                    Now a LINEAR GRADIENT #1080b4 → #00d4ff, and the stroke is
                    #00d4ff at 0% — present but invisible.
     State=hover    flat #00d4ff, stroke #00d4ff at 100%, 20px glow at 26%
     State=focus    identical to hover
     State=active   fill and stroke #00a9cc, no glow
     State=disabled fill #00d4ff at 22%, stroke at 0%

   So the button now lifts from a deep blue into the flat brand cyan as you
   arrive on it, rather than only gaining a glow.

   THE GRADIENT ANGLE IS DERIVED, NOT EYEBALLED.
   Figma gives `gradientTransform [[-1.058, 0.058, 1], [0.338, -0.338, 0.5]]`,
   which maps the object's 0–1 box into gradient space. Inverting the 2×2 part
   and taking the image of (1,0) gives a direction of (−1, −1) in that
   normalised box — up and to the left. At the comp's 126 × 48 that is
   (−126, −48) in pixels, and CSS's `linear-gradient(θ)` points along
   (sin θ, −cos θ), so θ = atan2(−126, 48) ≈ 291deg.

   That angle is exact only at the comp's aspect ratio, because Figma's
   transform is normalised to the box and CSS's is not. A button whose label
   makes it wider will tilt the ramp slightly. Pinning the angle keeps every
   button identical to every other, which matters more here than matching one
   comp width exactly — the alternative is a per-instance angle nobody can
   maintain.

   The stroke is kept in every state rather than dropped, so the button's size
   never changes between them. (Figma's active variant measures 2px narrower
   for exactly that reason; reproducing it would make the button twitch on
   click.)
   ----------------------------------------------------------------------- */
.btn--primary {
  background: linear-gradient(291deg, #1080b4 0%, var(--cyan) 100%);
  border: 0;
  color: var(--cyan-deep);
  font-weight: 600;                       /* Tektur SemiBold in the comp */
}
.btn--primary .btn__corner { color: var(--cyan); }

/* The glow is the component's: 20px at 26%. The motion spec still says
   "glow radius 28 → 40" — that frame predates this version of the button and
   its numbers no longer match. The scale it also asks for is kept, being a
   motion property no Figma variant can express. */
/* Hover and focus drop the gradient for the flat cyan and light the stroke —
   `background` (not `background-color`) because a gradient is a background
   IMAGE, and setting only the colour would leave the ramp painted on top of
   it. That is also why .btn's transition covers `background` rather than
   `background-color`: a gradient cannot tween, so the change is a cut, and
   naming the shorthand at least keeps the glow and border in step. */
.btn--primary:hover {
   background: linear-gradient(291deg, var(--cyan) 0%, var(--cyan) 100%);
   box-shadow: 0 0 20px rgba(0, 212, 255, 0.26);
}

.btn--primary:focus-visible {
  outline: none;
  background: var(--cyan);
  border-color: var(--cyan);
  box-shadow: 0 0 20px rgba(0, 212, 255, 0.26);
}

.btn--primary:active {
  background: #00a9cc;
  border-color: #00a9cc;
  box-shadow: none;
  transform: scale(1);
}
.btn--primary:active .btn__corner { color: #00a9cc; }

/* --- secondary -----------------------------------------------------------
   RE-READ with the set. Again only the RESTING state moved, and all three of
   its values did — it now sits back further before you touch it:

     State=default  fill #4ec3fa 8%, stroke #4ec3fa 34%, label #9cd7f6
                    (was #7dd3fc 8% / #7dd3fc 55% / #e3f4fd)
     State=hover    fill #00d4ff 13%, stroke #00d4ff 75%, 20px glow at 20%,
                    label #bfe6fa, brackets #00d4ff 80%
     State=focus    identical to hover
     State=active   fill #00d4ff 22%, stroke #7dd3fc 85%, label #e3f4fd
     State=disabled fill #7dd3fc 3%, stroke #7dd3fc 8%, label #3c4a5a
   ----------------------------------------------------------------------- */
.btn--secondary {
  background: rgba(78, 195, 250, 0.08);
  border-color: rgba(78, 195, 250, 0.34);
  color: #9cd7f6;
  font-weight: 400;
}
.btn--secondary .btn__corner { color: rgba(125, 211, 252, 0.55); }  /* rest is invisible; see below */

.btn--secondary:hover,
.btn--secondary:focus-visible {
  outline: none;
  background: rgba(0, 212, 255, 0.16);
  border-color: rgba(0, 212, 255, 0.75);
  box-shadow: 0 0 20px rgba(0, 212, 255, 0.22);
  color: #bfe6fa;
}
.btn--secondary:hover .btn__corner,
.btn--secondary:focus-visible .btn__corner { color: rgba(0, 212, 255, 0.80); }

.btn--secondary:active {
  background: rgba(0, 212, 255, 0.22);
  border-color: rgba(125, 211, 252, 0.85);
  box-shadow: none;
  color: #e3f4fd;
}

/* --- ghost ---------------------------------------------------------------
   Read from every variant of the set, not from the default alone.

   State=default  no fill, no visible stroke (#7dd3fc at 0%), radius 4,
                  label Tektur Regular #00d4ff
   State=hover    fill #00d4ff 13%, stroke #00d4ff 75%, RADIUS 2
   State=focus    identical to hover
   State=active   back to default — the set really does return it to the
                  resting look while pressed
   State=disabled label #3c4a5a, otherwise the default

   THE CORNER RADIUS CHANGES BETWEEN STATES, 4 at rest and 2 on hover. That is
   what the component set says, so it is what is built; it reads as the button
   tightening as it lights up. It is also the only button in the set that does
   this, which is why it is called out rather than left to be noticed.

   The hero's second action is this variant. It used to be `secondary` on the
   landing page, before the Hero component existed. */
.btn--ghost {
  background: transparent;
  border-color: transparent;
  border-radius: 4px;
  color: var(--cyan);
  font-weight: 400;
}
.btn--ghost .btn__corner { color: rgba(0, 212, 255, 0.55); }

.btn--ghost:hover,
.btn--ghost:focus-visible {
  outline: none;
  border-radius: 2px;
  background: rgba(0, 212, 255, 0.13);
  border-color: rgba(0, 212, 255, 0.75);
  color: var(--cyan);
}
.btn--ghost:hover .btn__corner,
.btn--ghost:focus-visible .btn__corner { color: rgba(0, 212, 255, 0.80); }

/* The set returns active to the resting look, brackets included. */
.btn--ghost:active {
  border-radius: 4px;
  background: transparent;
  border-color: transparent;
}

/* Reduced motion: the brackets still appear, they just stop travelling. */
@media (prefers-reduced-motion: reduce) {
  .btn, .btn__corner { transition-duration: 120ms; }
  .btn--primary:hover { transform: none; }
  .btn:hover .btn__corner,
  .btn:focus-visible .btn__corner { transform: translate(0, 0); }
}


/* ---------------------------------------------------------------------------
   Blockquote — component set "blockquote" (229:9641)

   Two variants that differ ONLY in hue, so the box is written once and the
   colour comes in through --quote-ink. Adding a third variant is one rule.

     Default    cyan   #00d4ff rule, 7% tint, #7dd3fc text
     Important  amber  #eb8b2d rule, 10% tint, #eb8b2d text

   Authored as ordinary markdown — `>` for the default, `> [!important]` for the
   other. See build/components.js, which emits the markup, and build/markdown.js,
   which reads the marker. Markup and CSS change together.

   The tint differs between the two (7% against 10%) because the amber needs a
   little more weight to read at the same strength as the cyan; both are the
   comp's own values, not a ramp.
   ------------------------------------------------------------------------- */
/* EVERY VALUE HERE IS READ OFF THE COMPONENT SET, 229:9641, AND IS WRITTEN
   LITERALLY ON PURPOSE.

   The text colour used to be `var(--cyan-soft)`. That token happens to hold
   #7dd3fc, so it rendered correctly — but it made the quote track a site token
   rather than the component. Retune --cyan-soft for some unrelated reason and
   this box silently stops being the comp. The design is the spec; a shared
   token is not a substitute for it. Same reason the tint is a literal rgba()
   rather than color-mix() over --quote-ink: one value to compare against Figma,
   not an expression to evaluate first.

   The three custom properties stay, because they are what makes Default and
   Important ONE box with two skins rather than two boxes that must be kept in
   step. They are the component's own knobs, declared here, read only here. */
.blockquote {
  --quote-ink: #00d4ff;                /* border + the fill it is tinted from */
  --quote-text: #76CFF9;               /* NOT --cyan-soft; see above */
  --quote-fill: rgba(0, 212, 255, .07);

  margin: 0;
  /* Re-measured 2026-09-03: the component set (229:9641) now draws 16/24/16/22
     with a 2px rule, where it drew 16/20/16/17 with a 3px one.

     The left value still needs converting. The comp's stroke is INSIDE the 880
     frame and its 22px padding is measured from the FRAME EDGE, so the text
     sits 22px from the edge and 20px clear of the rule. CSS puts the border
     outside the padding box, so matching that means padding-left 20 with a 2px
     border — 20 + 2 = the comp's 22. Write 22 here and every quote's text is
     2px further in than the comp draws it.

     Right is 24 as measured, there being no rule on that side. Top/bottom
     16 + 22 line + 16 = the comp's 54px exactly. */
  padding: 16px 24px 16px 20px;
  border-left: 2px solid var(--quote-ink);
  border-radius: 0;                    /* the comp is square; stated, not assumed */
  background: var(--quote-fill);
  color: var(--quote-text);
  font-family: var(--text);            /* resolves to "Exo 2" — the comp's face */
  font-size: 14px;                     /* Body/Paragraph Small */
  font-weight: 300;                    /* = Light; the 300 face IS loaded */
  letter-spacing: 1.2px;
  line-height: 24px;
}

.blockquote--important {
  --quote-ink: #eb8b2d;
  --quote-text: #EBA475;               /* the amber variant tints its text too */
  --quote-fill: rgba(235, 139, 45, .1);/* 10%, not 7% — the comp's own value */
}

.blockquote p { margin: 0; }
.blockquote p + p { margin-top: 0.9em; }

/* ---------------------------------------------------------------------------
   Numeralia — Figma component set "numeralia" (621:15561)

   A figure and the thing it counts. Two variants, and the ONLY difference is
   the gradient's two stops, so the box is written once and the stops are the
   variant:

     Blue     #007fba → #00d1ff
     Orange   #d06700 → #eb8b2d

   THE ANGLE IS MEASURED, NOT GUESSED. The comp fills the numeral with a
   gradientTransform of [[-1.091, .453, .698], [-.453, -.518, .921]]; inverting
   it puts the start at (1.011, .894) and the end at (.339, 1.482) in the box's
   own space, a direction of (-.672, .588) — 229deg in CSS, where 0 is "to top"
   and the direction vector is (sin θ, −cos θ). It read 180deg here before,
   which is the same two colours down a different diagonal.

   Markup is build/components.js; change the two together. It lives in
   common.css rather than a page sheet because two builders render it — the
   landing's community stats and a content page's `glance:` band.
   ------------------------------------------------------------------------- */
.numeralia {
  display: flex;
  flex-direction: column;
  gap: 4px;
}

.numeralia--blue   { --numeralia-from: #007fba; --numeralia-to: #00d1ff; }
.numeralia--orange { --numeralia-from: #d06700; --numeralia-to: #eb8b2d; }

.numeralia__value {
  font-family: var(--display);
  font-size: clamp(36px, 2rem + 1vw, 48px);
  font-weight: 700;
  line-height: 1.1;
  background: linear-gradient(229deg, var(--numeralia-from), var(--numeralia-to));
  -webkit-background-clip: text;
  background-clip: text;
  color: transparent;
  /* Fallback for anything that ignores background-clip: text — without it the
     number would be invisible rather than merely flat. */
  -webkit-text-fill-color: transparent;
}

@supports not ((background-clip: text) or (-webkit-background-clip: text)) {
  .numeralia__value { color: var(--numeralia-to); -webkit-text-fill-color: currentColor; }
}

.numeralia__label {
  color: #7a8ca0;
  font-family: var(--display);
  font-size: 11px;
  font-weight: 500;
  letter-spacing: 1.7px;
  line-height: 1.45;
  text-transform: uppercase;
}
