/* ── Yuli 钰理 design system ────────────────────────────────
   Sibling brand to Nemerc (nemerc.trade), deliberately built on the same
   skeleton and the opposite half of the colour wheel, so the two read as
   peers rather than as one being a recolour of the other.

     Nemerc   warm graphite #26272A · antique brass #9C7A3C (hue ~40°)
     Yuli  cool ink      #1F2630 · qing 青    #2E6F8E (hue ~200°)

   Palette: cool ink #1F2630 · qing blue #2E6F8E (primary accent: CTAs,
   links, eyebrows, rules) · deep cinnabar #5E2A38 (secondary sprinkle —
   the 朱砂 seal-red of a scholar's chop, used in small doses only) ·
   cool off-white #F3F5F7 · slate #4A545F. No gradients, no shadows,
   hairlines only — same restraint as Nemerc.

   Every foreground/background pair below is checked against WCAG AA;
   see design/palettes/check-contrast.py to re-verify after any change.

   Type: Source Serif 4 for display, Source Sans 3 for body and UI.
   Both are Adobe open-source families with exact CJK siblings
   (思源宋体 Source Han Serif SC / 思源黑体 Source Han Sans SC), so the
   Chinese edition drops in without redrawing the typographic scale. */

@font-face {
  font-family: 'Source Serif 4';
  src: url('../fonts/source-serif-latin.woff2') format('woff2');
  font-weight: 400 700;
  font-display: swap;
}

@font-face {
  font-family: 'Source Sans 3';
  src: url('../fonts/source-sans-latin.woff2') format('woff2');
  font-weight: 400 700;
  font-display: swap;
}

/* ── Chinese type ──────────────────────────────────────────────────────────
   Source Serif/Sans have no CJK coverage, so Chinese needs 思源宋体 /
   思源黑体 (shipped as their Google Fonts name, Noto Serif/Sans SC — the same
   Adobe designs, drawn to share the Source families' proportions).

   These are subsetted to the exact characters the site uses, but even so a
   CJK face is an order of magnitude heavier than a Latin one, so they are
   split in two:

     *-brand   the handful of characters that appear on ENGLISH pages
               (钰理, and 中文 in the language switch) — ~3 KB each
     full      everything the Chinese pages need — ~103-134 KB each

   The two halves carry different family names on purpose. A browser only
   downloads a face it actually uses, so naming the full faces only inside the
   [lang="zh-Hans"] rules below keeps English pages from pulling 237 KB of CJK
   to render two glyphs. */

@font-face {
  font-family: 'Noto Serif SC Brand';
  src: url('../fonts/noto-serif-sc-brand-2.woff2') format('woff2');
  font-weight: 400 700;
  font-display: swap;
}

@font-face {
  font-family: 'Noto Sans SC Brand';
  src: url('../fonts/noto-sans-sc-brand-2.woff2') format('woff2');
  font-weight: 400 700;
  font-display: swap;
}

@font-face {
  font-family: 'Noto Serif SC';
  src: url('../fonts/noto-serif-sc-2.woff2') format('woff2');
  font-weight: 400 700;
  font-display: swap;
}

@font-face {
  font-family: 'Noto Sans SC';
  src: url('../fonts/noto-sans-sc-2.woff2') format('woff2');
  font-weight: 400 700;
  font-display: swap;
}

/* ── The 破折号 ────────────────────────────────────────────────────────────
   Chinese writes a dash as U+2014 doubled (——), and the pair is meant to read
   as one unbroken rule. That only works if the glyph is full-width and drawn
   edge to edge, which is how a CJK face cuts it.

   Source Sans and Source Serif both *cover* U+2014, so on a Chinese page the
   Latin face — first in the stack — won it, and drew two proportional dashes
   with side bearings inside a 0.81em box. They could not meet, so every 破折号
   on the site rendered as two short dashes with a gap between them.

   The Han subsets cannot fix it: they were cut from the CJK codepoints in
   content/*.json, and U+2014 sits in General Punctuation, so it is not in them.
   Rather than re-cut a 120 KB face for one glyph, these carry that one glyph
   and nothing else, and `unicode-range` keeps them to it. 1.5 KB each, and only
   the Chinese stack names them — see :root:lang(zh) below. */

@font-face {
  font-family: 'Noto Serif SC Punct';
  src: url('../fonts/noto-serif-sc-punct.woff2') format('woff2');
  font-weight: 400 700;
  font-display: swap;
  unicode-range: U+2014;
}

@font-face {
  font-family: 'Noto Sans SC Punct';
  src: url('../fonts/noto-sans-sc-punct.woff2') format('woff2');
  font-weight: 400 700;
  font-display: swap;
  unicode-range: U+2014;
}

/* ── Tokens and base ── */

:root {
  --ink: #1F2630;
  --qing: #2E6F8E;
  /* --qing itself stays the true brand blue (borders, underlines, rules,
     the logo hairline — non-text UI only needs 3:1). Text set in the brand
     tone needs the full 4.5:1, which --qing does not clear against every
     background it lands on, so it gets darker/lighter variants below
     rather than the brand colour being bent to fit. */
  --eyebrow-color: #1F5A76;   /* --qing darkened for 4.5:1 on --bg/--mist/--surface */
  --qing-on-dark: #7FB6D0;    /* --qing lightened for 4.5:1 on any dark surface */
  --qing-btn: #256080;        /* --qing darkened for 4.5:1 with white button text */
  --qing-btn-hover: #1D5069;
  --accent2: #5E2A38;         /* seal-red sprinkle: tags, small rules */
  --bg: #F3F5F7;
  --mist: #E6EBEF;
  --ink-60: rgba(31, 38, 48, 0.76); /* WCAG AA (4.5:1) on --bg/--surface/--mist */
  --line: rgba(74, 84, 95, 0.22);
  --surface: #fff;            /* cards, inputs */
  --band: #1F2630;            /* dark accent sections (CTA band) */
  --btn-bg: #1F2630;
  --btn-bg-strong: #141A22;
  --btn-fg: #fff;
  /* CJK sits after the Latin face in each stack, so Latin glyphs always come
     from Source and only the characters Source lacks fall through to Noto.
     The system CJK faces trailing behind cover anything outside the subset. */
  --cjk-serif: 'Noto Serif SC Brand', 'Songti SC', 'SimSun', serif;
  --cjk-sans: 'Noto Sans SC Brand', 'PingFang SC', 'Microsoft YaHei', sans-serif;
  /* Split into parts so the Chinese stacks below can be written as the same
     three pieces plus one, instead of as a second full copy that drifts. */
  --latin-serif: 'Source Serif 4';
  --latin-sans: 'Source Sans 3';
  --sys-serif: ui-serif, Georgia, 'Times New Roman', serif;
  --sys-sans: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif;
  --font-display: var(--latin-serif), var(--cjk-serif), var(--sys-serif);
  --font: var(--latin-sans), var(--cjk-sans), var(--sys-sans);
  --container: 1200px;
  --gutter: clamp(1rem, 4vw, 2rem);
  --header-h: 72px;           /* .site-header__inner + its 1px bottom border */
  color-scheme: light;
}

/* Dark theme: off-white ink on a cool near-black ground, same qing accent,
   hairlines only. [data-theme] is set pre-paint by an inline script in each
   page head. */
[data-theme="dark"] {
  --ink: #E9EDF1;
  --eyebrow-color: var(--qing-on-dark); /* lighter qing for 4.5:1 on the dark ground */
  --bg: #14181D;
  --mist: #1B2027;
  --ink-60: rgba(233, 237, 241, 0.62);
  --line: rgba(127, 182, 208, 0.22);
  --surface: #1F252C;
  --band: #1B2027;
  --btn-bg: #E9EDF1;
  --btn-bg-strong: #fff;
  --btn-fg: #14181D;
  /* The seal-red lightened for the dark ground. Any light tint of a 344°
     burgundy reads pink — that is what this colour looks like when lightened,
     not a mistake — so the fix is saturation, not a hue shift. Pushed from
     33% to 44%, which keeps it recognisably the same accent instead of
     turning it into a cinnabar the light theme does not have. Clears 4.5:1
     on --bg, --mist/--band and --surface. */
  --accent2: #C87189;
  color-scheme: dark;
}

* { box-sizing: border-box; margin: 0; padding: 0; }

/* Prevents a flash of default white in the iOS rubber-band overscroll area,
   which shows the <html> background (not body's) past the page's edges. */
html { scroll-behavior: smooth; background: var(--bg); }

body {
  font-family: var(--font);
  font-size: 1rem;
  line-height: 1.65;
  color: var(--ink);
  background: var(--bg);
  -webkit-font-smoothing: antialiased;
  /* Sticky footer: every template is body > .skip-link, .site-header,
     main#main, .site-footer. 100dvh on top of 100vh: vh is the fallback for
     browsers without dvh support, dvh corrects for mobile browser chrome
     (address bar) that vh alone gets wrong. */
  min-height: 100vh;
  min-height: 100dvh;
  display: flex;
  flex-direction: column;
}

/* Grows to absorb leftover vertical space so .site-footer is pushed to the
   bottom of the viewport on short pages, while still expanding normally and
   pushing the footer below the fold on tall ones. */
main { flex: 1 0 auto; }

/* Bottom-edge iOS rubber-band overscroll: past the document's real bottom
   edge only the <html> background paints, but .site-footer is deliberately
   the dark var(--band), so the light var(--bg) canvas showing through during
   the bounce reads as a light strip where a dark one is expected. A flat html
   background can't fix this alone, because the *same* colour also paints the
   *top* overscroll fringe, which needs to stay light to match .site-header.
   So instead: a fixed, viewport-pinned dark patch sits behind the footer at
   the very bottom of the screen at all times. The negative z-index keeps it
   painted below normal in-flow content, so it is fully hidden in normal
   scroll and only shows in the sliver the bouncing document reveals. */
body::after {
  content: "";
  position: fixed;
  left: 0;
  right: 0;
  bottom: 0;
  height: 200px;
  background: var(--band);
  z-index: -1;
  pointer-events: none;
}

/* `height: auto` is not optional alongside max-width here. Every <img> on the
   site carries width/height attributes so the browser can reserve the right
   box before the file arrives (no layout shift), but those attributes also map
   to CSS width/height as presentational hints — so without this, an image
   scaled down by max-width keeps its full intrinsic height and any
   aspect-ratio rule is silently ignored. */
img { display: block; max-width: 100%; height: auto; }

a { color: inherit; }

.container {
  max-width: var(--container);
  margin: 0 auto;
  padding-left: var(--gutter);
  padding-right: var(--gutter);
}

/* ── Type ── */

h1, h2, h3 {
  font-family: var(--font-display);
  font-weight: 600;
  line-height: 1.18;
  letter-spacing: -0.008em;   /* Source Serif needs less negative tracking than Inter did */
  /* Evens the lines instead of filling each one and leaving the remainder on
     its own. Chinese needs it more than English: Han has no word spaces for
     the line breaker to work with, so "钰理——知道东西是从哪里来的。" broke at the
     column edge and orphaned 来的。 on a line by itself. Ignored where
     unsupported, which costs nothing. */
  text-wrap: balance;
}

/* `pretty` is the paragraph-scale version: it only prevents a last line with a
   single short word, without the reflow cost of balancing every line. */
p, .lead, dd { text-wrap: pretty; }

h1 { font-size: clamp(1.75rem, 4vw, 2.75rem); }
h2 { font-size: clamp(1.35rem, 3vw, 2rem); }
h3 { font-size: 1.1rem; }

.eyebrow {
  display: block;
  font-size: 0.75rem;
  font-weight: 600;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--eyebrow-color);
  margin-bottom: 1rem;
}

/* .section--ink/.cta-band are always a dark band regardless of site theme
   (--band), so the eyebrow needs the light-on-dark brass in both themes. */
.section--ink { --eyebrow-color: var(--qing-on-dark); }

/* Seal-red sprinkle: the one place the burgundy appears at body scale. */
.seal { color: var(--accent2); }
/* The seal-red rule is a section divider, not an underline — it needs air on
   both sides. With a top margin only, the paragraph after it sat flush against
   the rule and read as a strikethrough. */
.seal-rule {
  width: 3.25rem;
  height: 3px;
  background: var(--accent2);
  margin: 1.5rem 0 0;
  border: 0;
}

.seal-rule + p { margin-top: 1.5rem; }

/* The lead paragraph takes the display serif too — it is the bridge between
   the headline and the sans body copy, and reads as part of the headline. */
.lead {
  font-family: var(--font-display);
  font-size: clamp(1.05rem, 2vw, 1.25rem);
  line-height: 1.55;
  color: var(--ink-60);
  max-width: 38em;
}

.muted { color: var(--ink-60); }

/* ── Chinese pages ─────────────────────────────────────────────────────────
   Swapping the two CJK custom properties is the whole language switch: every
   rule already reads its font through --font / --font-display, so nothing else
   needs a :lang() variant. This is also what triggers the download of the full
   CJK faces — they are named nowhere else, so English pages never fetch them. */

:root:lang(zh) {
  --cjk-serif: 'Noto Serif SC', 'Songti SC', 'SimSun', serif;
  --cjk-sans: 'Noto Sans SC', 'PingFang SC', 'Microsoft YaHei', sans-serif;
  /* The punctuation face goes first, ahead of Latin, because it is the only
     way to stop Source claiming U+2014. It covers that one codepoint, so
     everything else falls straight through to the same stack English uses.
     English pages never name it, and so keep a Latin em dash. */
  --font-display: 'Noto Serif SC Punct', var(--latin-serif), var(--cjk-serif), var(--sys-serif);
  --font: 'Noto Sans SC Punct', var(--latin-sans), var(--cjk-sans), var(--sys-sans);
}

/* Latin typographic reflexes that actively hurt Chinese.

   Negative tracking is drawn for Latin sidebearings; Han glyphs are already
   flush to an em box, so tightening them jams the strokes together. Chinese
   also has no word spaces to open a line up, so it needs more leading than the
   same point size of Latin, not less. And there is no lowercase, so uppercase
   + wide tracking on the eyebrow reads as broken-apart characters rather than
   as a small-caps label. */

:lang(zh) h1,
:lang(zh) h2,
:lang(zh) h3 {
  letter-spacing: 0;
  line-height: 1.4;
}

:lang(zh) body,
:lang(zh) p {
  line-height: 1.85;
}

:lang(zh) .lead { line-height: 1.8; }

:lang(zh) .eyebrow {
  text-transform: none;
  letter-spacing: 0.18em;
  font-size: 0.8125rem;
}

/* Over the hero photograph the eyebrow is small, tracked-out, and sitting on a
   busy mid-grey. Latin caps survive that; Han strokes at the same size are thin
   enough to dissolve into the weave, so the Chinese hero gets a size and weight
   bump rather than a colour change (which would cost the qing accent). */
:lang(zh) .hero .eyebrow {
  font-size: 0.9375rem;
  font-weight: 700;
  letter-spacing: 0.12em;
}

/* Han punctuation carries its own generous sidebearings, so a bulleted
   separator between characters needs no extra help. */
:lang(zh) .site-footer__legal { letter-spacing: 0.02em; }

/* ── Buttons ── */

.btn {
  display: inline-flex;
  align-items: center;
  gap: 0.5rem;
  font: inherit;
  font-size: 0.9375rem;
  font-weight: 500;
  letter-spacing: 0.02em;
  padding: 0.8rem 1.6rem;
  background: var(--btn-bg);
  color: var(--btn-fg);
  border: 1px solid var(--btn-bg);
  text-decoration: none;
  cursor: pointer;
  transition: background 0.25s ease, color 0.25s ease;
}

.btn:hover { background: var(--btn-bg-strong); border-color: var(--btn-bg-strong); }

.btn--outline { background: transparent; color: var(--ink); }
.btn--outline:hover { background: var(--ink); color: var(--bg); }

.btn--qing { background: var(--qing-btn); border-color: var(--qing-btn); color: #fff; }
.btn--qing:hover { background: var(--qing-btn-hover); border-color: var(--qing-btn-hover); }

.btn--small { padding: 0.55rem 1.1rem; font-size: 0.875rem; }

.btn[disabled] { opacity: 0.5; cursor: default; }

.text-link {
  font-weight: 500;
  text-decoration: none;
  border-bottom: 1px solid var(--qing);
  padding-bottom: 1px;
}
.text-link:hover { color: var(--eyebrow-color); }

/* ── Button row ── */

.btn-row {
  display: flex;
  flex-wrap: wrap;
  gap: 0.75rem;
  margin-top: 1.65rem;
}

/* ── Header ── */

.site-header {
  position: sticky;
  top: 0;
  z-index: 100;
  background: var(--bg);
  border-bottom: 1px solid var(--line);
  isolation: isolate;
}

.site-header__inner {
  display: flex;
  align-items: center;
  justify-content: space-between;
  height: calc(var(--header-h) - 1px);
  position: relative;
  z-index: 1;
}

.wordmark { display: flex; align-items: center; flex-shrink: 0; }

/* Taller than Nemerc's 24px geometric mark: the 钰理 brush strokes carry
   fine detail that turns to mud much below ~28px. */
.wordmark img { height: 30px; width: auto; display: block; flex-shrink: 0; }

/* The logo's ink is baked into the SVG rather than inherited (it is an <img>,
   so currentColor never reaches it), which means the light lockup all but
   disappears on the dark ground. Ship both and swap on the theme attribute —
   set pre-paint by the inline script in each <head>, so there is no flash.
   Nemerc does this with `content: url(...)` on a single <img> instead; two
   elements cost one extra 9 KB SVG but do not depend on `content` working on
   a replaced element, and keep the alt text attached to the image that is
   actually shown. */
/* Selectors qualified with `img` so they outweigh the `.wordmark img` rule
   above — a bare `.wordmark__dark` loses that specificity fight and both
   lockups paint side by side. */
.wordmark img.wordmark__dark { display: none; }
[data-theme="dark"] .wordmark img.wordmark__light { display: none; }
[data-theme="dark"] .wordmark img.wordmark__dark { display: block; }

.site-nav { display: flex; align-items: center; gap: 1rem; margin-inline-start: auto; }

.site-nav a {
  position: relative;
  display: inline-flex;
  align-items: center;
  min-height: 44px;
  font-size: 0.9rem;
  font-weight: 500;
  letter-spacing: 0.04em;
  text-decoration: none;
  color: var(--ink-60);
  padding: 0 0.5rem;
}

.site-nav a:hover, .site-nav a[aria-current="page"] { color: var(--ink); }
.site-nav a[aria-current="page"]::after {
  content: "";
  position: absolute;
  bottom: 0;
  left: 0;
  right: 0;
  height: 2px;
  background: var(--qing);
}

.nav-toggle {
  display: none;
  background: none;
  border: 0;
  cursor: pointer;
  padding: 0.5rem;
  color: var(--ink);
  margin-right: -0.5rem; /* Optically align hamburger icon with the container edge on mobile */
}

@media (max-width: 850px) {
  .nav-toggle { display: block; }
  .site-nav {
    display: none;
    position: absolute;
    top: 100%;
    left: 0;
    right: 0;
    flex-direction: column;
    gap: 0;
    background: var(--bg);
    border-bottom: 1px solid var(--line);
    padding: 0.5rem var(--gutter) 1rem;
    z-index: 2;
  }
  .site-nav.open { display: flex; }
  .site-nav a { padding: 0.5rem 0; font-size: 0.9375rem; }
  .site-nav a[aria-current="page"] { color: var(--eyebrow-color); }
  .site-nav a[aria-current="page"]::after { display: none; }
  .wordmark { margin-right: auto; flex-shrink: 1; min-width: 0; }
  .wordmark img { max-width: min(218px, 42vw); }
  .theme-toggle,
  .nav-toggle { position: relative; z-index: 3; flex-shrink: 0; }
}

/* ── Language switch ── */

.lang-switch {
  font-size: 0.8125rem;
  font-weight: 600;
  letter-spacing: 0.06em;
  padding: 0.3rem 0.7rem;
  border: 1px solid var(--line);
  border-radius: 2px;
  color: var(--ink-60);
  min-height: 0;
}

.lang-switch:hover {
  color: var(--ink);
  border-color: var(--qing);
}

/* The switch is a sibling of the nav links, which claim a 44px hit area and an
   aria-current underline. It wants neither — it is not a page in the section. */
.site-nav a.lang-switch[aria-current]::after { content: none; }

/* The switch sits outside .site-nav on purpose. Inside it, it would vanish
   behind the hamburger on mobile — two taps for a Chinese reader who has
   landed on the English page, which is the one journey it exists to shorten. */
.site-header__inner > .lang-switch { margin-inline-start: 1rem; flex-shrink: 0; }

@media (max-width: 850px) {
  .site-header__inner > .lang-switch { margin-inline-start: auto; margin-inline-end: 0.5rem; }
}

/* ── Hero ────────────────────────────────────────────────────────────────
   A split hero rather than the full-bleed photograph nemerc.trade uses: text
   on a solid ground in one column, the photography in the other. Same content,
   structurally different page — which is the point, since the two sites are
   meant to read as separate companies rather than one recoloured.

   It also removes the scrim problem. White text over a photograph needs a
   tint heavy enough for the lightest frame in the slideshow, which means
   permanently dimming a good photograph to protect a paragraph. Here the type
   sits on --mist at full contrast and the photograph is left alone. */

.hero {
  position: relative;
  display: grid;
  background: var(--mist);
  color: var(--ink);
  border-bottom: 1px solid var(--line);
}

.hero__media {
  position: relative;
  overflow: hidden;               /* clips the slideshow's slow zoom */
  background: var(--band);        /* shows before the eager photo paints */
  min-height: 42vh;
}

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

/* Extra slideshow slides: hidden, and not downloaded (data-src), until
   motion.js starts the rotation. */
.hero__img--alt { opacity: 0; }

.hero__content {
  align-self: center;
  padding-block: clamp(1.75rem, 3.2vw, 2.5rem);
  padding-inline: var(--gutter);
}

/* The middle term only binds between about 700px and 1130px, where the copy
   column is at its most cramped and the headline takes two lines. Easing it
   from 4.6vw takes those two lines down by ~8px without touching the size at
   1200px and up, which is where the type was tuned. */
.hero__content h1 {
  font-size: clamp(2rem, 4.2vw, 3.25rem);
  max-width: 14em;
}

/* Han glyphs are full-width, so the same point size eats far more measure than
   Latin does: "我们从布的源头开始。" is ten characters and overran the column by
   about one, leaving 始。 stranded on a line of its own. */
:lang(zh) .hero__content h1 {
  font-size: clamp(1.75rem, 3.6vw, 2.75rem);
  max-width: 16em;
}

.hero__content .lead { margin-top: 1rem; }

.hero__actions {
  display: flex;
  flex-wrap: wrap;
  gap: 0.75rem;
  margin-top: 1.35rem;
}

@media (min-width: 861px) {
  .hero {
    /* Even halves, not the 5:7 this started as. The copy column also pays the
       152px rail indent on a 1440 screen, so a nominal 5/12 left it with about
       400px of actual measure and the h1 broke across three lines. */
    grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
    /* Down from min(76vh, 660px). The copy column needs about 350px at a
       1000px viewport, so three quarters of the screen was mostly slack under
       the buttons — and a hero that fills the window pushes the first real
       content off it for no gain. Low enough now that on a laptop the hero is
       close to content-bound, which is where a min-height should sit. */
    min-height: min(60vh, 520px);
  }

  /* Aligns the hero copy with the container's left edge on wide screens, so
     the h1 sits on the same rail as every heading below it, while the column
     itself still runs to the edge of the viewport. */
  .hero__content {
    /* 100vw, not 100% — a percentage here resolves against this column
       (~5/12 of the page), so the max() always chose the plain gutter and the
       headline never reached the rail. */
    padding-inline-start: max(var(--gutter), calc((100vw - var(--container)) / 2 + var(--gutter)));
    padding-inline-end: clamp(1.75rem, 4vw, 3.5rem);
  }
}

@media (max-width: 860px) {
  /* The copy comes first in the source — headline before photograph is the
     right reading order, and it puts the text in the left column on desktop
     with no reordering. On a phone the image goes back on top, because that
     is what survives being seen for one second. */
  .hero__media { order: -1; min-height: 46vh; }
}

/* Shorter than the home hero, which is meant to hold a whole screen. Contact
   opens with a statement, but the thing the reader came for is the address in
   the band underneath — so the hero stops short of the fold and lets the top
   of that band show, rather than making them scroll to find out there is one.
   Without this the page read as a headline on an empty ground followed by a
   dark slab: two blocks of air and no photograph, on a site where every other
   page opens with one. */
@media (min-width: 861px) {
  .hero--compact { min-height: min(58vh, 460px); }
}

@media (max-width: 860px) {
  .hero--compact .hero__media { min-height: 34vh; }
}

/* ── Sections ── */

.section { padding: clamp(2.5rem, 5vw, 3.5rem) 0; }
main > .section:first-child,
main > .hero + .section { padding: clamp(2.25rem, 4vw, 3rem) 0; }
.section--mist { background: var(--mist); }
.section--ink { background: var(--band); color: #EEF2F5; border-top: 1px solid var(--line); border-bottom: 1px solid var(--line); }
.section-head { max-width: 44em; margin-bottom: clamp(1.35rem, 3vw, 2rem); }
.section-head .lead { margin-top: 0.75rem; }

/* .section-head is 44em wide, which is a comfortable measure for the .lead at
   its larger size but runs to ~80 characters for body copy set at 1rem. Cap
   the smaller text separately so both sit near 65-70. */
.section-head p:not(.lead) { max-width: 34em; }
.section-head h1 + .lead,
.section-head h2 + .lead { margin-top: 1rem; }

/* Mobile: same rhythm, less vertical air — desktop spacing stays generous. */
@media (max-width: 720px) {
  :root { --gutter: clamp(0.875rem, 3.5vw, 1.25rem); }

  .section { padding: clamp(1.75rem, 5vw, 2.35rem) 0; }
  .section-head { margin-bottom: clamp(1.1rem, 3.5vw, 1.5rem); }
  .section-head .lead { margin-top: 0.5rem; }
  .eyebrow { margin-bottom: 0.65rem; }

  .hero__content { padding: clamp(1.75rem, 5vw, 2.5rem) var(--gutter); }
  .hero__content .lead { margin-top: 0.85rem; }
  .hero__actions { margin-top: 1.25rem; }

  .cta-band .btn { margin-top: 1.25rem; }
  .site-footer { padding: 1.75rem 0; }
}

/* Fact strip */

/* One continuous rule across the row with hairline dividers between the
   columns, rather than three separate bars. It reads as a single strip — a
   ledger row — instead of three cards that happen to be adjacent, which suits
   a page about specifications better. The rule is the brand blue; the dividers
   are the ordinary hairline, so the eye reads the row first and the columns
   second. */
.facts {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 0;
  border-top: 2px solid var(--qing);
}

.fact {
  padding: 1.2rem clamp(1.25rem, 3vw, 2.25rem) 0;
  border-inline-start: 1px solid var(--line);
}

/* The first column sits on the container's left rail, so it takes no divider
   and no indent — the row starts flush with every heading on the page. */
.fact:first-child {
  border-inline-start: 0;
  padding-inline-start: 0;
}
/* Serif, to match .step h3 — these are the same rank of thing on the two
   pages and were set in different families. */
.fact strong {
  display: block;
  font-family: var(--font-display);
  font-size: 1.15rem;
  font-weight: 600;
  letter-spacing: -0.008em;
  margin-bottom: 0.4rem;
}

:lang(zh) .fact strong { letter-spacing: 0; }
.fact p { font-size: 0.9375rem; color: var(--ink-60); }
.section--ink .fact p { color: rgba(250, 250, 248, 0.7); }

@media (max-width: 720px) {
  /* Stacked, the vertical dividers become horizontal ones between rows. */
  .facts { grid-template-columns: 1fr; }

  .fact {
    padding: 1rem 0 1.25rem;
    border-inline-start: 0;
    border-top: 1px solid var(--line);
  }

  .fact:first-child { border-top: 0; padding-top: 1.1rem; }
}

/* Split layout (about / story) */

.split {
  display: grid;
  grid-template-columns: 5fr 6fr;
  gap: clamp(1.75rem, 4vw, 3.5rem);
}

.split > * {
  align-self: center;
}

.split img { 
  width: 100%; 
  aspect-ratio: 1 / 1; 
  object-fit: cover; 
  background: var(--mist); 
  align-self: stretch;
  min-height: 100%;
}

@media (max-width: 800px) {
  .split { grid-template-columns: 1fr; }
  .split img { aspect-ratio: 16 / 9; min-height: auto; }
}

/* Steps */

/* Same strip treatment as .facts — one rule across the row, hairlines between
   the columns. They are the same kind of element (a row of parallel items) on
   the two pages, and were drawn as two different things. The rule here is the
   ordinary hairline rather than the brand blue: the facts are the page's claim
   and the steps are its procedure, so the strip is quieter. */
/* Column flow rather than a fixed four, so the strip fits however many steps
   the page has. About has four and Contact has three; hard-coded at four, the
   three-step row left a whole empty column on the right and the section read
   as unfinished. */
.steps {
  display: grid;
  grid-auto-flow: column;
  grid-auto-columns: minmax(0, 1fr);
  gap: 0;
  border-top: 1px solid var(--line);
  counter-reset: step;
}

.step {
  counter-increment: step;
  padding: 1.2rem clamp(1rem, 2.5vw, 1.75rem) 0;
  border-inline-start: 1px solid var(--line);
}

.step:first-child {
  border-inline-start: 0;
  padding-inline-start: 0;
}

.step::before {
  content: counter(step, decimal-leading-zero);
  display: block;
  font-size: 0.8rem;
  font-weight: 600;
  letter-spacing: 0.1em;
  color: var(--eyebrow-color);
  margin-bottom: 0.5rem;
}

.step h3 { margin-bottom: 0.35rem; }
.step p { font-size: 0.9375rem; color: var(--ink-60); }

@media (max-width: 800px) {
  .steps { grid-auto-flow: row; grid-template-columns: 1fr 1fr; }
  /* Two up: the third and fourth start a second row, so they need the rule
     back on top and the left-hand one loses its divider. */
  .step:nth-child(odd) { border-inline-start: 0; padding-inline-start: 0; }
  .step:nth-child(n + 3) { border-top: 1px solid var(--line); margin-top: 1.4rem; }
}

@media (max-width: 520px) {
  .steps { grid-auto-flow: row; grid-template-columns: 1fr; }
  .step {
    padding: 1rem 0 1.25rem;
    border-inline-start: 0;
    margin-top: 0;
  }
  .step + .step { border-top: 1px solid var(--line); }
  .step:first-child { padding-top: 1.1rem; }
}

/* ── Wide section heads ────────────────────────────────────────────────────
   A short measure keeps text readable, but applying it to the heading cluster
   as a whole left every section stacked in the left half of a 1200px
   container with the right half empty — readable, and unfinished-looking.

   NB: a component's responsive rules live with the component, not here. The
   CTA band's equivalent block used to sit in this section, which put it after
   the base .cta-band rules by accident of file order; moving the sections
   around silently reversed that and gave the button back a margin it was
   meant to lose. Keep overrides next to what they override.

   Above 900px the cluster becomes two columns: eyebrow and heading on the
   left, lead on the right, bottom-aligned so the two land on a shared
   baseline. Each column keeps its own comfortable measure; the section just
   stops pretending the page is 600px wide. */

@media (min-width: 900px) {
  .section-head {
    max-width: none;
    display: grid;
    grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
    column-gap: clamp(1.75rem, 4vw, 3.5rem);
    align-items: end;
  }

  .section-head > * { grid-column: 1; }

  .section-head .eyebrow { grid-row: 1; }
  .section-head h1,
  .section-head h2 { grid-row: 2; }

  /* Spans both heading rows and sits on the heading's baseline. */
  .section-head .lead {
    grid-column: 2;
    grid-row: 1 / 3;
    align-self: end;
    margin-top: 0;
    max-width: 34em;
  }

  /* Anything after the lead — the seal rule, running text on the 404 — stays
     in the left column below the heading. */
  .section-head .lead ~ * { grid-row: auto; }
}


/* The header is position:sticky, so any in-page jump — the skip link, or a
   future anchor — lands with its target hidden behind it. */
:target,
#main {
  scroll-margin-top: calc(var(--header-h) + 1rem);
}

/* ── Split variants ── */

/* The default .split pairs a 5:6 column ratio with a square image, which suits
   a long text column. Where the copy is only a couple of paragraphs that
   leaves a tall photo beside a short block of text and a lot of dead space, so
   this variant evens the columns and lets the photo keep its native 4:3 —
   shorter, uncropped, and much closer to the height of the text beside it. */
/* Scoped above the breakpoint: declared unconditionally it would land after
   the base `.split { grid-template-columns: 1fr }` mobile rule and win, so the
   section stayed two-up at 390px with a postage-stamp photo beside a squeezed
   column of text. */
@media (min-width: 801px) {
  .split--tight { grid-template-columns: 1fr 1fr; }
}

/* The base .split img is `align-self: stretch` with `min-height: 100%` so a
   square photo can grow to match a tall text column. Both have to be undone
   here or the stretch wins and aspect-ratio is ignored — the image just fills
   the row instead of taking its 4:3 height. */
.split--tight img {
  aspect-ratio: 4 / 3;
  align-self: center;
  min-height: 0;
}

@media (max-width: 800px) {
  .split--tight img { aspect-ratio: 16 / 9; }
}

/* Photo on the right. Home and About both lead their split with the image, so
   a third page doing the same starts to read as a template rather than as a
   layout. Order rather than a reversed column definition, so the source order
   — text first — stays the one a screen reader gets. Below the breakpoint the
   split is a single column and the photo goes back on top with the others. */
@media (min-width: 801px) {
  .split--flip img { order: 2; }
}

/* ── Prose rhythm ──────────────────────────────────────────────────────────
   The generator emits plain <p> runs rather than the per-element inline
   margins the hand-written pages carried, so spacing lives here where it can
   be tuned in one place. */

.split p + p,
.section-head p + p { margin-top: 1.25rem; }

.split__more { margin-top: 1.5rem; }

.section-head .lead + p { margin-top: 1.5rem; }

/* Chinese runs longer than the same English copy and has no ragged word
   spacing to lighten a block, so paragraphs need a little more air between
   them to stay scannable. */
:lang(zh) .split p + p,
:lang(zh) .section-head p + p { margin-top: 1.4rem; }

/* ── The name ──────────────────────────────────────────────────────────────
   钰理 is not decoration on this site, it is the argument: 钰 is a treasure,
   and 理 first meant working jade along its grain, which is what the company
   sells. So they get set at display size in the same brush face the logo is
   drawn from, rather than being mentioned in passing inside a paragraph.

   Real text, not an image or inline SVG — a two-glyph subset of Ma Shan Zheng
   is 1.6 KB, which is less than the outlines would have cost, and keeps the
   characters selectable, searchable and readable to a screen reader. */

@font-face {
  font-family: 'Ma Shan Zheng';
  src: url('../fonts/ma-shan-zheng-brand-2.woff2') format('woff2');
  font-weight: 400;
  font-display: swap;
}

/* The two characters take one column each of the same two-column rhythm the
   section head above them uses, so the rules over 钰 and 理 line up with the
   heading and the lead rather than floating on their own grid. */
.glyphs {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 0;
  border-top: 2px solid var(--qing);
  margin-bottom: clamp(1.25rem, 3vw, 1.85rem);
}

/* Same strip as .facts and .steps — see design/README.md. */
.glyph {
  padding: 1.2rem clamp(1.25rem, 3vw, 2.25rem) 0;
  border-inline-start: 1px solid var(--line);
}

.glyph:first-child {
  border-inline-start: 0;
  padding-inline-start: 0;
}

.glyph dt {
  display: flex;
  align-items: baseline;
  gap: 0.85rem;
}

.glyph__char {
  font-family: 'Ma Shan Zheng', var(--cjk-serif);
  font-size: clamp(3.5rem, 8vw, 5.25rem);
  line-height: 0.9;
  /* The brush strokes are drawn to sit on their own em box; the Latin
     tracking applied to headings would only smear them. */
  letter-spacing: 0;
}

.glyph__pinyin {
  font-family: var(--font-display);
  font-size: clamp(1rem, 2.4vw, 1.2rem);
  color: var(--eyebrow-color);
  /* Lifted off the character's baseline: the brush glyph's descender drops
     well below it, which left the romanisation looking dropped rather than
     set beside the character. */
  align-self: flex-end;
  padding-bottom: 0.75rem;
}

/* Pinyin is a reading aid for people who do not read the characters. To a
   Chinese reader, annotating 钰 and 理 reads like a primary-school textbook,
   so the Chinese pages show the characters alone. */
:lang(zh) .glyph__pinyin { display: none; }

.glyph dd {
  margin-top: 0.6rem;
  color: var(--ink-60);
  font-size: 0.9375rem;
  max-width: 22em;
}


@media (max-width: 560px) {
  .glyphs { grid-template-columns: 1fr; }
  .glyph {
    padding: 1rem 0 1.25rem;
    border-inline-start: 0;
  }
  .glyph + .glyph { border-top: 1px solid var(--line); }
  .glyph dd { max-width: none; }
}

/* ── Spec table ────────────────────────────────────────────────────────────
   A fabric buyer scans for numbers first — composition, weight, width — and
   these were previously buried mid-sentence in a lead paragraph, which is the
   one place nobody looks for them. Set as an actual term/definition list they
   are scannable in a glance, and the page reads as though it was written by
   someone who sells cloth. */

.spec__label {
  font-size: 0.75rem;
  font-weight: 600;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--eyebrow-color);
  margin-bottom: 0.85rem;
}

:lang(zh) .spec__label { text-transform: none; letter-spacing: 0.18em; }

.spec { border-top: 1px solid var(--line); }

.spec__row {
  display: grid;
  grid-template-columns: minmax(7.5rem, 34%) 1fr;
  gap: 0 1rem;
  padding: 0.6rem 0;
  border-bottom: 1px solid var(--line);
}

.spec dt {
  font-size: 0.8125rem;
  font-weight: 600;
  letter-spacing: 0.03em;
  color: var(--ink-60);
  /* Optical alignment: the label is smaller than the value, so matching their
     baselines needs a nudge rather than a shared row alignment. */
  align-self: center;
}

.spec dd {
  font-size: 0.9375rem;
  /* Tabular figures keep the numbers in the weight and width rows lining up
     with each other rather than drifting by digit width. */
  font-variant-numeric: tabular-nums;
}

.spec__note {
  margin-top: 0.85rem;
  margin-bottom: 1.25rem;
  font-size: 0.875rem;
  color: var(--ink-60);
}

/* Two columns hold down to about 360px — a 120px label still leaves ~225px
   for the longest value ("65% polyester / 35% rayon", ~190px). Stacking any
   earlier doubles the table's height for no gain. */
@media (max-width: 359px) {
  .spec__row { grid-template-columns: 1fr; gap: 0.15rem; }
}

/* ── Pull quote ────────────────────────────────────────────────────────────
   One line per page at most. It is a change of pace in a column of body copy,
   which only works while it stays rare — a page with three of these has none.

   Set in the display serif at lead size and marked with the seal-red rather
   than the qing: the accent is reserved for small, deliberate moments, and a
   sentence the whole section is built around is exactly that. */

/* A plain column of body copy at a readable measure. Distinct from
   .section-head, which owns the eyebrow/heading/lead cluster and its own
   bottom margin — nesting running text inside that was giving the section two
   heads and doubling the gap under it. */
.prose { max-width: 34em; }
.prose p + p { margin-top: 1.25rem; }
:lang(zh) .prose p + p { margin-top: 1.4rem; }

.pull {
  max-width: 34em;
  margin: clamp(1.75rem, 4vw, 2.5rem) 0;
  padding-left: clamp(1.25rem, 3vw, 2rem);
  border-left: 3px solid var(--accent2);
}

.pull p {
  font-family: var(--font-display);
  font-size: clamp(1.25rem, 3vw, 1.75rem);
  line-height: 1.35;
  letter-spacing: -0.01em;
  color: var(--ink);
}

:lang(zh) .pull p { letter-spacing: 0; line-height: 1.6; }

@media (max-width: 560px) {
  .pull { padding-left: 1rem; }
}

/* ── Contact band ─────────────────────────────────────────────────────────
   The address, set large, in the same dark band the CTA uses elsewhere — on
   the contact page the address IS the call to action, so it takes the band
   rather than sitting in a paragraph above one.

   It is a link and plain selectable text at the same time, deliberately. A
   `mailto:` does nothing at all for a reader in webmail with no handler
   registered, which is a large share of buyers and most of them on a phone;
   the copy button site.js adds covers those, and selecting the text covers
   the rest. */

/* The address takes the full width of the band with the copy button opposite
   it, and the practicalities run underneath as a strip. The first arrangement
   put the strip in a second column beside the address, which left ~150px of
   empty band under the button — the two blocks are not the same height and
   never will be. */
.contact-band__main {
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto;
  column-gap: clamp(1.5rem, 4vw, 3rem);
  align-items: center;
}

.contact-address {
  display: inline-block;
  font-family: var(--font-display);
  font-size: clamp(1.375rem, 4.4vw, 2.5rem);
  line-height: 1.15;
  letter-spacing: -0.015em;
  color: #EEF2F5;
  text-decoration: underline;
  text-decoration-thickness: 1px;
  text-decoration-color: var(--qing-on-dark);
  text-underline-offset: 0.18em;
  /* An address is one unbroken token, so it will not wrap on its own and a
     longer one than today's would push the band into a horizontal scroll.
     This is the safety net for whatever the real address turns out to be. */
  overflow-wrap: anywhere;
}

.contact-address:hover { color: var(--qing-on-dark); }

.contact-band__note {
  margin-top: 1rem;
  max-width: 34em;
  font-size: 0.9375rem;
  color: rgba(250, 250, 248, 0.7);
}

.contact-band__phone {
  margin-top: 0.5rem;
  font-size: 0.9375rem;
  color: rgba(250, 250, 248, 0.7);
}

.contact-band__phone a {
  color: #EEF2F5;
  white-space: nowrap;
  text-decoration: underline;
  text-decoration-color: rgba(250, 250, 248, 0.3);
  text-underline-offset: 0.2em;
}

.contact-band__phone a:hover { color: var(--qing-on-dark); text-decoration-color: currentColor; }

/* Created by site.js, and only where the clipboard API exists — a copy button
   that silently does nothing is worse than no button. */
.contact-copy {
  justify-self: end;
  padding: 0.55rem 1.1rem;
  font: inherit;
  font-size: 0.8125rem;
  letter-spacing: 0.02em;
  white-space: nowrap;
  color: #EEF2F5;
  background: transparent;
  border: 1px solid rgba(250, 250, 248, 0.28);
  border-radius: 2px;
  cursor: pointer;
  transition: border-color 0.15s ease, color 0.15s ease;
}

.contact-copy:hover { border-color: rgba(250, 250, 248, 0.6); }
.contact-copy[data-copied] { color: var(--qing-on-dark); border-color: var(--qing-on-dark); }

/* The same strip as .facts and .steps — one rule across the row, hairlines
   between the columns. Four of them rather than three, and the rule is the
   light-on-dark blue because the band is dark. */
.contact-meta {
  margin-top: clamp(1.75rem, 4vw, 2.25rem);
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  gap: 0;
  border-top: 2px solid var(--qing-on-dark);
}

.contact-meta__row {
  padding: 1.2rem clamp(1rem, 2.5vw, 1.75rem) 0;
  border-inline-start: 1px solid rgba(250, 250, 248, 0.16);
}

.contact-meta__row:first-child {
  border-inline-start: 0;
  padding-inline-start: 0;
}

.contact-meta dt {
  font-family: var(--font-display);
  font-size: 1rem;
  font-weight: 600;
  letter-spacing: -0.005em;
  margin-bottom: 0.3rem;
}

:lang(zh) .contact-meta dt { letter-spacing: 0; }

.contact-meta dd { font-size: 0.9375rem; color: rgba(250, 250, 248, 0.7); }
.contact-meta__nowrap { white-space: nowrap; }

/* Four columns need more room than three; below this they go two-up, and the
   vertical dividers become horizontal ones between the rows. */
@media (max-width: 900px) {
  .contact-meta { grid-template-columns: 1fr 1fr; }
  .contact-meta__row:nth-child(odd) { border-inline-start: 0; padding-inline-start: 0; }
  .contact-meta__row:nth-child(n + 3) {
    margin-top: 1.2rem;
    border-top: 1px solid rgba(250, 250, 248, 0.16);
  }
}

@media (max-width: 560px) {
  .contact-meta { grid-template-columns: 1fr; }
  .contact-meta__row {
    padding: 1.1rem 0 0;
    border-inline-start: 0;
  }
  .contact-meta__row + .contact-meta__row {
    margin-top: 0;
    border-top: 1px solid rgba(250, 250, 248, 0.16);
  }
  /* The button has nowhere to sit beside a wrapping address this narrow. */
  .contact-band__main { grid-template-columns: 1fr; }
  .contact-copy { justify-self: start; margin-top: 1.25rem; }
}

/* ── CTA band ── */

.cta-band { text-align: left; }
.cta-band .lead { margin-top: 0.75rem; }
.cta-band .btn { margin-top: 1.5rem; }

/* The CTA band had the same problem in reverse: a short dark band with all
   its content in the left half and the button stacked under it. Above 900px
   the action moves to the right, which is where the eye finishes reading. */
@media (min-width: 900px) {
  .cta-band .container {
    display: grid;
    grid-template-columns: minmax(0, 1fr) auto;
    column-gap: clamp(2rem, 5vw, 4rem);
    align-items: center;
  }

  .cta-band .btn {
    align-self: center;
    margin-top: 0;
    white-space: nowrap;
  }
}
.section--ink .lead { color: rgba(250, 250, 248, 0.75); }

/* ── Footer ── */

/* Same dark band as the CTA section / RFQ bar, so the page ends in one
   continuous dark zone instead of a light strip sandwiched between them.
   No margin-top: that would just reintroduce the same light gap between
   this and a preceding .section--ink CTA band. */
.site-footer {
  background: var(--band);
  color: #EEF2F5;
  border-top: 1px solid var(--line);
  padding: 1.5rem 0;
}

.site-footer {
  padding-top: clamp(1.75rem, 4vw, 2.5rem);
  padding-bottom: clamp(1.5rem, 3.5vw, 2rem);
}

.site-footer__inner {
  display: flex;
  flex-wrap: wrap;
  align-items: flex-start;
  justify-content: space-between;
  gap: 1.75rem;
}

/* The mark, rather than the full lockup: the wordmark already sits in the
   header, and repeating it at the foot of a two-page site reads as padding.
   The mark alone closes the page the way a maker's stamp does. */
.site-footer__brand img {
  width: 66px;
  height: auto;
  margin-bottom: 1rem;
  opacity: 0.85;
}

.site-footer p { font-size: 0.85rem; color: rgba(247, 245, 240, 0.7); }

.site-footer p.site-footer__legal {
  margin-top: 0.3rem;
  font-size: 0.75rem;
  line-height: 1.55;
  color: rgba(247, 245, 240, 0.48);
}

.site-footer nav {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.75rem 1.15rem;
}

@media (min-width: 700px) {
  /* Stacked at the top-right, opposite the mark, rather than strung along a
     single baseline with it. */
  .site-footer nav { flex-direction: column; align-items: flex-end; gap: 0.6rem; }
  .site-footer .theme-toggle { margin-left: 0; margin-top: 0.25rem; }
}

.site-footer nav a { font-size: 0.85rem; color: rgba(247, 245, 240, 0.7); text-decoration: none; }
.site-footer nav a:hover { color: #EEF2F5; }

.site-footer .theme-toggle {
  color: rgba(247, 245, 240, 0.7);
  padding: 0;
  min-width: auto;
  min-height: auto;
  margin-left: 0.5rem;
}
.site-footer .theme-toggle:hover {
  color: #EEF2F5;
}

/* ── Theme toggle ── */

.theme-toggle {
  display: flex;
  align-items: center;
  justify-content: center;
  min-width: 44px;
  min-height: 44px;
  background: none;
  border: 0;
  cursor: pointer;
  padding: 0.5rem;
  margin-left: 0;
  color: var(--ink-60);
}

.theme-toggle:hover { color: var(--ink); }

[data-theme="dark"] .theme-toggle__moon { display: none; }
:root:not([data-theme="dark"]) .theme-toggle__sun { display: none; }

@media (max-width: 850px) {
  .wordmark { margin-right: auto; }
  .theme-toggle { margin-left: 0; }
  .nav-toggle { order: 2; }
}

[dir="rtl"] .theme-toggle { margin-right: 0; }
@media (max-width: 850px) {
  [dir="rtl"] .wordmark { margin-right: 0; margin-left: auto; }
  [dir="rtl"] .theme-toggle { margin-right: 0; }
}

/* ── Motion ────────────────────────────────────────────────────────────────
   The transitions behind assets/js/motion.js. That script only adds and
   removes these classes; nothing here applies until it does, so a page with
   JavaScript off, or with prefers-reduced-motion set, renders complete and
   fully opaque with no animation at all.

   The whole system is opacity plus one slow scale — the same vocabulary the
   GSAP version used, which is why replacing it cost nothing visually. */

.m-fade {
  opacity: 0;
  transition: opacity 1s ease-out;
}

.m-fade.m-in { opacity: 1; }

/* Hero slides: a slow settle out of a slight zoom, running for the whole hold
   plus crossfade so the movement stays below the threshold of noticing. */
.hero__img.m-zoom {
  transform: scale(1.05);
  transition: opacity 1.8s ease-in-out, transform 8.8s linear;
}

.hero__img.m-in { opacity: 1; }
.hero__img.m-slide-in { transform: scale(1); }

/* The first slide is already opaque and only needs the zoom, so it gets a
   shorter, eased settle rather than the slideshow's linear drift. */
.hero__img:not(.hero__img--alt).m-zoom {
  transition: transform 1.8s cubic-bezier(0.16, 1, 0.3, 1);
}

.hero__img:not(.hero__img--alt).m-in { transform: scale(1); }

@media (prefers-reduced-motion: reduce) {
  .m-fade,
  .hero__img.m-zoom {
    opacity: 1;
    transform: none;
    transition: none;
  }
}

/* ── Utilities ── */

.visually-hidden {
  position: absolute;
  width: 1px;
  height: 1px;
  overflow: hidden;
  clip: rect(0 0 0 0);
  white-space: nowrap;
}

/* Keyboard focus: a visible ring for links, buttons and form controls.

   Written in three steps rather than one :focus-visible rule. A selector list
   is dropped entirely if any selector in it is unrecognised, so on a browser
   without :focus-visible support a single rule leaves keyboard users with no
   focus indicator at all — the one group it exists for. So: give every :focus
   the ring, then take it back from pointer focus only where :focus-visible is
   understood. Old browsers get an always-on ring, which is the right way to
   fail. */
a:focus,
button:focus,
[tabindex]:focus,
input:focus,
textarea:focus,
select:focus {
  outline: 2px solid var(--qing);
  outline-offset: 2px;
  border-radius: 2px;
}

a:focus:not(:focus-visible),
button:focus:not(:focus-visible),
[tabindex]:focus:not(:focus-visible),
input:focus:not(:focus-visible),
textarea:focus:not(:focus-visible),
select:focus:not(:focus-visible) {
  outline: none;
}

a:focus-visible,
button:focus-visible,
[tabindex]:focus-visible,
input:focus-visible,
textarea:focus-visible,
select:focus-visible {
  outline: 2px solid var(--qing);
  outline-offset: 2px;
}

/* Skip to main content — first tab stop, hidden until focused. */
.skip-link {
  position: absolute;
  left: 0.75rem;
  top: -100px;
  z-index: 1000;
  padding: 0.6rem 1rem;
  background: var(--ink);
  color: var(--bg);
  text-decoration: none;
  border-radius: 0;
  transition: top 0.15s ease;
}
.skip-link:focus {
  top: 0.75rem;
}

@media (max-width: 850px) {
  .lang-switch {
    margin-left: 0;
    margin-right: 0;
  }
}

/* ── Print ─────────────────────────────────────────────────────────────────
   Someone printing a supplier's about page is filing it, so the priority is
   that the text survives and the ink lasts: no dark bands, no photography, no
   navigation, and URLs spelled out because a printed link is otherwise a dead
   end. */

@media print {
  :root {
    --ink: #000;
    --ink-60: #333;
    --bg: #fff;
    --mist: #fff;
    --surface: #fff;
    --band: #fff;
    --line: #bbb;
  }

  .site-header,
  .site-footer nav,
  .hero__media,
  .split img,
  .theme-toggle,
  .skip-link,
  .lang-switch { display: none; }

  /* body is a flex column with min-height:100dvh so the footer sits at the
     bottom of a short *screen*. On paper that reserves a viewport's worth of
     empty space in the middle of the document — the first draft printed two
     pages that were mostly blank. Paper does not need a sticky footer. */
  body {
    display: block;
    min-height: 0;
    font-size: 11pt;
    line-height: 1.5;
  }

  main { flex: none; }

  /* The fixed patch that covers the iOS overscroll fringe. */
  body::after { display: none; }

  /* The footer mark is the light-ink variant, because on screen it always sits
     on a dark band. On white paper it is invisible. */
  .site-footer__brand img { display: none; }

  .hero { display: block; }

  /* Scroll reveals never fire for content the reader never scrolled past, so
     printing captured whole sections still sitting at opacity 0 — invisible,
     but still occupying the page. This is why the first print draft came out
     as two mostly-blank sheets. */
  .m-fade,
  .hero__img {
    opacity: 1;
    transform: none;
    transition: none;
  }

  .section { padding: 1.5rem 0; }
  .section--ink { color: #000; border: 0; }
  .hero { min-height: 0; border: 0; }
  .hero__content { padding-inline: 0; }

  /* The multi-column layouts collapse: a printed page is one column. */
  .split,
  .steps,
  .glyphs,
  .facts,
  .section-head,
  .cta-band .container { display: block; }

  /* The strips' column padding and dividers only make sense side by side. On
     paper they stack, so the indent left every item but the first hanging off
     the left margin. */
  .fact, .step, .glyph {
    break-inside: avoid;
    margin-bottom: 1rem;
    padding-inline: 0;
    border-inline-start: 0;
  }

  .facts, .steps, .glyphs { border-top: 1px solid #bbb; }
  .fact + .fact, .step + .step, .glyph + .glyph { border-top: 1px solid #ddd; padding-top: 0.75rem; }

  /* A handful of colours are literal light values rather than tokens, because
     on screen they only ever sit on a dark band. Overriding the token block
     does not reach them, so they print as near-white on white — the CTA's lead
     and the footer's copyright both vanished from the first draft. */
  .section--ink .lead,
  .site-footer p,
  .site-footer p.site-footer__legal,
  .section--ink .fact p { color: #333; }

  .btn {
    border: 1px solid #000;
    background: none;
    color: #000;
    padding: 0.3rem 0.6rem;
  }

  /* A printed mailto: link is a dead end without the address beside it. The
     scheme comes along with attr(href) — CSS cannot strip it — which is a
     little noisy but unambiguous, and that is the trade worth making on paper. */
  a[href^="mailto:"]::after { content: " <" attr(href) ">"; }

  h1, h2, h3 { break-after: avoid; }
  p { orphans: 3; widows: 3; }
}

