/* ==========================================================================
   WebStudio — "The Standing Answer"
   Refractive violet glass on obsidian. Token-driven, no build step.
   ========================================================================== */

/* --------------------------------------------------------------------------
   1. TOKENS
   -------------------------------------------------------------------------- */
:root {
  /* Canvas — deep obsidian */
  --ink-950: #06040A;
  --ink-900: #08050E;
  --ink-850: #0A0711;
  --ink-800: #0C0814;
  --ink-700: #120C1D;
  --ink-600: #191026;

  /* Brand — neon violet */
  --violet-800: #3C0D6B;
  --violet-700: #5A189A;
  --violet-600: #7B2CBF;
  --violet-500: #9D4EDD;
  --violet-400: #C77DFF;
  --violet-300: #E0B8FF;

  /* Specular accents */
  --azure: #3A86FF;
  --magenta: #FF4FD8;

  /* Type. --text-mute clears 4.5:1 on --ink-950; do not darken it. */
  --text: #F6F1FC;
  --text-dim: #BEB1D0;
  --text-mute: #948AA6;
  --text-faint: #6B6280;

  /* Glass */
  --glass-bg: linear-gradient(
    158deg,
    rgba(255, 255, 255, 0.075) 0%,
    rgba(255, 255, 255, 0.022) 48%,
    rgba(157, 78, 221, 0.05) 100%
  );
  --glass-bg-strong: linear-gradient(
    158deg,
    rgba(255, 255, 255, 0.115) 0%,
    rgba(255, 255, 255, 0.035) 48%,
    rgba(157, 78, 221, 0.085) 100%
  );
  --glass-border: rgba(255, 255, 255, 0.13);
  --glass-border-hi: rgba(255, 255, 255, 0.26);
  --glass-blur: blur(26px) saturate(175%);
  --glass-blur-sm: blur(14px) saturate(155%);
  --glass-blur-lg: blur(44px) saturate(190%);

  /* Depth — every shadow carries an offset and a real blur. */
  --shadow-sm: 0 6px 18px rgba(0, 0, 0, 0.44);
  --shadow-md: 0 18px 44px rgba(0, 0, 0, 0.54);
  --shadow-lg: 0 34px 78px rgba(0, 0, 0, 0.62);
  --shadow-xl: 0 56px 120px rgba(0, 0, 0, 0.7);
  --shadow-rim: inset 0 1px 0 rgba(255, 255, 255, 0.16),
                inset 0 -1px 0 rgba(0, 0, 0, 0.3);
  --shadow-violet: 0 16px 44px rgba(123, 44, 191, 0.4);

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

  /* Motion */
  --ease-out: cubic-bezier(0.16, 1, 0.3, 1);
  --ease-arrive: cubic-bezier(0.22, 1, 0.36, 1);
  --ease-inout: cubic-bezier(0.65, 0, 0.35, 1);
  --dur-fast: 170ms;
  --dur: 400ms;
  --dur-slow: 820ms;

  /* Spacing */
  --s-1: 4px;
  --s-2: 8px;
  --s-3: 12px;
  --s-4: 16px;
  --s-5: 24px;
  --s-6: 32px;
  --s-7: 48px;
  --s-8: 64px;
  --s-9: 96px;
  --s-10: 132px;
  --s-11: 184px;

  /* Layout */
  --container: 1120px;
  --gutter: clamp(20px, 5vw, 40px);
  --measure: 66ch;
  --nav-h: 66px;

  /* Type */
  --font: "Archivo", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto,
    "Helvetica Neue", Arial, sans-serif;
  --font-mono: "Azeret Mono", ui-monospace, SFMono-Regular, "SF Mono", Menlo,
    Consolas, monospace;

  --t-display: clamp(2.7rem, 6.6vw, 5.2rem);
  --t-h1: clamp(2.15rem, 4.6vw, 3.4rem);
  --t-h2: clamp(1.6rem, 3vw, 2.3rem);
  --t-h3: clamp(1.1rem, 1.6vw, 1.3rem);
  --t-body: 1.0625rem;
  --t-sm: 0.9375rem;
  --t-xs: 0.8125rem;
  --t-mono: 0.78125rem;
}

/* --------------------------------------------------------------------------
   2. RESET
   -------------------------------------------------------------------------- */
*, *::before, *::after { box-sizing: border-box; }

html {
  -webkit-text-size-adjust: 100%;
  scroll-behavior: smooth;
}
@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }
}

body {
  margin: 0;
  background: var(--ink-950);
  color: var(--text);
  font-family: var(--font);
  font-size: var(--t-body);
  font-variation-settings: "wdth" 100;
  line-height: 1.62;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  overflow-x: hidden;
}

h1, h2, h3, h4, p, figure, blockquote, ul, ol, dl, dd { margin: 0; }
ul, ol { padding: 0; list-style: none; }
img, svg, canvas { display: block; max-width: 100%; }
img { height: auto; }
button, input, select, textarea { font: inherit; color: inherit; }
a { color: inherit; text-decoration: none; }

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

::selection { background: var(--violet-600); color: #fff; }

.skip-link {
  position: fixed;
  top: var(--s-3);
  left: var(--s-3);
  z-index: 200;
  padding: var(--s-3) var(--s-5);
  background: var(--violet-600);
  color: #fff;
  border-radius: var(--r-pill);
  font-size: var(--t-sm);
  font-weight: 600;
  transform: translateY(calc(-100% - 24px));
  transition: transform var(--dur) var(--ease-out);
}
.skip-link:focus-visible { transform: none; }

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

/* --------------------------------------------------------------------------
   3. SCENE CANVAS
   -------------------------------------------------------------------------- */
#scene-canvas {
  position: fixed;
  inset: 0;
  width: 100%;
  height: 100%;
  z-index: 0;
  pointer-events: none;
  opacity: 0;
  transition: opacity 900ms var(--ease-out);
}
#scene-canvas.is-ready { opacity: 1; }

/* Standing light fields. These are the room the glass sits in — without them
   the obsidian reads as flat black and the glass has nothing to refract. */
body::before {
  content: "";
  position: fixed;
  inset: -20% -10% auto -10%;
  height: 90vh;
  z-index: 0;
  pointer-events: none;
  background:
    radial-gradient(50% 60% at 22% 12%, rgba(123, 44, 191, 0.36), transparent 68%),
    radial-gradient(42% 52% at 84% 4%, rgba(58, 134, 255, 0.16), transparent 70%);
  filter: blur(28px);
}
body::after {
  content: "";
  position: fixed;
  inset: auto -10% -25% -10%;
  height: 80vh;
  z-index: 0;
  pointer-events: none;
  background:
    radial-gradient(46% 58% at 74% 88%, rgba(157, 78, 221, 0.26), transparent 70%),
    radial-gradient(38% 46% at 12% 96%, rgba(255, 79, 216, 0.1), transparent 72%);
  filter: blur(34px);
}

/* --------------------------------------------------------------------------
   4. CONTAINER
   -------------------------------------------------------------------------- */
.page {
  position: relative;
  z-index: 1;
}

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

.container-narrow {
  width: 100%;
  max-width: 56rem;
  margin: 0 auto;
}

/* --------------------------------------------------------------------------
   5. NAVIGATION
   -------------------------------------------------------------------------- */
.nav {
  position: sticky;
  top: 0;
  z-index: 80;
  height: var(--nav-h);
  border-bottom: 1px solid transparent;
  transition: border-color var(--dur) var(--ease-out),
              background-color var(--dur) var(--ease-out);
}

/* The bar is transparent over the hero and resolves into glass once the page
   has moved, so the first viewport stays uninterrupted. */
.nav.is-scrolled {
  border-bottom-color: rgba(255, 255, 255, 0.08);
  background: rgba(6, 4, 10, 0.72);
  -webkit-backdrop-filter: var(--glass-blur-sm);
  backdrop-filter: var(--glass-blur-sm);
}

/* While the sheet is open the bar has to be filled too — at scroll 0 it is
   transparent, so an open menu hung off a see-through header.

   It gets a SOLID colour and deliberately NO backdrop-filter: a filter here
   would make .nav a backdrop root, and its own child .nav-sheet would then
   have nothing left to sample, so the sheet's blur silently dies and the hero
   ghosts straight through the open menu. Opaque bar, blurred sheet. */
.nav.has-sheet {
  border-bottom-color: transparent;
  background: #08050e;
}

.nav-inner {
  display: flex;
  align-items: center;
  gap: var(--s-6);
  height: 100%;
  max-width: var(--container);
  margin: 0 auto;
  padding: 0 var(--gutter);
}

.brand {
  display: flex;
  align-items: center;
  gap: var(--s-3);
  font-size: 1.0625rem;
  font-weight: 700;
  letter-spacing: -0.022em;
  font-variation-settings: "wdth" 108;
  flex: none;
}
.brand .mark { width: 26px; height: 26px; flex: none; }
.brand-sub { color: var(--violet-400); }

.nav-links {
  display: none;
  align-items: center;
  gap: var(--s-6);
  margin-right: auto;
}
@media (min-width: 860px) {
  .nav-links { display: flex; }
}

.nav-links a {
  position: relative;
  font-size: var(--t-sm);
  font-weight: 500;
  color: var(--text-mute);
  transition: color var(--dur-fast) var(--ease-out);
}
.nav-links a:hover { color: var(--text); }
.nav-links a[aria-current="page"] { color: var(--text); }
.nav-links a[aria-current="page"]::after {
  content: "";
  position: absolute;
  left: 0;
  right: 0;
  bottom: -6px;
  height: 1.5px;
  border-radius: 2px;
  background: var(--violet-400);
}

.nav-actions {
  display: flex;
  align-items: center;
  gap: var(--s-3);
  margin-left: auto;
}
@media (min-width: 860px) { .nav-actions { margin-left: 0; } }

.btn.nav-cta { display: none; }
@media (min-width: 620px) { .btn.nav-cta { display: inline-flex; } }

.nav-toggle {
  display: grid;
  place-items: center;
  width: 40px;
  height: 40px;
  padding: 0;
  border: 1px solid var(--glass-border);
  border-radius: var(--r-sm);
  background: rgba(255, 255, 255, 0.05);
  cursor: pointer;
}
@media (min-width: 860px) { .nav-toggle { display: none; } }

/* All three bars share ONE grid cell. They were previously three grid rows
   with a gap, so rotating the outer two could never make them meet — the
   close state rendered as a broken arrow instead of an X. Stacking them and
   spacing with translate keeps the hamburger AND lets the X actually cross. */
.nav-toggle span {
  grid-area: 1 / 1;
  display: block;
  width: 16px;
  height: 1.5px;
  border-radius: 2px;
  background: var(--text);
  transition: transform var(--dur) var(--ease-arrive),
              opacity var(--dur-fast) var(--ease-out);
}
.nav-toggle span:nth-child(1) { transform: translateY(-5px); }
.nav-toggle span:nth-child(3) { transform: translateY(5px); }
.nav-toggle[aria-expanded="true"] span:nth-child(1) { transform: rotate(45deg); }
.nav-toggle[aria-expanded="true"] span:nth-child(2) { opacity: 0; }
.nav-toggle[aria-expanded="true"] span:nth-child(3) { transform: rotate(-45deg); }

.nav-sheet {
  position: fixed;
  inset: var(--nav-h) 0 auto 0;
  z-index: 79;
  display: grid;
  gap: var(--s-2);
  padding: var(--s-5) var(--gutter) var(--s-6);
  border-bottom: 1px solid rgba(255, 255, 255, 0.1);
  background: rgba(8, 5, 14, 0.94);
  -webkit-backdrop-filter: var(--glass-blur);
  backdrop-filter: var(--glass-blur);
  box-shadow: var(--shadow-lg);
  transform: translateY(-8px);
  opacity: 0;
  visibility: hidden;
  transition: transform var(--dur) var(--ease-arrive),
              opacity var(--dur-fast) var(--ease-out),
              visibility 0s linear var(--dur);
}
.nav-sheet.is-open {
  transform: none;
  opacity: 1;
  visibility: visible;
  transition-delay: 0s;
}
@media (min-width: 860px) {
  .nav-sheet,
  .nav-sheet.is-open { display: none; }
}

.nav-sheet a {
  padding: var(--s-3) 0;
  font-size: 1.0625rem;
  font-weight: 600;
  letter-spacing: -0.016em;
  border-bottom: 1px solid rgba(255, 255, 255, 0.07);
}
.nav-sheet a:last-of-type { border-bottom: 0; }
.nav-sheet .btn { margin-top: var(--s-3); }

/* --------------------------------------------------------------------------
   7. TYPE
   -------------------------------------------------------------------------- */
.h-display {
  font-size: var(--t-display);
  font-weight: 700;
  line-height: 1.02;
  letter-spacing: -0.038em;
  font-variation-settings: "wdth" 112;
  text-wrap: balance;
}

.h1 {
  font-size: var(--t-h1);
  font-weight: 700;
  line-height: 1.08;
  letter-spacing: -0.032em;
  font-variation-settings: "wdth" 110;
  text-wrap: balance;
}

.h2 {
  font-size: var(--t-h2);
  font-weight: 700;
  line-height: 1.14;
  letter-spacing: -0.028em;
  font-variation-settings: "wdth" 108;
  text-wrap: balance;
}

.h3 {
  font-size: var(--t-h3);
  font-weight: 650;
  line-height: 1.28;
  letter-spacing: -0.018em;
}

.lede {
  font-size: clamp(1.0625rem, 1.5vw, 1.25rem);
  line-height: 1.58;
  color: var(--text-dim);
  max-width: 46ch;
}

.prose { max-width: var(--measure); color: var(--text-dim); }
.prose + .prose { margin-top: var(--s-4); }

.dim { color: var(--text-mute); }

.mono {
  font-family: var(--font-mono);
  font-size: var(--t-mono);
  font-weight: 400;
  letter-spacing: 0.02em;
  color: var(--text-mute);
}

/* --------------------------------------------------------------------------
   8. SECTIONS
   -------------------------------------------------------------------------- */
/* Positioned so each section is the containing block for its own
   .gem-anchor — otherwise the anchors resolve against .page and the
   whole rail collapses to one spot. */
.section { position: relative; padding-block: var(--s-10); }
.section-tight { padding-block: var(--s-9); }
/* `relative` so a .gem-anchor inside can position against the heading. */
.section-head { position: relative; margin-bottom: var(--s-8); }
.section-head .h2 + .prose { margin-top: var(--s-4); }

.rule {
  height: 1px;
  border: 0;
  margin: 0;
  background: linear-gradient(
    90deg,
    rgba(255, 255, 255, 0.16),
    rgba(255, 255, 255, 0.03) 62%,
    transparent
  );
}

/* --------------------------------------------------------------------------
   9. BUTTONS
   -------------------------------------------------------------------------- */
.btn {
  position: relative;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--s-3);
  padding: 15px var(--s-6);
  border: 1px solid transparent;
  border-radius: var(--r-pill);
  font-size: var(--t-sm);
  font-weight: 650;
  letter-spacing: -0.012em;
  line-height: 1;
  cursor: pointer;
  white-space: nowrap;
  transition: transform var(--dur-fast) var(--ease-out),
              box-shadow var(--dur) var(--ease-out),
              background-color var(--dur-fast) var(--ease-out),
              border-color var(--dur-fast) var(--ease-out);
}
.btn:active { transform: translateY(1px) scale(0.99); }

.btn-primary {
  background: linear-gradient(180deg, var(--violet-500), var(--violet-700));
  border-color: rgba(224, 184, 255, 0.4);
  color: #fff;
  box-shadow: var(--shadow-violet), var(--shadow-rim);
}
.btn-primary:hover {
  background: linear-gradient(180deg, var(--violet-400), var(--violet-600));
  box-shadow: 0 22px 56px rgba(123, 44, 191, 0.5), var(--shadow-rim);
}

.btn-ghost {
  background: rgba(255, 255, 255, 0.05);
  border-color: var(--glass-border);
  color: var(--text);
  -webkit-backdrop-filter: var(--glass-blur-sm);
  backdrop-filter: var(--glass-blur-sm);
}
.btn-ghost:hover {
  background: rgba(255, 255, 255, 0.1);
  border-color: var(--glass-border-hi);
}

.btn-lg { padding: 18px var(--s-7); font-size: var(--t-body); }
.btn-block { width: 100%; }

.btn .ico { width: 17px; height: 17px; flex: none; }
.btn .arrow {
  transition: transform var(--dur) var(--ease-arrive);
}
.btn:hover .arrow { transform: translateX(4px); }

.btn-row {
  display: flex;
  flex-wrap: wrap;
  gap: var(--s-3);
  align-items: center;
}

/* Underlined text link with a real hover travel. */
.link {
  position: relative;
  font-weight: 550;
  color: var(--violet-400);
  padding-bottom: 2px;
  background-image: linear-gradient(currentColor, currentColor);
  background-size: 100% 1px;
  background-repeat: no-repeat;
  background-position: 0 100%;
  transition: color var(--dur-fast) var(--ease-out),
              background-size var(--dur) var(--ease-out);
}
.link:hover { color: var(--violet-300); background-size: 0% 1px; }

/* --------------------------------------------------------------------------
   10. GLASS SURFACES
   -------------------------------------------------------------------------- */
.glass {
  position: relative;
  border: 1px solid var(--glass-border);
  border-radius: var(--r-lg);
  background: var(--glass-bg);
  -webkit-backdrop-filter: var(--glass-blur);
  backdrop-filter: var(--glass-blur);
  box-shadow: var(--shadow-md), var(--shadow-rim);
}

@supports not ((backdrop-filter: blur(1px)) or (-webkit-backdrop-filter: blur(1px))) {
  .glass, .nav.is-scrolled, .nav-sheet, .btn-ghost {
    background: var(--ink-800);
  }
  .glass { background: var(--ink-700); }
}

/* --------------------------------------------------------------------------
   11. HERO
   -------------------------------------------------------------------------- */
/* The hero is exactly one viewport tall and the stage takes whatever the copy
   leaves, so the mark is WHOLLY visible the moment the page opens instead of
   being cropped by the fold. Its size follows from that rect — the scene
   contains the mark inside it — so this rule alone governs how big it lands. */
.hero {
  position: relative;
  display: flex;
  flex-direction: column;
  min-height: calc(100svh - var(--nav-h));
  padding-block: clamp(var(--s-4), 2.5vw, var(--s-6)) var(--s-6);
  text-align: center;
}

.hero-copy {
  max-width: 44rem;
  margin: 0 auto;
}
.hero .lede { margin-inline: auto; max-width: 40rem; }
.hero .btn-row { justify-content: center; }

.hero-stage {
  position: relative;
  flex: 1 1 auto;
  width: min(100%, 460px);
  min-height: 190px;
  margin: clamp(var(--s-3), 1.8vw, var(--s-4)) auto 0;
}

/* Where the mark travels after the hero.

   These boxes never paint anything — the scene reads their rects and flies the
   mark between them. Each one now lives INSIDE the thing it should relate to
   (a section's heading, the steps list, a glass panel) rather than floating at
   an arbitrary point in the section. That matters: when every anchor was
   `top: 50%` of its own section, every anchor landed at the vertical middle of
   the viewport, and the mark spent the whole page sliding along one horizontal
   line. Anchored to content, its height varies with what it is beside, and it
   reads as arriving at things instead of drifting past them.

   Absolutely positioned, so they cost the layout nothing. */
.gem-anchor {
  position: absolute;
  pointer-events: none;
  aspect-ratio: 1 / 1;
}

/* Beside a section heading: the mark sits out in the gutter, level with the
   line it is marking. */
.gem-anchor.at-head-left,
.gem-anchor.at-head-right {
  top: 50%;
  width: min(24vw, 210px);
  transform: translateY(-50%);
}
.gem-anchor.at-head-left { right: calc(100% - 2rem); }
.gem-anchor.at-head-right { left: calc(100% - 2rem); }

/* Alongside the numbered steps, riding down the list. */
.gem-anchor.at-list-left {
  top: 50%;
  width: min(22vw, 190px);
  right: calc(100% - 1rem);
  transform: translateY(-50%);
}

/* Behind a glass panel, working as a backlight. The panel's own backdrop-filter
   blurs the mark through it, which is the most flattering thing that happens to
   it anywhere on the page. */
.gem-anchor.at-panel {
  top: 50%;
  left: 50%;
  width: min(62%, 460px);
  transform: translate(-50%, -50%);
}

/* On a phone there is no gutter to sit in. The heading and list anchors move
   behind their own content, centred and smaller, and the panel anchors stay
   where they are. */
@media (max-width: 860px) {
  .gem-anchor.at-head-left,
  .gem-anchor.at-head-right,
  .gem-anchor.at-list-left {
    left: 50%;
    right: auto;
    width: min(52vw, 230px);
    transform: translate(-50%, -50%);
  }
}
/* 2D stand-in the WebGL gem replaces. Kept visible for the lite/static tiers. */
.mark-2d {
  display: grid;
  place-items: center;
  width: 100%;
  height: 100%;
  filter: drop-shadow(0 30px 60px rgba(123, 44, 191, 0.5));
  animation: mark-float 9s var(--ease-inout) infinite;
}
.mark-2d svg { width: 62%; height: auto; }
body.is-3d .mark-2d { display: none; }
@keyframes mark-float {
  0%, 100% { transform: translateY(0) rotate(-2deg); }
  50%      { transform: translateY(-16px) rotate(2deg); }
}

.hero-meta {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: center;
  gap: var(--s-3) var(--s-5);
  margin-top: var(--s-6);
}
.hero-meta .mono { display: inline-flex; align-items: center; gap: var(--s-2); }
.hero-meta .dot {
  width: 5px; height: 5px;
  border-radius: 50%;
  background: var(--violet-400);
  box-shadow: 0 0 0 3px rgba(157, 78, 221, 0.22);
}

/* --------------------------------------------------------------------------
   12. WORK — the proof, at scale. Not cards.
   -------------------------------------------------------------------------- */
.work-list { display: grid; gap: var(--s-10); }

.work-item {
  display: grid;
  gap: var(--s-5);
}

.work-shot {
  position: relative;
  border-radius: var(--r-lg);
  overflow: hidden;
  border: 1px solid var(--glass-border);
  background: var(--ink-800);
  box-shadow: var(--shadow-lg);
  aspect-ratio: 16 / 10;
  transition: transform var(--dur-slow) var(--ease-out),
              box-shadow var(--dur-slow) var(--ease-out),
              border-color var(--dur) var(--ease-out);
}
.work-shot img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  object-position: top center;
  transition: transform 1100ms var(--ease-out);
}
.work-item:hover .work-shot {
  transform: translateY(-6px);
  border-color: var(--glass-border-hi);
  box-shadow: var(--shadow-xl), 0 0 0 1px rgba(157, 78, 221, 0.3);
}
.work-item:hover .work-shot img { transform: scale(1.028); }

/* The violet cast the site sits under, lifted on hover so the real work
   shows its own colour. */
.work-shot::after {
  content: "";
  position: absolute;
  inset: 0;
  pointer-events: none;
  background: linear-gradient(
    172deg,
    rgba(6, 4, 10, 0) 42%,
    rgba(6, 4, 10, 0.55) 100%
  ),
  linear-gradient(0deg, rgba(90, 24, 154, 0.22), rgba(90, 24, 154, 0.22));
  transition: opacity var(--dur-slow) var(--ease-out);
}
.work-item:hover .work-shot::after { opacity: 0.35; }

.work-head {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--s-3) var(--s-5);
}
.work-name {
  font-size: clamp(1.4rem, 2.6vw, 1.95rem);
  font-weight: 700;
  letter-spacing: -0.028em;
  font-variation-settings: "wdth" 108;
  line-height: 1.12;
}
.work-url {
  font-family: var(--font-mono);
  font-size: var(--t-mono);
  color: var(--text-mute);
  transition: color var(--dur-fast) var(--ease-out);
}
.work-item:hover .work-url { color: var(--violet-400); }

.work-body { max-width: 58ch; color: var(--text-dim); }

.work-tags {
  display: flex;
  flex-wrap: wrap;
  gap: var(--s-2);
  margin-top: var(--s-4);
}
.tag {
  padding: 5px var(--s-3);
  border: 1px solid rgba(255, 255, 255, 0.12);
  border-radius: var(--r-pill);
  background: rgba(255, 255, 255, 0.04);
  font-family: var(--font-mono);
  font-size: 0.6875rem;
  letter-spacing: 0.03em;
  color: var(--text-dim);
}

/* --------------------------------------------------------------------------
   13. INDEX LIST — services as an editorial index, not a card grid
   -------------------------------------------------------------------------- */
.index-list { border-top: 1px solid rgba(255, 255, 255, 0.1); }

.index-row {
  display: grid;
  grid-template-columns: 1fr;
  gap: var(--s-2);
  padding-block: var(--s-6);
  border-bottom: 1px solid rgba(255, 255, 255, 0.1);
}
@media (min-width: 720px) {
  .index-row {
    grid-template-columns: minmax(0, 15rem) minmax(0, 1fr);
    gap: var(--s-7);
    align-items: start;
  }
}
.index-row .h3 { font-variation-settings: "wdth" 106; }
.index-row p { color: var(--text-dim); max-width: 52ch; }

/* --------------------------------------------------------------------------
   14. PROCESS — numbered because the order is the information
   -------------------------------------------------------------------------- */
/* `relative` so a .gem-anchor inside can position against the list. */
.steps { position: relative; display: grid; gap: var(--s-5); counter-reset: step; }
@media (min-width: 860px) {
  .steps { grid-template-columns: repeat(4, 1fr); gap: var(--s-4); }
}

.step {
  position: relative;
  padding: var(--s-6) var(--s-5) var(--s-5);
  border-radius: var(--r-md);
  border: 1px solid rgba(255, 255, 255, 0.1);
  background: rgba(255, 255, 255, 0.028);
}
.step::before {
  counter-increment: step;
  content: counter(step);
  display: grid;
  place-items: center;
  width: 26px; height: 26px;
  margin-bottom: var(--s-4);
  border-radius: 50%;
  background: rgba(157, 78, 221, 0.2);
  border: 1px solid rgba(199, 125, 255, 0.4);
  font-family: var(--font-mono);
  font-size: 0.6875rem;
  font-weight: 500;
  color: var(--violet-300);
}
.step h3 {
  font-size: var(--t-sm);
  font-weight: 650;
  letter-spacing: -0.012em;
  margin-bottom: var(--s-2);
}
.step p { font-size: var(--t-sm); color: var(--text-mute); line-height: 1.5; }

/* --------------------------------------------------------------------------
   15. PRICE
   -------------------------------------------------------------------------- */
.price-panel {
  padding: clamp(var(--s-6), 4vw, var(--s-9));
  border-radius: var(--r-xl);
  overflow: hidden;
}

.price-figure {
  display: flex;
  align-items: baseline;
  gap: var(--s-3);
  flex-wrap: wrap;
}
.price-amount {
  font-size: clamp(3.2rem, 9vw, 5.6rem);
  font-weight: 700;
  line-height: 0.95;
  letter-spacing: -0.045em;
  font-variation-settings: "wdth" 108;
}
.price-ccy {
  font-family: var(--font-mono);
  font-size: clamp(0.875rem, 1.6vw, 1.05rem);
  font-weight: 500;
  letter-spacing: 0.06em;
  color: var(--violet-400);
}

/* Two currencies, both of them real prices. The USD figure is set smaller so
   the panel still has one focal point, but it is set as a PRICE — not as a
   parenthetical — because an overseas visitor is reading it as the number
   they will actually pay. */
.price-pair {
  display: inline-flex;
  align-items: baseline;
  gap: var(--s-3);
}
.price-alt {
  padding-left: var(--s-5);
  border-left: 1px solid rgba(255, 255, 255, 0.16);
}
.price-alt .price-amount {
  font-size: clamp(1.75rem, 4.4vw, 2.6rem);
  letter-spacing: -0.035em;
  color: var(--text-dim);
}
.price-alt .price-ccy { color: var(--violet-500); }

/* The monthly is a real price but not the page's headline one, so it is set
   at a smaller size than the starter figure it sits beneath. */
.price-figure-sm .price-amount { font-size: clamp(2.1rem, 5vw, 3rem); }
.price-figure-sm .price-alt .price-amount { font-size: clamp(1.4rem, 3.2vw, 1.9rem); }
.price-figure-sm { margin-bottom: var(--s-5); }

/* Once it wraps onto its own line the vertical rule is pointing at nothing. */
@media (max-width: 560px) {
  .price-alt {
    padding-left: 0;
    border-left: 0;
    padding-top: var(--s-2);
  }
}

.price-includes {
  display: grid;
  gap: var(--s-3);
  margin-top: var(--s-7);
}
.price-includes li {
  display: grid;
  grid-template-columns: 20px 1fr;
  gap: var(--s-3);
  align-items: start;
  color: var(--text-dim);
  font-size: var(--t-sm);
}
.price-includes .ico { width: 18px; height: 18px; margin-top: 3px; color: var(--violet-400); }

.fineprint {
  margin-top: var(--s-7);
  padding-top: var(--s-5);
  border-top: 1px solid rgba(255, 255, 255, 0.1);
  font-size: var(--t-xs);
  line-height: 1.62;
  color: var(--text-mute);
  max-width: 62ch;
}
.fineprint strong { color: var(--text-dim); font-weight: 600; }


/* --------------------------------------------------------------------------
   16. FORM
   -------------------------------------------------------------------------- */
.form { display: grid; gap: var(--s-5); }

.field { display: grid; gap: var(--s-2); }
.field label {
  font-size: var(--t-sm);
  font-weight: 600;
  letter-spacing: -0.008em;
}
.field .hint { font-size: var(--t-xs); color: var(--text-mute); }

.input, .select, .textarea {
  width: 100%;
  padding: 14px var(--s-4);
  border: 1px solid rgba(255, 255, 255, 0.14);
  border-radius: var(--r-sm);
  background: rgba(255, 255, 255, 0.045);
  color: var(--text);
  font-size: var(--t-body);
  line-height: 1.5;
  transition: border-color var(--dur-fast) var(--ease-out),
              background-color var(--dur-fast) var(--ease-out),
              box-shadow var(--dur-fast) var(--ease-out);
}
.textarea { min-height: 132px; resize: vertical; }
.input::placeholder, .textarea::placeholder { color: var(--text-mute); }

.input:hover, .select:hover, .textarea:hover {
  border-color: rgba(255, 255, 255, 0.24);
}
.input:focus, .select:focus, .textarea:focus {
  outline: none;
  border-color: var(--violet-400);
  background: rgba(157, 78, 221, 0.08);
  box-shadow: 0 0 0 3px rgba(157, 78, 221, 0.2);
}

.select {
  appearance: none;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='none' stroke='%23948AA6' stroke-width='1.6' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M4 6.5 8 10.5 12 6.5'/%3E%3C/svg%3E");
  background-repeat: no-repeat;
  background-position: right var(--s-4) center;
  background-size: 16px;
  padding-right: var(--s-8);
}
.select option { background: var(--ink-800); color: var(--text); }

.field-error {
  min-height: 1.15em;
  font-size: var(--t-xs);
  color: #FF9BB8;
}
.field.has-error .input,
.field.has-error .select,
.field.has-error .textarea {
  border-color: #FF6E92;
  background: rgba(255, 110, 146, 0.07);
}

.grid-2 { display: grid; gap: var(--s-5); }
@media (min-width: 620px) { .grid-2 { grid-template-columns: 1fr 1fr; } }

.honeypot {
  position: absolute;
  left: -9999px;
  width: 1px; height: 1px;
  opacity: 0;
  pointer-events: none;
}

/* Live preview of the exact WhatsApp message that will be sent. */
.preview {
  padding: var(--s-5);
  border-radius: var(--r-md);
  border: 1px solid rgba(255, 255, 255, 0.1);
  background: rgba(6, 4, 10, 0.45);
}
.preview-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--s-4);
  margin-bottom: var(--s-3);
}
.preview-body {
  font-family: var(--font-mono);
  font-size: var(--t-mono);
  line-height: 1.72;
  color: var(--text-dim);
  white-space: pre-wrap;
  word-break: break-word;
  max-height: 15em;
  overflow-y: auto;
}
.preview-body .ph { color: var(--text-mute); }

.form-note {
  font-size: var(--t-xs);
  color: var(--text-mute);
  line-height: 1.6;
}

.confirm {
  padding: var(--s-7) var(--s-6);
  text-align: center;
}
.confirm .h2 { margin-bottom: var(--s-3); }
.confirm p { color: var(--text-dim); max-width: 44ch; margin: 0 auto var(--s-6); }
.confirm-mark {
  width: 54px; height: 54px;
  margin: 0 auto var(--s-5);
  display: grid;
  place-items: center;
  border-radius: 50%;
  background: rgba(157, 78, 221, 0.18);
  border: 1px solid rgba(199, 125, 255, 0.45);
  color: var(--violet-300);
}

/* --------------------------------------------------------------------------
   17. CLOSE + FOOTER
   -------------------------------------------------------------------------- */
.close-panel {
  position: relative;
  padding: clamp(var(--s-7), 5vw, var(--s-10)) clamp(var(--s-5), 4vw, var(--s-9));
  border-radius: var(--r-xl);
  overflow: hidden;
  text-align: left;
}
.close-panel .h1 { max-width: 16ch; }
.close-panel .lede { margin-top: var(--s-4); }
.close-panel .btn-row { margin-top: var(--s-7); }

.orb {
  position: absolute;
  border-radius: 50%;
  pointer-events: none;
  filter: blur(46px);
}
.orb-a {
  width: 340px; height: 340px;
  top: -140px; right: -80px;
  background: radial-gradient(circle, rgba(157, 78, 221, 0.5), transparent 68%);
}
.orb-b {
  width: 280px; height: 280px;
  bottom: -130px; left: -70px;
  background: radial-gradient(circle, rgba(58, 134, 255, 0.3), transparent 68%);
}

.footer {
  padding-block: var(--s-8);
  border-top: 1px solid rgba(255, 255, 255, 0.08);
  margin-top: var(--s-9);
}
.footer-inner {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: var(--s-5);
}
.footer-links { display: flex; flex-wrap: wrap; gap: var(--s-4) var(--s-6); }
.footer-links a {
  font-size: var(--t-sm);
  color: var(--text-mute);
  transition: color var(--dur-fast) var(--ease-out);
}
.footer-links a:hover { color: var(--text); }
.footer small { font-size: var(--t-xs); color: var(--text-mute); }

.social {
  display: flex;
  gap: var(--s-2);
}
.social a {
  display: grid;
  place-items: center;
  width: 38px; height: 38px;
  border-radius: 50%;
  border: 1px solid rgba(255, 255, 255, 0.12);
  background: rgba(255, 255, 255, 0.04);
  color: var(--text-dim);
  transition: color var(--dur-fast) var(--ease-out),
              border-color var(--dur-fast) var(--ease-out),
              background-color var(--dur-fast) var(--ease-out);
}
.social a:hover {
  color: var(--text);
  border-color: var(--glass-border-hi);
  background: rgba(255, 255, 255, 0.09);
}
.social .ico { width: 17px; height: 17px; }

/* --------------------------------------------------------------------------
   18. MOTION — one authored entrance, from an already-visible default
   -------------------------------------------------------------------------- */
[data-reveal] {
  opacity: 1;
}
.js [data-reveal] {
  opacity: 0;
  transform: translateY(18px);
  filter: blur(6px);
  transition:
    opacity 760ms var(--ease-out) var(--reveal-delay, 0ms),
    transform 900ms var(--ease-arrive) var(--reveal-delay, 0ms),
    filter 760ms var(--ease-out) var(--reveal-delay, 0ms);
}
.js [data-reveal].is-in {
  opacity: 1;
  transform: none;
  filter: none;
}

[data-parallax] { will-change: transform; }

@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: 0.001ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.001ms !important;
  }
  .js [data-reveal] { opacity: 1; transform: none; filter: none; }
  #scene-canvas { display: none; }
  body::before, body::after { filter: none; }
}

/* --------------------------------------------------------------------------
   19. FLOW
   -------------------------------------------------------------------------- */
.lede + .btn-row { margin-top: var(--s-7); }
.h-display + .lede { margin-top: var(--s-5); }
.h1 + .lede, .h2 + .lede { margin-top: var(--s-4); }

/* --------------------------------------------------------------------------
   20. PAGE HEAD
   -------------------------------------------------------------------------- */
.page-head {
  padding-block: var(--s-9) var(--s-7);
}
@media (min-width: 860px) {
  .page-head { padding-block: var(--s-10) var(--s-7); }
}
.page-head .lede { max-width: 52ch; }

/* --------------------------------------------------------------------------
   21. CASE STUDIES
   -------------------------------------------------------------------------- */
.case-list { display: grid; gap: var(--s-11); }

.case { display: grid; gap: var(--s-6); }

.case-shot {
  position: relative;
  display: block;
  border-radius: var(--r-lg);
  overflow: hidden;
  border: 1px solid var(--glass-border);
  background: var(--ink-800);
  box-shadow: var(--shadow-lg);
  aspect-ratio: 16 / 10;
  transition: transform var(--dur-slow) var(--ease-out),
              box-shadow var(--dur-slow) var(--ease-out),
              border-color var(--dur) var(--ease-out);
}
.case-shot img {
  width: 100%; height: 100%;
  object-fit: cover;
  object-position: top center;
  transition: transform 1100ms var(--ease-out);
}
.case-shot::after {
  content: "";
  position: absolute;
  inset: 0;
  pointer-events: none;
  background: linear-gradient(0deg, rgba(90, 24, 154, 0.2), rgba(90, 24, 154, 0.2));
  transition: opacity var(--dur-slow) var(--ease-out);
}
.case-shot:hover {
  transform: translateY(-6px);
  border-color: var(--glass-border-hi);
  box-shadow: var(--shadow-xl), 0 0 0 1px rgba(157, 78, 221, 0.3);
}
.case-shot:hover img { transform: scale(1.026); }
.case-shot:hover::after { opacity: 0; }

.case-head {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--s-2) var(--s-5);
}
.case-sector { margin-top: var(--s-2); }

.case-cols {
  display: grid;
  gap: var(--s-6);
  margin-top: var(--s-6);
}
@media (min-width: 760px) {
  .case-cols { grid-template-columns: 1fr 1fr; gap: var(--s-7); }
}
.case-cols p { color: var(--text-dim); }

.case-label {
  font-family: var(--font-mono);
  font-size: var(--t-mono);
  font-weight: 500;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: var(--violet-400);
  margin-bottom: var(--s-3);
}

.case-did { display: grid; gap: var(--s-3); }
.case-did li {
  position: relative;
  padding-left: var(--s-5);
  color: var(--text-dim);
  font-size: var(--t-sm);
  line-height: 1.58;
}
.case-did li::before {
  content: "";
  position: absolute;
  left: 0; top: 0.62em;
  width: 6px; height: 6px;
  border-radius: 50%;
  background: var(--violet-500);
}
.case-did em { color: var(--text); font-style: italic; }

.case-foot {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: var(--s-4);
  margin-top: var(--s-7);
  padding-top: var(--s-5);
  border-top: 1px solid rgba(255, 255, 255, 0.09);
}
.case-foot .work-tags { margin-top: 0; }
.case-stack .tag {
  border-color: rgba(157, 78, 221, 0.32);
  background: rgba(157, 78, 221, 0.1);
  color: var(--violet-300);
}

/* --------------------------------------------------------------------------
   22. FAQ
   -------------------------------------------------------------------------- */
.faq-list { border-top: 1px solid rgba(255, 255, 255, 0.1); }

.faq { border-bottom: 1px solid rgba(255, 255, 255, 0.1); }
.faq summary {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--s-5);
  padding-block: var(--s-5);
  cursor: pointer;
  list-style: none;
  font-size: var(--t-body);
  font-weight: 600;
  letter-spacing: -0.014em;
  transition: color var(--dur-fast) var(--ease-out);
}
.faq summary::-webkit-details-marker { display: none; }
.faq summary:hover { color: var(--violet-300); }

.faq-sign {
  position: relative;
  flex: none;
  width: 16px; height: 16px;
}
.faq-sign::before, .faq-sign::after {
  content: "";
  position: absolute;
  inset: 50% 0 auto 0;
  height: 1.5px;
  border-radius: 2px;
  background: var(--violet-400);
  transform: translateY(-50%);
  transition: transform var(--dur) var(--ease-arrive), opacity var(--dur) var(--ease-out);
}
.faq-sign::after { transform: translateY(-50%) rotate(90deg); }
.faq[open] .faq-sign::after { transform: translateY(-50%) rotate(0deg); opacity: 0; }

.faq-body {
  overflow: hidden;
  padding-bottom: var(--s-6);
}
.faq-body p { color: var(--text-dim); max-width: 62ch; font-size: var(--t-sm); line-height: 1.62; }
.faq[open] .faq-body { animation: faq-open 420ms var(--ease-out); }
@keyframes faq-open {
  from { opacity: 0; transform: translateY(-6px); }
  to   { opacity: 1; transform: none; }
}

/* --------------------------------------------------------------------------
   23. DELIVERABLES — what a brand engagement actually hands over
   -------------------------------------------------------------------------- */
.deliverables {
  display: flex;
  flex-wrap: wrap;
  gap: var(--s-2);
  margin-top: var(--s-4);
}

/* --------------------------------------------------------------------------
   24. FORM LAYOUT
   -------------------------------------------------------------------------- */
.form-layout {
  display: grid;
  gap: var(--s-6);
  align-items: start;
}
@media (min-width: 900px) {
  .form-layout { grid-template-columns: minmax(0, 1.25fr) minmax(0, 1fr); gap: var(--s-7); }
  .preview { position: sticky; top: calc(var(--nav-h) + var(--s-5)); }
}

.form-panel { padding: clamp(var(--s-5), 4vw, var(--s-7)); }
.form-panel [hidden] { display: none !important; }

.field label .dim { font-weight: 400; }

/* --------------------------------------------------------------------------
   25. OVERFLOW GUARDS

   Grid and flex children default to `min-width: auto`, so a <select> is as
   wide as its longest <option> ("Website — multi-page business site") whether
   or not the viewport can afford it. That one rule pushed the entire form past
   the right edge of a 390px screen.
   -------------------------------------------------------------------------- */
.form > *,
.grid-2 > *,
.field,
.form-layout > * { min-width: 0; }

.select, .input, .textarea { min-width: 0; max-width: 100%; }

.preview-head { flex-wrap: wrap; }

/* The page head's bottom padding and the next section's top padding stacked
   into a gap you could park a car in. */
.page-head + .section-tight { padding-top: 0; }

/* A display line is allowed to be long; it is not allowed to run to the very
   edge of the column and leave the lede beneath it looking indented.

   The `margin-inline` is load-bearing, not decoration: a max-width box inside
   a `text-align: center` parent still sits FLUSH LEFT and only centres its own
   text, so the heading rendered visibly left of the lede under it. Every
   width-constrained child of a centred block needs this. */
.page-head .h-display { max-width: 46rem; margin-inline: auto; }

/* Same trap, same fix, everywhere else a centred block constrains a child. */
.page-head .prose,
.page-head .btn-row,
.section-head .lede,
.section-head .h1,
.section-head .h2,
.price-panel .fineprint,
.close-panel .prose { margin-inline: auto; }


/* --------------------------------------------------------------------------
   26. CORRECTIONS FROM THE BUILD REVIEW
   -------------------------------------------------------------------------- */

/* The violet cast tied the screenshots into the page, but at full strength it
   also made the client work hard to read — which is the one thing a portfolio
   may not do. Keep the tint, halve it, and drop it entirely on hover. */
.work-shot::after {
  background:
    linear-gradient(172deg, rgba(6, 4, 10, 0) 58%, rgba(6, 4, 10, 0.34) 100%),
    linear-gradient(0deg, rgba(90, 24, 154, 0.1), rgba(90, 24, 154, 0.1));
}
.work-item:hover .work-shot::after { opacity: 0; }

.case-shot::after {
  background: linear-gradient(0deg, rgba(90, 24, 154, 0.09), rgba(90, 24, 154, 0.09));
}

/* 184px between cases reads as a considered pause on a desktop and as a
   loading failure on a phone. */
.case-list { gap: clamp(var(--s-9), 12vw, var(--s-11)); }
.work-list { gap: clamp(var(--s-8), 10vw, var(--s-10)); }

/* The chevron's reserved space was sized for the widest option, so short
   labels in a two-up row were clipped mid-word. */
.select { padding-right: var(--s-7); background-position: right var(--s-3) center; }

/* An empty preview shouldn't read as a stray fragment beside a tall form. */
.preview { min-height: 13rem; display: flex; flex-direction: column; }
.preview-body { flex: 1; }

/* --------------------------------------------------------------------------
   27. CONVENTIONAL-LAYOUT CORRECTIONS
   -------------------------------------------------------------------------- */

/* Centred copy makes a one-word last line unmissable. */
.lede, .prose, .hero-copy .h-display { text-wrap: pretty; }
.h-display, .h1, .h2 { text-wrap: balance; }

/* Two auto-width buttons stacked on a phone read as a mistake; matching their
   width makes the pair deliberate. */
@media (max-width: 560px) {
  .hero .btn-row { flex-direction: column; align-items: stretch; }
  .hero .btn-row .btn { width: 100%; }
}

/* Section headings sit centred over left-aligned content, which is the
   convention this layout committed to. */
.section-head { text-align: center; }
.section-head .prose { margin-inline: auto; text-align: center; }
.page-head { text-align: center; }
.page-head .lede { margin-inline: auto; }

/* The close panel is the page's last word; centre it like the hero. */
.close-panel { text-align: center; }
.close-panel .h1 { max-width: 20ch; margin-inline: auto; }
.close-panel .lede { margin-inline: auto; }
.close-panel .btn-row { justify-content: center; }

.price-panel { text-align: center; }
.price-panel .price-figure { justify-content: center; }
.price-panel .prose { margin-inline: auto; }
.price-panel .btn-row { justify-content: center; }
.price-panel .price-includes { text-align: left; max-width: 34rem; margin-inline: auto; }
.price-panel .fineprint { text-align: left; margin-inline: auto; }

/* The break is authored, so balancing would only fight it. */
.hero-title { text-wrap: initial; }

/* --shadow-violet is sized for a large CTA; on the 44px nav button the same
   halo reads as a smudge behind the bar. */
.btn.nav-cta {
  padding-block: 11px;
  box-shadow: 0 6px 18px rgba(123, 44, 191, 0.34), var(--shadow-rim);
}
.btn.nav-cta:hover {
  box-shadow: 0 10px 26px rgba(123, 44, 191, 0.42), var(--shadow-rim);
}

/* --------------------------------------------------------------------------
   28. CAROUSEL

   A scroll-snap track, not a transform slider: swipe, trackpad, keyboard and
   the scrollbar all work natively, and with JS disabled it degrades to a
   horizontally scrollable row rather than to a single stuck slide.
   -------------------------------------------------------------------------- */
.carousel { position: relative; }

.carousel-track {
  display: flex;
  gap: var(--s-5);
  overflow-x: auto;
  scroll-snap-type: x mandatory;
  overscroll-behavior-x: contain;
  /* Smooth scrolling is applied per-call in JS so a programmatic jump can opt
     out; setting it here would also fight the browser's own restoration. */
  scrollbar-width: none;
  -ms-overflow-style: none;
  padding-bottom: var(--s-1);
}
.carousel-track::-webkit-scrollbar { display: none; }
.carousel-track:focus-visible {
  outline: 2px solid var(--violet-400);
  outline-offset: 6px;
  border-radius: var(--r-lg);
}

.carousel-slide {
  flex: 0 0 100%;
  min-width: 0;
  scroll-snap-align: center;
  scroll-snap-stop: always;
}
.carousel-slide .work-item { height: 100%; }

.carousel-controls {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--s-5);
  margin-top: var(--s-6);
}

.carousel-dots {
  display: flex;
  align-items: center;
  gap: var(--s-1);
}

/* The dot keeps a constant 24px box and the pill inside it scales, so the
   active state costs a composited transform rather than a layout pass on
   every neighbour. */
.carousel-dot {
  position: relative;
  width: 24px;
  height: 20px;
  padding: 0;
  border: 0;
  background: none;
  cursor: pointer;
}
.carousel-dot::before {
  content: "";
  position: absolute;
  inset: 50% 0 auto 0;
  height: 7px;
  margin-top: -3.5px;
  border-radius: var(--r-pill);
  background: rgba(255, 255, 255, 0.22);
  transform: scaleX(0.292);
  transition: transform var(--dur) var(--ease-arrive),
              background-color var(--dur) var(--ease-out);
}
.carousel-dot:hover::before { background: rgba(255, 255, 255, 0.42); }
.carousel-dot.is-active::before {
  transform: none;
  background: var(--violet-400);
}

.carousel-nav { display: flex; gap: var(--s-2); }

.carousel-btn {
  display: grid;
  place-items: center;
  width: 42px;
  height: 42px;
  padding: 0;
  border: 1px solid var(--glass-border);
  border-radius: 50%;
  background: rgba(255, 255, 255, 0.05);
  color: var(--text);
  cursor: pointer;
  -webkit-backdrop-filter: var(--glass-blur-sm);
  backdrop-filter: var(--glass-blur-sm);
  transition: background-color var(--dur-fast) var(--ease-out),
              border-color var(--dur-fast) var(--ease-out),
              opacity var(--dur-fast) var(--ease-out);
}
.carousel-btn svg { width: 18px; height: 18px; }
.carousel-btn:hover:not(:disabled) {
  background: rgba(255, 255, 255, 0.1);
  border-color: var(--glass-border-hi);
}
.carousel-btn:disabled { opacity: 0.32; cursor: default; }

.section-foot { margin-top: var(--s-7); text-align: center; }

/* One slide per view at every width. A two-up desktop layout left three dots
   controlling two scroll positions, so the last dot was a dead control.
   Instead the slide goes side-by-side on wide screens, which keeps the
   screenshot large and the slide short. */
@media (min-width: 820px) {
  .carousel-slide .work-item {
    display: grid;
    grid-template-columns: minmax(0, 1.15fr) minmax(0, 1fr);
    align-items: center;
    gap: var(--s-7);
  }
  .carousel-slide .work-shot { aspect-ratio: 16 / 10; }
}

.section-head .h1 + .prose { margin-top: var(--s-4); }

/* --------------------------------------------------------------------------
   27. FLOATING CONTACT DOCK
   -------------------------------------------------------------------------- */

/* Bottom-right, on every page. z-index sits under the nav (80) and its sheet
   (79) so an open mobile menu is never fought by a floating button. */
.social-dock {
  position: fixed;
  right: clamp(12px, 2vw, 22px);
  bottom: clamp(12px, 2vw, 22px);
  z-index: 70;
  display: grid;
  gap: var(--s-2);
}

.social-dock-btn {
  position: relative;
  display: grid;
  place-items: center;
  width: 44px;
  height: 44px;
  border-radius: 999px;
  color: var(--text-dim);
  border: 1px solid var(--glass-border);
  background: rgba(12, 8, 20, 0.72);
  -webkit-backdrop-filter: var(--glass-blur-sm);
  backdrop-filter: var(--glass-blur-sm);
  box-shadow: var(--shadow-md);
  transition: transform var(--dur) var(--ease-arrive),
              color var(--dur) var(--ease-out),
              border-color var(--dur) var(--ease-out),
              background-color var(--dur) var(--ease-out);
}
.social-dock-btn .ico { width: 20px; height: 20px; }

.social-dock-btn:hover,
.social-dock-btn:focus-visible {
  color: var(--text);
  border-color: rgba(199, 125, 255, 0.5);
  background: rgba(28, 14, 46, 0.86);
  transform: translateY(-2px);
}

/* WhatsApp is the one people actually act on, so it carries the brand fill. */
.social-dock-btn.is-whatsapp {
  color: #fff;
  border-color: transparent;
  background: linear-gradient(180deg, var(--violet-500), var(--violet-700));
}
.social-dock-btn.is-whatsapp:hover,
.social-dock-btn.is-whatsapp:focus-visible {
  background: linear-gradient(180deg, var(--violet-400), var(--violet-600));
}

/* Label appears on hover on pointer devices only — on a phone it would sit
   permanently over the page. It opens to the LEFT: the dock is on the right
   edge, so a label on the far side would run off the viewport. */
.social-dock-tip {
  position: absolute;
  right: calc(100% + 10px);
  white-space: nowrap;
  font-family: var(--font-mono);
  font-size: 0.72rem;
  letter-spacing: 0.06em;
  color: var(--text-dim);
  background: rgba(12, 8, 20, 0.92);
  border: 1px solid var(--glass-border);
  border-radius: var(--r-sm);
  padding: 4px 8px;
  opacity: 0;
  transform: translateX(4px);
  pointer-events: none;
  transition: opacity var(--dur-fast) var(--ease-out),
              transform var(--dur-fast) var(--ease-out);
}
@media (hover: hover) {
  .social-dock-btn:hover .social-dock-tip,
  .social-dock-btn:focus-visible .social-dock-tip {
    opacity: 1;
    transform: none;
  }
}
@media (hover: none) {
  .social-dock-tip { display: none; }
}

/* The open mobile menu owns the screen; the dock steps out of its way. */
body.nav-open .social-dock {
  opacity: 0;
  pointer-events: none;
}
.social-dock { transition: opacity var(--dur) var(--ease-out); }

@media (max-width: 560px) {
  .social-dock-btn { width: 40px; height: 40px; }
  .social-dock-btn .ico { width: 18px; height: 18px; }
}

@media (prefers-reduced-motion: reduce) {
  .social-dock-btn { transition: none; }
  .social-dock-btn:hover { transform: none; }
}

/* --------------------------------------------------------------------------
   28. THE MARK LIGHTING THE SECTION IT LANDS IN
   --------------------------------------------------------------------------
   `.gem-here` is set by the scene on whichever section the mark has settled
   on. The response is deliberately quiet — a section that flashes every time
   you scroll past is worse than one that does nothing. What it should read as
   is the mark casting light on the part of the page it is beside.           */

.section .section-head,
.section .steps,
.section .price-panel,
.section .close-panel {
  transition: none;
}

/* A soft violet wash behind the focal element, fading up as the mark arrives.
   Sits behind its own content and never intercepts a click. */
.section-head::after,
.steps::after {
  content: "";
  position: absolute;
  z-index: -1;
  inset: -18% -12%;
  border-radius: 50%;
  background: radial-gradient(
    closest-side,
    rgba(157, 78, 221, 0.22),
    rgba(157, 78, 221, 0.07) 58%,
    transparent 78%
  );
  opacity: 0;
  pointer-events: none;
  transition: opacity 620ms var(--ease-out);
}
.gem-here .section-head::after,
.gem-here .steps::after { opacity: 1; }

/* The heading itself lifts very slightly out of the dim. */
.section-head .h1,
.section-head .h2,
.section-head .h-display {
  transition: color 620ms var(--ease-out), text-shadow 620ms var(--ease-out);
}
.gem-here .section-head .h1,
.gem-here .section-head .h2 {
  text-shadow: 0 0 34px rgba(157, 78, 221, 0.42);
}

/* A glass panel the mark is sitting behind gets its rim picked out, as though
   the light behind it is catching the edge. */
.price-panel,
.close-panel {
  transition: border-color 620ms var(--ease-out),
              box-shadow 620ms var(--ease-out);
}
.gem-here .price-panel,
.gem-here .close-panel {
  border-color: rgba(199, 125, 255, 0.34);
  box-shadow: var(--shadow-lg), 0 0 60px -18px rgba(157, 78, 221, 0.5);
}

/* The steps list gets its rail lit as the mark rides down beside it. */
.step { transition: border-color 620ms var(--ease-out); }
.gem-here .step { border-color: rgba(199, 125, 255, 0.26); }

@media (prefers-reduced-motion: reduce) {
  .section-head::after,
  .steps::after,
  .section-head .h1,
  .section-head .h2,
  .price-panel,
  .close-panel,
  .step { transition: none; }
}

/* --------------------------------------------------------------------------
   29. THE STRIKE — what the mark does to the text it dives into
   --------------------------------------------------------------------------
   A spectrum sweeps through the line, it glows, and it settles back to white.
   The spectrum is azure → violet → magenta: all three are already palette
   tokens, so it reads as colour without leaving the violet-on-obsidian world.

   The sweep is a gradient clipped to a COPY of the text in a pseudo-element,
   laid exactly over the real line and cross-faded in. Recolouring the real
   text would mean switching `-webkit-text-fill-color` to transparent, which
   is a discrete jump with no way to fade — the overlay is what buys the fade
   in and out. It carries aria-hidden semantics by being a pseudo-element, so
   screen readers never see the duplicate.                                    */

.gem-target { position: relative; }

.gem-target::after {
  content: attr(data-text);
  position: absolute;
  inset: 0;
  background-image: linear-gradient(
    100deg,
    var(--azure) 0%,
    var(--violet-400) 38%,
    var(--magenta) 68%,
    var(--azure) 100%
  );
  background-size: 260% 100%;
  background-position: 120% 0;
  -webkit-background-clip: text;
  background-clip: text;
  color: transparent;
  -webkit-text-fill-color: transparent;
  opacity: 0;
  pointer-events: none;
}

.gem-target.is-struck::after {
  animation: gem-sweep 1300ms var(--ease-out) 1;
}
.gem-target.is-struck {
  animation: gem-glow 1300ms var(--ease-out) 1;
}

@keyframes gem-sweep {
  0%   { opacity: 0; background-position: 125% 0; }
  20%  { opacity: 1; }
  68%  { opacity: 1; }
  100% { opacity: 0; background-position: -35% 0; }
}

@keyframes gem-glow {
  0%, 100% { text-shadow: none; }
  38%      { text-shadow: 0 0 32px rgba(157, 78, 221, 0.55); }
}

/* The price figure is a composite of several spans with a rule between them,
   so a clipped text copy could never line up. It takes the same spectrum as a
   colour sweep in time rather than in space. */
.gem-target-figure.is-struck .price-amount {
  animation: gem-figure 1300ms var(--ease-out) 1;
}
.gem-target-figure.is-struck .price-alt .price-amount {
  animation-delay: 110ms;
}

@keyframes gem-figure {
  0%   { color: inherit; text-shadow: none; }
  30%  { color: var(--azure); text-shadow: 0 0 34px rgba(58, 134, 255, 0.5); }
  58%  { color: var(--violet-400); text-shadow: 0 0 38px rgba(157, 78, 221, 0.6); }
  80%  { color: var(--magenta); text-shadow: 0 0 30px rgba(255, 79, 216, 0.45); }
  100% { color: inherit; text-shadow: none; }
}

/* The dive is a flourish, not information. Anyone who has asked for less
   motion gets the colour and the glow with no sweep and no travel. */
@media (prefers-reduced-motion: reduce) {
  .gem-target.is-struck::after,
  .gem-target.is-struck,
  .gem-target-figure.is-struck .price-amount { animation: none; }
}
