/* ==========================================================================
   FreeYantra — the "Signal" design system
   --------------------------------------------------------------------------
   Hand-authored. No Tailwind, no build step, no Node: edit this file and
   refresh. Organised so a new tool page needs no new CSS, only these classes.

     1. Fonts
     2. Tokens
     3. Base
     4. Layout helpers
     5. Header / nav
     6. Footer
     7. Buttons
     8. Panels, cards, badges
     9. Forms
    10. Hero
    11. Sections (split, pillars, marquee, integrations, quote, CTA)
    12. Tool page
    13. Animations (waveform, reveal, marquee, spinner)
    14. Utilities
    15. Responsive
   ========================================================================== */

/* -------------------------------------------------------------- 1. fonts -- */

@import url('https://fonts.googleapis.com/css2?family=Space+Grotesk:wght@300;400;700&family=Bebas+Neue&family=Inter:wght@400;500&display=swap');

/* ------------------------------------------------------------- 2. tokens -- */

/*
 * THEMES
 * --------------------------------------------------------------------------
 * Light ("Lilac Pop") is the default and lives in :root.
 * Dark ("Aurora Night") is a single override block on [data-theme="dark"].
 *
 * Every colour in this stylesheet comes from a token below — there are no
 * literal colours in the component rules. That is what makes a theme swap a
 * change to these two blocks and nothing else, and it is worth preserving:
 * a hard-coded rgba() in a component is invisible on one theme and broken on
 * the other.
 *
 * LILAC POP (light) and AURORA NIGHT (dark), since 2026-10-10 — chosen to
 * suit Gen-Z visitors as well as engineers, replacing Daylight/Midnight Blue.
 * The accent is a DIFFERENT colour per theme on purpose: violet #6d28d9 is
 * 6.4:1 on the lavender page; on navy-violet it would vanish, so dark uses
 * periwinkle #8b9cff (7.3:1) with dark text on it. Aurora Night is softer than
 * the near-black it replaced, which read as too dark.
 */

:root {
  /* Surfaces */
  --ink: #f5f2ff;          /* page background   */
  --ink-panel: #ffffff;    /* cards, alt bands  */
  --ink-raised: #ece6ff;   /* buttons, inputs   */
  --ink-line: #e9e2fc;     /* section borders   */
  --ink-edge: #dcd2f8;     /* card borders      */
  --ink-rule: #a596d4;     /* dividers          */

  /* Accent — the only one */
  --accent: #6d28d9;
  --accent-hi: #5b21b6;
  --accent-soft: rgba(109, 40, 217, 0.09);  /* tinted fills, alert backgrounds */
  --on-accent: #ffffff;                   /* text sitting ON the accent      */

  /* Text */
  --warm: #1a1033;
  --warm-dim: #3a2d5c;
  --warm-mute: #4f4570;
  --warm-faint: #675e8a;
  --warm-ghost: #a79fc4;

  /* Theme-dependent effects */
  --scrim-1: rgba(245, 242, 255, 0.95);   /* hero gradient, opaque end  */
  --scrim-2: rgba(245, 242, 255, 0.75);
  --scrim-0: rgba(245, 242, 255, 0);      /* …fading to nothing         */
  --header-bg: rgba(255, 255, 255, 0.92);
  --menu-shadow: 0 20px 50px rgba(26, 16, 51, 0.14);
  --panel-shadow: 0 1px 3px rgba(26, 16, 51, 0.06);
  --scroll-thumb: #d6ccf5;
  --art-bg: #f0ebff;                      /* generated artwork backdrop */
  /* The disc a third-party logo sits on. Deliberately the SAME value in both
     themes: an institutional crest is drawn for white paper, and recolouring
     someone else's mark to suit a dark page is not ours to do. It is a token
     rather than a literal so the rule "no literal colours in components" holds
     with no exception to remember. */
  --logo-plate: #ffffff;
  --player-filter: none;                  /* native <audio> needs no tint */
  --danger: #b91c1c;
  --danger-soft: rgba(185, 28, 28, 0.08);
  --select-arrow: url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='8'%3E%3Cpath fill='%231a56c4' d='M1 1l5 5 5-5'/%3E%3C/svg%3E");

  /* Type */
  --font-display: 'Space Grotesk', system-ui, -apple-system, sans-serif;
  --font-num: 'Bebas Neue', Impact, sans-serif;
  --font-ui: Inter, system-ui, -apple-system, sans-serif;
  /* No webfont: code should render in whatever the reader's editor uses, and a
     fourth face to download for the guide pages alone is not worth it. */
  --font-mono: ui-monospace, 'SF Mono', Menlo, Consolas, 'Liberation Mono', monospace;

  /* Rhythm */
  --header-h: 64px;
  --gutter: clamp(24px, 6vw, 96px);

  color-scheme: light;
}

[data-theme="dark"] {
  --ink: #101322;
  --ink-panel: #191d31;
  --ink-raised: #22273f;
  --ink-line: #262b45;
  --ink-edge: #2c3150;
  --ink-rule: #454b70;

  --accent: #8b9cff;
  --accent-hi: #a5b2ff;
  --accent-soft: rgba(139, 156, 255, 0.14);
  --on-accent: #0e1020;

  --warm: #eceaff;
  --warm-dim: #cbc8e6;
  --warm-mute: #a8a6c8;
  --warm-faint: #9290b5;
  --warm-ghost: #5f5d82;

  --scrim-1: rgba(16, 19, 34, 0.94);
  --scrim-2: rgba(16, 19, 34, 0.68);
  --scrim-0: rgba(16, 19, 34, 0);
  --header-bg: rgba(16, 19, 34, 0.95);
  --menu-shadow: 0 24px 60px rgba(0, 0, 0, 0.5);
  --panel-shadow: none;
  --scroll-thumb: #333959;
  --art-bg: #141829;
  --logo-plate: #ffffff;                  /* see the note in :root */
  --player-filter: invert(1) hue-rotate(180deg) saturate(0.6);
  --danger: #f87171;
  --danger-soft: rgba(239, 68, 68, 0.1);
  --select-arrow: url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='8'%3E%3Cpath fill='%234b91f7' d='M1 1l5 5 5-5'/%3E%3C/svg%3E");

  color-scheme: dark;
}

/* --------------------------------------------------------------- 3. base -- */

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

html {
  scroll-behavior: smooth;
}

body {
  margin: 0;
  min-height: 100vh;
  display: flex;
  flex-direction: column;
  background: var(--ink);
  color: var(--warm);
  font-family: var(--font-display);
  font-weight: 300;
  font-size: 16px;
  line-height: 1.6;
  -webkit-font-smoothing: antialiased;
}

main {
  flex: 1 0 auto;
}

h1,
h2,
h3 {
  margin: 0;
  font-weight: 700;
  line-height: 1.15;
  letter-spacing: -0.02em;
}

p {
  margin: 0;
}

a {
  color: var(--accent);
  text-decoration: none;
}

a:hover {
  text-decoration: underline;
}

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

::selection {
  background: var(--accent);
  color: var(--on-accent);
}

:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
}

/* Scrollbar, tinted to match */
::-webkit-scrollbar {
  width: 10px;
  height: 10px;
}
::-webkit-scrollbar-track {
  background: var(--ink);
}
::-webkit-scrollbar-thumb {
  background: var(--scroll-thumb);
  border-radius: 99px;
}
::-webkit-scrollbar-thumb:hover {
  background: var(--accent);
}

/* ---------------------------------------------------- 4. layout helpers -- */

/* The fluid horizontal padding used by every section on the site. */
.section {
  padding-left: var(--gutter);
  padding-right: var(--gutter);
}

.section-pad {
  padding-top: 80px;
  padding-bottom: 80px;
}

.grid {
  display: grid;
  gap: 24px;
}

.grid-3 {
  grid-template-columns: repeat(3, 1fr);
}

.grid-2 {
  grid-template-columns: repeat(2, 1fr);
}

/* Small uppercase accent eyebrow above section headlines. */
.kicker {
  font-family: var(--font-ui);
  font-size: 12px;
  font-weight: 400;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--accent);
}

/* Bebas Neue section label, e.g. "THE CATALOGUE", "RESULT". */
.label {
  font-family: var(--font-num);
  font-size: 16px;
  letter-spacing: 0.1em;
  color: var(--accent);
}

.label-mute {
  color: var(--warm-ghost);
}

.section-title {
  font-size: clamp(28px, 4vw, 52px);
  color: var(--warm);
}

.lead {
  font-size: 17px;
  line-height: 1.65;
  color: var(--warm-mute);
  max-width: 560px;
}

/* ----------------------------------------------------- 5. header / nav --- */

.site-header {
  position: sticky;
  top: 0;
  z-index: 50;
  height: var(--header-h);
  display: flex;
  align-items: center;
  background: var(--header-bg);
  backdrop-filter: blur(12px);
  border-bottom: 1px solid var(--ink-line);
  /* Slides down on load — see @keyframes drop-in */
  animation: drop-in 0.5s ease-out;
}

.site-header > .section {
  display: flex;
  align-items: center;
  gap: 20px;
  width: 100%;
}

/* Theme toggle — first item in the header, top-left */
.theme-toggle {
  display: grid;
  place-items: center;
  flex-shrink: 0;
  width: 34px;
  height: 34px;
  padding: 0;
  border: 1px solid var(--ink-edge);
  border-radius: 8px;
  background: var(--ink-panel);
  color: var(--warm-faint);
  cursor: pointer;
  transition: color 0.15s ease, border-color 0.15s ease, background 0.15s ease;
}

.theme-toggle:hover {
  color: var(--accent);
  border-color: var(--accent);
  background: var(--accent-soft);
}

/* Both icons ship; CSS picks the one that matches the *other* theme, because
   the button's job is to say where it will take you. */
.theme-toggle .icon-sun { display: none; }
.theme-toggle .icon-moon { display: block; }
[data-theme="dark"] .theme-toggle .icon-sun { display: block; }
[data-theme="dark"] .theme-toggle .icon-moon { display: none; }

/* The public visitor total, beside the theme toggle (2026-10-11). Same height
   and border as the toggle so the two read as one control group. */
.visitor-count {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  flex-shrink: 0;
  height: 34px;
  padding: 0 12px;
  margin-left: -8px;                   /* sit closer to the toggle than the brand does */
  border: 1px solid var(--ink-edge);
  border-radius: 999px;
  background: var(--ink-panel);
  font-family: var(--font-ui);
  font-size: 13px;
  color: var(--warm-mute);
  white-space: nowrap;
  cursor: default;
}

.visitor-count svg {
  color: var(--accent);
}

.visitor-count-num {
  font-weight: 600;
  color: var(--warm);
  font-variant-numeric: tabular-nums;
}

/* Where the header is tight, keep the number and the icon; the word stays for
   screen readers. */
@media (max-width: 1279px) {
  .visitor-count-label {
    position: absolute;
    width: 1px;
    height: 1px;
    overflow: hidden;
    clip: rect(0 0 0 0);
    white-space: nowrap;
  }

  .visitor-count {
    padding: 0 10px;
  }
}

.brand {
  display: flex;
  align-items: center;
  gap: 12px;
  flex-shrink: 0;
  color: var(--warm);
}

.brand:hover {
  text-decoration: none;
}

.brand-name {
  font-family: var(--font-display);
  font-weight: 700;
  font-size: 18px;
  letter-spacing: -0.02em;
}

.brand-divider {
  width: 1px;
  height: 16px;
  background: var(--ink-rule);
}

.brand-note {
  font-family: var(--font-ui);
  font-size: 13px;
  color: var(--warm-faint);
}

.nav {
  margin-left: auto;
  display: flex;
  align-items: center;
  gap: 28px;
}

.nav a,
.nav button {
  white-space: nowrap;                 /* a wrapped label doubles the header height */
  font-family: var(--font-ui);
  font-size: 14px;
  color: var(--warm-dim);
  background: none;
  border: 0;
  padding: 0;
  cursor: pointer;
  transition: color 0.15s ease;
}

.nav a:hover,
.nav button:hover {
  color: var(--warm);
  text-decoration: none;
}

.nav a.is-active {
  color: var(--accent);
}

/* Tools dropdown */
.dropdown {
  position: relative;
}

.dropdown-toggle {
  display: flex;
  align-items: center;
  gap: 5px;
}

.dropdown-toggle svg {
  transition: transform 0.2s ease;
}

.dropdown.is-open .dropdown-toggle svg {
  transform: rotate(180deg);
}

.dropdown-menu {
  position: absolute;
  right: 0;
  top: calc(100% + 14px);
  width: 320px;
  padding: 8px;
  background: var(--ink-panel);
  border: 1px solid var(--ink-edge);
  border-radius: 12px;
  box-shadow: var(--menu-shadow);
  display: none;
}

.dropdown.is-open .dropdown-menu {
  display: block;
  animation: fade-down 0.15s ease-out;
}

.dropdown-item {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  padding: 10px 12px;
  border-radius: 8px;
  transition: background 0.15s ease;
}

.dropdown-item:hover {
  background: var(--ink-raised);
  text-decoration: none;
}

.dropdown-item .name {
  display: block;
  font-family: var(--font-ui);
  font-size: 14px;
  color: var(--warm);
}

.dropdown-item .sub {
  display: block;
  font-family: var(--font-ui);
  font-size: 12px;
  color: var(--warm-faint);
}

.dropdown-item .live {
  font-family: var(--font-num);
  font-size: 13px;
  letter-spacing: 0.08em;
  color: var(--accent);
}

/* .dropdown-item.is-disabled was deleted on 2026-10-06 with the unbuilt tools.
   Nothing in the dropdown is disabled now — every tool in the catalogue has a
   page. See Models/ToolCatalogue.cs. */

.dropdown-sep {
  height: 1px;
  margin: 6px 4px;
  background: var(--ink-edge);
}

.dropdown-all {
  display: block;
  padding: 10px 12px;
  border-radius: 8px;
  font-family: var(--font-ui);
  font-size: 14px;
  color: var(--accent);
}

.dropdown-all:hover {
  background: var(--ink-raised);
  text-decoration: none;
}

/* Mobile menu */
.nav-toggle {
  display: none;
  margin-left: auto;
  background: none;
  border: 0;
  color: var(--warm);
  cursor: pointer;
  padding: 4px;
}

.mobile-nav {
  display: none;
  border-top: 1px solid var(--ink-line);
  background: var(--ink-panel);
  padding-top: 16px;
  padding-bottom: 20px;
}

.mobile-nav.is-open {
  display: block;
  animation: fade-down 0.2s ease-out;
}

.mobile-nav a,
.mobile-nav span {
  display: block;
  padding: 8px 0;
  font-family: var(--font-ui);
  font-size: 15px;
  color: var(--warm);
}

.mobile-nav .muted {
  color: var(--warm-ghost);
}

/* `.mobile-nav span` (0,1,1) outranks `.kicker` (0,1,0), which was quietly
   stripping the accent colour off the sheet's "Tools" label. */
.mobile-nav span.kicker {
  color: var(--accent);
}

.mobile-nav .sep {
  height: 1px;
  margin: 12px 0;
  background: var(--ink-edge);
}

/* ------------------------------------------------------------ 6. footer -- */

.site-footer {
  flex-shrink: 0;
  border-top: 1px solid var(--ink-line);
  background: var(--ink);
  padding-top: 48px;
  padding-bottom: 32px;
}

.footer-grid {
  display: grid;
  grid-template-columns: 1.6fr 1fr 1fr 1fr;
  gap: 40px;
}

.site-footer h3 {
  font-family: var(--font-num);
  font-size: 15px;
  font-weight: 400;
  letter-spacing: 0.08em;
  color: var(--warm-faint);
  margin-bottom: 12px;
}

.site-footer ul {
  list-style: none;
  margin: 0;
  padding: 0;
}

.site-footer li {
  margin-bottom: 8px;
  font-family: var(--font-ui);
  font-size: 14px;
  color: var(--ink-rule);
}

.site-footer li a {
  color: var(--warm-faint);
  transition: color 0.15s ease;
}

.site-footer li a:hover {
  color: var(--accent);
  text-decoration: none;
}

.footer-brand-name {
  font-family: var(--font-display);
  font-weight: 700;
  font-size: 16px;
  letter-spacing: -0.02em;
  color: var(--accent);
}

.footer-blurb {
  margin-top: 10px;
  max-width: 34ch;
  font-family: var(--font-ui);
  font-size: 13px;
  line-height: 1.6;
  color: var(--warm-ghost);
}

.footer-bottom {
  margin-top: 40px;
  padding-top: 24px;
  border-top: 1px solid var(--ink-line);
  font-family: var(--font-ui);
  font-size: 12px;
  color: var(--ink-rule);
}

/* ----------------------------------------------------------- 7. buttons -- */

.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 8px;
  padding: 11px 20px;
  border: 1px solid transparent;
  border-radius: 999px;                /* pills — part of Lilac Pop (2026-10-10) */
  font-family: var(--font-ui);
  font-size: 14px;
  line-height: 1.2;
  cursor: pointer;
  white-space: nowrap;
  transition: background 0.2s ease, border-color 0.2s ease, transform 0.2s ease;
}

.btn:hover {
  text-decoration: none;
}

.btn:disabled {
  opacity: 0.5;
  cursor: not-allowed;
}

.btn-accent {
  background: var(--accent);
  color: var(--on-accent);
}

.btn-accent:hover:not(:disabled) {
  background: var(--accent-hi);
}

.btn-outline {
  background: transparent;
  border-color: var(--ink-rule);
  color: var(--warm);
}

.btn-outline:hover:not(:disabled) {
  border-color: var(--warm-faint);
}

/* Used only on the accent CTA band, so it inverts against the accent
   rather than against the page. */
.btn-dark {
  background: var(--on-accent);
  color: var(--accent);
}

.btn-dark:hover:not(:disabled) {
  background: color-mix(in srgb, var(--on-accent) 88%, var(--accent));
}

.btn-lg {
  padding: 14px 28px;
  font-size: 15px;
}

.btn-plain {
  background: none;
  border: 0;
  padding: 0;
  font-family: var(--font-ui);
  font-size: 14px;
  color: var(--warm-faint);
  cursor: pointer;
  transition: color 0.15s ease;
}

.btn-plain:hover {
  color: var(--warm);
}

.btn-row {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 12px;
}

/* -------------------------------------------- 8. panels, cards, badges --- */

.panel {
  background: var(--ink-panel);
  border: 1px solid var(--ink-edge);
  border-radius: 12px;
  padding: 32px;
  box-shadow: var(--panel-shadow);
}

.panel + .panel {
  margin-top: 24px;
}

.card {
  display: block;
  overflow: hidden;
  background: var(--ink-panel);
  border: 1px solid var(--ink-edge);
  border-radius: 8px;
  color: inherit;
  box-shadow: var(--panel-shadow);
  transition: transform 0.2s ease-out, border-color 0.2s ease-out;
}

a.card:hover {
  transform: scale(1.02);
  border-color: var(--accent);
  text-decoration: none;
}

/* .card.is-soon dimmed the inert tile for an unbuilt tool. Deleted 2026-10-06
   with the tools themselves — every card is now a working link. */

.card-art {
  aspect-ratio: 1 / 1;
  width: 100%;
  background: var(--ink);
}

.card-body {
  padding: 20px;
}

.card-num {
  font-family: var(--font-num);
  font-size: 16px;
  letter-spacing: 0.08em;
  color: var(--accent);
}

.card-title {
  margin-top: 8px;
  font-size: 17px;
  line-height: 1.3;
  color: var(--warm);
}

.card-desc {
  margin-top: 8px;
  font-family: var(--font-ui);
  font-size: 13px;
  line-height: 1.6;
  color: var(--warm-faint);
}

.card-link {
  margin-top: 12px;
  font-family: var(--font-ui);
  font-size: 13px;
  color: var(--accent);
}

.badge {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  font-family: var(--font-ui);
  font-size: 12px;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  white-space: nowrap;
}

.badge .dot {
  width: 8px;
  height: 8px;
  border-radius: 50%;
  background: var(--warm-ghost);
}

.badge.is-online .dot {
  background: var(--accent);
}
.badge.is-online {
  color: var(--warm-faint);
}

.badge.is-offline .dot {
  background: var(--danger);
}
.badge.is-offline {
  color: var(--danger);
}

/* -------------------------------------------------------------- 9. forms -- */

.field {
  margin-bottom: 20px;
}

.field:last-child {
  margin-bottom: 0;
}

.field-row {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 20px;
}

label {
  display: block;
  margin-bottom: 8px;
  font-family: var(--font-ui);
  font-size: 13px;
  color: var(--warm-dim);
}

.control {
  display: block;
  width: 100%;
  padding: 12px 16px;
  background: var(--ink);
  border: 1px solid var(--ink-edge);
  border-radius: 4px;
  font-family: var(--font-display);
  font-weight: 300;
  font-size: 15px;
  color: var(--warm);
  transition: border-color 0.2s ease, box-shadow 0.2s ease;
}

.control::placeholder {
  color: var(--warm-ghost);
}

.control:focus {
  outline: none;
  border-color: var(--accent);
  box-shadow: 0 0 0 2px color-mix(in srgb, var(--accent) 28%, transparent);
}

/*
 * Textareas size themselves from their `rows` attribute — do NOT put a
 * min-height here. A global one was overriding rows="2" on every builder field
 * and turning a six-field form into 1,300px of empty boxes.
 *
 * `field-sizing: content` grows the box with what is typed in browsers that
 * support it; site.js does the same for the rest. Either way the box starts
 * small and only takes the space it needs.
 */
textarea.control {
  resize: vertical;
  line-height: 1.7;
  field-sizing: content;
  max-height: 340px;
}

/* Opt in for the one place a genuinely large input is wanted (TTS). */
textarea.control-lg {
  min-height: 200px;
  max-height: none;
}

/*
 * A three-line floor for fields that genuinely expect several lines — list
 * inputs ("one per line") and anything you paste content into. `field-sizing`
 * ignores the rows attribute, so without this the box opens one line tall and
 * clips a multi-line placeholder before the user has read it.
 */
textarea.control-lines {
  min-height: 92px;
}

select.control {
  appearance: none;
  cursor: pointer;
  padding-right: 40px;
  background-image: var(--select-arrow);
  background-repeat: no-repeat;
  background-position: right 16px center;
}

/* Inverted variant for the amber CTA band */
.control-on-accent {
  display: block;
  width: 100%;
  padding: 14px 20px;
  background: color-mix(in srgb, var(--on-accent) 14%, transparent);
  border: 1px solid color-mix(in srgb, var(--on-accent) 28%, transparent);
  border-radius: 4px;
  font-family: var(--font-ui);
  font-size: 15px;
  color: var(--on-accent);
  transition: border-color 0.2s ease, box-shadow 0.2s ease;
}

.control-on-accent::placeholder {
  color: color-mix(in srgb, var(--on-accent) 72%, transparent);
}

.control-on-accent:focus {
  outline: none;
  border-color: var(--on-accent);
  box-shadow: 0 0 0 2px color-mix(in srgb, var(--on-accent) 30%, transparent);
}

.hint {
  margin-top: 8px;
  font-family: var(--font-ui);
  font-size: 12px;
  color: var(--warm-ghost);
}

.counter {
  display: flex;
  justify-content: space-between;
  gap: 12px;
  margin-top: 8px;
  font-family: var(--font-ui);
  font-size: 12px;
  color: var(--warm-ghost);
}

.counter .over {
  color: var(--danger);
}

/* Status message */
.alert {
  display: flex;
  align-items: flex-start;
  gap: 10px;
  margin-bottom: 24px;
  padding: 12px 16px;
  border-left: 2px solid;
  border-radius: 4px;
  font-family: var(--font-ui);
  font-size: 14px;
  line-height: 1.5;
}

.alert-ok {
  border-color: var(--accent);
  background: var(--accent-soft);
  color: var(--accent);
}

.alert-error {
  border-color: var(--danger);
  background: var(--danger-soft);
  color: var(--danger);
}

.alert svg {
  flex-shrink: 0;
  margin-top: 2px;
}

/* Placeholder banner on the dummy pages */
.placeholder-note {
  display: flex;
  align-items: center;
  gap: 10px;
  margin-bottom: 32px;
  padding: 12px 16px;
  border-left: 2px solid var(--accent);
  border-radius: 4px;
  background: var(--accent-soft);
  font-family: var(--font-ui);
  font-size: 13px;
  color: var(--accent);
}

/* --------------------------------------------------------------- 10. hero -- */

.hero {
  position: relative;
  overflow: hidden;
  min-height: 100svh;
  background: var(--ink);
}

.hero-backdrop {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
}

/* Composition-independent: darkens the left where the text sits, fades right.
   Swapping the backdrop for a photograph needs no change here. */
.hero-scrim {
  position: absolute;
  inset: 0;
  z-index: 1;
  background: linear-gradient(
    to right,
    var(--scrim-1) 0%,
    var(--scrim-2) 55%,
    var(--scrim-0) 100%
  );
}

.hero-content {
  position: relative;
  z-index: 10;
  display: flex;
  flex-direction: column;
  justify-content: center;
  min-height: 100svh;
  width: 62%;
  padding-top: 96px;
  padding-bottom: 96px;
}

.hero h1 {
  font-size: clamp(40px, 6vw, 104px);
  line-height: 1;
  color: var(--warm);
}

.hero-sub {
  max-width: 460px;
  font-size: 17px;
  line-height: 1.65;
  color: var(--warm-mute);
}

.stats {
  display: flex;
  flex-wrap: wrap;
  align-items: flex-end;
  gap: 24px 40px;
  margin-top: 48px;
}

.stat-value {
  font-family: var(--font-num);
  font-size: 48px;
  line-height: 1;
  color: var(--accent);
}

.stat-label {
  margin-top: 4px;
  font-family: var(--font-ui);
  font-size: 13px;
  color: var(--warm-faint);
}

.stat-text {
  font-family: var(--font-ui);
  font-size: 16px;
  color: var(--accent);
}

/* ---------------------------------------------------------- 11. sections -- */

/* Split: statement left, art panel right */
.split {
  display: grid;
  grid-template-columns: 1fr 1fr;
  min-height: 480px;
  background: var(--ink-panel);
}

.split-body {
  display: flex;
  flex-direction: column;
  justify-content: center;
  padding-top: 64px;
  padding-bottom: 64px;
}

.pullquote {
  margin: 0;
  padding-left: 24px;
  border-left: 3px solid var(--accent);
  font-size: clamp(20px, 2.8vw, 34px);
  font-style: italic;
  font-weight: 300;
  line-height: 1.5;
  color: var(--warm);
}

.name-chip {
  display: inline-block;
  align-self: flex-start;
  margin-top: 20px;
  padding: 4px 12px;
  border-radius: 2px;
  background: var(--accent);
  font-family: var(--font-ui);
  font-size: 12px;
  letter-spacing: 0.08em;
  color: var(--on-accent);
}

.split-bio {
  margin-top: 16px;
  max-width: 52ch;
  font-family: var(--font-ui);
  font-size: 15px;
  line-height: 1.7;
  color: var(--warm-mute);
}

.split-note {
  margin-top: 16px;
  font-family: var(--font-ui);
  font-size: 14px;
  color: var(--accent);
}

.split-art {
  position: relative;
  min-height: 320px;
  overflow: hidden;
  border-left: 1px solid var(--ink-line);
  background:
    radial-gradient(120% 90% at 50% 100%, var(--accent-soft) 0%, transparent 65%),
    var(--art-bg);
}

/* Pillars */
.pillar {
  padding: 32px;
  background: var(--ink-panel);
  border: 1px solid var(--ink-line);
  border-radius: 8px;
}

.pillar svg {
  color: var(--accent);
  margin-bottom: 16px;
}

.pillar h3 {
  font-size: 20px;
  color: var(--warm);
}

.pillar p {
  margin-top: 12px;
  font-size: 15px;
  line-height: 1.65;
  color: var(--warm-mute);
}

/* Marquee */
.marquee {
  overflow: hidden;
  padding: 40px 0;
  background: var(--ink-panel);
  border-top: 1px solid var(--ink-line);
  border-bottom: 1px solid var(--ink-line);
}

.marquee-label {
  margin-bottom: 24px;
  text-align: center;
  font-family: var(--font-ui);
  font-size: 11px;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--warm-ghost);
}

/* The list is rendered twice, so -50% loops seamlessly. */
.marquee-track {
  display: flex;
  width: max-content;
  gap: 64px;
  animation: marquee 45s linear infinite;
  will-change: transform;
}

.marquee:hover .marquee-track {
  animation-play-state: paused;
}

.marquee-track span {
  white-space: nowrap;
  font-family: var(--font-ui);
  font-size: 15px;
  color: var(--warm-ghost);
}

/* Integration buttons */
.integrations {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: 16px;
}

.integration {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 16px 24px;
  background: var(--ink-raised);
  border: 1px solid var(--ink-edge);
  border-radius: 8px;
  font-family: var(--font-ui);
  font-size: 14px;
  color: var(--warm);
  transition: transform 0.2s ease-out, border-color 0.2s ease-out;
}

.integration:hover {
  transform: scale(1.05);
  border-color: var(--accent);
  text-decoration: none;
}

.integration svg {
  color: var(--accent);
}

/* Quote band */
.quote-band {
  background: var(--ink-panel);
  padding-top: 60px;
  padding-bottom: 60px;
  text-align: center;
}

.quote-mark {
  color: var(--accent);
  opacity: 0.3;
  margin: 0 auto;
}

.quote-text {
  margin: 16px auto 0;
  max-width: 640px;
  font-size: clamp(18px, 2.5vw, 28px);
  font-weight: 300;
  line-height: 1.5;
  color: var(--warm);
}

.quote-by {
  margin-top: 16px;
  font-family: var(--font-ui);
  font-size: 14px;
  color: var(--accent);
}

/* Amber email CTA */
.cta {
  background: var(--accent);
  padding-top: 80px;
  padding-bottom: 80px;
  text-align: center;
}

.cta h2 {
  font-size: clamp(30px, 4.5vw, 68px);
  line-height: 1.05;
  color: var(--on-accent);
}

.cta-sub {
  margin: 12px auto 0;
  max-width: 480px;
  font-size: 17px;
  font-weight: 300;
  color: color-mix(in srgb, var(--on-accent) 80%, transparent);
}

.cta-small {
  margin-top: 8px;
  font-family: var(--font-ui);
  font-size: 13px;
  color: color-mix(in srgb, var(--on-accent) 66%, transparent);
}

.cta-form {
  display: flex;
  gap: 8px;
  max-width: 440px;
  margin: 32px auto 0;
}

.cta-fine {
  margin-top: 12px;
  font-family: var(--font-ui);
  font-size: 12px;
  color: color-mix(in srgb, var(--on-accent) 72%, transparent);
}

/* ------------------------------------------------------- 12. tool page --- */

.breadcrumb {
  margin-bottom: 20px;
  font-family: var(--font-ui);
  font-size: 13px;
  color: var(--warm-ghost);
}

.breadcrumb a {
  color: var(--warm-ghost);
}

.breadcrumb a:hover {
  color: var(--accent);
}

.tool-head {
  display: flex;
  flex-wrap: wrap;
  align-items: flex-end;
  justify-content: space-between;
  gap: 24px;
  padding-top: 48px;
  padding-bottom: 48px;
  border-bottom: 1px solid var(--ink-line);
}

.tool-head h1 {
  font-size: clamp(32px, 5vw, 64px);
  line-height: 1.05;
  color: var(--warm);
}

/* Form left, output right */
.tool-grid {
  display: grid;
  grid-template-columns: 1.35fr 1fr;
  gap: 24px;
  align-items: start;
  padding-top: 56px;
  padding-bottom: 56px;
}

.result-empty {
  padding: 40px 20px;
  text-align: center;
  border: 1px dashed var(--ink-edge);
  border-radius: 8px;
}

.result-empty p {
  margin-top: 20px;
  font-family: var(--font-ui);
  font-size: 13px;
  color: var(--warm-ghost);
}

/* Native player, tinted to sit on the dark panel */
audio {
  width: 100%;
  border-radius: 4px;
  filter: var(--player-filter);
}

.meta-list {
  margin: 24px 0 0;
  display: grid;
  gap: 6px;
  font-family: var(--font-ui);
  font-size: 13px;
}

.meta-list div {
  display: flex;
  gap: 8px;
}

.meta-list dt {
  color: var(--warm-ghost);
  flex-shrink: 0;
}

.meta-list dd {
  margin: 0;
  color: var(--warm);
  word-break: break-all;
}

.notes {
  margin: 0;
  padding: 0;
  list-style: none;
  display: grid;
  gap: 12px;
  font-family: var(--font-ui);
  font-size: 13px;
  line-height: 1.6;
  color: var(--warm-faint);
}

.notes strong {
  font-weight: 400;
  color: var(--warm);
}

.prose {
  max-width: 720px;
  display: grid;
  gap: 20px;
  font-size: 16px;
  font-weight: 300;
  line-height: 1.75;
  color: var(--warm-mute);
}

/* --------------------------------------------- 12b. prompt builder tool --- */

/* Small outlined capsules under a page intro ("Context + Objective + Style") */
.pill-row {
  display: flex;
  flex-wrap: wrap;
  gap: 10px;
  margin-top: 20px;
}

.pill {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  padding: 6px 14px;
  border: 1px solid var(--ink-edge);
  border-radius: 99px;
  background: var(--ink-panel);
  font-family: var(--font-ui);
  font-size: 12px;
  color: var(--warm-faint);
}

.pill::before {
  content: '';
  width: 6px;
  height: 6px;
  border-radius: 50%;
  background: var(--accent);
}

/* Technique tabs */
.tabs {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
  padding-bottom: 24px;
}

.tab {
  padding: 10px 18px;
  border: 1px solid var(--ink-edge);
  border-radius: 6px;
  background: var(--ink-panel);
  font-family: var(--font-ui);
  font-size: 14px;
  color: var(--warm-dim);
  cursor: pointer;
  white-space: nowrap;
  transition: color 0.2s ease, border-color 0.2s ease, background 0.2s ease;
}

.tab:hover {
  color: var(--warm);
  border-color: var(--warm-ghost);
  text-decoration: none;
}

.tab.is-active {
  background: var(--accent);
  border-color: var(--accent);
  color: var(--on-accent);
}

/* The lettered framework cards (C O S T A R, and the step cards for the
   other techniques). Auto-fit so 6 sit in a row on desktop and wrap down
   gracefully rather than squashing. */
.framework-grid {
  display: grid;
  /* 112px is chosen so all six CO-STAR cards still fit on one row inside a
     panel at ~900px wide, and wrap cleanly below that. */
  grid-template-columns: repeat(auto-fit, minmax(112px, 1fr));
  gap: 14px;
}

.framework-card {
  padding: 12px 10px;
  border: 1px solid var(--ink-edge);
  border-radius: 8px;
  background: var(--ink);
  text-align: center;
  transition: border-color 0.2s ease, transform 0.2s ease;
}

.framework-card:hover {
  border-color: var(--accent);
  transform: translateY(-2px);
}

.framework-letter {
  font-family: var(--font-num);
  font-size: 26px;
  line-height: 1;
  color: var(--accent);
}

.framework-name {
  margin-top: 6px;
  font-family: var(--font-display);
  font-weight: 700;
  font-size: 14px;
  color: var(--warm);
}

.framework-hint {
  margin-top: 6px;
  font-family: var(--font-ui);
  font-size: 11px;
  line-height: 1.45;
  color: var(--warm-ghost);
}

/*
 * Builder beside output on wide screens.
 *
 * The output panel sticks below the header while you fill the form, so the
 * prompt is visible the whole time instead of being a scroll away. Below
 * 1100px it falls back to the stacked order in the original design.
 */
.builder-shell {
  display: grid;
  gap: 24px;
  align-items: start;
}

@media (min-width: 1100px) {
  .builder-shell {
    grid-template-columns: 1.25fr 1fr;
  }

  .builder-shell > .panel:last-child {
    position: sticky;
    top: calc(var(--header-h) + 24px);
  }
}

/* Builder fields, two across on desktop */
.builder-grid {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 20px;
}

/* Inside the narrower left column, one field per row reads better. */
@media (max-width: 1400px) {
  .builder-shell .builder-grid {
    grid-template-columns: 1fr;
  }
}

.builder-grid .field {
  margin-bottom: 0;
}

.builder-grid .field-wide {
  grid-column: 1 / -1;
}

/* Each builder field is labelled with its initial, e.g. "C — CONTEXT" */
.field-key {
  color: var(--accent);
  font-family: var(--font-num);
  font-size: 15px;
  letter-spacing: 0.06em;
  margin-right: 6px;
}

/* Repeating example pairs for the few-shot builder */
.example-row {
  padding: 16px;
  border: 1px solid var(--ink-edge);
  border-radius: 8px;
  background: var(--ink);
  margin-bottom: 12px;
}

.example-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  margin-bottom: 12px;
}

.example-title {
  font-family: var(--font-num);
  font-size: 15px;
  letter-spacing: 0.06em;
  color: var(--accent);
}

.example-remove {
  background: none;
  border: 0;
  padding: 0;
  font-family: var(--font-ui);
  font-size: 12px;
  color: var(--warm-ghost);
  cursor: pointer;
  transition: color 0.15s ease;
}

.example-remove:hover {
  color: var(--danger);
}

/* The assembled prompt: monospace, selectable, amber-edged */
.output-box {
  width: 100%;
  /* Tall enough to be useful, capped so the sticky column always fits on
     screen with its buttons. */
  min-height: 180px;
  max-height: calc(100vh - 320px);
  padding: 18px;
  border: 1px solid color-mix(in srgb, var(--accent) 35%, var(--ink-edge));
  border-radius: 8px;
  background: var(--ink);
  color: var(--warm);
  font-family: ui-monospace, "Cascadia Code", Consolas, monospace;
  font-size: 13px;
  line-height: 1.7;
  white-space: pre-wrap;
  word-break: break-word;
  resize: vertical;
  overflow-y: auto;
}

.output-box:focus {
  outline: none;
  border-color: var(--accent);
  box-shadow: 0 0 0 2px color-mix(in srgb, var(--accent) 28%, transparent);
}

/* Shown instead of the prompt until the first field is filled */
.output-box.is-empty {
  color: var(--warm-ghost);
  font-style: italic;
}

.output-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  flex-wrap: wrap;
  gap: 12px;
  margin-bottom: 16px;
}

/* The token estimate carries the most weight in the stats line, so it gets the
   accent; `cursor: help` hints that the title attribute explains the maths. */
.token-count {
  color: var(--accent);
  font-weight: 500;
  cursor: help;
}

/* ----------------------------------------------- 12d. prompt testing ------ */

/* Generic data table — test matrices, scorecards, exports */
.data-table {
  width: 100%;
  border-collapse: collapse;
  font-family: var(--font-ui);
  font-size: 13px;
}

.data-table th {
  position: sticky;
  top: 0;
  z-index: 1;
  padding: 10px 12px;
  text-align: left;
  font-weight: 500;
  color: var(--accent);
  background: var(--ink-panel);
  border-bottom: 1px solid var(--ink-edge);
  white-space: nowrap;
}

.data-table td {
  padding: 10px 12px;
  border-bottom: 1px solid var(--ink-line);
  color: var(--warm-dim);
  vertical-align: top;
}

.data-table tr:last-child td {
  border-bottom: 0;
}

.data-table .num {
  font-family: var(--font-num);
  font-size: 15px;
  color: var(--accent);
  white-space: nowrap;
}

/* Tables can be wide; let them scroll rather than break the layout. */
.table-scroll {
  max-height: 460px;
  overflow: auto;
  border: 1px solid var(--ink-edge);
  border-radius: 8px;
  background: var(--ink);
}

/* --- word-level diff ---------------------------------------------------- */
.diff-pane {
  padding: 16px;
  border: 1px solid var(--ink-edge);
  border-radius: 8px;
  background: var(--ink);
  font-family: ui-monospace, Consolas, monospace;
  font-size: 13px;
  line-height: 1.85;
  white-space: pre-wrap;
  word-break: break-word;
  max-height: 420px;
  overflow-y: auto;
}

.diff-add {
  background: color-mix(in srgb, var(--accent) 22%, transparent);
  color: var(--warm);
  border-radius: 2px;
  padding: 1px 2px;
}

.diff-del {
  background: color-mix(in srgb, var(--danger) 20%, transparent);
  color: var(--warm-mute);
  text-decoration: line-through;
  border-radius: 2px;
  padding: 1px 2px;
}

.diff-legend {
  display: flex;
  gap: 18px;
  flex-wrap: wrap;
  margin-bottom: 14px;
  font-family: var(--font-ui);
  font-size: 12px;
  color: var(--warm-faint);
}

.diff-legend span::before {
  content: '';
  display: inline-block;
  width: 10px;
  height: 10px;
  border-radius: 2px;
  margin-right: 6px;
  vertical-align: -1px;
}

.diff-legend .add::before { background: color-mix(in srgb, var(--accent) 45%, transparent); }
.diff-legend .del::before { background: color-mix(in srgb, var(--danger) 40%, transparent); }

/* --- saved versions ----------------------------------------------------- */
.version-row {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 10px 12px;
  border: 1px solid var(--ink-edge);
  border-radius: 6px;
  background: var(--ink);
  margin-bottom: 8px;
  font-family: var(--font-ui);
  font-size: 13px;
}

.version-row .name {
  flex: 1;
  color: var(--warm);
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.version-row .when {
  color: var(--warm-ghost);
  font-size: 12px;
  white-space: nowrap;
}

.mini-btn {
  padding: 4px 10px;
  border: 1px solid var(--ink-edge);
  border-radius: 4px;
  background: var(--ink-panel);
  font-family: var(--font-ui);
  font-size: 12px;
  color: var(--warm-dim);
  cursor: pointer;
  white-space: nowrap;
  transition: color 0.15s ease, border-color 0.15s ease;
}

.mini-btn:hover {
  color: var(--accent);
  border-color: var(--accent);
}

.mini-btn.danger:hover {
  color: var(--danger);
  border-color: var(--danger);
}

/* --- checklist items (robustness, hallucination, edge cases) ------------ */
.check-item {
  display: flex;
  align-items: flex-start;
  gap: 12px;
  padding: 14px 16px;
  border: 1px solid var(--ink-edge);
  border-left: 2px solid var(--ink-rule);
  border-radius: 6px;
  background: var(--ink);
  margin-bottom: 10px;
}

.check-item.is-pass { border-left-color: var(--accent); }
.check-item.is-warn { border-left-color: var(--danger); }

.check-icon {
  flex-shrink: 0;
  width: 20px;
  height: 20px;
  display: grid;
  place-items: center;
  border-radius: 50%;
  font-family: var(--font-ui);
  font-size: 11px;
  font-weight: 500;
  margin-top: 1px;
}

.is-pass .check-icon {
  background: var(--accent-soft);
  color: var(--accent);
}

.is-warn .check-icon {
  background: var(--danger-soft);
  color: var(--danger);
}

.check-body h4 {
  margin: 0;
  font-family: var(--font-display);
  font-weight: 700;
  font-size: 14px;
  color: var(--warm);
}

.check-body p {
  margin: 5px 0 0;
  font-family: var(--font-ui);
  font-size: 13px;
  line-height: 1.6;
  color: var(--warm-faint);
}

/* Tickable variants used by the edge-case and review checklists */
.check-item label {
  display: flex;
  align-items: flex-start;
  gap: 12px;
  margin: 0;
  cursor: pointer;
  width: 100%;
}

.check-item input[type='checkbox'] {
  flex-shrink: 0;
  width: 16px;
  height: 16px;
  margin-top: 2px;
  accent-color: var(--accent);
  cursor: pointer;
}

.check-item:has(input:checked) {
  border-left-color: var(--accent);
  opacity: 0.6;
}

/* --- highlighted spans in a reviewed AI answer -------------------------- */
.review-pane {
  padding: 18px;
  border: 1px solid var(--ink-edge);
  border-radius: 8px;
  background: var(--ink);
  font-size: 15px;
  line-height: 1.85;
  white-space: pre-wrap;
  word-break: break-word;
  max-height: 440px;
  overflow-y: auto;
}

.mark {
  border-radius: 3px;
  padding: 1px 3px;
  cursor: help;
  border-bottom: 2px solid;
}

.mark-number { background: color-mix(in srgb, var(--accent) 18%, transparent); border-color: var(--accent); }
.mark-date   { background: color-mix(in srgb, #8b5cf6 22%, transparent); border-color: #8b5cf6; }
.mark-entity { background: color-mix(in srgb, #0ea5e9 18%, transparent); border-color: #0ea5e9; }
.mark-absolute { background: color-mix(in srgb, var(--danger) 18%, transparent); border-color: var(--danger); }
.mark-citation { background: color-mix(in srgb, #10b981 20%, transparent); border-color: #10b981; }

/* --- score grid --------------------------------------------------------- */
.score-input {
  width: 62px;
  padding: 6px 8px;
  border: 1px solid var(--ink-edge);
  border-radius: 4px;
  background: var(--ink-panel);
  color: var(--warm);
  font-family: var(--font-ui);
  font-size: 13px;
  text-align: center;
}

.score-input:focus {
  outline: none;
  border-color: var(--accent);
}

.score-total {
  font-family: var(--font-num);
  font-size: 26px;
  line-height: 1;
  color: var(--accent);
}

/* Summary strip above a result: a few numbers, evenly spread */
.stat-strip {
  display: flex;
  flex-wrap: wrap;
  gap: 24px 36px;
  padding: 16px 18px;
  border: 1px solid var(--ink-edge);
  border-radius: 8px;
  background: var(--ink);
  margin-bottom: 20px;
}

.stat-strip .value {
  font-family: var(--font-num);
  font-size: 24px;
  line-height: 1;
  color: var(--accent);
}

.stat-strip .caption {
  margin-top: 3px;
  font-family: var(--font-ui);
  font-size: 12px;
  color: var(--warm-faint);
}

/* --------------------------------------------------- 12c. summarizer ------ */

/* Selected sentences, numbered by their position in the source document. */
.summary-list {
  list-style: none;
  margin: 0;
  padding: 0;
  display: grid;
  gap: 14px;
}

.summary-item {
  display: flex;
  align-items: flex-start;
  gap: 12px;
  padding: 14px 16px;
  border: 1px solid var(--ink-edge);
  border-left: 2px solid var(--accent);
  border-radius: 6px;
  background: var(--ink);
}

.summary-item p {
  margin: 0;
  font-size: 15px;
  line-height: 1.65;
  color: var(--warm);
}

/* The source position, so a sentence can be traced back to the document. */
.summary-position {
  flex-shrink: 0;
  min-width: 34px;
  font-family: var(--font-num);
  font-size: 15px;
  letter-spacing: 0.04em;
  color: var(--accent);
  cursor: help;
}

.chip-row {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
}

/* A technique panel is hidden until its tab is selected */
.technique {
  display: none;
}

.technique.is-active {
  display: block;
}

/* ------------------------------------------- 12e. developer guide (3D) ---- */

/*
 * The one place in the project with real 3D. It is here on purpose: this
 * section is about building with AI, and a depth-rendered lattice says that
 * without a single image asset.
 *
 * All of it is CSS transforms and CSS animations — no WebGL, no canvas, no
 * animation library. See "Why the reveals are CSS" in CLAUDE.md: a JS library
 * left the old React build's cards stuck at opacity:0 when frames did not
 * arrive. A transform that never animates still resolves to a visible,
 * correctly-positioned element.
 *
 * The only JavaScript is guide.js writing --tilt-x/--tilt-y/--mx/--my for the
 * pointer-tracking tilt. Every one of those has a fallback value in the
 * declaration, so the cards are complete before a single line of it runs.
 */

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

.guide-hero {
  position: relative;
  overflow: hidden;
  padding: 64px 0 72px;
  border-bottom: 1px solid var(--ink-line);
}

.guide-hero-grid {
  display: grid;
  grid-template-columns: 1.05fr 0.95fr;
  gap: 40px;
  align-items: center;
}

.guide-title {
  font-size: clamp(38px, 6vw, 84px);
  line-height: 0.98;
  letter-spacing: -0.03em;
}

/*
 * Extruded type. Five stacked shadows stepping away at decreasing opacity —
 * the flattest possible way to render depth, and it costs no layers.
 * Every step is derived from --accent, so it follows the theme like everything
 * else.
 */
.guide-title-accent {
  color: var(--accent);
  text-shadow:
    1px 1px 0 color-mix(in srgb, var(--accent) 52%, transparent),
    2px 2px 0 color-mix(in srgb, var(--accent) 36%, transparent),
    3px 3px 0 color-mix(in srgb, var(--accent) 24%, transparent),
    4px 4px 0 color-mix(in srgb, var(--accent) 14%, transparent),
    5px 5px 0 color-mix(in srgb, var(--accent) 7%, transparent);
}

.guide-stats {
  display: flex;
  flex-wrap: wrap;
  gap: 36px;
  margin-top: 36px;
}

.guide-stat-num {
  display: block;
  font-family: var(--font-num);
  font-size: 44px;
  line-height: 1;
  color: var(--accent);
}

.guide-stat-label {
  font-family: var(--font-ui);
  font-size: 11px;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--warm-faint);
}

/* --- the lattice --------------------------------------------------------- */

.lattice {
  position: relative;
  display: flex;
  align-items: center;
  justify-content: center;
  height: 380px;
  perspective: 1000px;
  perspective-origin: 50% 44%;
}

/*
 * Perspective grid floor. The mask is the one gradient in this stylesheet
 * written with a literal colour, and it is exempt from the token rule for a
 * concrete reason: a mask consumes only the ALPHA channel, so the colour is
 * never rendered and there is nothing for a theme to change.
 */
.lattice-floor {
  position: absolute;
  inset: auto 0 0 0;
  height: 250px;
  background-image:
    linear-gradient(to right, color-mix(in srgb, var(--accent) 26%, transparent) 1px, transparent 1px),
    linear-gradient(to bottom, color-mix(in srgb, var(--accent) 26%, transparent) 1px, transparent 1px);
  background-size: 46px 46px;
  transform: perspective(420px) rotateX(62deg);
  transform-origin: 50% 100%;
  -webkit-mask-image: linear-gradient(to top, rgba(0, 0, 0, 1), rgba(0, 0, 0, 0) 76%);
  mask-image: linear-gradient(to top, rgba(0, 0, 0, 1), rgba(0, 0, 0, 0) 76%);
  opacity: 0.5;
  pointer-events: none;
}

/*
 * The stage is a zero-size point that everything is positioned around, so
 * centring is free and every child transform is relative to the same origin.
 */
.lattice-stage {
  position: relative;
  width: 0;
  height: 0;
  /* --r is half the outer cube's edge: the radius the corner nodes sit at.
     Kept in one place so the responsive rules can shrink the whole assembly by
     changing --r and the two --s values together. */
  --r: 84px;
  transform-style: preserve-3d;
  animation: lattice-spin 26s linear infinite;
}

@keyframes lattice-spin {
  from { transform: rotateX(-20deg) rotateY(0deg); }
  to   { transform: rotateX(-20deg) rotateY(360deg); }
}

.cube {
  position: absolute;
  transform-style: preserve-3d;
}

/* --s is the edge length; every face reads it, so one value rescales a cube. */
.cube-outer { --s: 168px; }

.cube-inner {
  --s: 80px;
  animation: lattice-counter 17s linear infinite;
}

/* Counter-rotation reads as two independent objects rather than one rigid
   shape, which is what makes it look like a structure and not a box. */
@keyframes lattice-counter {
  from { transform: rotateY(0deg) rotateX(0deg); }
  to   { transform: rotateY(-360deg) rotateX(360deg); }
}

.face {
  position: absolute;
  width: var(--s);
  height: var(--s);
  left: calc(var(--s) / -2);
  top: calc(var(--s) / -2);
  border: 1px solid color-mix(in srgb, var(--accent) 42%, transparent);
  background: color-mix(in srgb, var(--accent) 5%, transparent);
}

.face-front  { transform: translateZ(calc(var(--s) / 2)); }
.face-back   { transform: rotateY(180deg) translateZ(calc(var(--s) / 2)); }
.face-right  { transform: rotateY(90deg)  translateZ(calc(var(--s) / 2)); }
.face-left   { transform: rotateY(-90deg) translateZ(calc(var(--s) / 2)); }
.face-top    { transform: rotateX(90deg)  translateZ(calc(var(--s) / 2)); }
.face-bottom { transform: rotateX(-90deg) translateZ(calc(var(--s) / 2)); }

/*
 * Corner nodes. --r is half the outer cube's edge, so each sits exactly on a
 * vertex. The pulse animates opacity and glow only — animating transform here
 * would overwrite the translate3d that puts the node in place.
 */
.node {
  position: absolute;
  width: 9px;
  height: 9px;
  margin: -4.5px;
  border-radius: 50%;
  background: var(--accent);
  transform: translate3d(
    calc(var(--nx) * var(--r)),
    calc(var(--ny) * var(--r)),
    calc(var(--nz) * var(--r)));
  animation: node-pulse 2.8s ease-in-out infinite;
  animation-delay: var(--delay);
}

@keyframes node-pulse {
  0%, 100% {
    opacity: 0.3;
    box-shadow: 0 0 5px color-mix(in srgb, var(--accent) 40%, transparent);
  }
  50% {
    opacity: 1;
    box-shadow: 0 0 18px color-mix(in srgb, var(--accent) 80%, transparent);
  }
}

.orbit {
  position: absolute;
  width: 300px;
  height: 300px;
  left: -150px;
  top: -150px;
  border: 1px dashed color-mix(in srgb, var(--accent) 28%, transparent);
  border-radius: 50%;
}

.orbit-a { animation: orbit-a 24s linear infinite; }
.orbit-b { animation: orbit-b 34s linear infinite; }

@keyframes orbit-a {
  from { transform: rotateX(76deg) rotateZ(0deg); }
  to   { transform: rotateX(76deg) rotateZ(360deg); }
}

@keyframes orbit-b {
  from { transform: rotateY(72deg) rotateZ(360deg); }
  to   { transform: rotateY(72deg) rotateZ(0deg); }
}

/* --- filter + tabs ------------------------------------------------------- */

.guide-filter {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 16px;
  margin-bottom: 28px;
}

.guide-filter .pill-row {
  margin-top: 0;
}

/* The filter pills are <button>s, which inherit none of this by default. */
.pill[data-lang] {
  cursor: pointer;
  font-family: var(--font-ui);
  transition: color 0.2s ease, border-color 0.2s ease, background 0.2s ease;
}

.pill[data-lang]:hover {
  border-color: var(--warm-ghost);
  color: var(--warm);
}

.pill[data-lang].is-active {
  border-color: var(--accent);
  background: var(--accent-soft);
  color: var(--accent);
}

.pill-count {
  font-family: var(--font-num);
  font-size: 12px;
  letter-spacing: 0.04em;
  opacity: 0.75;
}

/* Tabs that press. The shadow is the "edge" of the key; the active tab sits
   1px down with a shorter edge, which reads as held in. */
.tabs-3d .tab {
  display: inline-flex;
  align-items: center;
  gap: 2px;
  box-shadow: 0 2px 0 var(--ink-edge);
  transition:
    transform 0.15s ease,
    box-shadow 0.15s ease,
    background 0.2s ease,
    border-color 0.2s ease,
    color 0.2s ease;
}

.tabs-3d .tab:hover {
  transform: translateY(-2px);
  box-shadow: 0 4px 0 var(--ink-edge);
}

.tabs-3d .tab.is-active {
  transform: translateY(1px);
  box-shadow: 0 1px 0 var(--accent-hi);
}

.tab-count {
  font-family: var(--font-num);
  font-size: 11px;
  letter-spacing: 0.04em;
  margin-left: 6px;
  opacity: 0.7;
}

/* A category panel is hidden until its tab is selected — same contract as
   .technique on the prompt pages. */
.guide-panel {
  display: none;
}

.guide-panel.is-active {
  display: block;
}

.guide-blurb {
  max-width: 68ch;
  margin-bottom: 24px;
  color: var(--warm-mute);
  font-size: 15px;
}

/* --- the deck ------------------------------------------------------------ */

.deck {
  display: grid;
  /* min() so a 320px phone gets one full-width column instead of overflowing. */
  grid-template-columns: repeat(auto-fill, minmax(min(262px, 100%), 1fr));
  gap: 18px;
  /* The perspective lives on the container, so all the cards share one vanishing
     point and tilt as though they are on the same plane. */
  perspective: 1100px;
}

.deck-card {
  position: relative;
  display: flex;
  flex-direction: column;
  gap: 10px;
  padding: 20px;
  border: 1px solid var(--ink-edge);
  border-radius: 12px;
  background: var(--ink-panel);
  box-shadow: var(--panel-shadow);
  color: inherit;
  text-decoration: none;
  transform-style: preserve-3d;
  /* Fallbacks matter: with no JS, no pointer or reduced motion, this resolves
     to a flat, upright card. */
  transform:
    rotateX(var(--tilt-x, 0deg))
    rotateY(var(--tilt-y, 0deg));
  transition:
    transform 0.18s ease,
    border-color 0.2s ease,
    box-shadow 0.25s ease;
}

/* The UA's [hidden] rule is display:none at almost no specificity, so the
   display:flex above would beat it and the language filter would hide nothing.
   This is the whole filter, and it is one line of CSS. */
.deck-card[hidden] {
  display: none;
}

.deck-card:hover,
.deck-card:focus-visible {
  border-color: var(--accent);
  box-shadow: 0 18px 38px color-mix(in srgb, var(--accent) 16%, transparent);
  text-decoration: none;
}

/* Specular sheen that follows the pointer. --mx/--my are written by guide.js;
   at their default 50% it is a centred highlight, which is a reasonable
   resting state. */
.deck-glow {
  position: absolute;
  inset: 0;
  border-radius: inherit;
  pointer-events: none;
  opacity: 0;
  transition: opacity 0.25s ease;
  background: radial-gradient(
    220px circle at var(--mx, 50%) var(--my, 50%),
    color-mix(in srgb, var(--accent) 15%, transparent),
    transparent 70%);
}

.deck-card:hover .deck-glow {
  opacity: 1;
}

.deck-head {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: 12px;
}

/*
 * The monogram. translateZ lifts it off the card face, so when the card tilts
 * it parallaxes against the text — the detail that makes the tilt read as
 * depth rather than as a skew.
 *
 * Initials rather than logos: 50 vendor marks would be 50 licences to check.
 */
.deck-mark {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 44px;
  height: 44px;
  border-radius: 10px;
  font-family: var(--font-num);
  font-size: 19px;
  letter-spacing: 0.06em;
  color: var(--on-accent);
  background: linear-gradient(145deg, var(--accent), var(--accent-hi));
  box-shadow:
    inset 0 1px 0 color-mix(in srgb, var(--on-accent) 32%, transparent),
    0 6px 14px color-mix(in srgb, var(--accent) 28%, transparent);
  transform: translateZ(26px);
}

.deck-status {
  flex-shrink: 0;
  padding: 2px 9px;
  border: 1px solid var(--ink-edge);
  border-radius: 99px;
  font-family: var(--font-num);
  font-size: 11px;
  letter-spacing: 0.12em;
  color: var(--warm-faint);
}

.deck-status.is-ready {
  border-color: var(--accent);
  color: var(--accent);
}

.deck-name {
  font-family: var(--font-display);
  font-weight: 700;
  font-size: 18px;
  letter-spacing: -0.01em;
  color: var(--warm);
  transform: translateZ(14px);
}

.deck-tagline {
  flex: 1;
  font-size: 13.5px;
  line-height: 1.55;
  color: var(--warm-mute);
}

.deck-langs {
  display: flex;
  gap: 6px;
  margin-top: 4px;
}

/* All four are always shown, lit or unlit. "Java: no" is information; a missing
   chip is just an absence the reader has to notice. */
.deck-lang {
  padding: 3px 8px;
  border: 1px solid var(--ink-line);
  border-radius: 4px;
  font-family: var(--font-ui);
  font-size: 10px;
  letter-spacing: 0.08em;
  color: var(--warm-ghost);
}

.deck-lang.is-on {
  border-color: var(--accent);
  background: var(--accent-soft);
  color: var(--accent);
}

.deck-empty {
  padding: 32px 0;
  color: var(--warm-mute);
}

.link-button {
  border: 0;
  padding: 0;
  background: none;
  font: inherit;
  color: var(--accent);
  cursor: pointer;
  text-decoration: underline;
}

/* --- a provider page ----------------------------------------------------- */

.recipe-head {
  padding: 48px 0 40px;
  border-bottom: 1px solid var(--ink-line);
  background:
    radial-gradient(900px 300px at 12% -20%,
      color-mix(in srgb, var(--accent) 10%, transparent), transparent 70%);
}

.crumb {
  display: inline-block;
  margin-bottom: 24px;
  font-family: var(--font-ui);
  font-size: 13px;
  color: var(--warm-mute);
}

.crumb:hover {
  color: var(--accent);
}

.recipe-title-row {
  display: flex;
  align-items: center;
  gap: 20px;
}

.recipe-mark {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  flex-shrink: 0;
  width: 64px;
  height: 64px;
  border-radius: 14px;
  font-family: var(--font-num);
  font-size: 30px;
  color: var(--on-accent);
  background: linear-gradient(145deg, var(--accent), var(--accent-hi));
  box-shadow:
    inset 0 1px 0 color-mix(in srgb, var(--on-accent) 32%, transparent),
    0 12px 26px color-mix(in srgb, var(--accent) 30%, transparent);
}

.recipe-title {
  font-size: clamp(30px, 4vw, 48px);
  min-width: 0;
  overflow-wrap: anywhere;              /* "Microsoft.Extensions.AI" is one word to a phone */
  line-height: 1.05;
}

.recipe-meta {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
  margin-top: 24px;
}

/* The chips read as labels at card size; give them a little more room when they
   are the page's own metadata rather than a tile's. */
.recipe-meta .deck-lang {
  padding: 5px 11px;
  font-size: 11px;
}

.recipe-focus {
  border-left: 2px solid var(--accent);
}

.recipe-pending {
  position: relative;
  padding-left: 52px;
}

/* A slow pulse, so the panel reads as "in progress" rather than "broken". */
.pending-pulse {
  position: absolute;
  left: 22px;
  top: 28px;
  width: 10px;
  height: 10px;
  border-radius: 50%;
  background: var(--accent);
  animation: node-pulse 2.6s ease-in-out infinite;
}

/* --- code samples -------------------------------------------------------- */

/*
 * The staleness banner. A reader has no way to tell how old a sample is, and a
 * dead model id is the most likely way one breaks — so the model, the package
 * and the date we last checked are printed above the first block.
 */
.recipe-freshness {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: 12px 32px;
  margin-top: 20px;
  padding: 16px 20px;
  border: 1px solid var(--ink-edge);
  border-radius: 10px;
  background: var(--accent-soft);
}

.freshness-label {
  display: block;
  font-family: var(--font-ui);
  font-size: 10px;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--warm-faint);
}

.recipe-freshness code,
.recipe-freshness time {
  font-family: var(--font-mono);
  font-size: 13.5px;
  color: var(--accent);
}

.freshness-note {
  flex: 1 1 260px;
  font-size: 13px;
  line-height: 1.55;
  color: var(--warm-mute);
}

.recipe-steps {
  display: grid;
  gap: 28px;
  margin-top: 28px;
}

.snippet {
  margin: 0;
  border: 1px solid var(--ink-edge);
  border-radius: 10px;
  background: var(--ink-panel);
  overflow: hidden;
}

.snippet-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 16px;
  padding: 12px 16px;
  border-bottom: 1px solid var(--ink-line);
}

.snippet-title {
  font-family: var(--font-num);
  font-size: 15px;
  letter-spacing: 0.06em;
  color: var(--warm);
}

.snippet-copy {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: 5px 11px;
  border: 1px solid var(--ink-edge);
  border-radius: 6px;
  background: var(--ink);
  font-family: var(--font-ui);
  font-size: 12px;
  color: var(--warm-mute);
  cursor: pointer;
  transition: color 0.2s ease, border-color 0.2s ease;
}

.snippet-copy:hover {
  color: var(--accent);
  border-color: var(--accent);
}

.snippet-copy.is-done {
  color: var(--accent);
  border-color: var(--accent);
}

.snippet-note {
  padding: 14px 16px 0;
  font-size: 14px;
  line-height: 1.6;
  color: var(--warm-mute);
}

/*
 * pre must scroll rather than wrap: a wrapped line of code is a line whose
 * indentation lies, and indentation is syntax in Python.
 */
.snippet-code {
  margin: 14px 0 0;
  padding: 16px;
  overflow-x: auto;
  background: var(--ink);
  border-top: 1px solid var(--ink-line);
  font-family: var(--font-mono);
  font-size: 13px;
  line-height: 1.7;
  tab-size: 4;
}

.snippet-code code {
  font: inherit;
  color: var(--warm-dim);
  white-space: pre;
}

/*
 * Syntax colours. Only four, from tokens that already exist, because a
 * ten-colour theme would be the only part of the site with a second palette.
 * Comments are the quietest and strings the loudest, which is the ordering that
 * actually helps when scanning a sample.
 */
.tok-comment { color: var(--warm-ghost); font-style: italic; }
.tok-string  { color: var(--accent); }
.tok-keyword { color: var(--warm); font-weight: 500; }
.tok-number  { color: var(--warm-faint); }

/* --- the official-docs band, on every recipe page ------------------------ */

.docs-band {
  border-top: 1px solid var(--ink-line);
  background: var(--ink-panel);
}

.docs-callout {
  display: flex;
  align-items: center;
  justify-content: space-between;
  flex-wrap: wrap;
  gap: 24px;
}

.docs-callout .lead {
  max-width: 58ch;
}

.docs-url {
  margin-top: 20px;
  padding-top: 16px;
  border-top: 1px solid var(--ink-line);
  font-family: var(--font-mono);
  font-size: 12.5px;
  color: var(--warm-faint);
  word-break: break-all;
}

/* ------------------------------------------- 12f. about / portfolio ------- */

/*
 * The About page carries a portrait and a career history, which is the only
 * place on the site with either. Everything here is built from existing tokens;
 * the one genuinely new thing is a real photograph, and the frame around it is
 * what keeps a rectangular JPEG from looking pasted onto the design.
 */

/* A band that steps away from the page colour, for alternating sections. */
.section-alt {
  background: var(--ink-panel);
  border-top: 1px solid var(--ink-line);
  border-bottom: 1px solid var(--ink-line);
}

.about-split {
  display: grid;
  grid-template-columns: 1.15fr 0.85fr;
  gap: 48px;
  align-items: start;
}

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

.persona-hero {
  padding: 56px 0 64px;
  border-bottom: 1px solid var(--ink-line);
  background:
    radial-gradient(880px 320px at 8% -10%,
      color-mix(in srgb, var(--accent) 12%, transparent), transparent 70%);
}

.persona-grid {
  display: grid;
  grid-template-columns: 300px 1fr;
  gap: 48px;
  align-items: center;
}

/*
 * The offset accent outline behind the photo. It does two things: it stops a
 * plain square portrait from floating, and it ties the one photographic element
 * on the site back to the accent every other element uses.
 */
.portrait-frame {
  position: relative;
  width: 100%;
  max-width: 300px;
}

.portrait-frame::after {
  content: '';
  position: absolute;
  inset: 18px -18px -18px 18px;
  border: 1px solid var(--accent);
  border-radius: 22px;
  z-index: 0;
  pointer-events: none;
}

.portrait {
  position: relative;
  z-index: 1;
  display: block;
  width: 100%;
  height: auto;
  aspect-ratio: 1;
  object-fit: cover;
  border-radius: 20px;
  border: 1px solid var(--ink-edge);
  background: var(--ink-raised);
  box-shadow: 0 18px 44px color-mix(in srgb, var(--accent) 18%, transparent);
}

.persona-name {
  font-size: clamp(34px, 5.2vw, 68px);
  line-height: 1;
  letter-spacing: -0.03em;
}

.persona-role {
  margin-top: 12px;
  font-family: var(--font-ui);
  font-size: 15px;
  color: var(--warm-mute);
}

.persona-role strong {
  color: var(--accent);
  font-weight: 500;
}

/* --- the numbers --------------------------------------------------------- */

.figure-row {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(210px, 100%), 1fr));
  gap: 32px;
}

.figure-value {
  display: block;
  font-family: var(--font-num);
  font-size: clamp(38px, 5vw, 58px);
  line-height: 1;
  letter-spacing: 0.01em;
  color: var(--accent);
}

/* The + sits smaller and higher — it is a qualifier, not a digit. */
.figure-plus {
  font-size: 0.55em;
  vertical-align: super;
  opacity: 0.8;
}

.figure-label {
  display: block;
  margin-top: 10px;
  font-family: var(--font-ui);
  font-size: 13px;
  line-height: 1.45;
  color: var(--warm-faint);
}

/* --- the principle ------------------------------------------------------- */

.creed {
  padding: 72px 0;
  background: var(--accent);
  color: var(--on-accent);
}

.creed .kicker {
  color: var(--on-accent);
  opacity: 0.75;
}

.creed-quote {
  margin: 0;
  max-width: 24ch;
  font-family: var(--font-display);
  font-weight: 300;
  font-size: clamp(24px, 3.6vw, 44px);
  line-height: 1.25;
  letter-spacing: -0.02em;
}

.creed-note {
  margin-top: 24px;
  max-width: 56ch;
  font-family: var(--font-ui);
  font-size: 14.5px;
  line-height: 1.6;
  opacity: 0.82;
}

/* --- proof cards --------------------------------------------------------- */

/* .card already lifts on hover; these have no artwork, so the body needs the
   padding the cover image usually provides. */
.proof-card .card-body {
  padding-top: 24px;
}

.proof-card .card-desc {
  min-height: 0;
}

/* --- career timeline ----------------------------------------------------- */

/*
 * A single accent rule down the left with a node per role. The rule is drawn on
 * the <ol> rather than per item so it is continuous, and each node is an ::after
 * on the item so it lands on the rule regardless of the item's height.
 */
.timeline {
  position: relative;
  list-style: none;
  margin: 0;
  padding: 0 0 0 30px;
}

.timeline::before {
  content: '';
  position: absolute;
  left: 4px;
  top: 8px;
  bottom: 8px;
  width: 1px;
  background: var(--ink-edge);
}

.timeline-item {
  position: relative;
  padding-bottom: 44px;
}

.timeline-item:last-child {
  padding-bottom: 0;
}

.timeline-item::before {
  content: '';
  position: absolute;
  left: -30px;
  top: 7px;
  width: 9px;
  height: 9px;
  margin-left: 0;
  border-radius: 50%;
  background: var(--ink-rule);
  box-shadow: 0 0 0 4px var(--ink);
}

/* The current role gets the accent and a glow — the eye should land there. */
.timeline-item.is-current::before {
  background: var(--accent);
  box-shadow:
    0 0 0 4px var(--ink),
    0 0 14px color-mix(in srgb, var(--accent) 70%, transparent);
}

.timeline-when {
  font-family: var(--font-num);
  font-size: 15px;
  letter-spacing: 0.06em;
  color: var(--accent);
}

.timeline-role {
  margin-top: 4px;
  font-size: 20px;
  letter-spacing: -0.01em;
}

.timeline-tag {
  margin-left: 8px;
  padding: 2px 8px;
  border: 1px solid var(--ink-edge);
  border-radius: 99px;
  font-family: var(--font-ui);
  font-size: 10px;
  font-weight: 400;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--warm-faint);
  vertical-align: middle;
}

.timeline-org {
  margin-top: 4px;
  font-family: var(--font-ui);
  font-size: 13.5px;
  color: var(--warm-faint);
}

.timeline-detail {
  margin-top: 12px;
  max-width: 72ch;
  font-size: 15px;
  line-height: 1.65;
  color: var(--warm-mute);
}

/* --- education + certifications ------------------------------------------ */

.cred-badge {
  display: inline-block;
  margin-bottom: 8px;
  padding: 3px 10px;
  border: 1px solid var(--accent);
  border-radius: 99px;
  background: var(--accent-soft);
  font-family: var(--font-ui);
  font-size: 10px;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--accent);
}

/* --- education cards + generated crests ----------------------------------- */

/*
 * THE CRESTS ARE GENERATED, NOT THE INSTITUTES' OWN LOGOS — a rotating ring of
 * teeth around a disc of initials. Same reasoning as .deck-mark: no image
 * assets ship here beyond the portrait, and a real institutional mark is a
 * licence to check. It also means the seal themes with the page for free.
 */

.edu-grid {
  align-items: stretch;
}

.edu-card {
  display: flex;
  flex-direction: column;
  gap: 6px;
  padding: 26px 24px 24px;
  border: 1px solid var(--ink-edge);
  border-radius: 14px;
  background: var(--ink-panel);
  box-shadow: var(--panel-shadow);
}

/*
 * The accent rail across the top is the whole "this one is different" signal,
 * and it costs one pseudo-element.
 *
 * It is rounded to match the card rather than clipped by `overflow: hidden` —
 * ANY overflow other than visible forces transform-style to compute to flat,
 * which would silently kill the translateZ parallax on the crest and turn the
 * tilt back into a skew.
 */
.edu-card.is-eminent::before {
  content: '';
  position: absolute;
  inset: 0 0 auto 0;
  height: 3px;
  border-radius: 14px 14px 0 0;
  background: linear-gradient(90deg, var(--accent), var(--accent-hi));
}

.crest {
  position: relative;
  width: 78px;
  height: 78px;
  margin-bottom: 18px;
  flex-shrink: 0;
  /* Lifted off the card face, so it parallaxes when the card tilts — the same
     26px as .deck-mark and .tilt-mark. */
  transform: translateZ(26px);
}

/*
 * The ring of teeth. A repeating conic gradient gives the gear edge that reads
 * as a seal; the radial mask punches the middle out so only the rim remains.
 *
 * The mask is one of the few places a literal colour is allowed here: a mask
 * consumes the ALPHA channel only, so nothing of it is ever rendered and there
 * is nothing for a theme to change.
 */
.crest-ring {
  position: absolute;
  inset: 0;
  border-radius: 50%;
  background: repeating-conic-gradient(
    from 0deg,
    color-mix(in srgb, var(--accent) 55%, transparent) 0deg 6deg,
    transparent 6deg 15deg);
  -webkit-mask-image: radial-gradient(circle, transparent 61%, #000 63%);
  mask-image: radial-gradient(circle, transparent 61%, #000 63%);
  animation: crest-spin 30s linear infinite;
}

/* Ends on the identity pose, so the global reduced-motion rule leaves the seal
   correctly oriented rather than at an arbitrary angle. */
@keyframes crest-spin {
  from { transform: rotate(0deg); }
  to   { transform: rotate(360deg); }
}

.crest-face {
  position: absolute;
  inset: 12px;
  display: flex;
  align-items: center;
  justify-content: center;
  border-radius: 50%;
  border: 1px solid color-mix(in srgb, var(--accent) 40%, transparent);
  background: var(--ink-raised);
  font-family: var(--font-num);
  font-size: 20px;
  letter-spacing: 0.06em;
  color: var(--accent);
}

/*
 * The plate a REAL institutional logo sits on. It is --logo-plate in both
 * themes on purpose: an institution's crest is drawn for white paper, and
 * recolouring someone else's mark so it reads on a dark page is not a call
 * this stylesheet gets to make.
 */
.crest-plate {
  position: absolute;
  inset: 7px;
  display: flex;
  align-items: center;
  justify-content: center;
  border-radius: 50%;
  background: var(--logo-plate);
  box-shadow:
    inset 0 0 0 1px color-mix(in srgb, var(--accent) 22%, transparent),
    0 8px 22px color-mix(in srgb, var(--accent) 30%, transparent);
}

.crest-plate img {
  width: 92%;
  height: 92%;
  object-fit: contain;
}

/* The two that should catch the eye: a filled, lit disc and a faster ring. */
.edu-card.is-eminent .crest-face {
  border-color: transparent;
  color: var(--on-accent);
  background: linear-gradient(145deg, var(--accent), var(--accent-hi));
  box-shadow:
    inset 0 1px 0 color-mix(in srgb, var(--on-accent) 30%, transparent),
    0 8px 22px color-mix(in srgb, var(--accent) 38%, transparent);
}

.edu-card.is-eminent .crest-ring {
  background: repeating-conic-gradient(
    from 0deg,
    var(--accent) 0deg 6deg,
    transparent 6deg 15deg);
  animation-duration: 20s;
}

.edu-eminence {
  font-family: var(--font-ui);
  font-size: 10px;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--warm-ghost);
}

.edu-card.is-eminent .edu-eminence {
  color: var(--accent);
}

.edu-org {
  position: relative;
  font-size: 18px;
  line-height: 1.25;
  letter-spacing: -0.01em;
  transform: translateZ(14px);
}

.edu-award {
  font-size: 14.5px;
  line-height: 1.55;
  color: var(--warm-mute);
}

.edu-when {
  /* margin-top:auto pins the dates to the bottom, so three cards with titles of
     different lengths still line their dates up. */
  margin-top: auto;
  padding-top: 14px;
  font-family: var(--font-num);
  font-size: 15px;
  letter-spacing: 0.06em;
  color: var(--accent);
}

.edu-note {
  font-family: var(--font-ui);
  font-size: 12px;
  line-height: 1.5;
  color: var(--warm-faint);
}

.edu-when .cred-badge {
  margin-bottom: 0;
  margin-left: 6px;
  vertical-align: middle;
}

/* Completed is a statement of fact, not something to draw the eye to — the
   in-progress badge keeps the accent. */
.cred-badge.is-done {
  border-color: var(--ink-edge);
  background: transparent;
  color: var(--warm-faint);
}

/* --- contact ------------------------------------------------------------- */

.contact-band {
  border-top: 1px solid var(--ink-line);
}

.contact-card {
  display: flex;
  align-items: center;
  justify-content: space-between;
  flex-wrap: wrap;
  gap: 32px;
  padding: 40px;
  border: 1px solid var(--ink-edge);
  border-radius: 14px;
  background: var(--ink-panel);
  box-shadow: var(--panel-shadow);
}

.contact-links {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 12px;
}

/* ------------------------------------------- 12g. the fractional CTO offer -- */

/*
 * The second half of /about: what the engagement actually is, who it is for,
 * and the 3D tower that carries it.
 *
 * This is the only place outside §12e with real 3D, and for the same reason:
 * the page is about an engineering organisation built upward over a decade,
 * and depth says that without an image asset. All of it is CSS transforms and
 * CSS animations — do NOT reach for a WebGL or animation library here; the
 * reasoning under "Why the reveals are CSS" applies with more force, not less.
 */

/* --- the growth tower ----------------------------------------------------- */

.growth-grid {
  display: grid;
  grid-template-columns: 0.9fr 1.1fr;
  gap: 48px;
  align-items: center;
}

.stack {
  position: relative;
  display: flex;
  align-items: center;
  justify-content: center;
  height: 420px;
  perspective: 1000px;
  perspective-origin: 50% 46%;
}

/*
 * Perspective grid floor. As in .lattice-floor, the mask is the one place a
 * literal colour is allowed: a mask consumes only the ALPHA channel, so the
 * colour is never rendered and there is nothing for a theme to change.
 */
.stack-floor {
  position: absolute;
  inset: auto 0 0 0;
  height: 230px;
  background-image:
    linear-gradient(to right, color-mix(in srgb, var(--accent) 32%, transparent) 1px, transparent 1px),
    linear-gradient(to bottom, color-mix(in srgb, var(--accent) 32%, transparent) 1px, transparent 1px);
  background-size: 44px 44px;
  transform: perspective(420px) rotateX(64deg);
  transform-origin: 50% 100%;
  -webkit-mask-image: linear-gradient(to top, rgba(0, 0, 0, 1), rgba(0, 0, 0, 0) 74%);
  mask-image: linear-gradient(to top, rgba(0, 0, 0, 1), rgba(0, 0, 0, 0) 74%);
  opacity: 0.55;
  pointer-events: none;
}

/* A zero-size point everything is positioned around, so centring is free and
   every child transform shares one origin. */
.stack-stage {
  position: relative;
  width: 0;
  height: 0;
  transform-style: preserve-3d;
  animation: stack-orbit 38s linear infinite;
}

@keyframes stack-orbit {
  from { transform: rotateX(-16deg) rotateY(0deg); }
  to   { transform: rotateX(-16deg) rotateY(360deg); }
}

/*
 * A tier is a zero-size point that carries the plate's height and its own
 * rotation; the plate inside lies flat within it. The split matters: the plate
 * needs a static rotateX(90deg) to be horizontal, and a single element cannot
 * both hold that and animate a different rotation.
 */
.tier {
  position: absolute;
  left: 0;
  top: 0;
  transform-style: preserve-3d;
  animation: tier-spin var(--dur, 24s) linear infinite;
  animation-direction: var(--dir, normal);
}

@keyframes tier-spin {
  from { transform: translateY(var(--lift, 0px)) rotateY(0deg); }
  to   { transform: translateY(var(--lift, 0px)) rotateY(360deg); }
}

.plate {
  position: absolute;
  width: var(--w, 160px);
  height: var(--w, 160px);
  left: calc(var(--w, 160px) / -2);
  top: calc(var(--w, 160px) / -2);
  border: 1px solid color-mix(in srgb, var(--accent) 62%, transparent);
  border-radius: 10px;
  background: linear-gradient(
    135deg,
    color-mix(in srgb, var(--accent) 20%, transparent),
    color-mix(in srgb, var(--accent) 5%, transparent));
  transform: rotateX(90deg);
  /* Glow only — animating transform here would overwrite the rotation that
     makes the plate horizontal in the first place. 0% and 100% are identical,
     so reduced motion lands on the calm state. */
  animation: plate-glow 3.6s ease-in-out infinite;
  animation-delay: var(--delay, 0ms);
}

@keyframes plate-glow {
  0%, 100% {
    box-shadow: inset 0 0 18px color-mix(in srgb, var(--accent) 8%, transparent);
  }
  50% {
    box-shadow:
      0 0 26px color-mix(in srgb, var(--accent) 30%, transparent),
      inset 0 0 26px color-mix(in srgb, var(--accent) 18%, transparent);
  }
}

/* The line of force the plates are threaded onto. */
.stack-beam {
  position: absolute;
  width: 2px;
  height: 250px;
  left: -1px;
  top: -125px;
  border-radius: 2px;
  background: linear-gradient(
    to top,
    transparent,
    color-mix(in srgb, var(--accent) 55%, transparent));
}

.stack-spark {
  position: absolute;
  width: 10px;
  height: 10px;
  margin: -5px;
  border-radius: 50%;
  background: var(--accent);
  box-shadow: 0 0 18px color-mix(in srgb, var(--accent) 80%, transparent);
  animation: spark-rise 4.6s ease-in-out infinite;
}

/* Rises and returns, so the end state is the start state. */
@keyframes spark-rise {
  0%, 100% { transform: translateY(96px);  opacity: 0.25; }
  50%      { transform: translateY(-96px); opacity: 1; }
}

.stack-ring {
  position: absolute;
  width: 300px;
  height: 300px;
  left: -150px;
  top: -150px;
  border: 1px dashed color-mix(in srgb, var(--accent) 26%, transparent);
  border-radius: 50%;
}

.ring-b {
  width: 200px;
  height: 200px;
  left: -100px;
  top: -100px;
}

.ring-a { animation: ring-a 30s linear infinite; }
.ring-b { animation: ring-b 40s linear infinite; }

@keyframes ring-a {
  from { transform: translateY(104px) rotateX(78deg) rotateZ(0deg); }
  to   { transform: translateY(104px) rotateX(78deg) rotateZ(360deg); }
}

@keyframes ring-b {
  from { transform: translateY(-108px) rotateX(74deg) rotateZ(360deg); }
  to   { transform: translateY(-108px) rotateX(74deg) rotateZ(0deg); }
}

/* --- the five steps beside it --------------------------------------------- */

.ladder {
  list-style: none;
  margin: 0;
  padding: 0;
  display: grid;
  gap: 4px;
}

.ladder-step {
  display: grid;
  grid-template-columns: 46px 1fr;
  gap: 18px;
  align-items: baseline;
  padding: 16px 0;
  border-bottom: 1px solid var(--ink-line);
}

.ladder-step:last-child {
  border-bottom: 0;
}

.ladder-num {
  font-family: var(--font-num);
  font-size: 26px;
  line-height: 1;
  color: var(--accent);
}

.ladder-title {
  font-family: var(--font-display);
  font-weight: 700;
  font-size: 17px;
  letter-spacing: -0.01em;
  color: var(--warm);
}

.ladder-note {
  margin-top: 5px;
  font-size: 14.5px;
  line-height: 1.6;
  color: var(--warm-mute);
}

/* --- tilt ----------------------------------------------------------------- */

/*
 * The perspective lives on the container, so every card in a row shares one
 * vanishing point and tilts as though they sit on the same plane.
 *
 * about.js writes --tilt-x/--tilt-y/--mx/--my. Each has a fallback here, so
 * with no JS, no fine pointer or reduced motion this resolves to a flat,
 * upright element.
 */
.tilt-deck {
  perspective: 1100px;
}

.tilt-card {
  position: relative;
  transform-style: preserve-3d;
  transform:
    rotateX(var(--tilt-x, 0deg))
    rotateY(var(--tilt-y, 0deg));
  transition:
    transform 0.18s ease,
    border-color 0.2s ease,
    box-shadow 0.25s ease;
}

.tilt-card:hover {
  border-color: var(--accent);
  box-shadow: 0 18px 38px color-mix(in srgb, var(--accent) 16%, transparent);
}

.tilt-glow {
  position: absolute;
  inset: 0;
  border-radius: inherit;
  pointer-events: none;
  opacity: 0;
  transition: opacity 0.25s ease;
  background: radial-gradient(
    220px circle at var(--mx, 50%) var(--my, 50%),
    color-mix(in srgb, var(--accent) 14%, transparent),
    transparent 70%);
}

.tilt-card:hover .tilt-glow {
  opacity: 1;
}

/* Lifted off the card face, so it parallaxes against the flat text when the
   card tilts. That parallax is what makes the tilt read as depth rather than
   as a skew — the same 26px as .deck-mark. */
.tilt-mark {
  position: relative;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 44px;
  height: 44px;
  margin-bottom: 16px;
  border-radius: 10px;
  font-family: var(--font-num);
  font-size: 20px;
  letter-spacing: 0.04em;
  color: var(--on-accent);
  background: linear-gradient(145deg, var(--accent), var(--accent-hi));
  box-shadow:
    inset 0 1px 0 color-mix(in srgb, var(--on-accent) 32%, transparent),
    0 6px 14px color-mix(in srgb, var(--accent) 28%, transparent);
  transform: translateZ(26px);
}

.tilt-title {
  position: relative;
  font-size: 18px;
  line-height: 1.3;
  letter-spacing: -0.01em;
  transform: translateZ(14px);
}

.tilt-body {
  position: relative;
  margin-top: 10px;
  font-size: 14.5px;
  line-height: 1.6;
  color: var(--warm-mute);
}

/* The portrait gets the same treatment. Its own ::after outline sits outside
   the tilted box, so the frame is the tilt target and the outline rides with
   it. */
.portrait-tilt {
  perspective: 900px;
}

/* --- the service list ----------------------------------------------------- */

.svc-list {
  border-top: 1px solid var(--ink-line);
}

.svc {
  display: grid;
  grid-template-columns: 56px 1fr 1.5fr;
  gap: 24px;
  align-items: start;
  padding: 24px 0;
  border-bottom: 1px solid var(--ink-line);
}

.svc-num {
  font-family: var(--font-num);
  font-size: 26px;
  line-height: 1.1;
  color: var(--accent);
  opacity: 0.7;
  transition: opacity 0.2s ease, transform 0.2s ease;
}

.svc:hover .svc-num {
  opacity: 1;
  transform: translateX(4px);
}

.svc-name {
  font-size: 18px;
  line-height: 1.3;
  letter-spacing: -0.01em;
}

.svc-points {
  margin: 0;
  padding-left: 18px;
  font-size: 14.5px;
  line-height: 1.75;
  color: var(--warm-mute);
}

/* --- the transformation story --------------------------------------------- */

.case-grid {
  display: grid;
  grid-template-columns: 1.45fr 1fr;
  border: 1px solid var(--ink-edge);
  border-radius: 14px;
  overflow: hidden;
  background: var(--ink-panel);
  box-shadow: var(--panel-shadow);
}

.case-copy {
  padding: 36px;
}

.case-figures {
  display: grid;
  align-content: center;
  gap: 22px;
  padding: 36px;
  background: var(--accent);
  color: var(--on-accent);
}

.case-figure b {
  display: block;
  font-family: var(--font-num);
  font-size: clamp(34px, 4vw, 50px);
  line-height: 1;
  letter-spacing: 0.01em;
}

.case-figure span {
  display: block;
  margin-top: 6px;
  font-family: var(--font-ui);
  font-size: 13px;
  line-height: 1.45;
  opacity: 0.85;
}

.flow-row {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
  margin-top: 18px;
}

.flow-chip {
  padding: 5px 11px;
  border: 1px solid var(--ink-edge);
  border-radius: 99px;
  background: var(--ink-raised);
  font-family: var(--font-ui);
  font-size: 12px;
  color: var(--warm-mute);
}

/* --- who it is for -------------------------------------------------------- */

.who {
  padding: 22px 24px;
  border: 1px solid var(--ink-edge);
  border-radius: 12px;
  background: var(--ink-panel);
}

.who h3 {
  font-size: 16px;
  line-height: 1.3;
}

.who p {
  margin-top: 6px;
  font-size: 14px;
  line-height: 1.6;
  color: var(--warm-mute);
}

/* --- the certificate carousel --------------------------------------------- */

/*
 * A plain scroll-snap track. The buttons in .carousel-nav are an ENHANCEMENT:
 * about.js wires them up and disables them at the ends, but with no JavaScript
 * the track still scrolls by drag, wheel, trackpad and keyboard — which is why
 * it carries tabindex and an accessible name in the markup rather than relying
 * on the buttons for keyboard access.
 */

.carousel-head {
  display: flex;
  align-items: flex-end;
  justify-content: space-between;
  flex-wrap: wrap;
  gap: 20px 24px;
  margin-bottom: 28px;
}

.carousel-nav {
  display: flex;
  gap: 10px;
  flex-shrink: 0;
}

.carousel-btn {
  width: 44px;
  height: 44px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  border: 1px solid var(--ink-edge);
  border-radius: 50%;
  background: var(--ink-panel);
  color: var(--warm-mute);
  font-size: 17px;
  line-height: 1;
  cursor: pointer;
  transition:
    border-color 0.2s ease,
    color 0.2s ease,
    background 0.2s ease,
    opacity 0.2s ease;
}

.carousel-btn:hover:not(:disabled) {
  border-color: var(--accent);
  background: var(--accent-soft);
  color: var(--accent);
}

/* Disabled rather than hidden: a control that vanishes at the end of the track
   moves its sibling, and the user loses the button they were aiming at. */
.carousel-btn:disabled {
  opacity: 0.35;
  cursor: default;
}

.carousel-track {
  display: flex;
  gap: 18px;
  margin: 0;
  padding: 4px 4px 18px;
  list-style: none;
  overflow-x: auto;
  scroll-snap-type: x mandatory;
  scroll-behavior: smooth;
  scrollbar-width: thin;
  scrollbar-color: var(--scroll-thumb) transparent;
  /*
   * The 4px inline padding keeps a card's focus ring and hover shadow from
   * being clipped by the scroll container.
   *
   * scroll-padding-inline MUST match it. Without it the snapport starts at the
   * scrollport edge while the first card starts 4px in, so the track's resting
   * position is scrollLeft: 4 rather than 0 — and the "scroll left" button
   * never reaches its disabled state because it is never quite at the start.
   */
  scroll-padding-inline: 4px;
}

.cert-card {
  position: relative;
  flex: 0 0 296px;
  display: flex;
  flex-direction: column;
  gap: 8px;
  padding: 22px 22px 20px;
  border: 1px solid var(--ink-edge);
  border-radius: 14px;
  background: var(--ink-panel);
  box-shadow: var(--panel-shadow);
  scroll-snap-align: start;
  transition:
    transform 0.2s ease,
    border-color 0.2s ease,
    box-shadow 0.25s ease;
}

.cert-card:hover {
  transform: translateY(-4px);
  border-color: var(--accent);
  box-shadow: 0 18px 38px color-mix(in srgb, var(--accent) 16%, transparent);
}

/*
 * A centred sheen. Unlike .tilt-glow this needs no JavaScript — the carousel
 * cards do not tilt, because the container's perspective origin moves as the
 * track scrolls and cards far from centre would shear rather than tilt.
 */
.cert-glow {
  position: absolute;
  inset: 0;
  border-radius: inherit;
  pointer-events: none;
  opacity: 0;
  transition: opacity 0.25s ease;
  background: radial-gradient(
    200px circle at 50% 0%,
    color-mix(in srgb, var(--accent) 14%, transparent),
    transparent 70%);
}

.cert-card:hover .cert-glow {
  opacity: 1;
}

/*
 * THE CERTIFICATE IS THE CARD. The scan sits on top, the metadata under it,
 * and clicking opens the full size. Two cards have no scan and fall back to
 * .is-blank — a monogram panel, so the row still reads as one set.
 */

.cert-shot {
  position: relative;
  display: block;
  width: 100%;
  aspect-ratio: 4 / 3;
  margin: -22px -22px 16px;
  width: calc(100% + 44px);
  border: 0;
  border-bottom: 1px solid var(--ink-edge);
  border-radius: 14px 14px 0 0;
  /*
   * overflow: hidden is what lets the image scale inside a fixed frame. Safe
   * here — unlike .edu-card, these never carry transform-style: preserve-3d,
   * so there is no 3D context for it to flatten.
   */
  overflow: hidden;
  background: var(--ink-raised);
  padding: 0;
  cursor: zoom-in;
}

.cert-shot img {
  display: block;
  width: 100%;
  height: 100%;
  /* cover, not contain: a letterboxed certificate in a 4:3 frame is mostly
     empty background. The metadata underneath says what it is, and the zoom
     shows the whole thing. */
  object-fit: cover;
  object-position: top center;
  transition: transform 0.45s ease, filter 0.3s ease;
}

.cert-card:hover .cert-shot img {
  transform: scale(1.05);
}

/* The magnifier badge, revealed on hover and on keyboard focus. */
.cert-zoom {
  position: absolute;
  right: 10px;
  bottom: 10px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 32px;
  height: 32px;
  border-radius: 50%;
  color: var(--on-accent);
  background: var(--accent);
  box-shadow: 0 4px 14px color-mix(in srgb, var(--accent) 45%, transparent);
  opacity: 0;
  transform: translateY(6px) scale(0.9);
  transition: opacity 0.25s ease, transform 0.25s ease;
  pointer-events: none;
}

.cert-card:hover .cert-zoom,
.cert-shot:focus-visible .cert-zoom {
  opacity: 1;
  transform: translateY(0) scale(1);
}

.cert-shot:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: -2px;
}

/* No scan: the issuer monogram, centred, so the card still reads as a card. */
.cert-shot.is-blank {
  display: flex;
  align-items: center;
  justify-content: center;
  cursor: default;
  background:
    radial-gradient(120px circle at 50% 40%,
      color-mix(in srgb, var(--accent) 12%, transparent), transparent 70%),
    var(--ink-raised);
}

.cert-body {
  display: flex;
  flex-direction: column;
  gap: 8px;
  flex: 1;
}

/* The issuer monogram — never a logo; see CertificateCatalogue's remarks. */
.cert-mark {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 48px;
  height: 48px;
  flex-shrink: 0;
  border-radius: 12px;
  font-family: var(--font-num);
  font-size: 20px;
  letter-spacing: 0.05em;
  color: var(--on-accent);
  background: linear-gradient(145deg, var(--accent), var(--accent-hi));
  box-shadow: inset 0 1px 0 color-mix(in srgb, var(--on-accent) 30%, transparent);
}

.cert-cat {
  align-self: flex-start;
  padding: 3px 9px;
  border: 1px solid var(--ink-edge);
  border-radius: 99px;
  font-family: var(--font-ui);
  font-size: 10px;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--warm-faint);
  white-space: nowrap;
}

.cert-title {
  font-size: 16px;
  line-height: 1.35;
  letter-spacing: -0.01em;
}

/*
 * The auto margin lives here rather than on .cert-when, so everything BELOW
 * the issuer is pushed to the bottom of the card. That keeps the baseline
 * consistent across cards whose titles run one line or three — and across the
 * undated ones, which have no .cert-when at all and would otherwise leave
 * their rule floating halfway up.
 */
.cert-issuer {
  margin-bottom: auto;
  font-family: var(--font-ui);
  font-size: 13px;
  color: var(--warm-mute);
}

.cert-when {
  padding-top: 10px;
  font-family: var(--font-num);
  font-size: 14px;
  letter-spacing: 0.06em;
  color: var(--accent);
}

/* The one flourish that says "certificate": a short accent rule, the way a
   printed one carries a line under the award. */
.cert-rule {
  width: 34px;
  height: 2px;
  margin-top: 12px;
  border-radius: 2px;
  background: linear-gradient(90deg, var(--accent), transparent);
}

/* --- the lightbox --------------------------------------------------------- */

/*
 * <dialog> + showModal() gives the focus trap, Escape, inertness and
 * ::backdrop for free. Everything here is presentation.
 */
.lightbox {
  width: min(96vw, 1200px);
  max-width: none;
  max-height: 92vh;
  padding: 0;
  border: 1px solid var(--ink-edge);
  border-radius: 16px;
  background: var(--ink-panel);
  color: var(--warm);
  overflow: visible;
}

.lightbox::backdrop {
  /* The one place a literal is unavoidable: ::backdrop is rendered in the top
     layer, outside the tree, so it cannot see :root's custom properties. */
  background: rgba(16, 19, 34, 0.82);
  backdrop-filter: blur(3px);
}

.lightbox[open] {
  animation: lightbox-in 0.22s ease-out;
}

.lightbox[open]::backdrop {
  animation: lightbox-fade 0.22s ease-out;
}

@keyframes lightbox-in {
  from { opacity: 0; transform: scale(0.97) translateY(8px); }
  to   { opacity: 1; transform: none; }
}

@keyframes lightbox-fade {
  from { opacity: 0; }
  to   { opacity: 1; }
}

.lightbox-figure {
  margin: 0;
  display: flex;
  flex-direction: column;
}

.lightbox-figure img {
  display: block;
  width: 100%;
  max-height: calc(92vh - 92px);
  object-fit: contain;
  border-radius: 15px 15px 0 0;
  /* Certificates are printed on white. On the dark theme an unpadded scan is a
     bright rectangle; the plate keeps it deliberate. */
  background: var(--logo-plate);
}

.lightbox-figure figcaption {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: 4px 12px;
  padding: 16px 20px;
  border-top: 1px solid var(--ink-line);
}

.lightbox-name {
  font-family: var(--font-display);
  font-weight: 700;
  font-size: 15px;
  letter-spacing: -0.01em;
}

.lightbox-meta {
  font-family: var(--font-ui);
  font-size: 13px;
  color: var(--warm-faint);
}

.lightbox-close,
.lightbox-step {
  position: absolute;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  border: 1px solid var(--ink-edge);
  border-radius: 50%;
  background: var(--ink-panel);
  color: var(--warm-mute);
  cursor: pointer;
  transition: border-color 0.2s ease, color 0.2s ease, background 0.2s ease,
              opacity 0.2s ease;
}

.lightbox-close:hover,
.lightbox-step:hover:not(:disabled) {
  border-color: var(--accent);
  background: var(--accent-soft);
  color: var(--accent);
}

.lightbox-close {
  top: -14px;
  right: -14px;
  width: 40px;
  height: 40px;
  font-size: 24px;
  line-height: 1;
}

.lightbox-step {
  top: 50%;
  width: 44px;
  height: 44px;
  margin-top: -22px;
  font-size: 17px;
}

/* Disabled rather than hidden at the ends: a control that vanishes moves its
   sibling, and the user loses the button they were aiming at. */
.lightbox-step:disabled {
  opacity: 0.3;
  cursor: default;
}

.lightbox-prev { left: -22px; }
.lightbox-next { right: -22px; }

/* --- the standing call to action ------------------------------------------ */

/*
 * Below the header's z-index 50 on purpose: the mobile menu sheet must never
 * be overlapped by a floating button.
 */
.sticky-cta {
  position: fixed;
  right: 20px;
  bottom: 20px;
  z-index: 30;
  padding: 13px 22px;
  border-radius: 99px;
  background: var(--accent);
  color: var(--on-accent);
  font-family: var(--font-ui);
  font-size: 14px;
  font-weight: 500;
  text-decoration: none;
  box-shadow: 0 10px 30px color-mix(in srgb, var(--accent) 40%, transparent);
  transition: transform 0.2s ease, box-shadow 0.2s ease, opacity 0.2s ease;
}

.sticky-cta:hover,
.sticky-cta:focus-visible {
  transform: translateY(-2px);
  color: var(--on-accent);
  text-decoration: none;
  box-shadow: 0 14px 36px color-mix(in srgb, var(--accent) 50%, transparent);
}

/*
 * about.js adds this once the contact band is on screen — a button offering to
 * take you somewhere you are already looking is just something in the way.
 *
 * The default is visible, so with no JS the button simply stays put and works.
 */
.sticky-cta.is-gone {
  opacity: 0;
  transform: translateY(14px);
  pointer-events: none;
}

/* ------------------------------------------- 12h. accounts / sign-in ------ */

/*
 * Registration, sign-in, the profile, and the two legal pages.
 *
 * Nearly all of it reuses §9 forms and §8 panels — what is here is the handful
 * of shapes those sections do not have: a narrow centred column, provider
 * buttons, a labelled rule, consent checkboxes and the account area in the
 * header.
 *
 * NO LITERAL COLOURS, same as everywhere else. The one exception on these
 * pages is in the markup rather than here: the Google and Microsoft logos in
 * _ExternalSignIn.cshtml carry fixed hex, because they are somebody else's
 * trademark and must not follow our theme.
 */

/*
 * A sign-in form is one column of short fields. Letting it run to the full
 * 1200px content width makes a six-field form look like a tax return, so it
 * gets its own measure — 520px, which is about the width at which a label and
 * its input still read as a pair.
 */
.auth-shell {
  max-width: 520px;
  margin-inline: auto;
}

/* --- "continue with Google / Microsoft" ---------------------------------- */

.provider-row {
  display: grid;
  gap: 12px;
  margin-bottom: 20px;
}

/*
 * Each button is its own <form>, so the row is a grid of forms rather than of
 * buttons — hence the stretch below. Without it every button sizes to its text
 * and "Sign up with Google" and "Sign up with Microsoft" come out different
 * widths, which reads as a mistake.
 */
.provider-row form {
  display: grid;
}

.provider-btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 10px;
  width: 100%;
}

/*
 * A rule with a word in it. The background has to match whatever is behind the
 * line, which inside a .panel is --ink-panel — so this is deliberately NOT
 * reusable on a plain section background without changing that one value.
 */
.or-rule {
  position: relative;
  margin: 28px 0 24px;
  text-align: center;
}

.or-rule::before {
  content: '';
  position: absolute;
  inset-inline: 0;
  top: 50%;
  height: 1px;
  background: var(--ink-line);
}

.or-rule span {
  position: relative;
  padding: 0 14px;
  background: var(--ink-panel);
  font-family: var(--font-ui);
  font-size: 12px;
  color: var(--warm-ghost);
}

/* --- consent checkboxes --------------------------------------------------- */

.consent-block {
  display: grid;
  gap: 14px;
  margin-bottom: 28px;
  padding: 18px;
  border: 1px solid var(--ink-edge);
  border-radius: 8px;
  background: var(--ink);
}

/*
 * The label wraps the input so the whole line is a hit target — a 16px
 * checkbox on a phone is not one. align-items is flex-start rather than
 * centre because these labels run to two and three lines.
 */
.consent-item label {
  display: flex;
  align-items: flex-start;
  gap: 12px;
  margin: 0;
  font-size: 14px;
  line-height: 1.6;
  color: var(--warm-mute);
  cursor: pointer;
}

.consent-item input[type='checkbox'] {
  flex-shrink: 0;
  width: 16px;
  height: 16px;
  margin-top: 3px;
  accent-color: var(--accent);
  cursor: pointer;
}

.consent-item a {
  color: var(--accent);
  text-decoration: underline;
  text-underline-offset: 2px;
}

/* "Required." / "Optional — …" — the part that says what the box means. */
.consent-item em {
  display: block;
  margin-top: 3px;
  font-style: normal;
  font-size: 12px;
  color: var(--warm-ghost);
}

/* --- validation ----------------------------------------------------------- */

/*
 * Server-rendered, one per field. The tag helper emits the element whether or
 * not there is anything in it, so this is :empty-guarded — otherwise every
 * valid field carries 8px of invisible margin and the form breathes unevenly.
 */
.field-error {
  display: block;
  margin-top: 8px;
  font-family: var(--font-ui);
  font-size: 12.5px;
  line-height: 1.5;
  color: var(--danger);
}

.field-error:empty {
  display: none;
}

/* --- the line under the form --------------------------------------------- */

.auth-switch {
  margin-top: 28px;
  padding-top: 20px;
  border-top: 1px solid var(--ink-line);
  font-family: var(--font-ui);
  font-size: 14px;
  text-align: center;
  color: var(--warm-mute);
}

.auth-switch a {
  color: var(--accent);
  font-weight: 500;
}

/* --- the account area in the header --------------------------------------- */

/*
 * Signed out: a quiet text link, NOT a second button. The accent CTA beside it
 * is the thing we want pressed, and two buttons competing leaves the header
 * with no focal point.
 */
.account-link {
  font-family: var(--font-ui);
  font-size: 14px;
  color: var(--warm-dim);
  transition: color 0.2s ease;
}

.account-link:hover {
  color: var(--warm);
}

/* Signed in: the name, which needs to look like a destination rather than
   like a greeting printed in the chrome. */
.account-chip {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  max-width: 180px;

  /*
   * A flex item will not shrink below its content without this — min-width is
   * `auto` by default — so a long enough first name pinned the chip at its
   * 180px cap and pushed the whole header past the viewport. Measured at
   * 1060px with a 35-character name: 29px of horizontal overflow on every page.
   *
   * The nav had ~64px of slack before "The Maker" was added to it, which is
   * what turned a theoretical limit into a reachable one. With min-width: 0 the
   * chip gives way and the span's existing ellipsis does the rest.
   */
  min-width: 0;
  padding: 7px 14px;
  border: 1px solid var(--ink-edge);
  border-radius: 999px;
  font-family: var(--font-ui);
  font-size: 13px;
  color: var(--warm-dim);
  transition: border-color 0.2s ease, color 0.2s ease;
}

.account-chip:hover,
.account-chip.is-active {
  border-color: var(--accent);
  color: var(--accent);
}

/* A long name truncates rather than pushing the CTA off the end of the bar. */
.account-chip span {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* --- a button that has to look like a link -------------------------------- */

/*
 * "Remove" next to a linked provider, and "Sign out" in the mobile sheet. Both
 * change state, so both must be a <button> inside a POST form — but both sit
 * inline in a sentence where a button would be absurd. This is the one place
 * the two requirements pull apart, and the markup wins.
 */
.link-button {
  padding: 0;
  border: 0;
  background: none;
  font-family: var(--font-ui);
  font-size: inherit;
  color: var(--accent);
  text-decoration: underline;
  text-underline-offset: 2px;
  cursor: pointer;
}

.link-button:hover {
  opacity: 0.75;
}

/* --- the one destructive button on the site ------------------------------- */

/*
 * Outlined rather than filled. A solid red button is the most visually
 * prominent thing on the page, which is the wrong emphasis for the action you
 * least want pressed by accident — it fills on hover instead, once the pointer
 * is already there on purpose.
 */
.btn-danger {
  background: transparent;
  border-color: var(--danger);
  color: var(--danger);
}

.btn-danger:hover:not(:disabled) {
  background: var(--danger);
  color: var(--ink-panel);
}

/* ------------------------------------------- 12i. admin portal ------------ */

/*
 * /admin — the user list, the contact queue and the audit trail.
 *
 * Dense where the rest of the site is spacious, because this is a working
 * surface rather than a page anyone reads for pleasure: smaller type, tighter
 * rows, tabular numerals. It still uses the same tokens, so it follows the
 * theme like everything else.
 */

.admin-bar {
  border-bottom: 1px solid var(--ink-line);
  background: var(--ink-raised);
}

.admin-bar .section {
  display: flex;
  align-items: center;
  gap: 24px;
  padding-top: 12px;
  padding-bottom: 12px;
}

.admin-mark {
  font-family: var(--font-num);
  font-size: 15px;
  letter-spacing: 0.12em;
  color: var(--accent);
}

.admin-bar nav {
  display: flex;
  gap: 20px;
}

.admin-bar nav a {
  font-family: var(--font-ui);
  font-size: 14px;
  color: var(--warm-dim);
  transition: color 0.2s ease;
}

.admin-bar nav a:hover,
.admin-bar nav a.is-active {
  color: var(--accent);
}

/* Pushed to the end, and the first thing to go when the bar runs out of room. */
.admin-note {
  margin-left: auto;
  font-family: var(--font-ui);
  font-size: 12px;
  color: var(--warm-ghost);
}

/* --- the dashboard -------------------------------------------------------- */

.stat-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(190px, 1fr));
  gap: 16px;
}

.stat-card {
  display: flex;
  flex-direction: column;
  gap: 4px;
  padding: 20px;
  border: 1px solid var(--ink-edge);
  border-radius: 8px;
  background: var(--ink-panel);
}

/* The one card that should pull the eye — an unworked queue. */
.stat-card.is-flagged {
  border-color: var(--accent);
  background: var(--accent-soft);
}

.stat-num {
  font-family: var(--font-num);
  font-size: 40px;
  line-height: 1;
  color: var(--warm);
}

.stat-card.is-flagged .stat-num {
  color: var(--accent);
}

.stat-label {
  font-family: var(--font-ui);
  font-size: 13px;
  color: var(--warm-dim);
}

.stat-sub {
  font-family: var(--font-ui);
  font-size: 12px;
  color: var(--warm-ghost);
}

/* --- filters and tools ---------------------------------------------------- */

.admin-tools {
  display: grid;
  gap: 16px;
  margin-bottom: 24px;
}

.filter-row {
  display: flex;
  flex-wrap: wrap;
  align-items: flex-end;
  gap: 12px;
  padding: 16px;
  border: 1px solid var(--ink-edge);
  border-radius: 8px;
  background: var(--ink-panel);
}

/* Each control sizes to its label rather than stretching across the row. */
.filter-row .field {
  margin-bottom: 0;
  min-width: 150px;
}

/* --- tables --------------------------------------------------------------- */

/*
 * The wrapper scrolls, not the page. Seven columns of account data do not fit
 * on a laptop, let alone a phone, and a table that forces the WHOLE document
 * sideways takes the navigation with it.
 */
.admin-table-wrap {
  overflow-x: auto;
  border: 1px solid var(--ink-edge);
  border-radius: 8px;
  background: var(--ink-panel);
}

.admin-table {
  width: 100%;
  border-collapse: collapse;
  font-family: var(--font-ui);
  font-size: 13.5px;
}

.admin-table th {
  position: sticky;
  top: 0;
  padding: 12px 14px;
  border-bottom: 1px solid var(--ink-line);
  background: var(--ink-raised);
  font-family: var(--font-num);
  font-size: 12px;
  font-weight: 400;
  letter-spacing: 0.08em;
  text-align: left;
  white-space: nowrap;
  color: var(--warm-dim);
}

.admin-table td {
  padding: 12px 14px;
  border-bottom: 1px solid var(--ink-line);
  color: var(--warm-mute);
  vertical-align: top;
}

.admin-table tr:last-child td {
  border-bottom: 0;
}

.admin-table tbody tr:hover {
  background: var(--ink-raised);
}

/* An unworked row, so the queue reads at a glance. */
.admin-table tr.is-new td:first-child {
  box-shadow: inset 2px 0 0 var(--accent);
}

.admin-table a {
  color: var(--accent);
}

/*
 * Tabular numerals on anything compared down a column — masked numbers, ids,
 * counts. Proportional digits make two addresses of the same length look like
 * different lengths, which is exactly the comparison this column is for.
 */
.admin-table .is-mono,
.hint.is-mono {
  font-variant-numeric: tabular-nums;
  font-feature-settings: 'tnum';
  white-space: nowrap;
}

.admin-table .is-actions {
  display: flex;
  gap: 12px;
  white-space: nowrap;
}

.tag {
  display: inline-block;
  margin-left: 6px;
  padding: 2px 7px;
  border-radius: 3px;
  background: var(--accent-soft);
  font-family: var(--font-num);
  font-size: 11px;
  letter-spacing: 0.06em;
  color: var(--accent);
  vertical-align: 1px;
}

.tag.is-warn {
  background: var(--danger-soft);
  color: var(--danger);
}

/* --- the strip under a table ---------------------------------------------- */

.admin-foot {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: 20px;
  margin-top: 20px;
}

.pager {
  display: flex;
  align-items: center;
  gap: 12px;
  font-family: var(--font-ui);
  font-size: 13px;
  color: var(--warm-ghost);
}

/*
 * The export controls. The sentence is part of the control, not a caption —
 * this is the one button on the site that turns the encrypted store into a
 * plaintext file, and it should not be possible to press it without having read
 * what it does.
 */
.export-row {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 12px;
  max-width: 560px;
  margin-left: auto;
  padding: 14px 16px;
  border: 1px solid var(--ink-edge);
  border-radius: 8px;
  background: var(--ink-panel);
}

.export-row .hint {
  flex: 1 1 240px;
  margin-top: 0;
}

/* --- one message ---------------------------------------------------------- */

/*
 * pre-wrap, so the paragraphs somebody typed into a textarea survive. Razor
 * encodes the content, so markup in a message is shown rather than rendered —
 * which matters when the text is whatever a stranger submitted.
 */
.message-body {
  padding: 18px;
  border: 1px solid var(--ink-edge);
  border-radius: 8px;
  background: var(--ink);
  font-family: var(--font-display);
  font-weight: 300;
  font-size: 15px;
  line-height: 1.75;
  white-space: pre-wrap;
  overflow-wrap: anywhere;      /* a 200-character unbroken URL must not widen the panel */
  color: var(--warm);
}

/* --- the sign-in wall switch ---------------------------------------------- */

/*
 * The one control in the portal that changes what visitors can do. It reads as
 * a statement with a status light rather than as a form row, because what an
 * administrator needs first is "which way is it set right now".
 */
.switch-state {
  display: flex;
  align-items: flex-start;
  gap: 10px;
  font-family: var(--font-ui);
  font-size: 15px;
  line-height: 1.6;
  color: var(--warm);
}

.switch-dot {
  flex-shrink: 0;
  width: 9px;
  height: 9px;
  margin-top: 6px;
  border: 1px solid var(--ink-rule);
  border-radius: 50%;
  background: var(--ink-raised);
}

/* Accent, not green/red: "a sign-in is required" is a setting, not a fault, and
   this stylesheet has exactly one accent colour. */
.switch-dot.is-on {
  border-color: var(--accent);
  background: var(--accent);
  box-shadow: 0 0 0 3px color-mix(in srgb, var(--accent) 22%, transparent);
}

/* --- the link in the site header ------------------------------------------ */

.admin-link {
  padding: 6px 12px;
  border: 1px solid var(--accent);
  border-radius: 999px;
  font-family: var(--font-ui);
  font-size: 13px;
  color: var(--accent);
  transition: background 0.2s ease, color 0.2s ease;
}

.admin-link:hover {
  background: var(--accent);
  color: var(--on-accent);
}

/* --- the contact form's honeypot ------------------------------------------ */

/*
 * Hidden from people and from assistive technology, present in the DOM for
 * anything that fills every input it finds.
 *
 * NOT display:none and NOT type="hidden" — the bots worth catching skip both.
 * Taken out of the layout and off the tab order instead.
 */
.honeypot {
  position: absolute;
  left: -9999px;
  width: 1px;
  height: 1px;
  overflow: hidden;
}

/* ------------------------------------------- 12j. admin-only counters ----- */

/*
 * The view counters (2026-10-06): a dock on the hero, and a badge on every tool
 * and recipe card.
 *
 * NONE OF THIS IS EVER SENT TO A VISITOR. The markup is written by
 * _VisitorDock and _ViewBadge, both of which ask the authorization policy
 * first and render nothing at all when the answer is no — so these rules exist
 * only for the handful of requests that are an administrator's. Hiding them
 * with CSS instead would ship the site's traffic figures in the HTML of every
 * page and leave one stylesheet edit between them and the public.
 *
 * They read as instrumentation rather than as design: dashed borders, the
 * numeric face, no shadow, nothing competing with the page they sit on.
 */

.visitor-dock {
  position: absolute;
  z-index: 11;
  top: calc(var(--header-h) + 56px);
  right: var(--gutter);
  width: 232px;
  padding: 18px;
  border: 1px dashed var(--accent);
  border-radius: 12px;
  background: color-mix(in srgb, var(--ink-panel) 88%, transparent);
  backdrop-filter: blur(6px);
}

.visitor-dock-head {
  display: flex;
  align-items: center;
  gap: 7px;
  font-family: var(--font-ui);
  font-size: 11px;
  font-weight: 500;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--accent);
}

/* A live-indicator dot. Its keyframes start and end lit, so reduced motion
   leaves it solid rather than permanently dim — see §13. */
.visitor-dock-dot {
  width: 7px;
  height: 7px;
  border-radius: 50%;
  background: var(--accent);
  animation: dock-pulse 2.4s ease-in-out infinite;
}

.visitor-dock-stat {
  display: flex;
  flex-direction: column;
  margin-top: 14px;
}

.visitor-dock-num {
  font-family: var(--font-num);
  font-size: 34px;
  line-height: 1;
  letter-spacing: 0.02em;
  color: var(--warm);
}

.visitor-dock-label {
  margin-top: 3px;
  font-family: var(--font-ui);
  font-size: 12px;
  color: var(--warm-mute);
}

.visitor-dock-sub {
  font-family: var(--font-ui);
  font-size: 11px;
  color: var(--warm-faint);
}

.visitor-dock-note {
  margin-top: 14px;
  padding-top: 12px;
  border-top: 1px solid var(--ink-line);
  font-family: var(--font-ui);
  font-size: 11px;
  line-height: 1.5;
  color: var(--warm-faint);
}

.visitor-dock-link {
  display: inline-block;
  margin-top: 10px;
  font-family: var(--font-ui);
  font-size: 12px;
  color: var(--accent);
}

/* --- the badge on a card -------------------------------------------------- */

.view-badge {
  display: inline-flex;
  flex-shrink: 0;
  align-items: center;
  gap: 5px;
  padding: 1px 8px;
  border: 1px dashed var(--accent);
  border-radius: 99px;
  font-family: var(--font-num);
  font-size: 13px;
  letter-spacing: 0.06em;
  color: var(--accent);
}

.view-badge-num {
  line-height: 1.4;
}

/* The row the tool card's number shares with its badge. Without this the badge
   would sit on its own line and push the title down on every card — including
   the ones nobody is counting. */
.card-num-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 10px;
}

/* Same job on a recipe card, where the badge sits beside the RECIPE / SOON
   pill rather than replacing it. */
.deck-head-right {
  display: inline-flex;
  flex-shrink: 0;
  align-items: center;
  gap: 8px;
}

/* ------------------------------------- 12k. seo headings + claude kit ---- */

/*
 * A descriptive second line INSIDE an h1 (2026-10-09).
 *
 * The page headings were brand labels — "TEXT TO SPEECH", "ALL TOOLS", a
 * provider's bare name — which read well and tell a search engine nothing
 * about what the page is for. The note carries the words people search with
 * ("free", "online", "C# examples") as part of the heading, styled down to a
 * subtitle so the design does not change. Block, so it always sits under the
 * label rather than running on from it.
 */
.h1-note {
  display: block;
  margin-top: 10px;
  font-family: var(--font-ui);
  font-size: clamp(13px, 1.3vw, 16px);
  font-weight: 400;
  line-height: 1.4;
  letter-spacing: 0.06em;
  text-transform: none;
  color: var(--warm-mute);
}

/* The homepage kicker, moved inside the h1. .kicker already styles it; this
   only makes it a block above the slogan, as it was when it was a <p>. */
.h1-kicker {
  display: block;
  margin-bottom: 12px;
  line-height: 1.5;
}

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

/* "Build it yourself" at the foot of a tool page. */
.related-band {
  border-top: 1px solid var(--ink-line);
}

/* The hub's comparison table. Scrolls sideways on a phone rather than
   squeezing five columns into 375px. */
.kit-compare-wrap {
  overflow-x: auto;
  border: 1px solid var(--ink-line);
  border-radius: 8px;
}

.kit-compare {
  width: 100%;
  min-width: 760px;
  border-collapse: collapse;
  font-family: var(--font-ui);
  font-size: 14px;
  line-height: 1.5;
}

.kit-compare th,
.kit-compare td {
  padding: 14px 16px;
  text-align: left;
  vertical-align: top;
  border-bottom: 1px solid var(--ink-line);
  color: var(--warm-mute);
}

.kit-compare thead th {
  font-family: var(--font-num);
  font-size: 15px;
  font-weight: 400;
  letter-spacing: 0.08em;
  color: var(--accent);
  background: var(--ink-raised);
}

.kit-compare tbody th {
  font-family: var(--font-display);
  font-size: 16px;
  white-space: nowrap;
}

.kit-compare tbody tr:last-child th,
.kit-compare tbody tr:last-child td {
  border-bottom: 0;
}

.kit-compare code {
  font-family: var(--font-mono);
  font-size: 12.5px;
  color: var(--warm);
}

/* FAQ — native <details>, so it opens without JavaScript and the answers are
   in the HTML a crawler reads either way. */
.faq-list {
  display: grid;
  gap: 10px;
  max-width: 820px;
}

.faq-item {
  border: 1px solid var(--ink-line);
  border-radius: 8px;
  background: var(--ink-panel);
}

.faq-item summary {
  padding: 16px 18px;
  cursor: pointer;
  font-family: var(--font-display);
  font-size: 17px;
  font-weight: 700;
  color: var(--warm);
}

.faq-item summary:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
}

.faq-item[open] summary {
  color: var(--accent);
}

.faq-item p {
  padding: 0 18px 18px;
  font-size: 16px;
  line-height: 1.65;
  color: var(--warm-mute);
}

/* The quiet third route under the homepage hero buttons — the tools, now
   that the hero leads with code. A sentence, not a third button: three
   buttons in a row is how a hero stops having a focal point. */
.hero-alt {
  margin-top: 16px;
  font-family: var(--font-ui);
  font-size: 14px;
  color: var(--warm-mute);
}

/* The "try it in the browser" line on a recipe page. */
.recipe-tool-link {
  margin-top: 14px;
  font-family: var(--font-ui);
  font-size: 14px;
  color: var(--warm-mute);
}

/* ------------------------------------------- 12l. contact replies ------- */

/* The thread and reply box on /admin/messages/{id}. */
.reply-thread {
  list-style: none;
  margin: 0;
  padding: 0;
  display: grid;
  gap: 14px;
}

.reply-item {
  padding: 14px 16px;
  border-left: 3px solid var(--accent);
  background: var(--ink-raised);
  border-radius: 4px;
}

.reply-item.is-failed {
  border-left-color: var(--danger);
}

.reply-meta {
  margin: 0 0 8px;
  font-family: var(--font-ui);
  font-size: 13px;
  color: var(--warm-mute);
}

.reply-status {
  color: var(--accent);
}

.reply-status.is-failed {
  color: var(--danger);
}

.reply-form textarea {
  width: 100%;
}

.reply-actions {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: 14px;
  margin-top: 14px;
}

.reply-resolve {
  display: flex;
  align-items: center;
  gap: 8px;
  font-family: var(--font-ui);
  font-size: 14px;
  color: var(--warm-mute);
}

/* ------------------------------------------- 12m. new-drops popup -------- */

/*
 * A centred modal: a native <dialog> opened with showModal(), which centres it
 * and puts it in the top layer above everything, the sticky header included.
 * Every colour is a token. .is-open (added a frame after showModal) runs the
 * entrance; removing it before close() runs the exit.
 */
.drop-pop {
  width: min(460px, calc(100vw - 32px));
  max-height: calc(100dvh - 32px);
  padding: 0;
  overflow: auto;
  background: var(--ink-panel);
  color: var(--warm);
  border: 1px solid var(--ink-edge);
  border-radius: 18px;
  box-shadow: var(--menu-shadow);
  opacity: 0;
  transform: translateY(24px) scale(0.96);
  transition: opacity 0.3s ease, transform 0.35s cubic-bezier(0.2, 0.9, 0.3, 1.2);
}

.drop-pop.is-open {
  opacity: 1;
  transform: none;
}

/* The page behind, dimmed and softened — from the page colour, so it reads
   as dusk on the light theme and deeper night on the dark one. */
.drop-pop::backdrop {
  background: color-mix(in srgb, var(--ink) 72%, transparent);
  backdrop-filter: blur(4px);
  opacity: 0;
  transition: opacity 0.3s ease;
}

.drop-pop.is-open::backdrop {
  opacity: 1;
}

.drop-pop-inner {
  position: relative;
  padding: 34px 30px 22px;
}

/* the accent rail, with a sheen that runs along it */
.drop-pop-rail {
  position: absolute;
  inset: 0 0 auto 0;
  height: 4px;
  background: linear-gradient(90deg, var(--accent), var(--accent-soft), var(--accent));
  background-size: 200% 100%;
  animation: drop-rail 3s linear infinite;
}

@keyframes drop-rail {
  from { background-position: 0% 0; }
  to { background-position: 200% 0; }
}

.drop-pop-close {
  position: absolute;
  top: 12px;
  right: 12px;
  display: grid;
  place-items: center;
  width: 32px;
  height: 32px;
  border: 0;
  border-radius: 8px;
  background: transparent;
  color: var(--warm-mute);
  cursor: pointer;
}

.drop-pop-close:hover,
.drop-pop-close:focus-visible {
  background: var(--ink-raised);
  color: var(--warm);
}

.drop-pop-kicker {
  display: flex;
  align-items: center;
  gap: 8px;
  margin: 0 0 10px;
  font-family: var(--font-ui);
  font-size: 11px;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--accent);
}

/* a "live" dot: solid centre, expanding ring */
.drop-pop-dot {
  position: relative;
  width: 8px;
  height: 8px;
  border-radius: 50%;
  background: var(--accent);
}

.drop-pop-dot::after {
  content: "";
  position: absolute;
  inset: -4px;
  border-radius: 50%;
  border: 2px solid var(--accent);
  opacity: 0;
  animation: drop-ping 1.8s ease-out infinite;
}

@keyframes drop-ping {
  0% { transform: scale(0.5); opacity: 0.8; }
  100% { transform: scale(1.6); opacity: 0; }
}

.drop-pop-title {
  margin: 0 32px 10px 0;
  font-size: 22px;
  line-height: 1.2;
  color: var(--warm);
}

.drop-pop-title span {
  color: var(--accent);
}

.drop-pop-body {
  margin: 0 0 14px;
  font-size: 14px;
  line-height: 1.6;
  color: var(--warm-mute);
}

/* what has landed — a seamless loop, the same -50% trick as the marquee */
.drop-pop-ticker {
  margin: 0 -30px 16px;
  padding: 8px 0;
  overflow: hidden;
  border-top: 1px dashed var(--ink-edge);
  border-bottom: 1px dashed var(--ink-edge);
  mask-image: linear-gradient(90deg, transparent, #000 12%, #000 88%, transparent);
}

.drop-pop-track {
  display: flex;
  gap: 8px;
  width: max-content;
  animation: drop-ticker 22s linear infinite;
}

.drop-pop:hover .drop-pop-track {
  animation-play-state: paused;
}

@keyframes drop-ticker {
  from { transform: translateX(0); }
  to { transform: translateX(-50%); }
}

.drop-pop-chip {
  padding: 4px 10px;
  border-radius: 999px;
  background: var(--accent-soft);
  font-family: var(--font-ui);
  font-size: 12px;
  white-space: nowrap;
  color: var(--accent);
}

.drop-pop-insta {
  display: flex;
  width: 100%;
  justify-content: center;
  align-items: center;
  gap: 10px;
}

.drop-pop-or {
  display: flex;
  align-items: center;
  gap: 10px;
  margin: 14px 0 10px;
  font-family: var(--font-ui);
  font-size: 12px;
  color: var(--warm-ghost);
}

.drop-pop-or::before,
.drop-pop-or::after {
  content: "";
  flex: 1;
  height: 1px;
  background: var(--ink-edge);
}

.drop-pop-form {
  display: flex;
  gap: 8px;
}

.drop-pop-form .control {
  flex: 1;
  min-width: 0;
}

.drop-pop-note {
  margin: 10px 0 0;
  font-family: var(--font-ui);
  font-size: 12px;
  color: var(--warm-mute);
}

/* A phone: the same centred card, tighter. */
@media (max-width: 720px) {
  .drop-pop-inner {
    padding: 28px 18px 16px;
  }

  .drop-pop-ticker {
    margin-left: -18px;
    margin-right: -18px;
  }

  .drop-pop-title {
    font-size: 19px;
  }

  /* Compact on a phone: the paragraph goes, the headline, ticker and the two
     actions stay, so the modal is quick to read and quick to close. */
  .drop-pop-body {
    display: none;
  }
}

/* Reduced motion: no slide, no ticker, no ping — the card simply appears. */
@media (prefers-reduced-motion: reduce) {
  .drop-pop,
  .drop-pop::backdrop {
    transition: none;
    transform: none;
  }

  .drop-pop-track,
  .drop-pop-rail,
  .drop-pop-dot::after {
    animation: none;
  }
}

/* ------------------------------------------- 12n. help address --------- */

/* The help@ address under the /contact heading and in the footer brand
   column. A link, plainly — an address someone has to copy by hand is one
   they mistype. */
.contact-direct {
  font-family: var(--font-ui);
  font-size: 15px;
  color: var(--warm-mute);
}

.contact-direct a,
.footer-contact a {
  font-weight: 600;
  word-break: break-all;
}

.footer-contact {
  margin-top: 14px;
  font-family: var(--font-ui);
  font-size: 14px;
}

/* ------------------------------------------- 12o. help chat ------------- */

/*
 * A launcher in the corner and a small panel above it. Non-modal: the page
 * stays usable while it is open. Every colour is a token.
 */
.chat {
  position: fixed;
  right: 24px;
  bottom: 24px;
  z-index: 55;                          /* above the header (50); the pop-up is in the top layer */
  display: flex;
  /* The launcher is first in the markup (it is what a keyboard reaches
     first); column-reverse keeps it in the corner with the panel above it. */
  flex-direction: column-reverse;
  align-items: flex-end;
  gap: 12px;
}

.chat-launch {
  display: inline-flex;
  align-items: center;
  gap: 10px;
  padding: 12px 18px;
  border: 0;
  border-radius: 999px;
  background: var(--accent);
  color: var(--on-accent);
  font-family: var(--font-ui);
  font-size: 14px;
  font-weight: 600;
  box-shadow: var(--menu-shadow);
  cursor: pointer;
  transition: transform 0.2s ease;
}

.chat-launch:hover {
  transform: translateY(-2px);
}

.chat-launch:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 3px;
}

.chat.is-open .chat-launch span {
  display: none;
}

.chat-panel {
  display: flex;
  flex-direction: column;
  width: min(384px, calc(100vw - 32px));
  height: min(560px, calc(100dvh - 120px));
  overflow: hidden;
  background: var(--ink-panel);
  border: 1px solid var(--ink-edge);
  border-top: 4px solid var(--accent);
  border-radius: 16px;
  box-shadow: var(--menu-shadow);
  animation: chat-in 0.25s ease;
}

.chat-panel[hidden] {
  display: none;
}

@keyframes chat-in {
  from { opacity: 0; transform: translateY(12px); }
  to { opacity: 1; transform: none; }
}

.chat-head {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: 12px;
  padding: 16px 18px 12px;
  border-bottom: 1px solid var(--ink-line);
}

.chat-kicker {
  display: flex;
  align-items: center;
  gap: 8px;
  margin: 0 0 4px;
  font-family: var(--font-ui);
  font-size: 11px;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--accent);
}

.chat-dot {
  width: 7px;
  height: 7px;
  border-radius: 50%;
  background: var(--accent);
}

.chat-title {
  margin: 0;
  font-size: 18px;
  color: var(--warm);
}

.chat-close {
  display: grid;
  place-items: center;
  width: 32px;
  height: 32px;
  border: 0;
  border-radius: 8px;
  background: transparent;
  color: var(--warm-mute);
  cursor: pointer;
}

.chat-close:hover,
.chat-close:focus-visible {
  background: var(--ink-raised);
  color: var(--warm);
}

.chat-log {
  flex: 1;
  overflow-y: auto;
  display: flex;
  flex-direction: column;
  gap: 10px;
  padding: 16px 18px;
}

.chat-msg {
  max-width: 88%;
  padding: 10px 14px;
  border-radius: 14px;
  font-family: var(--font-ui);
  font-size: 14px;
  line-height: 1.55;
}

.chat-msg p {
  margin: 0;
  white-space: pre-wrap;
  overflow-wrap: anywhere;
}

.chat-msg.is-bot {
  align-self: flex-start;
  background: var(--ink-raised);
  color: var(--warm);
  border-bottom-left-radius: 4px;
}

.chat-msg.is-user {
  align-self: flex-end;
  background: var(--accent);
  color: var(--on-accent);
  border-bottom-right-radius: 4px;
}

.chat-links {
  margin: 8px 0 0;
  padding: 0;
  list-style: none;
  display: grid;
  gap: 4px;
}

.chat-links a {
  font-weight: 600;
  overflow-wrap: anywhere;
}

/* three dots while the answer is on its way */
.chat-msg.is-typing p::after {
  content: "• • •";
  letter-spacing: 2px;
  color: var(--warm-mute);
  animation: chat-typing 1.2s ease-in-out infinite;
}

@keyframes chat-typing {
  0%, 100% { opacity: 0.3; }
  50% { opacity: 1; }
}

.chat-suggest {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
  padding: 0 18px 12px;
}

.chat-suggest[hidden] {
  display: none;
}

.chat-chip {
  padding: 6px 12px;
  border: 1px solid var(--ink-edge);
  border-radius: 999px;
  background: var(--ink-panel);
  color: var(--accent);
  font-family: var(--font-ui);
  font-size: 12px;
  cursor: pointer;
}

.chat-chip:hover,
.chat-chip:focus-visible {
  border-color: var(--accent);
}

.chat-form {
  display: flex;
  gap: 8px;
  align-items: flex-end;
  padding: 12px 18px 6px;
  border-top: 1px solid var(--ink-line);
}

.chat-form textarea {
  flex: 1;
  min-width: 0;
  max-height: 120px;
  resize: none;
}

.chat-send {
  flex-shrink: 0;
  padding: 11px 14px;
}

.chat-note {
  margin: 0;
  padding: 0 18px 12px;
  font-family: var(--font-ui);
  font-size: 11px;
  color: var(--warm-mute);
}

@media (max-width: 720px) {
  .chat {
    right: 12px;
    bottom: 12px;
  }

  .chat-panel {
    width: calc(100vw - 24px);
    height: min(70dvh, 560px);
  }

  .chat-launch span {
    display: none;
  }
}

@media (prefers-reduced-motion: reduce) {
  .chat-panel,
  .chat-msg.is-typing p::after {
    animation: none;
  }

  .chat-launch {
    transition: none;
  }
}

/* ------------------------------------------- 12p. blog ------------------ */

/*
 * Two columns on a wide screen: the article at a reading measure, a sticky
 * sidebar beside it. It used to be one 760px column hard against the left,
 * which on a desktop left half the page empty.
 */
.post-layout {
  display: grid;
  grid-template-columns: minmax(0, 1fr) 300px;
  gap: 64px;
  max-width: 1160px;
  margin: 0 auto;
}

.post-main {
  min-width: 0;
  max-width: 780px;                    /* about 75 characters a line */
}

.post-head {
  padding-top: 56px;
  padding-bottom: 28px;
}

.post-aside {
  position: sticky;
  top: 96px;                           /* under the sticky header */
  align-self: start;
  display: grid;
  gap: 18px;
  padding-top: 56px;
}

.post-aside-label {
  margin: 0 0 8px;
  font-family: var(--font-ui);
  font-size: 11px;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--accent);
}

.post-toc[hidden] {
  display: none;
}

.post-toc ol {
  margin: 0;
  padding: 0 0 0 14px;
  border-left: 2px solid var(--ink-line);
  list-style: none;
  display: grid;
  gap: 8px;
}

.post-toc a {
  font-family: var(--font-ui);
  font-size: 14px;
  line-height: 1.4;
  color: var(--warm-mute);
}

.post-toc a:hover,
.post-toc a:focus-visible {
  color: var(--accent);
}

.post-aside-card {
  padding: 20px;
  border: 1px solid var(--ink-edge);
  border-radius: 14px;
  background: var(--ink-panel);
}

.post-aside-title {
  margin: 0 0 6px;
  font-family: var(--font-display);
  font-weight: 700;
  font-size: 18px;
  color: var(--warm);
}

.post-aside-text {
  margin: 0 0 14px;
  font-size: 14px;
  line-height: 1.6;
  color: var(--warm-mute);
}

.post-aside-btn {
  display: flex;
  justify-content: center;
  width: 100%;
}

.post-aside-link {
  display: block;
  margin-top: 10px;
  font-family: var(--font-ui);
  font-size: 13px;
  text-align: center;
}

/* headings the contents list jumps to land below the sticky header */
.post-body h2 {
  scroll-margin-top: 96px;
}

.post-title {
  margin: 10px 0 24px;
  font-size: clamp(30px, 4.4vw, 48px);
  line-height: 1.12;
  color: var(--warm);
}

.post-byline {
  display: flex;
  gap: 18px;
  align-items: center;
  padding: 18px;
  border: 1px solid var(--ink-line);
  border-radius: 12px;
  background: var(--ink-panel);
}

.post-photo {
  flex-shrink: 0;
  width: 96px;
  height: 120px;                       /* passport proportions, 4:5 */
  border-radius: 8px;
  object-fit: cover;
}

.post-photo.is-placeholder {
  display: grid;
  place-items: center;
  padding: 8px;
  border: 2px dashed var(--ink-edge);
  background: var(--ink-raised);
  font-family: var(--font-ui);
  font-size: 11px;
  line-height: 1.35;
  text-align: center;
  color: var(--warm-mute);
}

.post-author {
  margin: 0 0 6px;
  font-family: var(--font-display);
  font-weight: 700;
  font-size: 17px;
  color: var(--warm);
}

.post-bio {
  margin: 0;
  font-size: 14px;
  line-height: 1.6;
  color: var(--warm-mute);
}

.post-body {
  padding-bottom: 72px;
  font-size: 17px;
  line-height: 1.75;
  color: var(--warm-mute);
}

.post-body p {
  margin: 0 0 18px;
}

.post-body h2 {
  margin: 40px 0 12px;
  font-size: 24px;
  color: var(--warm);
}

.post-lead {
  font-size: 20px;
  color: var(--warm);
}

.post-table-wrap {
  margin: 8px 0 14px;
  overflow-x: auto;
  border: 1px solid var(--ink-line);
  border-radius: 10px;
}

.post-table {
  width: 100%;
  border-collapse: collapse;
  font-family: var(--font-ui);
  font-size: 14px;
}

.post-table caption {
  padding: 12px 14px;
  text-align: left;
  font-size: 13px;
  color: var(--warm-mute);
  caption-side: top;
}

.post-table th,
.post-table td {
  padding: 10px 14px;
  text-align: left;
  border-top: 1px solid var(--ink-line);
  color: var(--warm);
}

.post-table thead th {
  background: var(--ink-raised);
  font-weight: 600;
}

.post-table tbody th {
  font-family: var(--font-mono);
  font-weight: 400;
  white-space: nowrap;
}

.post-note {
  font-size: 13px;
}

.post-checklist {
  margin: 0 0 18px;
  padding: 0;
  list-style: none;
  display: grid;
  gap: 8px;
}

.post-checklist li {
  position: relative;
  padding-left: 30px;
}

/* an empty box to tick — a checklist, drawn */
.post-checklist li::before {
  content: "";
  position: absolute;
  left: 0;
  top: 6px;
  width: 16px;
  height: 16px;
  border: 2px solid var(--accent);
  border-radius: 4px;
}

.post-sources {
  margin-top: 48px;
  padding-top: 8px;
  border-top: 1px solid var(--ink-line);
  font-size: 14px;
}

.post-sources h2 {
  font-size: 18px;
}

.post-sources li {
  margin-bottom: 8px;
}

.post-role {
  margin: -2px 0 8px;
  font-family: var(--font-ui);
  font-size: 13px;
  letter-spacing: 0.04em;
  color: var(--accent);
}

/* The cost chart — one series, labelled bars, the table above is its data view. */
.post-chart {
  margin: 24px 0;
  padding: 20px 22px 16px;
  border: 1px solid var(--ink-line);
  border-radius: 12px;
  background: var(--ink-panel);
}

.post-chart-title {
  margin: 0 0 14px !important;
  font-family: var(--font-ui);
  font-size: 14px;
  font-weight: 600;
  color: var(--warm);
}

.post-chart-plot {
  display: grid;
  gap: 12px;
}

.post-chart-row {
  display: grid;
  grid-template-columns: 112px minmax(0, 1fr);
  align-items: center;
  gap: 12px;
}

.post-chart-label {
  font-family: var(--font-mono);
  font-size: 13px;
  color: var(--warm-mute);
  text-align: right;
}

/* The bar runs to at most 78% of the track, so the largest value's label
   still fits after it instead of being clipped. Square at the baseline,
   4px rounded at the data end. */
.post-chart-track {
  display: flex;
  align-items: center;
  gap: 8px;
  min-width: 0;
  border-left: 1px solid var(--ink-edge);
}

.post-chart-bar {
  display: block;
  width: calc(var(--share, 0%) * 0.78);
  min-width: 3px;
  height: 20px;
  border-radius: 0 4px 4px 0;
  background: var(--accent);
}

.post-chart-value {
  font-family: var(--font-ui);
  font-size: 13px;
  font-weight: 600;
  color: var(--warm);
  white-space: nowrap;
}

.post-chart figcaption {
  margin-top: 12px;
  font-size: 13px;
  color: var(--warm-mute);
}

@media (max-width: 720px) {
  .post-chart-row {
    grid-template-columns: 92px minmax(0, 1fr);
  }
}

/* the hub: the newest post full width, the rest in a grid below */
.post-feature {
  display: grid;
  grid-template-columns: minmax(0, 1.6fr) minmax(0, 1fr);
  gap: 40px;
  padding: 36px 40px;
  border: 1px solid var(--ink-edge);
  border-top: 4px solid var(--accent);
  border-radius: 18px;
  background: var(--ink-panel);
  color: inherit;
  transition: border-color 0.2s ease, transform 0.2s ease;
}

.post-feature:hover,
.post-feature:focus-visible {
  border-color: var(--accent);
  transform: translateY(-2px);
}

.post-feature-text {
  display: grid;
  gap: 12px;
  align-content: start;
}

.post-feature-title {
  font-family: var(--font-display);
  font-weight: 700;
  font-size: clamp(24px, 2.6vw, 34px);
  line-height: 1.2;
  color: var(--warm);
}

.post-feature-side {
  padding-left: 32px;
  border-left: 1px solid var(--ink-line);
}

.post-feature-tags {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
}

.post-tag {
  padding: 5px 12px;
  border-radius: 999px;
  background: var(--accent-soft);
  font-family: var(--font-ui);
  font-size: 13px;
  color: var(--accent);
}

.post-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(320px, 1fr));
  gap: 18px;
  margin-top: 18px;
}

.post-card {
  display: grid;
  gap: 8px;
  padding: 22px 24px;
  border: 1px solid var(--ink-edge);
  border-radius: 14px;
  background: var(--ink-panel);
  color: inherit;
  transition: border-color 0.2s ease, transform 0.2s ease;
}

.post-card:hover,
.post-card:focus-visible {
  border-color: var(--accent);
  transform: translateY(-2px);
}

.post-card-meta {
  font-family: var(--font-ui);
  font-size: 12px;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--accent);
}

.post-card-title {
  font-family: var(--font-display);
  font-weight: 700;
  font-size: 22px;
  line-height: 1.25;
  color: var(--warm);
}

.post-card-summary {
  font-size: 15px;
  line-height: 1.6;
  color: var(--warm-mute);
}

.post-card-more {
  font-family: var(--font-ui);
  font-size: 14px;
  font-weight: 600;
  color: var(--accent);
}

@media (max-width: 1024px) {
  .post-layout {
    grid-template-columns: minmax(0, 1fr);
    gap: 0;
  }

  .post-main {
    max-width: none;
  }

  .post-aside {
    position: static;
    padding-top: 0;
    padding-bottom: 56px;
  }

  /* On one column the contents list would sit after the article it indexes. */
  .post-toc {
    display: none;
  }

  .post-feature {
    grid-template-columns: minmax(0, 1fr);
    gap: 24px;
  }

  .post-feature-side {
    padding-left: 0;
    padding-top: 20px;
    border-left: 0;
    border-top: 1px solid var(--ink-line);
  }
}

@media (max-width: 720px) {
  .post-feature {
    padding: 24px 20px;
  }

  .post-byline {
    align-items: flex-start;
  }

  .post-photo {
    width: 72px;
    height: 90px;
  }
}

@media (prefers-reduced-motion: reduce) {
  .post-card,
  .post-feature {
    transition: none;
  }
}

/* ---- the blog hub (redesigned 2026-10-10) --------------------------- */

.blog-hero {
  border-bottom: 1px solid var(--ink-line);
  background:
    radial-gradient(60% 80% at 85% 20%, var(--accent-soft), transparent 70%),
    var(--ink);
}

.blog-hero-grid {
  display: grid;
  grid-template-columns: minmax(0, 1.35fr) minmax(0, 1fr);
  gap: 56px;
  align-items: center;
  padding-top: 72px;
  padding-bottom: 64px;
}

.blog-hero-title {
  font-size: clamp(36px, 5vw, 64px);
  line-height: 1.05;
  color: var(--warm);
}

.blog-promises {
  display: flex;
  flex-wrap: wrap;
  gap: 10px;
  margin: 24px 0 0;
  padding: 0;
  list-style: none;
}

.blog-promises li {
  padding: 7px 14px;
  border: 1px solid var(--ink-edge);
  border-radius: 999px;
  background: var(--ink-panel);
  font-family: var(--font-ui);
  font-size: 13px;
  color: var(--warm);
}

.blog-promises li::before {
  content: "✓ ";
  color: var(--accent);
  font-weight: 700;
}

.blog-author-card {
  display: flex;
  gap: 18px;
  align-items: flex-start;
  padding: 22px;
  border: 1px solid var(--ink-edge);
  border-radius: 16px;
  background: var(--ink-panel);
  box-shadow: var(--menu-shadow);
}

.blog-author-card img {
  flex-shrink: 0;
  width: 96px;
  height: 120px;
  border-radius: 10px;
  object-fit: cover;
}

.blog-author-name {
  margin: 0 0 4px;
  font-family: var(--font-display);
  font-weight: 700;
  font-size: 20px;
  color: var(--warm);
}

.blog-author-bio {
  margin: 0;
  font-size: 14px;
  line-height: 1.6;
  color: var(--warm-mute);
}

/* the featured post */
.blog-feature {
  display: grid;
  grid-template-columns: minmax(0, 5fr) minmax(0, 7fr);
  overflow: hidden;
  border: 1px solid var(--ink-edge);
  border-radius: 20px;
  background: var(--ink-panel);
  box-shadow: var(--panel-shadow);
  color: inherit;
  transition: border-color 0.2s ease, transform 0.2s ease, box-shadow 0.2s ease;
}

/* A card is one big link: the site-wide a:hover underline would draw a line
   under every word in it. */
.blog-feature:hover,
.blog-feature:focus-visible,
.post-card:hover,
.post-card:focus-visible,
.post-feature:hover,
.post-feature:focus-visible {
  text-decoration: none;
}

.blog-feature:hover,
.blog-feature:focus-visible {
  border-color: var(--accent);
  transform: translateY(-3px);
  box-shadow: var(--menu-shadow);
}

.blog-cover {
  position: relative;
  display: flex;
  flex-direction: column;
  justify-content: flex-start;
  min-height: 360px;
  padding: 28px;
  overflow: hidden;
  background:
    linear-gradient(160deg, var(--accent) 0%, color-mix(in srgb, var(--accent) 55%, var(--ink)) 100%);
  color: var(--on-accent);
}

.blog-cover::after {
  /* a faint grid, the same texture the hero art uses */
  content: "";
  position: absolute;
  inset: 0;
  background-image:
    linear-gradient(color-mix(in srgb, var(--on-accent) 10%, transparent) 1px, transparent 1px),
    linear-gradient(90deg, color-mix(in srgb, var(--on-accent) 10%, transparent) 1px, transparent 1px);
  background-size: 28px 28px;
  pointer-events: none;
}

.blog-cover > * {
  position: relative;
  z-index: 1;
}

.blog-cover-tag {
  align-self: flex-start;
  padding: 5px 12px;
  border-radius: 999px;
  background: color-mix(in srgb, var(--on-accent) 18%, transparent);
  font-family: var(--font-ui);
  font-size: 11px;
  letter-spacing: 0.16em;
  text-transform: uppercase;
}

.blog-cover-figure {
  margin-top: 28px;
  font-family: var(--font-num);
  font-size: 120px;
  line-height: 0.9;
}

.blog-cover-label {
  font-family: var(--font-display);
  font-weight: 700;
  font-size: 22px;
}

.blog-cover-bars {
  display: flex;
  align-items: flex-end;
  gap: 8px;
  height: 64px;
  margin-top: auto;
  padding-top: 24px;
}

.blog-cover-bars span {
  width: 14px;
  height: var(--h, 50%);
  border-radius: 4px 4px 0 0;
  background: color-mix(in srgb, var(--on-accent) 70%, transparent);
}

.blog-cover-tags {
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
  margin-top: 16px;
}

.blog-cover-tags span {
  padding: 4px 10px;
  border-radius: 999px;
  border: 1px solid color-mix(in srgb, var(--on-accent) 40%, transparent);
  font-family: var(--font-ui);
  font-size: 12px;
}

.blog-feature-body {
  display: flex;
  flex-direction: column;
  gap: 14px;
  padding: 36px 40px;
}

.blog-feature-title {
  font-family: var(--font-display);
  font-weight: 700;
  font-size: clamp(24px, 2.4vw, 34px);
  line-height: 1.2;
  color: var(--warm);
}

.blog-feature-summary {
  font-size: 16px;
  line-height: 1.65;
  color: var(--warm-mute);
}

.blog-inside {
  display: grid;
  gap: 8px;
  padding: 16px 18px;
  border-radius: 12px;
  background: var(--ink-raised);
}

.blog-inside .post-aside-label {
  margin-bottom: 2px;
}

.blog-inside-item {
  position: relative;
  padding-left: 22px;
  font-size: 14px;
  line-height: 1.5;
  color: var(--warm);
}

.blog-inside-item::before {
  content: "";
  position: absolute;
  left: 0;
  top: 5px;
  width: 12px;
  height: 12px;
  border-radius: 3px;
  background: var(--accent);
}

.blog-feature-foot {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: 16px;
  margin-top: auto;
  padding-top: 8px;
}

.blog-byline-mini {
  display: flex;
  align-items: center;
  gap: 10px;
}

.blog-byline-mini img {
  width: 36px;
  height: 45px;
  border-radius: 6px;
  object-fit: cover;
}

.blog-byline-mini > span {
  display: grid;
}

.blog-byline-name {
  font-family: var(--font-ui);
  font-size: 14px;
  font-weight: 600;
  color: var(--warm);
}

.blog-byline-role {
  font-family: var(--font-ui);
  font-size: 12px;
  color: var(--warm-mute);
}

@media (max-width: 1024px) {
  .blog-hero-grid,
  .blog-feature {
    grid-template-columns: minmax(0, 1fr);
  }

  .blog-hero-grid {
    gap: 32px;
  }

  .blog-cover {
    min-height: 240px;
  }

  .blog-cover-figure {
    font-size: 88px;
  }
}

@media (max-width: 720px) {
  .blog-feature-body {
    padding: 24px 20px;
  }

  .blog-author-card img {
    width: 72px;
    height: 90px;
  }
}

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

/*
 * Narrow laptops, 1025–1199px (2026-10-10). Adding Blog made the header —
 * already called full — wrap a label in the signed-in admin view (Admin link
 * plus the name chip) at 1060–1100px. Measured, then fixed here: tighter link
 * spacing and no tagline beside the wordmark, which is the most expendable
 * thing in the header. Below 1025px the nav is the mobile sheet anyway.
 */
@media (min-width: 1025px) and (max-width: 1199px) {
  .nav {
    gap: 18px;
  }

  .brand-divider,
  .brand-note {
    display: none;
  }
}

/* -------------------------------------------------------- 13. animations -- */

/*
 * Waveform — 20 amber bars, each running the same scaleY loop with a 0.05s
 * stagger, which reads as a wave rolling left to right.
 *
 * Per-bar height and delay come from inline custom properties set in the
 * Razor partial, so this stays one rule rather than twenty.
 */
.waveform {
  display: flex;
  align-items: flex-end;
  gap: 4px;
  height: 48px;
}

.waveform .bar {
  width: 6px;
  height: var(--h);
  border-radius: 1px;
  background: var(--accent);
  transform-origin: bottom;
  animation: wave 2s ease-in-out infinite;
  animation-delay: var(--d);
}

.waveform.is-active .bar {
  animation-duration: 0.9s;
}

.waveform.is-ghost {
  opacity: 0.25;
}

@keyframes wave {
  0%,
  100% {
    transform: scaleY(0.4);
  }
  50% {
    transform: scaleY(1);
  }
}

/*
 * Entrance reveal.
 *
 * CSS rather than a JS animation library on purpose: a JS library sets
 * opacity:0 up front and needs a frame to clear it, so if frames never arrive
 * the content stays invisible and the page reads as blank. A CSS animation
 * belongs to the engine's own timeline, and the reduced-motion block below
 * collapses it onto the visible end state.
 *
 * Stagger comes from an inline animation-delay.
 */
.reveal {
  animation: reveal-up 0.55s ease-out backwards;
}

@keyframes reveal-up {
  from {
    opacity: 0;
    transform: translateY(24px);
  }
  to {
    opacity: 1;
    transform: translateY(0);
  }
}

@keyframes drop-in {
  from {
    opacity: 0;
    transform: translateY(-64px);
  }
  to {
    opacity: 1;
    transform: translateY(0);
  }
}

@keyframes fade-down {
  from {
    opacity: 0;
    transform: translateY(-6px);
  }
  to {
    opacity: 1;
    transform: translateY(0);
  }
}

@keyframes marquee {
  from {
    transform: translateX(0);
  }
  to {
    transform: translateX(-50%);
  }
}

.spinner {
  width: 15px;
  height: 15px;
  border: 2px solid color-mix(in srgb, var(--on-accent) 35%, transparent);
  border-top-color: var(--on-accent);
  border-radius: 50%;
  animation: spin 0.7s linear infinite;
}

@keyframes spin {
  to {
    transform: rotate(360deg);
  }
}

/*
 * The admin dock's live dot.
 *
 * 0% AND 100% ARE BOTH FULLY LIT, deliberately. The reduced-motion rule
 * collapses every animation to 0.01ms, which lands on the `to` keyframe — so a
 * fade-out written as 0% → 100% would leave the dot permanently dim for anyone
 * who asked for less motion. Same reasoning as .node and .stack-spark, solved
 * in the keyframes instead of with a pin.
 */
@keyframes dock-pulse {
  0%,
  100% {
    opacity: 1;
  }

  50% {
    opacity: 0.3;
  }
}

/* --------------------------------------------------------- 14. utilities -- */

.hidden {
  display: none !important;
}

.center {
  text-align: center;
}

.mt-2 { margin-top: 16px; }
.mt-3 { margin-top: 24px; }
.mt-4 { margin-top: 32px; }
.mt-6 { margin-top: 48px; }
.mb-2 { margin-bottom: 16px; }
.mb-3 { margin-bottom: 24px; }
.mb-6 { margin-bottom: 48px; }

/* -------------------------------------------------------- 15. responsive -- */

@media (max-width: 1024px) {
  /* Five links plus the dropdown and the CTA do not fit on a narrow laptop
     without the button wrapping, so the sheet starts here rather than 768px. */
  .nav,
  .site-header .btn,
  /* The account area goes with them — the sheet carries its own copy, and
     below this width the header has room for the brand and the toggle only. */
  .account-link,
  .account-chip,
  .admin-link {
    display: none;
  }

  /* The admin bar keeps its links and drops the reminder, which is the least
     load-bearing thing on it. */
  .admin-note {
    display: none;
  }

  .nav-toggle {
    display: block;

    /*
     * WITHOUT THIS THE MENU BUTTON IS 8 PIXELS WIDE ON A PHONE.
     *
     * .brand is flex-shrink: 0 and carries the wordmark AND the tagline, so at
     * 375px the row overflows and the only item that CAN shrink — this one —
     * absorbs all of it. The icon still paints, clipped, which is why it looks
     * like a styling quirk rather than the whole mobile navigation being
     * unreachable. Found while checking that the new sign-in links in the sheet
     * could actually be opened.
     */
    flex-shrink: 0;
  }

  .grid-3 {
    grid-template-columns: repeat(2, 1fr);
  }

  .hero-content {
    width: 100%;
  }

  /*
   * The hero text takes the whole width here, so the dock has no column to sit
   * in — and it is NOT hidden, because an administrator on a phone is still an
   * administrator and the numbers are the whole point of the panel.
   *
   * IT GOES BACK INTO THE FLOW RATHER THAN MOVING. The first attempt kept it
   * absolute and pinned it to the bottom of the hero, which put it straight
   * over the hero stats at 375px — measured, not guessed. An overlay has
   * nowhere safe to sit on a layout that already fills the viewport.
   */
  .visitor-dock {
    /*
     * RELATIVE, NOT STATIC. z-index only applies to a positioned element, so
     * `static` would silently drop the panel behind .hero-scrim — in the flow,
     * correctly sized, and painted over. Caught in the browser: the geometry
     * was right and the panel was not on screen.
     */
    position: relative;

    /*
     * AND THE OFFSETS HAVE TO GO WITH IT. §12j sets top/right for the absolute
     * placement; left in place they do not move the box in the flow, they
     * repaint it 120px lower — straight over the headline, with the layout
     * still reporting no overlap. Caught in the browser, twice in two minutes.
     */
    inset: auto;
    display: flex;
    flex-wrap: wrap;
    align-items: baseline;
    gap: 6px 22px;
    width: auto;
    margin: calc(var(--header-h) + 20px) var(--gutter) 0;
    padding: 12px 16px;
  }

  /*
   * …and the hero stops demanding a full viewport of its own when the panel is
   * above it, or the headline starts a dock's height below the fold.
   *
   * :has() rather than a class on the page, so neither Index.cshtml nor the
   * partial has to know the other exists — the rule is true whenever the panel
   * is there, which is the only time it matters.
   */
  .hero:has(.visitor-dock) .hero-content {
    min-height: auto;
    padding-top: 32px;
  }

  .visitor-dock-stat {
    flex-direction: row;
    align-items: baseline;
    gap: 7px;
    margin-top: 0;
  }

  .visitor-dock-num {
    font-size: 24px;
  }

  .visitor-dock-note {
    flex-basis: 100%;
    margin-top: 2px;
    padding-top: 8px;
  }

  .split,
  .tool-grid {
    grid-template-columns: 1fr;
  }

  .split-art {
    border-left: 0;
    border-top: 1px solid var(--ink-line);
  }

  .footer-grid {
    grid-template-columns: 1fr 1fr;
  }
}

@media (max-width: 720px) {
  .grid-3,
  .grid-2,
  .field-row,
  .builder-grid,
  .footer-grid {
    grid-template-columns: 1fr;
  }

  .framework-grid {
    grid-template-columns: repeat(2, 1fr);
  }

  .tabs {
    /* Swipeable strip rather than a wrapped block that eats the fold */
    flex-wrap: nowrap;
    overflow-x: auto;
    padding-bottom: 20px;
    scrollbar-width: none;
  }

  .tabs::-webkit-scrollbar {
    display: none;
  }

  .section-pad {
    padding-top: 56px;
    padding-bottom: 56px;
  }

  .cta-form {
    flex-direction: column;
  }

  .stats {
    gap: 20px 28px;
  }

  .stat-value {
    font-size: 38px;
  }

  .panel {
    padding: 24px;
  }

  /*
   * The tagline goes. It is marketing copy, and at 375px "FreeYantra | AI
   * utilities, free to use" is most of the header — which is the other half of
   * why the menu button had nowhere to go (see .nav-toggle above). The
   * wordmark stays; it is the link home.
   */
  .brand-note,
  .brand-divider {
    display: none;
  }
}

/* --- about / portfolio ---------------------------------------------------- */

@media (max-width: 900px) {
  /* Portrait above the text, and smaller — a 300px square beside nothing looks
     like a placeholder. */
  .persona-grid {
    grid-template-columns: 1fr;
    gap: 32px;
  }

  .portrait-frame {
    max-width: 200px;
  }

  .about-split {
    grid-template-columns: 1fr;
    gap: 32px;
  }

  .contact-card {
    padding: 28px;
  }

  /* The tower goes above its list rather than beside it: at this width a
     420px-tall object in a half-column column is taller than the text next
     to it. */
  .growth-grid {
    grid-template-columns: 1fr;
    gap: 24px;
  }

  .case-grid {
    grid-template-columns: 1fr;
  }

  .svc {
    grid-template-columns: 48px 1fr;
  }

  /* The bullets move under the title, across both remaining columns. */
  .svc-points {
    grid-column: 2;
  }
}

@media (max-width: 720px) {
  .persona-hero {
    padding: 36px 0 44px;
  }

  .figure-row {
    gap: 24px;
  }

  /*
   * Shrink the whole tower in one line. The plate sizes come from inline
   * custom properties in _GrowthStack.cshtml, so there is nothing here to
   * override per part — but `scale` is an INDEPENDENT transform property,
   * applied before `transform`, so it rescales the assembly without touching
   * the rotation that .stack-stage's animation is writing.
   */
  .stack {
    height: 280px;
  }

  .stack-stage {
    scale: 0.62;
  }

  .case-copy,
  .case-figures {
    padding: 26px;
  }

  /* The lightbox is edge-to-edge on a phone, so its outside controls move in. */
  .lightbox {
    width: 96vw;
    border-radius: 12px;
  }

  .lightbox-close {
    top: 8px;
    right: 8px;
    width: 36px;
    height: 36px;
  }

  .lightbox-prev { left: 6px; }
  .lightbox-next { right: 6px; }

  .lightbox-step {
    width: 38px;
    height: 38px;
    margin-top: -19px;
  }

  .lightbox-figure img {
    max-height: calc(92vh - 110px);
  }

  /* Full width at the bottom: a floating pill beside a thumb is a miss
     waiting to happen. */
  .sticky-cta {
    left: 16px;
    right: 16px;
    bottom: 16px;
    text-align: center;
  }

  /* …which then spans the footer. Only the page carrying one pays for the
     clearance, hence :has() rather than a rule on body. Without :has() support
     this simply does not apply and the button overlaps as before. */
  body:has(.sticky-cta) {
    padding-bottom: 76px;
  }
}

  .creed {
    padding: 48px 0;
  }

  .creed-quote {
    max-width: none;
  }

  .timeline {
    padding-left: 24px;
  }

  .timeline-item::before {
    left: -24px;
  }

  .contact-links {
    width: 100%;
  }
}

/* --- developer guide ------------------------------------------------------ */

@media (max-width: 1024px) {
  .guide-hero-grid {
    grid-template-columns: 1fr;
  }

  .lattice {
    height: 320px;
  }
}

@media (max-width: 720px) {
  /* Shrink the whole assembly by moving the three sizes that define it. */
  .lattice { height: 250px; }
  .lattice-stage { --r: 58px; }
  .cube-outer { --s: 116px; }
  .cube-inner { --s: 56px; }

  .orbit {
    width: 210px;
    height: 210px;
    left: -105px;
    top: -105px;
  }

  .guide-stats {
    gap: 24px;
  }

  .guide-stat-num {
    font-size: 34px;
  }

  .recipe-title-row {
    gap: 14px;
  }

  .recipe-mark {
    width: 48px;
    height: 48px;
    font-size: 22px;
  }

  .docs-callout {
    flex-direction: column;
    align-items: flex-start;
  }
}

/* ------------------------------------- 12q. security layers figure ------- */

/*
 * The "Beyond SSL" post's figure (2026-10-12): five plates stacked in 3D,
 * widest at the top, which is where traffic arrives. Each layer lets less
 * through, so the stack narrows towards the data. A green-lit request falls
 * through every plate; red attacks stop at the layer built to catch them. The
 * dashed ring around the stack is layer 6, monitoring, which watches all of
 * them rather than sitting in the path.
 *
 * CSS only, like _GrowthStack and _AiLattice. Decorative: the flow diagram
 * beside it says the same thing in words, so the stage is aria-hidden. Under
 * reduced motion the particles are removed rather than frozen mid-fall, which
 * would show an attack that looks like it got through.
 */
.sec-figure {
  margin: 28px 0;
  padding: 24px 22px 18px;
  border: 1px solid var(--ink-line);
  border-radius: 14px;
  background: var(--ink-panel);
}

.sec-figure-title {
  margin: 0 0 6px !important;
  font-family: var(--font-ui);
  font-size: 14px;
  font-weight: 600;
  color: var(--warm);
}

.sec-figure-grid {
  display: grid;
  grid-template-columns: minmax(0, 300px) minmax(0, 1fr);
  gap: 18px;
  align-items: center;
}

.sec-stage-wrap {
  height: 300px;
  perspective: 1100px;
  display: grid;
  place-items: center;
  overflow: hidden;
}

.sec-stage {
  position: relative;
  width: 1px;
  height: 1px;
  transform-style: preserve-3d;
  /* The plates rise towards the viewer as they climb (z up to 160px), so the
     stack's visual centre sits above its base: shift it down to centre it. */
  transform: translateY(62px) rotateX(58deg) rotateZ(-42deg);
  scale: 0.86;
}

.sec-plate {
  position: absolute;
  left: calc(var(--s) / -2);
  top: calc(var(--s) / -2);
  width: var(--s);
  height: var(--s);
  border: 1.5px solid var(--accent);
  border-radius: 10px;
  background: color-mix(in srgb, var(--accent) 9%, transparent);
  transform: translateZ(var(--z));
  box-shadow: 0 0 0 1px color-mix(in srgb, var(--accent) 18%, transparent) inset;
}

.sec-plate span {
  position: absolute;
  left: 8px;
  top: 6px;
  font-family: var(--font-num);
  font-size: 20px;
  line-height: 1;
  color: var(--accent);
}

.sec-plate.is-core {
  background: color-mix(in srgb, var(--accent) 22%, transparent);
}

/* Layer 6: monitoring, a ring around the whole stack rather than a plate in it. */
.sec-watch {
  position: absolute;
  left: -112px;
  top: -112px;
  width: 224px;
  height: 224px;
  border: 1.5px dashed var(--warm-faint);
  border-radius: 50%;
  transform: translateZ(70px);
  animation: sec-orbit 24s linear infinite;
}

.sec-watch span {
  position: absolute;
  top: -11px;
  left: 50%;
  font-family: var(--font-num);
  font-size: 18px;
  color: var(--warm-mute);
}

/* The request that is allowed through, and the attacks that are not. */
.sec-dot {
  position: absolute;
  left: -5px;
  top: -5px;
  width: 10px;
  height: 10px;
  border-radius: 50%;
  opacity: 0;
}

.sec-dot.is-ok {
  background: var(--accent);
  box-shadow: 0 0 10px var(--accent);
  animation: sec-pass 4.8s ease-in infinite;
}

.sec-dot.is-bad {
  left: var(--x);
  top: var(--y);
  background: var(--danger);
  box-shadow: 0 0 10px var(--danger);
  animation: sec-stop 4.8s ease-in infinite;
  animation-delay: var(--delay);
}

@keyframes sec-pass {
  0%   { transform: translateZ(230px); opacity: 0; }
  10%  { opacity: 1; }
  80%  { transform: translateZ(4px); opacity: 1; }
  100% { transform: translateZ(4px); opacity: 0; }
}

/* An attack falls to its layer, flares, and is gone. */
@keyframes sec-stop {
  0%   { transform: translateZ(230px) scale(1); opacity: 0; }
  10%  { opacity: 1; }
  55%  { transform: translateZ(var(--stop)) scale(1); opacity: 1; }
  70%  { transform: translateZ(var(--stop)) scale(2.6); opacity: 0; }
  100% { transform: translateZ(var(--stop)) scale(2.6); opacity: 0; }
}

@keyframes sec-orbit {
  to { transform: translateZ(70px) rotate(360deg); }
}

/* The flow, in words: the request's path, and what each layer stops. */
.sec-flow {
  list-style: none;
  margin: 0;
  padding: 0;
  display: grid;
  gap: 8px;
}

.sec-flow li {
  display: grid;
  grid-template-columns: 26px minmax(0, 1fr);
  gap: 10px;
  align-items: baseline;
  padding: 8px 12px;
  border: 1px solid var(--ink-edge);
  border-radius: 10px;
  background: var(--ink);
  font-size: 14px;
  line-height: 1.4;
}

.sec-flow li b {
  font-family: var(--font-num);
  font-weight: 400;
  font-size: 18px;
  color: var(--accent);
}

.sec-flow li em {
  font-style: normal;
  color: var(--warm-mute);
}

.sec-flow li.is-rail {
  border-style: dashed;
}

.sec-flow li.is-rail b {
  color: var(--warm-mute);
}

.sec-figure figcaption {
  margin-top: 12px;
  font-size: 13px;
  color: var(--warm-mute);
}

.sec-legend-ok,
.sec-legend-bad {
  display: inline-block;
  width: 9px;
  height: 9px;
  margin: 0 4px 0 2px;
  border-radius: 50%;
  vertical-align: middle;
}

.sec-legend-ok { background: var(--accent); }
.sec-legend-bad { background: var(--danger); }

@media (max-width: 720px) {
  .sec-figure-grid {
    grid-template-columns: minmax(0, 1fr);
  }

  .sec-stage-wrap {
    height: 250px;
  }

  .sec-stage {
    scale: 0.74;
  }
}

@media (prefers-reduced-motion: reduce) {
  .sec-dot {
    display: none;
  }
}

/* ------------------------------ 12r. compact heroes + Claude Kit cards --- */

/*
 * COMPACT HEROES (2026-10-17, owner's request): a visitor should see what a
 * page offers without scrolling. Every hero now aims at roughly a third of a
 * laptop screen, so the real content starts above the fold. These rules come
 * after the originals on purpose and override them; the originals are kept
 * so the history of each value is still readable where it was set.
 */
.hero {
  min-height: 0;
}

.hero-content {
  display: grid;
  grid-template-columns: minmax(0, 1.55fr) minmax(0, 1fr);
  align-items: center;
  gap: 40px;
  width: auto;
  min-height: 0;
  padding-top: 28px;
  padding-bottom: 26px;
}

.hero h1 {
  font-size: clamp(30px, 3.2vw, 46px);
}

.hero-sub {
  max-width: 720px;
  margin-top: 12px;
  font-size: 15.5px;
  line-height: 1.55;
}

.hero-main .btn-row {
  margin-top: 20px;
}

.hero-alt {
  margin-top: 12px;
}

.hero-side {
  display: grid;
  gap: 16px;
  justify-items: start;
}

.hero-side .stats {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 12px 28px;
  margin-top: 0;
}

.hero-side .stat-value {
  font-size: 30px;
}

.hero-side .stat-label {
  margin-top: 2px;
  font-size: 12px;
}

/* The admin dock sits in the right column now, in the flow. Relative, not
   static, so it still paints above the scrim (see the 1024px block). */
.hero-side .visitor-dock {
  position: relative;
  inset: auto;
  width: 100%;
  margin: 0;
}

.guide-hero {
  padding: 28px 0 24px;
}

.guide-title {
  font-size: clamp(30px, 3.4vw, 50px);
}

.guide-hero .lead {
  margin-top: 12px;
}

.guide-stats {
  gap: 28px;
  margin-top: 16px;
}

.guide-stat-num {
  font-size: 28px;
}

.guide-hero .lattice {
  height: 190px;
}

.guide-hero .lattice-stage {
  scale: 0.6;
}

.guide-hero .lattice-floor {
  height: 120px;
}

.blog-hero-grid {
  padding-top: 32px;
  padding-bottom: 28px;
}

.recipe-head {
  padding: 28px 0 24px;
}

.persona-hero {
  padding: 32px 0 36px;
}

@media (max-width: 900px) {
  .hero-content {
    grid-template-columns: minmax(0, 1fr);
    gap: 20px;
    padding-top: 28px;
    padding-bottom: 28px;
  }

  .hero-side .stats {
    grid-template-columns: repeat(4, auto);
  }

  .hero-side .waveform {
    display: none;
  }

  .guide-hero .lattice {
    display: none;
  }
}

@media (max-width: 560px) {
  .hero-side .stats {
    grid-template-columns: repeat(2, minmax(0, 1fr));
  }
}

/* Second pass (measured at 1366×768 and 375×812): the hub intros, the blog's
   author card and the /about portrait were what kept the heroes tall. */
.guide-hero .lead,
.blog-hero .lead,
.persona-hero .lead {
  font-size: 15.5px;
  line-height: 1.55;
  max-width: 640px;
}

.guide-hero-grid {
  grid-template-columns: minmax(0, 1.6fr) minmax(0, 1fr);
  gap: 24px;
}

.blog-hero-title {
  font-size: clamp(28px, 3vw, 40px);
}

.blog-promises {
  margin-top: 14px;
}

.blog-author-card {
  padding: 16px;
}

.blog-author-card img {
  width: 72px;
  height: 90px;
}

.blog-author-bio {
  display: -webkit-box;
  -webkit-box-orient: vertical;
  -webkit-line-clamp: 3;
  line-clamp: 3;
  overflow: hidden;
}

.persona-grid {
  grid-template-columns: 160px 1fr;
  gap: 36px;
}

.portrait-frame {
  max-width: 160px;
}

.portrait-frame::after {
  inset: 12px -12px -12px 12px;
}

.persona-name {
  font-size: clamp(28px, 3.4vw, 44px);
}

@media (max-width: 720px) {
  /* A phone shows the first few lines of a hub's intro; the rest is one tap
     of scrolling away and the cards matter more. The text stays in the page. */
  .guide-hero .lead,
  .blog-hero .lead {
    display: -webkit-box;
    -webkit-box-orient: vertical;
    -webkit-line-clamp: 3;
    line-clamp: 3;
    overflow: hidden;
  }

  .guide-stats {
    flex-wrap: nowrap;
    gap: 18px;
  }

  .guide-stat-num {
    font-size: 24px;
  }

  .guide-stat-label {
    font-size: 10px;
    letter-spacing: 0.08em;
  }

  .guide-hero .hint,
  .hero-alt,
  .blog-promises {
    display: none;
  }

  .hero-side .stat-value {
    font-size: 26px;
  }

  .persona-grid {
    grid-template-columns: 96px 1fr;
    gap: 16px;
  }

  /* On a phone the numbers and the author card are the first things to go:
     the cards below say more than a stat row, and every post page carries
     the byline. Hidden on phones only; the markup is unchanged. */
  .hero-side .stats,
  .guide-hero .guide-stats,
  .blog-hero .blog-author-card {
    display: none;
  }

  .persona-hero .lead {
    display: -webkit-box;
    -webkit-box-orient: vertical;
    -webkit-line-clamp: 4;
    line-clamp: 4;
    overflow: hidden;
  }
}

/*
 * CLAUDE KIT CARDS AND DECKS (2026-10-17): a Free tag, the topic, and the
 * public copy count on every card; topic chips and "Load more" on every deck.
 */
.kit-card-foot {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 8px;
  margin-top: auto;
  padding-top: 8px;
}

.kit-free {
  display: inline-block;
  padding: 2px 9px;
  border-radius: 999px;
  background: color-mix(in srgb, var(--accent) 14%, transparent);
  font-family: var(--font-ui);
  font-size: 11px;
  font-weight: 600;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--accent);
  vertical-align: middle;
}

.kit-topic,
.kit-copies {
  font-family: var(--font-ui);
  font-size: 12px;
  color: var(--warm-mute);
}

.kit-copies {
  margin-left: auto;
}

.recipe-head .kit-free,
.recipe-head .kit-copies {
  margin-left: 8px;
  vertical-align: middle;
}

.kit-topics {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
  margin-bottom: 18px;
}

.kit-topic-chip {
  padding: 6px 14px;
  border: 1px solid var(--ink-edge);
  border-radius: 999px;
  background: var(--ink-panel);
  font-family: var(--font-ui);
  font-size: 13px;
  color: var(--warm-dim);
  cursor: pointer;
}

.kit-topic-chip span {
  margin-left: 4px;
  color: var(--warm-faint);
}

.kit-topic-chip:hover {
  border-color: var(--accent);
}

.kit-topic-chip.is-active {
  border-color: var(--accent);
  background: var(--accent);
  color: var(--on-accent);
}

.kit-topic-chip.is-active span {
  color: inherit;
}

.kit-more {
  display: flex;
  margin: 22px auto 0;
}

.kit-more[hidden],
.kit-deck .deck-card[hidden] {
  display: none;
}

/* ---------------------------------------------- 12s. the homepage HUD ---- */

/*
 * HALF A SCREEN, WITH A HUD (2026-10-17, owner's request). The compact hero
 * read as cramped, so the homepage hero is back to about half the viewport,
 * centred, with a JARVIS-style display on the right. See _JarvisHud.cshtml.
 */
.hero-content {
  min-height: clamp(400px, 50vh, 600px);
  grid-template-columns: minmax(0, 1.2fr) minmax(0, 1fr);
}

.hero h1 {
  font-size: clamp(34px, 4vw, 60px);
}

.hero-side {
  justify-items: center;
}

.hud {
  position: relative;
  /* Sized by the screen's height as well as its width, so the HUD never
     pushes the hero past about half the screen. */
  width: min(100%, 440px, 46vh);
  min-width: 260px;
  aspect-ratio: 1;
  color: var(--accent);
}

.hud-svg {
  position: absolute;
  inset: 10%;
  width: 80%;
  height: 80%;
  overflow: visible;
  filter: drop-shadow(0 0 6px color-mix(in srgb, var(--accent) 45%, transparent));
}

/* Rotate every ring about the centre of the drawing, not of its own box. */
.hud-ring {
  transform-box: view-box;
  transform-origin: 50% 50%;
}

.hud-r1 { animation: hud-spin 60s linear infinite; }
.hud-r2 { animation: hud-spin 24s linear infinite reverse; }
.hud-r3 { animation: hud-spin 14s linear infinite; }
.hud-r4 { animation: hud-spin 9s linear infinite reverse; }
.hud-r5 { animation: hud-spin 30s linear infinite reverse; }
.hud-sweep { animation: hud-spin 4s linear infinite; }

@keyframes hud-spin {
  from { transform: rotate(0deg); }
  to   { transform: rotate(360deg); }
}

.hud-core-glow {
  transform-box: fill-box;
  transform-origin: 50% 50%;
  animation: hud-pulse 2.8s ease-in-out infinite;
}

.hud-core-dot {
  animation: hud-blink 2.8s ease-in-out infinite;
}

/* Both end where they start, so reduced motion freezes them lit. */
@keyframes hud-pulse {
  0%, 100% { transform: scale(1); opacity: 0.9; }
  50%      { transform: scale(1.18); opacity: 0.55; }
}

@keyframes hud-blink {
  0%, 100% { opacity: 1; }
  50%      { opacity: 0.55; }
}

/* The readouts: real text in the four corners, like a HUD's data panels. */
.hud-readouts {
  list-style: none;
  margin: 0;
  padding: 0;
}

.hud-ro {
  position: absolute;
  display: grid;
  gap: 2px;
  min-width: 112px;
  padding: 8px 12px;
  border: 1px solid color-mix(in srgb, var(--accent) 45%, transparent);
  border-radius: 10px;
  background: color-mix(in srgb, var(--ink-panel) 72%, transparent);
  backdrop-filter: blur(4px);
  font-family: var(--font-ui);
}

.hud-ro b {
  font-family: var(--font-num);
  font-weight: 400;
  font-size: 30px;
  line-height: 1;
  color: var(--accent);
}

.hud-ro span {
  font-size: 11px;
  letter-spacing: 0.04em;
  color: var(--warm-mute);
}

.hud-ro-tl { top: 0; left: -6%; }
.hud-ro-tr { top: 0; right: -6%; text-align: right; }
.hud-ro-bl { bottom: 0; left: -6%; }
.hud-ro-br { bottom: 0; right: -6%; text-align: right; }

@media (max-width: 900px) {
  /* This section comes after §12r, so its two-column rule must be undone
     here too, or a phone gets two squeezed columns. Found by measuring. */
  .hero-content {
    grid-template-columns: minmax(0, 1fr);
    min-height: 0;
  }

  .hud {
    width: min(100%, 300px, 30vh);
  }

  .hud-ro-tl, .hud-ro-bl { left: 0; }
  .hud-ro-tr, .hud-ro-br { right: 0; }
}

/* On a phone the rings go and the readouts become a plain 2×2 grid. */
@media (max-width: 720px) {
  .hud {
    width: 100%;
    aspect-ratio: auto;
  }

  .hud-svg {
    display: none;
  }

  .hud-readouts {
    display: grid;
    grid-template-columns: repeat(2, minmax(0, 1fr));
    gap: 10px;
  }

  .hud {
    min-width: 0;
  }

  .hud-ro {
    position: static;
    min-width: 0;
    text-align: left !important;
  }

  .hud-ro b {
    font-size: 24px;
  }
}

/* ------------------------------------ 12t. the hub HUDs (reactor, globe) - */

/*
 * A different JARVIS-style display on each hub hero (2026-10-17, owner's
 * request): an arc reactor on the Claude Kit (_HudReactor), a holographic
 * globe on Code Recipes (_HudGlobe). They replace the cube lattice there.
 * _AiLattice is no longer rendered anywhere; its CSS stays until someone
 * decides to delete it. Both reuse the homepage HUD's hud-spin keyframes.
 */
.hudx {
  justify-self: center;
  width: min(100%, 290px, 36vh);
  min-width: 200px;
  aspect-ratio: 1;
  color: var(--accent);
}

.hudx svg {
  display: block;
  width: 100%;
  height: 100%;
  overflow: visible;
  filter: drop-shadow(0 0 6px color-mix(in srgb, var(--accent) 45%, transparent));
}

.hudx-slow     { animation: hud-spin 50s linear infinite; }
.hudx-slow-rev { animation: hud-spin 36s linear infinite reverse; }
.hudx-mid-rev  { animation: hud-spin 18s linear infinite reverse; }
.hudx-fast     { animation: hud-spin 7s linear infinite; }

.hudx-pulse {
  transform-box: fill-box;
  transform-origin: 50% 50%;
  animation: hud-pulse 2.4s ease-in-out infinite;
}

.hudx-blink {
  animation: hud-blink 2.8s ease-in-out infinite;
}

.hudx-tags {
  opacity: 0.85;
}

/* The globe: meridians squeeze through zero and out the other side, which
   reads as longitude lines turning. Ends where it starts. */
.gx-meridian {
  transform-box: view-box;
  transform-origin: 50% 50%;
  animation: gx-turn 8s linear infinite;
}

@keyframes gx-turn {
  0%   { transform: scaleX(1);    opacity: 0.9; }
  25%  { transform: scaleX(0);    opacity: 0.35; }
  50%  { transform: scaleX(-1);   opacity: 0.9; }
  75%  { transform: scaleX(0);    opacity: 0.35; }
  100% { transform: scaleX(1);    opacity: 0.9; }
}

.gx-scan {
  animation: gx-sweep 3.6s ease-in-out infinite;
}

@keyframes gx-sweep {
  0%   { transform: translateY(0);     opacity: 0; }
  10%  { opacity: 1; }
  90%  { opacity: 1; }
  100% { transform: translateY(300px); opacity: 0; }
}

@media (max-width: 900px) {
  .hudx {
    display: none;
  }
}

@media (prefers-reduced-motion: reduce) {
  .gx-scan {
    display: none;
  }
}

/*
 * Reduced motion: collapse every animation to effectively zero duration. With
 * `backwards` fill on .reveal that lands on the visible end state, so content
 * is shown immediately rather than hidden.
 */
@media (prefers-reduced-motion: reduce) {
  *,
  *::before,
  *::after {
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.01ms !important;
    scroll-behavior: auto !important;
  }

  /*
   * Every lattice keyframe was written so its `to` state is the identity pose —
   * 360deg, not 359 — which means the rule above leaves a correctly assembled,
   * static object rather than a scrambled one.
   *
   * node-pulse is the exception: its end state is the dim half of the pulse, so
   * the nodes would freeze at 30% opacity. Pin them lit.
   */
  .node,
  .pending-pulse {
    opacity: 1 !important;
  }

  /*
   * The growth tower is written the same way — tier-spin, stack-orbit, ring-a
   * and ring-b all end on 360deg, and plate-glow's 0% and 100% are identical,
   * so the rule above leaves a correctly assembled, calmly lit tower.
   *
   * .stack-spark is the exception: it rises and returns, so its end state is
   * the dim bottom of the travel. Pin it lit, as with .node.
   */
  .stack-spark {
    opacity: 1 !important;
  }

  /* Neither guide.js nor about.js attaches the pointer tilt at all under
     reduced motion, but pin the variables too so nothing can leave an element
     rotated. */
  .deck-card,
  .tilt-card {
    --tilt-x: 0deg;
    --tilt-y: 0deg;
  }

  /* The certificate scan must not drift on hover, and the zoom badge has to
     stay legible rather than sitting at 0.01ms of its fade. */
  .cert-card:hover .cert-shot img {
    transform: none;
  }

  .cert-zoom {
    opacity: 1;
    transform: none;
  }
}
