/* ============================================================================
   Rayspring Ministry Digital — design system
   ----------------------------------------------------------------------------
   Architecture (this order matters, do not reshuffle):

     1. @font-face          self-hosted, no external request
     2. :root               the LIGHT base — Warm Ivory, the brand default
     3. html[data-mode]     shared dark base, applied by the dark themes
     4. html[data-theme]    per-theme overrides (5 themes)
     5. base / layout / components

   THE RULE: every colour in this file must be a var(--token). A literal hex
   outside the token blocks will look correct in one theme and wrong in the
   other four. There is a checker in scripts/check-tokens.php.

   These are the THEMES OF THIS MARKETING SITE. They are not the website
   templates we sell to churches — those live in the database
   (`ministry_templates`) and are rendered as screenshots, not as CSS.
   ========================================================================== */

/* ── 1. Fonts ────────────────────────────────────────────────────────────── */

@font-face {
  font-family: "Inter";
  font-style: normal;
  font-weight: 100 900;
  font-display: swap;
  src: url("../fonts/inter-latin.woff2") format("woff2");
}

/* The Inter Latin subset claims (blank) glyphs for the Myanmar block, so
   Burmese words inside an English page render as boxes unless this same-family
   face wins for that range. */
@font-face {
  font-family: "Inter";
  font-style: normal;
  font-weight: 100 900;
  font-display: swap;
  unicode-range: U+1000-109F, U+A9E0-A9FF, U+AA60-AA7F;
  src: url("../fonts/noto-myanmar-400.woff2") format("woff2");
}

/* Lora: the editorial serif used only for large headlines. Warm, readable and
   quietly traditional — the "trustworthy, not corporate" signal. */
@font-face {
  font-family: "Lora";
  font-style: normal;
  font-weight: 400 700;
  font-display: swap;
  src: url("../fonts/lora-latin.woff2") format("woff2");
}
@font-face {
  font-family: "Lora";
  font-style: italic;
  font-weight: 400 700;
  font-display: swap;
  src: url("../fonts/lora-latin-italic.woff2") format("woff2");
}

@font-face {
  font-family: "Noto Sans Myanmar";
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: url("../fonts/noto-myanmar-400.woff2") format("woff2");
}
@font-face {
  font-family: "Noto Sans Myanmar";
  font-style: normal;
  font-weight: 500;
  font-display: swap;
  src: url("../fonts/noto-myanmar-500.woff2") format("woff2");
}
@font-face {
  font-family: "Noto Sans Myanmar";
  font-style: normal;
  font-weight: 600;
  font-display: swap;
  src: url("../fonts/noto-myanmar-600.woff2") format("woff2");
}
@font-face {
  font-family: "Noto Sans Myanmar";
  font-style: normal;
  font-weight: 700;
  font-display: swap;
  src: url("../fonts/noto-myanmar-700.woff2") format("woff2");
}

/* ── 2. :root — "Ivory", the light default ───────────────────────────────── */

:root {
  /* ── The brand palette ───────────────────────────────────────────────────
     Named exactly as the 2026 brand brief specifies, so a value can be checked
     against it without translation. These are the authoritative constants; the
     semantic tokens below are built from them, and the four other themes
     re-scope the semantic layer rather than these.

     Two additions the brief does not contain, both measured rather than
     invented:

       --color-gold-text  The brief's --color-gold-dark (#9D7B38) measures
                          3.56:1 on ivory. A token whose job is eyebrows,
                          labels and small links cannot sit below 4.5:1, so
                          small text uses this instead and #9D7B38 stays a
                          decorative fill. Same trap, same fix as before.
       --color-slate-text The brief's --color-slate (#657078) is 4.58:1 on
                          ivory but only 4.16:1 on ivory-deep, which is the
                          alternating band — so it passes on one section and
                          fails on the next. */
  --color-ink: #102129;
  --color-ink-soft: #1a303a;
  --color-ivory: #f7f3ea;
  --color-ivory-deep: #eee8dc;
  --color-gold: #c7a35a;
  --color-gold-dark: #9d7b38;
  --color-gold-text: #7a5a1f;   /* 5.73:1 on ivory, 5.20:1 on ivory-deep */
  --color-stone: #d9d5cc;
  --color-slate: #657078;
  --color-slate-text: #5c666d;  /* 5.30:1 on ivory, 4.81:1 on ivory-deep */
  --color-white: #ffffff;
  --color-danger: #b54747;
  --color-success: #49745b;

  /* The older constant names, kept as aliases so nothing that already reads
     them breaks, and so there is exactly one place each colour is defined. */
  --ivory: var(--color-ivory);
  --ink: var(--color-ink);
  --gold: var(--color-gold);
  --blue: var(--color-slate);
  --warm-white: var(--color-white);
  --soft-gray: var(--color-stone);

  /* Semantic surfaces */
  --bg: var(--ivory);
  --bg-2: #f4f0e7;
  --surface: var(--warm-white);
  --surface-2: #f7f4ee;
  --surface-sunken: #efebe2;
  --surface-ink: #20252b;         /* deep panels that stay dark in light themes */
  --surface-ink-2: #272d34;

  /* Text */
  --text: #20252b;
  --text-strong: #12161a;
  --text-muted: #5d666f;
  --text-subtle: #5f666d;
  --text-on-ink: #f3f0e9;
  --text-on-ink-muted: #b6bcc2;
  --text-on-accent: #ffffff;

  /* Accents */
  /* Bronze rather than bright gold: this token is used for SMALL TEXT
     (eyebrows, ghost links, price labels), and #b3873a measured only
     2.87:1 against the tinted band — well under the 4.5:1 that size needs.
     #7a5a1f clears 5.0:1 on every light surface in every light theme, and
     white text on it as a button clears 6.3:1. The bright gold survives as
     --gold, used for decorative fills where nothing has to be read. */
  --accent: #7a5a1f;
  --accent-strong: #5f4616;
  --accent-soft: rgba(197, 154, 74, 0.16);

  /* ── Spring: the water half of the brand ────────────────────────────────
     Ray's 2026 brief adds 光泉浅蓝 #74A9B7 — the "spring water" blue that
     makes the Sacred Technology direction read as light-through-water rather
     than as another gold-on-ivory ministry site. It is the only genuinely new
     colour in that brief; the ivory, ink and gold it names were already within
     a few points of these tokens.

     It measures **2.34:1 on ivory**, so it lands in exactly the same place as
     the bright gold (2.15:1) and gets exactly the same treatment: --spring is
     a DECORATIVE FILL — ripples, flow lines, node glows, gradient washes —
     and nothing that has to be read is allowed to use it. Small text uses
     --spring-text (#36646F, 5.91:1 on ivory and 5.36:1 on the tinted band,
     so it holds on the alternating sections too).

     On a dark panel the relationship inverts, the same way gold does:
     #74A9B7 is 5.95:1 on --surface-ink, so the dark blocks re-scope
     --spring-text back to the bright value. */
  --spring: #74a9b7;              /* fill only — 2.34:1 on ivory */
  --spring-text: #36646f;         /* 5.91:1 ivory · 5.36:1 ivory-deep */
  --spring-strong: #2a5561;
  --spring-soft: rgba(116, 169, 183, 0.14);
  --spring-line: rgba(54, 100, 111, 0.30);
  --spring-glow: rgba(116, 169, 183, 0.42);
  --accent-line: rgba(160, 122, 48, 0.42);
  --accent-2: #4f6b86;            /* muted blue, darkened for contrast */
  --accent-2-soft: rgba(97, 125, 152, 0.14);

  /* Lines & shadows */
  --line: rgba(32, 37, 43, 0.11);
  --line-strong: rgba(32, 37, 43, 0.2);
  --line-on-ink: rgba(255, 255, 255, 0.14);
  --shadow-sm: 0 2px 8px rgba(32, 37, 43, 0.05), 0 8px 24px rgba(32, 37, 43, 0.05);
  --shadow-md: 0 4px 16px rgba(32, 37, 43, 0.07), 0 20px 48px rgba(32, 37, 43, 0.07);
  --shadow-lg: 0 8px 32px rgba(32, 37, 43, 0.09), 0 36px 80px rgba(32, 37, 43, 0.1);

  /* Status */
  --ok-text: #1f6b46;
  --ok-bg: rgba(31, 107, 70, 0.09);
  --ok-line: rgba(31, 107, 70, 0.28);
  --warn-text: #8a5a12;
  --warn-bg: rgba(181, 126, 30, 0.12);
  --warn-line: rgba(181, 126, 30, 0.32);
  --err-text: #97302a;
  --err-bg: rgba(151, 48, 42, 0.09);
  --err-line: rgba(151, 48, 42, 0.28);
  --info-text: #3c5f80;
  --info-bg: rgba(97, 125, 152, 0.12);

  /* Header */
  --header-bg: rgba(250, 247, 240, 0.86);
  --header-bg-scrolled: rgba(250, 247, 240, 0.96);
  --header-line: rgba(32, 37, 43, 0.08);

  /* Typography */
  --font-body: "Inter", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto,
    "Helvetica Neue", var(--cjk-sans), "Noto Sans Myanmar", sans-serif;
  --font-display: "Lora", "Iowan Old Style", Georgia, var(--cjk-serif), serif;
  --font-mono: ui-monospace, SFMono-Regular, "SF Mono", Menlo, monospace;

  /* The CJK faces are a token of their own so the reader can change them.
     See the `html[data-font]` block below — Ray's complaint was that the
     Chinese "太丑", and he is right that there is no single answer: which CJK
     face looks right is a matter of what is installed and what the reader is
     used to, and a Windows reader and a Mac reader are not looking at the
     same thing even when the CSS is identical. So the site picks a sensible
     default and then gets out of the way. */
  --cjk-sans: "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei UI",
    "Microsoft YaHei", "Source Han Sans SC", "Noto Sans CJK SC", "Noto Sans SC";
  --cjk-serif: "Songti SC", "Source Han Serif SC", "Noto Serif CJK SC",
    "Noto Serif SC", "STSong", SimSun;

  /* Scale — fluid, clamped so nothing explodes on a 4K monitor */
  --step--1: clamp(0.82rem, 0.79rem + 0.14vw, 0.9rem);
  --step-0: clamp(1rem, 0.96rem + 0.2vw, 1.09rem);
  --step-1: clamp(1.18rem, 1.11rem + 0.35vw, 1.4rem);
  --step-2: clamp(1.42rem, 1.29rem + 0.63vw, 1.85rem);
  --step-3: clamp(1.7rem, 1.47rem + 1.1vw, 2.45rem);
  --step-4: clamp(2.05rem, 1.65rem + 1.9vw, 3.25rem);
  --step-5: clamp(2.4rem, 1.75rem + 3vw, 4.1rem);

  /* Shape & motion */
  --radius-xs: 6px;
  --radius-sm: 10px;
  --radius-md: 16px;
  --radius-lg: 24px;
  --radius-pill: 999px;
  --container: 1180px;
  --container-narrow: 780px;
  --gutter: clamp(1.15rem, 4vw, 2.5rem);
  --header-height: 72px;
  /* The ambient wash behind every page. A token because it is three colours,
     and a colour that is not a token looks right in one theme and wrong in the
     other six. Painted by body::before, which is fixed and sits at z-index -1
     so it never intercepts a click. */
  --body-ambient:
    radial-gradient(54% 42% at 82% -8%, rgba(199, 163, 90, 0.13), transparent 60%),
    radial-gradient(42% 36% at 2% 12%, rgba(101, 112, 120, 0.07), transparent 58%),
    radial-gradient(86% 58% at 50% 112%, rgba(238, 232, 220, 0.55), transparent 64%),
    var(--bg);
  /* 44px against the 72px header. The lockup is 3.8:1, so 44 makes it 167px
     wide and the 1200px breakpoint's measured sum (brand 195 + nav 753 +
     actions 121 + gutters 80) still holds. Taller pushes the brand column past
     195 and the desktop nav starts colliding before its own breakpoint. */
  --brand-logo-height: 2.75rem;
  --ease: cubic-bezier(0.22, 1, 0.36, 1);
  --speed: 0.28s;

  color-scheme: light;
}

/* ── 3. Shared dark base ─────────────────────────────────────────────────── */

html[data-mode="dark"] {
  --bg: #15181c;
  --bg-2: #1a1e23;
  --surface: #1d2228;
  --surface-2: #232830;
  --surface-sunken: #171b20;
  --surface-ink: #101317;
  --surface-ink-2: #171b20;

  --text: #e9e5dc;
  --text-strong: #f6f3ec;
  --text-muted: #a3aab2;
  --text-subtle: #868d95;
  --text-on-ink: #e9e5dc;
  --text-on-ink-muted: #a3aab2;
  --text-on-accent: #17120a;

  /* In dark mode the gold can be brighter — it is text ON a dark field, so a
     lighter tone is what passes contrast, the opposite of light mode. */
  --accent: #d7b273;
  --accent-strong: #e6c68f;

  /* Spring inverts the same way. #74a9b7 is 5.95:1 on the dark surface, so the
     value that was fill-only in light mode becomes the readable one here, and
     the dark bronze-blue that carried small text on ivory would be 2.36:1 —
     unreadable. Both tokens swap rather than one being reused. */
  --spring: #8fc0cd;
  --spring-text: #9ecdda;
  --spring-strong: #b6dde7;
  --spring-soft: rgba(143, 192, 205, 0.13);
  --spring-line: rgba(158, 205, 218, 0.30);
  --spring-glow: rgba(143, 192, 205, 0.34);
  --body-ambient:
    radial-gradient(56% 44% at 80% -8%, rgba(215, 178, 115, 0.12), transparent 60%),
    radial-gradient(44% 38% at 3% 10%, rgba(120, 140, 160, 0.06), transparent 58%),
    radial-gradient(88% 60% at 50% 114%, rgba(28, 32, 38, 0.42), transparent 64%),
    var(--bg);
  --accent-soft: rgba(215, 178, 115, 0.15);
  --accent-line: rgba(215, 178, 115, 0.34);
  --accent-2: #8aa6c2;
  --accent-2-soft: rgba(138, 166, 194, 0.15);

  --line: rgba(255, 255, 255, 0.1);
  --line-strong: rgba(255, 255, 255, 0.18);
  --line-on-ink: rgba(255, 255, 255, 0.12);
  --shadow-sm: 0 2px 8px rgba(0, 0, 0, 0.3), 0 8px 24px rgba(0, 0, 0, 0.24);
  --shadow-md: 0 4px 16px rgba(0, 0, 0, 0.36), 0 20px 48px rgba(0, 0, 0, 0.3);
  --shadow-lg: 0 8px 32px rgba(0, 0, 0, 0.42), 0 36px 80px rgba(0, 0, 0, 0.4);

  --ok-text: #6fd3a4;
  --ok-bg: rgba(111, 211, 164, 0.12);
  --ok-line: rgba(111, 211, 164, 0.3);
  --warn-text: #e5bf76;
  --warn-bg: rgba(229, 191, 118, 0.13);
  --warn-line: rgba(229, 191, 118, 0.32);
  --err-text: #f0a29b;
  --err-bg: rgba(240, 162, 155, 0.12);
  --err-line: rgba(240, 162, 155, 0.3);
  --info-text: #9fbcd8;
  --info-bg: rgba(159, 188, 216, 0.12);

  --header-bg: rgba(21, 24, 28, 0.84);
  --header-bg-scrolled: rgba(21, 24, 28, 0.96);
  --header-line: rgba(255, 255, 255, 0.09);

  color-scheme: dark;
}

/* ── 4. Themes ───────────────────────────────────────────────────────────── */

/* Ivory is :root; this block exists so the selector is explicit. */
html[data-theme="ivory"] { }

/* Linen — softer, more paper, less contrast; the quietest of the light set. */
html[data-theme="linen"] {
  --bg: #f6f3ec;
  --bg-2: #efeade;
  --surface: #fdfbf7;
  --surface-2: #f3efe5;
  --surface-sunken: #e9e4d8;
  --line: rgba(52, 44, 32, 0.12);
  --line-strong: rgba(52, 44, 32, 0.2);
  --text: #2c2a25;
  --text-strong: #1b1a16;
  --text-muted: #63605a;
  --accent: #75551d;
  --accent-strong: #5b4215;
  --header-bg: rgba(246, 243, 236, 0.88);
  --header-bg-scrolled: rgba(246, 243, 236, 0.97);
  --body-ambient:
    radial-gradient(54% 42% at 82% -8%, rgba(184, 150, 82, 0.12), transparent 60%),
    radial-gradient(42% 36% at 2% 12%, rgba(120, 108, 86, 0.06), transparent 58%),
    radial-gradient(86% 58% at 50% 112%, rgba(233, 228, 216, 0.6), transparent 64%),
    var(--bg);
}

/* Dayspring — cooler light, blue-forward; reads more "organisation". */
html[data-theme="dayspring"] {
  --bg: #f5f7f9;
  --bg-2: #eaeff4;
  --surface: #ffffff;
  --surface-2: #f2f5f8;
  --surface-sunken: #e6ecf2;
  --surface-ink: #1c2937;
  --surface-ink-2: #23323f;
  --text: #1e2a36;
  --text-strong: #101a24;
  --text-muted: #55636f;
  --accent: #2f5677;
  --accent-strong: #23415a;
  --accent-soft: rgba(56, 98, 135, 0.12);
  --accent-line: rgba(56, 98, 135, 0.32);
  --accent-2: #6d5220;
  --accent-2-soft: rgba(160, 124, 51, 0.14);
  --line: rgba(30, 42, 54, 0.12);
  --line-strong: rgba(30, 42, 54, 0.2);
  --header-bg: rgba(245, 247, 249, 0.88);
  --header-bg-scrolled: rgba(245, 247, 249, 0.97);
  --body-ambient:
    radial-gradient(54% 42% at 82% -8%, rgba(56, 98, 135, 0.09), transparent 60%),
    radial-gradient(42% 36% at 2% 12%, rgba(160, 124, 51, 0.06), transparent 58%),
    radial-gradient(86% 58% at 50% 112%, rgba(220, 230, 240, 0.55), transparent 64%),
    var(--bg);
}

/* Vesper — warm dark; charcoal and brass, the evening version of Ivory. */
html[data-theme="vesper"] {
  --bg: #191713;
  --bg-2: #1f1c17;
  --surface: #23201a;
  --surface-2: #2b2721;
  --surface-sunken: #14120f;
  --surface-ink: #100e0b;
  --surface-ink-2: #17150f;
  --text: #ece5d6;
  --text-strong: #f7f2e6;
  --text-muted: #a9a08e;
  --accent: #dcb672;
  --accent-strong: #ecca8e;
  --line: rgba(236, 229, 214, 0.11);
  --line-strong: rgba(236, 229, 214, 0.2);
  --header-bg: rgba(25, 23, 19, 0.85);
  --header-bg-scrolled: rgba(25, 23, 19, 0.97);
  --body-ambient:
    radial-gradient(58% 46% at 78% -6%, rgba(220, 182, 114, 0.13), transparent 60%),
    radial-gradient(46% 40% at 2% 8%, rgba(150, 120, 70, 0.07), transparent 58%),
    radial-gradient(90% 62% at 50% 116%, rgba(42, 34, 22, 0.36), transparent 64%),
    var(--bg);
}

/* Nightwatch — cool deep dark; the highest-contrast option. */
html[data-theme="nightwatch"] {
  --bg: #10151b;
  --bg-2: #141b23;
  --surface: #18202a;
  --surface-2: #1e2833;
  --surface-sunken: #0c1116;
  --surface-ink: #090d12;
  --surface-ink-2: #0f151c;
  --text: #e3e9ef;
  --text-strong: #f4f7fa;
  --text-muted: #97a3b0;
  --accent: #86aed3;
  --accent-strong: #a4c4e2;
  --accent-soft: rgba(134, 174, 211, 0.15);
  --accent-line: rgba(134, 174, 211, 0.34);
  --accent-2: #d5b477;
  --accent-2-soft: rgba(213, 180, 119, 0.15);
  --line: rgba(227, 233, 239, 0.1);
  --line-strong: rgba(227, 233, 239, 0.19);
  --header-bg: rgba(16, 21, 27, 0.85);
  --header-bg-scrolled: rgba(16, 21, 27, 0.97);
  --body-ambient:
    radial-gradient(56% 44% at 80% -8%, rgba(134, 174, 211, 0.12), transparent 60%),
    radial-gradient(44% 38% at 3% 10%, rgba(213, 180, 119, 0.07), transparent 58%),
    radial-gradient(88% 60% at 50% 114%, rgba(20, 34, 50, 0.4), transparent 64%),
    var(--bg);
}

/* Porcelain — the coolest light theme, cobalt rather than bronze. Ported from
   Rayspring Tech, where it is the one that reads least like a church and most
   like a supplier; some ministries are choosing a vendor, not a chapel. */
html[data-theme="porcelain"] {
  --bg: #fbfcfe;
  --bg-2: #f1f5fa;
  --surface: #ffffff;
  --surface-2: #f5f8fc;
  --surface-sunken: #e9eff7;
  --surface-ink: #16202c;
  --surface-ink-2: #1e2a38;
  --text: #16202c;
  --text-strong: #0b131c;
  --text-muted: #4e5a68;
  /* Measured, not picked. #6b7684 looked right and was 4.50:1 on --bg and
     4.22:1 on --surface-sunken, so the pricing table's column headings failed
     while the same colour passed elsewhere on the page — the exact trap
     --color-slate-text exists for in the light base. 5.05:1 on the sunken
     surface, 5.69:1 on the page background. */
  --text-subtle: #5a6675;
  /* 6.76:1 on this background. Cobalt goes muddy if it is lightened to look
     "friendlier", and then it fails on the alternating band. */
  --accent: #254ecc;
  --accent-strong: #1c3ea6;
  --accent-soft: rgba(37, 78, 204, 0.11);
  --accent-line: rgba(37, 78, 204, 0.3);
  --accent-2: #7a5a1f;
  --accent-2-soft: rgba(160, 124, 51, 0.13);
  --line: rgba(22, 32, 44, 0.11);
  --line-strong: rgba(22, 32, 44, 0.19);
  --header-bg: rgba(251, 252, 254, 0.88);
  --header-bg-scrolled: rgba(251, 252, 254, 0.97);
  --body-ambient:
    radial-gradient(54% 42% at 82% -8%, rgba(37, 78, 204, 0.07), transparent 60%),
    radial-gradient(42% 36% at 2% 12%, rgba(37, 78, 204, 0.05), transparent 58%),
    radial-gradient(86% 58% at 50% 112%, rgba(210, 222, 238, 0.5), transparent 64%),
    var(--bg);
}

/* Gilded — the darkest theme, and the only one where the gold carries the
   whole page rather than sitting beside a blue. */
html[data-theme="gilded"] {
  --bg: #0b0a07;
  --bg-2: #121009;
  --surface: #17140d;
  --surface-2: #1f1b12;
  --surface-sunken: #080705;
  --surface-ink: #060504;
  --surface-ink-2: #0d0b07;
  --text: #f0e7d2;
  --text-strong: #fbf5e6;
  --text-muted: #b0a487;
  --text-subtle: #8d846c;
  --accent: #e2c07d;
  --accent-strong: #f0d59c;
  --accent-soft: rgba(226, 192, 125, 0.14);
  --accent-line: rgba(226, 192, 125, 0.32);
  --accent-2: #d0b389;
  --accent-2-soft: rgba(208, 179, 137, 0.13);
  --line: rgba(240, 231, 210, 0.12);
  --line-strong: rgba(240, 231, 210, 0.21);
  --header-bg: rgba(11, 10, 7, 0.85);
  --header-bg-scrolled: rgba(11, 10, 7, 0.97);
  --body-ambient:
    radial-gradient(58% 46% at 78% -6%, rgba(226, 192, 125, 0.13), transparent 60%),
    radial-gradient(46% 40% at 2% 8%, rgba(160, 128, 62, 0.07), transparent 58%),
    radial-gradient(90% 62% at 50% 116%, rgba(46, 36, 16, 0.34), transparent 64%),
    var(--bg);
}

/* Cosmos — the night sky. The darkest theme by a long way, and the only one
   with a moving background: assets/js/motion.js paints a starfield and sends a
   meteor across it every few seconds.

   The base is a blue-black rather than a true #000. Pure black makes the stars
   look like dead pixels and makes every panel edge a hard line; a little blue
   left in reads as depth, which is the whole point of the theme.

   --body-ambient is the nebula the stars sit in. It is drawn under the canvas,
   so between them there is atmosphere behind the stars rather than a flat
   field with dots on it. */
html[data-theme="cosmos"] {
  --bg: #05060b;
  --bg-2: #090b13;
  --surface: #0c0f1a;
  --surface-2: #121623;
  --surface-sunken: #030409;
  --surface-ink: #020308;
  --surface-ink-2: #070910;
  --text: #e4e9f4;
  --text-strong: #f5f8ff;
  --text-muted: #a2acc4;
  --text-subtle: #8791a8;
  --text-on-ink: #e4e9f4;
  --text-on-ink-muted: #a2acc4;
  /* Starlight rather than gold. The other dark themes are lit by a lamp; this
     one is lit by a sky. */
  --accent: #9db8e8;
  --accent-strong: #bcd0f4;
  --accent-soft: rgba(157, 184, 232, 0.15);
  --accent-line: rgba(157, 184, 232, 0.34);
  --accent-2: #d8c48f;
  --accent-2-soft: rgba(216, 196, 143, 0.14);
  --line: rgba(228, 233, 244, 0.1);
  --line-strong: rgba(228, 233, 244, 0.2);
  --header-bg: rgba(5, 6, 11, 0.82);
  --header-bg-scrolled: rgba(5, 6, 11, 0.96);
  --body-ambient:
    radial-gradient(52% 40% at 72% -4%, rgba(94, 122, 196, 0.16), transparent 62%),
    radial-gradient(44% 38% at 8% 14%, rgba(150, 96, 190, 0.1), transparent 60%),
    radial-gradient(70% 50% at 30% 96%, rgba(38, 60, 122, 0.16), transparent 66%),
    radial-gradient(90% 64% at 50% 118%, rgba(2, 3, 8, 0.6), transparent 62%),
    var(--bg);
}

/*
   Let the sky through.

   .hero paints an opaque var(--bg), and so do the alternating bands — which
   means the fixed starfield canvas behind the page was being covered by the
   very first screenful, the one place it most needed to be seen. The stars
   were there the whole time and nobody could see them.

   Only for this theme: everywhere else those backgrounds are doing their job.
   The hero's own radial warmth (.hero::before) stays, so the sky is lit rather
   than bare.
*/
html[data-theme="cosmos"] .hero { background: transparent; }
html[data-theme="cosmos"] .section--muted { background: rgba(18, 22, 35, 0.55); }
html[data-theme="cosmos"] .section--sunken { background: rgba(3, 4, 9, 0.6); }

/* ── 5. Base ─────────────────────────────────────────────────────────────── */

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

html {
  -webkit-text-size-adjust: 100%;
  scroll-behavior: smooth;
  scroll-padding-top: calc(var(--header-height) + 1rem);
}

@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }
  *, *::before, *::after {
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.01ms !important;
  }
}

body {
  margin: 0;
  background: var(--bg);
  color: var(--text);
  font-family: var(--font-body);
  font-size: var(--step-0);
  line-height: 1.65;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  /* A long word in a Burmese heading must wrap rather than push the layout
     sideways on a 360px phone. */
  overflow-wrap: break-word;
}

/*
   The ambient wash, on every page.

   A fixed layer rather than a background on <body>: a background would scroll
   with the document and the gradients would streak past. Fixed, it sits still
   while the page moves over it, which is what makes it read as light in the
   room rather than as a pattern.

   z-index -1 and pointer-events:none — it must never take a click, and it must
   never paint over content. Both matter: an earlier full-bleed layer on this
   project's hero swallowed the call-to-action.
*/
body::before {
  content: "";
  position: fixed;
  inset: 0;
  z-index: -1;
  pointer-events: none;
  background: var(--body-ambient);
}

/* Printing a gradient wastes a reader's toner and prints grey over the text. */
@media print {
  body::before { display: none; }
}

/* Burmese is a tall, dense script: it needs looser leading and a slightly
   smaller display size or headings collide with their own diacritics. */
html[lang="my"] { --track-label: 0; }
html[lang="my"] body { line-height: 1.95; }
html[lang="my"] h1,
html[lang="my"] h2,
html[lang="my"] h3 { line-height: 1.6; font-size: 0.92em; }
html[lang="my"] .button { line-height: 1.45; }

/*
   Chinese needs three things undone, not a different font.

   1. Wide tracking. `letter-spacing: 0.1em` on a Latin label is elegant; on
      Chinese it inserts a gap after EVERY character, so 常见问题 is set as
      常 见 问 题. That single rule is most of what made the Chinese pages look
      wrong. Every label above routes its value through --track-label, keeping
      its own Latin value as the fallback, so zeroing the token here changes
      nothing in English.

   2. Heading leading. h1–h6 are set at 1.18, which suits a Latin face with
      ascenders and descenders to breathe into. CJK glyphs fill their em box
      edge to edge and collide at that figure.

   3. The serif. Already handled below — Lora has no CJK glyphs, so a Chinese
      heading in it falls through to whatever system serif exists and looks
      accidental.

   No webfont. The obvious move is Noto Sans SC from Google, and it is what the
   sister site does — but a CJK face is megabytes even subsetted, this site's
   whole design target is a phone on a slow connection, and loading it from
   Google would send every Chinese reader's IP to Google from a site whose
   privacy page says it does not do that. PingFang SC on Apple, Microsoft YaHei
   on Windows and Noto Sans CJK on Android are already installed and already
   good.
*/
html[lang="zh-CN"] { --track-label: 0; }
html[lang="zh-CN"] body { line-height: 1.85; }

h1, h2, h3, h4, h5, h6 {
  margin: 0 0 0.6em;
  font-family: var(--font-display);
  font-weight: 600;
  line-height: 1.18;
  letter-spacing: -0.012em;
  color: var(--text-strong);
  text-wrap: balance;
}

/* CJK and Burmese have no need for a Latin serif — the fallback would be a
   random system serif and look accidental. */
html[lang="zh-CN"] h1, html[lang="zh-CN"] h2, html[lang="zh-CN"] h3,
html[lang="my"] h1, html[lang="my"] h2, html[lang="my"] h3 {
  font-family: var(--font-body);
  font-weight: 700;
  letter-spacing: 0;
  /* 1.18 is Latin leading. CJK and Burmese both fill the em box and collide. */
  line-height: 1.35;
}
html[lang="my"] h1, html[lang="my"] h2, html[lang="my"] h3 { line-height: 1.6; }

h1 { font-size: var(--step-5); }
h2 { font-size: var(--step-4); }
h3 { font-size: var(--step-2); }
h4 { font-size: var(--step-1); }

p { margin: 0 0 1.1em; }
p:last-child { margin-bottom: 0; }

a {
  color: var(--accent);
  text-decoration-thickness: 1px;
  text-underline-offset: 0.18em;
  transition: color var(--speed) var(--ease);
}
a:hover { color: var(--accent-strong); }

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

hr {
  border: 0;
  border-top: 1px solid var(--line);
  margin: 2.5rem 0;
}

::selection { background: var(--accent-soft); color: var(--text-strong); }

/* Focus: visible on every interactive element, in every theme. Removing this
   is how a site becomes unusable by keyboard. */
:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 3px;
  border-radius: var(--radius-xs);
}

.skip-link {
  position: absolute;
  left: -9999px;
  top: 0;
  z-index: 200;
  padding: 0.85rem 1.3rem;
  background: var(--accent);
  color: var(--text-on-accent);
  border-radius: 0 0 var(--radius-sm) 0;
  font-weight: 600;
}
.skip-link:focus { left: 0; }

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

/* ── Layout ──────────────────────────────────────────────────────────────── */

.container {
  width: 100%;
  max-width: var(--container);
  margin-inline: auto;
  padding-inline: var(--gutter);
}
.container--narrow { max-width: var(--container-narrow); }

.section {
  padding-block: clamp(3.25rem, 7vw, 6rem);
  position: relative;
}
.section--tight { padding-block: clamp(2.25rem, 4.5vw, 3.75rem); }
.section--muted { background: var(--bg-2); }
.section--sunken { background: var(--surface-sunken); }

/* Ink sections stay dark in every theme, so their contents need the inverted
   text tokens. Scoping the tokens here means the components inside do not
   each need a modifier class. */
.section--ink {
  background: var(--surface-ink);
  --text: var(--text-on-ink);
  --text-strong: #fdfbf6;
  --text-muted: var(--text-on-ink-muted);
  --surface: var(--surface-ink-2);
  --surface-2: var(--surface-ink-2);
  --line: var(--line-on-ink);
  --line-strong: rgba(255, 255, 255, 0.22);
  --accent: #dcb672;
  --accent-strong: #ecca8e;
  /* Inside a dark panel the accent is the BRIGHT gold, so text sitting on it
     must go back to dark ink — white on #dcb672 is only 1.91:1. */
  --text-on-accent: #1c1509;
  color: var(--text-on-ink);
}
.section--ink h1, .section--ink h2, .section--ink h3, .section--ink h4 { color: #fdfbf6; }

.section__head { max-width: 46rem; margin-bottom: clamp(1.75rem, 3.5vw, 3rem); }
.section__head--center { margin-inline: auto; text-align: center; }

.eyebrow {
  display: inline-flex;
  align-items: center;
  gap: 0.5rem;
  margin: 0 0 0.9rem;
  font-family: var(--font-body);
  font-size: var(--step--1);
  font-weight: 600;
  letter-spacing: var(--track-label, 0.1em);
  text-transform: uppercase;
  color: var(--accent);
}
.eyebrow::before {
  content: "";
  width: 1.6rem;
  height: 1px;
  background: currentColor;
  opacity: 0.65;
}
.section__head--center .eyebrow::before { display: none; }

.lede {
  font-size: var(--step-1);
  color: var(--text-muted);
  max-width: 42rem;
  line-height: 1.6;
}
.section__head--center .lede { margin-inline: auto; }

.grid { display: grid; gap: clamp(1rem, 2.2vw, 1.75rem); }
.grid--2 { grid-template-columns: repeat(auto-fit, minmax(min(100%, 21rem), 1fr)); }
.grid--3 { grid-template-columns: repeat(auto-fit, minmax(min(100%, 17.5rem), 1fr)); }
.grid--4 { grid-template-columns: repeat(auto-fit, minmax(min(100%, 14rem), 1fr)); }

/* The four kinds of ministry website are four, and they are meant to sit two by
   two — which is what the grid--2 on them says. auto-fit does not read class
   names: at 21rem a track and a 1200px container it fits three, so the fourth
   card dropped to a row of its own with two thirds of the row empty beside it.
   That is the auto-fit-with-unequal-content class already recorded for the
   mobile footer, in a place where it reads as a missing card rather than a gap.

   Capped here rather than on .grid--2 itself, because that utility is shared by
   twelve layouts and one of them (/work) holds three items — capping the
   utility would have moved the orphan rather than removed it. */
@media (min-width: 60rem) {
  .service-grid { grid-template-columns: repeat(2, 1fr); }
}

.stack > * + * { margin-top: 1rem; }
.cluster { display: flex; flex-wrap: wrap; gap: 0.75rem; align-items: center; }

/* ── Buttons ─────────────────────────────────────────────────────────────── */

.button {
  --btn-bg: var(--accent);
  --btn-fg: var(--text-on-accent);
  --btn-line: transparent;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 0.55rem;
  padding: 0.78rem 1.5rem;
  border: 1px solid var(--btn-line);
  border-radius: var(--radius-pill);
  background: var(--btn-bg);
  color: var(--btn-fg);
  font-family: var(--font-body);
  font-size: var(--step-0);
  font-weight: 600;
  line-height: 1.3;
  text-decoration: none;
  cursor: pointer;
  transition: transform var(--speed) var(--ease), background var(--speed) var(--ease),
    border-color var(--speed) var(--ease), color var(--speed) var(--ease),
    box-shadow var(--speed) var(--ease);
  /* 44px minimum touch target — a phone is the primary device for most of
     the people this site is for. */
  min-height: 44px;
}
.button:hover:not(:disabled) {
  background: var(--accent-strong);
  color: var(--btn-fg);
  transform: translateY(-1px);
  box-shadow: var(--shadow-sm);
}
.button:active:not(:disabled) { transform: translateY(0); }
.button:disabled,
.button[aria-disabled="true"] {
  opacity: 0.5;
  cursor: not-allowed;
  transform: none;
  box-shadow: none;
}

.button--secondary {
  --btn-bg: transparent;
  --btn-fg: var(--text-strong);
  --btn-line: var(--line-strong);
}
.button--secondary:hover:not(:disabled) {
  background: var(--accent-soft);
  border-color: var(--accent-line);
  color: var(--text-strong);
}

.button--ghost {
  --btn-bg: transparent;
  --btn-fg: var(--accent);
  --btn-line: transparent;
  padding-inline: 0.4rem;
}
.button--ghost:hover:not(:disabled) {
  background: transparent;
  color: var(--accent-strong);
  transform: none;
  box-shadow: none;
  text-decoration: underline;
}

.button--sm { padding: 0.55rem 1.1rem; font-size: var(--step--1); min-height: 38px; }

.button--lg { padding: 0.95rem 1.9rem; font-size: var(--step-1); }
.button--block { width: 100%; }

/* ── Cards ───────────────────────────────────────────────────────────────── */

.card {
  display: flex;
  flex-direction: column;
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: var(--radius-md);
  padding: clamp(1.3rem, 2.4vw, 1.85rem);
  transition: border-color var(--speed) var(--ease), transform var(--speed) var(--ease),
    box-shadow var(--speed) var(--ease);
}
.card--link:hover,
.card--link:focus-within {
  border-color: var(--accent-line);
  transform: translateY(-2px);
  box-shadow: var(--shadow-md);
}
.card__icon {
  width: 2.75rem;
  height: 2.75rem;
  display: grid;
  place-items: center;
  margin-bottom: 1rem;
  border-radius: var(--radius-sm);
  background: var(--accent-soft);
  color: var(--accent);
}
.card__icon svg { width: 1.4rem; height: 1.4rem; }
.card__title { margin: 0 0 0.5rem; font-size: var(--step-1); }
.card__text { margin: 0; color: var(--text-muted); font-size: var(--step-0); }
.card__foot { margin-top: auto; padding-top: 1.1rem; }

.card__media {
  margin: calc(-1 * clamp(1.3rem, 2.4vw, 1.85rem));
  margin-bottom: 1.2rem;
  border-radius: var(--radius-md) var(--radius-md) 0 0;
  overflow: hidden;
  background: var(--surface-sunken);
  aspect-ratio: 16 / 10;
}
.card__media img { width: 100%; height: 100%; object-fit: cover; }

/* A card whose whole surface is clickable: the pseudo-element covers the card
   so the entire area is a target, without nesting interactive elements. */
.card__stretch::after {
  content: "";
  position: absolute;
  inset: 0;
  border-radius: inherit;
}
.card--link { position: relative; }

/* ── Header ──────────────────────────────────────────────────────────────── */

.site-header {
  position: sticky;
  top: 0;
  z-index: 100;
  background: var(--header-bg);
  border-bottom: 1px solid transparent;
  backdrop-filter: saturate(180%) blur(14px);
  -webkit-backdrop-filter: saturate(180%) blur(14px);
  transition: background var(--speed) var(--ease), border-color var(--speed) var(--ease),
    box-shadow var(--speed) var(--ease);
}
.site-header.is-scrolled {
  background: var(--header-bg-scrolled);
  border-bottom-color: var(--header-line);
  box-shadow: var(--shadow-sm);
}
.site-header__inner {
  display: flex;
  align-items: center;
  gap: 1rem;
  min-height: var(--header-height);
}

.brand {
  display: inline-flex;
  align-items: center;
  gap: 0.65rem;
  text-decoration: none;
  color: var(--text-strong);
  font-family: var(--font-display);
  font-weight: 600;
  font-size: 1.05rem;
  line-height: 1.15;
  letter-spacing: -0.01em;
  flex-shrink: 0;
}
.brand:hover { color: var(--text-strong); }

/* ── The logo ──────────────────────────────────────────────────────────────
   One file, exactly as it was drawn. It is navy and gold on transparency, made
   for a light background, and on the vesper background the wordmark measures
   1.06:1 — invisible. An earlier version of this shipped a computed ivory
   recolouring for the dark themes; it was readable, and it was not the logo, so
   the artwork is never touched and a dark surface gets a light plate under it.

   `width: auto` with a fixed height, and never the reverse: the artwork is
   3.8:1 and squashing it into a box is the one thing that must not happen to a
   logo. --brand-logo-h is set inline by the partial, per placement.
   ------------------------------------------------------------------------- */
.brand-logo { display: inline-flex; align-items: center; }
.brand-logo__art {
  display: block;
  height: var(--brand-logo-h, 2.5rem);
  width: auto;
}

/*
   The plate. Ivory, because that is the paper the logo was drawn on — the same
   reason the favicon tile is ivory rather than ink. Padding in em of the logo's
   own height, so one rule serves the 44px header and the 46px footer.

   The numbers are measured against the header, not chosen for looks. A plate
   grows the brand column, and the first attempt (0.34em/0.5em) took it to 211px
   against a budget of 195 — so on vesper and nightwatch the language and theme
   buttons were squeezed from 122px to 105px and the globe icon visibly shrank.
   Picking a theme must not move the rest of the header. At 0.14em/0.18em the
   plate is 183x56, the artwork stays its full size, and the controls do not
   move between themes.
*/
.brand-logo--plate {
  padding: 0.14em 0.18em;
  border-radius: var(--radius-sm);
  background: var(--color-ivory);
  font-size: var(--brand-logo-h, 2.5rem);   /* so the em padding scales with it */
}
.brand-logo--plate .brand-logo__art { height: 1em; }

/* 'auto': plated only where the theme is actually dark. */
.brand-logo--plate-auto { padding: 0; background: none; }
html[data-mode="dark"] .brand-logo--plate-auto {
  padding: 0.14em 0.18em;
  background: var(--color-ivory);
}


.primary-nav { margin-left: auto; }
/* A button in a horizontal bar must never wrap: two lines of label make the pill
   57px tall against a 39px neighbour and the whole row looks mis-built. */
.primary-nav__cta .button { white-space: nowrap; }
.primary-nav__list {
  display: flex;
  align-items: center;
  gap: 0.15rem;
  list-style: none;
  margin: 0;
  padding: 0;
}
.primary-nav__link {
  display: block;
  padding: 0.5rem 0.78rem;
  border-radius: var(--radius-sm);
  color: var(--text-muted);
  font-size: 0.94rem;
  font-weight: 500;
  text-decoration: none;
  white-space: nowrap;
  transition: color var(--speed) var(--ease), background var(--speed) var(--ease);
}
.primary-nav__link:hover { color: var(--text-strong); background: var(--accent-soft); }
.primary-nav__link.is-active { color: var(--accent); font-weight: 600; }

/*
   margin-left: auto, not just on .primary-nav. Below 1200px the nav is taken
   out of flow, and an out-of-flow element's auto margin pushes nothing — so the
   controls sat immediately after the logo with up to 135px of dead space to
   their right, which is why the phone header looked unbalanced.
*/
.header-actions { display: flex; align-items: center; gap: 0.4rem; margin-left: auto; }

/* Dropdowns (language + theme + share) */
.dropdown { position: relative; }

/* ── Share ─────────────────────────────────────────────────────────────────
   A <details>, so the menu opens with JavaScript off. Everything below just
   makes it look like the other two header dropdowns instead of like a browser
   disclosure triangle.
   ------------------------------------------------------------------------ */
.share > summary { list-style: none; }
.share > summary::-webkit-details-marker { display: none; }
.share__btn { padding-inline: 0.55rem; }
.share__btn svg { width: 17px; height: 17px; }
.share[open] > .share__btn { border-color: var(--line-strong); color: var(--text-strong); }
.share__menu { min-width: 12.5rem; }
/* The copy button confirms in place; without the fixed height the menu jumps
   as "Copy link" becomes "Link copied". */
/* 44px, not 2.6rem.

   2.6rem is 41.6px, which rendered as 43 with the border — one pixel under the
   floor, on six items, in a menu opened by a thumb. visual-check's tap-target
   rule never saw it because the share menu is closed when the page loads and
   it measures what is on screen; it took opening the menu first.

   In px rather than rem deliberately: the floor is a physical measurement of a
   fingertip, and it must not shrink when a reader turns their browser font
   size down. Everything else here stays in rem. */
.share__menu .dropdown__item { min-height: 44px; }
.share__menu .is-done { color: var(--accent); font-weight: 600; }

/* Every row carries its mark. The slot is a fixed 18px whether or not anything
   draws in it, so the labels line up down one edge — and so a mark that ever
   goes missing leaves a gap rather than shunting the whole menu left, which is
   the difference between a visible fault and an invisible one.

   No colour of its own. currentColor means each mark is exactly as readable as
   the word beside it, in all seven themes, with nothing extra to keep in step;
   the brands' own greens and blues would each need a literal hex, would be
   wrong on a dark panel, and check-tokens.php would fail the run for them. */
.share__icon {
  flex: 0 0 18px;
  width: 18px;
  height: 18px;
  color: inherit;
}
.share__icon svg { display: block; width: 100%; height: 100%; }
.share__label { flex: 1 1 auto; }

/* The copy button holds both marks and shows one. Driven by the `is-done`
   class site.js already sets, so there is no second place to keep in step. */
.share__icon--done { display: none; }
.dropdown__item.is-done .share__icon--idle { display: none; }
.dropdown__item.is-done .share__icon--done { display: block; }

/* ── Language flags ────────────────────────────────────────────────────────
   A flag is a picture of a country, so it is never left to carry the meaning
   on its own: the button keeps the language code beside it and every menu row
   keeps the language's own name. The SVG is aria-hidden for the same reason —
   the accessible name comes from the text, not from a flag a screen reader
   would have to guess at.

   The rounded corner and hairline stop a flag with a white edge (the United
   States, Myanmar's star) from bleeding into a light background.
   ------------------------------------------------------------------------ */
.flag {
  display: block;
  width: 20px;
  height: 14px;
  flex-shrink: 0;
  border-radius: 2px;
  box-shadow: 0 0 0 1px var(--line-strong);
}
.dropdown__btn--flag { gap: 0.4rem; }
.dropdown__menu .flag { margin-right: 0.1rem; }
.dropdown__item { display: flex; align-items: center; gap: 0.5rem; }
.dropdown__btn {
  display: inline-flex;
  align-items: center;
  gap: 0.35rem;
  min-height: 38px;
  padding: 0.4rem 0.6rem;
  border: 1px solid var(--line);
  border-radius: var(--radius-pill);
  background: transparent;
  color: var(--text-muted);
  font: inherit;
  font-size: 0.86rem;
  cursor: pointer;
  transition: border-color var(--speed) var(--ease), color var(--speed) var(--ease);
}
.dropdown__btn:hover { border-color: var(--line-strong); color: var(--text-strong); }
.dropdown__menu {
  position: absolute;
  right: 0;
  top: calc(100% + 0.45rem);
  z-index: 110;
  min-width: 11.5rem;
  padding: 0.35rem;
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: var(--radius-sm);
  box-shadow: var(--shadow-md);
}
.dropdown__menu[hidden] { display: none; }
.dropdown__item {
  display: flex;
  align-items: center;
  gap: 0.55rem;
  width: 100%;
  padding: 0.52rem 0.7rem;
  border: 0;
  border-radius: var(--radius-xs);
  background: transparent;
  color: var(--text);
  font: inherit;
  font-size: 0.9rem;
  text-align: left;
  text-decoration: none;
  cursor: pointer;
}
.dropdown__item:hover { background: var(--accent-soft); color: var(--text-strong); }
.dropdown__item[aria-current="true"] { color: var(--accent); font-weight: 600; }
.theme-swatch {
  width: 1rem;
  height: 1rem;
  flex-shrink: 0;
  border-radius: 50%;
  border: 1px solid var(--line-strong);
}

/* The theme button used to show the ACTIVE theme's colour. In Warm Ivory that is
   #faf7f0 on an ivory header — a circle the same colour as the thing behind it,
   which is an invisible control. It shows a half-filled circle now: the standard
   "appearance" mark, legible in all five themes. The real colour swatches stay
   in the dropdown, where each one sits on a surface and is worth seeing. */
.theme-dropdown__icon {
  width: 1.05rem;
  height: 1.05rem;
  flex-shrink: 0;
  border-radius: 50%;
  border: 1.5px solid currentColor;
  background: linear-gradient(90deg, currentColor 0 50%, transparent 50% 100%);
}

.nav-toggle {
  display: none;
  align-items: center;
  justify-content: center;
  width: 44px;
  height: 44px;
  padding: 0;
  border: 1px solid var(--line);
  border-radius: var(--radius-sm);
  background: transparent;
  color: var(--text-strong);
  cursor: pointer;
}

/* Below ~400px the header ran out of room and pushed the page sideways.
   The controls must keep their touch size, so the brand is what gives way. */
@media (max-width: 430px) {
  .site-header__inner { gap: 0.5rem; }
  .brand { min-width: 0; flex: 0 1 auto; gap: 0.45rem; }
  /*
     The logo used to be a 34px mark plus text that could be ellipsised. It is
     one picture now, and a picture cannot be truncated without becoming
     "Rayspri" — so it shrinks instead. 2.1rem keeps the lockup at 128px, which
     leaves the theme and language controls their full 44px touch targets on a
     320px screen.
  */
  :root { --brand-logo-height: 2.1rem; }
  .header-actions { flex-shrink: 0; gap: 0.25rem; }
  .dropdown__btn { padding-inline: 0.45rem; }
  /* The chevron is decoration; the half-circle mark carries the meaning. */
  .theme-dropdown__chevron { display: none; }

  /*
     Four controls now have to fit beside the logo: language, theme, share and
     the menu. At 360px that is 323px of content for a 128px logo and the rest,
     so each control is squared off at 40 — the floor visual-check.py enforces,
     and still above the 24px WCAG 2.5.8 minimum.

     The globe goes and the language code stays. Of the two the code is the one
     that carries information: it tells you which language you are already in,
     which is the question someone opening that menu is actually asking.
  */
  .dropdown__btn { min-height: 40px; min-width: 40px; justify-content: center; }
  /* The flag identifies the language faster than the code does, so on a narrow
     header it is the code that goes. This used to hide the button's svg, which
     was the globe; now the svg IS the flag and hiding it would leave a button
     with nothing on it. */
  .dropdown__btn--flag .dropdown__code { display: none; }
  .nav-toggle { width: 40px; height: 40px; }
  .share__btn { padding-inline: 0.5rem; }
}

/* Below 360px — old small Androids and the first iPhone SE — the logo gives up
   the last of its width rather than the controls giving up their touch size. */
@media (max-width: 359px) {
  :root { --brand-logo-height: 1.7rem; }
}

/* 1200px, measured, not guessed: brand 195 + nav 753 + actions 121 + gutters 80
   needs about 1197px. At the old 1000px the desktop nav appeared 200px before it
   fitted, so between those widths the CTA was squeezed to 90px and its label
   wrapped onto two lines — which is what made the header look broken. */
@media (max-width: 1200px) {
  .nav-toggle { display: inline-flex; }
  /*
     Absolute, not fixed, and this is not a style preference.

     .site-header carries a backdrop-filter, and any non-none filter, transform
     or backdrop-filter makes that element the containing block for every
     position:fixed descendant. So `inset: var(--header-height) 0 0` was being
     measured against the 73px header instead of the screen: the panel opened
     72px tall with its eight items running 583px past the bottom of it, and a
     phone showed one link, a rule, and the hero bleeding through. It looked
     like the menu was refusing to open.

     Positioning against the header deliberately is the fix. top:100% is the
     header's own bottom edge, so the panel cannot drift out of step with
     --header-height, and the height is taken from the viewport instead.

     dvh, not vh: on a phone vh is the tallest the viewport ever gets, so with
     the browser's own chrome showing, a 100vh panel puts its last link under
     the address bar. The vh line is the fallback and must stay first.
  */
  .primary-nav {
    position: absolute;
    top: 100%;
    left: 0;
    right: 0;
    height: calc(100vh - var(--header-height));
    height: calc(100dvh - var(--header-height));
    z-index: 99;
    margin: 0;
    padding: 1.5rem var(--gutter) 3rem;
    background: var(--bg);
    overflow-y: auto;
    overscroll-behavior: contain;
    transform: translateY(-8px);
    opacity: 0;
    pointer-events: none;
    transition: opacity var(--speed) var(--ease), transform var(--speed) var(--ease);
  }
  .primary-nav.is-open { opacity: 1; transform: none; pointer-events: auto; }
  .primary-nav__list { flex-direction: column; align-items: stretch; gap: 0.15rem; }
  .primary-nav__link {
    padding: 0.9rem 0.75rem;
    font-size: 1.05rem;
    border-bottom: 1px solid var(--line);
    border-radius: 0;
  }
  .primary-nav__cta { margin-top: 1.5rem; }
  .primary-nav__cta .button { width: 100%; }
  body.nav-open { overflow: hidden; }
}

/* ── Hero ────────────────────────────────────────────────────────────────── */

.hero {
  position: relative;
  padding-block: clamp(3rem, 8vw, 6.5rem);
  background: var(--bg);
  overflow: hidden;
}
.hero::before {
  content: "";
  position: absolute;
  /* Wider than the hero so the pan below never drags an edge into view. */
  inset: -8% -5%;
  /* A single soft radial warmth rather than a heavy gradient — enough to lift
     the page off flat white without looking like a 2012 template. */
  background:
    radial-gradient(60rem 32rem at 78% -12%, var(--accent-soft), transparent 62%),
    radial-gradient(46rem 26rem at 8% 104%, var(--accent-2-soft), transparent 66%);
  pointer-events: none;
  animation: hero-light-pan 18s ease-in-out infinite alternate;
}

/*
   The watermark. Text, not the logo: CLAUDE.md is explicit that the artwork is
   never redrawn, and a wordmark traced into an SVG path would be exactly that.
   So this is the name set in a serif, tinted by a token, at around 6% — a
   texture you notice only once you look for it.

   The colour is baked into the data URI because a background-image cannot read
   a custom property, which is why there are two of them and --hero-watermark
   picks. `stroke` on the glyphs rather than `fill` keeps it legible as texture
   at 6% instead of dissolving into a grey smear.
*/
:root {
  --hero-watermark-light: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 300 64'%3E%3Ctext x='150' y='30' text-anchor='middle' font-family='Georgia,serif' font-size='22' letter-spacing='1.5' fill='%23403a28' opacity='0.075'%3ERayspring%3C/text%3E%3Ctext x='150' y='50' text-anchor='middle' font-family='Georgia,serif' font-size='9' letter-spacing='5' fill='%23403a28' opacity='0.065'%3EMINISTRY DIGITAL%3C/text%3E%3C/svg%3E");
  --hero-watermark-dark: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 300 64'%3E%3Ctext x='150' y='30' text-anchor='middle' font-family='Georgia,serif' font-size='22' letter-spacing='1.5' fill='%23ecd49c' opacity='0.085'%3ERayspring%3C/text%3E%3Ctext x='150' y='50' text-anchor='middle' font-family='Georgia,serif' font-size='9' letter-spacing='5' fill='%23ecd49c' opacity='0.07'%3EMINISTRY DIGITAL%3C/text%3E%3C/svg%3E");
  --hero-watermark: var(--hero-watermark-light);
}
html[data-mode="dark"] { --hero-watermark: var(--hero-watermark-dark); }

.hero::after {
  content: "";
  position: absolute;
  inset: 0;
  z-index: 0;
  pointer-events: none;
  background: var(--hero-watermark) no-repeat right 2% top 24px / 420px auto;
  animation: hero-mark-drift 26s ease-in-out infinite alternate;
}
/* .hero__inner is already position:relative, so it sits above both layers. */

@keyframes hero-light-pan {
  from { transform: translateX(-3.5%); }
  to   { transform: translateX(3.5%); }
}
@keyframes hero-mark-drift {
  from { transform: translateY(10px); }
  to   { transform: translateY(-14px); }
}

@media (max-width: 700px) {
  .hero::after { background-size: 260px auto; background-position: right 4% top 16px; }
}

/* The project's existing rule: motion is decoration, and decoration stops when
   the reader has asked for it to. */
@media (prefers-reduced-motion: reduce) {
  .hero::before,
  .hero::after { animation: none; }
}
.hero__inner {
  position: relative;
  display: grid;
  gap: clamp(2rem, 4vw, 3.5rem);
  align-items: center;
  grid-template-columns: minmax(0, 1.08fr) minmax(0, 1fr);
}
@media (max-width: 900px) {
  .hero__inner { grid-template-columns: 1fr; }
}

/* No hero image: one centred column rather than a conspicuously empty half. */
.hero__inner--solo {
  grid-template-columns: minmax(0, 1fr);
  max-width: 54rem;
  text-align: center;
}
.hero__inner--solo .hero__lede { margin-inline: auto; }
.hero__inner--solo .hero__actions { justify-content: center; }
.hero__inner--solo .eyebrow { justify-content: center; }
.hero__inner--solo .eyebrow::before { display: none; }
.hero__title { margin-bottom: 1rem; }
.hero__lede { font-size: var(--step-1); color: var(--text-muted); max-width: 34rem; }
.hero__actions { margin-top: 1.9rem; }
.hero__note {
  margin-top: 1.3rem;
  font-size: var(--step--1);
  color: var(--text-subtle);
}
.hero__media {
  position: relative;
  border-radius: var(--radius-lg);
  overflow: hidden;
  background: var(--surface-2);
  border: 1px solid var(--line);
  box-shadow: var(--shadow-lg);
  aspect-ratio: 4 / 3;
}
.hero__media img { width: 100%; height: 100%; object-fit: cover; }

/* Showcase hero — real client websites on the first screen.
   The copy column gets the wider share: the sites are the proof, but the
   sentence explaining what is being sold still has to be read first. */
.hero--showcase .hero__inner { grid-template-columns: minmax(0, 1fr) minmax(0, 1.02fr); }
@media (max-width: 980px) {
  .hero--showcase .hero__inner { grid-template-columns: 1fr; }
}

/* Half the width means the h1 needs a step down: at the full display size this
   headline broke into four short lines and pushed everything else off the
   screen, including the two buttons it exists to lead to. */
.hero--showcase .hero__title { font-size: var(--step-4); }
.hero--showcase .hero__actions .button {
  padding-inline: 1.2rem;
  font-size: var(--step-0);
}

.hero__showcase {
  position: relative;
  padding-block-end: clamp(2rem, 5vw, 3.5rem);
}
.hero__showcase--single { padding-block-end: 0; }
.hero__showcase .browser--lead { position: relative; z-index: 2; }
/* The second site peeks out from behind the first: two proofs in the space of
   one, and the overlap reads as a stack of real work rather than a gallery. */
.hero__showcase .browser--trail {
  position: absolute;
  inset-block-end: 0;
  inset-inline-end: 0;
  width: 58%;
  z-index: 3;
  /* A ring in the page colour separates the two frames where they overlap;
     without it the front one reads as a rendering glitch on the back one. */
  outline: 6px solid var(--bg);
}

/* A browser frame. The address bar carries the site's real host, which is the
   entire point — a screenshot proves a design, a domain proves it is online. */
.browser {
  display: block;
  border-radius: var(--radius-md);
  overflow: hidden;
  background: var(--surface);
  border: 1px solid var(--line);
  box-shadow: var(--shadow-lg);
  text-decoration: none;
  color: inherit;
  transition: transform var(--speed) var(--ease), box-shadow var(--speed) var(--ease);
}
a.browser:hover { transform: translateY(-3px); }

/* ── The phone beside the browser ───────────────────────────────────────────
   Most of the people these ministries are trying to reach open the site on a
   phone, so a desktop-only mockup quietly fails to answer the first question a
   pastor has. The phone overlaps the browser's lower-left corner: side by side
   they read as two unrelated pictures, overlapping they read as one site seen
   twice.

   No notch, no bezel, no side buttons. A rounder corner than the browser's and
   a shadow is the whole device illusion, and it re-themes for free because
   both are built from tokens rather than from a drawn frame.

   .browser needs overflow: hidden for its own rounded corners, so the phone
   would be clipped by it — it is positioned against .hero__showcase instead,
   which is why the overhang lives there.
   ------------------------------------------------------------------------ */
.browser { position: relative; }
.browser__phone {
  position: absolute;
  z-index: 2;
  bottom: -9%;
  left: -7%;
  width: 26%;
  overflow: hidden;
  border-radius: 14px;
  background: var(--surface);
  border: 1px solid var(--line);
  box-shadow: var(--shadow-lg);
  pointer-events: none;
}
.browser__phone img {
  display: block;
  width: 100%;
  /* A tall crop of the top of the page — a whole phone screenshot scaled to
     26% would be a stamp nobody can read. */
  aspect-ratio: 9 / 17.5;
  object-fit: cover;
  object-position: top center;
}

/* The overhang has to come out of the container, or the phone is cut off by
   the section edge on the left and by the fold underneath. */
.hero__showcase { overflow: visible; }

@media (max-width: 700px) {
  /* On a phone the reader is already holding one. The mockup costs width the
     screenshot needs more. */
  .browser__phone { display: none; }
}
.browser__bar {
  display: flex;
  align-items: center;
  gap: 0.6rem;
  padding: 0.5rem 0.7rem;
  background: var(--surface-2);
  border-block-end: 1px solid var(--line);
}
.browser__dots { display: inline-flex; gap: 0.28rem; flex: none; }
.browser__dots i {
  width: 0.46rem;
  height: 0.46rem;
  border-radius: 50%;
  background: var(--line-strong);
}
.browser__url {
  flex: 1;
  min-width: 0;
  padding: 0.2rem 0.7rem;
  border-radius: var(--radius-pill);
  background: var(--bg);
  font-family: var(--font-mono);
  font-size: 0.72rem;
  color: var(--text-muted);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.browser > img {
  display: block;
  width: 100%;
  height: auto;
  aspect-ratio: 16 / 10;
  object-fit: cover;
  object-position: top center;
  background: var(--surface-sunken);
}

/* Below this width the overlap becomes a pile. Stack them instead. */
@media (max-width: 620px) {
  .hero__showcase { padding: 0; }
  .hero__showcase .browser--trail {
    position: static;
    width: 100%;
    margin-block-start: 1rem;
  }
}

/* Live figures, never typed copy — see Controller::heroFacts(). */
.hero__facts {
  display: flex;
  flex-wrap: wrap;
  gap: clamp(1.25rem, 3.5vw, 2.75rem);
  margin: clamp(1.75rem, 3.5vw, 2.5rem) 0 0;
  padding-block-start: clamp(1.25rem, 2.5vw, 1.75rem);
  border-block-start: 1px solid var(--line);
}
.hero__inner--solo .hero__facts { justify-content: center; }
.hero__fact-value {
  font-family: var(--font-display);
  font-size: var(--step-2);
  font-weight: 600;
  line-height: 1.1;
  color: var(--text-strong);
}
.hero__fact-label {
  margin: 0.3rem 0 0;
  max-width: 10rem;
  font-size: var(--step--1);
  line-height: 1.4;
  color: var(--text-muted);
}

/* Page hero — the smaller variant on inner pages */
.page-hero {
  padding-block: clamp(2.5rem, 5.5vw, 4.5rem) clamp(1.75rem, 3.5vw, 3rem);
  background: var(--bg-2);
  border-bottom: 1px solid var(--line);
}
.page-hero__title { margin-bottom: 0.75rem; }

.breadcrumbs {
  display: flex;
  flex-wrap: wrap;
  /* Centre, not baseline. Below 820px the links become 44px inline-flex boxes
     for the tap target while the last crumb — the current page — stays a plain
     inline <span> at 16px, because it is not a link and never was. With the
     default alignment the tall boxes centre their text and the short span sits
     on the text baseline, so the last crumb and its "/" rode about 4px higher
     than the rest of the trail. It only showed between 390px and 820px, which
     is why it survived: the desktop rule does not apply and the check does not
     measure alignment. */
  align-items: center;
  flex-wrap: wrap;
  gap: 0.4rem;
  margin-bottom: 1.1rem;
  padding: 0;
  list-style: none;
  font-size: var(--step--1);
  color: var(--text-subtle);
}
.breadcrumbs li { display: inline-flex; align-items: center; }
.breadcrumbs li + li::before { content: "/"; margin-right: 0.4rem; opacity: 0.5; }
/* The current page is not a link, so it never picked up the touch-target box
   the others have. It does not need the height — it needs the same centre. */
.breadcrumbs [aria-current="page"] { display: inline-flex; align-items: center; }
.breadcrumbs a { color: var(--text-muted); text-decoration: none; }
.breadcrumbs a:hover { color: var(--accent); text-decoration: underline; }

/* ── Pricing ─────────────────────────────────────────────────────────────── */

.plan {
  position: relative;
  display: flex;
  flex-direction: column;
  padding: clamp(1.4rem, 2.6vw, 2rem);
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: var(--radius-md);
}
.plan--recommended {
  border-color: var(--accent-line);
  box-shadow: var(--shadow-md);
}
.plan__badge {
  position: absolute;
  top: -0.75rem;
  left: clamp(1.4rem, 2.6vw, 2rem);
  padding: 0.25rem 0.75rem;
  border-radius: var(--radius-pill);
  background: var(--accent);
  color: var(--text-on-accent);
  font-size: 0.72rem;
  font-weight: 700;
  letter-spacing: var(--track-label, 0.06em);
  text-transform: uppercase;
}
.plan__name { margin: 0 0 0.35rem; font-size: var(--step-1); }
.plan__best-for { margin: 0 0 1.2rem; font-size: var(--step--1); color: var(--text-subtle); }
.plan__price {
  display: flex;
  align-items: baseline;
  flex-wrap: wrap;
  gap: 0.4rem;
  margin-bottom: 0.35rem;
  font-family: var(--font-display);
  font-size: var(--step-3);
  font-weight: 600;
  color: var(--text-strong);
  line-height: 1.1;
}
.plan__price-note { font-family: var(--font-body); font-size: var(--step--1); font-weight: 500; color: var(--text-muted); }
.plan__hosting { margin: 0 0 1.3rem; font-size: var(--step--1); color: var(--text-muted); }
.plan__meta {
  display: flex;
  flex-wrap: wrap;
  gap: 0.4rem 1rem;
  margin-bottom: 1.2rem;
  padding-bottom: 1.2rem;
  border-bottom: 1px solid var(--line);
  font-size: var(--step--1);
  color: var(--text-muted);
}
.plan__features { list-style: none; margin: 0 0 1.5rem; padding: 0; font-size: var(--step-0); }
.plan__features li {
  position: relative;
  padding-left: 1.6rem;
  margin-bottom: 0.5rem;
  color: var(--text-muted);
}
.plan__features li::before {
  content: "";
  position: absolute;
  left: 0.15rem;
  top: 0.62em;
  width: 0.55rem;
  height: 0.3rem;
  border-left: 2px solid var(--accent);
  border-bottom: 2px solid var(--accent);
  transform: rotate(-45deg);
}
.plan__features--excluded li { color: var(--text-subtle); }
.plan__features--excluded li::before {
  border: 0;
  top: 0.72em;
  width: 0.6rem;
  height: 0;
  border-top: 2px solid var(--text-subtle);
  transform: none;
  opacity: 0.7;
}
.plan__excluded-title {
  margin: 0 0 0.55rem;
  font-family: var(--font-body);
  font-size: var(--step--1);
  font-weight: 600;
  letter-spacing: var(--track-label, 0.05em);
  text-transform: uppercase;
  color: var(--text-subtle);
}
.plan__cta { margin-top: auto; }

.addon-table { width: 100%; border-collapse: collapse; font-size: var(--step-0); }
.addon-table th,
.addon-table td { padding: 0.85rem 0.6rem; text-align: left; border-bottom: 1px solid var(--line); }
.addon-table th { font-weight: 600; color: var(--text-subtle); font-size: var(--step--1); text-transform: uppercase; letter-spacing: var(--track-label, 0.05em); }
.addon-table td:last-child { text-align: right; white-space: nowrap; font-weight: 600; }

/* ── Templates ───────────────────────────────────────────────────────────── */

.template-card { position: relative; overflow: hidden; padding: 0; }
.template-card__shots {
  position: relative;
  aspect-ratio: 16 / 11;
  background: var(--surface-sunken);
  overflow: hidden;
}
.template-card__desktop { width: 100%; height: 100%; object-fit: cover; object-position: top center; }
.template-card__mobile {
  position: absolute;
  right: 5%;
  bottom: -8%;
  width: 24%;
  border-radius: 10px;
  border: 3px solid var(--surface);
  box-shadow: var(--shadow-md);
  object-fit: cover;
  object-position: top center;
}
/* Shown until a real screenshot exists. Communicates the template's palette
   instead of pretending an image failed to load. */
.template-card__placeholder {
  position: relative;
  width: 100%;
  height: 100%;
  display: grid;
  place-items: center;
  background: var(--surface-2);
}
.template-card__bands {
  position: absolute;
  inset: 0;
  display: flex;
  flex-direction: column;
}
.template-card__bands span { flex: 1; }
.template-card__code {
  position: relative;
  padding: 0.4rem 1.1rem;
  border-radius: var(--radius-pill);
  background: rgba(255, 255, 255, 0.9);
  /* The pill is deliberately white in every theme, so its text has to be dark
     in every theme — which means the theme-scoped --text is wrong here. --ink
     is a brand constant and never re-scoped, so it says what is meant. */
  color: var(--ink);
  font-family: var(--font-display);
  font-size: var(--step-1);
  font-weight: 600;
  letter-spacing: 0.02em;
  box-shadow: var(--shadow-sm);
}

.template-card__actions { display: flex; flex-wrap: wrap; gap: 0.5rem; }

/* Template detail: a browser frame with the phone overlapping its lower edge. */
.template-shots { position: relative; padding-block-end: clamp(1.5rem, 5vw, 3rem); }
.template-shots__desktop > img {
  display: block;
  width: 100%;
  height: auto;
  aspect-ratio: 16 / 10;
  object-fit: cover;
  object-position: top center;
}
.template-shots__phone {
  position: absolute;
  inset-block-end: 0;
  inset-inline-end: 4%;
  width: 26%;
  min-width: 5.5rem;
  max-width: 11rem;
  aspect-ratio: 9 / 17;
  overflow: hidden;
  border-radius: 14px;
  /* The ring is the page colour, so the phone reads as sitting in front of the
     browser rather than as a hole cut in it. */
  border: 5px solid var(--bg);
  box-shadow: var(--shadow-lg);
  background: var(--surface-sunken);
}
.template-shots__phone img { width: 100%; height: 100%; object-fit: cover; object-position: top center; }

@media (max-width: 560px) {
  .template-shots { padding-block-end: 0; }
  .template-shots__phone {
    position: static;
    width: 9rem;
    margin-block-start: 1rem;
    border-color: var(--line);
    border-width: 1px;
  }
}

.template-card__body { padding: clamp(1.2rem, 2.2vw, 1.6rem); }
.template-card__palette { display: flex; gap: 0.3rem; margin-bottom: 0.85rem; }
.template-card__swatch {
  width: 1.1rem;
  height: 1.1rem;
  border-radius: 50%;
  border: 1px solid var(--line-strong);
}
.template-card__meta {
  display: flex;
  flex-wrap: wrap;
  gap: 0.35rem;
  margin: 0.9rem 0 0;
  padding: 0;
  list-style: none;
}
.tag {
  display: inline-block;
  padding: 0.2rem 0.6rem;
  border: 1px solid var(--line);
  border-radius: var(--radius-pill);
  font-size: 0.76rem;
  color: var(--text-muted);
  background: var(--surface-2);
}
.tag--accent { border-color: var(--accent-line); color: var(--accent); background: var(--accent-soft); }

.filter-bar { display: flex; flex-wrap: wrap; gap: 0.5rem; margin-bottom: 2rem; }
.filter-bar__btn {
  padding: 0.45rem 1rem;
  border: 1px solid var(--line);
  border-radius: var(--radius-pill);
  background: transparent;
  color: var(--text-muted);
  font: inherit;
  font-size: 0.9rem;
  text-decoration: none;
  cursor: pointer;
  min-height: 38px;
  display: inline-flex;
  align-items: center;
}
.filter-bar__btn:hover { border-color: var(--accent-line); color: var(--text-strong); }
.filter-bar__btn.is-active {
  background: var(--accent);
  border-color: var(--accent);
  color: var(--text-on-accent);
  font-weight: 600;
}

/* ── Process / steps ─────────────────────────────────────────────────────── */

.process-layout {
  display: grid;
  gap: clamp(1.5rem, 4vw, 3.5rem);
  grid-template-columns: minmax(0, 1.35fr) minmax(0, 1fr);
  align-items: start;
}
@media (max-width: 860px) {
  .process-layout { grid-template-columns: 1fr; }
}
.process-note {
  padding: clamp(1.25rem, 2.5vw, 1.9rem);
  border: 1px solid var(--line);
  border-radius: var(--radius-md);
  background: var(--surface-2);
  font-size: var(--step-0);
}

.process { counter-reset: step; list-style: none; margin: 0; padding: 0; }
.process__item {
  counter-increment: step;
  position: relative;
  padding-left: 3.4rem;
  padding-bottom: 2rem;
}
.process__item::before {
  content: counter(step);
  position: absolute;
  left: 0;
  top: 0;
  width: 2.35rem;
  height: 2.35rem;
  display: grid;
  place-items: center;
  border-radius: 50%;
  background: var(--accent-soft);
  border: 1px solid var(--accent-line);
  color: var(--accent);
  font-family: var(--font-display);
  font-weight: 600;
}
.process__item::after {
  content: "";
  position: absolute;
  left: 1.17rem;
  top: 2.6rem;
  bottom: 0.35rem;
  width: 1px;
  background: var(--line);
}
.process__item:last-child { padding-bottom: 0; }
.process__item:last-child::after { display: none; }
.process__title { margin: 0.25rem 0 0.4rem; font-size: var(--step-1); }
.process__text { margin: 0; color: var(--text-muted); }

/* ── Stats ───────────────────────────────────────────────────────────────── */

.stat { text-align: center; }
.stat__value {
  display: block;
  font-family: var(--font-display);
  font-size: var(--step-4);
  font-weight: 600;
  color: var(--accent);
  line-height: 1.1;
}
.stat__label { display: block; margin-top: 0.4rem; font-size: var(--step--1); color: var(--text-muted); }

/* ── FAQ ─────────────────────────────────────────────────────────────────── */

.faq { border-top: 1px solid var(--line); }
.faq__item { border-bottom: 1px solid var(--line); }
.faq__question {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: 1rem;
  width: 100%;
  padding: 1.25rem 0;
  border: 0;
  background: transparent;
  color: var(--text-strong);
  font-family: var(--font-display);
  font-size: var(--step-1);
  font-weight: 600;
  text-align: left;
  cursor: pointer;
  line-height: 1.35;
}
html[lang="zh-CN"] .faq__question,
html[lang="my"] .faq__question { font-family: var(--font-body); }
.faq__icon {
  flex-shrink: 0;
  width: 1.15rem;
  height: 1.15rem;
  margin-top: 0.3rem;
  position: relative;
}
.faq__icon::before,
.faq__icon::after {
  content: "";
  position: absolute;
  inset: 50% 0 auto 0;
  height: 2px;
  background: var(--accent);
  border-radius: 2px;
  transition: transform var(--speed) var(--ease);
}
.faq__icon::after { transform: rotate(90deg); }
.faq__question[aria-expanded="true"] .faq__icon::after { transform: rotate(0deg); }
.faq__answer {
  overflow: hidden;
  color: var(--text-muted);
}
.faq__answer[hidden] { display: none; }
.faq__answer-inner { padding-bottom: 1.4rem; max-width: 48rem; }

/* ── Forms ───────────────────────────────────────────────────────────────── */

.form { display: grid; gap: 1.15rem; }
.form-row { display: grid; gap: 1.15rem; grid-template-columns: repeat(auto-fit, minmax(min(100%, 15rem), 1fr)); }
.field { display: flex; flex-direction: column; gap: 0.4rem; }
.field__label {
  font-size: var(--step--1);
  font-weight: 600;
  color: var(--text-strong);
  letter-spacing: 0.01em;
}
.field__label .req { color: var(--err-text); margin-left: 0.15rem; }
.field__hint { font-size: 0.82rem; color: var(--text-subtle); }
.field__error { font-size: 0.82rem; color: var(--err-text); font-weight: 500; }

.input,
.select,
.textarea {
  width: 100%;
  padding: 0.72rem 0.9rem;
  border: 1px solid var(--line-strong);
  border-radius: var(--radius-sm);
  background: var(--surface);
  color: var(--text);
  font: inherit;
  font-size: var(--step-0);
  line-height: 1.5;
  min-height: 46px;
  transition: border-color var(--speed) var(--ease), box-shadow var(--speed) var(--ease);
}
.input::placeholder,
.textarea::placeholder { color: var(--text-subtle); opacity: 1; }
.input:focus,
.select:focus,
.textarea:focus {
  outline: none;
  border-color: var(--accent);
  box-shadow: 0 0 0 3px var(--accent-soft);
}
.input[aria-invalid="true"],
.textarea[aria-invalid="true"],
.select[aria-invalid="true"] { border-color: var(--err-line); }
.textarea { min-height: 8.5rem; resize: vertical; }
.select {
  appearance: none;
  padding-right: 2.4rem;
  /* Inline caret so no image request is needed and it recolours per theme. */
  background-image: linear-gradient(45deg, transparent 50%, currentColor 50%),
    linear-gradient(135deg, currentColor 50%, transparent 50%);
  background-position: calc(100% - 1.15rem) 55%, calc(100% - 0.8rem) 55%;
  background-size: 0.35rem 0.35rem, 0.35rem 0.35rem;
  background-repeat: no-repeat;
}

/* Groups a long form into named parts. The supported-ministry form does the
   same thing with an inline font-size; this is that, named, so a third form
   does not invent a third version of it. */
.form-heading {
  margin: 2rem 0 0.25rem;
  font-size: var(--step-1);
}
.form-heading:first-child { margin-top: 0; }

.checkbox {
  display: flex;
  align-items: flex-start;
  gap: 0.65rem;
  font-size: var(--step--1);
  color: var(--text-muted);
  line-height: 1.5;
  cursor: pointer;
}
.checkbox input {
  flex-shrink: 0;
  width: 1.15rem;
  height: 1.15rem;
  margin-top: 0.15rem;
  accent-color: var(--accent);
  cursor: pointer;
}

/* The honeypot. Hidden from people, present for bots. Not display:none —
   some bots skip those; off-screen with aria-hidden is the reliable shape. */
.hp-field {
  position: absolute !important;
  left: -9999px;
  width: 1px;
  height: 1px;
  overflow: hidden;
}

.form-status {
  padding: 0.9rem 1.1rem;
  border: 1px solid;
  border-radius: var(--radius-sm);
  font-size: var(--step-0);
}
.form-status--success { color: var(--ok-text); background: var(--ok-bg); border-color: var(--ok-line); }
.form-status--error { color: var(--err-text); background: var(--err-bg); border-color: var(--err-line); }
.form-status--warning { color: var(--warn-text); background: var(--warn-bg); border-color: var(--warn-line); }
.form-status--info { color: var(--info-text); background: var(--info-bg); border-color: var(--accent-line); }

.notice {
  padding: 1rem 1.2rem;
  border-left: 3px solid var(--accent);
  border-radius: 0 var(--radius-sm) var(--radius-sm) 0;
  background: var(--accent-soft);
  font-size: var(--step-0);
}
.notice--warn { border-left-color: var(--warn-line); background: var(--warn-bg); color: var(--warn-text); }

/* ── Prose (article body, sanitised HTML from the CMS) ───────────────────── */

.prose { max-width: 44rem; font-size: var(--step-1); line-height: 1.75; color: var(--text); }
.prose > * + * { margin-top: 1.15em; }
.prose h2 { margin-top: 2em; font-size: var(--step-3); }
.prose h3 { margin-top: 1.6em; font-size: var(--step-2); }
.prose ul, .prose ol { padding-left: 1.4em; }
.prose li + li { margin-top: 0.4em; }
.prose blockquote {
  margin-inline: 0;
  padding: 0.4em 0 0.4em 1.3em;
  border-left: 3px solid var(--accent-line);
  color: var(--text-muted);
  font-style: italic;
}
.prose img { border-radius: var(--radius-md); margin-block: 1.6em; }
.prose figure { margin: 1.6em 0; }
.prose figcaption { margin-top: 0.6em; font-size: var(--step--1); color: var(--text-subtle); text-align: center; }
.prose table { width: 100%; border-collapse: collapse; font-size: var(--step-0); }
.prose th, .prose td { padding: 0.6em 0.7em; border: 1px solid var(--line); text-align: left; }
.prose th { background: var(--surface-2); font-weight: 600; }
.prose code {
  padding: 0.15em 0.4em;
  border-radius: var(--radius-xs);
  background: var(--surface-2);
  font-family: var(--font-mono);
  font-size: 0.88em;
}
.prose pre {
  padding: 1rem 1.2rem;
  border-radius: var(--radius-sm);
  background: var(--surface-ink);
  color: var(--text-on-ink);
  overflow-x: auto;
}
.prose pre code { background: transparent; padding: 0; }
/* Any embedded iframe (a sermon video) must stay inside its column. */
.prose iframe { width: 100%; aspect-ratio: 16 / 9; border: 0; border-radius: var(--radius-md); }

/* ── Media / embeds ──────────────────────────────────────────────────────── */

.embed {
  position: relative;
  aspect-ratio: 16 / 9;
  border-radius: var(--radius-md);
  overflow: hidden;
  background: var(--surface-ink);
}
.embed iframe { position: absolute; inset: 0; width: 100%; height: 100%; border: 0; }

/* ── Testimonials ────────────────────────────────────────────────────────── */

.quote { display: flex; flex-direction: column; gap: 1.2rem; }
.quote__text {
  margin: 0;
  font-family: var(--font-display);
  font-size: var(--step-1);
  line-height: 1.55;
  color: var(--text-strong);
}
html[lang="zh-CN"] .quote__text,
html[lang="my"] .quote__text { font-family: var(--font-body); }
.quote__author { display: flex; align-items: center; gap: 0.85rem; margin-top: auto; }
.quote__avatar { width: 2.75rem; height: 2.75rem; border-radius: 50%; object-fit: cover; flex-shrink: 0; }
.quote__name { font-weight: 600; font-size: var(--step-0); }
.quote__role { font-size: var(--step--1); color: var(--text-subtle); }

/* ── Pagination ──────────────────────────────────────────────────────────── */

.pagination {
  display: flex;
  flex-wrap: wrap;
  gap: 0.4rem;
  justify-content: center;
  align-items: center;
  margin-top: 3rem;
  list-style: none;
  padding: 0;
}
.pagination a,
.pagination span {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: 42px;
  min-height: 42px;
  padding: 0 0.7rem;
  border: 1px solid var(--line);
  border-radius: var(--radius-sm);
  color: var(--text-muted);
  text-decoration: none;
  font-size: 0.92rem;
}
.pagination a:hover { border-color: var(--accent-line); color: var(--accent); }
.pagination [aria-current="page"] {
  background: var(--accent);
  border-color: var(--accent);
  color: var(--text-on-accent);
  font-weight: 600;
}

/* ── Footer ──────────────────────────────────────────────────────────────── */

.site-footer__brand { display: inline-flex; margin-bottom: 1.1rem; }

.site-footer {
  margin-top: clamp(3rem, 7vw, 6rem);
  padding-block: clamp(2.75rem, 6vw, 4.5rem) 2rem;
  background: var(--surface-ink);
  color: var(--text-on-ink);
  --text: var(--text-on-ink);
  --text-muted: var(--text-on-ink-muted);
  --line: var(--line-on-ink);
  --accent: #dcb672;
  --accent-strong: #ecca8e;
  --text-on-accent: #1c1509;
}
.site-footer h2, .site-footer h3, .site-footer h4 { color: #fdfbf6; }
.site-footer__grid {
  display: grid;
  gap: clamp(1.75rem, 4vw, 3rem);
  grid-template-columns: minmax(0, 1.4fr) repeat(auto-fit, minmax(min(100%, 10rem), 1fr));
  padding-bottom: 2.5rem;
  border-bottom: 1px solid var(--line-on-ink);
}
.site-footer__about { max-width: 24rem; }
.site-footer__about p { color: var(--text-on-ink-muted); font-size: var(--step--1); line-height: 1.7; }
.site-footer__title {
  margin: 0 0 0.9rem;
  font-family: var(--font-body);
  font-size: 0.78rem;
  font-weight: 700;
  letter-spacing: var(--track-label, 0.1em);
  text-transform: uppercase;
  color: var(--text-on-ink-muted);
}
.site-footer__list { list-style: none; margin: 0; padding: 0; }
.site-footer__list li + li { margin-top: 0.55rem; }
.site-footer__list a {
  display: inline-flex;
  align-items: center;
  color: var(--text-on-ink);
  text-decoration: none;
  font-size: var(--step--1);
  opacity: 0.86;
}
.site-footer__list a:hover { color: var(--accent); opacity: 1; text-decoration: underline; }
.site-footer__bottom {
  display: flex;
  flex-wrap: wrap;
  gap: 0.8rem 1.6rem;
  align-items: center;
  justify-content: space-between;
  padding-top: 1.75rem;
  font-size: 0.82rem;
  color: var(--text-on-ink-muted);
}
.site-footer__legal { display: flex; flex-wrap: wrap; gap: 1rem; list-style: none; margin: 0; padding: 0; }
.site-footer__legal a { display: inline-flex; align-items: center; color: var(--text-on-ink-muted); text-decoration: none; }
.site-footer__legal a:hover { color: var(--accent); text-decoration: underline; }

/* Every link in the footer is one line of small type — about sixteen pixels
   tall, stacked, roughly twenty of them. On a desktop that is fine; on the
   phone that is the primary device for most of the people this site is for, it
   is a column of targets too small and too close together to hit reliably.
   Scoped to touch so the desktop footer stays compact.

   These were invisible to the audit for months: its inline-link exception
   matched anything inside an <li>, which is every navigation link there is.

   `(pointer: coarse)`, not width alone. Width was the wrong question and an
   iPad is the proof: 834px in portrait and 1194 in landscape, both over this
   820px line, and every one of those pixels is touched with a finger. So the
   footer links stayed 25px tall and the breadcrumbs 20px on the one device
   family where a reader is most likely to be sitting in a pew with it. The
   touch-target block at the end of this file already asked the right question
   — this one was copied from a breakpoint instead. Same class of fault as a
   rule keyed to a screen size when it meant to ask about a fingertip. */
@media (max-width: 820px), (pointer: coarse) {
  .site-footer__list a,
  .site-footer__legal a { min-height: 44px; }
  /* Full width, so the tap area is the row rather than the width of the word.
     "FAQ" is 25px of text; the finger aiming at it is not. */
  .site-footer__list a { width: 100%; }
  .site-footer__list li + li { margin-top: 0; }
  .site-footer__about a { display: inline-flex; align-items: center; min-height: 44px; }

  /* A two-character breadcrumb ("首页") is 26px of text. The hit box is widened
     with padding and pulled back with an equal negative margin, so the target
     grows without the crumb trail visibly shifting. */
  .breadcrumbs a {
    display: inline-flex;
    align-items: center;
    min-height: 44px;
    padding-inline: 0.5rem;
    margin-inline: -0.5rem;
  }

  /* A link that is alone in its paragraph is a control, not part of a
     sentence — an email address in a card, for instance. min-height alone does
     nothing to an inline element, so it has to become inline-flex first. */
  .card p > a:only-child:not(.button) {
    display: inline-flex;
    align-items: center;
    min-height: 44px;
  }
}

/* The "powered by" line. Honest about what this is: one person, with
   Rayspring Tech behind it. */
.site-footer__parent { font-size: 0.8rem; opacity: 0.72; }

/* ── CTA panel ───────────────────────────────────────────────────────────── */

.cta-panel {
  padding: clamp(2rem, 5vw, 3.75rem);
  border-radius: var(--radius-lg);
  background: var(--surface-ink);
  color: var(--text-on-ink);
  --text: var(--text-on-ink);
  --text-muted: var(--text-on-ink-muted);
  --line: var(--line-on-ink);
  --accent: #dcb672;
  --accent-strong: #ecca8e;
  --text-on-accent: #1c1509;
  text-align: center;
}
.cta-panel h2 { color: #fdfbf6; }
.cta-panel .lede { color: var(--text-on-ink-muted); margin-inline: auto; }
.cta-panel .cluster { justify-content: center; margin-top: 1.9rem; }
.cta-panel .button--secondary {
  --btn-fg: #fdfbf6;
  --btn-line: rgba(255, 255, 255, 0.28);
}

/* ── Utilities ───────────────────────────────────────────────────────────── */

.text-center { text-align: center; }
.text-muted { color: var(--text-muted); }
.text-small { font-size: var(--step--1); }
.mt-0 { margin-top: 0; }
.mt-1 { margin-top: 0.5rem; }
.mt-2 { margin-top: 1rem; }
.mt-3 { margin-top: 1.75rem; }
.mt-4 { margin-top: 2.5rem; }
.mb-0 { margin-bottom: 0; }
.flow > * + * { margin-top: 1.1rem; }

.empty-state {
  padding: clamp(2rem, 5vw, 3.5rem);
  border: 1px dashed var(--line-strong);
  border-radius: var(--radius-md);
  text-align: center;
  color: var(--text-muted);
}

/* Entrance animation.
   
   SCOPED TO html.js ON PURPOSE. Starting at opacity:0 means that if the
   JavaScript never runs — a parse error, a blocked file, an old browser —
   every element carrying .reveal stays permanently invisible, which on this
   site is most of the home page. The inline head script adds the `js` class,
   so without it the content is simply visible with no animation.

   Switched off entirely for anyone who asked for reduced motion (handled at
   the top of the file). */
html.js .reveal {
  opacity: 0;
  transform: translateY(14px);
  transition: opacity 0.6s var(--ease), transform 0.6s var(--ease);
}
html.js .reveal.is-visible { opacity: 1; transform: none; }

@media (prefers-reduced-motion: reduce) {
  html.js .reveal { opacity: 1; transform: none; }
}

/* ── Touch targets ───────────────────────────────────────────────────────── */
/* 38px is comfortable with a mouse and too small for a thumb. Rather than
   inflating every control everywhere, they grow only on small screens and
   touch devices. This block lives at the END of the file on purpose: the
   component rules it overrides are defined later than the button section, so
   an earlier copy lost on cascade order despite equal specificity. */

@media (max-width: 820px), (pointer: coarse) {
  .button--sm,
  .dropdown__btn,
  .filter-bar__btn,
  .nav-toggle {
    min-height: 44px;
  }
  .button--sm { padding-block: 0.7rem; }
  .pagination a,
  .pagination span { min-height: 44px; min-width: 44px; }

  /* The brand is a link too — 34px of image is a small target for a thumb. */
  .brand { padding-block: 0.35rem; }
  .site-footer .brand { padding-block: 0.5rem; }
}

/* ── Print ───────────────────────────────────────────────────────────────── */

@media print {
  .site-header, .site-footer, .nav-toggle, .header-actions, .cta-panel { display: none !important; }
  body { background: #fff; color: #000; }
  a[href^="http"]::after { content: " (" attr(href) ")"; font-size: 0.8em; }
}

/* ── Case study card ─────────────────────────────────────────────────────── */
/*
   Replaces the stretched-link card. Two real actions instead of one invisible
   one, and a fixed-ratio shot so two cards of unequal text still line up —
   the old grid left one card's button floating half a screen above the other.
*/

.case-card {
  display: flex;
  flex-direction: column;
  padding: 0;
  overflow: hidden;
}
.case-card__shot {
  display: block;
  aspect-ratio: 16 / 10;
  overflow: hidden;
  background: var(--surface-sunken);
  border-bottom: 1px solid var(--line);
}
.case-card__shot img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  object-position: top center;
  transition: transform 0.5s ease;
}
.case-card:hover .case-card__shot img { transform: scale(1.02); }

.case-card__body {
  display: flex;
  flex-direction: column;
  flex: 1;
  gap: 0.6rem;
  padding: clamp(1.2rem, 2.2vw, 1.7rem);
}
.case-card__meta {
  margin: 0;
  font-size: var(--step--1);
  color: var(--text-subtle);
}
.case-card__title { margin: 0; font-size: var(--step-2); line-height: 1.25; }
.case-card__text { margin: 0; color: var(--text-muted); }

/* Pushed to the bottom so both cards' buttons sit on the same line. */
.case-card__actions {
  display: flex;
  flex-wrap: wrap;
  gap: 0.6rem;
  margin-top: auto;
  padding-top: 0.9rem;
}
.case-card__actions .button { display: inline-flex; align-items: center; }

@media (prefers-reduced-motion: reduce) {
  .case-card__shot img { transition: none; }
  .case-card:hover .case-card__shot img { transform: none; }
}

/* Case study at full width, screenshot and story side by side, alternating
   down the page. The home page shows its only two projects this way: at card
   size a ministry website is an unreadable smudge, which is the opposite of
   what a portfolio is for. */
.case-features { display: grid; gap: clamp(2.5rem, 5.5vw, 4.5rem); }
.case-feature {
  display: grid;
  gap: clamp(1.5rem, 3.5vw, 3rem);
  align-items: center;
  grid-template-columns: minmax(0, 1.2fr) minmax(0, 1fr);
}
.case-feature--textonly { grid-template-columns: minmax(0, 1fr); max-width: 46rem; }
.case-feature__shot { order: 1; }
.case-feature__body { order: 2; }
/* Alternate the sides so a list of projects does not read as a form. */
.case-feature:nth-child(even) .case-feature__shot { order: 2; }
.case-feature:nth-child(even) .case-feature__body { order: 1; }

@media (max-width: 860px) {
  .case-feature { grid-template-columns: 1fr; }
  /* Stacked, the picture always comes first — an alternating stack just looks
     like the markup is inconsistent. */
  .case-feature__shot,
  .case-feature:nth-child(even) .case-feature__shot { order: 1; }
  .case-feature__body,
  .case-feature:nth-child(even) .case-feature__body { order: 2; }
}

.case-feature__meta {
  margin: 0 0 0.6rem;
  font-size: var(--step--1);
  color: var(--text-subtle);
}
.case-feature__title { margin: 0 0 0.7rem; font-size: var(--step-3); line-height: 1.2; }
.case-feature__text { margin: 0; color: var(--text-muted); font-size: var(--step-0); }
.case-feature__actions {
  display: flex;
  flex-wrap: wrap;
  gap: 0.6rem;
  margin-block-start: 1.4rem;
}
.case-feature__actions .button { display: inline-flex; align-items: center; }

/* Who this is for. Read from `audience_types`, and kept deliberately lighter
   than a card: it used to sit directly above a second six-card grid and the
   two blocks were indistinguishable. */
.audiences {
  list-style: none;
  margin: 0;
  padding: 0;
  display: grid;
  gap: 0.5rem;
  grid-template-columns: repeat(auto-fit, minmax(min(100%, 19rem), 1fr));
}
.audience {
  display: flex;
  align-items: flex-start;
  gap: 0.9rem;
  height: 100%;
  padding: 1rem 1.1rem;
  border: 1px solid transparent;
  border-radius: var(--radius-md);
  color: inherit;
  text-decoration: none;
  transition: background var(--speed) var(--ease), border-color var(--speed) var(--ease),
    box-shadow var(--speed) var(--ease);
}
a.audience:hover,
a.audience:focus-visible {
  background: var(--surface);
  border-color: var(--line);
  box-shadow: var(--shadow-sm);
}
.audience__icon {
  flex: none;
  display: grid;
  place-items: center;
  width: 2.4rem;
  height: 2.4rem;
  border-radius: var(--radius-sm);
  background: var(--accent-soft);
  color: var(--accent);
}
.audience__icon svg { width: 1.15rem; height: 1.15rem; }
.audience__name {
  display: block;
  font-weight: 600;
  color: var(--text-strong);
  transition: color var(--speed) var(--ease);
}
a.audience:hover .audience__name { color: var(--accent); }
.audience__blurb {
  display: block;
  margin-block-start: 0.28rem;
  font-size: var(--step--1);
  line-height: 1.55;
  color: var(--text-muted);
}

/* card_grid settings.style = "list": the same items with the boxes taken away,
   ruled like a ledger. Used where a card grid would be the second one in a row. */
.point-list {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(100%, 23rem), 1fr));
  column-gap: clamp(2rem, 4.5vw, 4rem);
}
.point {
  display: flex;
  align-items: flex-start;
  gap: 1rem;
  padding-block: clamp(1.1rem, 2vw, 1.5rem);
  border-block-start: 1px solid var(--line);
}
.point__icon {
  flex: none;
  display: grid;
  place-items: center;
  width: 1.9rem;
  height: 1.9rem;
  color: var(--accent);
}
.point__icon svg { width: 1.3rem; height: 1.3rem; }
.point__body { min-width: 0; }
.point__title { margin: 0 0 0.3rem; font-size: var(--step-0); line-height: 1.35; }
.point__text {
  margin: 0;
  font-size: var(--step--1);
  line-height: 1.65;
  color: var(--text-muted);
}
.point__foot { margin: 0.5rem 0 0; }

.link-arrow {
  font-size: var(--step--1);
  font-weight: 600;
  color: var(--accent);
  text-decoration: none;
}
.link-arrow:hover { text-decoration: underline; }

@media (prefers-reduced-motion: reduce) {
  a.browser:hover { transform: none; }
}

/* Case study detail: the story, and the facts a visitor is checking. */
.case-story {
  display: grid;
  gap: clamp(2rem, 5vw, 4rem);
  grid-template-columns: minmax(0, 1fr) minmax(0, 19rem);
  align-items: start;
}
@media (max-width: 900px) {
  .case-story { grid-template-columns: 1fr; }
}
.case-story__body { max-width: 42rem; }

.case-facts {
  position: sticky;
  top: calc(var(--header-height) + 1.5rem);
  padding: clamp(1.25rem, 2.5vw, 1.75rem);
  border: 1px solid var(--line);
  border-radius: var(--radius-md);
  background: var(--surface);
}
@media (max-width: 900px) { .case-facts { position: static; } }

.case-facts__list { margin: 0 0 1.25rem; }
.case-facts__list > div + div {
  margin-top: 0.85rem;
  padding-top: 0.85rem;
  border-top: 1px solid var(--line);
}
.case-facts__list dt {
  font-size: var(--step--1);
  font-weight: 600;
  letter-spacing: var(--track-label, 0.08em);
  text-transform: uppercase;
  color: var(--accent);
  margin-bottom: 0.2rem;
}
.case-facts__list dd { margin: 0; color: var(--text); }
.case-facts__note {
  margin: 1rem 0 0;
  font-size: var(--step--1);
  line-height: 1.55;
  color: var(--text-subtle);
}

.case-gallery {
  list-style: none;
  margin: 0;
  padding: 0;
  display: grid;
  gap: clamp(1rem, 2.2vw, 1.75rem);
  grid-template-columns: repeat(auto-fit, minmax(min(100%, 17rem), 1fr));
}
.case-gallery img {
  width: 100%;
  aspect-ratio: 4 / 3;
  object-fit: cover;
  object-position: top center;
  border: 1px solid var(--line);
  border-radius: var(--radius-md);
  background: var(--surface-sunken);
}

/* A three-column grid holding one or two items stretches them across the whole
   page. These cap the row so the cards keep the size they were designed at. */
.grid--fill-1 { grid-template-columns: minmax(0, min(100%, 24rem)); }
.grid--fill-2 { grid-template-columns: repeat(auto-fit, minmax(min(100%, 17.5rem), 24rem)); }

/* A list that reads as prose, for places where a card grid would be the third
   card grid on the page. Marker is a token, not a colour literal. */
.plain-list { margin: 0; padding: 0; list-style: none; display: grid; gap: 0.9rem; }
.plain-list li {
  position: relative;
  padding-inline-start: 1.6rem;
  color: var(--text-muted);
  line-height: 1.6;
}
.plain-list li::before {
  content: "";
  position: absolute;
  inset-inline-start: 0;
  top: 0.62em;
  width: 0.5rem;
  height: 0.5rem;
  border-radius: 50%;
  background: var(--accent);
}

/* ── Phone bottom bar ────────────────────────────────────────────────────────
   Five destinations one tap away, where the thumb already is. Everything else
   stays behind the hamburger.

   Only below 820px: that is the width this project already treats as "touch"
   (the tap-target audit uses the same number), and a tab bar on a laptop is
   just a strip of wasted screen.

   Driven by the `mobile` navigation menu — see app/views/layout/bottom-nav.php.
   Every colour is a token, so it follows all five themes. */

.bottom-nav { display: none; }

@media (max-width: 820px) {
  .bottom-nav {
    position: fixed;
    inset-inline: 0;
    inset-block-end: 0;
    z-index: 60;
    display: grid;
    grid-auto-flow: column;
    grid-auto-columns: 1fr;
    background: var(--header-bg-scrolled);
    border-block-start: 1px solid var(--line);
    /* The blur is what stops text scrolling underneath from showing through
       and making the labels unreadable. */
    backdrop-filter: blur(12px);
    /* The iPhone home indicator sits in the bottom ~34px. Without this the
       last row of labels is under it and the whole bar feels mis-aligned. */
    padding-block-end: env(safe-area-inset-bottom, 0px);
  }

  .bottom-nav__item {
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: 0.2rem;
    /* 56px of bar plus whatever the safe area adds — comfortably past the 44px
       minimum, and the tap area is the full column width. */
    min-height: 56px;
    padding-block: 0.45rem 0.4rem;
    color: var(--text-muted);
    text-decoration: none;
    position: relative;
  }

  .bottom-nav__icon { display: grid; place-items: center; }
  .bottom-nav__icon svg { width: 1.35rem; height: 1.35rem; }

  .bottom-nav__label {
    font-size: 0.66rem;
    font-weight: 600;
    letter-spacing: 0.01em;
    /* A long Burmese label must not push the columns out of alignment. */
    max-width: 100%;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
  }

  .bottom-nav__item.is-current { color: var(--accent); }
  /* The short rule above the active item, so the current place is legible
     without relying on colour alone. */
  .bottom-nav__item.is-current::before {
    content: "";
    position: absolute;
    inset-block-start: 0;
    inset-inline-start: 50%;
    transform: translateX(-50%);
    width: 1.6rem;
    height: 2px;
    border-radius: 0 0 2px 2px;
    background: var(--accent);
  }

  /* The bar floats over the page, so the end of the footer would otherwise sit
     underneath it and be unreachable. */
  body { padding-block-end: calc(56px + env(safe-area-inset-bottom, 0px)); }
}

@media print {
  .bottom-nav { display: none !important; }
}

/* ── The guided enquiry (/start) ────────────────────────────────────────────
   Written for a reader who has never commissioned a website, so every choice
   carries a line explaining what it is. That explanation is the feature — a
   bare checkbox labelled "About" tells somebody who does not already know
   exactly nothing.

   Whole-card targets rather than the checkbox alone: this form has fifty
   choices on it and most of them will be answered with a thumb.
   ------------------------------------------------------------------------ */

.brief__step {
  margin: 0 0 2.25rem;
  padding: 0;
  border: 0;
}
.brief__legend {
  display: flex;
  align-items: baseline;
  gap: 0.7rem;
  margin-bottom: 0.6rem;
  padding: 0;
  font-family: var(--font-display);
  font-size: var(--step-2);
  font-weight: 600;
  color: var(--text-strong);
}
html[lang="zh-CN"] .brief__legend,
html[lang="my"] .brief__legend { font-family: var(--font-body); font-weight: 700; }

.brief__num {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 1.7em;
  height: 1.7em;
  flex: none;
  border-radius: 50%;
  background: var(--accent-soft);
  color: var(--accent);
  font-family: var(--font-body);
  font-size: 0.72em;
  font-weight: 700;
}

.brief__choices {
  display: grid;
  gap: 0.6rem;
  margin-top: 1rem;
  grid-template-columns: repeat(auto-fit, minmax(15rem, 1fr));
}
/* The page list is long and each row carries a sentence, so it reads as a list
   rather than as a wall of tiles. */
.brief__choices--pages { grid-template-columns: repeat(auto-fit, minmax(19rem, 1fr)); }

.choice {
  display: flex;
  gap: 0.7rem;
  padding: 0.85rem 1rem;
  border: 1px solid var(--line);
  border-radius: var(--radius-sm);
  background: var(--surface);
  cursor: pointer;
  transition: border-color var(--speed) var(--ease), background var(--speed) var(--ease);
}
.choice:hover { border-color: var(--line-strong); }
.choice input { margin-top: 0.25rem; flex: none; accent-color: var(--accent); }
/* :has is what lets the whole card show the selection. Where it is not
   supported the card simply stays plain and the checkbox still shows state,
   so nothing is lost. */
.choice:has(input:checked) { border-color: var(--accent); background: var(--accent-soft); }
.choice:has(input:focus-visible) { outline: 2px solid var(--accent); outline-offset: 2px; }

.choice__body { display: flex; flex-direction: column; gap: 0.15rem; min-width: 0; }
.choice__label { font-weight: 600; color: var(--text-strong); line-height: 1.35; }
.choice__hint { font-size: var(--step--1); color: var(--text-muted); line-height: 1.45; }

.brief__submit {
  padding-top: 1.5rem;
  border-top: 1px solid var(--line);
}
.brief__submit .field__hint { margin-top: 0.75rem; max-width: 34rem; }


/* ============================================================================
   The reader's own choice of CJK face.
   ----------------------------------------------------------------------------
   There is no webfont here and there is not going to be one: a CJK face is
   megabytes even subsetted, this site's design target is a phone on a slow
   connection, and loading one from Google would send every Chinese reader's IP
   to Google from a site whose privacy page says it does not do that. That
   reasoning has not changed.

   What has changed is the conclusion drawn from it. "No webfont" was treated as
   "no choice", and it is not: PingFang, Heiti, Songti, Yuanti and their Windows
   and Android equivalents are already installed on the reader's machine and
   cost nothing to switch between. Which of them looks right is genuinely a
   matter of taste and of what the reader is used to — a Windows reader seeing
   Microsoft YaHei and a Mac reader seeing PingFang are not looking at the same
   page even though the CSS is identical — so the picker in the header offers
   four and remembers the answer.

   `system` is the default and changes nothing; the other three re-scope the two
   CJK tokens, which every rule on the site already reads through. Latin text is
   untouched in all four: this is a CJK control, and swapping Inter for
   something else would be a different (and worse) idea.
   ========================================================================== */

html[data-font="hei"] {
  --cjk-sans: "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei UI",
    "Microsoft YaHei", "Source Han Sans SC", "Noto Sans CJK SC";
  --cjk-serif: var(--cjk-sans);
}
html[data-font="song"] {
  --cjk-sans: "Songti SC", "Source Han Serif SC", "Noto Serif CJK SC", "STSong", SimSun;
  --cjk-serif: var(--cjk-sans);
}
html[data-font="round"] {
  --cjk-sans: "Yuanti SC", "Yuanti TC", "PingFang SC", "Microsoft YaHei UI",
    "Source Han Sans SC", "Noto Sans CJK SC";
  --cjk-serif: var(--cjk-sans);
}

/* A one-line preview of each face inside the menu, set in the face it offers,
   because the name of a font tells a reader who is not a designer nothing. */
.font-dropdown__sample {
  font-size: 1.05rem;
  line-height: 1.2;
}
.dropdown__item[data-font-value="system"] .font-dropdown__sample { font-family: var(--font-body); }
.dropdown__item[data-font-value="hei"] .font-dropdown__sample {
  font-family: "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei UI", "Microsoft YaHei", sans-serif;
}
.dropdown__item[data-font-value="song"] .font-dropdown__sample {
  font-family: "Songti SC", "Source Han Serif SC", "STSong", SimSun, serif;
}
.dropdown__item[data-font-value="round"] .font-dropdown__sample {
  font-family: "Yuanti SC", "Yuanti TC", "PingFang SC", sans-serif;
}
.dropdown__item[data-font-value] {
  display: flex;
  align-items: center;
  gap: 1rem;
  justify-content: space-between;
  /* The label and the sample each wrapped onto two lines inside the default
     11.5rem menu, which made four options look like eight. Neither is long
     enough to need wrapping; the menu is widened instead. */
  white-space: nowrap;
}
[data-font-dropdown] .dropdown__menu { min-width: 15rem; }
/* The button's own glyph. Not an icon from the set: 文 IS the control's
   meaning, it needs no legend, and a reader who cannot read the label can
   still see what the button is for. */
.font-dropdown__icon {
  display: block;
  font-size: 1rem;
  font-weight: 600;
  line-height: 1;
  font-family: var(--cjk-sans), sans-serif;
}


/* ── Four dropdowns will not fit a 360px phone ──────────────────────────────
   Adding the typeface picker put a third control in the header row, and every
   menu is right-aligned to its own button — so the leftmost one, the language
   menu, opened 18px off the left edge of a 360px screen. mobile-test.py caught
   it on the first run, which is the entire reason that suite drives the header
   with real taps rather than measuring the CSS.

   The first fix for that centred each menu on its own button, and centring is
   still anchored to the button: it cured the leftmost control and broke the
   rightmost one. The share menu opened 9.6px past the right edge of a 320px
   screen AND of a 390px one — the same 9.6px either way, which is the tell
   that the overflow is a property of the anchor rather than of the screen —
   and opening it widened the document from 320 to 329. Nothing wobbled on
   load, because the menu is inside a closed <details> until it is pressed;
   the scanner that reported it measures elements rather than the page, so it
   called it a page that scrolls sideways, which it is not until you tap.

   Anchored to .header-actions instead, so there is ONE anchor for all four
   rather than four anchors that each have to be checked separately. The row
   sits inside the container's gutter by definition, so a menu right-aligned
   to it cannot leave the screen on either side, and a fifth control cannot
   reintroduce this.

   Nothing here is `position: fixed`, because a fixed child of a filtered
   ancestor is measured against the header rather than the screen — the bug
   that made the mobile nav 72px tall, documented in CLAUDE.md. */
@media (max-width: 480px) {
  .header-actions { position: relative; }
  .header-actions .dropdown { position: static; }
  .header-actions .dropdown__menu {
    right: 0;
    left: auto;
    transform: none;
    max-width: calc(100vw - 1.5rem);
  }
}


/* ── The header row at laptop width ─────────────────────────────────────────
   The typeface picker is a third control in a row that already held the
   language menu, the theme menu and share, and on a 1366px laptop the English
   navigation is long enough that the whole row overflowed by eight pixels.
   visual-check.py found it; nothing else would have, because it only shows on
   one viewport in one language.

   Tightened between the point where the desktop nav appears and 1440px rather
   than dropping a control: every one of them earns its place, and hiding the
   font picker exactly where a Chinese reader on a laptop would look for it is
   the wrong trade. Above 1440 nothing changes. */
@media (min-width: 1024px) and (max-width: 1440px) {
  .primary-nav__list { gap: 0; }
  .primary-nav__list > li > a { padding-inline: 0.55rem; }
  .header-actions { gap: 0.3rem; }
  .dropdown__btn { padding-inline: 0.45rem; gap: 0.25rem; }
}


/* ── The footer on a phone ──────────────────────────────────────────────────
   `repeat(auto-fit, minmax(min(100%, 10rem), 1fr))` fits two 165px columns
   onto a 390px screen, and the four blocks are nothing like the same height:
   Languages has three items, Browse has eight. The result was a ragged
   two-column grid with roughly five hundred pixels of nothing beside the
   language list, and the brand paragraph broken mid-phrase down a column too
   narrow to read Chinese in — 为教会、宣教士与非营利事 / 工提供… splits a word
   across lines, which a Latin measure would never do.

   One column below 640px. Vertical space in a footer is free and scrolling is
   what a thumb is already doing; a cramped grid is not a saving. */
@media (max-width: 640px) {
  .site-footer__grid { grid-template-columns: 1fr; gap: 2rem; }
  .site-footer__about { max-width: none; }
}

/* The fixed bottom bar is 59px tall including its safe-area inset, and the
   body reserved 56 — so the last three pixels of the footer sat under it and
   could never be scrolled into view. Reserve the bar's real height, and add
   the inset rather than assuming a notch is zero. */
@media (max-width: 900px) {
  body:has(.bottom-nav) {
    padding-bottom: calc(60px + env(safe-area-inset-bottom, 0px));
  }
}

/* ── The enquiry's saved-draft line ─────────────────────────────────────────
   Quiet, above the submit button, and only visible once something has been
   saved. It is there because a form that remembers silently is unsettling and
   because a visitor who does not know their answers are kept has no reason to
   stop and come back — which is the whole point of keeping them. */
.brief__draft {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.5rem 1rem;
  margin-bottom: 1rem;
  font-size: var(--step--1);
  color: var(--text-subtle);
}
.brief__saved::before {
  content: "";
  display: inline-block;
  width: 0.5rem;
  height: 0.5rem;
  margin-inline-end: 0.45rem;
  border-radius: 50%;
  background: var(--accent);
  vertical-align: middle;
}
.link-button {
  display: inline-flex;
  align-items: center;
  min-height: 44px;
  padding: 0;
  border: 0;
  background: none;
  color: var(--accent);
  font: inherit;
  font-size: var(--step--1);
  text-decoration: underline;
  text-underline-offset: 3px;
  cursor: pointer;
}
.link-button:hover { color: var(--text); }
.lede--sm { font-size: var(--step--1); color: var(--text-subtle); margin-top: 0.6rem; }

/* ── The footer mailing list ────────────────────────────────────────────────
   Above the sitemap because it is the only thing in this footer a visitor can
   act on. Two columns on a desktop, stacked on a phone — the same 640px
   breakpoint the rest of the footer uses, for the same reason: a 165px column
   is not wide enough to read Chinese in. */
.site-footer__signup {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(0, 1.05fr);
  gap: clamp(1.25rem, 4vw, 3rem);
  align-items: center;
  padding-bottom: clamp(2rem, 4vw, 3rem);
  margin-bottom: clamp(2rem, 4vw, 3rem);
  border-bottom: 1px solid var(--line-on-ink);
}
@media (max-width: 640px) { .site-footer__signup { grid-template-columns: 1fr; } }
.site-footer__signup p { color: var(--text-on-ink-muted); font-size: var(--step--1); line-height: 1.7; }
.subscribe-form { display: flex; flex-wrap: wrap; gap: 0.5rem; }
.subscribe-form .input {
  flex: 1 1 14rem;
  min-height: 48px;
  background: rgba(255, 255, 255, 0.07);
  border-color: var(--line-on-ink);
  /* The token, not the hex. check-tokens caught this immediately: a literal
     that happens to match one theme's ink text is a literal that is wrong in
     the other seven. */
  color: var(--text-on-ink);
}
.subscribe-form .input::placeholder { color: var(--text-on-ink-muted); }
.subscribe-form__note {
  flex: 1 0 100%;
  margin: 0;
  font-size: var(--step--2);
  color: var(--text-on-ink-muted);
}

/* ═══════════════════════════════════════════════════════════════════════════
   SACRED TECHNOLOGY — the light-and-water layer
   ═══════════════════════════════════════════════════════════════════════════

   Five reusable original backgrounds, from Ray's §35. All CSS and inline SVG:
   no WebGL, no downloaded video, no Lottie. The brief asks for that order
   explicitly (CSS gradient → SVG filter → SVG path → minimal canvas) and it is
   also the only order that survives the performance budget in §18 — the design
   target has always been a phone on a slow connection.

   **Everything here is structure, not motion.** The nodes, labels, rules and
   layout below render and read exactly the same with `motion.css` deleted;
   what that file adds is the travelling light and the sequenced glow. Same
   division as the rest of the site: `site.js` switches themes and opens the
   menu, `motion.js` decorates, and none of the first may ever depend on the
   second. A visitor with `prefers-reduced-motion` sees a complete, legible
   diagram — which is what §35 means by "动画内容必须可访问".

   Every colour is a token. --spring is fill-only (2.34:1 on ivory); anything
   that has to be read uses --spring-text. See the token block for why.
   ─────────────────────────────────────────────────────────────────────────── */

/* ── 1. LightSpringBackground ─────────────────────────────────────────────
   Light through moving water. Three offset radial washes plus a very low
   conic sheen, so the "surface" reads as depth rather than as a flat tint.
   Painted on a ::before at z-index 0 with the content raised above it, so it
   never intercepts a click and never needs its own stacking context. */
.spring-bg { position: relative; isolation: isolate; }
.spring-bg > * { position: relative; z-index: 1; }
.spring-bg::before {
  content: "";
  position: absolute;
  inset: 0;
  z-index: 0;
  pointer-events: none;
  background:
    radial-gradient(42% 58% at 12% 8%, var(--spring-soft), transparent 62%),
    radial-gradient(38% 46% at 88% 22%, var(--accent-soft), transparent 60%),
    radial-gradient(64% 52% at 50% 108%, var(--spring-soft), transparent 66%);
}

/* The ripple itself: three concentric arcs, drawn not downloaded. Sized in em
   so one rule serves every context it is dropped into. */
.spring-ripple {
  position: absolute;
  width: 26em; height: 26em;
  border-radius: 50%;
  pointer-events: none;
  opacity: 0.5;
  background:
    radial-gradient(circle, transparent 38%, var(--spring-glow) 39%, transparent 41%),
    radial-gradient(circle, transparent 56%, var(--spring-glow) 57%, transparent 59%),
    radial-gradient(circle, transparent 74%, var(--spring-glow) 75%, transparent 77%);
}

/* ── 2. SubtleGridBackground ──────────────────────────────────────────────
   A technical surface that is not a developer console. Two hairline gradients
   at 4.5rem plus a dot lattice, all at low alpha and faded out at the edges by
   a mask so it never ends in a hard line. */
.grid-bg { position: relative; isolation: isolate; }
.grid-bg > * { position: relative; z-index: 1; }
.grid-bg::before {
  content: "";
  position: absolute;
  inset: 0;
  z-index: 0;
  pointer-events: none;
  opacity: 0.5;
  background-image:
    linear-gradient(to right, var(--spring-line) 1px, transparent 1px),
    linear-gradient(to bottom, var(--spring-line) 1px, transparent 1px);
  background-size: 4.5rem 4.5rem;
  /* Only the alpha of a mask is read, so the colour is arbitrary — but a
     literal here is still a literal, and check-tokens cannot know the
     difference. A token keeps the rule true in all eight themes for free. */
  -webkit-mask-image: radial-gradient(72% 62% at 50% 42%, var(--text) 30%, transparent 78%);
          mask-image: radial-gradient(72% 62% at 50% 42%, var(--text) 30%, transparent 78%);
}

/* ── 3. FlowLinesBackground / the workflow itself ─────────────────────────
   §33C is explicit that this must be real HTML, CSS and SVG rather than a
   picture of a diagram — "不要把流程做成一张不能响应的静态图片" — because a
   picture cannot reflow onto a 360px phone, cannot be read by a screen
   reader, and cannot be translated. So each stage is a real element carrying
   real translated text, and the connectors are drawn between them.

   Desktop: a vertical spine with the stages down it.
   Phone: the same list, tighter, still vertical. It never scrolls sideways,
   which is the failure mode §5 calls out by name. */
.workflow {
  display: grid;
  gap: 0.5rem;
  margin: 0;
  padding: 0;
  list-style: none;
}
.workflow__step {
  position: relative;
  display: grid;
  grid-template-columns: 2.25rem 1fr;
  align-items: start;
  gap: 0.85rem;
  padding: 0.7rem 0.9rem 0.7rem 0;
}
/* The connector. A pseudo-element on each step except the last, so adding or
   removing a stage in the CMS cannot leave a line pointing at nothing. */
.workflow__step:not(:last-child)::before {
  content: "";
  position: absolute;
  left: 1.125rem;
  top: 2.5rem;
  bottom: -0.5rem;
  width: 1px;
  background: linear-gradient(to bottom, var(--spring-line), transparent);
}
.workflow__dot {
  position: relative;
  width: 2.25rem; height: 2.25rem;
  border-radius: 50%;
  display: grid;
  place-items: center;
  background: var(--surface);
  border: 1px solid var(--spring-line);
  color: var(--spring-text);
  font-size: var(--step--2);
  font-variant-numeric: tabular-nums;
  flex: none;
}
.workflow__dot svg { width: 1rem; height: 1rem; }
/* The two stages that are not machine work. §7 requires the human review step
   to be visible in the flow rather than implied, and the gold marks it as the
   one a person owns. */
.workflow__step--human .workflow__dot {
  border-color: var(--accent-line);
  color: var(--accent);
  background: var(--accent-soft);
}
.workflow__label {
  font-size: var(--step--1);
  font-weight: 600;
  color: var(--text);
  line-height: 1.45;
}
.workflow__note {
  display: block;
  margin-top: 0.15rem;
  font-size: var(--step--2);
  font-weight: 400;
  color: var(--text-muted);
  line-height: 1.55;
}

/* The card the workflow sits in. Deliberately not another rounded box with a
   big shadow — §14 bans that — so it is a hairline, a soft wash and 20px. */
.workflow-panel {
  position: relative;
  overflow: hidden;
  border-radius: 20px;
  border: 1px solid var(--line);
  background:
    linear-gradient(180deg, var(--spring-soft), transparent 42%),
    var(--surface);
  padding: 1.35rem 1.25rem;
}
.workflow-panel__head {
  display: flex;
  /* `baseline`, which this was, misaligned the row by 3px the moment the
     Burmese title wrapped — ui-audit caught it on /my/ai-automation and
     nowhere else, because Burmese is the only language whose title needs two
     lines here. Two items of different font sizes have different baseline
     offsets, so their centres never coincide; the eye forgives that on one
     line and stops forgiving it on two.

     Exactly the breadcrumb fault recorded in CLAUDE.md, and the fix is the one
     recorded with it: align the row, do not give each element its own height.
     `wrap` is the other half — at a narrow width the meta drops to its own
     line rather than squeezing the title into a column two characters wide. */
  align-items: center;
  flex-wrap: wrap;
  justify-content: space-between;
  gap: 0.4rem 0.75rem;
  margin-bottom: 0.9rem;
  padding-bottom: 0.75rem;
  border-bottom: 1px solid var(--line);
}
.workflow-panel__title {
  margin: 0;
  font-size: var(--step--1);
  font-weight: 600;
  color: var(--text-strong);
}
.workflow-panel__meta {
  font-size: var(--step--2);
  color: var(--spring-text);
  letter-spacing: var(--track-label);
  text-transform: none;
}

/* ── 4. LanguageStream ────────────────────────────────────────────────────
   The three languages moving through the pipeline. Set in each language's own
   script, because a row that says "ZH / EN / MY" demonstrates nothing —
   §4 makes the same point about the switcher. Static and readable without
   motion.css; that file makes them drift. */
.lang-stream {
  display: flex;
  flex-wrap: wrap;
  gap: 0.4rem;
  margin-top: 0.85rem;
}
.lang-chip {
  font-size: var(--step--2);
  line-height: 1;
  padding: 0.4rem 0.6rem;
  border-radius: 8px;
  border: 1px solid var(--spring-line);
  background: var(--spring-soft);
  color: var(--spring-text);
  white-space: nowrap;
}
.lang-chip--pending {
  border-style: dashed;
  background: transparent;
  color: var(--text-muted);
}

/* ── 5. RippleCTA ─────────────────────────────────────────────────────────
   One short ripple on press. The keyframes live in motion.css; without it the
   button is simply a button, which is the point. */
.ripple-cta { position: relative; overflow: hidden; }

/* ── Value pillars (§6) ───────────────────────────────────────────────────
   Three cards, each with a drawn mark rather than a stock line icon. §6 asks
   for "高级、简洁的视觉卡片，不使用通用线性图标堆砌". */
.pillar {
  position: relative;
  display: flex;
  flex-direction: column;
  gap: 0.6rem;
  padding: 1.5rem 1.35rem;
  border-radius: 16px;
  border: 1px solid var(--line);
  background: var(--surface);
  overflow: hidden;
}
.pillar::after {
  content: "";
  position: absolute;
  inset: auto -30% -46% auto;
  width: 13rem; height: 13rem;
  border-radius: 50%;
  background: radial-gradient(circle, var(--spring-soft), transparent 68%);
  pointer-events: none;
}
.pillar__mark {
  width: 2.75rem; height: 2.75rem;
  color: var(--spring-text);
}
.pillar__mark svg { width: 100%; height: 100%; }
.pillar h3 { margin: 0; font-size: var(--step-1); line-height: 1.3; }
.pillar p { margin: 0; color: var(--text-muted); line-height: 1.7; }

/* ── Trust bar (§5) ───────────────────────────────────────────────────────
   The five facts under the hero. A list, not five more cards. */
.trust-bar {
  display: flex;
  flex-wrap: wrap;
  gap: 0.5rem 1.4rem;
  margin: 1.4rem 0 0;
  padding: 0;
  list-style: none;
}
.trust-bar li {
  display: flex;
  align-items: center;
  gap: 0.45rem;
  font-size: var(--step--1);
  color: var(--text-muted);
}
.trust-bar li::before {
  content: "";
  width: 0.4rem; height: 0.4rem;
  border-radius: 50%;
  background: var(--spring);
  flex: none;
}

/* The headline in the split layout.

   §5's Chinese headline is 24 characters. At --step-4 in a 1.05fr column that
   set FIVE lines on a 1440px screen and pushed the workflow — the thing the
   hero exists to show — most of the way below the fold. CJK has no spaces to
   break on and no hyphenation, so a headline that fits in English wraps hard
   in Chinese and harder in Burmese; sizing the split hero off the English is
   how it ends up wrong in the two languages most of these ministries read.

   One step down, and the ceiling is a rem value rather than a viewport unit so
   it cannot keep growing on a wide monitor while the column beside it does not.
   Measured after: three lines in Chinese, three in English, four in Burmese. */
.hero--split .hero__title {
  font-size: clamp(1.9rem, 1.2rem + 2.1vw, 3rem);
  line-height: 1.22;
  max-width: 16ch;
}
.hero--split .hero__lede { font-size: var(--step-0); max-width: 32rem; }
.hero--split .hero__note ul {
  display: flex;
  flex-wrap: wrap;
  gap: 0.4rem 1.3rem;
  margin: 0;
  padding: 0;
  list-style: none;
}
.hero--split .hero__note li { display: flex; align-items: center; gap: 0.4rem; }
.hero--split .hero__note li::before {
  content: "";
  width: 0.35rem; height: 0.35rem;
  border-radius: 50%;
  background: var(--spring);
  flex: none;
}

/* ── Hero split (§5) ──────────────────────────────────────────────────────
   Text left, workflow right. Collapses to one column well before the phone,
   because the workflow needs its full width to stay legible in Burmese —
   which wraps longer than either other language. */
.hero--split .hero__inner {
  display: grid;
  grid-template-columns: minmax(0, 1.05fr) minmax(0, 0.95fr);
  gap: clamp(1.75rem, 4vw, 3.25rem);
  align-items: center;
}
@media (max-width: 900px) {
  .hero--split .hero__inner { grid-template-columns: 1fr; }
  .workflow-panel { order: 2; }
}

/* ── Feature list ─────────────────────────────────────────────────────────
   What a plan or a service kind includes. Was a three-column grid of cards,
   one per feature, each containing a tick and four words — eleven boxes of
   mostly empty space that a phone user had to scroll past. Ray's word for it
   was 奇丑无比.

   A card is a promise that something can be opened. None of these went
   anywhere, so the card was drawing a box around a sentence and charging a
   screenful for it. This is the same information at about a fifth of the
   height.

   Two columns from 40rem, one below it — not `auto-fit`, which is what put a
   hole in the mobile footer once when the columns had unequal content. */
.feature-list {
  display: grid;
  gap: 0.15rem 2rem;
  margin: 0;
  padding: 0;
  list-style: none;
}
@media (min-width: 40rem) { .feature-list { grid-template-columns: 1fr 1fr; } }

.feature-list li {
  display: flex;
  align-items: flex-start;
  gap: 0.7rem;
  padding: 0.6rem 0;
  border-bottom: 1px solid var(--line);
  line-height: 1.6;
}
/* The last row of each column, not just the last item — otherwise the left
   column keeps a rule the right one has already dropped. */
.feature-list li:last-child,
.feature-list li:nth-last-child(2):nth-child(odd) { border-bottom: 0; }
@media (max-width: 39.99rem) {
  .feature-list li:nth-last-child(2):nth-child(odd) { border-bottom: 1px solid var(--line); }
  .feature-list li:last-child { border-bottom: 0; }
}

.feature-list__tick {
  flex: none;
  width: 1.15rem;
  height: 1.15rem;
  margin-top: 0.22rem;
  color: var(--spring-text);
}
.feature-list__tick svg { width: 100%; height: 100%; }
