@charset "UTF-8";
/* ==========================================================================
   Satyam Studio — public site
   ==========================================================================

   Token architecture
   ------------------
   1. Neutral primitives      raw greys, never used directly by components
   2. Theme tokens            --accent / --accent-2 + background tints,
                              swapped by [data-theme] on <html>
   3. Semantic tokens         --surface, --border, --grad, --glow ... derived
                              from the theme; components only ever use these

   Adding a theme = one block of 6 declarations. Nothing else changes.
   ========================================================================== */

/* ------------------------------------------------------------------ *
   1. Neutral primitives + non-colour tokens
 * ------------------------------------------------------------------ */

:root {
  /* Type */
  --font: ui-sans-serif, system-ui, -apple-system, "Segoe UI", Roboto,
          "Helvetica Neue", Arial, "Noto Sans", sans-serif;
  --font-display: "Georgia", "Times New Roman", serif;
  --font-mono: ui-monospace, "SF Mono", "Cascadia Mono", Menlo, Consolas, monospace;

  /* Fluid scale — every size derives from the viewport, so there are no
     awkward jumps between breakpoints. */
  --fs-hero:  clamp(2.5rem, 9vw, 6.5rem);
  --fs-h2:    clamp(1.9rem, 5vw, 3.5rem);
  --fs-h3:    clamp(1.1rem, 2.4vw, 1.45rem);
  --fs-lead:  clamp(1rem, 1.9vw, 1.2rem);
  --fs-body:  1rem;
  --fs-sm:    0.9rem;
  --fs-xs:    0.78rem;
  --fs-2xs:   0.7rem;

  /* Space */
  --shell:     1240px;
  --shell-wide: 1440px;
  --gutter:    clamp(1.1rem, 4vw, 3rem);
  /* Generous vertical rhythm is what makes an editorial layout read as
     considered rather than crowded. */
  --section-y: clamp(4.5rem, 12vw, 11rem);

  /* Radius */
  --r-xs: 8px;
  --r-sm: 12px;
  --r:    18px;
  --r-lg: 26px;
  --r-xl: 34px;
  --r-pill: 999px;

  /* Motion */
  --ease:     cubic-bezier(0.22, 0.68, 0.24, 1);
  --ease-out: cubic-bezier(0.16, 1, 0.3, 1);
  --ease-spring: cubic-bezier(0.34, 1.46, 0.64, 1);
  --t-fast: 0.18s;
  --t:      0.32s;
  --t-slow: 0.62s;

  /* Chrome */
  --header-h: 66px;
  /* 46, not 44: at exactly the minimum, sub-pixel rounding can land a
     control just under it. */
  --tap: 46px;

  /* Neutral primitives */
  --white: #ffffff;
  --grey-100: #f4f5f7;
  --grey-300: #c8ccd4;
  --grey-500: #8b93a1;
  --grey-600: #6b7280;
  --grey-700: #4b5361;

  --ok:     #4ade80;
  --danger: #fb7185;
}

/* ------------------------------------------------------------------ *
   2. Themes
   Each theme declares only its two accents and its background tints.
   Everything else is derived in section 3.
 * ------------------------------------------------------------------ */

/* Crimson — black + red. The default. */
:root,
[data-theme="crimson"] {
  --accent:   #ff2e4e;
  --accent-2: #ff7a5c;
  --accent-ink: #14060a;

  --bg:   #070406;
  --bg-2: #0d0709;
  --bg-3: #140b0e;
}

/* Ember — red into orange over black. */
[data-theme="ember"] {
  --accent:   #ff5722;
  --accent-2: #ffb03a;
  --accent-ink: #150802;

  --bg:   #070503;
  --bg-2: #0d0805;
  --bg-3: #140d08;
}

/* Neon — green into purple. */
[data-theme="neon"] {
  --accent:   #22e07a;
  --accent-2: #a855f7;
  --accent-ink: #04140a;

  --bg:   #04070a;
  --bg-2: #080d12;
  --bg-3: #0d1319;
}

/* Violet — purple into pink. */
[data-theme="violet"] {
  --accent:   #a78bfa;
  --accent-2: #f472b6;
  --accent-ink: #120a1c;

  --bg:   #06050b;
  --bg-2: #0b0912;
  --bg-3: #110e1a;
}

/* Ocean — cyan into blue. */
[data-theme="ocean"] {
  --accent:   #22d3ee;
  --accent-2: #4f7cff;
  --accent-ink: #04121a;

  --bg:   #040709;
  --bg-2: #070d12;
  --bg-3: #0c141b;
}

/* Gold — warm amber. */
[data-theme="gold"] {
  --accent:   #f5a524;
  --accent-2: #ffd166;
  --accent-ink: #14100a;

  --bg:   #08070a;
  --bg-2: #0d0b10;
  --bg-3: #131017;
}

/* Mono — no hue, pure contrast. */
[data-theme="mono"] {
  --accent:   #f4f4f5;
  --accent-2: #a1a1aa;
  --accent-ink: #0a0a0b;

  --bg:   #07070a;
  --bg-2: #0c0c10;
  --bg-3: #121217;
}

/* Light editorial theme. Every other theme is dark, so the shared tokens below
   assume white-on-dark: surfaces are white at low alpha and shadows are heavy.
   Inverting the accent alone would leave white borders on white paper, so this
   theme restates the surface, border and shadow tokens as black at low alpha.
   `color-scheme: light` comes with it so form controls and scrollbars follow. */
/* Qualified with `html` deliberately. The semantic tokens below are declared on
   `:root`, which has the same specificity as a bare attribute selector and
   comes later, so it would win — leaving near-white text on near-white paper.
   `html[data-theme]` outranks it regardless of order. */
html[data-theme="paper"] {
  color-scheme: light;

  --accent:   #131316;
  --accent-2: #4a4a52;
  --accent-ink: #ffffff;

  --bg:   #f7f6f4;
  --bg-2: #ffffff;
  --bg-3: #efedea;

  --text:   #101014;
  --text-2: #3d3d45;
  --muted:  #71717a;
  --faint:  #a1a1aa;

  --surface:   rgba(16, 16, 20, 0.035);
  --surface-2: rgba(16, 16, 20, 0.06);
  --glass:     rgba(255, 255, 255, 0.72);
  --blur:      saturate(1.1) blur(14px);

  --border:        rgba(16, 16, 20, 0.12);
  --border-2:      rgba(16, 16, 20, 0.2);
  --border-accent: rgba(16, 16, 20, 0.28);

  --accent-soft: rgba(16, 16, 20, 0.06);
  --accent-line: rgba(16, 16, 20, 0.22);
  --accent-glow: rgba(16, 16, 20, 0.18);

  --shadow:    0 16px 40px -26px rgba(16, 16, 20, 0.28);
  --shadow-lg: 0 36px 80px -40px rgba(16, 16, 20, 0.3);
  --shadow-accent: 0 14px 34px -18px rgba(16, 16, 20, 0.3);
}

/* ------------------------------------------------------------------ *
   3. Semantic tokens derived from the active theme
   color-mix keeps every soft tint in step with the accent automatically.
 * ------------------------------------------------------------------ */

:root {
  --text:   var(--grey-100);
  --text-2: var(--grey-300);
  --muted:  var(--grey-500);
  --faint:  var(--grey-600);

  /* Glass surfaces */
  --surface:   rgba(255, 255, 255, 0.04);
  --surface-2: rgba(255, 255, 255, 0.07);
  --glass:     rgba(255, 255, 255, 0.045);
  --blur:      saturate(1.5) blur(16px);

  /* Borders — fall back to plain white alpha where color-mix is missing */
  --border:        rgba(255, 255, 255, 0.09);
  --border-2:      rgba(255, 255, 255, 0.16);
  --border-accent: rgba(255, 255, 255, 0.2);

  /* Accent tints */
  --accent-soft: rgba(255, 255, 255, 0.1);
  --accent-line: rgba(255, 255, 255, 0.24);
  --accent-glow: rgba(255, 255, 255, 0.3);

  /* Two-tone gradients */
  --grad:      linear-gradient(135deg, var(--accent), var(--accent-2));
  --grad-soft: linear-gradient(135deg,
                 color-mix(in srgb, var(--accent) 18%, transparent),
                 color-mix(in srgb, var(--accent-2) 18%, transparent));
  --grad-line: linear-gradient(90deg, transparent,
                 color-mix(in srgb, var(--accent) 55%, transparent), transparent);

  --shadow:    0 18px 48px -22px rgba(0, 0, 0, 0.8);
  --shadow-lg: 0 40px 96px -36px rgba(0, 0, 0, 0.9);
  --shadow-accent: 0 14px 40px -16px color-mix(in srgb, var(--accent) 55%, transparent);
}

@supports (color: color-mix(in srgb, red 50%, transparent)) {
  :root {
    --accent-soft: color-mix(in srgb, var(--accent) 14%, transparent);
    --accent-line: color-mix(in srgb, var(--accent) 34%, transparent);
    --accent-glow: color-mix(in srgb, var(--accent) 45%, transparent);
    --border-accent: color-mix(in srgb, var(--accent) 32%, transparent);
    --text-accent: color-mix(in srgb, var(--accent) 72%, var(--white));
  }
}

/* ------------------------------------------------------------------ *
   4. Reset & base
 * ------------------------------------------------------------------ */

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

html {
  -webkit-text-size-adjust: 100%;
  text-size-adjust: 100%;
  scroll-behavior: smooth;
  scroll-padding-top: calc(var(--header-h) + 14px);
  /* Stops iOS rubber-band showing a light strip behind the page */
  background: var(--bg);
}

body {
  font-family: var(--font);
  font-size: var(--fs-body);
  line-height: 1.62;
  color: var(--text);
  background: var(--bg);
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  overflow-x: hidden;
  -webkit-tap-highlight-color: transparent;
  transition: background var(--t-slow) var(--ease);
}

body.is-locked { overflow: hidden; }

/* The [hidden] attribute must always win. Component rules below set
   `display`, which would otherwise override the UA default of `display: none`
   and leave invisible overlays swallowing every click. */
[hidden] { display: none !important; }

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

button, input, select, textarea { font: inherit; color: inherit; }
button { background: none; border: 0; cursor: pointer; }

a { color: inherit; text-decoration: none; }

h1, h2, h3, h4 { line-height: 1.08; font-weight: 640; letter-spacing: -0.025em; }

ul, ol { list-style: none; padding: 0; }

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

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

.sprite { position: absolute; width: 0; height: 0; overflow: hidden; }

.skip-link {
  position: fixed;
  top: 8px; left: 8px;
  z-index: 300;
  padding: 0.7rem 1.1rem;
  background: var(--accent);
  color: var(--accent-ink);
  border-radius: var(--r-xs);
  font-weight: 650;
  transform: translateY(-160%);
  transition: transform var(--t) var(--ease);
}
.skip-link:focus-visible { transform: translateY(0); }

@media (pointer: fine) {
  ::-webkit-scrollbar { width: 10px; height: 10px; }
  ::-webkit-scrollbar-track { background: var(--bg); }
  ::-webkit-scrollbar-thumb {
    background: rgba(255, 255, 255, 0.14);
    border-radius: 999px;
    border: 2px solid var(--bg);
  }
  ::-webkit-scrollbar-thumb:hover { background: var(--accent-line); }
}

/* ------------------------------------------------------------------ *
   5. Layout primitives
 * ------------------------------------------------------------------ */

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

.section { padding-block: var(--section-y); position: relative; }

/* Alternate sections get a faint top glow rather than a hard band. */
.section-alt {
  background:
    radial-gradient(110% 80% at 50% 0%, var(--accent-soft), transparent 60%),
    var(--bg-2);
  border-block: 1px solid var(--border);
}

/* Utility: !important so it beats component `display` rules declared later. */
.hide-sm { display: none !important; }

/* Reusable glass panel */
.glass {
  background: var(--glass);
  backdrop-filter: var(--blur);
  -webkit-backdrop-filter: var(--blur);
  border: 1px solid var(--border);
}

/* Gradient hairline used as a section divider */
.rule {
  height: 1px;
  background: var(--grad-line);
  border: 0;
}

/* ------------------------------------------------------------------ *
   6. Icons & buttons
 * ------------------------------------------------------------------ */

.ico {
  width: 1.15em;
  height: 1.15em;
  flex: none;
  fill: none;
  stroke: currentColor;
  stroke-width: 1.7;
  stroke-linecap: round;
  stroke-linejoin: round;
}
.ico use { pointer-events: none; }
.ico.solid { fill: currentColor; stroke: none; }

.btn {
  --btn-bg: var(--surface-2);
  --btn-fg: var(--text);
  --btn-bd: var(--border-2);

  position: relative;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 0.55rem;
  min-height: var(--tap);
  padding: 0.78rem 1.5rem;
  border: 1px solid var(--btn-bd);
  border-radius: var(--r-pill);
  background: var(--btn-bg);
  color: var(--btn-fg);
  font-size: var(--fs-sm);
  font-weight: 620;
  letter-spacing: -0.01em;
  white-space: nowrap;
  cursor: pointer;
  overflow: hidden;
  transition: transform var(--t-fast) var(--ease),
              box-shadow var(--t) var(--ease),
              border-color var(--t) var(--ease),
              background var(--t) var(--ease),
              opacity var(--t) var(--ease);
  touch-action: manipulation;
}

.btn .ico { transition: transform var(--t) var(--ease); }
.btn:active { transform: scale(0.97); }

.btn-primary {
  --btn-fg: var(--accent-ink);
  --btn-bd: transparent;
  /* Reads as lit rather than filled: the gradient runs from a dark edge into
     the accent, and the glow sits under it. */
  background: linear-gradient(100deg,
    color-mix(in srgb, var(--accent) 55%, #000 45%) 0%,
    var(--accent) 55%,
    var(--accent-2) 100%);
  box-shadow:
    var(--shadow-accent),
    0 0 0 1px color-mix(in srgb, var(--accent) 40%, transparent);
  font-weight: 680;
}

/* Fully rounded, the way the reference sets every call to action. */
.btn, .btn-primary, .btn-ghost { border-radius: var(--r-pill); }

/* Sheen sweep on hover — pure CSS, no JS */
.btn-primary::after {
  content: "";
  position: absolute;
  inset: 0;
  background: linear-gradient(100deg, transparent 20%,
              rgba(255, 255, 255, 0.35) 50%, transparent 80%);
  transform: translateX(-120%);
  transition: transform 0.7s var(--ease);
}

.btn-ghost { --btn-bg: transparent; }
.btn-sm { min-height: 40px; padding: 0.5rem 1.05rem; font-size: var(--fs-xs); }
.btn-lg { min-height: 54px; padding: 0.95rem 1.8rem; font-size: var(--fs-body); }
.btn-block { width: 100%; }
.btn[disabled], .btn[aria-disabled="true"] { opacity: 0.5; pointer-events: none; }

@media (hover: hover) and (pointer: fine) {
  .btn:hover { transform: translateY(-2px); }
  .btn:hover .ico { transform: translateX(3px); }
  .btn-primary:hover::after { transform: translateX(120%); }
  .btn:not(.btn-primary):hover {
    background: var(--surface-2);
    border-color: var(--border-accent);
  }
}

.icon-btn {
  display: inline-grid;
  place-items: center;
  width: var(--tap);
  height: var(--tap);
  border: 1px solid var(--border);
  border-radius: 50%;
  background: var(--glass);
  color: var(--text);
  backdrop-filter: var(--blur);
  -webkit-backdrop-filter: var(--blur);
  transition: background var(--t) var(--ease), border-color var(--t) var(--ease),
              transform var(--t-fast) var(--ease), color var(--t) var(--ease),
              opacity var(--t) var(--ease);
  touch-action: manipulation;
}
.icon-btn .ico { width: 1.25rem; height: 1.25rem; }
.icon-btn:active { transform: scale(0.9); }
.icon-btn[disabled] { opacity: 0.3; pointer-events: none; }

@media (hover: hover) and (pointer: fine) {
  .icon-btn:hover {
    background: var(--accent-soft);
    border-color: var(--border-accent);
    color: var(--text);
  }
}

/* ------------------------------------------------------------------ *
   7. Scroll progress
 * ------------------------------------------------------------------ */

.scroll-progress {
  position: fixed;
  inset: 0 0 auto 0;
  height: 2px;
  z-index: 120;
  pointer-events: none;
}
.scroll-progress span {
  display: block;
  height: 100%;
  width: 0;
  background: var(--grad);
  box-shadow: 0 0 12px var(--accent-glow);
}

/* ------------------------------------------------------------------ *
   8. Header
 * ------------------------------------------------------------------ */

.site-header {
  position: fixed;
  inset: 0 0 auto 0;
  z-index: 100;
  padding-top: env(safe-area-inset-top);
  border-bottom: 1px solid transparent;
  transition: transform var(--t) var(--ease), background var(--t) var(--ease),
              border-color var(--t) var(--ease), backdrop-filter var(--t) var(--ease);
}

.site-header.is-stuck {
  background: color-mix(in srgb, var(--bg) 82%, transparent);
  backdrop-filter: blur(18px) saturate(1.6);
  -webkit-backdrop-filter: blur(18px) saturate(1.6);
  border-bottom-color: var(--border);
}
@supports not (color: color-mix(in srgb, red 50%, transparent)) {
  .site-header.is-stuck { background: rgba(7, 4, 6, 0.85); }
}

.site-header.is-hidden { transform: translateY(-100%); }

.header-inner {
  display: flex;
  align-items: center;
  gap: 0.85rem;
  height: var(--header-h);
}

.brand { display: flex; align-items: center; gap: 0.65rem; margin-right: auto; min-width: 0; }

/* The mark keeps the brand lime on a dark tile, matching the supplied logo,
   rather than taking the theme accent. A logo that changes colour with the
   theme is not really a logo. The S is cut out of the shape, so the tile is
   currentColor and the letter is whatever sits behind it. */
.brand-mark {
  display: grid;
  place-items: center;
  width: 40px; height: 40px;
  flex: none;
  border-radius: 12px;
  color: #c5f82a;
  background: #0a0a0b;
  box-shadow: 0 8px 22px -12px rgba(197, 248, 42, 0.55);
}
.brand-mark svg { width: 24px; height: 24px; }

.brand-text { display: flex; flex-direction: column; line-height: 1.15; min-width: 0; }
.brand-text strong {
  font-size: 0.98rem;
  font-weight: 680;
  letter-spacing: -0.018em;
  white-space: nowrap;
}
.brand-text small {
  font-size: 0.64rem;
  color: var(--muted);
  letter-spacing: 0.09em;
  text-transform: uppercase;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.nav-desktop { display: none; }
.header-actions { display: flex; align-items: center; gap: 0.5rem; }

/* ------------------------------------------------------------------ *
   9. Mobile drawer
 * ------------------------------------------------------------------ */

.drawer-backdrop {
  position: fixed;
  inset: 0;
  z-index: 130;
  background: rgba(3, 3, 5, 0.7);
  backdrop-filter: blur(4px);
  opacity: 0;
  transition: opacity var(--t) var(--ease);
}
.drawer-backdrop.is-open { opacity: 1; }

.drawer {
  position: fixed;
  top: 0; right: 0;
  z-index: 140;
  display: flex;
  flex-direction: column;
  width: min(340px, 88vw);
  height: 100dvh;
  padding: calc(1rem + env(safe-area-inset-top)) 1.15rem
           calc(1.15rem + env(safe-area-inset-bottom));
  background:
    radial-gradient(90% 40% at 100% 0%, var(--accent-soft), transparent 70%),
    var(--bg-3);
  border-left: 1px solid var(--border);
  box-shadow: var(--shadow-lg);
  transform: translateX(100%);
  transition: transform var(--t-slow) var(--ease-out);
  overflow-y: auto;
  overscroll-behavior: contain;
}
.drawer.is-open { transform: translateX(0); }

.drawer-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding-bottom: 1.1rem;
  border-bottom: 1px solid var(--border);
}
.drawer-title {
  font-size: var(--fs-xs);
  letter-spacing: 0.18em;
  text-transform: uppercase;
  color: var(--muted);
}

.drawer-nav { display: flex; flex-direction: column; padding-block: 0.75rem; }
.drawer-nav a {
  display: flex;
  align-items: center;
  gap: 0.85rem;
  min-height: 52px;
  padding: 0.5rem 0.6rem;
  border-radius: var(--r-sm);
  font-size: 1.02rem;
  font-weight: 560;
  color: var(--text-2);
  transition: background var(--t-fast) var(--ease), color var(--t-fast) var(--ease),
              padding-left var(--t-fast) var(--ease);
}
.drawer-nav a .ico { width: 1.2rem; height: 1.2rem; color: var(--accent); }
.drawer-nav a:active { background: var(--surface-2); color: var(--text); }

@media (hover: hover) and (pointer: fine) {
  .drawer-nav a:hover {
    background: var(--accent-soft);
    color: var(--text);
    padding-left: 0.9rem;
  }
}

.drawer-foot { margin-top: auto; padding-top: 1.15rem; border-top: 1px solid var(--border); }
.drawer-socials { display: flex; gap: 0.5rem; margin-top: 0.95rem; flex-wrap: wrap; }

.social-btn {
  display: grid;
  place-items: center;
  width: var(--tap); height: var(--tap);
  border: 1px solid var(--border);
  border-radius: 50%;
  color: var(--text-2);
  background: var(--surface);
  transition: color var(--t) var(--ease), border-color var(--t) var(--ease),
              background var(--t) var(--ease), transform var(--t-fast) var(--ease);
}
.social-btn:active { transform: scale(0.9); }
@media (hover: hover) and (pointer: fine) {
  .social-btn:hover {
    color: var(--accent);
    border-color: var(--border-accent);
    background: var(--accent-soft);
    transform: translateY(-2px);
  }
}

/* ------------------------------------------------------------------ *
   10. Hero
 * ------------------------------------------------------------------ */

.hero {
  position: relative;
  display: flex;
  flex-direction: column;
  justify-content: flex-end;
  min-height: 100svh;
  padding-top: calc(var(--header-h) + 2rem);
  overflow: hidden;
  isolation: isolate;
}

/* overflow:hidden so the oversized glow orbs inside can never contribute to
   the page's scroll width. */
.hero-bg {
  position: absolute;
  inset: 0;
  z-index: -2;
  overflow: hidden;
  background: var(--bg);
}

/* Animated gradient mesh in the theme colours */
.hero-glow {
  position: absolute;
  border-radius: 50%;
  filter: blur(90px);
  will-change: transform;
}
.hero-glow-1 {
  width: 68vmax; height: 68vmax;
  top: -30vmax; right: -22vmax;
  background: radial-gradient(circle,
    color-mix(in srgb, var(--accent) 50%, transparent), transparent 65%);
  opacity: 0.6;
  animation: drift-a 22s var(--ease) infinite alternate;
}
.hero-glow-2 {
  width: 56vmax; height: 56vmax;
  bottom: -26vmax; left: -18vmax;
  background: radial-gradient(circle,
    color-mix(in srgb, var(--accent-2) 42%, transparent), transparent 65%);
  opacity: 0.5;
  animation: drift-b 26s var(--ease) infinite alternate;
}

@supports not (color: color-mix(in srgb, red 50%, transparent)) {
  .hero-glow-1 { background: radial-gradient(circle, var(--accent), transparent 65%); opacity: 0.35; }
  .hero-glow-2 { background: radial-gradient(circle, var(--accent-2), transparent 65%); opacity: 0.3; }
}

@keyframes drift-a {
  from { transform: translate3d(0, 0, 0) scale(1); }
  to   { transform: translate3d(-8vw, 6vh, 0) scale(1.12); }
}
@keyframes drift-b {
  from { transform: translate3d(0, 0, 0) scale(1.08); }
  to   { transform: translate3d(7vw, -5vh, 0) scale(1); }
}

/* Fine grid + vignette for texture */
.hero-grain {
  position: absolute;
  inset: 0;
  opacity: 0.5;
  background-image:
    linear-gradient(rgba(255, 255, 255, 0.022) 1px, transparent 1px),
    linear-gradient(90deg, rgba(255, 255, 255, 0.022) 1px, transparent 1px);
  background-size: 52px 52px;
  mask-image: radial-gradient(125% 85% at 50% 35%, #000 25%, transparent 78%);
  -webkit-mask-image: radial-gradient(125% 85% at 50% 35%, #000 25%, transparent 78%);
}

.hero-track { position: relative; flex: 1; display: grid; align-items: center; }

.hero-slide {
  grid-area: 1 / 1;
  display: flex;
  align-items: center;
  opacity: 0;
  visibility: hidden;
  transform: translateY(20px);
  transition: opacity var(--t-slow) var(--ease-out),
              transform var(--t-slow) var(--ease-out),
              visibility var(--t-slow);
  pointer-events: none;
}
.hero-slide.is-active {
  opacity: 1;
  visibility: visible;
  transform: none;
  pointer-events: auto;
}

.hero-slide-media {
  position: absolute;
  inset: 0;
  z-index: -1;
  opacity: 0;
  transition: opacity var(--t-slow) var(--ease-out);
}
.hero-slide.is-active .hero-slide-media { opacity: 1; }
.hero-slide-media img { width: 100%; height: 100%; object-fit: cover; }
.hero-slide-media::after {
  content: "";
  position: absolute;
  inset: 0;
  background: linear-gradient(180deg,
    color-mix(in srgb, var(--bg) 60%, transparent) 0%,
    color-mix(in srgb, var(--bg) 40%, transparent) 40%,
    var(--bg) 100%);
}

.hero-content {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 1.4rem;
  padding-block: 2rem;
}

.kicker {
  display: inline-flex;
  align-items: center;
  gap: 0.6rem;
  padding: 0.44rem 1rem 0.44rem 0.75rem;
  border: 1px solid var(--border-accent);
  border-radius: var(--r-pill);
  background: var(--accent-soft);
  color: var(--text);
  font-size: var(--fs-xs);
  font-weight: 600;
  letter-spacing: 0.05em;
  text-transform: uppercase;
  backdrop-filter: blur(8px);
  -webkit-backdrop-filter: blur(8px);
  max-width: 100%;
}
.kicker-dot {
  width: 7px; height: 7px;
  flex: none;
  border-radius: 50%;
  background: var(--accent);
  animation: pulse 2.4s var(--ease) infinite;
}

@keyframes pulse {
  0%, 100% { box-shadow: 0 0 0 3px var(--accent-soft); }
  50%      { box-shadow: 0 0 0 9px transparent; }
}

.hero-title {
  font-size: var(--fs-hero);
  font-weight: 700;
  letter-spacing: -0.04em;
  /* 0.95 was tight enough that descenders in one line touched the caps of the
     next ("something" / "unforgettable"). This still reads as display type. */
  line-height: 1.04;
  max-width: 18ch;
  text-wrap: balance;
}

/* Gradient-filled emphasis. Falls back to a solid accent. */
.hero-title em,
.section-title em {
  font-family: var(--font-display);
  font-style: italic;
  font-weight: 400;
  letter-spacing: -0.02em;
  color: var(--accent);
}
@supports (background-clip: text) or (-webkit-background-clip: text) {
  .hero-title em,
  .section-title em {
    background: var(--grad);
    -webkit-background-clip: text;
    background-clip: text;
    color: transparent;
    /* Clipping the background to the glyphs also clips the descenders of an
       italic face, so give them a little room to sit in. */
    padding-right: 0.08em;
  }
}

.hero-sub {
  font-size: var(--fs-lead);
  color: var(--text-2);
  max-width: 48ch;
  text-wrap: pretty;
}

.hero-cta { display: flex; flex-wrap: wrap; gap: 0.7rem; margin-top: 0.3rem; }

.hero-controls {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 1rem;
  padding-bottom: 1.25rem;
}

.hero-dots { display: flex; align-items: center; gap: 0.45rem; }
.hero-dot {
  position: relative;
  width: 30px; height: var(--tap);
  border: 0;
  background: none;
  cursor: pointer;
  touch-action: manipulation;
}
.hero-dot::before {
  content: "";
  position: absolute;
  top: 50%; left: 0;
  translate: 0 -50%;
  width: 24px; height: 3px;
  border-radius: 999px;
  background: rgba(255, 255, 255, 0.22);
  transition: background var(--t) var(--ease), width var(--t) var(--ease);
}
.hero-dot[aria-selected="true"] { width: 52px; }
.hero-dot[aria-selected="true"]::before {
  width: 46px;
  background: var(--grad);
}

.hero-arrows { display: flex; gap: 0.5rem; }

/* Kinetic marquee across the bottom of the hero */
.hero-marquee {
  position: relative;
  display: flex;
  overflow: hidden;
  padding-block: 0.9rem;
  border-block: 1px solid var(--border);
  background: color-mix(in srgb, var(--bg-2) 70%, transparent);
  mask-image: linear-gradient(90deg, transparent, #000 8%, #000 92%, transparent);
  -webkit-mask-image: linear-gradient(90deg, transparent, #000 8%, #000 92%, transparent);
}
.marquee-track {
  display: flex;
  align-items: center;
  gap: 2.5rem;
  flex: none;
  padding-right: 2.5rem;
  animation: marquee 38s linear infinite;
  will-change: transform;
}
.marquee-track span {
  display: inline-flex;
  align-items: center;
  gap: 2.5rem;
  font-size: var(--fs-sm);
  font-weight: 600;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--muted);
  white-space: nowrap;
}
.marquee-track span::after {
  content: "";
  width: 5px; height: 5px;
  border-radius: 50%;
  background: var(--accent);
  flex: none;
}
.hero-marquee:hover .marquee-track { animation-play-state: paused; }

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

.hero-scroll {
  position: absolute;
  left: 50%;
  bottom: calc(5.5rem + env(safe-area-inset-bottom));
  translate: -50% 0;
  display: none;
  flex-direction: column;
  align-items: center;
  gap: 0.35rem;
  color: var(--faint);
  font-size: var(--fs-xs);
  z-index: 2;
}
.hero-scroll span {
  display: block;
  width: 1px; height: 32px;
  background: linear-gradient(180deg, transparent, var(--accent));
}
.hero-scroll .ico { animation: bob 2.2s var(--ease) infinite; }

@keyframes bob {
  0%, 100% { transform: translateY(0); opacity: 0.5; }
  50%      { transform: translateY(5px); opacity: 1; }
}

/* ------------------------------------------------------------------ *
   11. Stats (bento tiles)
 * ------------------------------------------------------------------ */

.stats {
  padding-block: clamp(2rem, 5vw, 3.5rem);
  background: var(--bg-2);
  border-bottom: 1px solid var(--border);
}

.stats-grid {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 0.7rem;
}

.stat {
  position: relative;
  display: flex;
  flex-direction: column;
  gap: 0.2rem;
  padding: 1.15rem 1.2rem;
  border: 1px solid var(--border);
  border-radius: var(--r);
  background: var(--glass);
  backdrop-filter: var(--blur);
  -webkit-backdrop-filter: var(--blur);
  overflow: hidden;
  transition: border-color var(--t) var(--ease), transform var(--t) var(--ease);
}
.stat::before {
  content: "";
  position: absolute;
  inset: 0 0 auto 0;
  height: 1px;
  background: var(--grad-line);
}
.stat-value {
  font-size: clamp(1.7rem, 6vw, 2.9rem);
  font-weight: 700;
  letter-spacing: -0.04em;
  line-height: 1.05;
  font-variant-numeric: tabular-nums;
  background: linear-gradient(150deg, var(--white) 20%, var(--accent));
  -webkit-background-clip: text;
  background-clip: text;
  color: transparent;
}
.stat-label {
  font-size: var(--fs-2xs);
  color: var(--muted);
  letter-spacing: 0.1em;
  text-transform: uppercase;
}

@media (hover: hover) and (pointer: fine) {
  .stat:hover { border-color: var(--border-accent); transform: translateY(-3px); }
}

/* ------------------------------------------------------------------ *
   11b. Brands strip (social proof)
 * ------------------------------------------------------------------ */

.brands {
  padding-block: clamp(2rem, 5vw, 3.25rem);
  background: var(--bg);
  border-bottom: 1px solid var(--border);
}

/* The heading sits closer to the row than a normal section head, because a client
   list is a caption to a claim rather than a chapter of its own. */
.brands .section-head { margin-bottom: clamp(1.4rem, 3.5vw, 2.2rem); }

/* --- the frame the marquee scrolls inside ---
   Inert until site.js marks it, so every other arrangement is unaffected. The mask
   is what makes the row read as continuing past the edges instead of being cut. */
.brands-rail { position: relative; }

.brands-rail.is-marquee {
  overflow: hidden;
  /* Bleed past the shell so the row runs the full width of the screen. */
  margin-inline: calc(var(--gutter) * -1);
  padding-block: 0.4rem;
  mask-image: linear-gradient(90deg, transparent, #000 6%, #000 94%, transparent);
  -webkit-mask-image: linear-gradient(90deg, transparent, #000 6%, #000 94%, transparent);
}

.brands-rail.is-marquee .brands-strip {
  --brands-gap: clamp(0.5rem, 1.6vw, 0.9rem);
  display: flex;
  width: max-content;
  gap: var(--brands-gap);
  /* The trailing gap matters. `gap` only sits *between* children, so without it the
     track is one gap short of two whole halves and -50% lands half a gap early —
     a small but visible stutter every time the loop comes round. */
  padding-right: var(--brands-gap);
  /* site.js lays out two identical halves, so -50% is exactly one of them. */
  animation: marquee 42s linear infinite;
  will-change: transform;
}

/* Reading a moving row is unpleasant; stopping it on hover is the least the
   section can do for someone who wants to look. */
.brands-rail.is-marquee:hover .brands-strip { animation-play-state: paused; }

/* Still and scrollable rather than still and truncated: with motion off the row
   has to remain reachable by hand. */
html[data-motion="off"] .brands-rail.is-marquee,
html[data-anim="none"] .brands-rail.is-marquee { overflow-x: auto; }
html[data-motion="off"] .brands-rail.is-marquee .brands-strip,
html[data-anim="none"] .brands-rail.is-marquee .brands-strip { animation: none; }

@media (prefers-reduced-motion: reduce) {
  .brands-rail.is-marquee { overflow-x: auto; }
  .brands-rail.is-marquee .brands-strip { animation: none; }
}

.brands-rail.is-marquee .brand-item {
  flex: none;
  min-height: 0;
  padding: 0.7rem 1.15rem;
}

.brands-strip {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 0.6rem;
  align-items: stretch;
}

.brand-item {
  display: grid;
  place-items: center;
  min-height: 84px;
  padding: 1rem 1.15rem;
  border: 1px solid var(--border);
  border-radius: var(--r-sm);
  background: var(--glass);
  backdrop-filter: var(--blur);
  -webkit-backdrop-filter: var(--blur);
  transition: border-color var(--t) var(--ease), background var(--t) var(--ease),
              transform var(--t) var(--ease);
}

/* Uploaded logos are normalised to greyscale, then colour up on hover */
.brand-item img {
  max-height: 40px;
  width: auto;
  object-fit: contain;
  filter: grayscale(1) brightness(1.7);
  opacity: 0.75;
  transition: filter var(--t) var(--ease), opacity var(--t) var(--ease);
}

/* Fallback wordmark when no logo has been uploaded yet */
.brand-word {
  font-size: clamp(1rem, 3.4vw, 1.3rem);
  font-weight: 700;
  letter-spacing: -0.02em;
  color: var(--text-2);
  text-align: center;
  transition: color var(--t) var(--ease);
}

.brand-item:active { transform: scale(0.98); }

@media (hover: hover) and (pointer: fine) {
  .brand-item:hover {
    border-color: var(--border-accent);
    background: var(--accent-soft);
    transform: translateY(-3px);
  }
  .brand-item:hover img { filter: none; opacity: 1; }
  .brand-item:hover .brand-word { color: var(--text); }
}

@media (min-width: 560px) {
  .brands-strip { grid-template-columns: repeat(3, minmax(0, 1fr)); }
}
@media (min-width: 900px) {
  .brands-strip { grid-template-columns: repeat(auto-fit, minmax(150px, 1fr)); }
}

/* ------------------------------------------------------------------ *
   12. Section headings
 * ------------------------------------------------------------------ */

.section-head {
  display: flex;
  flex-direction: column;
  gap: 0.9rem;
  max-width: 64ch;
  margin-bottom: clamp(2.2rem, 5vw, 3.6rem);
}
.section-head-left { max-width: 50ch; }
.section-head-row { flex-direction: column; max-width: none; }

/* Section headings are centred small-caps rather than left-aligned display
   type: "ABOUT OUR AGENCY", "TRUSTED BY LEADING BRANDS". The restraint is the
   effect — the weight stays light and the tracking does the work. */
.section-head {
  align-items: center;
  text-align: center;
  margin-inline: auto;
}

/* The about block keeps its left-aligned column, where the copy runs long
   enough that centring would hurt readability. */
.section-head-left {
  align-items: flex-start;
  text-align: left;
  margin-inline: 0;
}

.eyebrow {
  display: inline-flex;
  align-items: center;
  gap: 0.55rem;
  font-size: var(--fs-2xs);
  font-weight: 600;
  letter-spacing: 0.22em;
  text-transform: uppercase;
  color: var(--accent);
}
.eyebrow .ico { width: 0.9rem; height: 0.9rem; color: var(--accent); }

.section-title {
  /* Smaller than display type, and uppercase, so tracking rather than size
     carries the emphasis. */
  font-size: clamp(1.35rem, 3.6vw, 2.35rem);
  font-weight: 450;
  text-transform: uppercase;
  letter-spacing: 0.01em;
  line-height: 1.25;
  text-wrap: balance;
}

/* Emphasis stays in the accent but drops the italic serif, which would read as
   a different voice from the flat capitals around it. */
.section-title em {
  font-family: inherit;
  font-style: normal;
  font-weight: 550;
}
.section-lead { font-size: var(--fs-lead); color: var(--muted); max-width: 56ch; text-wrap: pretty; }

.carousel-arrows { display: flex; gap: 0.5rem; }
.carousel-arrows-center { justify-content: center; margin-top: 1.6rem; }

.empty-note {
  padding: 2.75rem 1rem;
  text-align: center;
  color: var(--faint);
  border: 1px dashed var(--border-2);
  border-radius: var(--r);
}

/* ------------------------------------------------------------------ *
   13. Services — bento grid
 * ------------------------------------------------------------------ */

.services-grid {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  gap: 0.75rem;
  grid-auto-flow: dense;
}

.service-card {
  position: relative;
  display: flex;
  gap: 1rem;
  padding: 1.4rem 1.35rem;
  border: 1px solid var(--border);
  border-radius: var(--r);
  background: var(--glass);
  backdrop-filter: var(--blur);
  -webkit-backdrop-filter: var(--blur);
  overflow: hidden;
  transition: border-color var(--t) var(--ease), background var(--t) var(--ease),
              transform var(--t) var(--ease), box-shadow var(--t) var(--ease);
}

/* Gradient wash that fades in on hover */
.service-card::before {
  content: "";
  position: absolute;
  inset: 0;
  background: var(--grad-soft);
  opacity: 0;
  transition: opacity var(--t) var(--ease);
  pointer-events: none;
}
.service-card:active { transform: scale(0.99); }

@media (hover: hover) and (pointer: fine) {
  .service-card:hover {
    border-color: var(--border-accent);
    transform: translateY(-4px);
    box-shadow: var(--shadow);
  }
  .service-card:hover::before { opacity: 1; }
  .service-card:hover .service-icon {
    color: var(--accent-ink);
    background: var(--grad);
    border-color: transparent;
    transform: rotate(-6deg) scale(1.06);
  }
}

.service-icon {
  position: relative;
  display: grid;
  place-items: center;
  width: 48px; height: 48px;
  flex: none;
  border-radius: 14px;
  color: var(--accent);
  background: var(--accent-soft);
  border: 1px solid var(--border-accent);
  transition: background var(--t) var(--ease), color var(--t) var(--ease),
              transform var(--t) var(--ease-spring), border-color var(--t) var(--ease);
}
.service-icon .ico { width: 1.45rem; height: 1.45rem; }

.service-body { position: relative; min-width: 0; }
.service-title { font-size: 1.05rem; font-weight: 660; margin-bottom: 0.32rem; letter-spacing: -0.015em; }
.service-desc { font-size: var(--fs-sm); color: var(--muted); text-wrap: pretty; }
.service-note {
  display: inline-block;
  margin-top: 0.7rem;
  padding: 0.22rem 0.65rem;
  border-radius: var(--r-pill);
  background: var(--accent-soft);
  border: 1px solid var(--border-accent);
  font-size: var(--fs-2xs);
  font-weight: 620;
  color: var(--accent);
}

/* ------------------------------------------------------------------ *
   14. Filters
 * ------------------------------------------------------------------ */

.filters {
  display: flex;
  gap: 0.45rem;
  margin-bottom: 1.7rem;
  padding-bottom: 0.5rem;
  overflow-x: auto;
  scrollbar-width: none;
  -webkit-overflow-scrolling: touch;
  scroll-snap-type: x proximity;
  /* Bleed to the screen edges so chips can scroll fully on mobile */
  margin-inline: calc(var(--gutter) * -1);
  padding-inline: var(--gutter);
}
.filters::-webkit-scrollbar { display: none; }

.filter-chip {
  flex: none;
  min-height: 44px;
  padding: 0.5rem 1.1rem;
  border: 1px solid var(--border);
  border-radius: var(--r-pill);
  background: var(--surface);
  color: var(--text-2);
  font-size: var(--fs-sm);
  font-weight: 560;
  white-space: nowrap;
  scroll-snap-align: start;
  transition: background var(--t) var(--ease), color var(--t) var(--ease),
              border-color var(--t) var(--ease), transform var(--t-fast) var(--ease);
  touch-action: manipulation;
}
.filter-chip:active { transform: scale(0.95); }
.filter-chip[aria-selected="true"] {
  background: var(--grad);
  border-color: transparent;
  color: var(--accent-ink);
  font-weight: 680;
  box-shadow: var(--shadow-accent);
}
.filter-chip-count { opacity: 0.6; margin-left: 0.35rem; font-variant-numeric: tabular-nums; }

@media (hover: hover) and (pointer: fine) {
  .filter-chip:hover:not([aria-selected="true"]) {
    border-color: var(--border-accent);
    color: var(--text);
    background: var(--accent-soft);
  }
}

/* ------------------------------------------------------------------ *
   15. Work grid
 * ------------------------------------------------------------------ */

.work-grid {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  gap: 1rem;
}

.work-card {
  position: relative;
  display: flex;
  flex-direction: column;
  border: 1px solid var(--border);
  border-radius: var(--r-lg);
  background: var(--glass);
  overflow: hidden;
  cursor: pointer;
  text-align: left;
  width: 100%;
  transition: transform var(--t) var(--ease), border-color var(--t) var(--ease),
              box-shadow var(--t) var(--ease);
  touch-action: manipulation;
}
.work-card:active { transform: scale(0.985); }

@media (hover: hover) and (pointer: fine) {
  .work-card:hover {
    transform: translateY(-6px);
    border-color: var(--border-accent);
    box-shadow: var(--shadow-lg);
  }
  .work-card:hover .work-thumb img { transform: scale(1.07); }
  .work-card:hover .work-play { opacity: 1; transform: scale(1); }
  .work-card:hover .work-title { color: var(--accent); }
}

.work-thumb {
  position: relative;
  aspect-ratio: 16 / 10;
  background: linear-gradient(145deg, var(--bg-3), var(--bg-2));
  overflow: hidden;
}
.work-thumb img {
  width: 100%; height: 100%;
  object-fit: cover;
  transition: transform var(--t-slow) var(--ease-out);
}
.work-thumb::after {
  content: "";
  position: absolute;
  inset: 0;
  background: linear-gradient(180deg, transparent 55%,
              color-mix(in srgb, var(--bg) 75%, transparent));
  pointer-events: none;
}

.thumb-placeholder {
  position: absolute;
  inset: 0;
  display: grid;
  place-items: center;
  color: var(--accent);
  opacity: 0.18;
}
.thumb-placeholder .ico { width: 2.6rem; height: 2.6rem; }

.work-play {
  position: absolute;
  right: 0.9rem; bottom: 0.9rem;
  z-index: 1;
  display: grid;
  place-items: center;
  width: 46px; height: 46px;
  border-radius: 50%;
  background: var(--grad);
  color: var(--accent-ink);
  box-shadow: var(--shadow-accent);
  opacity: 0.95;
  transition: opacity var(--t) var(--ease), transform var(--t) var(--ease-spring);
}
.work-play .ico { width: 1.1rem; height: 1.1rem; fill: currentColor; stroke: none; margin-left: 2px; }

.work-featured {
  position: absolute;
  top: 0.85rem; left: 0.85rem;
  z-index: 1;
  display: inline-flex;
  align-items: center;
  gap: 0.32rem;
  padding: 0.3rem 0.65rem;
  border-radius: var(--r-pill);
  background: color-mix(in srgb, var(--bg) 70%, transparent);
  backdrop-filter: blur(8px);
  -webkit-backdrop-filter: blur(8px);
  border: 1px solid var(--border-accent);
  color: var(--accent);
  font-size: var(--fs-2xs);
  font-weight: 680;
  letter-spacing: 0.05em;
  text-transform: uppercase;
}
.work-featured .ico { width: 0.8rem; height: 0.8rem; fill: currentColor; stroke: none; }

.work-body { padding: 1.2rem 1.25rem 1.35rem; display: flex; flex-direction: column; gap: 0.5rem; }

.work-meta {
  display: flex;
  align-items: center;
  gap: 0.55rem;
  font-size: var(--fs-2xs);
  color: var(--accent);
  font-weight: 680;
  letter-spacing: 0.09em;
  text-transform: uppercase;
}
.work-meta .dot { width: 3px; height: 3px; border-radius: 50%; background: currentColor; opacity: 0.5; }
.work-meta .year { color: var(--faint); }

.work-title {
  font-size: var(--fs-h3);
  font-weight: 660;
  letter-spacing: -0.02em;
  transition: color var(--t) var(--ease);
}
.work-desc {
  font-size: var(--fs-sm);
  color: var(--muted);
  display: -webkit-box;
  -webkit-line-clamp: 2;
  line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
}

.tag-row { display: flex; flex-wrap: wrap; gap: 0.35rem; margin-top: 0.25rem; }
.tag {
  padding: 0.2rem 0.6rem;
  border: 1px solid var(--border);
  border-radius: var(--r-pill);
  font-size: var(--fs-2xs);
  color: var(--faint);
}

/* ------------------------------------------------------------------ *
   16. About
 * ------------------------------------------------------------------ */

.about-layout {
  display: grid;
  gap: clamp(2.25rem, 6vw, 4.5rem);
  grid-template-columns: minmax(0, 1fr);
}

/* Padding reserves room for the badge that overhangs the photo, so the badge
   never sticks outside this box and can't widen the page. */
.about-visual {
  position: relative;
  max-width: 440px;
  padding-right: 0.6rem;
  padding-bottom: 1.4rem;
}

/* Gradient frame around the portrait */
.about-photo {
  position: relative;
  aspect-ratio: 4 / 5;
  border-radius: var(--r-xl);
  background: linear-gradient(150deg, var(--bg-3), var(--bg-2));
  overflow: hidden;
  isolation: isolate;
}
.about-photo::before {
  content: "";
  position: absolute;
  inset: 0;
  padding: 1.5px;
  border-radius: inherit;
  background: var(--grad);
  -webkit-mask: linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0);
  mask: linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0);
  -webkit-mask-composite: xor;
  mask-composite: exclude;
  opacity: 0.7;
  z-index: 2;
  pointer-events: none;
}
.about-photo img { width: 100%; height: 100%; object-fit: cover; }
.about-photo::after {
  content: "";
  position: absolute;
  inset: 0;
  background: linear-gradient(180deg, transparent 55%,
              color-mix(in srgb, var(--bg) 65%, transparent));
  pointer-events: none;
}

.about-photo-fallback {
  position: absolute;
  inset: 0;
  display: grid;
  place-content: center;
  justify-items: center;
  gap: 0.8rem;
  color: var(--faint);
  text-align: center;
  padding: 1.5rem;
}
.about-photo-fallback .ico { width: 2.8rem; height: 2.8rem; color: var(--accent); opacity: 0.4; }
.about-photo-fallback span { font-size: var(--fs-sm); letter-spacing: 0.08em; text-transform: uppercase; }

.about-badge {
  position: absolute;
  right: 0; bottom: 0;
  z-index: 3;
  display: flex;
  align-items: center;
  gap: 0.7rem;
  padding: 0.9rem 1.2rem;
  border-radius: var(--r);
  background: var(--grad);
  color: var(--accent-ink);
  box-shadow: var(--shadow-accent);
}
.about-badge strong { font-size: 1.9rem; font-weight: 760; letter-spacing: -0.045em; line-height: 1; }
.about-badge span {
  font-size: var(--fs-2xs);
  font-weight: 680;
  line-height: 1.2;
  text-transform: uppercase;
  letter-spacing: 0.06em;
}

.about-text { font-size: var(--fs-lead); color: var(--text-2); margin-bottom: 1.8rem; text-wrap: pretty; }

.about-facts {
  display: grid;
  gap: 1.1rem;
  padding: 1.4rem 0;
  margin-bottom: 2rem;
  border-block: 1px solid var(--border);
  grid-template-columns: minmax(0, 1fr);
}
.about-facts dt {
  font-size: var(--fs-2xs);
  color: var(--accent);
  letter-spacing: 0.12em;
  text-transform: uppercase;
  font-weight: 650;
  margin-bottom: 0.25rem;
}
.about-facts dd { font-size: var(--fs-sm); color: var(--text); font-weight: 550; }

.skills-title {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  font-size: var(--fs-2xs);
  font-weight: 650;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--muted);
  margin-bottom: 1.2rem;
}
.skills-title .ico { color: var(--accent); }

.skills-list {
  display: grid;
  gap: 1.1rem;
  grid-template-columns: minmax(0, 1fr);
}
.skill-row {
  display: grid;
  gap: 0.45rem;
  grid-template-columns: minmax(0, 1fr);
}
.skill-head { display: flex; justify-content: space-between; align-items: baseline; gap: 1rem; }
.skill-name { font-size: var(--fs-sm); font-weight: 580; }
.skill-pct {
  font-size: var(--fs-xs);
  color: var(--accent);
  font-variant-numeric: tabular-nums;
  font-weight: 680;
}

.skill-bar {
  height: 6px;
  border-radius: 999px;
  background: rgba(255, 255, 255, 0.07);
  overflow: hidden;
}
.skill-fill {
  display: block;
  height: 100%;
  width: 0;
  border-radius: 999px;
  background: var(--grad);
  box-shadow: 0 0 14px var(--accent-glow);
  transition: width 1.2s var(--ease-out);
}

/* ------------------------------------------------------------------ *
   17. Timeline
 * ------------------------------------------------------------------ */

.timeline {
  position: relative;
  display: grid;
  gap: 0.5rem;
  padding-left: 1.75rem;
  grid-template-columns: minmax(0, 1fr);
}
.timeline::before {
  content: "";
  position: absolute;
  top: 0.8rem; bottom: 0.8rem; left: 5px;
  width: 2px;
  border-radius: 2px;
  background: linear-gradient(180deg, var(--accent), var(--accent-2), transparent);
  opacity: 0.5;
}

.tl-item { position: relative; padding: 1.2rem 0 1.6rem 0.4rem; }
.tl-item::before {
  content: "";
  position: absolute;
  top: 1.6rem; left: calc(-1.75rem + 6px);
  translate: -50% 0;
  width: 12px; height: 12px;
  border-radius: 50%;
  background: var(--bg-2);
  border: 2px solid var(--accent);
  box-shadow: 0 0 0 4px var(--accent-soft);
}
.tl-item:not(:last-child) { border-bottom: 1px solid var(--border); }

.tl-period {
  display: inline-flex;
  align-items: center;
  gap: 0.5rem;
  font-size: var(--fs-2xs);
  font-weight: 680;
  letter-spacing: 0.09em;
  text-transform: uppercase;
  color: var(--accent);
  margin-bottom: 0.45rem;
}
.tl-current {
  padding: 0.12rem 0.5rem;
  border-radius: var(--r-pill);
  background: var(--accent-soft);
  border: 1px solid var(--border-accent);
  font-size: 0.62rem;
}
.tl-role { font-size: var(--fs-h3); font-weight: 660; margin-bottom: 0.28rem; letter-spacing: -0.02em; }
.tl-org { font-size: var(--fs-sm); color: var(--text-2); font-weight: 550; margin-bottom: 0.55rem; }
.tl-org .sep { color: var(--faint); margin-inline: 0.4rem; }
.tl-desc { font-size: var(--fs-sm); color: var(--muted); max-width: 64ch; text-wrap: pretty; }

/* ------------------------------------------------------------------ *
   18. Studio
 * ------------------------------------------------------------------ */

.studio-card {
  position: relative;
  display: grid;
  gap: 2rem;
  padding: clamp(1.75rem, 5vw, 3.75rem);
  border: 1px solid var(--border-accent);
  border-radius: var(--r-xl);
  background:
    radial-gradient(130% 140% at 100% 0%, var(--accent-soft), transparent 55%),
    var(--bg-2);
  overflow: hidden;
}

.status-pill {
  display: inline-flex;
  align-items: center;
  gap: 0.55rem;
  align-self: flex-start;
  padding: 0.44rem 1rem 0.44rem 0.78rem;
  border: 1px solid var(--border-accent);
  border-radius: var(--r-pill);
  background: var(--accent-soft);
  color: var(--text);
  font-size: var(--fs-xs);
  font-weight: 680;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  margin-bottom: 1.2rem;
}
.pulse {
  width: 8px; height: 8px;
  flex: none;
  border-radius: 50%;
  background: var(--accent);
  animation: pulse 2.2s var(--ease) infinite;
}

.studio-title {
  font-size: clamp(1.95rem, 6vw, 3.2rem);
  letter-spacing: -0.04em;
  margin-bottom: 0.45rem;
}
.studio-type {
  font-size: var(--fs-sm);
  font-weight: 650;
  letter-spacing: 0.09em;
  text-transform: uppercase;
  margin-bottom: 1.15rem;
  background: var(--grad);
  -webkit-background-clip: text;
  background-clip: text;
  color: transparent;
}
.studio-note { font-size: var(--fs-lead); color: var(--text-2); max-width: 48ch; margin-bottom: 1.6rem; }

.studio-meta {
  display: grid;
  gap: 0.65rem;
  margin-bottom: 2rem;
  grid-template-columns: minmax(0, 1fr);
}
.studio-meta p { display: flex; align-items: center; gap: 0.65rem; font-size: var(--fs-sm); color: var(--text-2); }
.studio-meta .ico { color: var(--accent); flex: none; }

.studio-visual {
  display: none;
  position: relative;
  place-items: center;
  /* The largest ring can exceed a narrow column; clip rather than widen it. */
  overflow: hidden;
}
.studio-rings {
  position: absolute;
  inset: 0;
  display: grid;
  place-items: center;
  overflow: hidden;
}
.studio-rings span {
  position: absolute;
  border: 1px solid var(--border-accent);
  border-radius: 50%;
  animation: ripple 4.5s var(--ease) infinite;
}
.studio-rings span:nth-child(1) { width: 150px; height: 150px; }
.studio-rings span:nth-child(2) { width: 240px; height: 240px; animation-delay: 1.5s; }
.studio-rings span:nth-child(3) { width: 330px; height: 330px; animation-delay: 3s; }

@keyframes ripple {
  0%   { opacity: 0; transform: scale(0.85); }
  40%  { opacity: 1; }
  100% { opacity: 0; transform: scale(1.15); }
}

.studio-icon {
  width: 104px; height: 104px;
  color: var(--accent);
  fill: none;
  stroke: currentColor;
  stroke-width: 1.1;
  stroke-linecap: round;
  stroke-linejoin: round;
  filter: drop-shadow(0 0 24px var(--accent-glow));
}

/* ------------------------------------------------------------------ *
   19. Carousels
 * ------------------------------------------------------------------ */

.carousel {
  overflow-x: auto;
  overflow-y: hidden;
  scroll-snap-type: x mandatory;
  -webkit-overflow-scrolling: touch;
  scrollbar-width: none;
  overscroll-behavior-x: contain;
  padding-block: 0.5rem;
}
.carousel::-webkit-scrollbar { display: none; }

.carousel-track {
  display: flex;
  gap: 1rem;
  padding-inline: var(--gutter);
  width: max-content;
  min-width: 100%;
}
@media (min-width: 1280px) {
  .carousel-track {
    padding-inline: max(var(--gutter), calc((100vw - var(--shell)) / 2 + var(--gutter)));
  }
}

.carousel-item { flex: none; width: min(330px, 84vw); scroll-snap-align: start; }

.tutorial-card {
  display: flex;
  flex-direction: column;
  height: 100%;
  border: 1px solid var(--border);
  border-radius: var(--r-lg);
  background: var(--glass);
  overflow: hidden;
  cursor: pointer;
  text-align: left;
  width: 100%;
  transition: transform var(--t) var(--ease), border-color var(--t) var(--ease),
              box-shadow var(--t) var(--ease);
  touch-action: manipulation;
}
.tutorial-card:active { transform: scale(0.985); }
@media (hover: hover) and (pointer: fine) {
  .tutorial-card:hover {
    transform: translateY(-5px);
    border-color: var(--border-accent);
    box-shadow: var(--shadow);
  }
  .tutorial-card:hover .work-play { opacity: 1; transform: scale(1.06); }
  .tutorial-card:hover .tut-thumb img { transform: scale(1.06); }
}

.tut-thumb {
  position: relative;
  aspect-ratio: 16 / 9;
  background: linear-gradient(145deg, var(--bg-3), var(--bg-2));
  overflow: hidden;
}
.tut-thumb img {
  width: 100%; height: 100%;
  object-fit: cover;
  transition: transform var(--t-slow) var(--ease-out);
}

.tut-duration {
  position: absolute;
  right: 0.6rem; bottom: 0.6rem;
  z-index: 1;
  display: inline-flex;
  align-items: center;
  gap: 0.3rem;
  padding: 0.24rem 0.6rem;
  border-radius: var(--r-xs);
  background: color-mix(in srgb, var(--bg) 80%, transparent);
  backdrop-filter: blur(6px);
  -webkit-backdrop-filter: blur(6px);
  font-size: var(--fs-2xs);
  font-variant-numeric: tabular-nums;
  color: var(--text);
}
.tut-duration .ico { width: 0.8rem; height: 0.8rem; }

.tut-body { padding: 1.15rem 1.2rem 1.3rem; display: flex; flex-direction: column; gap: 0.55rem; flex: 1; }
.tut-badges { display: flex; flex-wrap: wrap; gap: 0.35rem; }

.badge {
  padding: 0.22rem 0.62rem;
  border-radius: var(--r-pill);
  font-size: var(--fs-2xs);
  font-weight: 650;
  letter-spacing: 0.04em;
  border: 1px solid var(--border);
  color: var(--text-2);
  background: var(--surface-2);
}
.badge-accent { color: var(--accent); border-color: var(--border-accent); background: var(--accent-soft); }
.badge-level-Beginner { color: #7fdcae; border-color: rgba(127, 220, 174, 0.32); background: rgba(127, 220, 174, 0.1); }
.badge-level-Intermediate { color: #8fbaff; border-color: rgba(143, 186, 255, 0.32); background: rgba(143, 186, 255, 0.1); }
.badge-level-Advanced { color: #ff9d9f; border-color: rgba(255, 157, 159, 0.32); background: rgba(255, 157, 159, 0.1); }

.tut-title { font-size: 1.03rem; font-weight: 650; line-height: 1.32; letter-spacing: -0.015em; }
.tut-desc {
  font-size: var(--fs-sm);
  color: var(--muted);
  display: -webkit-box;
  -webkit-line-clamp: 3;
  line-clamp: 3;
  -webkit-box-orient: vertical;
  overflow: hidden;
}

/* Testimonials */
.testimonial-card {
  position: relative;
  display: flex;
  flex-direction: column;
  gap: 1.1rem;
  height: 100%;
  padding: 1.7rem 1.55rem;
  border: 1px solid var(--border);
  border-radius: var(--r-lg);
  background: var(--glass);
  backdrop-filter: var(--blur);
  -webkit-backdrop-filter: var(--blur);
  overflow: hidden;
}
.testimonial-card::before {
  content: "";
  position: absolute;
  inset: 0 0 auto 0;
  height: 1px;
  background: var(--grad-line);
}

.stars { display: flex; gap: 0.15rem; color: var(--accent); }
.stars .ico { width: 0.95rem; height: 0.95rem; fill: currentColor; stroke: none; }
.stars .is-off { color: var(--border-2); }

.testimonial-quote {
  font-size: var(--fs-body);
  color: var(--text-2);
  line-height: 1.68;
  flex: 1;
  text-wrap: pretty;
}
.testimonial-quote::before { content: "\201C"; }
.testimonial-quote::after { content: "\201D"; }

.testimonial-who {
  display: flex;
  align-items: center;
  gap: 0.85rem;
  padding-top: 0.95rem;
  border-top: 1px solid var(--border);
}
.avatar {
  display: grid;
  place-items: center;
  width: 44px; height: 44px;
  flex: none;
  border-radius: 50%;
  background: var(--accent-soft);
  border: 1px solid var(--border-accent);
  color: var(--accent);
  font-weight: 700;
  font-size: var(--fs-sm);
  overflow: hidden;
}
.avatar img { width: 100%; height: 100%; object-fit: cover; }
.testimonial-name { font-size: var(--fs-sm); font-weight: 650; }
.testimonial-role { font-size: var(--fs-xs); color: var(--muted); }

/* ------------------------------------------------------------------ *
   20. Gallery
 * ------------------------------------------------------------------ */

.gallery-grid {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 0.55rem;
}

.gallery-item {
  position: relative;
  aspect-ratio: 1;
  border-radius: var(--r-sm);
  overflow: hidden;
  border: 1px solid var(--border);
  background: var(--bg-3);
  cursor: pointer;
  padding: 0;
  width: 100%;
  transition: transform var(--t) var(--ease), border-color var(--t) var(--ease);
  touch-action: manipulation;
}
.gallery-item:nth-child(5n + 1) { grid-column: span 2; aspect-ratio: 16 / 10; }

.gallery-item img {
  width: 100%; height: 100%;
  object-fit: cover;
  transition: transform var(--t-slow) var(--ease-out);
}
.gallery-item:active { transform: scale(0.98); }

.gallery-caption {
  position: absolute;
  inset: auto 0 0 0;
  padding: 1.75rem 0.85rem 0.8rem;
  background: linear-gradient(180deg, transparent,
              color-mix(in srgb, var(--bg) 92%, transparent));
  font-size: var(--fs-xs);
  color: var(--text);
  text-align: left;
}
.gallery-cat {
  color: var(--accent);
  font-weight: 680;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  font-size: 0.64rem;
}

@media (hover: hover) and (pointer: fine) {
  .gallery-item:hover { border-color: var(--border-accent); }
  .gallery-item:hover img { transform: scale(1.08); }
}

/* ------------------------------------------------------------------ *
   21. Vision
 * ------------------------------------------------------------------ */

.vision {
  position: relative;
  padding-block: clamp(3.5rem, 9vw, 7rem);
  background:
    radial-gradient(90% 130% at 50% 0%, var(--accent-soft), transparent 58%),
    var(--bg);
  border-block: 1px solid var(--border);
  text-align: center;
  overflow: hidden;
}
.vision-inner { display: flex; flex-direction: column; align-items: center; gap: 1.4rem; }
.vision-quote {
  width: 2.8rem; height: 2.8rem;
  color: var(--accent);
  opacity: 0.55;
  fill: currentColor;
  stroke: none;
}
.vision blockquote {
  font-family: var(--font-display);
  font-style: italic;
  font-size: clamp(1.35rem, 4.2vw, 2.3rem);
  line-height: 1.36;
  max-width: 36ch;
  text-wrap: balance;
  background: linear-gradient(150deg, var(--white) 25%, var(--accent));
  -webkit-background-clip: text;
  background-clip: text;
  color: transparent;
}
.vision-by { font-size: var(--fs-sm); color: var(--muted); }

/* ------------------------------------------------------------------ *
   22. Contact
 * ------------------------------------------------------------------ */

.contact-layout {
  display: grid;
  gap: clamp(2.25rem, 6vw, 4rem);
  grid-template-columns: minmax(0, 1fr);
}

.contact-cards {
  display: grid;
  gap: 0.65rem;
  grid-template-columns: minmax(0, 1fr);
}

.contact-card {
  display: flex;
  align-items: center;
  gap: 1rem;
  min-height: 68px;
  padding: 1rem 1.15rem;
  border: 1px solid var(--border);
  border-radius: var(--r);
  background: var(--glass);
  transition: border-color var(--t) var(--ease), background var(--t) var(--ease),
              transform var(--t-fast) var(--ease);
}
.contact-card:active { transform: scale(0.99); }
@media (hover: hover) and (pointer: fine) {
  .contact-card:hover {
    border-color: var(--border-accent);
    background: var(--accent-soft);
    transform: translateX(4px);
  }
  .contact-card:hover .contact-arrow { opacity: 1; transform: translate(2px, -2px); }
}

.contact-card-icon {
  display: grid;
  place-items: center;
  width: 44px; height: 44px;
  flex: none;
  border-radius: 13px;
  background: var(--accent-soft);
  border: 1px solid var(--border-accent);
  color: var(--accent);
}
.contact-card-body { min-width: 0; flex: 1; }
.contact-card-label {
  display: block;
  font-size: var(--fs-2xs);
  color: var(--faint);
  letter-spacing: 0.1em;
  text-transform: uppercase;
}
.contact-card-value {
  display: block;
  font-size: var(--fs-sm);
  font-weight: 580;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.contact-arrow {
  color: var(--accent);
  opacity: 0.45;
  transition: opacity var(--t) var(--ease), transform var(--t) var(--ease);
}

.contact-form {
  display: grid;
  gap: 1.05rem;
  padding: clamp(1.35rem, 4vw, 2.25rem);
  border: 1px solid var(--border);
  border-radius: var(--r-lg);
  background: var(--bg-2);
  box-shadow: var(--shadow);
}

.form-row {
  display: grid;
  gap: 1.05rem;
  grid-template-columns: minmax(0, 1fr);
}

/* align-content:start stops inputs stretching to match a taller sibling
   field in the same two-column row. */
.field {
  display: grid;
  gap: 0.42rem;
  align-content: start;
  grid-template-columns: minmax(0, 1fr);
}
.field label {
  font-size: var(--fs-2xs);
  font-weight: 650;
  letter-spacing: 0.09em;
  text-transform: uppercase;
  color: var(--muted);
}
.field label span { color: var(--accent); }

.field input, .field textarea, .field select {
  width: 100%;
  min-height: 50px;
  padding: 0.8rem 1rem;
  border: 1px solid var(--border);
  border-radius: var(--r-sm);
  background: rgba(255, 255, 255, 0.028);
  color: var(--text);
  /* 16px minimum stops iOS Safari zooming on focus */
  font-size: 16px;
  appearance: none;
  transition: border-color var(--t) var(--ease), background var(--t) var(--ease),
              box-shadow var(--t) var(--ease);
}
.field textarea { min-height: 134px; resize: vertical; line-height: 1.6; }
.field input::placeholder, .field textarea::placeholder { color: var(--faint); }

.field input:focus, .field textarea:focus, .field select:focus {
  outline: none;
  border-color: var(--border-accent);
  background: rgba(255, 255, 255, 0.045);
  box-shadow: 0 0 0 3px var(--accent-soft);
}

.field.has-error input, .field.has-error textarea {
  border-color: rgba(251, 113, 133, 0.6);
  background: rgba(251, 113, 133, 0.06);
}
.field-error { font-size: var(--fs-xs); color: var(--danger); }
.field-error:empty { display: none; }

.select-wrap { position: relative; display: grid; }
.select-wrap select { padding-right: 2.75rem; cursor: pointer; }
.select-wrap .ico {
  position: absolute;
  right: 1rem; top: 50%;
  translate: 0 -50%;
  pointer-events: none;
  color: var(--muted);
}
.select-wrap select option { background: var(--bg-3); color: var(--text); }

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

.form-status {
  font-size: var(--fs-sm);
  padding: 0.8rem 1rem;
  border-radius: var(--r-sm);
  display: none;
}
.form-status.is-ok {
  display: block;
  color: #a7edc7;
  background: rgba(74, 222, 128, 0.1);
  border: 1px solid rgba(74, 222, 128, 0.3);
}
.form-status.is-bad {
  display: block;
  color: #ffb9c1;
  background: rgba(251, 113, 133, 0.1);
  border: 1px solid rgba(251, 113, 133, 0.3);
}

.btn.is-loading { pointer-events: none; }
.btn.is-loading .btn-label { opacity: 0.6; }
.btn.is-loading .ico { animation: spin 0.75s linear infinite; }
@keyframes spin { to { transform: rotate(360deg); } }

/* ------------------------------------------------------------------ *
   23. Footer
 * ------------------------------------------------------------------ */

.site-footer {
  padding-top: clamp(2.5rem, 6vw, 4.5rem);
  padding-bottom: calc(1.75rem + env(safe-area-inset-bottom));
  background:
    radial-gradient(80% 100% at 50% 100%, var(--accent-soft), transparent 60%),
    var(--bg-2);
  border-top: 1px solid var(--border);
}

.footer-inner {
  display: grid;
  gap: 1.9rem;
  padding-bottom: 2rem;
  border-bottom: 1px solid var(--border);
  grid-template-columns: minmax(0, 1fr);
}

.footer-brand { display: flex; align-items: center; gap: 0.9rem; }
.footer-brand p { font-size: var(--fs-sm); color: var(--muted); line-height: 1.45; }
.footer-brand strong { color: var(--text); font-size: 1rem; }

.footer-nav { display: flex; flex-wrap: wrap; gap: 0.4rem 1.4rem; }
.footer-nav a {
  min-height: 44px;
  display: inline-flex;
  align-items: center;
  font-size: var(--fs-sm);
  color: var(--muted);
  transition: color var(--t-fast) var(--ease);
}
.footer-nav a:active { color: var(--accent); }
@media (hover: hover) and (pointer: fine) { .footer-nav a:hover { color: var(--accent); } }

.footer-social { display: flex; gap: 0.5rem; flex-wrap: wrap; }

.footer-bottom {
  display: flex;
  flex-direction: column;
  gap: 0.4rem;
  padding-top: 1.5rem;
  font-size: var(--fs-xs);
  color: var(--faint);
}

/* ------------------------------------------------------------------ *
   24. Theme switcher
 * ------------------------------------------------------------------ */

/* WhatsApp button. Keeps its own brand green rather than the theme accent —
   it is a recognised mark, and recolouring it costs the instant recognition
   that makes it worth having. */
.wa-fab {
  position: fixed;
  right: max(1rem, env(safe-area-inset-right));
  bottom: calc(4.6rem + env(safe-area-inset-bottom));
  z-index: 115;
  display: grid;
  place-items: center;
  width: 52px;
  height: 52px;
  border-radius: 50%;
  background: #25d366;
  color: #fff;
  box-shadow: 0 10px 26px -8px rgba(37, 211, 102, 0.65);
  transition: transform var(--t-fast) var(--ease), box-shadow var(--t-fast) var(--ease);
}
.wa-fab svg { width: 1.7rem; height: 1.7rem; }
.wa-fab:hover { transform: translateY(-2px) scale(1.04); }
.wa-fab:focus-visible { outline: 2px solid #fff; outline-offset: 3px; }

.theme-fab {
  position: fixed;
  right: max(1rem, env(safe-area-inset-right));
  bottom: calc(1rem + env(safe-area-inset-bottom));
  z-index: 115;
  display: grid;
  place-items: center;
  width: 48px; height: 48px;
  border-radius: 50%;
  border: 1px solid var(--border-2);
  background: color-mix(in srgb, var(--bg-3) 88%, transparent);
  backdrop-filter: var(--blur);
  -webkit-backdrop-filter: var(--blur);
  box-shadow: var(--shadow);
  color: var(--text);
  transition: transform var(--t) var(--ease-spring), border-color var(--t) var(--ease);
}
.theme-fab .ico { width: 1.3rem; height: 1.3rem; }
/* Glow via box-shadow rather than an oversized pseudo-element, so nothing
   extends past the button's own box. */
.theme-fab {
  box-shadow: var(--shadow), 0 0 22px -4px var(--accent-glow);
}
.theme-fab:active { transform: scale(0.9); }
@media (hover: hover) and (pointer: fine) {
  .theme-fab:hover { transform: rotate(45deg) scale(1.06); border-color: var(--border-accent); }
}

.theme-panel {
  position: fixed;
  right: max(1rem, env(safe-area-inset-right));
  bottom: calc(4.6rem + env(safe-area-inset-bottom));
  z-index: 116;
  width: min(272px, calc(100vw - 2rem));
  padding: 1rem;
  border: 1px solid var(--border-2);
  border-radius: var(--r);
  background: color-mix(in srgb, var(--bg-3) 96%, transparent);
  backdrop-filter: var(--blur);
  -webkit-backdrop-filter: var(--blur);
  box-shadow: var(--shadow-lg);
  transform-origin: bottom right;
  opacity: 0;
  transform: translateY(10px) scale(0.96);
  transition: opacity var(--t) var(--ease), transform var(--t) var(--ease-out);
}
.theme-panel.is-open { opacity: 1; transform: none; }

.theme-panel h2 {
  font-size: var(--fs-2xs);
  font-weight: 680;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--muted);
  margin-bottom: 0.85rem;
}

.theme-grid { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 0.5rem; }

.theme-option {
  display: flex;
  align-items: center;
  gap: 0.55rem;
  min-height: var(--tap);
  padding: 0.5rem 0.6rem;
  border: 1px solid var(--border);
  border-radius: var(--r-sm);
  background: var(--surface);
  color: var(--text-2);
  font-size: var(--fs-xs);
  font-weight: 580;
  text-align: left;
  transition: border-color var(--t) var(--ease), background var(--t) var(--ease),
              color var(--t) var(--ease), transform var(--t-fast) var(--ease);
  touch-action: manipulation;
}
.theme-option:active { transform: scale(0.96); }
.theme-option[aria-pressed="true"] {
  border-color: var(--border-accent);
  background: var(--accent-soft);
  color: var(--text);
}

/* Live gradient preview built from the theme's own tokens */
.theme-swatch {
  width: 22px; height: 22px;
  flex: none;
  border-radius: 7px;
  border: 1px solid rgba(255, 255, 255, 0.18);
  background: linear-gradient(135deg, var(--sw-1), var(--sw-2));
}

@media (hover: hover) and (pointer: fine) {
  .theme-option:hover { border-color: var(--border-accent); color: var(--text); }
}

/* ------------------------------------------------------------------ *
   25. Lightbox
 * ------------------------------------------------------------------ */

.lightbox {
  position: fixed;
  inset: 0;
  z-index: 160;
  display: grid;
  place-items: center;
  padding: clamp(0.75rem, 3vw, 2rem);
  padding-top: max(clamp(0.75rem, 3vw, 2rem), calc(env(safe-area-inset-top) + 3.75rem));
  padding-bottom: max(clamp(0.75rem, 3vw, 2rem), calc(env(safe-area-inset-bottom) + 0.75rem));
  background: color-mix(in srgb, var(--bg) 94%, transparent);
  backdrop-filter: blur(16px);
  -webkit-backdrop-filter: blur(16px);
  opacity: 0;
  transition: opacity var(--t) var(--ease);
}
.lightbox.is-open { opacity: 1; }

.lightbox-close {
  position: absolute;
  top: calc(env(safe-area-inset-top) + 0.85rem);
  right: 0.85rem;
  z-index: 3;
}

.lightbox-nav {
  position: absolute;
  top: 50%;
  translate: 0 -50%;
  z-index: 3;
  width: 48px; height: 48px;
}
.lightbox-prev { left: 0.6rem; }
.lightbox-next { right: 0.6rem; }

.lightbox-stage {
  display: flex;
  flex-direction: column;
  gap: 1rem;
  width: 100%;
  max-width: 1120px;
  max-height: 100%;
  margin: 0;
}

.lightbox-media {
  position: relative;
  flex: 1;
  min-height: 0;
  display: grid;
  place-items: center;
  border-radius: var(--r);
  overflow: hidden;
  background: #04050a;
  touch-action: pan-y pinch-zoom;
}
.lightbox-media img {
  max-width: 100%;
  max-height: min(72svh, 100%);
  width: auto;
  object-fit: contain;
  border-radius: var(--r);
}
.lightbox-media iframe,
.lightbox-media video {
  width: 100%;
  aspect-ratio: 16 / 9;
  max-height: 72svh;
  border: 0;
  border-radius: var(--r);
  background: #000;
}

.lightbox-fallback {
  display: grid;
  place-items: center;
  gap: 1rem;
  padding: 3.5rem 1.5rem;
  text-align: center;
  color: var(--muted);
}
.lightbox-fallback .ico { width: 3rem; height: 3rem; color: var(--accent); opacity: 0.4; }

.lightbox-caption { text-align: center; flex: none; }
.lightbox-caption h3 { font-size: var(--fs-h3); margin-bottom: 0.35rem; }
.lightbox-caption p { font-size: var(--fs-sm); color: var(--muted); max-width: 70ch; margin-inline: auto; text-wrap: pretty; }
.lightbox-count {
  display: inline-block;
  margin-top: 0.65rem;
  font-size: var(--fs-xs);
  color: var(--faint);
  font-variant-numeric: tabular-nums;
}

/* ------------------------------------------------------------------ *
   26. Toasts
 * ------------------------------------------------------------------ */

.toast-stack {
  position: fixed;
  z-index: 200;
  left: 50%;
  bottom: calc(1rem + env(safe-area-inset-bottom));
  translate: -50% 0;
  display: flex;
  flex-direction: column;
  gap: 0.5rem;
  width: min(420px, calc(100vw - 5.5rem));
  pointer-events: none;
}

.toast {
  display: flex;
  align-items: center;
  gap: 0.7rem;
  padding: 0.85rem 1.1rem;
  border: 1px solid var(--border-2);
  border-radius: var(--r);
  background: color-mix(in srgb, var(--bg-3) 96%, transparent);
  backdrop-filter: blur(12px);
  -webkit-backdrop-filter: blur(12px);
  box-shadow: var(--shadow);
  font-size: var(--fs-sm);
  animation: toast-in 0.4s var(--ease-out);
}
.toast.is-leaving { animation: toast-out 0.28s var(--ease) forwards; }
.toast .ico { flex: none; }
.toast-ok .ico { color: var(--ok); }
.toast-bad .ico { color: var(--danger); }

@keyframes toast-in { from { opacity: 0; transform: translateY(14px) scale(0.97); } }
@keyframes toast-out { to { opacity: 0; transform: translateY(10px) scale(0.97); } }

/* ------------------------------------------------------------------ *
   27. Reveal on scroll
 * ------------------------------------------------------------------ */

/* Supplied artwork in place of the drawn mark. The tile's own background and
   glow are dropped, since the file brings its own. */
.brand-mark-image {
  background: none;
  box-shadow: none;
  border-radius: 10px;
  overflow: hidden;
}
.brand-mark-image img { width: 100%; height: 100%; object-fit: contain; display: block; }

/* Artwork the owner supplied, rather than the square mark this repo ships.
   A real logo is usually a wordmark — a lockup two or three times wider than it
   is tall — and forcing it into the 40px tile shrank it to an illegible stamp.
   Height still rules so it lines up with the studio name beside it, but the width
   is free up to a sensible ceiling.

   Two classes deep deliberately. A touch-device rule further down this file pins
   `.brand-mark` back to a 40px square for tap-target reasons, and with one class
   it won that cascade — so the supplied logo was squeezed into a stamp on exactly
   the phones the owner was looking at, and nowhere else. */
.brand-mark.brand-mark-custom {
  /* `fit-content`, not `auto`: the box has a max-width and with `auto` it took all
     of it, so a square logo sat in a 190px box with 130px of dead space shoving the
     navigation along. */
  width: fit-content;
  height: var(--logo-h, 40px);
  max-width: clamp(110px, 22vw, 190px);
  border-radius: 0;
  background: none;
  box-shadow: none;
}

/* --- how the artwork is framed ---
   `data-logo-shape` is written by site.js, which resolves `auto` by reading the
   file's own proportions. A square image is a badge and belongs in a circle; a
   wide lockup would be destroyed by one. */

.brand-mark.brand-mark-custom[data-logo-shape="circle"],
.brand-mark.brand-mark-custom[data-logo-shape="rounded"] {
  width: var(--logo-h, 40px);
  aspect-ratio: 1;
  max-width: none;
  overflow: hidden;
}
.brand-mark.brand-mark-custom[data-logo-shape="circle"] { border-radius: 50%; }
.brand-mark.brand-mark-custom[data-logo-shape="rounded"] { border-radius: 26%; }

/* `cover`, not `contain`. Artwork like a profile badge has its own dark ground
   filling the square, so containing it inside the circle would letterbox that
   ground and show the corners as flat slabs. Cover crops to the circle, which is
   the whole point of choosing it. */
.brand-mark.brand-mark-custom[data-logo-shape="circle"] img,
.brand-mark.brand-mark-custom[data-logo-shape="rounded"] img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  max-width: none;
}

/* The 404 page's mark is a bare anchor, so it carries the shape itself. */
a.brand-mark-custom[data-logo-shape="circle"],
a.brand-mark-custom[data-logo-shape="rounded"] {
  width: var(--logo-h-lg, 52px);
  height: var(--logo-h-lg, 52px);
  overflow: hidden;
}
a.brand-mark-custom[data-logo-shape="circle"] { border-radius: 50%; }
a.brand-mark-custom[data-logo-shape="rounded"] { border-radius: 26%; }
a.brand-mark-custom[data-logo-shape="circle"] img,
a.brand-mark-custom[data-logo-shape="rounded"] img {
  width: 100%;
  height: 100%;
  object-fit: cover;
}
.brand-mark.brand-mark-custom img,
a.brand-mark-custom img {
  width: auto;
  max-width: 100%;
  height: 100%;
  object-fit: contain;
}
/* The 404 page's mark is a bare anchor, not a `.brand-mark` tile. */
a.brand-mark-custom { display: inline-block; height: 52px; }

/* A supplied lockup nearly always carries the studio name inside the artwork, so
   repeating it as text beside it is saying the same thing twice — and on a phone
   there is only room for one, which is why the tagline was rendering truncated to
   "WHERE EVERY FRAME TELL…". The artwork wins there and gets the space back. On a
   desktop both stay, because someone may have uploaded a bare icon rather than a
   lockup and it should not end up nameless. */
@media (max-width: 639px) {
  .brand:has(.brand-mark-custom) .brand-text { display: none; }
  .brand-mark.brand-mark-custom { max-width: min(220px, 56vw); }
}


/* ------------------------------------------------------------------ *
   Team cards
 * ------------------------------------------------------------------ */

.team-grid {
  display: grid;
  /* auto-fit rather than a fixed count: one person centres, several fill the
     row, and nothing has to change when the studio grows. */
  grid-template-columns: repeat(auto-fit, minmax(min(230px, 100%), 260px));
  justify-content: center;
  gap: clamp(0.9rem, 3vw, 1.4rem);
}

.team-card {
  overflow: hidden;
  border: 1px solid var(--border);
  border-radius: var(--r-lg);
  background: var(--surface);
  transition: transform var(--t) var(--ease), border-color var(--t) var(--ease);
}
@media (hover: hover) and (pointer: fine) {
  .team-card:hover { transform: translateY(-4px); border-color: var(--border-accent); }
}

.team-photo {
  position: relative;
  display: grid;
  place-items: center;
  /* A portrait crop, so faces sit well whatever the source image's shape. */
  aspect-ratio: 3 / 4;
  background:
    radial-gradient(120% 90% at 50% 0%, var(--accent-soft), transparent 65%),
    var(--bg-3);
}
.team-photo img { width: 100%; height: 100%; object-fit: cover; display: block; }

/* Shown instead of a broken image when no photo has been set yet. */
.team-initials {
  font-size: clamp(2.4rem, 9vw, 3.4rem);
  font-weight: 700;
  letter-spacing: -0.03em;
  color: var(--accent);
  opacity: 0.85;
}

/* Light panel under the portrait, as in the reference. Deliberately not
   theme-tinted: the contrast against the dark card is the point. */
.team-meta {
  display: grid;
  gap: 0.15rem;
  padding: 0.9rem 1rem 1rem;
  background: #f4f4f5;
  text-align: center;
}
.team-meta strong { color: #101014; font-size: 1.02rem; letter-spacing: -0.01em; }
.team-meta span { color: #5a5a63; font-size: var(--fs-sm); }

/* The unit after a statistic — "%" or "+" — carries the accent while the number
   stays white, so the figure reads first and the accent marks it rather than
   competing with it. Overrides the gradient clip on .stat-value. */
.stat-unit {
  -webkit-text-fill-color: var(--accent);
  color: var(--accent);
  background: none;
  margin-left: 0.04em;
}

/* ------------------------------------------------------------------ *
   Coverflow carousel
 * ------------------------------------------------------------------ *
   The centre item comes forward and its neighbours fall back, so the eye is
   told where to look without an overlay or a caption. Opt-in per carousel via
   the data attribute, since it only suits ones the visitor browses rather than
   reads. */

.carousel[data-coverflow] {
  /* Padding, not margin: it lets the first and last item reach the middle of
     the track, so they can be the centred one too. Half the item width, so the
     centred item sits exactly in the middle. */
  padding-inline: max(1rem, calc(50% - 9.375rem));
  scroll-snap-type: x mandatory;
  align-items: center;
}

/* Narrower than a normal carousel item so the neighbours show at both edges.
   Without them in frame there is nothing for the centre item to stand out
   against, and the effect reads as a plain scroller. */
.carousel[data-coverflow] .carousel-item { width: min(300px, 70vw); }

.carousel[data-coverflow] .carousel-item {
  scroll-snap-align: center;
  transform: scale(0.86);
  opacity: 0.45;
  filter: saturate(0.7);
  transition:
    transform 0.45s var(--ease-out),
    opacity 0.45s var(--ease-out),
    filter 0.45s var(--ease-out);
}

.carousel[data-coverflow] .carousel-item.is-center {
  transform: scale(1);
  opacity: 1;
  filter: none;
  z-index: 2;
}

/* Without the scale the layout would still shift as items are marked, so the
   effect is reduced to a fade rather than removed entirely. */
@media (prefers-reduced-motion: reduce) {
  .carousel[data-coverflow] .carousel-item {
    transform: none;
    transition: opacity 0.2s linear;
  }
  .carousel[data-coverflow] .carousel-item.is-center { transform: none; }
}

/* ------------------------------------------------------------------ *
   Site notice — a setup problem only the owner can fix
 * ------------------------------------------------------------------ */

/* Fixed to the bottom rather than inserted at the top: the header is fixed at
   top:0, so a notice in the normal flow ends up hidden behind it. */
.site-notice {
  position: fixed;
  inset: auto 0 0 0;
  z-index: 70;
  background: linear-gradient(90deg, rgba(120, 18, 24, 0.97), rgba(70, 10, 14, 0.97));
  backdrop-filter: blur(10px);
  border-top: 1px solid rgba(244, 105, 108, 0.55);
  padding: 0.8rem clamp(1rem, 4vw, 2rem);
  padding-bottom: calc(0.8rem + env(safe-area-inset-bottom));
  color: #fff;
  box-shadow: 0 -18px 44px -22px rgba(0, 0, 0, 0.9);
  animation: notice-in 0.4s var(--ease-out) both;
}

@keyframes notice-in {
  from { transform: translateY(100%); }
  to { transform: none; }
}

/* Keep the theme button clear of the bar while it is on screen. */
body:has([data-site-notice]) .theme-fab { bottom: 5.5rem; }

.site-notice-inner {
  max-width: 1180px;
  margin: 0 auto;
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.5rem 0.85rem;
}

.site-notice strong { font-size: 0.95rem; }

.site-notice p {
  margin: 0;
  flex: 1 1 260px;
  min-width: 0;
  font-size: 0.85rem;
  line-height: 1.5;
  color: rgba(255, 255, 255, 0.86);
  overflow-wrap: anywhere;
}

.site-notice-close {
  margin-left: auto;
  width: 2rem;
  height: 2rem;
  border: 0;
  border-radius: 999px;
  background: rgba(255, 255, 255, 0.12);
  color: #fff;
  font-size: 1.2rem;
  line-height: 1;
  cursor: pointer;
}
.site-notice-close:hover { background: rgba(255, 255, 255, 0.22); }

@media (max-width: 620px) {
  .site-notice-inner > .btn { flex: 1 1 auto; justify-content: center; }
}

/* Only hide these once JS has confirmed it is running and able to reveal them
   again — see the watchdog in index.html. Without the `.js` class the content
   is simply visible, so a script failure degrades the animation, not the page. */
.js .reveal, .js .reveal-item {
  opacity: 0;
  transform: translateY(24px);
}
.reveal, .reveal-item {
  transition: opacity 0.72s var(--ease-out), transform 0.72s var(--ease-out);
}
.reveal.is-in, .reveal-item.is-in { opacity: 1; transform: none; }

/* Ring drawn around an item arrived at from the admin's Preview button */
.is-spotlight {
  position: relative;
  z-index: 3;
  animation: spotlight 2.6s var(--ease) forwards;
}

@keyframes spotlight {
  0%   { box-shadow: 0 0 0 0 var(--accent), 0 0 0 0 var(--accent-glow); }
  12%  { box-shadow: 0 0 0 3px var(--accent), 0 0 42px 8px var(--accent-glow); }
  70%  { box-shadow: 0 0 0 3px var(--accent), 0 0 42px 8px var(--accent-glow); }
  100% { box-shadow: 0 0 0 0 transparent, 0 0 0 0 transparent; }
}

.skeleton {
  border-radius: var(--r);
  background: linear-gradient(100deg, var(--surface) 30%, var(--surface-2) 50%, var(--surface) 70%);
  background-size: 220% 100%;
  animation: shimmer 1.4s linear infinite;
}
@keyframes shimmer { to { background-position: -220% 0; } }
.sk-card { height: 260px; }
.sk-row { height: 74px; }

/* ------------------------------------------------------------------ *
   28. Breakpoints
 * ------------------------------------------------------------------ */

/* Small phones — tighten everything a notch */
@media (max-width: 359px) {
  :root { --fs-hero: clamp(2rem, 11vw, 2.6rem); --gutter: 1rem; }
  .hero-title { max-width: 100%; }
  .btn-lg { min-height: 48px; padding: 0.8rem 1.3rem; font-size: var(--fs-sm); }
  .hero-cta { width: 100%; }
  .hero-cta .btn { flex: 1 1 100%; }
  .stats-grid { gap: 0.5rem; }
  .stat { padding: 0.9rem 0.95rem; }
  .theme-fab { width: 44px; height: 44px; }
}

@media (min-width: 420px) {
  .about-facts { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .about-facts div:last-child { grid-column: 1 / -1; }
}

@media (min-width: 560px) {
  .hide-sm { display: inline-flex !important; }
  .stats-grid { grid-template-columns: repeat(4, minmax(0, 1fr)); }
  .services-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .work-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .form-row { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .contact-cards { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .gallery-grid { grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 0.7rem; }
  .gallery-item:nth-child(5n + 1) { grid-column: span 2; grid-row: span 2; aspect-ratio: 1; }
  .footer-bottom { flex-direction: row; justify-content: space-between; }
  .hero-scroll { display: flex; }
}

@media (min-width: 768px) {
  :root { --header-h: 74px; }
  .section-head-row { flex-direction: row; align-items: flex-end; justify-content: space-between; gap: 2rem; }
  .footer-inner { grid-template-columns: minmax(0, 1fr) auto; align-items: center; }
  .footer-nav { grid-column: 1 / -1; }
  .theme-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}

@media (min-width: 900px) {
  .nav-desktop { display: flex; align-items: center; gap: 0.2rem; }
  .nav-desktop a {
    position: relative;
    padding: 0.55rem 0.9rem;
    border-radius: var(--r-pill);
    font-size: var(--fs-sm);
    font-weight: 560;
    color: var(--text-2);
    transition: color var(--t-fast) var(--ease), background var(--t-fast) var(--ease);
  }
  .nav-desktop a::after {
    content: "";
    position: absolute;
    left: 50%; bottom: 0.28rem;
    translate: -50% 0;
    width: 0; height: 2px;
    border-radius: 2px;
    background: var(--grad);
    transition: width var(--t) var(--ease);
  }
  .nav-desktop a:hover { color: var(--text); background: var(--surface); }
  .nav-desktop a.is-active { color: var(--text); }
  .nav-desktop a.is-active::after { width: 20px; }

  .nav-toggle { display: none; }

  .services-grid { grid-template-columns: repeat(3, minmax(0, 1fr)); }
  /* A uniform grid, deliberately. The previous every-6th-tile-double-width
     rhythm only tiles evenly when the count happens to suit it; with fifteen
     services it left ragged rows that read as a mistake rather than a choice,
     and the owner can add or remove services at any time. */

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

  .about-layout { grid-template-columns: minmax(280px, 0.85fr) minmax(0, 1.15fr); align-items: start; }
  .about-visual { position: sticky; top: calc(var(--header-h) + 2rem); }

  .studio-card { grid-template-columns: minmax(0, 1.2fr) minmax(0, 0.8fr); align-items: center; }
  .studio-visual { display: grid; min-height: 360px; }

  .contact-layout { grid-template-columns: minmax(0, 0.9fr) minmax(0, 1.1fr); align-items: start; }
  .contact-cards { grid-template-columns: minmax(0, 1fr); }
  .contact-intro { position: sticky; top: calc(var(--header-h) + 2rem); }

  .timeline { padding-left: 2.4rem; }
  .tl-item::before { left: calc(-2.4rem + 6px); }

  .lightbox-prev { left: 1.5rem; }
  .lightbox-next { right: 1.5rem; }
}

@media (min-width: 1100px) {
  .gallery-grid { grid-template-columns: repeat(4, minmax(0, 1fr)); }
}

/* Large desktops and TVs — widen the shell rather than stretching type */
@media (min-width: 1600px) {
  :root { --shell: var(--shell-wide); }
  .services-grid { grid-template-columns: repeat(4, minmax(0, 1fr)); }
  .services-grid .service-card:nth-child(6n + 1) { grid-column: span 2; }
  .gallery-grid { grid-template-columns: repeat(5, minmax(0, 1fr)); }
}

@media (min-width: 2000px) {
  :root { --shell: 1600px; --fs-hero: clamp(4rem, 5.5vw, 7.5rem); }
}

/* Short landscape phones — stop the hero eating the whole screen */
@media (max-height: 560px) and (orientation: landscape) {
  .hero { min-height: auto; padding-top: calc(var(--header-h) + 1.5rem); }
  .hero-content { padding-block: 1.5rem; }
  .hero-scroll { display: none; }
  .hero-title { font-size: clamp(1.75rem, 6vh, 2.75rem); }
}

/* ------------------------------------------------------------------ *
   29. Touch refinements
   Anything a finger has to hit gets the full 44px on touch devices, even
   where a compact size looks better with a mouse.
 * ------------------------------------------------------------------ */

@media (pointer: coarse), (hover: none) {
  .footer-nav a,
  .nav-desktop a,
  .btn-sm,
  .theme-option { min-height: var(--tap); }

  .brand-mark { width: 40px; height: 40px; }
  .brand { padding-block: 0.2rem; }

  /* Widen the thin scroll cue so it is actually tappable */
  .hero-scroll { padding-inline: 1rem; }

  /* Hover-reveal styling never applies, so show captions outright */
  .gallery-caption { opacity: 1; }
}

/* ------------------------------------------------------------------ *
   30. Accessibility & print
 * ------------------------------------------------------------------ */

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

  *, *::before, *::after {
    animation-duration: 0.001ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.001ms !important;
    scroll-behavior: auto !important;
  }

  /* Must out-specify `.js .reveal`, or reduced-motion users get a blank page. */
  .reveal, .reveal-item,
  .js .reveal, .js .reveal-item { opacity: 1; transform: none; }
  .skill-fill { transition: none; }
  .marquee-track { animation: none; }
  .hero-glow { animation: none; }
}

@media (prefers-contrast: more) {
  :root {
    --border: rgba(255, 255, 255, 0.34);
    --border-2: rgba(255, 255, 255, 0.55);
    --muted: #b9c0cb;
    --faint: #9aa2ae;
    --glass: rgba(255, 255, 255, 0.08);
  }
  .work-thumb::after, .about-photo::after { display: none; }
}

@media print {
  .site-header, .drawer, .drawer-backdrop, .hero-controls, .hero-scroll,
  .hero-marquee, .lightbox, .toast-stack, .scroll-progress, .carousel-arrows,
  .filters, .theme-fab, .theme-panel { display: none !important; }

  body { background: #fff; color: #000; }
  .section, .stats, .vision { padding-block: 1.5rem; border: 0; }
  /* Must out-specify `.js .reveal`, or nothing appears on paper. */
  .reveal, .reveal-item,
  .js .reveal, .js .reveal-item { opacity: 1; transform: none; }
  .hero { min-height: auto; }
  .hero-title em, .section-title em, .stat-value, .vision blockquote,
  .studio-type { color: #000 !important; background: none !important; }
}


/* ==========================================================================
   31. Additions — interaction polish
   ==========================================================================
   Appended rather than woven in: everything above is the original layout and
   still owns it. These rules only add behaviour on top, so any of them can be
   deleted without touching the page's structure.
   ========================================================================== */

/* --- the quiet admin link in the footer --- */

.footer-admin {
  margin-left: 0.75rem;
  padding-left: 0.75rem;
  border-left: 1px solid var(--border);
  color: var(--faint);
  font-size: var(--fs-xs);
  transition: color var(--t-fast) var(--ease);
}
.footer-admin:hover { color: var(--accent); }

@media (max-width: 559px) {
  .footer-admin {
    display: block;
    margin: 0.5rem 0 0;
    padding: 0;
    border-left: 0;
  }
}

/* --- page intro -----------------------------------------------------------
   A single soft wipe on first paint. Deliberately short and once only: an
   entrance animation that runs on every navigation stops being an effect and
   becomes a delay. */

.page-intro {
  position: fixed;
  inset: 0;
  z-index: 400;
  display: grid;
  place-items: center;
  background: var(--bg);
  pointer-events: none;
  animation: intro-out 0.85s var(--ease-out) 0.15s forwards;
}
.page-intro span {
  width: 72px;
  height: 2px;
  border-radius: 2px;
  background: var(--grad);
  transform-origin: left;
  animation: intro-bar 0.7s var(--ease-out) both;
}
@keyframes intro-bar {
  from { scale: 0 1; opacity: 0; }
  to   { scale: 1 1; opacity: 1; }
}
@keyframes intro-out {
  to { opacity: 0; visibility: hidden; }
}

/* --- cursor spotlight -----------------------------------------------------
   Follows a fine pointer only. On touch there is no cursor to follow, and the
   element is never inserted. */

.cursor-glow {
  position: fixed;
  top: 0; left: 0;
  z-index: 1;
  width: 460px; height: 460px;
  margin: -230px 0 0 -230px;
  border-radius: 50%;
  background: radial-gradient(circle, var(--accent-glow), transparent 62%);
  opacity: 0;
  filter: blur(46px);
  pointer-events: none;
  will-change: transform;
  transition: opacity 0.5s var(--ease);
}
.cursor-glow.is-on { opacity: 0.4; }

/* --- tilt + magnetic hover --- */

@media (hover: hover) and (pointer: fine) {
  .work-card, .service-card, .tutorial-card, .team-card {
    transform-style: preserve-3d;
  }
  .tilt {
    transition: transform 0.45s var(--ease-out);
  }
  .tilt.is-tilting {
    /* Driven from JS via custom properties so the transform stays in one
       place and cannot fight a CSS hover rule for the same property. */
    transition: transform 0.09s linear;
    transform:
      perspective(900px)
      rotateX(var(--tilt-x, 0deg))
      rotateY(var(--tilt-y, 0deg))
      translateY(-4px);
  }

  .btn-primary { overflow: hidden; }
  .btn-primary::after {
    content: "";
    position: absolute;
    top: 0; left: 0;
    width: 100%; height: 100%;
    background: linear-gradient(100deg, transparent 20%,
                rgba(255, 255, 255, 0.28) 50%, transparent 80%);
    translate: -110% 0;
    pointer-events: none;
  }
  .btn-primary:hover::after { animation: sheen 0.75s var(--ease-out); }
}
@keyframes sheen { to { translate: 110% 0; } }

/* Buttons need position:relative for ::after; .btn already has it. */

/* --- ripple on tap --- */

.ripple {
  position: absolute;
  border-radius: 50%;
  background: currentColor;
  opacity: 0.28;
  transform: scale(0);
  pointer-events: none;
  animation: ripple 0.62s var(--ease-out) forwards;
}
@keyframes ripple {
  to { transform: scale(2.6); opacity: 0; }
}

/* --- back to top --- */

.to-top {
  position: fixed;
  right: clamp(0.9rem, 3vw, 1.5rem);
  bottom: calc(clamp(0.9rem, 3vw, 1.5rem) + 3 * (var(--tap) + 0.6rem));
  z-index: 120;
  display: grid;
  place-items: center;
  width: var(--tap); height: var(--tap);
  border: 1px solid var(--border-2);
  border-radius: 50%;
  background: var(--glass);
  backdrop-filter: var(--blur);
  color: var(--text);
  opacity: 0;
  translate: 0 12px;
  pointer-events: none;
  transition: opacity var(--t) var(--ease), translate var(--t) var(--ease),
              background var(--t-fast) var(--ease), color var(--t-fast) var(--ease);
}
.to-top.is-on { opacity: 1; translate: 0 0; pointer-events: auto; }
.to-top:hover { background: var(--accent); color: var(--accent-ink); border-color: transparent; }

/* --- section reveal for headings, a touch more character --- */

.js .section-title { --reveal-shift: 18px; }

/* --- image loading: fade in rather than snap in --- */

img[data-loading] { opacity: 0; }
img[data-loaded] {
  animation: img-in 0.6s var(--ease-out) both;
}
@keyframes img-in {
  from { opacity: 0; scale: 1.02; }
  to   { opacity: 1; scale: 1; }
}

/* --- keyboard users get a visible focus ring on cards --- */

.work-card:focus-visible, .tutorial-card:focus-visible, .gallery-item:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 3px;
}

/* --- respect reduced motion for every addition above --- */

@media (prefers-reduced-motion: reduce) {
  .page-intro { display: none; }
  .cursor-glow { display: none; }
  .tilt, .tilt.is-tilting { transform: none !important; transition: none; }
  .ripple { display: none; }
  .btn-primary::after { display: none; }
  img[data-loaded] { animation: none; }
  img[data-loading] { opacity: 1; }
}

@media print {
  .to-top, .cursor-glow, .page-intro, .footer-admin { display: none !important; }
}


/* ==========================================================================
   32. Motion engine
   ==========================================================================
   Every value here is set from the admin panel at runtime, as a custom property
   on <html>. The effect itself is chosen with [data-anim], so a new effect is one
   block below and nothing else — no JavaScript change, no new class names.

   The defaults in :root are what the page uses before settings arrive, so a slow
   API never means an unstyled first paint.
   ========================================================================== */

:root {
  --anim-duration: 720ms;
  --anim-delay: 0ms;
  --anim-stagger: 70ms;
  --anim-distance: 26px;
  --anim-ease: cubic-bezier(0.16, 1, 0.3, 1);
}

/* The five feels, matching ANIM_EASINGS in server/schema.js */
[data-ease="smooth"] { --anim-ease: cubic-bezier(0.16, 1, 0.3, 1); }
[data-ease="glide"]  { --anim-ease: cubic-bezier(0.19, 1, 0.22, 1); }
[data-ease="spring"] { --anim-ease: cubic-bezier(0.34, 1.46, 0.64, 1); }
[data-ease="snap"]   { --anim-ease: cubic-bezier(0.22, 0.68, 0.24, 1); }
[data-ease="linear"] { --anim-ease: linear; }

/* Reveal targets share one transition; only the resting transform differs. */
.js[data-motion="on"] .reveal,
.js[data-motion="on"] .reveal-item {
  opacity: 0;
  transition:
    opacity var(--anim-duration) var(--anim-ease) var(--anim-delay),
    transform var(--anim-duration) var(--anim-ease) var(--anim-delay),
    filter var(--anim-duration) var(--anim-ease) var(--anim-delay),
    clip-path var(--anim-duration) var(--anim-ease) var(--anim-delay);
  will-change: opacity, transform;
}

/* Each effect's starting position. `.is-in` clears all of them at once. */
[data-anim="rise"]        .reveal:not(.is-in), [data-anim="rise"]        .reveal-item:not(.is-in) { transform: translateY(var(--anim-distance)); }
[data-anim="fall"]        .reveal:not(.is-in), [data-anim="fall"]        .reveal-item:not(.is-in) { transform: translateY(calc(var(--anim-distance) * -1)); }
[data-anim="slide-left"]  .reveal:not(.is-in), [data-anim="slide-left"]  .reveal-item:not(.is-in) { transform: translateX(var(--anim-distance)); }
[data-anim="slide-right"] .reveal:not(.is-in), [data-anim="slide-right"] .reveal-item:not(.is-in) { transform: translateX(calc(var(--anim-distance) * -1)); }
[data-anim="scale"]       .reveal:not(.is-in), [data-anim="scale"]       .reveal-item:not(.is-in) { transform: scale(0.94); }
[data-anim="blur"]        .reveal:not(.is-in), [data-anim="blur"]        .reveal-item:not(.is-in) { filter: blur(11px); transform: translateY(calc(var(--anim-distance) * 0.4)); }
[data-anim="tilt"]        .reveal:not(.is-in), [data-anim="tilt"]        .reveal-item:not(.is-in) { transform: perspective(1000px) rotateX(9deg) translateY(var(--anim-distance)); }
[data-anim="wipe"]        .reveal:not(.is-in), [data-anim="wipe"]        .reveal-item:not(.is-in) { clip-path: inset(0 0 100% 0); transform: translateY(calc(var(--anim-distance) * 0.3)); }

.js[data-motion="on"] .reveal.is-in,
.js[data-motion="on"] .reveal-item.is-in {
  opacity: 1;
  transform: none;
  filter: none;
  clip-path: inset(0 0 0 0);
  will-change: auto;
}

/* Motion off, by choice or by preference: everything is simply present. The
   selector has to out-specify the effect blocks above, hence the doubling. */
.js[data-motion="off"] .reveal,
.js[data-motion="off"] .reveal-item,
html[data-motion="off"] [data-anim] .reveal:not(.is-in),
html[data-motion="off"] [data-anim] .reveal-item:not(.is-in) {
  opacity: 1 !important;
  transform: none !important;
  filter: none !important;
  clip-path: none !important;
  transition: none !important;
}

/* ==========================================================================
   33. Hero, rebuilt
   ========================================================================== */

.hero-kicker-row {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.6rem 1rem;
}

/* Headline revealed a word at a time. Each word sits in a clipping span so it
   rises out of its own line rather than the whole block fading. */
.kinetic { display: inline-block; overflow: hidden; vertical-align: bottom; }
.kinetic > span {
  display: inline-block;
  transform: translateY(105%);
  opacity: 0;
  transition:
    transform calc(var(--anim-duration) * 1.05) var(--anim-ease),
    opacity calc(var(--anim-duration) * 0.7) linear;
  transition-delay: var(--kinetic-delay, 0ms);
}
.hero-slide.is-active .kinetic > span { transform: translateY(0); opacity: 1; }
html[data-motion="off"] .kinetic > span,
html[data-motion="off"] .hero-slide .kinetic > span {
  transform: none !important;
  opacity: 1 !important;
  transition: none !important;
}

/* --- slide transitions, matching HERO_TRANSITIONS --- */

.hero-slide {
  transition:
    opacity var(--t-slow) var(--ease-out),
    transform var(--t-slow) var(--ease-out),
    clip-path var(--t-slow) var(--ease-out);
}
[data-hero-transition="fade"]   .hero-slide:not(.is-active) { opacity: 0; }
[data-hero-transition="slide"]  .hero-slide:not(.is-active) { opacity: 0; transform: translateX(6%); }
[data-hero-transition="zoom"]   .hero-slide:not(.is-active) { opacity: 0; transform: scale(1.06); }
[data-hero-transition="reveal"] .hero-slide:not(.is-active) { opacity: 0; clip-path: inset(0 0 0 100%); }
[data-hero-transition="reveal"] .hero-slide.is-active { clip-path: inset(0 0 0 0); }

/* --- background motion, matching HERO_EFFECTS --- */

[data-hero-fx="kenburns"] .hero-slide.is-active .hero-slide-media img {
  animation: kenburns 14s var(--ease) both;
}
@keyframes kenburns {
  from { transform: scale(1.02) translate3d(0, 0, 0); }
  to   { transform: scale(1.16) translate3d(-1.5%, -1.5%, 0); }
}
[data-hero-fx="none"] .hero-glow { animation: none; }

/* --- per-slide timer, drawn on the dots --- */

/* The timer draws over the dash that .hero-dot::before already paints, so it has
   to match that geometry — 24×3 centred — rather than filling the 30×46 hit
   area, which is what made the active dot read as a solid block. */
.hero-dot-fill {
  position: absolute;
  top: 50%; left: 0;
  translate: 0 -50%;
  width: 0; height: 3px;
  max-width: 24px;
  border-radius: 2px;
  background: var(--accent);
  pointer-events: none;
}
.hero-dot[aria-selected="true"] .hero-dot-fill { width: 24px; }
.hero-dot.is-timing .hero-dot-fill {
  animation: dot-timer var(--hero-interval, 7000ms) linear both;
}
@keyframes dot-timer { from { width: 0; } to { width: 24px; } }

/* The marquee lives at the very bottom of the hero, which is exactly where the
   section stepper sits in stage layout. One of them has to give, and the
   navigation is the one doing real work. */
html[data-layout="stage"] .hero-marquee { display: none; }

.hero-counter {
  font-family: var(--font-mono);
  font-size: var(--fs-xs);
  color: var(--muted);
  font-variant-numeric: tabular-nums;
}

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

.hero-countdown { margin-top: 1.6rem; }

/* ==========================================================================
   34. Stage layout — one section at a time
   ==========================================================================
   Built on CSS scroll snapping rather than a JavaScript slider, deliberately.
   The content stays in the document, so it is still readable by a search engine
   and a screen reader, still linkable, and still scrollable with a wheel, a
   swipe, a keyboard or a scrollbar. A slider would have to reimplement all of
   that, worse.

   Snapping is `proximity`, not `mandatory`: a section taller than the viewport
   must be readable to its end without the browser fighting the reader back to
   the top. That single keyword is the difference between organised and trapped.
   ========================================================================== */

html[data-layout="stage"] {
  scroll-snap-type: y proximity;
  scroll-padding-top: 0;
}

html[data-layout="stage"] .stage-panel {
  min-height: 100svh;
  scroll-snap-align: start;
  scroll-snap-stop: always;
  display: flex;
  flex-direction: column;
  justify-content: center;
  /* Clears the fixed header, and keeps the section navigator off the text. */
  padding-block: calc(var(--header-h) + 2.5rem) 4.5rem;
  position: relative;
}

/* Vertical room — not width — decides whether a panel fits its screen, and both
   laptops and phones are short. Trimming the breathing room is what lets a
   heading, a deck and its control bar land above the fold; without this the
   arrows that make a section slideable sit just below it, which is worse than a
   slightly tighter panel. */
@media (max-height: 1000px) {
  html[data-layout="stage"] .stage-panel {
    padding-block: calc(var(--header-h) + 1.1rem) 2.6rem;
  }
}

/* The hero already fills the screen and centres itself. */
html[data-layout="stage"] .hero.stage-panel { padding-block: 0; }

/* A panel whose content genuinely exceeds the screen stops centring, or its top
   would be cut off above the fold. */
html[data-layout="stage"] .stage-panel.is-tall { justify-content: flex-start; }

/* --- section navigator --- */

.stage-nav {
  position: fixed;
  z-index: 90;
  right: max(0.55rem, env(safe-area-inset-right));
  top: 50%;
  translate: 0 -50%;
  display: none;
  flex-direction: column;
  gap: 0.1rem;
  padding: 0.45rem 0.3rem;
  border: 1px solid var(--border);
  border-radius: var(--r-pill);
  background: var(--glass);
  backdrop-filter: var(--blur);
  box-shadow: var(--shadow);
}
html[data-layout="stage"] .stage-nav { display: flex; }

.stage-dot {
  position: relative;
  display: grid;
  place-items: center;
  width: 26px; height: 26px;
  border-radius: 50%;
  color: var(--muted);
}
.stage-dot span {
  width: 7px; height: 7px;
  border-radius: 50%;
  background: currentColor;
  transition: scale var(--t) var(--ease-spring), background var(--t) var(--ease);
}
.stage-dot:hover { color: var(--text); }
.stage-dot[aria-current="true"] { color: var(--accent); }
.stage-dot[aria-current="true"] span { scale: 1.5; background: var(--accent); }

/* The label appears on hover or focus, to the left so it never leaves the
   viewport on a narrow screen. */
.stage-dot::after {
  content: attr(data-label);
  position: absolute;
  right: calc(100% + 0.5rem);
  padding: 0.28rem 0.6rem;
  border-radius: var(--r-xs);
  background: var(--bg-3);
  border: 1px solid var(--border);
  color: var(--text);
  font-size: var(--fs-2xs);
  font-weight: 600;
  white-space: nowrap;
  opacity: 0;
  translate: 6px 0;
  pointer-events: none;
  transition: opacity var(--t-fast) var(--ease), translate var(--t-fast) var(--ease);
}
.stage-dot:hover::after, .stage-dot:focus-visible::after { opacity: 1; translate: 0 0; }

/* --- prev / next --- */

.stage-step {
  position: fixed;
  z-index: 90;
  left: 50%;
  translate: -50% 0;
  bottom: max(0.9rem, env(safe-area-inset-bottom));
  display: none;
  align-items: center;
  gap: 0.35rem;
  padding: 0.3rem;
  border: 1px solid var(--border);
  border-radius: var(--r-pill);
  background: var(--glass);
  backdrop-filter: var(--blur);
  box-shadow: var(--shadow);
}
html[data-layout="stage"] .stage-step { display: flex; }
.stage-step .icon-btn { width: 38px; height: 38px; }
.stage-step-label {
  min-width: 7.5rem;
  padding-inline: 0.3rem;
  font-size: var(--fs-2xs);
  font-weight: 650;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  text-align: center;
  color: var(--muted);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* On a phone the side rail is too fiddly; the bottom bar carries navigation. */
@media (max-width: 767px) {
  html[data-layout="stage"] .stage-nav { display: none; }
  html[data-layout="stage"] .stage-panel { padding-block: calc(var(--header-h) + 1.5rem) 5.5rem; }
  .to-top { bottom: calc(max(0.9rem, env(safe-area-inset-bottom)) + 3.6rem); }
}

/* Landscape phones have no vertical room for full-height panels. */
@media (max-height: 520px) and (orientation: landscape) {
  html[data-layout="stage"] .stage-panel {
    min-height: auto;
    scroll-snap-align: none;
    padding-block: calc(var(--header-h) + 1rem) 2rem;
  }
  html[data-layout="stage"] { scroll-snap-type: none; }
  .stage-nav { display: none !important; }
}

/* ==========================================================================
   35. Countdown
   ========================================================================== */

.countdown { display: grid; gap: 0.7rem; justify-items: center; }
.countdown-label {
  font-size: var(--fs-xs);
  font-weight: 650;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--muted);
}
.countdown-row { display: flex; flex-wrap: wrap; gap: 0.5rem; justify-content: center; }

.cd-unit {
  display: grid;
  justify-items: center;
  gap: 0.2rem;
  min-width: 4.2rem;
}
.cd-value {
  font-variant-numeric: tabular-nums;
  font-weight: 720;
  letter-spacing: -0.03em;
  line-height: 1;
}
.cd-name {
  font-size: var(--fs-2xs);
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--muted);
}
.countdown-done {
  font-size: var(--fs-h3);
  font-weight: 680;
  background: var(--grad);
  -webkit-background-clip: text;
  background-clip: text;
  color: transparent;
}

/* --- cards --- */
[data-cd-style="cards"] .cd-unit {
  padding: 0.85rem 0.7rem;
  border: 1px solid var(--border);
  border-radius: var(--r);
  background: var(--surface);
  box-shadow: var(--shadow);
}
[data-cd-style="cards"] .cd-value { font-size: clamp(1.7rem, 6vw, 2.6rem); }

/* --- minimal --- */
[data-cd-style="minimal"] .countdown-row { gap: 0.2rem; align-items: baseline; }
[data-cd-style="minimal"] .cd-unit { min-width: 0; }
[data-cd-style="minimal"] .cd-value { font-size: clamp(1.6rem, 5.5vw, 2.4rem); }
[data-cd-style="minimal"] .cd-unit:not(:last-child) .cd-value::after {
  content: ":";
  margin-inline: 0.25rem;
  color: var(--faint);
  font-weight: 400;
}

/* --- ring --- */
[data-cd-style="ring"] .cd-unit {
  position: relative;
  width: 5.4rem; height: 5.4rem;
  min-width: 0;
  place-content: center;
  border-radius: 50%;
  /* --pct is set per unit from JS; conic-gradient makes the dial with no SVG. */
  background:
    conic-gradient(var(--accent) calc(var(--pct, 0) * 1%), var(--surface-2) 0);
}
[data-cd-style="ring"] .cd-unit::before {
  content: "";
  position: absolute;
  inset: 5px;
  border-radius: 50%;
  background: var(--bg);
}
[data-cd-style="ring"] .cd-value,
[data-cd-style="ring"] .cd-name { position: relative; }
[data-cd-style="ring"] .cd-value { font-size: 1.35rem; }

/* --- flip --- */
[data-cd-style="flip"] .cd-unit {
  padding: 0.75rem 0.7rem;
  border: 1px solid var(--border-2);
  border-radius: var(--r-sm);
  background: linear-gradient(180deg, var(--surface-2) 0 50%, var(--surface) 50% 100%);
  position: relative;
}
/* The hinge line that makes it read as a flip clock. */
[data-cd-style="flip"] .cd-unit::after {
  content: "";
  position: absolute;
  left: 0; right: 0; top: 50%;
  height: 1px;
  background: var(--bg);
}
[data-cd-style="flip"] .cd-value { font-size: clamp(1.6rem, 5.5vw, 2.4rem); }
[data-cd-style="flip"] .cd-value.is-ticking { animation: flip-tick 0.42s var(--ease-out); }
@keyframes flip-tick {
  0%   { transform: rotateX(-70deg); opacity: 0.3; }
  100% { transform: rotateX(0); opacity: 1; }
}

/* ==========================================================================
   36. Maintenance
   ========================================================================== */

.maintenance {
  position: fixed;
  inset: 0;
  z-index: 500;
  display: grid;
  place-items: center;
  padding: clamp(1.2rem, 5vw, 3rem);
  overflow-y: auto;
  background:
    radial-gradient(90% 70% at 50% 0%, var(--accent-soft), transparent 62%),
    var(--bg);
}
.maintenance-inner {
  display: grid;
  justify-items: center;
  gap: 1.15rem;
  width: 100%;
  max-width: 44rem;
  text-align: center;
  animation: maint-in 0.7s var(--ease-out) both;
}
@keyframes maint-in { from { opacity: 0; transform: translateY(18px); } }

.maintenance-mark {
  width: 64px; height: 64px;
  border-radius: 16px;
  overflow: hidden;
}
.maintenance-mark img { width: 100%; height: 100%; object-fit: contain; }

.maintenance-pill {
  display: inline-flex;
  align-items: center;
  gap: 0.45rem;
  padding: 0.32rem 0.85rem;
  border: 1px solid var(--border-accent);
  border-radius: var(--r-pill);
  background: var(--accent-soft);
  color: var(--text-accent, var(--accent));
  font-size: var(--fs-xs);
  font-weight: 650;
  letter-spacing: 0.08em;
  text-transform: uppercase;
}
.maintenance h1 { font-size: var(--fs-h2); text-wrap: balance; }
.maintenance-text {
  font-size: var(--fs-lead);
  color: var(--text-2);
  max-width: 46ch;
  text-wrap: pretty;
}
.maintenance .countdown { margin-top: 0.6rem; }
.maintenance-socials { display: flex; flex-wrap: wrap; gap: 0.6rem; justify-content: center; }
.maintenance-foot {
  margin-top: 0.8rem;
  font-size: var(--fs-xs);
  color: var(--faint);
}

/* The bar the owner sees while maintenance is on and they are signed in. */
.preview-bar {
  position: fixed;
  inset: auto 0 0 0;
  z-index: 300;
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: center;
  gap: 0.5rem 0.9rem;
  padding: 0.6rem 1rem;
  padding-bottom: max(0.6rem, env(safe-area-inset-bottom));
  background: var(--accent);
  color: var(--accent-ink);
  font-size: var(--fs-sm);
  font-weight: 600;
  box-shadow: 0 -10px 30px -18px rgba(0, 0, 0, 0.9);
}
.preview-bar a {
  text-decoration: underline;
  text-underline-offset: 2px;
}
.preview-bar .ico { width: 1.1rem; height: 1.1rem; }

/* Keep the floating controls clear of the bar. */
body.has-preview-bar .stage-step { bottom: 3.6rem; }
body.has-preview-bar .to-top { bottom: 7rem; }

@media (prefers-reduced-motion: reduce) {
  .kinetic > span { transform: none !important; opacity: 1 !important; }
  .hero-dot.is-timing .hero-dot-fill { animation: none; width: 100%; }
  [data-hero-fx="kenburns"] .hero-slide.is-active .hero-slide-media img { animation: none; }
  .cd-value.is-ticking { animation: none; }
  .maintenance-inner { animation: none; }
}

@media print {
  .stage-nav, .stage-step, .preview-bar { display: none !important; }
  html[data-layout="stage"] .stage-panel { min-height: auto; padding-block: 1.5rem; }
}


/* ==========================================================================
   37. Two more themes
   ==========================================================================
   Same six declarations as every other theme in section 2 — two accents, the ink
   that sits legibly on top of them, and three background tints. Everything else
   derives from those, so nothing below this needs to know these exist.

   --accent-ink is the one that repays care: it is the text colour printed ON the
   accent, in buttons and pills. Lime needs near-black on it, red needs white,
   and getting either the wrong way round produces a button nobody can read.
   ========================================================================== */

/* Signal — black, white and the brand lime. The logo's own palette, so the
   mark stops being the only lime thing on the page. */
[data-theme="signal"] {
  --accent:   #c5f82a;
  --accent-2: #7ef2b0;
  --accent-ink: #0b1405;

  --bg:   #050506;
  --bg-2: #0a0b0c;
  --bg-3: #111214;
}

/* Scarlet — red, black and white. Where crimson runs warm and cinematic, this
   is colder and higher contrast: a true red against near-black, with white as
   the second accent so gradients resolve to paper rather than to orange. */
[data-theme="scarlet"] {
  --accent:   #ef2b3d;
  --accent-2: #ffffff;
  --accent-ink: #fff6f7;

  --bg:   #07070a;
  --bg-2: #0c0c10;
  --bg-3: #131318;
}

/* White as an accent needs the borders and tints pulled back, or every card
   edge reads as a highlight and the page loses its hierarchy. */
[data-theme="scarlet"] {
  --border:   rgba(255, 255, 255, 0.1);
  --border-2: rgba(255, 255, 255, 0.17);
}


/* ==========================================================================
   38. Section layouts
   ==========================================================================
   Each of these rearranges markup that is already on the page — no alternative
   renderer, no JavaScript branch, nothing for the two to disagree about. The
   default of every section is the arrangement the stylesheet had before these
   existed, so leaving them alone changes nothing.

   The `carousel` variants deliberately use native horizontal scroll snapping
   rather than the scripted carousel: swipe, wheel, trackpad and keyboard all
   work without a line of JavaScript, and there is no arrow state to keep in
   sync with a list that the admin can reorder underneath it.
   ========================================================================== */

/* --------------------------------- services ----------------------------- */

/* One per row, icon beside the text — reads as a menu of offerings. */
[data-layout-services="list"] .services-grid { grid-template-columns: minmax(0, 1fr) !important; }
[data-layout-services="list"] .service-card { align-items: center; }

/* Denser, for a long list where the descriptions are short. */
[data-layout-services="compact"] .services-grid {
  grid-template-columns: repeat(auto-fill, minmax(min(210px, 100%), 1fr)) !important;
  gap: 0.55rem;
}
[data-layout-services="compact"] .service-card { padding: 1rem 1.05rem; gap: 0.75rem; }
[data-layout-services="compact"] .service-desc { display: none; }

/* The first service gets the width of two, as an anchor for the section. */
@media (min-width: 900px) {
  [data-layout-services="feature"] .service-card:first-child { grid-column: span 2; }
  [data-layout-services="feature"] .service-card:first-child .service-title { font-size: var(--fs-h3); }
}

/* --------------------------------- projects ------------------------------ */

/* One large piece, the rest beside it, for when there is a clear best piece. */
@media (min-width: 900px) {
  [data-layout-projects="showcase"] .work-grid { grid-template-columns: repeat(3, minmax(0, 1fr)); }
  [data-layout-projects="showcase"] .work-card:first-child { grid-column: span 2; grid-row: span 2; }
  [data-layout-projects="showcase"] .work-card:first-child .work-thumb { aspect-ratio: 16 / 10; }
  [data-layout-projects="showcase"] .work-card:first-child .work-title { font-size: var(--fs-h3); }
}

/* Columns rather than rows, so cards of different heights pack tightly. */
@media (min-width: 700px) {
  [data-layout-projects="masonry"] .work-grid {
    display: block;
    columns: 2;
    column-gap: 1rem;
  }
  [data-layout-projects="masonry"] .work-card {
    /* A card must not be split across a column break. */
    break-inside: avoid;
    margin-bottom: 1rem;
    width: 100%;
  }
}
@media (min-width: 1100px) {
  [data-layout-projects="masonry"] .work-grid { columns: 3; }
}

[data-layout-projects="carousel"] .work-grid {
  display: flex !important;
  gap: 1rem;
  overflow-x: auto;
  overflow-y: hidden;
  scroll-snap-type: x mandatory;
  overscroll-behavior-x: contain;
  scrollbar-width: none;
  padding-bottom: 0.5rem;
  /* Bleeds to the screen edge so a partly visible next card signals "more". */
  margin-inline: calc(var(--gutter) * -1);
  padding-inline: var(--gutter);
}
[data-layout-projects="carousel"] .work-grid::-webkit-scrollbar { display: none; }
[data-layout-projects="carousel"] .work-card {
  flex: none;
  width: min(340px, 82vw);
  scroll-snap-align: start;
}

[data-layout-projects="list"] .work-grid { grid-template-columns: minmax(0, 1fr) !important; }
@media (min-width: 700px) {
  [data-layout-projects="list"] .work-card { flex-direction: row; align-items: stretch; }
  [data-layout-projects="list"] .work-thumb { width: 40%; flex: none; aspect-ratio: 4 / 3; }
  [data-layout-projects="list"] .work-body { justify-content: center; }
}

/* --------------------------------- gallery ------------------------------- */

/* Uniform squares — the mosaic's every-fifth-tile emphasis switched off. */
[data-layout-gallery="grid"] .gallery-item:nth-child(5n + 1) {
  grid-column: auto !important;
  grid-row: auto !important;
}

@media (min-width: 560px) {
  [data-layout-gallery="masonry"] .gallery-grid {
    display: block;
    columns: 3;
    column-gap: 0.7rem;
  }
  [data-layout-gallery="masonry"] .gallery-item {
    break-inside: avoid;
    margin-bottom: 0.7rem;
    width: 100%;
    /* Let the photograph decide its own height; that is the point of masonry. */
    aspect-ratio: auto;
  }
  [data-layout-gallery="masonry"] .gallery-item img { height: auto; }
  [data-layout-gallery="masonry"] .gallery-item:nth-child(5n + 1) {
    grid-column: auto;
    grid-row: auto;
  }
}
@media (min-width: 1100px) {
  [data-layout-gallery="masonry"] .gallery-grid { columns: 4; }
}

[data-layout-gallery="carousel"] .gallery-grid {
  display: flex !important;
  gap: 0.6rem;
  overflow-x: auto;
  scroll-snap-type: x mandatory;
  overscroll-behavior-x: contain;
  scrollbar-width: none;
  margin-inline: calc(var(--gutter) * -1);
  padding-inline: var(--gutter);
}
[data-layout-gallery="carousel"] .gallery-grid::-webkit-scrollbar { display: none; }
[data-layout-gallery="carousel"] .gallery-item {
  flex: none;
  width: min(280px, 74vw);
  scroll-snap-align: start;
  grid-column: auto !important;
  grid-row: auto !important;
}

/* -------------------------- tutorials & testimonials -------------------- */

/* A grid out of a carousel: the track stops scrolling and wraps instead. The
   arrows hide themselves, since there is nothing left for them to scroll. */
[data-layout-tutorials="grid"] .carousel[data-carousel="tutorials"],
[data-layout-testimonials="grid"] .carousel[data-carousel="testimonials"],
[data-layout-testimonials="stack"] .carousel[data-carousel="testimonials"] {
  overflow: visible;
  scroll-snap-type: none;
  padding-inline: var(--gutter);
}
[data-layout-tutorials="grid"] .carousel-track[data-tutorials],
[data-layout-testimonials="grid"] .carousel-track[data-testimonials] {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(min(300px, 100%), 1fr));
  gap: 1rem;
  max-width: var(--shell);
  margin-inline: auto;
}
[data-layout-tutorials="grid"] .carousel-item,
[data-layout-testimonials="grid"] .carousel-item { width: auto; }
[data-layout-tutorials="grid"] .carousel-arrows,
[data-layout-testimonials="grid"] .carousel-arrows { display: none; }

/* Stack — one column, centred, for studios with only a few quotes. */
[data-layout-testimonials="stack"] .carousel-track[data-testimonials] {
  display: grid;
  gap: 1rem;
  max-width: 46rem;
  margin-inline: auto;
}
[data-layout-testimonials="stack"] .carousel-item { width: auto; }
[data-layout-testimonials="stack"] .carousel-arrows { display: none; }

/* Coverflow only makes sense while it is a carousel. */
[data-layout-tutorials="grid"] .carousel-item.is-center { transform: none; opacity: 1; }

/* ----------------------------- team & brands ---------------------------- */

[data-layout-team="row"] .team-grid {
  display: flex !important;
  flex-wrap: nowrap;
  overflow-x: auto;
  gap: 0.9rem;
  scroll-snap-type: x proximity;
  scrollbar-width: none;
  justify-content: flex-start;
  margin-inline: calc(var(--gutter) * -1);
  padding-inline: var(--gutter);
}
[data-layout-team="row"] .team-grid::-webkit-scrollbar { display: none; }
[data-layout-team="row"] .team-card { flex: none; width: min(220px, 66vw); scroll-snap-align: start; }

[data-layout-brands="grid"] .brands-strip {
  grid-template-columns: repeat(auto-fill, minmax(min(150px, 100%), 1fr)) !important;
  gap: 0.7rem;
}
[data-layout-brands="grid"] .brand-item {
  padding: 1.1rem 1rem;
  border: 1px solid var(--border);
  border-radius: var(--r);
  background: var(--surface);
}

/* ==========================================================================
   39. Platform links
   ========================================================================== */

/* A labelled pill, for places with room for the name as well as the glyph. */
.link-pill {
  display: inline-flex;
  align-items: center;
  gap: 0.5rem;
  min-height: var(--tap);
  padding: 0.5rem 0.95rem;
  border: 1px solid var(--border);
  border-radius: var(--r-pill);
  background: var(--surface);
  font-size: var(--fs-sm);
  font-weight: 560;
  transition: border-color var(--t-fast) var(--ease),
              background var(--t-fast) var(--ease),
              transform var(--t-fast) var(--ease);
}
.link-pill .ico { width: 1.1rem; height: 1.1rem; color: var(--accent); }
.link-pill-value { color: var(--muted); font-size: var(--fs-xs); }

@media (hover: hover) and (pointer: fine) {
  .link-pill:hover {
    border-color: var(--border-accent);
    background: var(--accent-soft);
    transform: translateY(-2px);
  }
}


/* ==========================================================================
   40. Brands, corrected
   ==========================================================================
   Two problems with the strip as it was.

   First, uploaded logos were normalised to greyscale with the colour restored
   only on :hover — inside a `(hover: hover)` query. On a phone or a tablet there
   is no hover, so every brand logo stayed washed out permanently. The
   normalisation now lives inside the hover query with the restore, which means a
   touch device simply shows the artwork as supplied.

   Second, a brand with no logo rendered as a line of plain text. With four
   seeded brands and no uploads that is four text boxes, which reads as broken
   rather than as pending — so the icon and the name are shown together instead.
   ========================================================================== */

/* Touch and keyboard: the logo exactly as it was uploaded. */
.brand-item img {
  filter: none;
  opacity: 1;
  max-height: 38px;
}

@media (hover: hover) and (pointer: fine) {
  /* Pointer devices get the muted strip, because there is a hover to reward. */
  .brand-item img {
    filter: grayscale(1) brightness(1.28);
    opacity: 0.7;
  }
  .brand-item:hover img,
  .brand-item:focus-visible img { filter: none; opacity: 1; }
}

/* --- the icon + name lockup, used whenever there is no logo image --- */

/* Horizontal. Stacked and centred, the icon, the name and the note each claimed a
   line of their own and the result was three small things in a wide box — which is
   what made four brands look like a placeholder. Side by side they read as one
   entry in a list. */
.brand-lockup {
  display: flex;
  align-items: center;
  gap: 0.7rem;
  text-align: left;
  min-width: 0;
}

.brand-text {
  display: grid;
  gap: 0.1rem;
  min-width: 0;
}

.brand-glyph {
  display: grid;
  place-items: center;
  flex: none;
  width: 38px; height: 38px;
  border-radius: 11px;
  background: var(--accent-soft);
  color: var(--accent);
  transition: background var(--t) var(--ease), color var(--t) var(--ease),
              scale var(--t) var(--ease-spring);
}
.brand-glyph .ico { width: 1.2rem; height: 1.2rem; }

.brand-lockup .brand-word {
  font-size: clamp(1rem, 2.6vw, 1.15rem);
  line-height: 1.15;
  text-align: left;
  white-space: nowrap;
}

.brand-note {
  font-size: var(--fs-2xs);
  letter-spacing: 0.05em;
  text-transform: uppercase;
  color: var(--faint);
  white-space: nowrap;
  transition: color var(--t) var(--ease);
}

@media (hover: hover) and (pointer: fine) {
  .brand-item:hover .brand-glyph {
    background: var(--accent);
    color: var(--accent-ink);
    scale: 1.08;
  }
  .brand-item:hover .brand-note { color: var(--muted); }
}

/* Tiles tall enough for the lockup, and even across the row. */
.brand-item { min-height: 92px; }

/* A brand that links somewhere should look like it does. */
a.brand-item { cursor: pointer; }


/* ==========================================================================
   41. Extra pages
   ==========================================================================
   A page is a document, so it scrolls normally — the one-section-at-a-time stage
   layout is a homepage device and would fight prose. The reading column is
   measured in `ch` rather than pixels, because comfortable line length is a
   function of the type size, not of the screen.
   ========================================================================== */

body.is-page { padding-top: var(--header-h); }

.page-loading {
  display: grid;
  gap: 0.9rem;
  max-width: var(--shell);
  margin-inline: auto;
  padding: clamp(2rem, 8vw, 5rem) var(--gutter);
}
.page-loading .skeleton { display: block; border-radius: var(--r-sm); }

.page-head { padding: clamp(2.2rem, 7vw, 4.5rem) 0 clamp(1.2rem, 3vw, 2rem); }

.page-back {
  display: inline-flex;
  align-items: center;
  gap: 0.45rem;
  margin-bottom: 1.4rem;
  font-size: var(--fs-sm);
  font-weight: 600;
  color: var(--muted);
  transition: color var(--t-fast) var(--ease), gap var(--t-fast) var(--ease);
}
/* The arrow is the sprite's right-facing one, turned to point back. */
.page-back .ico { rotate: 180deg; width: 1rem; height: 1rem; }
.page-back:hover { color: var(--accent); gap: 0.7rem; }

.page-title {
  font-size: clamp(2rem, 6vw, 3.6rem);
  letter-spacing: -0.03em;
  text-wrap: balance;
}
.page-sub {
  margin-top: 0.9rem;
  max-width: 60ch;
  font-size: var(--fs-lead);
  color: var(--text-2);
  text-wrap: pretty;
}
.page-sub code {
  font-family: var(--font-mono);
  font-size: 0.9em;
  padding: 0.1em 0.4em;
  border-radius: 5px;
  background: var(--surface-2);
}

.page-hero {
  margin-bottom: clamp(1.5rem, 4vw, 2.5rem);
  border-radius: var(--r-lg);
  overflow: hidden;
  border: 1px solid var(--border);
}
.page-hero img { width: 100%; height: auto; display: block; }

/* --- the reading column --- */

.prose {
  max-width: 70ch;
  padding-bottom: clamp(2rem, 6vw, 4rem);
  font-size: clamp(1rem, 1.6vw, 1.08rem);
  line-height: 1.75;
  color: var(--text-2);
}
[data-layout-page="wide"] .prose { max-width: none; }
[data-layout-page="centred"] .prose { max-width: 58ch; margin-inline: auto; text-align: center; }
[data-layout-page="centred"] .page-head .shell { text-align: center; }
[data-layout-page="centred"] .page-sub { margin-inline: auto; }

.prose > * + * { margin-top: 1.1em; }

.prose h2, .prose h3, .prose h4 {
  margin-top: 2.2em;
  color: var(--text);
  letter-spacing: -0.02em;
  text-wrap: balance;
}
.prose h2 { font-size: clamp(1.4rem, 3.4vw, 1.9rem); }
.prose h3 { font-size: clamp(1.15rem, 2.6vw, 1.4rem); }
.prose h4 { font-size: 1.08rem; }
.prose > h2:first-child, .prose > h3:first-child { margin-top: 0; }

.prose strong { color: var(--text); font-weight: 650; }
.prose em { font-style: italic; }

.prose a {
  color: var(--text);
  text-decoration: underline;
  text-decoration-color: var(--accent-line);
  text-decoration-thickness: 2px;
  text-underline-offset: 3px;
  transition: text-decoration-color var(--t-fast) var(--ease), color var(--t-fast) var(--ease);
}
.prose a:hover { color: var(--accent); text-decoration-color: var(--accent); }

.prose ul, .prose ol { padding-left: 1.4rem; }
.prose ul { list-style: none; }
.prose ul > li { position: relative; padding-left: 0.4rem; }
.prose ul > li::before {
  content: "";
  position: absolute;
  left: -0.9rem; top: 0.72em;
  width: 6px; height: 6px;
  border-radius: 50%;
  background: var(--accent);
}
.prose ol { list-style: decimal; }
.prose ol > li::marker { color: var(--accent); font-weight: 650; }
.prose li + li { margin-top: 0.5em; }

.prose blockquote {
  padding: 0.2rem 0 0.2rem 1.2rem;
  border-left: 3px solid var(--accent);
  color: var(--text);
  font-size: 1.06em;
}

.prose code {
  font-family: var(--font-mono);
  font-size: 0.88em;
  padding: 0.12em 0.42em;
  border-radius: 6px;
  background: var(--surface-2);
  color: var(--text);
}

.prose hr {
  height: 1px;
  border: 0;
  margin-block: 2.4em;
  background: var(--grad-line);
}

.prose img {
  width: 100%;
  height: auto;
  border-radius: var(--r);
  border: 1px solid var(--border);
}

/* --- module grids that pages need and the homepage has no equivalent of --- */

.page-card-grid,
.page-quote-grid {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  gap: 1rem;
}
@media (min-width: 700px) {
  .page-card-grid { grid-template-columns: repeat(auto-fill, minmax(min(300px, 100%), 1fr)); }
  .page-quote-grid { grid-template-columns: repeat(auto-fill, minmax(min(340px, 100%), 1fr)); }
}
/* These cards are carousel items on the homepage, so they carry no width here. */
.page-card-grid .tutorial-card,
.page-quote-grid .testimonial-card { width: auto; height: 100%; }

/* --- call to action --- */

.page-cta {
  display: grid;
  justify-items: center;
  gap: 1rem;
  padding: clamp(2rem, 6vw, 3.5rem) var(--gutter);
  border: 1px solid var(--border);
  border-radius: var(--r-xl);
  background:
    radial-gradient(120% 100% at 50% 0%, var(--accent-soft), transparent 70%),
    var(--surface);
  text-align: center;
}
.page-cta-actions { display: flex; flex-wrap: wrap; gap: 0.7rem; justify-content: center; }

/* --- not found / failed --- */

.page-missing {
  display: grid;
  place-items: center;
  min-height: 60svh;
  padding-block: clamp(2rem, 8vw, 5rem);
  text-align: center;
}
.page-missing .shell { display: grid; justify-items: center; gap: 1rem; }
.page-missing .page-sub { margin-inline: auto; }

@media print {
  .page-back, .page-cta { display: none !important; }
  .prose { max-width: none; color: #000; }
}


/* ==========================================================================
   42. Density
   ==========================================================================
   Sections were running to twice the height of the viewport, which defeats the
   one-section-at-a-time layout entirely — you end up scrolling inside a panel
   that was supposed to be a panel. Everything spatial derives from these two
   custom properties, so one setting tightens the whole page.
   ========================================================================== */

[data-density="compact"] {
  --section-y: clamp(2.4rem, 6vw, 5rem);
  --gutter: clamp(1rem, 3vw, 2.2rem);
}
[data-density="tight"] {
  --section-y: clamp(1.6rem, 4vw, 3rem);
  --gutter: clamp(0.9rem, 2.5vw, 1.6rem);
}

/* The grids tighten too, or the padding shrinks and the content does not. */
[data-density="compact"] .services-grid { gap: 0.6rem; }
[data-density="compact"] .work-grid { gap: 0.7rem; }
[data-density="compact"] .section-head { margin-bottom: clamp(1.2rem, 3vw, 2rem); }

[data-density="tight"] .services-grid { gap: 0.45rem; }
[data-density="tight"] .work-grid { gap: 0.5rem; }
[data-density="tight"] .gallery-grid { gap: 0.35rem; }
[data-density="tight"] .section-head { margin-bottom: clamp(0.9rem, 2vw, 1.4rem); }
[data-density="tight"] .service-card { padding: 1rem 1.1rem; }
[data-density="tight"] .work-body { padding: 0.85rem 0.95rem 1rem; }

/* In stage layout a tight page can drop the top padding too, since the panel is
   already centring its content. */
[data-density="tight"] html[data-layout="stage"] .stage-panel,
html[data-density="tight"][data-layout="stage"] .stage-panel {
  padding-block: calc(var(--header-h) + 1.2rem) 4rem;
}

/* ==========================================================================
   43. Slides — one item at a time
   ==========================================================================
   The arrangement that answers "this section takes too much space": instead of
   eight cards stacked into three rows, one card fills the width and the rest are
   a swipe away. Native horizontal scroll snapping does the moving, so a trackpad,
   a touch screen, a wheel and the arrow keys all work without being reimplemented.

   `scroll-snap-align: center` rather than `start`, because a single full-width
   item reads as centred and the scroll padding for the header would otherwise
   offset it.
   ========================================================================== */

.slide-rail {
  display: flex !important;
  gap: clamp(0.6rem, 2vw, 1rem);
  overflow-x: auto;
  overflow-y: hidden;
  scroll-snap-type: x mandatory;
  overscroll-behavior-x: contain;
  scrollbar-width: none;
  -webkit-overflow-scrolling: touch;
  /* Bleed to the edges so a sliver of the next item signals that there is more. */
  margin-inline: calc(var(--gutter) * -1);
  padding-inline: var(--gutter);
  padding-block: 0.35rem;
}
.slide-rail::-webkit-scrollbar { display: none; }

.slide-rail > * {
  flex: 0 0 100%;
  scroll-snap-align: center;
  scroll-snap-stop: always;
  /* Grid children may carry span rules from another layout; neutralise them. */
  grid-column: auto !important;
  grid-row: auto !important;
}

/* Just enough of the neighbour to read as a deck rather than a page. */
@media (min-width: 640px) {
  .slide-rail > * { flex-basis: calc(100% - clamp(2rem, 8vw, 5rem)); }
}

/* --- a portfolio slide is a two-column spread on a wide screen --- */

@media (min-width: 900px) {
  .slide-rail .work-card {
    flex-direction: row;
    align-items: stretch;
    /* Sized so the whole panel — header offset, section heading, filter chips,
       the slide and its control bar — still fits a laptop screen. That is the
       entire point of choosing slides over a grid. */
    min-height: min(44svh, 380px);
  }
  .slide-rail .work-card .work-thumb {
    width: 56%;
    flex: none;
    aspect-ratio: auto;
    height: auto;
  }
  .slide-rail .work-card .work-body {
    justify-content: center;
    padding: clamp(1.2rem, 3vw, 2.2rem);
  }
  .slide-rail .work-card .work-title { font-size: var(--fs-h3); }
  .slide-rail .work-card .work-desc {
    -webkit-line-clamp: 4;
    line-clamp: 4;
  }
}

/* Gallery slides are a single large frame. */
.slide-rail .gallery-item { aspect-ratio: 16 / 10; }
@media (max-width: 639px) {
  .slide-rail .gallery-item { aspect-ratio: 4 / 5; }
}
.slide-rail .gallery-caption { opacity: 1; }

/* Cards built for a carousel already have a width; the rail decides it now. */
.slide-rail .carousel-item,
.slide-rail .tutorial-card,
.slide-rail .testimonial-card,
.slide-rail .team-card { width: auto; height: auto; }

.slide-rail .service-card { min-height: min(34svh, 280px); align-items: center; }

/* In stage layout the deck must not exceed what the panel can show. */
html[data-layout="stage"] .slide-rail .gallery-item { aspect-ratio: auto; height: min(46svh, 420px); }
html[data-layout="stage"] .slide-rail .gallery-item img { height: 100%; }

/* --- the control bar, built by site.js --- */

.slide-bar {
  display: flex;
  align-items: center;
  gap: 0.6rem;
  margin-top: clamp(0.8rem, 2vw, 1.2rem);
}

.slide-dots {
  display: flex;
  flex: 1 1 auto;
  align-items: center;
  gap: 0.35rem;
  min-width: 0;
  overflow-x: auto;
  scrollbar-width: none;
}
.slide-dots::-webkit-scrollbar { display: none; }

.slide-dot {
  flex: none;
  width: 22px; height: 3px;
  border: 0;
  border-radius: 2px;
  padding: 0;
  background: var(--border-2);
  cursor: pointer;
  transition: background var(--t) var(--ease), width var(--t) var(--ease);
}
.slide-dot[aria-current="true"] { width: 34px; background: var(--accent); }

/* The tap target is the padded wrapper, so a 3px bar is still hittable. */
.slide-dot-hit {
  flex: none;
  display: grid;
  place-items: center;
  height: var(--tap);
  padding-inline: 2px;
  border: 0;
  background: none;
  cursor: pointer;
}

.slide-count {
  flex: none;
  font-family: var(--font-mono);
  font-size: var(--fs-xs);
  color: var(--muted);
  font-variant-numeric: tabular-nums;
}

.slide-arrows { flex: none; display: flex; gap: 0.3rem; }

/* The theme button and the back-to-top button are pinned to the right edge of the
   viewport. Where the shell reaches that edge too — anything under the shell plus
   that column on both sides — they land directly on top of the next arrow, and the
   deck looks broken because its main control cannot be pressed. Reserving the
   column keeps the bar reading the same at every width. */
@media (max-width: 1340px) {
  .slide-bar { padding-right: calc(var(--tap) + 1.2rem); }
}

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

@media print {
  .slide-bar { display: none !important; }
  .slide-rail { display: grid !important; overflow: visible; }
}


/* ==========================================================================
   44. Icons on the timeline and the skill bars
   ==========================================================================
   These sections had no visual anchor at all — a wall of text where every other
   section has a mark to scan by. The icon is chosen per row in the panel.
   ========================================================================== */

.tl-icon {
  position: absolute;
  left: 0;
  top: 0.15rem;
  display: grid;
  place-items: center;
  width: 26px; height: 26px;
  border-radius: 8px;
  background: var(--accent-soft);
  color: var(--accent);
}
.tl-icon .ico { width: 0.95rem; height: 0.95rem; }

/* The timeline item already reserves its left gutter for the rail marker, so the
   icon sits in the flow above it and the text indents to clear it. */
.tl-item { position: relative; }
.tl-item .tl-period { padding-left: 2.1rem; min-height: 26px; }

@media (min-width: 900px) {
  /* With the wider gutter the icon can sit out on the rail itself. */
  .tl-icon { left: -3.25rem; top: 0; }
  .tl-item .tl-period { padding-left: 0; }
}

.skill-name { display: inline-flex; align-items: center; gap: 0.45rem; }
.skill-icon {
  width: 0.95rem; height: 0.95rem;
  flex: none;
  color: var(--accent);
  opacity: 0.85;
}


/* ==========================================================================
   45. Slides, per section
   ==========================================================================
   site.js adds `.slide-rail` to the host element, which carries all the shared
   behaviour in section 43. These rules only undo the arrangement each grid had
   before, since a grid's own `grid-template-columns` would otherwise fight the
   rail's flex layout.
   ========================================================================== */

[data-layout-services="slides"] .services-grid,
[data-layout-projects="slides"] .work-grid,
[data-layout-gallery="slides"] .gallery-grid,
[data-layout-team="slides"] .team-grid {
  grid-template-columns: none !important;
  columns: auto !important;
  justify-content: flex-start;
}

/* --- tutorials and testimonials: a carousel standing down for a rail ---
   These two sections render into a scripted coverflow carousel. When site.js turns
   the track into a rail it marks the wrapper, and everything the carousel does
   has to be undone: it scrolls and snaps in its own right, its track is sized to
   its content, and it scales and fades everything that is not the centre item.
   Keyed on the marker rather than on a list of layout names, so adding another
   sliding arrangement later cannot forget to update this. */

.carousel.carousel-railed {
  /* Not a scroll container any more. Two nested ones means the inner — the rail
     the reader is actually looking at — never receives the gesture. */
  display: block;
  overflow: visible;
  scroll-snap-type: none;
  padding-block: 0;
  /* The carousel was full-bleed so the coverflow could run off both edges. A rail
     has a heading above it and a control bar below, both inside the shell, so it
     has to share their measure or the three disagree about where the page starts. */
  max-width: var(--shell);
  margin-inline: auto;
  padding-inline: var(--gutter);
  /* Stated rather than inherited. As a flex item of the stage panel it was being
     shrink-to-fit against its own content — six cards wide — so on a phone the
     deck was measured against 1240px and every card overflowed the screen. */
  width: 100%;
  align-self: stretch;
}

/* `width: max-content` is right for a track that scrolls its own content and
   fatal for one whose children are sized in percentages — a third of max-content
   is a third of nothing in particular, and only two of three cards fitted. */
.carousel-railed .slide-rail {
  width: auto;
  min-width: 0;
}

/* The scale and the fade are the coverflow effect. In a deck every visible card
   is equally the subject, and the scale also made one card measurably taller than
   its neighbours, which the paging maths then had to fight. */
/* Two classes deep on purpose: `.carousel[data-coverflow] .carousel-item` counts
   three, so a shorter selector here loses and the cards stay faded and scaled. */
.carousel.carousel-railed .carousel-item,
.carousel.carousel-railed .carousel-item.is-center {
  transform: none;
  opacity: 1;
  filter: none;
}

/* The carousel's own arrows would sit above the rail's, stepping by a different
   amount. One set of controls per deck. */
.section:has(.carousel-railed) .carousel-arrows { display: none; }

/* With those arrows gone the split heading has nothing to sit opposite, and
   `space-between` would leave it stranded against the left edge. */
.section:has(.carousel-railed) .section-head-row {
  flex-direction: column;
  align-items: center;
  justify-content: center;
  text-align: center;
}

/* The rail is focusable so the arrow keys can drive it; make that visible. */
.slide-rail:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 6px;
  border-radius: var(--r);
}


/* ==========================================================================
   46. How many per view
   ==========================================================================
   `data-per-view` on the rail is the intent set in the panel: three, two or one.
   These rules step it down as the screen narrows, because three cards across a
   phone is unreadable — and site.js measures what is actually visible rather than
   trusting the attribute, so the dots and the arrows always page by what the
   reader can see.
   ========================================================================== */

.slide-rail {
  /* Named so the flex-basis sums below can subtract it exactly. */
  --rail-gap: clamp(0.6rem, 2vw, 1rem);
  gap: var(--rail-gap);
}

/* One-up keeps the peek of the neighbour; the multi-up arrangements do not need
   it, because the neighbour is already on screen. */
.slide-rail[data-per-view="2"] > *,
.slide-rail[data-per-view="3"] > * {
  scroll-snap-align: start;
  /* `always` is correct for one-up — it stops the reader flicking past a slide —
     but it also forbids a programmatic scroll from passing intermediate snap
     points. Paging three cards at a time would land on the first of them, so a
     multi-up rail must allow the jump. */
  scroll-snap-stop: normal;
}

.slide-rail[data-per-view="2"] > * {
  flex-basis: calc((100% - var(--rail-gap)) / 2);
}
.slide-rail[data-per-view="3"] > * {
  flex-basis: calc((100% - 2 * var(--rail-gap)) / 3);
}

/* Tablets: three becomes two. */
@media (max-width: 1023px) {
  .slide-rail[data-per-view="3"] > * {
    flex-basis: calc((100% - var(--rail-gap)) / 2);
  }
}

/* Phones: everything except the compact team card becomes one, with a peek. */
@media (max-width: 639px) {
  .slide-rail[data-per-view="3"] > *,
  .slide-rail[data-per-view="2"]:not(.slide-rail-team) > * {
    flex-basis: calc(100% - clamp(1.5rem, 9vw, 3rem));
    scroll-snap-align: center;
  }
  /* Two portraits side by side still work at 390px, and that is the point of
     choosing pairs for the team. */
  .slide-rail-team[data-per-view="2"] > * {
    flex-basis: calc((100% - var(--rail-gap)) / 2);
  }
}

/* The two-column portfolio spread is a one-up idea; at two or three across the
   cards go back to being cards. */
.slide-rail[data-per-view="2"] .work-card,
.slide-rail[data-per-view="3"] .work-card {
  flex-direction: column;
  min-height: 0;
}
.slide-rail[data-per-view="2"] .work-card .work-thumb,
.slide-rail[data-per-view="3"] .work-card .work-thumb {
  width: auto;
  aspect-ratio: 16 / 10;
}
.slide-rail[data-per-view="2"] .work-card .work-body,
.slide-rail[data-per-view="3"] .work-card .work-body {
  padding: 0.9rem 1rem 1.1rem;
}
/* The one-up spread can afford a big title and four lines of blurb. Three across
   cannot: the heading, the filter chips, the cards and the control bar all have to
   fit one screen, or the reader never sees the arrows that make it a slider. */
.slide-rail[data-per-view="2"] .work-card .work-title,
.slide-rail[data-per-view="3"] .work-card .work-title {
  font-size: clamp(1rem, 1.4vw, 1.12rem);
}
.slide-rail[data-per-view="2"] .work-card .work-desc,
.slide-rail[data-per-view="3"] .work-card .work-desc {
  -webkit-line-clamp: 2;
  line-clamp: 2;
}
/* One row of tags. A second row is the difference between fitting and not. */
.slide-rail[data-per-view="2"] .tag-row,
.slide-rail[data-per-view="3"] .tag-row {
  flex-wrap: nowrap;
  overflow: hidden;
}

/* In stage layout the thumbnail is measured against the screen rather than the
   card, the way the gallery frames already are. The panel has to hold a heading,
   the filter chips, the deck and its controls, and the thumbnail is the only part
   that can give without losing information. */
html[data-layout="stage"] .slide-rail[data-per-view="2"] .work-card .work-thumb,
html[data-layout="stage"] .slide-rail[data-per-view="3"] .work-card .work-thumb,
html[data-layout="stage"] .slide-rail[data-per-view="2"] .tut-thumb,
html[data-layout="stage"] .slide-rail[data-per-view="3"] .tut-thumb {
  aspect-ratio: auto;
  height: min(20svh, 200px);
}

/* A 768-tall laptop is the tightest common screen. Give up the decoration — a
   smaller preview, one line less of blurb, no tag row — rather than push the
   control bar off the screen, because a deck whose arrows you cannot see is just a
   row of cards with most of them hidden. */
@media (max-height: 820px) {
  html[data-layout="stage"] .slide-rail[data-per-view="2"] .work-card .work-thumb,
  html[data-layout="stage"] .slide-rail[data-per-view="3"] .work-card .work-thumb,
  html[data-layout="stage"] .slide-rail[data-per-view="2"] .tut-thumb,
  html[data-layout="stage"] .slide-rail[data-per-view="3"] .tut-thumb {
    height: min(16svh, 140px);
  }
  html[data-layout="stage"] .slide-rail[data-per-view="2"] .tag-row,
  html[data-layout="stage"] .slide-rail[data-per-view="3"] .tag-row,
  html[data-layout="stage"] .slide-rail[data-per-view="2"] .tut-badges,
  html[data-layout="stage"] .slide-rail[data-per-view="3"] .tut-badges {
    display: none;
  }
  html[data-layout="stage"] .slide-rail[data-per-view="2"] .tut-desc,
  html[data-layout="stage"] .slide-rail[data-per-view="3"] .tut-desc {
    -webkit-line-clamp: 2;
    line-clamp: 2;
  }
  html[data-layout="stage"] .slide-rail[data-per-view="2"] .tut-body,
  html[data-layout="stage"] .slide-rail[data-per-view="3"] .tut-body {
    padding: 0.85rem 0.95rem 1rem;
    gap: 0.4rem;
  }
  html[data-layout="stage"] .slide-rail[data-per-view="2"] .work-card .work-body,
  html[data-layout="stage"] .slide-rail[data-per-view="3"] .work-card .work-body {
    padding: 0.7rem 0.9rem 0.85rem;
    gap: 0.35rem;
  }
}

/* Shorter than a 640px phone in portrait and the standfirst is a luxury. The
   portfolio carries a row of filter chips no other section has, so it is the first
   panel to run out of room; dropping one paragraph buys back exactly that row. */
@media (max-height: 700px) {
  html[data-layout="stage"] .stage-panel:has(.slide-rail) .section-lead { display: none; }
}

/* Gallery frames stay square-ish when several are visible. */
.slide-rail[data-per-view="2"] .gallery-item,
.slide-rail[data-per-view="3"] .gallery-item { aspect-ratio: 1; height: auto; }
html[data-layout="stage"] .slide-rail[data-per-view="2"] .gallery-item,
html[data-layout="stage"] .slide-rail[data-per-view="3"] .gallery-item {
  height: auto;
}

/* Service cards shrink their minimum when sharing the row. */
.slide-rail[data-per-view="2"] .service-card,
.slide-rail[data-per-view="3"] .service-card { min-height: 0; align-items: flex-start; }

/* The team line: even columns, portraits aligned, nothing stretched. */
.slide-rail-team > * { align-self: stretch; }



/* ==========================================================================
   47. Spotlight — one card on stage, its neighbours waiting
   ==========================================================================
   The arrangement from the reference: the active card at full size and full
   colour, the ones either side scaled back and dimmed so they read as next and
   previous rather than as competing for attention.

   It is a one-per-view rail like `slides`, so it inherits all the paging, the
   dots, the keyboard and the drag from section 43 — the only additions here are
   the narrower item (so both neighbours stay in frame) and the scaling, which
   needs `is-center` from site.js because a stylesheet cannot know which card the
   reader is looking at.
   ========================================================================== */

.slide-rail[data-deck="spotlight"] {
  /* The width of the card on stage.
     It was capped at 340px, which is a phone-sized card. On a phone that is
     right — 62vw of 390px is 242px and the neighbours peek in nicely — but the
     cap applied at every width, so on a 1280px screen the "spotlight" was a
     340px card adrift in a 1180px rail. It read as a slide stuck mid-scroll
     rather than as one thing given the stage, which is the whole point of the
     arrangement.

     The ceiling is now 29rem. It is not larger still because the card is
     portrait (3/4 below) and has to fit the section along with its heading and
     its controls: at 464px wide a 3/4 card is 619px tall, which is about all a
     900px-tall laptop has to give once the header and the deck bar are paid for.
     Wider than this and the height cap would take over and quietly square the
     card off. */
  --spot-w: clamp(210px, 62vw, 29rem);

  /* Half an item plus the gap, so the first and last card can still reach the
     middle of the rail instead of stopping against the edge. Derived from the
     width above rather than written out again — these two were separate numbers
     (340px and 11rem) that had to agree, and nothing would have reported it if a
     later change to one had left the end cards unable to reach the centre. */
  padding-inline: max(var(--gutter), calc(50% - var(--spot-w) / 2));
  align-items: center;
}

.js .slide-rail[data-deck="spotlight"] > * {
  flex: 0 0 var(--spot-w);
  scroll-snap-align: center;
  /* `always` would be right for a deck you flick through one at a time, but the
     arrows page programmatically and it forbids passing a snap point. */
  scroll-snap-stop: normal;
  transform: scale(0.86);
  opacity: 0.4;
  filter: saturate(0.65);
  transition:
    transform var(--t-slow) var(--ease-out),
    opacity var(--t-slow) var(--ease-out),
    filter var(--t-slow) var(--ease-out);
}

.js .slide-rail[data-deck="spotlight"] > .is-center {
  transform: scale(1);
  opacity: 1;
  filter: none;
  z-index: 2;
}

/* The scale is decoration; the dimming is what tells you which card is active, so
   it survives when someone asks for less movement. */
@media (prefers-reduced-motion: reduce) {
  .js .slide-rail[data-deck="spotlight"] > * {
    transform: none;
    transition: opacity 0.2s linear;
  }
  .js .slide-rail[data-deck="spotlight"] > .is-center { transform: none; }
}

/* --- portrait framing, as in the reference --- */

/* A spotlight card is tall: it is one thing being looked at rather than one of a
   row, and a portrait frame is what makes a person or a phone-shot clip read
   properly. Capped against the screen so the card, the heading and the controls
   still make one viewful. */
.slide-rail[data-deck="spotlight"] .gallery-item {
  aspect-ratio: 3 / 4;
  height: auto;
  /* Raised with the width. A 480px ceiling under a 464px-wide card would cap the
     height before the 3/4 ratio was reached and silently turn the portrait frame
     into a landscape one — the card would grow sideways and stop looking like the
     reference. */
  max-height: min(68svh, 640px);
}
html[data-layout="stage"] .slide-rail[data-deck="spotlight"] .gallery-item {
  height: min(58svh, 520px);
  aspect-ratio: auto;
}

/* The caption is an overlay that normally appears on hover. On a card that is
   deliberately centre stage it should just be readable — and on a touch screen
   there is no hover to reveal it with. */
.slide-rail[data-deck="spotlight"] .gallery-caption {
  opacity: 1;
  transform: none;
}
.slide-rail[data-deck="spotlight"] .gallery-cat {
  display: inline-block;
  margin-bottom: 0.2rem;
  color: var(--accent);
  font-weight: 700;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  font-size: var(--fs-2xs);
}

/* Only the active card is being read, so the others do not need to compete. */
.js .slide-rail[data-deck="spotlight"] > *:not(.is-center) .gallery-caption { opacity: 0; }

.slide-rail[data-deck="spotlight"] .work-card,
.slide-rail[data-deck="spotlight"] .tutorial-card {
  flex-direction: column;
  min-height: 0;
}
.slide-rail[data-deck="spotlight"] .work-thumb {
  width: auto;
  aspect-ratio: 4 / 5;
  height: auto;
}
.slide-rail[data-deck="spotlight"] .team-photo { aspect-ratio: 3 / 4; }


/* ==========================================================================
   48. Inline card previews
   ==========================================================================
   A short clip playing in the card itself, muted and looping, for the one card
   the reader is looking at. Sits over the still rather than replacing it, so the
   poster frame covers the moment before playback starts and there is never an
   empty box.
   ========================================================================== */

[data-preview] { position: relative; }

.card-preview {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
  border: 0;
  /* The card is a <button> that opens the lightbox. A video on top of it would
     swallow the click and the card would stop working. */
  pointer-events: none;
  /* Under the caption and the play badge, over the still. */
  z-index: 1;

  /* Hidden until there is a picture.
     This is the fix for "it goes black": a <video> paints black from the moment
     it is inserted until it has decoded a frame, and an embed paints its own
     black stage while its player boots. Both are inserted *over* the thumbnail,
     so the card went black for the whole of the loading period — and stayed black
     if the clip never loaded at all, which is what a wrong link or a blocked
     embed looks like.

     The player is added without `is-ready`, and site.js adds it on the first real
     frame: `playing` for a file, `load` for an embed. Until then the card shows
     the still it was already showing. It used to animate in on insertion, which
     is exactly one event too early. */
  opacity: 0;
  transition: opacity 0.4s var(--ease-out);
}

.card-preview.is-ready { opacity: 1; }

/* An embed cannot be `object-fit`, so a 16:9 player in a squarer card would
   letterbox itself in black bars. Scaled to cover the card instead, and the
   overflow is clipped by the thumbnail. */
.card-preview:is(iframe) {
  /* Widest of the two ratios wins, so there is never an uncovered edge. */
  width: max(100%, calc(100% * 16 / 9));
  height: max(100%, calc(100% * 9 / 16));
  left: 50%;
  top: 50%;
  right: auto;
  bottom: auto;
  transform: translate(-50%, -50%);
}

[data-preview] { overflow: hidden; }

@media (prefers-reduced-motion: reduce) {
  .card-preview { transition: none; }
}

/* The badge means "there is a video here". While one is actually playing it is
   just clutter over the picture. */
/* `is-ready`, not merely present: the player is in the DOM and invisible for as
   long as it takes to produce a frame, and hiding the badge then would take the
   "there is a video here" signal away while the card was still just a still. */
[data-preview]:has(.card-preview.is-ready) .work-play { opacity: 0; }

/* Captions and badges have to stay on top of the moving picture. */
.gallery-caption,
.work-featured,
.tut-duration { z-index: 2; }


/* ==========================================================================
   49. Worded deck controls
   ==========================================================================
   "Prev" and "Next" spelled out, as in the reference. A spotlight is a browse
   rather than a scrub, and the words say so — where a fifteen-page rail keeps the
   chevrons, which fit beside its dots.
   ========================================================================== */

.deck-step {
  display: inline-flex;
  align-items: center;
  gap: 0.4rem;
  min-height: var(--tap);
  padding-inline: 0.7rem;
  border: 0;
  border-radius: var(--r-pill);
  background: none;
  color: var(--text-2);
  font: inherit;
  font-size: var(--fs-sm);
  font-weight: 600;
  cursor: pointer;
  transition: color var(--t-fast) var(--ease), background var(--t-fast) var(--ease);
}
.deck-step .ico { width: 1rem; height: 1rem; }
.deck-step:hover:not(:disabled) { color: var(--text); background: var(--surface-2); }
.deck-step:disabled { opacity: 0.35; cursor: default; }
.deck-step:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }

/* Worded controls are wide enough to want the middle of the bar, with the counter
   and the dots reading as a group on the left. */
.slide-bar-text .slide-arrows { gap: 0.8rem; }
@media (max-width: 479px) {
  /* Two words plus the counter plus a long dot strip does not fit 390px. The dots
     are the redundant one — the words already say which way is which. */
  .slide-bar-text .slide-dots { display: none; }
}


/* ==========================================================================
   50. Contact routing
   ==========================================================================
   In chat-first mode the phone and email cards open WhatsApp instead of a dialler
   or a mail client. The card still shows the real number and address; this line is
   what stops that being a surprise. Saying where a link goes costs one line of
   type and is the difference between a shortcut and a trick.
   ========================================================================== */

.contact-card-via {
  display: inline-flex;
  align-items: center;
  gap: 0.3rem;
  margin-top: 0.28rem;
  font-size: var(--fs-2xs);
  font-weight: 650;
  letter-spacing: 0.03em;
  color: var(--accent);
}
.contact-card-via .ico { width: 0.8rem; height: 0.8rem; }

/* The "continue on WhatsApp" offer after a message has been saved. A real link
   rather than a window opened after the await, which a popup blocker would eat. */
.form-chat {
  display: inline-block;
  margin-top: 0.4rem;
  font-weight: 700;
  color: var(--accent);
  text-decoration: underline;
  text-underline-offset: 3px;
}
.form-chat:hover { color: var(--text); }



/* ==========================================================================
   51. Reviews written by visitors
   ==========================================================================
   The section still leads with what clients said; the form is behind a disclosure
   underneath it. Everything a visitor writes is held for approval, so the copy has
   to be honest about that rather than implying it is already live.
   ========================================================================== */

.review-invite {
  display: grid;
  justify-items: center;
  gap: 0.9rem;
  margin-top: clamp(1.4rem, 3vw, 2.2rem);
}

.review-form {
  display: grid;
  gap: 0.9rem;
  width: min(100%, 620px);
  padding: clamp(1.1rem, 3vw, 1.7rem);
  border: 1px solid var(--border);
  border-radius: var(--r-lg);
  background: var(--surface);
  text-align: left;
}

/* An optional field should say so, quietly, rather than leaving someone to guess
   from the absence of an asterisk. */
.field-optional {
  margin-left: 0.35rem;
  font-size: var(--fs-2xs);
  font-weight: 600;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: var(--faint);
}

.field-hint {
  margin-top: 0.3rem;
  font-size: var(--fs-2xs);
  color: var(--muted);
}

/* --- the star rating input ---
   Real radio inputs. A rating is one value out of five, which is exactly what a
   radio group is, so arrow keys, tab order and screen-reader announcement all come
   for free — none of which is true of five divs with click handlers.

   Laid out in reverse (5 first in the DOM) so that `~` can reach the stars after
   the hovered one, which is what makes the whole run up to a star light rather
   than just the one under the pointer. `row-reverse` puts 1 back on the left. */

.rating-field { border: 0; padding: 0; margin: 0; }
.rating-field legend {
  padding: 0;
  margin-bottom: 0.4rem;
  font-size: var(--fs-sm);
  font-weight: 600;
  color: var(--text-2);
}

.rating-input {
  display: flex;
  flex-direction: row-reverse;
  justify-content: flex-end;
  gap: 0.15rem;
}

.rating-star {
  display: grid;
  place-items: center;
  width: var(--tap); height: var(--tap);
  cursor: pointer;
  color: var(--border-2);
  transition: color var(--t-fast) var(--ease), scale var(--t-fast) var(--ease-spring);
}
.rating-star .ico { width: 1.5rem; height: 1.5rem; }

/* The input itself is the accessible control, so it is placed rather than removed:
   `display: none` would take it out of the tab order along with the keyboard
   support that is the reason for using a radio in the first place. */
.rating-star input {
  position: absolute;
  width: 1px; height: 1px;
  opacity: 0;
  pointer-events: none;
}

.rating-star:has(input:checked),
.rating-star:has(input:checked) ~ .rating-star { color: var(--accent); }

.rating-input:hover .rating-star { color: var(--border-2); }
.rating-input .rating-star:hover,
.rating-input .rating-star:hover ~ .rating-star { color: var(--accent); }

.rating-star:has(input:focus-visible) {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
  border-radius: 8px;
}
.rating-star:active { scale: 0.92; }

/* Off-screen but still read aloud: the star glyphs mean nothing without it. */
.sr-only {
  position: absolute;
  width: 1px; height: 1px;
  margin: -1px;
  padding: 0;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
  border: 0;
}

@media (prefers-reduced-motion: reduce) {
  .rating-star { transition: color var(--t-fast) var(--ease); }
  .rating-star:active { scale: 1; }
}


/* ==========================================================================
   52. Garnet — red on black, as a gradient
   ==========================================================================
   The other reds are flat dark grounds with a red accent. This one puts the red
   into the ground itself: a deep gradient that is nearly black at the edges and
   warms towards the middle, so the page has a direction to it rather than sitting
   on one value.

   The gradient is on `body`, not on `--bg`, because a custom property holding a
   gradient cannot be used everywhere a colour is expected — `color-mix`, the glass
   surfaces and the borders all need a real colour. So `--bg` stays a solid that
   matches the darkest part of the gradient, and everything derived from it keeps
   working.
   ========================================================================== */

[data-theme="garnet"] {
  --accent:   #ff2d46;
  --accent-2: #ff8a5c;
  --accent-ink: #14060a;

  --bg:   #0a0406;
  --bg-2: #14070c;
  --bg-3: #1e0a11;
}
[data-theme="garnet"] {
  --border:   rgba(255, 120, 140, 0.13);
  --border-2: rgba(255, 120, 140, 0.22);
}

html[data-theme="garnet"] body {
  background:
    radial-gradient(115% 80% at 50% -10%, rgba(255, 45, 70, 0.20), transparent 60%),
    radial-gradient(100% 70% at 50% 110%, rgba(255, 45, 70, 0.13), transparent 62%),
    linear-gradient(180deg, #14070c 0%, #0a0406 45%, #0a0406 55%, #16080d 100%);
  /* Fixed, so the gradient belongs to the window rather than scrolling away and
     leaving flat black behind it. */
  background-attachment: fixed;
}

/* A section that paints its own alternate ground would cover the gradient in
   stripes, which is the one thing a gradient theme must not look like. */
html[data-theme="garnet"] .section-alt,
html[data-theme="garnet"] .brands { background: transparent; }

/* Panels stay legible over a lit ground by being slightly more opaque than they
   are on a flat one. */
html[data-theme="garnet"] {
  --surface:   rgba(255, 255, 255, 0.045);
  --surface-2: rgba(255, 255, 255, 0.075);
}

@media print {
  html[data-theme="garnet"] body { background: #fff; }
}



/* ==========================================================================
   53. Images in the role each one plays
   ==========================================================================
   Not one generic "picture" field everywhere. A service is best shown by an example
   of the work, a career entry by the mark of who it was for, and a skill by the
   logo of the tool — three different jobs that happen to all be images. Every one
   of them is optional and falls back to the icon that was already there, so a
   half-illustrated list still reads as deliberate rather than half-finished.
   ========================================================================== */

/* --- a service: an example of the work, across the top --- */

/* The card is a horizontal row of icon + text. With a picture it becomes a column,
   because a photograph squeezed into a 48px column beside the text would be a
   thumbnail of nothing. */
.service-card.has-shot {
  flex-direction: column;
  gap: 0;
  padding: 0;
  overflow: hidden;
}

.service-shot {
  display: block;
  /* `align-self`, because a deck sets `align-items: center` on the card and a
     centred column child shrink-wraps to its content — which for an image sized at
     100% of nothing is 0x0. This is what makes the picture appear at all inside the
     sliding arrangements. */
  align-self: stretch;
  width: 100%;
  aspect-ratio: 16 / 9;
  overflow: hidden;
  background: var(--bg-3);
}
.service-shot img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
  transition: transform var(--t-slow) var(--ease-out);
}

/* The icon still appears, lifted onto the corner of the picture: it is what carries
   the section's rhythm when only some cards have images. */
.service-card.has-shot .service-icon {
  position: absolute;
  top: calc(56.25% - 24px);
  left: 1.1rem;
  width: 44px; height: 44px;
  border: 1px solid var(--border);
  background: color-mix(in srgb, var(--bg) 82%, transparent);
  backdrop-filter: var(--blur);
  -webkit-backdrop-filter: var(--blur);
  z-index: 1;
}
.service-card.has-shot .service-body { padding: 1.6rem 1.35rem 1.4rem; }

@media (hover: hover) and (pointer: fine) {
  .service-card.has-shot:hover .service-shot img { transform: scale(1.05); }
}

/* --- a career entry: the organisation's own mark --- */

/* The tile keeps its size and position so the timeline's rhythm is unbroken; only
   what is inside it changes. `contain`, because a logo cropped is a logo misused. */
.tl-icon.has-logo {
  background: color-mix(in srgb, var(--bg-3) 88%, transparent);
  color: inherit;
  overflow: hidden;
  padding: 5px;
}
.tl-icon.has-logo img {
  width: 100%;
  height: 100%;
  object-fit: contain;
  display: block;
}

/* --- a skill: the tool's logo in place of the glyph --- */

/* Sized to match the glyph it replaces exactly, or the rows stop lining up. */
/* Exactly the glyph's box — .skill-icon is 0.95rem — because these sit in a row of
   names and a logo even a pixel taller pushes its line out of step with the rest. */
.skill-logo {
  width: 0.95rem;
  height: 0.95rem;
  object-fit: contain;
  flex: none;
  border-radius: 3px;
}

@media print {
  .service-shot { display: none; }
}


/* ==========================================================================
   54. The "More" menu in the header
   ==========================================================================
   Extra pages used to be appended straight onto the nav row, which grew with every
   page the owner added until it collided with the logo — and put a page link
   indistinguishably beside a section anchor. They live in here instead.

   A button and a panel, not a hover dropdown: a hover menu cannot be reached by
   touch at all, and this holds the owner's own pages.
   ========================================================================== */

.nav-more { position: relative; }

.nav-more-btn {
  display: inline-flex;
  align-items: center;
  gap: 0.3rem;
  padding: 0.35rem 0.1rem;
  border: 0;
  background: none;
  color: var(--muted);
  font: inherit;
  font-size: var(--fs-sm);
  font-weight: 560;
  cursor: pointer;
  transition: color var(--t-fast) var(--ease);
}
.nav-more-btn .ico {
  width: 0.85rem;
  height: 0.85rem;
  transition: transform var(--t-fast) var(--ease);
}
.nav-more-btn:hover { color: var(--text); }
.nav-more.is-open .nav-more-btn { color: var(--text); }
.nav-more.is-open .nav-more-btn .ico { transform: rotate(180deg); }
.nav-more-btn:focus-visible { outline: 2px solid var(--accent); outline-offset: 3px; }

.nav-more-menu {
  position: absolute;
  top: calc(100% + 0.55rem);
  /* Right-aligned: it is the last thing on the row, so opening leftwards is the
     only direction that cannot run off the screen. */
  right: 0;
  z-index: 120;
  display: grid;
  min-width: 190px;
  padding: 0.4rem;
  border: 1px solid var(--border-2);
  border-radius: var(--r);
  background: color-mix(in srgb, var(--bg-2) 94%, transparent);
  backdrop-filter: var(--blur);
  -webkit-backdrop-filter: var(--blur);
  box-shadow: var(--shadow);
  animation: nav-more-in var(--t-fast) var(--ease-out) both;
}

.nav-more-menu a {
  padding: 0.5rem 0.7rem;
  border-radius: var(--r-sm);
  font-size: var(--fs-sm);
  color: var(--text-2);
  white-space: nowrap;
  transition: background var(--t-fast) var(--ease), color var(--t-fast) var(--ease);
}
.nav-more-menu a:hover { background: var(--surface-2); color: var(--text); }

@keyframes nav-more-in {
  from { opacity: 0; transform: translateY(-4px); }
  to   { opacity: 1; transform: none; }
}

@media (prefers-reduced-motion: reduce) {
  .nav-more-menu { animation: none; }
  .nav-more-btn .ico { transition: none; }
}



/* ==========================================================================
   55. An Instagram post in the lightbox
   ==========================================================================
   Instagram's own public embed, which needs no API token. It is not a video player
   in a 16:9 box — it is a portrait card with a header, the media, and an action bar
   — so framing it like YouTube would letterbox the post inside its own chrome.

   The height is fixed because the embed only reports its natural size back to the
   page when Instagram's `embed.js` is loaded, and loading a third-party script on
   every page view to save a scrollbar is a poor trade. `scrolling="no"` plus a
   frame tall enough for a reel is the honest version of that compromise.
   ========================================================================== */

.lightbox-ig {
  display: grid;
  justify-items: center;
  gap: 0.7rem;
  width: min(400px, 92vw);
  margin-inline: auto;
}

.lightbox-ig iframe {
  width: 100%;
  /* Tall enough for a 9:16 reel plus Instagram's header and action bar, capped so
     it cannot outgrow the viewport on a short screen. */
  height: min(72svh, 660px);
  border: 0;
  border-radius: var(--r);
  background: var(--bg-2);
  display: block;
}

/* Not decoration. An embed can be refused — a private account, or a region that
   cannot load it — and then this is the only way through to the post. */
.lightbox-ig-out {
  display: inline-flex;
  align-items: center;
  gap: 0.4rem;
  font-size: var(--fs-sm);
  font-weight: 650;
  color: var(--muted);
  transition: color var(--t-fast) var(--ease);
}
.lightbox-ig-out:hover { color: var(--accent); }
.lightbox-ig-out .ico { width: 1rem; height: 1rem; }

@media (max-width: 420px) {
  .lightbox-ig iframe { height: min(66svh, 560px); }
}



/* ==========================================================================
   56. Section flow — curved seams instead of stacked boxes
   ==========================================================================
   "The portfolio is still straight from up to down." It was: every section met
   the next one on a flat horizontal line, so however good an individual section
   looked, the page as a whole read as boxes on a shelf.

   The fix is geometry at the seam, not movement. `.section-alt` already marks
   the banded sections, so curving the top and bottom edge of a band curves both
   of its seams at once — one rule reshapes the whole page.

   Why not alternate the curve direction per section with `:nth-of-type()`: any
   section can be switched off in the panel, and CSS has no way to count only the
   visible ones. An alternating rule would flip on hidden siblings and land two
   identical curves side by side, and which pairs broke would depend on which
   sections an owner had turned off — a bug that only appears on someone else's
   site. So the S is built into a single band instead: the top edge sweeps one
   way, the bottom edge the other, and every band reads as a wave no matter how
   many of its neighbours are switched on.

   Scoped to `[data-layout="flow"]` throughout. One-section-at-a-time layout
   gives each section the whole screen with the next one off-canvas, so there is
   no seam to shape.
   ========================================================================== */

html[data-layout="flow"][data-flow="wave"] .section-alt,
html[data-layout="flow"][data-flow="arc"] .section-alt {
  /* The depth of the curve. Big enough to read on a phone, capped so it does not
     eat the section on an ultrawide monitor. */
  --flow-curve: clamp(1.4rem, 4.5vw, 4.5rem);

  /* A hard top and bottom border cannot survive a curve — it would stop where
     the straight edge stops and leave the curved part unoutlined. A full border
     follows the radius the whole way round. */
  border: 1px solid var(--border);

  /* Room for the curve, so a heading never sits inside the sweep. */
  padding-block: calc(var(--section-y) + var(--flow-curve) * 0.5);

  /* Follows the radius, so the crest catches a highlight like a real edge. */
  box-shadow: inset 0 1px 0 var(--border-2);
}

/* Top-left and bottom-right take the full width, the opposite corners take
   none: the top edge sweeps down from the right, the bottom edge sweeps up from
   the left, and the band is an S rather than a pill. */
html[data-layout="flow"][data-flow="wave"] .section-alt {
  border-radius: 100% 0 100% 0 / var(--flow-curve) 0 var(--flow-curve) 0;
}

/* Both top corners claim the full width, so the browser scales them to half
   each and the edge becomes one symmetrical dome. Same at the bottom. Calmer
   than the wave, and the better choice when only a few sections are on. */
html[data-layout="flow"][data-flow="arc"] .section-alt {
  border-radius: 50% / var(--flow-curve);
}

/* Two banded sections can end up adjacent — Portfolio and Team are, when Team is
   switched on — and there the curves meet back to back with a hairline of page
   background trapped between them. Pulling the second one up by the depth of the
   curve closes that gap and turns the pair into one continuous flowing band. */
html[data-layout="flow"][data-flow="wave"] .section-alt + .section-alt,
html[data-layout="flow"][data-flow="arc"] .section-alt + .section-alt {
  margin-top: calc(var(--flow-curve) * -1);
}

/* The radial glow in the band's own background is anchored to its top edge,
   which is now a curve rather than a line. Pushing the origin down by the depth
   of the sweep puts the light back where the edge actually is. */
html[data-layout="flow"][data-flow="wave"] .section-alt,
html[data-layout="flow"][data-flow="arc"] .section-alt {
  background:
    radial-gradient(110% 80% at 50% var(--flow-curve), var(--accent-soft), transparent 60%),
    var(--bg-2);
}

/* A curved band clips its own corners, and anything that overflows them — a
   card shadow, a deck bleeding to the gutter — gets cut on the diagonal. The
   shell is inset by the curve depth at the widths where there is room to spare,
   so content stays inside the shape. Below that the gutter is already the
   minimum and the curve is shallow enough not to reach the content. */
@media (min-width: 900px) {
  html[data-layout="flow"][data-flow="wave"] .section-alt > .shell,
  html[data-layout="flow"][data-flow="arc"] .section-alt > .shell {
    padding-inline: calc(var(--gutter) + var(--flow-curve) * 0.4);
  }
}

/* Decoration that must not cost legibility: on a paper copy, and for anyone who
   has asked the browser to stop dressing things up, the seams go back to lines. */
@media print {
  html[data-layout="flow"] .section-alt {
    border-radius: 0 !important;
    margin-top: 0 !important;
    box-shadow: none !important;
  }
}



/* ==========================================================================
   57. Social account cards
   ==========================================================================
   One card per platform, each flicking through the accounts on it. The card is
   the platform; the slides are the accounts. That grouping is the whole design:
   two Instagram accounts are two slides on one card, not two cards with the same
   title, so the section does not grow every time a second account appears
   somewhere.

   The arrangement of the cards themselves is the section's own layout setting,
   which is why there are no rules here for two or three across — `[data-per-view]`
   and `[data-deck]` from sections 42 and 47 already do that, and this section was
   able to offer grid, pairs, trio, spotlight and slides without a line of new
   layout CSS.
   ========================================================================== */

.accounts-grid {
  display: grid;
  /* Wide enough that a handle and a follower count sit on one line each. Below
     two columns' worth the grid collapses to one rather than squeezing, because a
     260px card with a button in it is the point at which the button starts
     wrapping. */
  grid-template-columns: repeat(auto-fit, minmax(min(280px, 100%), 1fr));
  gap: clamp(0.8rem, 2vw, 1.4rem);
}

.account-card {
  position: relative;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: clamp(0.9rem, 2vw, 1.3rem);
  padding: clamp(1.3rem, 3vw, 2rem) clamp(1rem, 2.5vw, 1.6rem);
  border: 1px solid var(--border-2);
  border-radius: var(--r-lg);
  background:
    radial-gradient(120% 80% at 50% 0%, var(--accent-soft), transparent 62%),
    var(--bg-2);
  transition:
    transform var(--t) var(--ease),
    border-color var(--t) var(--ease),
    box-shadow var(--t) var(--ease);
}

@media (hover: hover) and (pointer: fine) {
  .account-card:hover {
    transform: translateY(-4px);
    border-color: var(--border-accent);
    box-shadow: var(--shadow-accent);
  }
}

/* --- the platform, named --- */

.account-head {
  display: flex;
  align-items: center;
  gap: 0.5rem;
}
.account-glyph {
  display: grid;
  place-items: center;
  color: var(--accent);
}
.account-glyph .ico { width: 1.15rem; height: 1.15rem; }

/* Set like a wordmark rather than a heading: it is the name of a place, and at
   this size it is what the eye lands on first. */
.account-platform {
  margin: 0;
  font-size: clamp(1.05rem, 2.6vw, 1.5rem);
  font-weight: 800;
  letter-spacing: 0.02em;
  text-transform: uppercase;
  line-height: 1;
}

/* --- the slideshow --- */

.account-window {
  position: relative;
  width: 100%;
  /* The clipping frame. Without it every slide would be visible in a row and the
     translate would simply move them all sideways. */
  overflow: hidden;
}

.account-track {
  display: flex;
  margin: 0;
  padding: 0;
  list-style: none;
  transition: transform var(--t-slow) var(--ease-out);
}

.account-slide {
  flex: 0 0 100%;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 0.45rem;
  /* Room for the arrows, which sit over the slide's left and right edges. */
  padding-inline: calc(var(--tap) * 0.75);
  text-align: center;
  /* The inactive slides are still in the flow, so they must not be readable
     through the clip on a sub-pixel rounding error. */
  opacity: 0;
  transition: opacity var(--t) var(--ease);
}
.account-slide.is-current { opacity: 1; }

.account-avatar {
  display: grid;
  place-items: center;
  width: clamp(56px, 14vw, 76px);
  aspect-ratio: 1;
  overflow: hidden;
  border-radius: 50%;
  border: 1px solid var(--border-accent);
  background: var(--bg-3);
  color: var(--accent);
}
.account-avatar img { width: 100%; height: 100%; object-fit: cover; }
.account-avatar-glyph { display: grid; place-items: center; }
.account-avatar-glyph .ico { width: 1.6rem; height: 1.6rem; }

.account-name {
  font-weight: 650;
  font-size: var(--fs-body);
  /* A long account name must not widen the card and break the grid. */
  max-width: 100%;
  overflow-wrap: anywhere;
}
.account-handle {
  font-family: var(--font-mono);
  font-size: var(--fs-xs);
  color: var(--muted);
  overflow-wrap: anywhere;
}
.account-followers {
  font-size: var(--fs-xs);
  color: var(--muted);
}
.account-followers strong {
  color: var(--text);
  font-variant-numeric: tabular-nums;
}

/* --- arrows, over the slide's edges --- */

.account-arrow {
  position: absolute;
  top: 50%;
  transform: translateY(-50%);
  display: grid;
  place-items: center;
  /* A full tap target, not a decorative circle: this is the control on a phone. */
  width: var(--tap);
  height: var(--tap);
  padding: 0;
  border: 1px solid var(--border-2);
  border-radius: 50%;
  background: var(--glass);
  backdrop-filter: var(--blur);
  -webkit-backdrop-filter: var(--blur);
  color: var(--text);
  cursor: pointer;
  transition:
    background var(--t-fast) var(--ease),
    border-color var(--t-fast) var(--ease),
    color var(--t-fast) var(--ease);
}
.account-arrow:hover {
  background: var(--accent);
  border-color: var(--accent);
  color: var(--accent-ink);
}
.account-arrow .ico { width: 1rem; height: 1rem; }
.account-prev { left: 0; }
.account-next { right: 0; }

/* --- the button, and the dots under it --- */

.account-cta {
  width: 100%;
  justify-content: center;
  border-radius: var(--r-pill);
}

.account-dots {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 0.4rem;
}
.account-dot {
  width: 7px;
  height: 7px;
  padding: 0;
  border: 0;
  border-radius: 50%;
  background: var(--border-2);
  cursor: pointer;
  transition: background var(--t-fast) var(--ease), transform var(--t-fast) var(--ease);
}
.account-dot[aria-current="true"] {
  background: var(--accent);
  transform: scale(1.35);
}

/* The dot itself is 7px, which is far below a usable tap target. The row is given
   the height instead, so the reachable area is the full band rather than the
   pixel. */
.account-dots { min-height: 2rem; }

/* --- inside a rail --- */

/* When the section is a deck the cards are laid out by the rail, so the grid's
   own column sizing has to stand down or it fights the flex basis. */
.slide-rail.accounts-grid { display: flex !important; }

/* A spotlit card is the one being read, so its slideshow controls should be the
   only ones inviting a press. */
.js .slide-rail[data-deck="spotlight"] > .account-card:not(.is-center) .account-arrow,
.js .slide-rail[data-deck="spotlight"] > .account-card:not(.is-center) .account-dots {
  opacity: 0;
  pointer-events: none;
}

@media (prefers-reduced-motion: reduce) {
  .account-track { transition: none; }
  .account-card { transition: none; }
  .account-card:hover { transform: none; }
}

/* On paper a slideshow is one slide, so the rest would simply be missing. Every
   account is laid out instead, and the controls go. */
@media print {
  .account-arrow,
  .account-dots { display: none !important; }
  .account-window { overflow: visible; }
  .account-track { display: grid; gap: 0.6rem; transform: none !important; }
  .account-slide { opacity: 1 !important; padding-inline: 0; }
}



/* The reviews section before it has any reviews. Centred under the heading, so a
   section waiting for its first review reads as an invitation rather than as
   something that failed to load. */
.reviews-empty {
  text-align: center;
  margin-inline: auto;
  color: var(--muted);
}
