/* Paws Trend — inner pages (about, services, contact, services/*).

   Loads *after* studio.css and depends on it: studio.css supplies the token
   system, aurora, navbar, footer, social icons and buttons. This file only
   adds the vocabulary those pages use that the homepage does not
   (.glass-card, .bento-grid, .tag, .page-header, form controls, …), styled to
   the system documented in docs/design-system.md.

   It replaces assets/css/style.css on those pages — that sheet is the older
   pure-black system still used by index-classic.html.

   Tokens only: no raw hex below this line. */

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

.container {
  max-width: 1200px;
  margin: 0 auto;
  padding: 0 var(--space-5);
}

main > section,
body > section {
  padding: var(--space-9) 0;
}

.section-heading {
  margin-bottom: var(--space-8);
  text-align: center;
}

.section-heading h2 {
  margin-top: var(--space-4);
}

/* ── Page header ────────────────────────────────────────────────────────────
   The inner-page counterpart to the homepage hero: one dark band directly on
   the light page, so every page opens on the same dark-on-light contrast the
   system is built around. The legacy markup carries an inline --page-bg
   photograph; it is deliberately not painted — the aurora plus this band is
   the system's own language, and an arbitrary stock photo behind white text
   is the one thing here with no contrast guarantee. */

.page-header {
  position: relative;
  overflow: hidden;
  margin: 0 auto;
  padding: calc(var(--space-10) + var(--space-6)) 0 var(--space-9);
  border-radius: 0 0 var(--radius-shell) var(--radius-shell);
  background: var(--ink);
  color: var(--on-ink-dim);
  text-align: center;
}

.page-header .container { position: relative; z-index: 1; }

.page-header h1 {
  margin: 0 0 var(--space-4);
  color: var(--on-ink);
  font-size: clamp(40px, 6vw, 76px);
  font-weight: 800;
  letter-spacing: -0.045em;
  line-height: 1.05;
}

.page-header p {
  max-width: 60ch;
  margin: 0 auto;
  color: var(--on-ink-dim);
  font-size: clamp(17px, 2vw, 20px);
}

/* Decorative particle layer (assets/js/particles.js). */
.page-header-canvas {
  position: absolute;
  inset: 0;
  z-index: 0;
  width: 100%;
  height: 100%;
  opacity: 0.5;
  pointer-events: none;
}

/* ── Gradient word ──────────────────────────────────────────────────────────
   Same treatment as studio.css's .gradient-word. On --ink the gradient's own
   hues carry it; no text-shadow is inherited here to bleed through. */
.gradient-text {
  background: var(--gradient);
  -webkit-background-clip: text;
          background-clip: text;
  color: transparent;
}

/* ── Cards ──────────────────────────────────────────────────────────────────
   The dark glass surface from the homepage. Rebinding the surface + ink
   tokens on the card means its descendants inherit the dark context.

   `color` is re-declared deliberately: body sets color: var(--text-dim),
   which resolves at body level and inherits down as an already-computed
   value, so rebinding the token alone cannot reach it. See
   docs/design-system.md §3. */

.glass-card {
  --surface: #14161a;
  --surface-sunken: #0f1114;
  --line: rgba(255, 255, 255, 0.10);
  --line-strong: rgba(255, 255, 255, 0.18);
  --text: #fafafa;
  --text-dim: rgba(255, 255, 255, 0.66);

  position: relative;
  padding: var(--space-6);
  border: 1px solid rgba(255, 255, 255, 0.14);
  border-radius: var(--radius-card);
  background: rgba(9, 10, 13, 0.92);
  color: var(--text-dim);
  /* --liquid-bevel leads the stack so the card reads as thick glass, matching
     the .step/.stat cards on the homepage — see studio.css.
     The wide layer was 32px blur / 0 spread -> 16px of sideways reach
     (blur/2 + spread), enough to wash into a neighbour even at this page's
     wider gaps. Negative spread pulls that in the same way studio.css does
     for .step/.stat: 22px blur, -8px spread -> reach = 22/2 - 8 = 3px. */
  box-shadow: var(--liquid-bevel),
              0 1px 2px rgba(0, 0, 0, 0.35),
              0 10px 22px -8px rgba(0, 0, 0, 0.45);
  transition: transform var(--transition), box-shadow var(--transition);
}

@supports ((backdrop-filter: blur(1px)) or (-webkit-backdrop-filter: blur(1px))) {
  .glass-card {
    -webkit-backdrop-filter: blur(var(--glass-blur-card)) saturate(var(--glass-saturate));
            backdrop-filter: blur(var(--glass-blur-card)) saturate(var(--glass-saturate));
  }

}

.glass-card h2,
.glass-card h3,
.glass-card h4 { color: var(--text); }

.glass-card p { color: var(--text-dim); }

/* .tilt is legacy markup from the old system; here it simply opts a card into
   the lift. No JS is bound to it on these pages. */
/* :hover doubled is not a typo. Every one of these cards also carries
   data-aos="fade-up", and AOS's own stylesheet resets its transform once
   revealed via `[data-aos^=fade][data-aos^=fade].aos-animate` — two
   attribute selectors, specificity (0,3,0) — which outranks a plain
   `.glass-card:hover` at (0,2,0) regardless of source order. Doubling the
   pseudo-class costs nothing semantically and brings this rule to the same
   (0,3,0), at which point pages.css loading after aos.css in the document
   decides the tie in our favour. Without it this hover transform silently
   never painted on a card that had already scrolled into view. */
.glass-card:hover:hover,
.glass-card.tilt:hover:hover {
  /* No scale here: any scale factor, even <1, changes the card's painted
     footprint, which is exactly what must never happen on hover — see the
     matching .step/.stat treatment in studio.css. translateY alone is kept
     because moving the card is not growing it. */
  transform: translateY(-6px);
  /* Was 48px blur / 0 spread -> 24px reach. -14px spread brings it to
     28/2 - 14 = 0px, the same pair studio.css uses for .step/.stat's hover. */
  box-shadow: var(--liquid-bevel),
              0 2px 6px rgba(0, 0, 0, 0.4),
              0 14px 28px -14px rgba(0, 0, 0, 0.55);
}

/* The liquid-glass lens rim and pointer ripple these cards carry live in
   studio.css alongside the .glass primitives they extend — one system for
   every dark card on the site rather than a second copy here. Both need a
   positioned box and content that clears the two pseudo-elements. */
/* :not(.liquid-canvas) is load-bearing, not tidiness. Bare `> *` has the same
   specificity as .liquid-canvas and this sheet loads after studio.css, so it
   won this cascade and forced the WebGL canvas back to position: relative —
   which on the grid-layout .service-card sized it to a single 132px grid cell
   instead of the card. */
.glass-card > *:not(.liquid-canvas),
.service-card > *:not(.liquid-canvas) {
  position: relative;
  z-index: 1;
}

/* Switches on the rim's pointer specular and chromatic fringe. studio.css
   turns these on for .hero-shell/.step/.stat in its own dark-card token
   block; these two cards are declared here, so they opt in here. */
.glass-card,
.service-card {
  --liquid-spec-edge: var(--liquid-spec);
  --liquid-fringe-a: var(--liquid-ring-a);
  --liquid-fringe-b: var(--liquid-ring-b);
  /* Same polished-lacquer reflection the homepage cards carry. Applied here
     rather than in studio.css because both rules above set the `background`
     shorthand, which resets background-image. */
  background-image: var(--liquid-gloss);
}

@media (prefers-reduced-motion: reduce) {
  .glass-card { transition: none; }
  /* :hover:hover to match the specificity bump on the live rule above — a
     lower-specificity `transform: none` here would lose to it, not to AOS. */
  .glass-card:hover:hover { transform: none; }
}

/* Links inside a dark card need the vivid accent, not the light-page ink
   form — see the --svc-*-ink note in docs/design-system.md §3. */
.glass-card a:not(.btn) {
  color: var(--blue);
  text-decoration: none;
}

.glass-card a:not(.btn):hover { text-decoration: underline; }

/* ── Bento grid ─────────────────────────────────────────────────────────── */

.bento-grid {
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  gap: var(--space-5);
}

.span-2 { grid-column: span 2; }
.span-4 { grid-column: span 4; }

@media (max-width: 900px) {
  .bento-grid { grid-template-columns: repeat(2, 1fr); }
  .span-2, .span-4 { grid-column: span 2; }
}

@media (max-width: 560px) {
  .bento-grid { grid-template-columns: 1fr; }
  .span-2, .span-4 { grid-column: span 1; }
}


/* ── Per-page service accent ────────────────────────────────────────────────
   studio.css rebinds the --svc channel only on .showcase[data-service]. Each
   service page sets data-service on <body> so its tag, icons, quote rule and
   fallback illustration all take that service's hue, exactly as the homepage
   tabs do. The -ink form is used on the light page and the vivid form inside
   dark cards, per docs/design-system.md §3. */

[data-service="build"] {
  --svc: var(--svc-build); --svc-ink: var(--svc-build-ink);
  --svc-soft: var(--svc-build-soft); --svc-glow: var(--svc-build-glow);
}
[data-service="intelligence"] {
  --svc: var(--svc-intelligence); --svc-ink: var(--svc-intelligence-ink);
  --svc-soft: var(--svc-intelligence-soft); --svc-glow: var(--svc-intelligence-glow);
}
[data-service="content"] {
  --svc: var(--svc-content); --svc-ink: var(--svc-content-ink);
  --svc-soft: var(--svc-content-soft); --svc-glow: var(--svc-content-glow);
}
[data-service="growth"] {
  --svc: var(--svc-growth); --svc-ink: var(--svc-growth-ink);
  --svc-soft: var(--svc-growth-soft); --svc-glow: var(--svc-growth-glow);
}
[data-service="foundation"] {
  --svc: var(--svc-foundation); --svc-ink: var(--svc-foundation-ink);
  --svc-soft: var(--svc-foundation-soft); --svc-glow: var(--svc-foundation-glow);
}

/* Inside a dark card the vivid hue is the accessible one. */
.glass-card { --svc-ink: var(--svc); }

/* ── Tag ────────────────────────────────────────────────────────────────────
   On the light page the accent must be the -ink form (the vivid blue is
   3.49:1 on white and fails AA); inside a dark card the vivid form is the
   accessible one. */

.tag {
  display: inline-flex;
  align-items: center;
  padding: var(--space-2) var(--space-4);
  border-radius: var(--radius-pill);
  background: var(--svc-soft);
  color: var(--svc-ink);
  font-size: 13px;
  font-weight: 600;
  letter-spacing: 0.02em;
}

.glass-card .tag { color: var(--svc); }

/* ── Buttons ────────────────────────────────────────────────────────────────
   studio.css owns .btn and .btn--primary (BEM). This markup predates that and
   uses the hyphenated .btn-primary, so it is mapped here rather than editing
   eight pages of HTML. */

.btn-primary {
  background: var(--ink);
  color: var(--on-ink);
  border-color: transparent;
}

.btn-primary:hover { background: var(--text); }

/* Inside a dark card the near-black fill would disappear. */
.glass-card .btn-primary,
.page-header .btn-primary {
  background: var(--on-ink);
  color: var(--ink);
}

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

.form-group {
  display: flex;
  flex-direction: column;
  gap: var(--space-3);
  margin-bottom: var(--space-5);
}

.form-group label {
  color: var(--text);
  font-size: 14px;
  font-weight: 600;
}

.form-group input,
.form-group textarea,
.form-group select {
  width: 100%;
  padding: var(--space-4);
  border: 1px solid var(--line-strong);
  border-radius: var(--radius-sm);
  background: var(--surface);
  color: var(--text);
  font-family: inherit;
  font-size: 16px; /* 16px min: smaller triggers iOS zoom-on-focus */
  transition: border-color var(--transition), box-shadow var(--transition);
}

.form-group input::placeholder,
.form-group textarea::placeholder { color: var(--text-dim); }

.form-group input:focus,
.form-group textarea:focus,
.form-group select:focus {
  outline: none;
  border-color: var(--blue);
  box-shadow: 0 0 0 3px var(--blue-soft);
}

.form-success {
  padding: var(--space-4);
  border-radius: var(--radius-sm);
  background: var(--svc-foundation-soft);
  color: var(--svc-foundation-ink);
  font-weight: 600;
}

.glass-card .form-success { color: var(--svc-foundation); }

/* ── Footer bits studio.css does not already cover ──────────────────────── */

.footer-logo img { height: 28px; width: auto; }

.footer-tagline {
  margin-top: var(--space-3);
  color: var(--text-dim);
  font-size: 15px;
}

.footer-bottom {
  margin-top: var(--space-6);
  padding-top: var(--space-5);
  border-top: 1px solid var(--line);
  color: var(--text-dim);
  font-size: 14px;
  text-align: center;
}

/* ── Navbar ─────────────────────────────────────────────────────────────────
   Opaque, not glass. studio.css's bar is 72%-white with a backdrop-filter,
   which is correct over the light aurora but composites to ~#b9b9b9 when a
   dark card scrolls beneath it — where --text-dim links measure ~2.8:1 and
   fail AA (the same failure measured on the homepage hero). The homepage can
   track a single dark section with an observer; these pages have dark cards
   passing under the bar continuously, so it simply stays opaque. */
.navbar {
  background: var(--surface);
  -webkit-backdrop-filter: none;
          backdrop-filter: none;
}

/* The hamburger used to be hidden here as a dead control. It is now the real
   mobile nav toggle (assets/js/nav.js, loaded on every page) and studio.css
   shows it below 900px — this file loads after studio.css, so leaving the
   display:none here silently disabled the mobile menu on all eight inner
   pages. Deliberately not re-added. */

/* ══════════════════════════════════════════════════════════════════════════
   ELABORATED SECTION PATTERNS
   ══════════════════════════════════════════════════════════════════════════
   Reusable blocks for the rewritten inner pages. Everything below composes
   from the tokens already defined; no new colour enters the system here. */

/* ── Section intro ──────────────────────────────────────────────────────── */

.section-intro {
  max-width: 62ch;
  margin: 0 auto var(--space-8);
  text-align: center;
}

.section-intro h2 {
  margin: var(--space-4) 0 var(--space-4);
  font-size: clamp(28px, 4vw, 44px);
  font-weight: 800;
  letter-spacing: -0.045em;
  line-height: 1.1;
}

.section-intro p {
  margin: 0;
  color: var(--text-dim);
  font-size: clamp(17px, 2vw, 19px);
}

/* ── Split: copy beside a visual ────────────────────────────────────────── */

.split {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: var(--space-8);
  align-items: center;
}

.split--reverse .split-visual { order: -1; }

.split-copy h2 {
  margin: var(--space-4) 0 var(--space-5);
  font-size: clamp(28px, 4vw, 44px);
  font-weight: 800;
  letter-spacing: -0.045em;
  line-height: 1.1;
}

.split-copy p {
  margin-bottom: var(--space-4);
  color: var(--text-dim);
  font-size: 17px;
}

.split-copy p:last-child { margin-bottom: 0; }

@media (max-width: 900px) {
  .split { grid-template-columns: 1fr; gap: var(--space-6); }
  .split--reverse .split-visual { order: 0; }
}

/* ── 3D object stage ────────────────────────────────────────────────────────
   assets/js/page-object.js mounts a canvas here and adds .is-live only once
   the scene actually built. The inline SVG fallback stays visible until then,
   so a WebGL failure, the static tier, or no JS at all still leaves a
   deliberate illustration rather than an empty box. */

.object-stage {
  position: relative;
  display: grid;
  place-items: center;
  /* Tall enough for the object to read. The scene's camera distance is tuned
     for the homepage's wide, short showcase window, so in a squarer stage the
     object needs vertical room or it sits small and lost in the box. */
  min-height: 440px;
  padding: var(--space-6);
  border: 1px solid rgba(255, 255, 255, 0.14);
  border-radius: var(--radius-card);
  background: rgba(9, 10, 13, 0.92);
  box-shadow: 0 12px 32px rgba(0, 0, 0, 0.45);
  overflow: hidden;
}

.object-stage .object-fallback {
  width: min(100%, 320px);
  height: auto;
  color: var(--svc);
  opacity: 0.9;
}

.object-stage.is-live .object-fallback { display: none; }

/* ── Value / feature cards with an icon ─────────────────────────────────── */

.value-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(260px, 1fr));
  gap: var(--space-5);
}

.value-icon {
  display: grid;
  place-items: center;
  width: 44px;
  height: 44px;
  margin-bottom: var(--space-5);
  border-radius: var(--radius-sm);
  background: var(--svc-soft);
  color: var(--svc);
}

.glass-card h3 {
  margin: 0 0 var(--space-3);
  font-size: 19px;
  font-weight: 700;
  letter-spacing: -0.02em;
}

.glass-card p { margin: 0; font-size: 15px; line-height: 1.6; }

/* ── Numbered process ───────────────────────────────────────────────────── */

.process-list {
  display: grid;
  gap: var(--space-1);
  counter-reset: step;
}

.process-step {
  display: grid;
  grid-template-columns: auto 1fr;
  gap: var(--space-5);
  padding: var(--space-6);
  border: 1px solid var(--line);
  border-radius: var(--radius-window);
  background: var(--surface);
}

.process-step + .process-step { margin-top: var(--space-4); }

.process-step-num {
  display: grid;
  place-items: center;
  width: 40px;
  height: 40px;
  border-radius: var(--radius-pill);
  background: var(--ink);
  color: var(--on-ink);
  font-size: 15px;
  font-weight: 700;
}

.process-step h3 { margin: 0 0 var(--space-3); font-size: 18px; font-weight: 700; }
.process-step p { margin: 0; color: var(--text-dim); font-size: 16px; }

@media (max-width: 560px) {
  .process-step { grid-template-columns: 1fr; gap: var(--space-4); }
}

/* ── Pull quote ─────────────────────────────────────────────────────────────
   A stated position in the brand's own voice. Not a testimonial: nothing here
   is attributed to a client, because no client quote in this repo has been
   verified. */

.pull-quote {
  max-width: 46ch;
  margin: var(--space-8) auto;
  padding-left: var(--space-5);
  border-left: 3px solid var(--svc);
  color: var(--text);
  font-size: clamp(21px, 3vw, 28px);
  font-weight: 600;
  letter-spacing: -0.03em;
  line-height: 1.35;
}

/* ── Fact row ───────────────────────────────────────────────────────────── */

.fact-row {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
  gap: var(--space-5);
  text-align: center;
}

.fact-value {
  display: block;
  color: var(--text);
  font-size: clamp(40px, 6vw, 64px);
  font-weight: 800;
  letter-spacing: -0.05em;
  line-height: 1;
}

.fact-label {
  display: block;
  margin-top: var(--space-3);
  color: var(--text-dim);
  font-size: 15px;
}

/* Unfilled client data, drawn as an obviously empty slot so it can never be
   mistaken for a shipped figure. Mirrors .stat-value--input in studio.css. */
.fact-value--input {
  display: inline-block;
  padding: var(--space-3) var(--space-4);
  border: 1px dashed var(--line-strong);
  border-radius: var(--radius-sm);
  background: var(--surface-sunken);
  font-size: 14px;
  font-weight: 600;
  letter-spacing: 0;
  line-height: 1.35;
}

/* ── Checklist ──────────────────────────────────────────────────────────── */

.check-list { margin: 0; padding: 0; list-style: none; }

.check-list li {
  position: relative;
  margin-bottom: var(--space-4);
  padding-left: var(--space-6);
  color: var(--text-dim);
  font-size: 16px;
}

.check-list li::before {
  content: '';
  position: absolute;
  top: 0.55em;
  left: 0;
  width: 10px;
  height: 10px;
  border-radius: 3px;
  background: var(--svc);
}

.glass-card .check-list li { color: var(--text-dim); }

/* ── Interactive service cards (services.html) ──────────────────────────────
   The overview page deliberately does NOT mount five 3D scenes: five WebGL
   contexts on one page is a real cost for a page whose job is to route people
   onward. Each card instead carries flat line art that reacts on hover, and
   links to the service page that does carry the live object. */

.service-list { display: grid; gap: var(--space-5); }

.service-card {
  /* Positioned so the shared liquid-glass pseudo-elements in studio.css can
     inset against it, exactly as .glass-card already does. */
  position: relative;
  display: grid;
  grid-template-columns: 132px 1fr auto;
  gap: var(--space-6);
  align-items: center;
  padding: var(--space-6);
  border: 1px solid rgba(255, 255, 255, 0.14);
  border-radius: var(--radius-card);
  background: rgba(9, 10, 13, 0.92);
  /* Re-applied after the shorthand above, which resets background-image. This
     rule sits later in the file than the shared gloss block, so it wins. */
  background-image: var(--liquid-gloss);
  color: var(--text-dim);
  text-decoration: none;
  /* Same reach fix as .glass-card above: 32px blur / 0 spread reached 16px
     sideways; -8px spread brings it to 22/2 - 8 = 3px. */
  box-shadow: var(--liquid-bevel),
              0 1px 2px rgba(0, 0, 0, 0.35), 0 10px 22px -8px rgba(0, 0, 0, 0.45);
  transition: transform var(--transition), box-shadow var(--transition),
              border-color var(--transition);
}

.service-card {
  --text: #fafafa;
  --text-dim: rgba(255, 255, 255, 0.66);
  --svc-ink: var(--svc);
}

/* :hover doubled — same reasoning as .glass-card:hover:hover above: every
   .service-card carries data-aos="fade-up", and AOS's own
   `[data-aos^=fade][data-aos^=fade].aos-animate` rule at specificity (0,3,0)
   would otherwise silently beat a plain `.service-card:hover` at (0,2,0)
   once the card has scrolled into view, regardless of source order. */
.service-card:hover:hover {
  /* No scale here: any scale factor, even <1, changes the card's painted
     footprint, which is exactly what must never happen on hover — see the
     matching .step/.stat treatment in studio.css. translateY alone is kept
     because moving the card is not growing it. */
  transform: translateY(-4px);
  border-color: var(--svc);
  /* Same reach fix as .glass-card:hover:hover above: 48px blur / 0 spread
     reached 24px sideways; -14px spread brings it to 28/2 - 14 = 0px. The
     1px outline is now `inset` rather than outset: an outset spread-only
     shadow (no blur) adds its full spread to the painted card on every side,
     which enlarges the card on hover — the exact bug this pass exists to
     kill. Inset draws it just inside the card's own edge instead. */
  box-shadow: var(--liquid-bevel),
              0 2px 6px rgba(0, 0, 0, 0.4), 0 14px 28px -14px rgba(0, 0, 0, 0.55),
              inset 0 0 0 1px var(--svc-glow);
}

.service-num {
  display: block;
  margin-bottom: var(--space-3);
  color: var(--svc);
  /* 20px/800 is deliberate, not decorative. At 13px this is small text needing
     4.5:1, and vivid violet measured 4.02:1 on the card — the weakest of the
     five hues. At >=18.66px bold WCAG's threshold is 3:1, which all five clear,
     and a larger index number reads better here regardless. */
  font-size: 20px;
  font-weight: 800;
  letter-spacing: 0.06em;
}

.service-card h3 {
  margin: 0 0 var(--space-3);
  color: var(--text);
  font-size: clamp(20px, 2.4vw, 26px);
  font-weight: 700;
  letter-spacing: -0.03em;
}

.service-card p { margin: 0; color: var(--text-dim); font-size: 15px; line-height: 1.6; }

/* The interactive part: line art that brightens, scales and re-draws toward
   the service hue on hover. */
.service-art {
  width: 132px;
  height: 96px;
  color: var(--text-dim);
  transition: color var(--transition);
}

.service-art * {
  transition: stroke-dashoffset 0.7s ease, opacity var(--transition);
}

.service-card:hover .service-art {
  color: var(--svc);
}

.service-arrow {
  color: var(--svc);
  transition: transform var(--transition);
}

/* Composes with the hover content inset in studio.css: same specificity (0,3,0)
   and this file loads later, so whatever this rule writes is the arrow's whole
   transform — the inset scale has to be repeated here or the arrow would be the
   one child that ignores it. */
.service-card:hover .service-arrow {
  transform: translateX(6px) scale(var(--card-inset-scale, 0.95));
}

@media (prefers-reduced-motion: reduce) {
  .service-card,
  .service-art,
  .service-arrow { transition: none; }
  /* :hover:hover to match the specificity bump on the live rule above — a
     lower-specificity `transform: none` here would lose to it, not to AOS. */
  .service-card:hover:hover { transform: none; }
  .service-card:hover .service-arrow { transform: none; }
}

@media (max-width: 720px) {
  .service-card { grid-template-columns: 1fr; gap: var(--space-5); }
  .service-arrow { display: none; }
  .service-art { width: 108px; height: 78px; }
}

/* ── Contact layout ─────────────────────────────────────────────────────── */

.contact-grid {
  display: grid;
  grid-template-columns: 1.6fr 1fr;
  gap: var(--space-6);
  align-items: start;
}

@media (max-width: 900px) {
  .contact-grid { grid-template-columns: 1fr; }
}

.contact-aside .glass-card + .glass-card { margin-top: var(--space-5); }

.contact-aside h3 { margin: 0 0 var(--space-3); font-size: 17px; }
.contact-aside p { margin: 0; font-size: 15px; }

/* A numbered "what happens next" so the form is not a black hole. */
.next-list { margin: 0; padding: 0; list-style: none; counter-reset: nx; }

.next-list li {
  position: relative;
  margin-bottom: var(--space-4);
  padding-left: var(--space-7);
  color: var(--text-dim);
  font-size: 15px;
  line-height: 1.6;
  counter-increment: nx;
}

.next-list li::before {
  content: counter(nx);
  position: absolute;
  top: 0;
  left: 0;
  display: grid;
  place-items: center;
  width: 26px;
  height: 26px;
  border-radius: var(--radius-pill);
  background: var(--svc-soft);
  color: var(--svc);
  font-size: 13px;
  font-weight: 700;
}

/* Hidden until submit. assets/js/main.js adds .visible — the page previously
   showed this confirmation before anyone had sent anything. */
.form-success { display: none; }
.form-success.visible { display: block; }

/* ── Situations ─────────────────────────────────────────────────────────── */

.situation-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
  gap: var(--space-5);
}

.situation {
  padding: var(--space-6);
  border: 1px solid var(--line);
  border-left: 3px solid var(--svc);
  border-radius: var(--radius-window);
  background: var(--surface);
}

.situation-quote {
  margin: 0 0 var(--space-4);
  color: var(--text);
  font-size: 17px;
  font-weight: 600;
  font-style: italic;
  letter-spacing: -0.01em;
  line-height: 1.4;
}

.situation p:last-child { margin: 0; color: var(--text-dim); font-size: 15px; line-height: 1.6; }

/* ── FAQ ────────────────────────────────────────────────────────────────────
   Native <details>/<summary>: keyboard accessible and open-by-default when
   printed or when JS is unavailable, with no script of our own. */

.faq-list {
  display: grid;
  gap: var(--space-4);
  max-width: 820px;
  margin: 0 auto;
}

.faq-item {
  border: 1px solid var(--line);
  border-radius: var(--radius-window);
  background: var(--surface);
  overflow: hidden;
}

.faq-item summary {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-4);
  padding: var(--space-5) var(--space-6);
  color: var(--text);
  font-size: 17px;
  font-weight: 700;
  letter-spacing: -0.02em;
  cursor: pointer;
  list-style: none;
}

/* Both are needed: the ::marker rule covers modern engines, the WebKit
   pseudo-element covers older Safari. */
.faq-item summary::marker { content: ''; }
.faq-item summary::-webkit-details-marker { display: none; }

.faq-item summary::after {
  content: '+';
  flex-shrink: 0;
  color: var(--svc);
  font-size: 24px;
  font-weight: 400;
  line-height: 1;
}

.faq-item[open] summary::after { content: '\2212'; }

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

.faq-answer {
  margin: 0;
  padding: 0 var(--space-6) var(--space-5);
  color: var(--text-dim);
  font-size: 16px;
  line-height: 1.65;
}

/* ── Contact form anti-spam ─────────────────────────────────────────────────
   The honeypot is positioned off-screen rather than display:none, since some
   bots skip fields that are not rendered at all. It is aria-hidden and
   tabindex="-1" in the markup, so it is unreachable for real users including
   keyboard and screen-reader users. */

.hp-field {
  position: absolute;
  left: -9999px;
  width: 1px;
  height: 1px;
  overflow: hidden;
}

.cf-turnstile { margin-bottom: var(--space-5); }

.form-error {
  display: none;
  margin-top: var(--space-4);
  padding: var(--space-4);
  border-radius: var(--radius-sm);
  background: rgba(255, 77, 77, 0.12);
  color: #ff9d9d;
  font-size: 15px;
  font-weight: 600;
}

.form-error.visible { display: block; }

/* pages.css loads after studio.css, so its .tag font-size wins over the
   touch-comfort bump there. Repeated here rather than fighting with
   specificity. */
@media (pointer: coarse) {
  .tag { font-size: 14px; }
}

/* ── Grid items must be allowed to shrink ───────────────────────────────────
   Grid and flex items default to min-width:auto, which refuses to shrink below
   their content's min-content width. On a 390px phone .split measured 334px
   wide with 434px of content — 100px of overflow that cascaded up through
   .container and <section> and expanded the layout viewport itself to 462px.

   That is why the earlier mobile audit looked clean: scrollWidth and
   innerWidth had BOTH grown, so comparing them showed no overflow while the
   page was quietly being zoomed out. min-width:0 lets the item shrink and the
   viewport stay at the device width. */

.split > *,
.value-grid > *,
.situation-grid > *,
.bento-grid > *,
.contact-grid > *,
.process-step > *,
.service-card > * {
  min-width: 0;
}

/* The stage's own padding plus the fallback art's 320px floor was a large part
   of that min-content width. */
.object-stage {
  min-width: 0;
  padding: var(--space-5);
}

.object-stage .object-fallback {
  width: 100%;
  max-width: 320px;
}

/* ── AOS horizontal reveals on small screens ────────────────────────────────
   AOS's fade-left/fade-right hold the element at translate3d(±100px,0,0)
   until it scrolls into view. On a phone that parks it outside the viewport,
   and the browser responds by widening the layout viewport itself — a 390px
   device was reporting window.innerWidth of 462 and rendering the whole page
   zoomed out, with the fixed navbar sized to the wrong width.

   The reveal is kept, but as a vertical/opacity move only. Horizontal
   slide-ins are a poor fit for a narrow screen regardless. */

@media (max-width: 900px) {
  [data-aos="fade-left"],
  [data-aos="fade-right"] {
    transform: translate3d(0, 24px, 0);
  }

  [data-aos="fade-left"].aos-animate,
  [data-aos="fade-right"].aos-animate {
    transform: translate3d(0, 0, 0);
  }
}

/* ── Milestone timeline (about.html) ────────────────────────────────────────
   A sticky year beside a scrolling column of milestone cards. Two rules shape
   the whole block:

   1. The markup is the source of truth, not a JS data array. Every milestone
      renders as a plain <li> with no JS at all, which is what keeps the copy
      readable for crawlers, for reader modes and for the no-JS path the
      accessibility checklist requires. assets/js/milestones.js only reads the
      DOM and animates it.
   2. The sticky spine is therefore opt-in: it stays display:none until that
      script marks the container ready. A year that sticks but never changes
      would be worse than no spine, so the no-JS render simply does not get
      one and falls back to the year printed on each card.

   Each item carries its own data-service, so the marker and rail pick up that
   discipline's hue from the [data-service] block above. */

.timeline {
  display: grid;
  grid-template-columns: minmax(0, 280px) minmax(0, 1fr);
  gap: var(--space-7);
  align-items: start;
}

/* ── Sticky spine ───────────────────────────────────────────────────────── */

.timeline-spine { display: none; }

[data-timeline-ready] .timeline-spine {
  display: block;
  position: sticky;
  top: 26vh;
}

/* Clips the outgoing year as it slides away. The height is locked to the
   line-box so the tag and progress bar below it never shift on a swap. */
.timeline-year-mask {
  display: block;
  overflow: hidden;
  height: 1em;
  font-size: clamp(48px, 9vw, 108px);
  line-height: 1;
}

.timeline-year {
  display: block;
  color: var(--text);
  font-size: inherit;
  font-weight: 800;
  letter-spacing: -0.05em;
  line-height: 1;
  font-variant-numeric: tabular-nums;
}

.timeline-spine .tag { margin-top: var(--space-4); }

.timeline-progress {
  margin-top: var(--space-5);
  height: 2px;
  border-radius: var(--radius-pill);
  background: var(--line-strong);
}

.timeline-progress-fill {
  width: var(--timeline-progress, 0%);
  height: 100%;
  border-radius: inherit;
  background: var(--svc);
}

.timeline-count {
  display: block;
  margin-top: var(--space-4);
  color: var(--text-dim);
  font-size: 13px;
  font-weight: 600;
  font-variant-numeric: tabular-nums;
}

/* ── The rail and its items ─────────────────────────────────────────────── */

.timeline-body {
  position: relative;
  padding-left: var(--space-6);
}

.timeline-list { margin: 0; padding: 0; list-style: none; }

/* The unfilled rail. Sits behind the markers, inset far enough that a marker
   centres on it at every width. */
.timeline-body::before {
  content: '';
  position: absolute;
  top: var(--space-3);
  bottom: var(--space-9);
  left: 5px;
  width: 2px;
  border-radius: var(--radius-pill);
  background: var(--line-strong);
}

/* Scrubbed on every frame by assets/js/milestones.js. Decorative only, and it
   carries the current milestone's hue so the rail, the marker and the spine
   tag all move together. */
.timeline-rail-fill {
  position: absolute;
  top: var(--space-3);
  left: 5px;
  width: 2px;
  height: var(--timeline-fill, 0%);
  border-radius: var(--radius-pill);
  background: var(--svc);
  transition: background 240ms ease;
}

.timeline-item {
  position: relative;
  padding-bottom: var(--space-8);
}

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

.timeline-marker {
  position: absolute;
  top: var(--space-4);
  left: calc(var(--space-6) * -1);
  width: 12px;
  height: 12px;
  border: 2px solid var(--bg);
  border-radius: var(--radius-pill);
  background: var(--line-strong);
  transition: background 240ms ease, transform 240ms ease;
}

.timeline-item.is-current .timeline-marker {
  transform: scale(1.35);
  background: var(--svc);
}

/* Sized deliberately, not stylistically. At 13px/700 the vivid violet measures
   4.09:1 on the composited dark card and fails AA; every other discipline hue
   clears 4.5. Rather than exempt one service or invent a lighter violet, the
   year sits at 20px/800, which is WCAG "large text" (>=18.66px bold) and needs
   only 3:1 - a bar all five hues clear with room to spare. Do not shrink this
   below 18.66px without re-measuring all five. */
.timeline-card .timeline-card-year {
  margin: 0 0 var(--space-2);
  color: var(--svc-ink);
  font-size: 20px;
  font-weight: 800;
  letter-spacing: -0.02em;
  font-variant-numeric: tabular-nums;
}

.timeline-card h3 {
  margin: 0 0 var(--space-3);
  color: var(--text);
  font-size: clamp(22px, 2.6vw, 28px);
  font-weight: 700;
  letter-spacing: -0.035em;
  line-height: 1.2;
}

.timeline-card p.timeline-card-body { margin: 0; font-size: 16px; line-height: 1.6; }

@media (max-width: 900px) {
  .timeline { grid-template-columns: 1fr; gap: var(--space-5); }

  /* The spine cannot stick beside anything at this width, so it rides above
     the list as a compact bar instead. It has to clear the fixed navbar, which
     measures 75px here and 83px on desktop; sticking at 0 parks the year
     underneath the bar where it cannot be read. The opaque background and the
     rule below it are what stop cards showing through as they pass under. */
  [data-timeline-ready] .timeline-spine {
    top: 76px;
    z-index: 2;
    /* Bled out to the viewport edges and padded back in, so the opaque band
       covers the full width. Left inside .container's gutter the background
       stops at 28px and cards slide past in the margin. --space-5 is that
       gutter; the two must stay in step. */
    margin: 0 calc(var(--space-5) * -1);
    padding: var(--space-4) var(--space-5);
    border-bottom: 1px solid var(--line);
    background: var(--bg);
  }

  .timeline-year-mask { font-size: clamp(36px, 12vw, 56px); }
  .timeline-spine .tag { margin-top: var(--space-3); }
  .timeline-progress { margin-top: var(--space-4); }
  .timeline-count { margin-top: var(--space-3); }
}

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