/* ==========================================================================
   Vihanvi Infratech — shared design system for the service pages
   (residential, commercial, elevators, construction, upvc_furniture)

   Same liquid glass material as the home page: ported from the Clipo
   build (Clipo-main/index.css — .glass-card, .navbar__btn, .glass-input).
   The fill is a bare 12% of --c-glass; the material is carried entirely
   by a stack of offset shadows — light insets catching the top-left and
   bottom-right edges, dark insets seating the face, outer casts for
   weight — each scaled by --glass-reflex-light / --glass-reflex-dark.

   Because those two multipliers are variables, the *same* rules work on
   a dark surface: any block that sits over photography just re-declares
   Clipo's dark-theme values locally and every child inverts with it.
   ========================================================================== */

:root {
  /* ── Clipo glass tokens (light) ── */
  --c-glass: #bbbbbc;
  --c-light: #fff;
  --c-dark: #000;
  --c-content: #0d1015;
  --c-bg: #f5f5f7;
  --glass-reflex-dark: 1;
  --glass-reflex-light: 1;
  --saturation: 150%;

  /* Type tones derived from the ink, so there is no second palette */
  --ink: var(--c-content);
  --ink-soft: color-mix(in srgb, var(--c-content) 58%, transparent);
  --ink-faint: color-mix(in srgb, var(--c-content) 38%, transparent);
  --line: color-mix(in srgb, var(--c-content) 10%, transparent);
  --line-strong: color-mix(in srgb, var(--c-content) 18%, transparent);

  /* Cormorant Garamond runs small and light for its point size — it is a
     display face, and the headings using it are sized accordingly. Jost
     is the geometric counterpart underneath it. */
  --display: 'Cormorant Garamond', 'Playfair Display', Georgia, serif;
  --sans: 'Jost', 'Plus Jakarta Sans', system-ui, sans-serif;
  --ease: cubic-bezier(1, 0, 0.4, 1);
  --ease-out: cubic-bezier(0.16, 1, 0.3, 1);
  --shell: 1240px;

  /* The two header panes float over whatever the page is doing — a dark
     hero clip at the top, pale glass bands further down. Rather than
     swapping themes on scroll, they lift their own backdrop: the picture
     still reads through, but always light enough to carry ink type. */
  --pane-backdrop: blur(6px) saturate(180%) brightness(1.34);

  --r-lg: 32px;
  --r-md: 24px;
  --r-sm: 16px;

  /* The full card stack */
  --glass-shadow:
    inset 0 0 0 1px color-mix(in srgb, var(--c-light) calc(var(--glass-reflex-light) * 10%), transparent),
    inset 1.8px 3px 0px -2px color-mix(in srgb, var(--c-light) calc(var(--glass-reflex-light) * 90%), transparent),
    inset -2px -2px 0px -2px color-mix(in srgb, var(--c-light) calc(var(--glass-reflex-light) * 80%), transparent),
    inset -3px -8px 1px -6px color-mix(in srgb, var(--c-light) calc(var(--glass-reflex-light) * 60%), transparent),
    inset -0.3px -1px 4px 0px color-mix(in srgb, var(--c-dark) calc(var(--glass-reflex-dark) * 12%), transparent),
    inset -1.5px 2.5px 0px -2px color-mix(in srgb, var(--c-dark) calc(var(--glass-reflex-dark) * 20%), transparent),
    inset 0px 3px 4px -2px color-mix(in srgb, var(--c-dark) calc(var(--glass-reflex-dark) * 20%), transparent),
    inset 2px -6.5px 1px -4px color-mix(in srgb, var(--c-dark) calc(var(--glass-reflex-dark) * 10%), transparent),
    0px 1px 5px 0px color-mix(in srgb, var(--c-dark) calc(var(--glass-reflex-dark) * 10%), transparent),
    0px 6px 16px 0px color-mix(in srgb, var(--c-dark) calc(var(--glass-reflex-dark) * 8%), transparent);

  /* The control stack — the same, minus the three dark seats, so
     buttons and small chips sit lighter than panels */
  --glass-shadow-btn:
    inset 0 0 0 1px color-mix(in srgb, var(--c-light) calc(var(--glass-reflex-light) * 10%), transparent),
    inset 1.8px 3px 0px -2px color-mix(in srgb, var(--c-light) calc(var(--glass-reflex-light) * 90%), transparent),
    inset -2px -2px 0px -2px color-mix(in srgb, var(--c-light) calc(var(--glass-reflex-light) * 80%), transparent),
    inset -3px -8px 1px -6px color-mix(in srgb, var(--c-light) calc(var(--glass-reflex-light) * 60%), transparent),
    inset -0.3px -1px 4px 0px color-mix(in srgb, var(--c-dark) calc(var(--glass-reflex-dark) * 12%), transparent),
    0px 1px 5px 0px color-mix(in srgb, var(--c-dark) calc(var(--glass-reflex-dark) * 10%), transparent),
    0px 6px 16px 0px color-mix(in srgb, var(--c-dark) calc(var(--glass-reflex-dark) * 8%), transparent);

  /* Fields read recessed: the two outer casts turned inward */
  --glass-shadow-inset:
    inset 0 0 0 1px color-mix(in srgb, var(--c-light) calc(var(--glass-reflex-light) * 10%), transparent),
    inset 1.8px 3px 0px -2px color-mix(in srgb, var(--c-light) calc(var(--glass-reflex-light) * 90%), transparent),
    inset -2px -2px 0px -2px color-mix(in srgb, var(--c-light) calc(var(--glass-reflex-light) * 80%), transparent),
    inset -3px -8px 1px -6px color-mix(in srgb, var(--c-light) calc(var(--glass-reflex-light) * 60%), transparent),
    inset -0.3px -1px 4px 0px color-mix(in srgb, var(--c-dark) calc(var(--glass-reflex-dark) * 12%), transparent),
    inset 0px 1px 5px 0px color-mix(in srgb, var(--c-dark) calc(var(--glass-reflex-dark) * 10%), transparent),
    inset 0px 6px 16px 0px color-mix(in srgb, var(--c-dark) calc(var(--glass-reflex-dark) * 8%), transparent);
}

/* Clipo's dark-theme token set. Applied to any block sitting over
   photography — every glass surface and every piece of type inside it
   flips without a single extra rule. */
.on-dark,
.hero,
.reel-panel {
  --c-glass: #555555;
  --c-content: #f5f5f7;
  --glass-reflex-dark: 2;
  --glass-reflex-light: 0.4;
}

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

html {
  scroll-behavior: smooth;
}

body {
  margin: 0;
  background: var(--c-bg);
  color: var(--c-content);
  font-family: var(--sans);
  font-size: 16px;
  line-height: 1.6;
  overflow-x: hidden;
  -webkit-font-smoothing: antialiased;
}

/* Ambient field behind everything. Glass needs something to refract —
   over flat paper the blur has nothing to pick up. Neutral greys only,
   so the material never borrows a hue. */
body::before {
  content: '';
  position: fixed;
  inset: -10%;
  z-index: -1;
  pointer-events: none;
  background:
    radial-gradient(46% 36% at 8% 6%, rgba(96, 104, 116, 0.22), transparent 62%),
    radial-gradient(44% 34% at 92% 14%, rgba(140, 146, 156, 0.24), transparent 64%),
    radial-gradient(50% 40% at 84% 54%, rgba(88, 96, 110, 0.18), transparent 64%),
    radial-gradient(42% 34% at 12% 52%, rgba(150, 156, 164, 0.22), transparent 62%),
    radial-gradient(52% 40% at 76% 94%, rgba(96, 104, 116, 0.20), transparent 64%),
    radial-gradient(40% 32% at 18% 90%, rgba(136, 142, 152, 0.20), transparent 62%),
    var(--c-bg);
}

/* ── Ink page ─────────────────────────────────────────────────────────
   For a page built entirely of dark bands. The pale --c-bg ground and
   the ambient field above it exist to give glass something to refract,
   which is worth nothing on a page whose bands are all opaque and dark —
   there, the only place either one is ever visible is as a white edge
   past the last band, or in the gap a pinned section leaves behind it.

   Set on <body>, so it also covers the overscroll area that html and
   body both hand to the canvas. */
.page--ink {
  background: #0b0e13;
}

.page--ink::before {
  display: none;
}

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

a {
  color: inherit;
}

::selection {
  background: var(--c-content);
  color: var(--c-bg);
}

::-webkit-scrollbar {
  width: 9px;
}

::-webkit-scrollbar-track {
  background: color-mix(in srgb, var(--c-content) 5%, transparent);
}

::-webkit-scrollbar-thumb {
  background: var(--line-strong);
  border-radius: 99em;
}

::-webkit-scrollbar-thumb:hover {
  background: var(--ink-soft);
}

/* ── Lightweight pass ─────────────────────────────────────────────────
   Set by pages.js on a metered or slow connection, a low-memory phone,
   or under reduced-motion. Two things go: the hero clip is never
   fetched (its poster stands in), and every backdrop-filter is dropped.

   The filters are the expensive half. Each one forces the compositor to
   re-sample everything painted behind it, and there are dozens on a
   page — bands, cards, buttons, the header panes. On a weak GPU that is
   the difference between a page that scrolls and one that stutters. The
   fills are raised to compensate, since without the blur there is
   nothing else holding the type off the background. */
.is-lite *,
.is-lite *::before,
.is-lite *::after {
  backdrop-filter: none !important;
  -webkit-backdrop-filter: none !important;
}

.is-lite .section--paper,
.is-lite .section--white,
.is-lite .section--tint,
.is-lite .section--navy,
.is-lite .footer {
  background-color: color-mix(in srgb, var(--c-glass) 22%, transparent);
}

.is-lite .step,
.is-lite .form-card,
.is-lite .notice,
.is-lite .btn,
.is-lite .site-logo,
.is-lite .switcher,
.is-lite .header-btn {
  background-color: color-mix(in srgb, var(--c-glass) 40%, transparent);
}

/* The ambient field exists to give the blur something to refract. With
   no blur left it is just six gradients repainted on every scroll. */
.is-lite body::before {
  display: none;
}

/* ── Glass primitive ─────────────────────────────────────────────────── */

.glass {
  color: var(--c-content);
  background-color: color-mix(in srgb, var(--c-glass) 12%, transparent);
  border: none;
  backdrop-filter: blur(8px) saturate(var(--saturation));
  -webkit-backdrop-filter: blur(8px) saturate(var(--saturation));
  box-shadow: var(--glass-shadow);
  transition:
    transform 200ms,
    background-color 400ms var(--ease),
    box-shadow 400ms var(--ease),
    color 400ms var(--ease);
}

/* ── Shell ───────────────────────────────────────────────────────────── */

.shell {
  width: 100%;
  max-width: var(--shell);
  margin: 0 auto;
  padding: 0 clamp(20px, 4vw, 48px);
}

.section {
  padding: clamp(64px, 9vw, 130px) 0;
}


/* Section bands are the same glass, laid flat and edge to edge */
.section--paper,
.section--white,
.section--tint,
.section--navy {
  background-color: color-mix(in srgb, var(--c-glass) 10%, transparent);
  backdrop-filter: blur(8px) saturate(var(--saturation));
  -webkit-backdrop-filter: blur(8px) saturate(var(--saturation));
  box-shadow:
    inset 0 1px 0 color-mix(in srgb, var(--c-light) calc(var(--glass-reflex-light) * 70%), transparent),
    inset 0 -1px 0 color-mix(in srgb, var(--c-light) calc(var(--glass-reflex-light) * 40%), transparent);
}

/* A shade heavier, to separate it from the band above */
.section--navy {
  background-color: color-mix(in srgb, var(--c-glass) 20%, transparent);
}

.section--line {
  border-top: 1px solid color-mix(in srgb, var(--c-light) 60%, transparent);
}

/* A band that carries on from a full-bleed photograph above it instead
   of breaking back to paper. The other four bands are translucent glass
   over the page's own pale ground, which is exactly what shows as a
   white shelf under a dark image — so this one brings its own ground and
   drops the bright inset edges to a whisper.

   Pair it with .section--invert, which flips the ink and the glass
   tokens; on its own this is a dark band with dark type on it. */
.section--ink {
  background-color: #0b0e13;
  backdrop-filter: none;
  -webkit-backdrop-filter: none;
  box-shadow:
    inset 0 1px 0 rgba(255, 255, 255, 0.06),
    inset 0 -1px 0 rgba(255, 255, 255, 0.06);
}

/* Textured band. The SVG textures carry no ground of their own — they're
   patterns laid on transparency — so they go over the band's glass fill
   rather than in place of it. A negative z-index puts the texture above
   that fill but under the content, which is the whole trick here. The
   sheet itself is handed in per band as --texture.

   A photograph can go in the same slot, but it arrives opaque, and these
   bands carry dark ink. --texture-veil is the pane that goes over it to
   knock it back to the same whisper the SVGs sit at. */
.section--texture {
  position: relative;
  isolation: isolate;
  /* The sheet is blown up to 1.14 on .page--deep so it has margin to
     parallax into, which puts 7% of it outside the band at top and
     bottom. Unclipped that spills over whatever the band is sitting
     against — invisible between two pale bands, obvious the moment one
     of them follows a full-bleed photograph. */
  overflow: hidden;
}

.section--texture::before {
  content: '';
  position: absolute;
  inset: 0;
  z-index: -1;
  pointer-events: none;
  background-image:
    linear-gradient(var(--texture-veil, transparent), var(--texture-veil, transparent)),
    var(--texture);
  background-position: center;
  background-size: cover;
  background-repeat: no-repeat;
}

/* The light counterpart to .section--ink, and it exists for the same
   reason. .section--white is translucent glass that borrows its
   brightness from the page behind it, so on .page--ink it resolves to a
   dark band — a light band on a dark page has to bring its own ground.

   Takes the standard ink tokens, so it wants no .section--invert on it:
   dark type, exactly as on the other service pages. */
.section--bright {
  background-color: #fff;
  backdrop-filter: none;
  -webkit-backdrop-filter: none;
  /* No edge highlights: two of these bands sit directly on top of each
     other, and a seating line between them is exactly the seam this is
     meant not to have. */
  box-shadow: none;
}

/* A ground shared by a run of sections. Two adjacent .section--texture
   bands each scale their sheet to their own height and each parallax it
   on their own, so the join between them shows as a seam that travels
   while you scroll. Grouping the sections under one of these gives the
   run a single sheet and a single ground, and the seam has nowhere to
   come from.

   The sections inside must not carry .section--bright or any other band
   fill — an opaque background on a child paints straight over the sheet
   this is holding. */
.paper-band {
  background-color: #fff;
}

/* Fits the whole sheet into the band instead of filling the band with
   it. For art that is drawn to a frame — contact.png carries its vignettes
   down the outside edges and leaves the middle clear for the form — where
   cropping to the band's proportions cuts off the part that does the work.

   The band keeps its own height under this: the art is fitted into
   whatever room the copy asks for, not the other way round. Where the two
   shapes disagree the leftover shows as ground either side of the sheet,
   which is the trade a fitted sheet makes.

   The deep-page blow-up comes off with it. That 1.14 exists to give the
   parallax somewhere to travel, and a sheet that is fitted rather than
   filled has no margin to travel into — scaling it would only crop the
   picture again, which is the thing this is undoing. */
.section--texture-whole::before {
  background-size: contain;
  background-position: center;
}

.page--deep .section--texture-whole::before {
  transform: none;
}

/* The other way of showing a whole sheet, for art that is a photograph
   rather than a frame: fill the band and give the band the picture's shape
   so that filling it crops nothing.

   Fitting is the wrong tool for these, because it only lands cleanly if
   the band is already the sheet's shape, and left alone it never is: the
   width comes from the window, the height from whatever copy the band is
   holding. On a laptop that makes the band half again wider than any of
   these photographs, and contain pays for the mismatch in white gutters
   down both sides with the copy sitting off the edge of the picture.

   So the band takes the sheet's own ratio — --sheet-ratio, handed in per
   band from the file's own dimensions. At that ratio filling and fitting
   are the same thing: the sheet reaches all four edges with nothing
   cropped and nothing left over.

   The ratio is spent as a height and never as an aspect-ratio, which
   would be the obvious way to write this and is a trap: a ratio with a
   bounded height transfers back through itself, so the moment the ceiling
   below clamps the height the band takes its *width* from the ratio too
   and collapses to a column in the middle of the page.

   As a floor it only ever adds height, so copy taller than the picture
   still takes the room it needs. min() is the ceiling on it, stopping a
   wide window from turning the band into a full page of photograph. Where
   either of those puts the band off-ratio the sheet takes a shallow crop
   off the top and bottom — the better trade against bars down the sides,
   and why this fills rather than fits. */
.section--texture-fill {
  min-height: min(86svh, calc(100vw / (var(--sheet-ratio, 3 / 2))));
  /* Grid rather than flex, so .shell keeps the full width and the auto
     margins it centres itself with. align-content is the other half of
     the fix: once the band is the picture's height rather than the card
     stack's, the stack has to be held on the middle of the picture. */
  display: grid;
  align-content: center;
}

/* Nothing to parallax into here either — the sheet is sized to the band. */
.page--deep .section--texture-fill::before {
  transform: none;
}

/* A band whose photograph is meant to stay readable can't be veiled away,
   so the ink has to go the other direction instead: Clipo's dark tokens
   again, exactly as on .hero, and every card and every line inside flips
   with them. The card fill goes heavier too — a bare 12% is nowhere near
   enough cover for body copy once there's a picture behind it. */
.section--invert {
  --c-glass: #555555;
  --c-content: #f5f5f7;
  --glass-reflex-dark: 2;
  --glass-reflex-light: 0.4;
  color: var(--c-content);
}

/* The 8px blur these panes inherit is tuned for glass sitting on flat
   paper, where there's nothing behind worth keeping. Over a photograph
   it just smears the picture. Dropped to a hair of blur with the
   saturation and brightness pushed instead — the picture stays sharp
   through the pane, and the specular edges in --glass-shadow are what
   read as glass. */
.section--invert .step {
  background-color: color-mix(in srgb, var(--c-glass) 12%, transparent);
  backdrop-filter: blur(1.5px) saturate(190%) brightness(1.08);
  -webkit-backdrop-filter: blur(1.5px) saturate(190%) brightness(1.08);
}

.section--invert .step:hover {
  background-color: color-mix(in srgb, var(--c-glass) 24%, transparent);
}

/* With the fill this thin the picture comes most of the way through, so
   the type can't lean on the pane any more — it carries its own shadow
   instead. That keeps the glass clear and the copy legible, rather than
   trading one for the other. */
.section--invert .step-title,
.section--invert .step-text {
  text-shadow: 0 1px 12px rgba(0, 0, 0, 0.55);
}

.section--invert .step-text {
  color: color-mix(in srgb, var(--c-content) 88%, transparent);
}

/* The icons are drawn in navy and flattened to ink on the light bands;
   here they have to go the other way. */
.section--invert .step-mark img {
  filter: brightness(0) invert(1) opacity(0.9);
}

/* ── Section headings ────────────────────────────────────────────────── */

.section-head {
  max-width: 620px;
  margin-bottom: clamp(40px, 5vw, 72px);
}

.section-head--center {
  max-width: 900px;
  margin-left: auto;
  margin-right: auto;
  text-align: center;
}

/* Kept on one line; the lower clamp floor is what lets it hold on phones */
.section-head--center .section-title {
  white-space: nowrap;
  font-size: clamp(1.35rem, 3.4vw, 3rem);
}

.section-title {
  font-family: var(--display);
  font-size: clamp(1.9rem, 3.6vw, 3.1rem);
  font-weight: 600;
  line-height: 1.12;
  letter-spacing: -0.015em;
  color: var(--c-content);
  margin: 0;
}

.section-lede {
  margin: 18px 0 0;
  color: var(--ink-soft);
  font-size: 0.97rem;
  line-height: 1.75;
}

/* The small-caps label above a section title. Its colour comes off
   --ink-faint, which is derived from --c-content, so it inverts along
   with everything else the moment it lands inside a .section--invert
   band — no dark-band variant needed. */
.eyebrow {
  display: inline-flex;
  align-items: center;
  gap: 11px;
  margin-bottom: 15px;
  font-size: 0.63rem;
  font-weight: 700;
  letter-spacing: 0.24em;
  text-transform: uppercase;
  color: var(--ink-faint);
}

/* A rule running into the label, so it reads as a marker on the band
   rather than a stray line of small caps floating above the heading */
.eyebrow::before {
  content: '';
  width: 28px;
  height: 1px;
  flex-shrink: 0;
  background: currentColor;
  opacity: 0.55;
}

/* ── Header: logo plate + section switcher ────────────────────────────
   There is no bar any more. What sits over the top of the page is two
   floating panes on the same Clipo glass: the mark on the left, and a
   pill of the current page's own sections, centred.

   The pill carries a bubble that slides to whichever section you're in.
   It is absolutely positioned and moved with translateX rather than being
   re-laid-out, so the slide stays on the compositor; its width animates
   too, because these labels are not the same length. Ported from the
   Clipo build (js/script.js, .switcher__bubble). */

.site-logo {
  position: fixed;
  top: 22px;
  left: clamp(16px, 3vw, 34px);
  z-index: 60;
  height: 56px;
  display: grid;
  place-items: center;
  padding: 0 20px;
  border-radius: 99em;
  color: var(--c-content);
  background-color: color-mix(in srgb, var(--c-glass) 30%, transparent);
  backdrop-filter: var(--pane-backdrop);
  -webkit-backdrop-filter: var(--pane-backdrop);
  box-shadow: var(--glass-shadow);
  transition:
    transform 200ms,
    background-color 400ms var(--ease);
}

.site-logo:hover {
  transform: scale(1.04);
  background-color: color-mix(in srgb, var(--c-glass) 25%, transparent);
}

.site-logo img {
  height: 34px;
  width: auto;
}

.switcher {
  position: fixed;
  top: 22px;
  left: 50%;
  translate: -50%;
  z-index: 60;
  display: flex;
  align-items: center;
  height: 56px;
  max-width: calc(100vw - 32px);
  padding: 4px;
  border: none;
  border-radius: 99em;
  overflow: hidden;
  /* The bubble rides at z-index -1; this keeps it from falling behind
     the page instead of behind the labels. */
  isolation: isolate;
  touch-action: none;
  color: var(--c-content);
  background-color: color-mix(in srgb, var(--c-glass) 30%, transparent);
  backdrop-filter: var(--pane-backdrop);
  -webkit-backdrop-filter: var(--pane-backdrop);
  box-shadow: var(--glass-shadow);
  transition:
    background-color 400ms var(--ease),
    box-shadow 400ms var(--ease);
}

.switcher__option {
  position: relative;
  z-index: 2;
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 8px;
  height: 100%;
  padding: 0 20px;
  border-radius: 99em;
  font-size: 0.78rem;
  font-weight: 800;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  white-space: nowrap;
  text-decoration: none;
  color: var(--c-content);
  /* Barely faded — these read as ink first and inactive second; the
     bubble is what marks the current one, not a drop in weight */
  opacity: 0.8;
  cursor: pointer;
  transition: opacity 200ms var(--ease);
}

.switcher__option:hover {
  opacity: 0.94;
}

/* is-near is the one the bubble would land on if you let go now */
.switcher__option.is-active,
.switcher__option.is-near {
  opacity: 1;
}

/* The one under the bubble is the only draggable thing here, so it is the
   only one that advertises a grab */
.switcher__option.is-active {
  cursor: grab;
}

.switcher.is-dragging .switcher__option {
  cursor: grabbing;
}

/* Dragging across labels would otherwise select their text */
.switcher.is-dragging {
  user-select: none;
  -webkit-user-select: none;
}

.switcher__option:focus-visible {
  outline: 2px solid var(--c-content);
  outline-offset: -4px;
}

/* Labels carry the pill on desktop; the icons only appear once there
   isn't room for words */
.switcher__option i {
  display: none;
  font-size: 1.05rem;
}

.switcher__bubble {
  position: absolute;
  left: 0;
  top: 4px;
  z-index: -1;
  height: calc(100% - 8px);
  border-radius: 99em;
  background-color: color-mix(in srgb, var(--c-glass) 24%, transparent);
  box-shadow: var(--glass-shadow-btn);
  pointer-events: none;
  will-change: transform, width;
  transition:
    transform 520ms var(--ease-out),
    width 380ms var(--ease-out),
    background-color 400ms var(--ease);
}

.switcher.is-dragging {
  cursor: grabbing;
}

/* The transform tracks the finger with no easing at all — anything here
   would lag the drag. Width is the exception: it isn't following the
   pointer, it's previewing the option the bubble will land on, so it
   glides. */
.switcher.is-dragging .switcher__bubble {
  transition: width 220ms var(--ease-out);
  background-color: color-mix(in srgb, var(--c-glass) 34%, transparent);
}

/* The pair on the right, balancing the logo plate on the left. Same
   material and the same lifted backdrop, so all three panes read as one
   row floating over the page. */
.header-actions {
  position: fixed;
  top: 22px;
  right: clamp(16px, 3vw, 34px);
  z-index: 60;
  display: flex;
  gap: 10px;
}

.header-btn {
  height: 56px;
  display: inline-flex;
  align-items: center;
  gap: 9px;
  padding: 0 22px;
  border: none;
  border-radius: 99em;
  font-family: var(--sans);
  font-size: 0.7rem;
  font-weight: 800;
  letter-spacing: 0.13em;
  text-transform: uppercase;
  text-decoration: none;
  white-space: nowrap;
  cursor: pointer;
  color: var(--c-content);
  background-color: color-mix(in srgb, var(--c-glass) 26%, transparent);
  backdrop-filter: var(--pane-backdrop);
  -webkit-backdrop-filter: var(--pane-backdrop);
  box-shadow: var(--glass-shadow-btn);
  transition:
    transform 200ms,
    background-color 400ms var(--ease);
}

.header-btn:hover {
  transform: scale(1.04);
  background-color: color-mix(in srgb, var(--c-glass) 42%, transparent);
}

.header-btn:active {
  transform: scale(0.98);
}

.header-btn i {
  font-size: 1.02rem;
}

/* The enquiry leads its neighbour, on thicker glass — the same move
   .btn--solid makes further down the page */
.header-btn--solid {
  background-color: color-mix(in srgb, var(--c-glass) 46%, transparent);
}

.header-btn--solid:hover {
  background-color: color-mix(in srgb, var(--c-glass) 62%, transparent);
}

/* Anchored sections have to clear the pill when jumped to */
.section[id] {
  scroll-margin-top: 104px;
}

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

.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 10px;
  padding: 15px 30px;
  border: none;
  border-radius: 99em;
  font-family: var(--sans);
  font-size: 0.68rem;
  font-weight: 700;
  letter-spacing: 0.18em;
  text-transform: uppercase;
  text-decoration: none;
  cursor: pointer;
  color: var(--c-content);
  background-color: color-mix(in srgb, var(--c-glass) 12%, transparent);
  backdrop-filter: blur(8px) saturate(var(--saturation));
  -webkit-backdrop-filter: blur(8px) saturate(var(--saturation));
  box-shadow: var(--glass-shadow-btn);
  transition:
    transform 200ms,
    background-color 400ms var(--ease),
    box-shadow 400ms var(--ease),
    color 400ms var(--ease);
}

/* Clipo's only hover on the material: the fill roughly doubles */
.btn:hover {
  transform: scale(1.05);
  background-color: color-mix(in srgb, var(--c-glass) 25%, transparent);
}

.btn:active {
  transform: scale(0.98);
}

/* The primary sits on thicker glass so it leads its neighbour */
.btn--solid {
  background-color: color-mix(in srgb, var(--c-glass) 26%, transparent);
}

.btn--solid:hover {
  background-color: color-mix(in srgb, var(--c-glass) 40%, transparent);
}

/* The quieter of the two */
.btn--ghost {
  background-color: color-mix(in srgb, var(--c-glass) 10%, transparent);
}

.btn--gold {
  background-color: color-mix(in srgb, var(--c-glass) 32%, transparent);
}

.btn--block {
  width: 100%;
  padding: 18px 30px;
}

.btn--sm {
  padding: 12px 22px;
  font-size: 0.63rem;
}

/* Text link with a moving arrow */
.link-arrow {
  display: inline-flex;
  align-items: center;
  gap: 9px;
  font-size: 0.8rem;
  font-weight: 700;
  letter-spacing: 0.06em;
  color: var(--ink-soft);
  text-decoration: none;
  transition: color 300ms var(--ease);
}

.link-arrow i {
  transition: transform 400ms var(--ease-out);
}

.link-arrow:hover {
  color: var(--c-content);
}

.link-arrow:hover i {
  transform: translateX(5px);
}

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

.hero {
  position: relative;
  /* svh, not vh. On a phone vh measures the viewport at its tallest — the
     size it only reaches once the address bar has scrolled away — so a
     92vh hero arrives taller than the screen with its own bottom edge
     hidden behind the bar. svh is the short measure, which is what is
     actually on screen when the page loads, and unlike dvh it holds still
     instead of reflowing every time the bar slides. On a desktop the two
     are the same number. */
  min-height: min(92svh, 860px);
  display: flex;
  align-items: flex-end;
  padding: 150px 0 clamp(56px, 7vw, 96px);
  overflow: hidden;
  background: #0d1015;
}

.hero-media {
  position: absolute;
  inset: 0;
  background-position: center;
  background-size: cover;
  background-repeat: no-repeat;
}

/* The clip is the hero on the pages that have one — no still behind it,
   so the dark .hero fill is what shows until the first frame arrives. */
.hero-media video {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
}

/* Scrim — black-on-photo headings were unreadable without it */
.hero-media::after {
  content: '';
  position: absolute;
  inset: 0;
  background: linear-gradient(to bottom,
      rgba(10, 13, 17, 0.55) 0%,
      rgba(10, 13, 17, 0.2) 34%,
      rgba(10, 13, 17, 0.62) 78%,
      rgba(10, 13, 17, 0.88) 100%);
}

.hero-inner {
  position: relative;
  z-index: 2;
  max-width: 100%;
}

.hero-title {
  font-family: var(--display);
  font-size: clamp(1.6rem, 5.4vw, 4.6rem);
  font-weight: 600;
  line-height: 1.05;
  letter-spacing: -0.022em;
  color: var(--c-content);
  margin: 0;
  white-space: nowrap;
  text-shadow: 0 8px 40px rgba(0, 0, 0, 0.35);
}

.hero-lede {
  margin: 26px 0 0;
  max-width: 480px;
  color: var(--ink-soft);
  font-size: 1rem;
  line-height: 1.75;
}

/* Over a still, the standard soft ink is enough. Over a clip that keeps
   changing underneath it, the tone needs lifting and a shadow of its own
   — the scrim alone can't hold every frame. */
.hero .hero-lede {
  color: color-mix(in srgb, var(--c-content) 82%, transparent);
  text-shadow: 0 2px 20px rgba(0, 0, 0, 0.55);
}

.hero-actions {
  display: flex;
  flex-wrap: wrap;
  gap: 12px;
  margin-top: 36px;
}

/* ── Workflow steps ──────────────────────────────────────────────────── */

.grid {
  display: grid;
  gap: clamp(16px, 1.8vw, 26px);
}

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

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

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

.step {
  position: relative;
  padding: clamp(28px, 2.6vw, 40px) clamp(24px, 2.2vw, 32px);
  overflow: hidden;
  border: none;
  border-radius: var(--r-md);
  color: var(--c-content);
  background-color: color-mix(in srgb, var(--c-glass) 12%, transparent);
  backdrop-filter: blur(8px) saturate(var(--saturation));
  -webkit-backdrop-filter: blur(8px) saturate(var(--saturation));
  box-shadow: var(--glass-shadow);
  transition:
    transform 200ms,
    background-color 400ms var(--ease),
    box-shadow 400ms var(--ease);
}

.step:hover {
  transform: scale(1.03);
  background-color: color-mix(in srgb, var(--c-glass) 25%, transparent);
}

/* Ghosted step numeral */
.step-no {
  position: absolute;
  top: 12px;
  right: 20px;
  font-family: var(--display);
  font-size: 2.9rem;
  font-weight: 600;
  line-height: 1;
  color: color-mix(in srgb, var(--c-content) 12%, transparent);
  pointer-events: none;
}

.step-mark {
  width: 46px;
  height: 46px;
  display: grid;
  place-items: center;
  border-radius: 50%;
  margin-bottom: clamp(22px, 2.4vw, 32px);
  background-color: color-mix(in srgb, var(--c-glass) 14%, transparent);
  box-shadow: var(--glass-shadow-btn);
  transition: background-color 400ms var(--ease);
}

.step:hover .step-mark {
  background-color: color-mix(in srgb, var(--c-glass) 30%, transparent);
}

/* The icon files are drawn in brand navy; flattened to ink here rather
   than duplicated as a second set of assets. */
.step-mark img {
  width: 21px;
  height: 21px;
  object-fit: contain;
  filter: brightness(0) saturate(100%) opacity(0.85);
}

.step-title {
  font-family: var(--sans);
  font-size: 1.02rem;
  font-weight: 700;
  letter-spacing: -0.01em;
  margin: 0 0 9px;
  color: var(--c-content);
}

.step-text {
  margin: 0;
  color: var(--ink-soft);
  font-size: 0.88rem;
  line-height: 1.7;
}

/* ── How it works: cards beside a photograph ─────────────────────────
   The copy column leads slightly, because four cards in a 2x2 need more
   room than one picture does to stay legible. */

/* The picture column is gone — bg-2 is the band's own background now, so
   the copy is the only thing in here. Held to the right, which leaves the
   left of the sheet clear to be looked at. */
.how-grid {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  justify-items: end;
  /* Narrower than the standard shell rather than wider: right-aligned in
     a tighter column, the stack pulls in off the page edge and stops
     landing on the window in the sheet behind it. */
  max-width: min(100%, 1260px);
}

.how-copy {
  width: min(100%, 430px);
}

/* The section-head margin is sized for a heading with a band of its own
   beneath it; here it only has to clear the first card. */
.how-copy .section-head {
  margin-bottom: clamp(20px, 2.2vw, 30px);
}

/* Held on one line. The copy column is the narrow one, so the default
   clamp breaks the heading in two — the floor comes down to let it set
   across whatever width the column has. */
/* Capped to what fits 430px on one line — nowrap in a narrow column
   overflows rather than breaking, so the ceiling has to come down with
   the column. */
.how-copy .section-title {
  white-space: nowrap;
  font-size: clamp(1.25rem, 2.2vw, 2rem);
}

/* Four cards stacked, each one running across: mark on the left, copy
   beside it, numeral held at the right. */
.how-cards {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  gap: clamp(10px, 1.2vw, 14px);
}

/* Two items only — the mark and .step-copy. The title and text have to
   be wrapped for this: left loose as siblings they become grid items in
   their own right, and the text drops onto a second row while the title
   is squeezed into the trailing column. */
.how-cards .step {
  display: flex;
  align-items: center;
  gap: clamp(13px, 1.3vw, 17px);
  padding: clamp(13px, 1.4vw, 18px) clamp(15px, 1.5vw, 20px);
}

.how-cards .step-mark {
  flex-shrink: 0;
  transition:
    transform 450ms var(--ease-out),
    background-color 400ms var(--ease),
    box-shadow 400ms var(--ease);
}

.how-cards .step-copy {
  min-width: 0;
}

/* ── Two across ───────────────────────────────────────────────────────
   For a band whose steps are written long. The elevator copy runs to
   three lines a card where the other pages run to two, and one to a row
   makes a column taller than any picture can sit behind — the stack
   walks off the bottom of the sheet. Paired up it comes back to half the
   height, at the cost of half the picture's width. That is the cheaper
   of the two: the sheet has a clear right-hand third to give, and a
   stack that overruns its own band has nothing. */
/* The base 1260 cap is a shell's width, and against it the stack ends
   where the page's type column ends — which on a wide window leaves a
   margin of picture to the right of the cards that reads as the stack
   having drifted inboard. Widened here so the pair sits out on the right
   of the sheet the way a single column does against a narrower cap. */
.how-grid--split {
  max-width: min(100%, 1660px);
}

.how-grid--split .how-copy {
  width: min(100%, 880px);
}

.how-grid--split .how-cards {
  grid-template-columns: repeat(2, minmax(0, 1fr));
}

/* The nowrap ceiling on the heading is cut to what fits a 430px column.
   With twice that to set across, it can have the section's own size
   back. */
.how-grid--split .section-title {
  font-size: clamp(1.5rem, 2.6vw, 2.3rem);
}

/* ── Ink ──────────────────────────────────────────────────────────────
   Black, in three steps so the card has a hierarchy. The only thing kept
   from the coloured versions is the strength of the copy: at the page's
   --ink-soft, which is 58%, it washed into the coral wall and was the one
   thing on the card you had to work to read. */
.how-cards {
  --card-ink: #000;
  --card-ink-soft: rgba(0, 0, 0, 0.82);
  --card-ink-faint: rgba(0, 0, 0, 0.16);
}

.how-cards .step-title {
  color: var(--card-ink);
}

/* Up from 58% to 86%: the copy is the reason the card exists and it was
   the one thing on it you had to work to read. */
.how-cards .step-text {
  color: var(--card-ink-soft);
}

.how-cards .step-no {
  color: var(--card-ink-faint);
}

/* Back to the base sheet's treatment: flattened to ink, no recolouring. */
.how-cards .step-mark img {
  filter: brightness(0) saturate(100%) opacity(0.85);
}

/* Hover deepens the copy to full strength and brings the numeral up —
   the card gets more legible as you address it, not just brighter. */
.how-cards .step:hover .step-text {
  color: rgba(0, 0, 0, 0.95);
}

.how-cards .step:hover .step-no {
  color: rgba(0, 0, 0, 0.3);
}

/* The leading-edge rule and the sweep pick up the warm ink too, or they
   read as a stray cool line on a warm card. */
.how-cards .step::after {
  background: var(--card-ink);
  opacity: 0.55;
}

.how-cards .step-title,
.how-cards .step-text,
.how-cards .step-no {
  transition: color 420ms var(--ease);
}

/* ── Hover ──
   A light travelling across the face. Set off the left edge and moved
   right across the card, which is the direction the card itself reads in
   — a sweep that ran the other way would fight the type.

   Kept faint: it passes over the copy, not behind it, because the copy
   is not a positioned box and giving it a stacking context of its own to
   sit above this would mean wrapping it in another element. At 22% light
   it registers as a highlight rather than a wash. */
.how-cards .step::before {
  content: '';
  position: absolute;
  inset: 0;
  z-index: 2;
  pointer-events: none;
  background: linear-gradient(104deg,
      transparent 34%,
      color-mix(in srgb, var(--c-light) calc(var(--glass-reflex-light) * 22%), transparent) 50%,
      transparent 64%);
  transform: translateX(-115%);
  transition: transform 1s var(--ease-out);
}

.how-cards .step:hover::before {
  transform: translateX(115%);
}

/* A rule that grows up the leading edge. On a wide card the left edge is
   where the eye enters, so that is where the card acknowledges the
   cursor first. */
.how-cards .step::after {
  content: '';
  position: absolute;
  left: 0;
  top: 50%;
  width: 2px;
  height: 0;
  z-index: 2;
  pointer-events: none;
  transform: translateY(-50%);
  border-radius: 0 2px 2px 0;
  background: color-mix(in srgb, var(--c-content) 55%, transparent);
  transition: height 520ms var(--ease-out);
}

.how-cards .step:hover::after {
  height: 58%;
}

/* The mark comes forward and the fill lifts past the base 30% */
.how-cards .step:hover .step-mark {
  scale: 1.08;
  background-color: color-mix(in srgb, var(--c-glass) 38%, transparent);
  box-shadow:
    var(--glass-shadow-btn),
    0 8px 18px -8px rgba(6, 8, 12, 0.4);
}

/* The numeral is a ghost at rest — 12% of the ink. Bringing it up on
   hover is what makes the card feel like it woke up rather than just
   moved. */
.how-cards .step-no {
  transition: color 420ms var(--ease), transform 450ms var(--ease-out);
}

.how-cards .step:hover .step-no {
  color: color-mix(in srgb, var(--c-content) 26%, transparent);
}

/* The stacked card sets this to clear the icon; across, it is what would
   push the copy off the bottom of the row. */
.how-cards .step-mark {
  margin-bottom: 0;
}

/* Smaller and set against the right edge — at 2.9rem it collides with
   the copy now that the copy runs the full width of the card. */
.how-cards .step-no {
  top: 50%;
  right: 18px;
  transform: translateY(-50%);
  font-size: 1.9rem;
}

.how-cards .step-title {
  margin: 0 0 3px;
}

/* Kept clear of the numeral in the corner */
.how-cards .step-text {
  margin: 0;
  padding-right: 34px;
}

/* Just the photograph — no frame, no cast. Scaled by the width of its
   column rather than fitted inside the card stack's height, which is
   what was holding it small: the chair is wider than it is tall, so
   height was never the binding constraint. */
.how-media {
  display: flex;
  align-items: center;
  justify-content: flex-start;
}

.how-media img {
  width: 100%;
  height: auto;
  object-fit: contain;
}

/* Stacked, the picture goes under the cards: it is the illustration
   here, and leading with it would push the heading off the fold. */
/* Nothing left to sit beside, so the copy stops hugging the right and
   takes the band */
@media (max-width: 900px) {
  .how-grid {
    justify-items: stretch;
  }

  .how-copy {
    width: 100%;
  }

  /* Two columns were bought with the width of a wide band. Without it
     they are two cramped columns instead of one readable one, and the
     long copy that asked for the pairing is the first thing to suffer. */
  .how-grid--split .how-cards {
    grid-template-columns: minmax(0, 1fr);
  }
}

/* The numeral has nowhere to hide on a narrow card */
@media (max-width: 520px) {
  .how-cards .step-no {
    display: none;
  }

  .how-cards .step-text {
    padding-right: 0;
  }
}

/* ── Full-bleed video band ───────────────────────────────────────────── */

.video-band {
  position: relative;
  width: 100%;
  height: clamp(300px, 52vw, 660px);
  overflow: hidden;
  background: #0d1015;
  box-shadow:
    inset 0 1px 0 color-mix(in srgb, var(--c-light) 60%, transparent),
    inset 0 -1px 0 color-mix(in srgb, var(--c-light) 60%, transparent);
}

.video-band video {
  width: 100%;
  height: 100%;
  object-fit: cover;
}

/* ── Portfolio — expanding panels ────────────────────────────────────── */

.reel {
  display: flex;
  gap: 10px;
  height: clamp(420px, 52vw, 580px);
}

.reel-panel {
  position: relative;
  flex: 1 1 0;
  min-width: 0;
  border: none;
  border-radius: var(--r-md);
  overflow: hidden;
  cursor: pointer;
  background: #0d1015;
  box-shadow: var(--glass-shadow);
  transition: flex-grow 800ms var(--ease-out), box-shadow 500ms var(--ease);
}

.reel-panel.is-open {
  flex-grow: 3.4;
}

.reel-panel>img {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  transition: transform 1.1s var(--ease-out), filter 700ms ease;
  filter: grayscale(0.2) brightness(0.9);
}

.reel-panel.is-open>img {
  transform: scale(1.04);
  filter: none;
}

/* The scrim only has a title to carry now, not a paragraph, so it climbs
   nowhere near as far up the frame — the photograph is left alone above
   the bottom third. */
.reel-panel::after {
  content: '';
  position: absolute;
  inset: 0;
  background: linear-gradient(to top,
      rgba(8, 10, 14, 0.8) 0%,
      rgba(8, 10, 14, 0.4) 22%,
      rgba(8, 10, 14, 0.06) 45%,
      transparent 65%);
}

/* Collapsed: a vertical spine with the number and name */
.reel-tab {
  position: absolute;
  left: 0;
  right: 0;
  bottom: 0;
  z-index: 2;
  display: flex;
  align-items: center;
  gap: 14px;
  padding: 26px 22px;
  writing-mode: vertical-rl;
  transform: rotate(180deg);
  transform-origin: center;
  color: var(--c-content);
  transition: opacity 400ms var(--ease);
}

.reel-panel.is-open .reel-tab {
  opacity: 0;
  pointer-events: none;
}

.reel-tab b {
  font-family: var(--sans);
  font-size: 0.7rem;
  font-weight: 700;
  letter-spacing: 0.18em;
  color: var(--ink-soft);
}

.reel-tab span {
  font-family: var(--display);
  font-size: clamp(1rem, 1.3vw, 1.2rem);
  font-weight: 600;
  white-space: nowrap;
}

/* Expanded body — the title alone. It lands on whatever the photograph
   happens to be doing at the bottom of the frame, which is not always
   dark, so the shadow is what keeps it legible over a bright patch. */
.reel-body {
  position: absolute;
  left: 0;
  right: 0;
  bottom: 0;
  z-index: 3;
  padding: clamp(24px, 2.6vw, 40px);
  color: var(--c-content);
  text-shadow: 0 2px 12px rgba(0, 0, 0, 0.55);
  opacity: 0;
  transform: translateY(16px);
  transition: opacity 550ms var(--ease-out) 150ms, transform 550ms var(--ease-out) 150ms;
}

.reel-panel.is-open .reel-body {
  opacity: 1;
  transform: none;
}

.reel-title {
  font-family: var(--display);
  font-size: clamp(1.45rem, 2.2vw, 2.05rem);
  font-weight: 600;
  letter-spacing: -0.015em;
  margin: 0;
  white-space: nowrap;
}

/* A line of copy under the title, on the panels that carry one. Inside
   .reel-panel --c-content is already the light token, so this reads as
   softened white without naming a colour. */
.reel-text {
  margin: 11px 0 0;
  max-width: 46ch;
  font-size: 0.91rem;
  line-height: 1.7;
  color: color-mix(in srgb, var(--c-content) 80%, transparent);
}

/* The scrim above is cut short deliberately — it only has a title to
   carry on the other service pages. A panel with a paragraph needs it
   back, or the copy sits on bare photograph. Scoped with :has() so the
   title-only reels elsewhere keep the lighter wash. */
.reel-panel:has(.reel-text)::after {
  background: linear-gradient(to top,
      rgba(8, 10, 14, 0.88) 0%,
      rgba(8, 10, 14, 0.62) 24%,
      rgba(8, 10, 14, 0.24) 48%,
      transparent 76%);
}

/* ── Service cards ───────────────────────────────────────────────────── */

.card {
  display: flex;
  flex-direction: column;
  overflow: hidden;
  border: none;
  border-radius: var(--r-md);
  color: var(--c-content);
  background-color: color-mix(in srgb, var(--c-glass) 12%, transparent);
  backdrop-filter: blur(8px) saturate(var(--saturation));
  -webkit-backdrop-filter: blur(8px) saturate(var(--saturation));
  box-shadow: var(--glass-shadow);
  transition:
    transform 200ms,
    background-color 400ms var(--ease),
    box-shadow 400ms var(--ease);
}

.card:hover {
  transform: scale(1.02);
  background-color: color-mix(in srgb, var(--c-glass) 25%, transparent);
}

.card-media {
  overflow: hidden;
  height: 300px;
  flex-shrink: 0;
}

.card--sm .card-media {
  height: 240px;
}

.card-media img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  transition: transform 900ms var(--ease-out);
}

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

.card-body {
  padding: clamp(24px, 2.4vw, 36px);
  display: flex;
  flex-direction: column;
  flex: 1;
}

.card-index {
  font-size: 0.63rem;
  font-weight: 700;
  letter-spacing: 0.22em;
  text-transform: uppercase;
  color: var(--ink-faint);
}

.card-title {
  font-family: var(--display);
  font-size: clamp(1.3rem, 1.7vw, 1.6rem);
  font-weight: 600;
  letter-spacing: -0.01em;
  margin: 10px 0 14px;
  color: var(--c-content);
}

.card-text {
  margin: 0;
  color: var(--ink-soft);
  font-size: 0.9rem;
  line-height: 1.75;
}

/* Heavier glass, for a card with no photograph to carry it */
.card--quiet {
  justify-content: space-between;
  background-color: color-mix(in srgb, var(--c-glass) 26%, transparent);
}

.card--quiet:hover {
  background-color: color-mix(in srgb, var(--c-glass) 38%, transparent);
}

.card--quiet .link-arrow {
  margin-top: 32px;
}

/* ── Call to action strip ────────────────────────────────────────────
   A short band between the page and its form: heading on the left, the
   two ways of starting on the right. Deliberately not a .section — it
   runs tighter than one, because it is a turn in the page rather than
   another chapter of it. Pair it with .section--invert; it expects to
   sit on a photograph. */

.cta {
  padding: clamp(52px, 6.5vw, 92px) 0;
  box-shadow:
    inset 0 1px 0 color-mix(in srgb, var(--c-light) calc(var(--glass-reflex-light) * 50%), transparent),
    inset 0 -1px 0 color-mix(in srgb, var(--c-light) calc(var(--glass-reflex-light) * 50%), transparent);
}

.cta-inner {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: clamp(26px, 4vw, 56px);
}

.cta-title {
  margin: 0;
  font-family: var(--display);
  font-size: clamp(1.45rem, 2.9vw, 2.4rem);
  font-weight: 600;
  line-height: 1.2;
  letter-spacing: -0.018em;
  color: var(--c-content);
  /* The veil knocks the photograph back but can't flatten it, so the
     type carries its own cover the way .section--invert's cards do */
  text-shadow: 0 2px 18px rgba(0, 0, 0, 0.5);
}

.cta-actions {
  display: flex;
  flex-wrap: wrap;
  gap: 12px;
}

/* Below this the two halves are stacked, and a right-aligned pair of
   buttons under a left-aligned heading reads as a mistake */
@media (max-width: 720px) {
  .cta-actions {
    width: 100%;
  }

  .cta-actions .btn {
    flex: 1 1 auto;
  }
}

/* ── Notice panel (the placeholder pages) ────────────────────────────── */

.notice {
  max-width: 740px;
  margin: 0 auto;
  padding: clamp(40px, 5vw, 72px) clamp(26px, 4vw, 58px);
  text-align: center;
  border: none;
  border-radius: var(--r-lg);
  color: var(--c-content);
  background-color: color-mix(in srgb, var(--c-glass) 16%, transparent);
  backdrop-filter: blur(8px) saturate(var(--saturation));
  -webkit-backdrop-filter: blur(8px) saturate(var(--saturation));
  box-shadow: var(--glass-shadow);
}

.notice-title {
  font-family: var(--display);
  font-size: clamp(2.2rem, 4.8vw, 3.8rem);
  font-weight: 600;
  line-height: 1.06;
  letter-spacing: -0.02em;
  margin: 0 0 20px;
}

.notice p {
  margin: 0 auto 34px;
  max-width: 46ch;
  color: var(--ink-soft);
  line-height: 1.8;
}

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

.contact-grid {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: clamp(36px, 5vw, 80px);
  align-items: center;
}

.contact-line {
  display: flex;
  align-items: center;
  gap: 16px;
  margin-top: 36px;
}

.contact-line-mark {
  width: 48px;
  height: 48px;
  display: grid;
  place-items: center;
  border: none;
  border-radius: 50%;
  flex-shrink: 0;
  color: var(--c-content);
  background-color: color-mix(in srgb, var(--c-glass) 14%, transparent);
  backdrop-filter: blur(8px) saturate(var(--saturation));
  -webkit-backdrop-filter: blur(8px) saturate(var(--saturation));
  box-shadow: var(--glass-shadow-btn);
}

.contact-line small {
  display: block;
  font-size: 0.63rem;
  letter-spacing: 0.2em;
  text-transform: uppercase;
  color: var(--ink-faint);
}

.contact-line a {
  font-size: 1.06rem;
  font-weight: 700;
  text-decoration: none;
  color: var(--c-content);
  transition: opacity 300ms var(--ease);
}

.contact-line a:hover {
  opacity: 0.6;
}

.form-card {
  padding: clamp(28px, 3.4vw, 46px);
  border: none;
  border-radius: var(--r-lg);
  color: var(--c-content);
  background-color: color-mix(in srgb, var(--c-glass) 16%, transparent);
  backdrop-filter: blur(8px) saturate(var(--saturation));
  -webkit-backdrop-filter: blur(8px) saturate(var(--saturation));
  box-shadow: var(--glass-shadow);
}

.form-title {
  font-family: var(--display);
  font-size: 1.5rem;
  font-weight: 600;
  margin: 0 0 28px;
}

.field {
  margin-bottom: 18px;
}

/* Two short fields sharing a line. The gap is set on the column axis
   only — the fields keep their own bottom margin, so a row of two lines
   up with the full-width fields above and below it. */
.field-row {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 0 18px;
}

@media (max-width: 540px) {
  .field-row {
    grid-template-columns: minmax(0, 1fr);
  }
}

.field label {
  display: block;
  font-size: 0.62rem;
  font-weight: 600;
  letter-spacing: 0.2em;
  text-transform: uppercase;
  color: var(--ink-faint);
  margin-bottom: 9px;
}

/* Clipo's .glass-input — the same stack, seated inward */
.field input,
.field textarea {
  width: 100%;
  padding: 14px 18px;
  font-family: var(--sans);
  font-size: 0.95rem;
  color: var(--c-content);
  border: none;
  border-radius: var(--r-sm);
  outline: none;
  resize: none;
  background-color: color-mix(in srgb, var(--c-dark) 5%, transparent);
  backdrop-filter: blur(8px) saturate(var(--saturation));
  -webkit-backdrop-filter: blur(8px) saturate(var(--saturation));
  box-shadow: var(--glass-shadow-inset);
  transition: all 0.3s ease;
}

.field input::placeholder,
.field textarea::placeholder {
  color: #888;
  font-weight: 500;
}

.field input:focus,
.field textarea:focus {
  background-color: color-mix(in srgb, var(--c-light) 80%, transparent);
}

/* ── Contact band ────────────────────────────────────────────────────
   The ground is sampled off contact.png's own field, so wherever the
   sheet stops — the letterboxing that comes with showing it whole, and
   the strips a wide viewport leaves at either end — the band carries on
   in the same cream instead of showing a seam. */

.contact-band {
  background-color: #f3e7dc;
}

/* Centred in the gap the artwork leaves down the middle. Capped narrow:
   the vignettes close in from both sides, and a full-width card would
   sit on top of them. */
.contact-form {
  width: min(100%, 560px);
  margin-inline: auto;
}

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

.footer {
  padding: clamp(54px, 6vw, 78px) 0 40px;
  color: var(--c-content);
  background-color: color-mix(in srgb, var(--c-glass) 14%, transparent);
  backdrop-filter: blur(8px) saturate(var(--saturation));
  -webkit-backdrop-filter: blur(8px) saturate(var(--saturation));
  box-shadow: var(--glass-shadow-btn);
}

/* The brand column runs wider than the link columns — it carries the
   mark, a line of copy and the social row, where the others are lists.
   Four tracks where the other service pages use three: this page has a
   long enough service list to be worth its own column. The gap comes
   down to match, or four columns at 72px leave no room for the lists. */
.footer-grid {
  display: grid;
  grid-template-columns: 1.5fr 1fr 1fr 1.15fr;
  gap: clamp(28px, 3.4vw, 54px);
  padding-bottom: clamp(32px, 4vw, 48px);
  border-bottom: 1px solid var(--line);
}

/* A footer with three lists needs one of them to look like the way out */
.footer-cta {
  margin-top: 22px;
}

/* Standing on a photograph, the glass fill and its blur are only in the
   way — they smear the picture and add nothing, since the veil under the
   type is already doing the work of making it readable. */
.footer--photo {
  background-color: transparent;
  backdrop-filter: none;
  -webkit-backdrop-filter: none;
  box-shadow: none;
}

/* Everything on the photograph runs plain white. The ink tones this footer
   would otherwise inherit — --ink-soft at 58%, --ink-faint at 38% — are
   ratios drawn for type on paper, where a lighter grey still separates
   cleanly from the page. On a terracotta wall they just read as text that
   has been left half-finished. */
.footer--photo,
.footer--photo .footer-blurb,
.footer--photo .footer-head,
.footer--photo .footer-links a,
.footer--photo .footer-contact li,
.footer--photo .footer-contact a,
.footer--photo .footer-contact i,
.footer--photo .socials a,
.footer--photo .link-arrow {
  color: #fff;
}

/* The two labels stay white but sit back on weight and letter-spacing
   instead of on tone, so the columns still have a hierarchy. */
.footer--photo .footer-head {
  opacity: 0.72;
}

/* ── The pane ──
   The problem this solves: a veil heavy enough to carry white type across
   the whole band flattens the photograph to brown to do it, and a pane
   opaque enough to do it locally hides the room behind the columns. Both
   were tried; both cost the picture.

   So the cover is split three ways, none of them heavy. The veil is barely
   there (0.16) so the room reads everywhere. The pane blurs a little and
   darkens a little — enough to settle the wall's variation without
   erasing it. And the type carries a shadow of its own, which is what
   actually holds the small text, because a shadow costs the picture
   nothing at all: it only darkens the few pixels behind each glyph. */
.footer-pane {
  padding: clamp(26px, 3.2vw, 46px) clamp(24px, 3vw, 42px) clamp(20px, 2.4vw, 32px);
  border-radius: var(--r-lg);
  background-color: transparent;
  /* A hair of saturation, nothing else. Pushed hard it does not read as
     clearer glass, it reads as a different photograph — 190% took the
     terracotta wall to neon. 106% is the most that can go on before the
     colour stops being the one in the file. The pane's edges are the
     specular highlights in --glass-shadow, which cost the picture
     nothing. */
  backdrop-filter: saturate(106%);
  -webkit-backdrop-filter: saturate(106%);
  box-shadow: var(--glass-shadow);
}

/* No text-shadow. Stacked up thick enough to guarantee legibility over
   every part of the picture it turned into a black smear around each
   glyph, which costs more than it buys — white on this terracotta already
   clears AA on its own. */
.footer--photo .footer-grid,
.footer--photo .footer-note {
  text-shadow: none;
}

/* The note sits below the columns, on the pale floor strip at the foot of
   the picture rather than on the terracotta wall — so it is the one line
   in here that runs black instead of white, and it is centred to sit apart
   from the four columns above it. */
.footer--photo .footer-note {
  color: #000;
  text-align: center;
}

/* The pane holds the columns together, so the rule under them can drop
   back to a hairline — at the paper footer's weight it reads as a scratch
   across the glass. */
.footer-pane .footer-grid {
  border-bottom-color: color-mix(in srgb, var(--c-light) 20%, transparent);
}

/* The lite pass strips every backdrop-filter on the page, which would
   leave this as a 6% wash with white type on a photograph. Flat fill
   instead. */
.is-lite .footer-pane {
  background-color: rgba(28, 13, 6, 0.72);
}

/* ── Link hover ──
   Colour is not available: the resting state is already white, so there is
   nowhere brighter to go. It moves instead — a rule drawing in from the
   left while the label steps after it, which reads as the link being
   picked up rather than merely lit.

   inline-block is what gives the anchor a box to draw the rule across and
   a transform to shift; as inline it has neither. reveal-anim.js staggers
   the <li> around these, never the anchors, so transform here is free. */
.footer--photo .footer-links a,
.footer--photo .footer-contact a,
.footer--photo .link-arrow {
  position: relative;
  display: inline-block;
  transition: transform 460ms var(--ease-out), opacity 320ms var(--ease);
}

.footer--photo .footer-links a::after,
.footer--photo .footer-contact a::after {
  content: '';
  position: absolute;
  left: 0;
  right: 0;
  bottom: -2px;
  height: 1px;
  background: currentColor;
  opacity: 0.85;
  transform: scaleX(0);
  transform-origin: left center;
  transition: transform 520ms var(--ease-out);
}

.footer--photo .footer-links a:hover::after,
.footer--photo .footer-contact a:hover::after {
  transform: scaleX(1);
}

.footer--photo .footer-links a:hover,
.footer--photo .footer-contact a:hover {
  transform: translateX(5px);
}

/* The arrow already travels on its own; the label goes with it now */
.footer--photo .link-arrow:hover {
  transform: translateX(3px);
}

/* The heading label sits back on opacity rather than tone, so it lifts to
   full white on hover of the column it belongs to. */
.footer--photo .footer-col {
  transition: none;
}

.footer--photo .footer-col:hover .footer-head {
  opacity: 1;
}

.footer--photo .footer-head {
  transition: opacity 420ms var(--ease);
}

/* The social discs pick up the same idea: they come forward instead of
   changing colour. */
.footer--photo .socials a {
  transition:
    transform 420ms var(--ease-out),
    background-color 400ms var(--ease);
}

.footer--photo .socials a:hover {
  transform: translateY(-3px) scale(1.06);
  background-color: color-mix(in srgb, var(--c-light) 26%, transparent);
}

.footer-blurb {
  margin: 18px 0 26px;
  max-width: 34ch;
  font-size: 0.86rem;
  line-height: 1.75;
  color: var(--ink-soft);
}

.footer-head {
  margin: 0 0 20px;
  font-size: 0.63rem;
  font-weight: 600;
  letter-spacing: 0.2em;
  text-transform: uppercase;
  color: var(--ink-faint);
}

.footer-links,
.footer-contact {
  display: grid;
  gap: 13px;
  list-style: none;
  margin: 0;
  padding: 0;
}

.footer-links a,
.footer-contact a {
  text-decoration: none;
  color: var(--ink-soft);
  transition: color 300ms var(--ease);
}

.footer-links a:hover,
.footer-contact a:hover {
  color: var(--c-content);
}

.footer-links {
  font-size: 0.88rem;
}

.footer-contact li {
  display: flex;
  align-items: flex-start;
  gap: 12px;
  font-size: 0.88rem;
  line-height: 1.6;
  color: var(--ink-soft);
}

.footer-contact i {
  flex-shrink: 0;
  margin-top: 2px;
  font-size: 1rem;
  color: var(--ink-faint);
}

.footer-brand {
  display: flex;
  align-items: center;
  gap: 12px;
}

.footer-brand img {
  height: 40px;
  width: auto;
}

.footer-brand span {
  font-family: var(--display);
  font-size: 1rem;
  font-weight: 600;
  letter-spacing: 0.04em;
}

.footer-note {
  margin: clamp(24px, 3vw, 34px) 0 0;
  font-size: 0.74rem;
  color: var(--ink-soft);
}

/* The brand column takes the full width first, since it's the one that
   can't usefully halve; the two lists pair off beneath it. */
@media (max-width: 900px) {
  .footer-grid {
    grid-template-columns: repeat(2, minmax(0, 1fr));
  }

  .footer-col--brand {
    grid-column: 1 / -1;
  }
}

@media (max-width: 560px) {
  .footer-grid {
    grid-template-columns: minmax(0, 1fr);
  }
}

.socials {
  display: flex;
  gap: 10px;
}

.socials a {
  width: 38px;
  height: 38px;
  display: grid;
  place-items: center;
  border: none;
  border-radius: 50%;
  text-decoration: none;
  color: var(--ink-soft);
  background-color: color-mix(in srgb, var(--c-glass) 12%, transparent);
  backdrop-filter: blur(8px) saturate(var(--saturation));
  -webkit-backdrop-filter: blur(8px) saturate(var(--saturation));
  box-shadow: var(--glass-shadow-btn);
  transition:
    transform 200ms,
    background-color 400ms var(--ease),
    color 400ms var(--ease);
}

.socials a:hover {
  transform: scale(1.05);
  background-color: color-mix(in srgb, var(--c-glass) 25%, transparent);
  color: var(--c-content);
}

/* ── WhatsApp ────────────────────────────────────────────────────────── */

.wa {
  position: fixed;
  right: 26px;
  bottom: 26px;
  z-index: 70;
  width: 56px;
  height: 56px;
  display: grid;
  place-items: center;
  border: none;
  border-radius: 50%;
  text-decoration: none;
  color: var(--c-content);
  background-color: color-mix(in srgb, var(--c-glass) 22%, transparent);
  backdrop-filter: blur(8px) saturate(var(--saturation));
  -webkit-backdrop-filter: blur(8px) saturate(var(--saturation));
  box-shadow: var(--glass-shadow-btn);
  transition: transform 200ms, background-color 400ms var(--ease);
}

.wa:hover {
  transform: scale(1.07);
  background-color: color-mix(in srgb, var(--c-glass) 36%, transparent);
}

.wa span {
  position: absolute;
  right: 68px;
  white-space: nowrap;
  padding: 10px 16px;
  border: none;
  border-radius: 99em;
  font-size: 0.72rem;
  color: var(--c-content);
  background-color: color-mix(in srgb, var(--c-glass) 22%, transparent);
  backdrop-filter: blur(8px) saturate(var(--saturation));
  -webkit-backdrop-filter: blur(8px) saturate(var(--saturation));
  box-shadow: var(--glass-shadow-btn);
  opacity: 0;
  visibility: hidden;
  transition: opacity 300ms var(--ease), visibility 300ms var(--ease);
}

.wa:hover span {
  opacity: 1;
  visibility: visible;
}

/* ── Responsive ──────────────────────────────────────────────────────── */

@media (max-width: 1080px) {

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

/* Below this the panels are too narrow to expand — stack them open instead */
@media (max-width: 1000px) {
  .reel {
    flex-direction: column;
    height: auto;
    gap: 14px;
  }

  .reel-panel,
  .reel-panel.is-open {
    flex: 0 0 auto;
    height: clamp(280px, 46vw, 380px);
  }

  .reel-panel>img {
    filter: none;
  }

  .reel-tab {
    display: none;
  }

  .reel-body {
    opacity: 1;
    transform: none;
  }

  .reel-title {
    white-space: normal;
  }
}

@media (max-width: 900px) {

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

  .hero {
    min-height: 78svh;
    padding-top: 120px;
  }
}

/* Three panes on one line is the first thing to give. The buttons shed
   their labels down to icons, which buys the pill its full width back. */
@media (max-width: 1180px) {
  .header-btn {
    padding: 0;
    width: 56px;
    justify-content: center;
  }

  .header-btn span {
    display: none;
  }

  .header-btn i {
    font-size: 1.2rem;
  }
}

/* Below this the four labels can't share the line either, so the pill
   drops to icons too. The bubble measures whatever the options end up
   being, so nothing in the script needs to know about this. */
@media (max-width: 860px) {
  .site-logo,
  .switcher,
  .header-btn {
    height: 50px;
  }

  .site-logo {
    padding: 0 15px;
  }

  .site-logo img {
    height: 28px;
  }

  .header-btn {
    width: 50px;
  }

  .switcher__option {
    padding: 0 17px;
  }

  .switcher__option span {
    display: none;
  }

  .switcher__option i {
    display: block;
    font-size: 1.2rem;
  }
}

/* On a phone the three panes stop sharing a line. The logo and the two
   buttons keep the top corners; the pill goes to the bottom of the
   screen, where a thumb actually reaches, and shrinks to suit. */
@media (max-width: 700px) {
  .switcher {
    top: auto;
    bottom: calc(16px + env(safe-area-inset-bottom, 0px));
    height: 54px;
    max-width: calc(100vw - 24px);
  }

  /* Words come back at the bottom — the pill has the full width to
     itself down here, which it never had sharing the top row with the
     logo and the buttons. Tightly tracked so all four still fit across
     a phone. */
  .switcher__option {
    padding: 0 13px;
    font-size: 0.64rem;
    letter-spacing: 0.05em;
  }

  .switcher__option span {
    display: block;
  }

  .switcher__option i {
    display: none;
  }

  /* Same reason in reverse: with the pill gone from the top row there is
     nothing left up here for the buttons to compete with, so they drop
     the icons and say what they do. */
  .site-logo,
  .header-btn {
    height: 46px;
  }

  .header-btn {
    width: auto;
    padding: 0 14px;
    font-size: 0.58rem;
    letter-spacing: 0.08em;
  }

  .header-btn span {
    display: inline;
  }

  .header-btn i {
    display: none;
  }

  /* Only the top row has to be cleared now */
  .section[id] {
    scroll-margin-top: 88px;
  }

  /* The title holds one line everywhere else, but the longer service
     names can't do it at this width */
  .hero-title {
    white-space: normal;
  }

  /* The chat bubble sits bottom-right; lift it clear of the pill */
  .wa {
    bottom: calc(76px + env(safe-area-inset-bottom, 0px));
  }
}

/* The smallest phones still in use. "How It Works" is what runs out of
   room first along the bottom, and the two buttons plus the logo are what
   run out along the top, so both tighten a notch rather than truncating. */
@media (max-width: 400px) {
  .switcher__option {
    padding: 0 10px;
    font-size: 0.58rem;
    letter-spacing: 0.03em;
  }

  .header-btn {
    padding: 0 11px;
    font-size: 0.53rem;
    letter-spacing: 0.05em;
  }

  .header-actions {
    gap: 7px;
  }
}

@media (max-width: 680px) {

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

  .card-media,
  .card--sm .card-media {
    height: 220px;
  }

}

/* ── Touch ───────────────────────────────────────────────────────────
   Not a width question. A hover on a device with no pointer to hover
   with either never fires or, worse, fires on tap and then sticks until
   you touch something else — so the things that only exist on hover have
   to be told they aren't available here. */
@media (hover: none) {

  /* The label unfurls from the chat button on hover. There is no hover;
     the icon says what it is on its own, and the label would only sit
     across the page waiting for a tap that reveals it. */
  .wa span {
    display: none;
  }

  /* These lift or slide on hover and, tapped, stay lifted. Left at rest
     on touch, where the tap itself is the feedback. */
  .wa:hover,
  .socials a:hover,
  .footer--photo .footer-links a:hover,
  .footer--photo .footer-contact a:hover,
  .footer--photo .link-arrow:hover {
    transform: none;
  }
}

/* ── Phones ────────────────────────────────────────────────────────── */
@media (max-width: 700px) {

  /* Nothing should be able to push the page wider than the phone: a long
     unbroken string — an address, a URL — is the usual culprit, and an
     image given a width in the markup is the other. */
  img {
    max-width: 100%;
  }

  body {
    overflow-wrap: break-word;
  }

  /* 44px is the smallest thing a finger reliably hits. The social row is
     drawn at 38 for a mouse. */
  .socials a {
    width: 44px;
    height: 44px;
  }

  .socials {
    gap: 12px;
  }

  /* The hero's own floor, in the same short-viewport measure as the rule
     it overrides */
  .hero {
    min-height: 78svh;
  }

  /* The pane is a frame around the columns; on a phone the columns are
     the full width and the frame is just inset it can't afford. */
  .footer-pane {
    padding: 22px 18px 18px;
  }

  .footer {
    /* Clear of the section pill, which lives along the bottom edge from
       700px down */
    padding-bottom: calc(78px + env(safe-area-inset-bottom, 0px));
  }
}

/* iOS Safari zooms the page in when a field smaller than 16px takes
   focus, and does not zoom back out. Every input on the site is held at
   the threshold rather than under it — the fields are wide enough here
   that the extra half-point costs nothing. */
@media (max-width: 620px) {

  .field input,
  .field textarea {
    font-size: 16px;
  }
}

@media (prefers-reduced-motion: reduce) {
  * {
    animation-duration: 0.01ms !important;
    transition-duration: 0.01ms !important;
  }

  html {
    scroll-behavior: auto;
  }
}

/* ==========================================================================
   DEPTH — .page--deep
   ==========================================================================
   An opt-in layer, worn by the residential page alone. Nothing here adds
   or moves content: it takes the flat page and gives it a Z axis.

   Three separate ideas, and it is worth keeping them apart:

   1. Scroll parallax. Layers that should read as further away travel
      slower than the scroll. Driven from pages.js, which writes numbers
      into the custom properties below — the stylesheet still owns what
      those numbers mean, and turning the whole thing off is a matter of
      dropping one class from <body>.

   2. Pointer tilt. The cards and the portfolio panels rotate a couple of
      degrees toward the cursor, on a real perspective, so they read as
      panes with thickness rather than rectangles.

   3. Separation inside those cards. The numeral, the icon and the type
      slide against each other as the card turns, by different amounts —
      which is the part that makes it look built rather than filtered.
      Note this is done with plain 2D offsets rather than preserve-3d:
      these cards carry overflow:hidden and a backdrop-filter, and either
      one on its own forces a 3D context back to flat. Driving the shift
      from the pointer position gets the same read and cannot be
      flattened out from under us.

   Every transform here is composited, and every one of them is skipped
   for prefers-reduced-motion and for the lite pass — see the guard in
   pages.js and the reset at the bottom of this block. */

/* ── 1. Scroll parallax ─────────────────────────────────────────────── */

/* The clip drifts at roughly a third of the scroll and creeps wider as it
   goes, so the hero pulls away rather than sliding off. It starts a touch
   over-scaled to give the drift somewhere to go without exposing an edge. */
.page--deep .hero-media {
  transform: translate3d(0, var(--hero-y, 0px), 0) scale(var(--hero-s, 1.06));
  will-change: transform;
}

/* Type leaves faster than the picture, and each line faster than the one
   above it — the spread between these three rates is the depth cue. */
.page--deep .hero-inner {
  opacity: var(--hero-o, 1);
}

.page--deep .hero-title {
  transform: translate3d(0, var(--l1, 0px), 0);
}

.page--deep .hero-lede {
  transform: translate3d(0, var(--l2, 0px), 0);
}

.page--deep .hero-actions {
  transform: translate3d(0, var(--l3, 0px), 0);
}

/* The band textures sit behind their own content and move against it.
   Scaled up first so there is margin to travel into — at 1.14 the sheet
   can move ±7% of its height without ever showing the band beneath. */
.page--deep .section--texture::before {
  transform: translate3d(0, var(--tex-y, 0px), 0) scale(1.14);
  will-change: transform;
}

/* ── 2. Pointer tilt ────────────────────────────────────────────────── */

/* Perspective belongs on the container, not the panel: one vanishing
   point shared by the row is what makes the panels feel like they are in
   the same room. Set per-element it would give each its own, and the row
   would splay. */
.page--deep .reel {
  perspective: 1600px;
}

.page--deep .reel-panel {
  transform: rotateX(var(--rx, 0deg)) rotateY(var(--ry, 0deg)) translateZ(0);
  transition:
    flex-grow 800ms var(--ease-out),
    box-shadow 500ms var(--ease),
    transform 500ms var(--ease-out);
}

/* The photograph inside slides the other way against the tilt. This is
   the whole of the illusion — a pane of glass with a picture behind it
   shows you a little more of one edge as you turn it. */
.page--deep .reel-panel>img {
  transform: scale(1.1) translate3d(var(--ix, 0px), var(--iy, 0px), 0);
}

.page--deep .reel-panel.is-open>img {
  transform: scale(1.14) translate3d(var(--ix, 0px), var(--iy, 0px), 0);
}

/* Lifted while the cursor is on it, so the open panel casts further than
   its neighbours instead of only growing wider. */
.page--deep .reel-panel.is-open {
  box-shadow:
    var(--glass-shadow),
    0 40px 80px -30px rgba(6, 8, 12, 0.7);
}

.page--deep .grid--4 {
  perspective: 1300px;
}

/* ── The horizontal card stack ──────────────────────────────────────
   No tilt and no translateZ on these, unlike .grid--4, and the reason is
   text legibility rather than taste.

   Under a perspective, translateZ magnifies the element. The browser
   rasterises the card once at its layout size and then scales that
   bitmap to fill the magnified box, so every glyph is resampled and the
   copy goes soft the moment you hover it. rotateX/rotateY resample it
   too. On .grid--4 the cards carry a short title and a line of text and
   it passes; here each card is mostly copy, at body size, and it is the
   first thing you notice.

   So the lift is 2D: translateY moves the box on whole pixels and the
   text is composited sharp. The depth comes from the shadow instead,
   which is doing most of the work of a lift anyway. */
/* The lift is on `translate`, not `transform`, and that is not a stylistic
   choice. reveal-anim.js animates these cards in from the right, which
   means it owns their transform — an inline transform beats a stylesheet,
   so a hover written as transform would simply stop working once the
   entrance had run. `translate` is a separate property that composes with
   transform instead of replacing it, so the two coexist.

   Still 2D: a translateZ lift under a perspective magnifies the card, and
   the browser scales the already-rasterised text to fit, which is what was
   making the copy go soft on hover. */
.page--deep .how-cards .step {
  translate: none;
  transition:
    translate 420ms var(--ease-out),
    background-color 400ms var(--ease),
    box-shadow 420ms var(--ease);
}

.page--deep .how-cards .step:hover {
  translate: 0 -5px;
  box-shadow:
    var(--glass-shadow),
    0 22px 44px -22px rgba(6, 8, 12, 0.5);
}

/* The cursor parallax comes off too. It is only legible as separation
   when the card around it is turning, and the offsets on the numeral and
   the mark just read as drift on a card that is holding still. */
.page--deep .how-cards .step-no,
.page--deep .how-cards .step-mark,
.page--deep .how-cards .step-title,
.page--deep .how-cards .step-text {
  transform: none;
}

.page--deep .step {
  transform:
    rotateX(var(--rx, 0deg)) rotateY(var(--ry, 0deg)) translateZ(var(--tz, 0px));
  transition:
    transform 450ms var(--ease-out),
    background-color 400ms var(--ease),
    box-shadow 400ms var(--ease);
}

/* Overrides the flat scale hover from the base sheet — the card comes
   toward you now instead of getting bigger, which is not the same thing. */
.page--deep .step:hover {
  transform:
    rotateX(var(--rx, 0deg)) rotateY(var(--ry, 0deg)) translateZ(30px);
  box-shadow:
    var(--glass-shadow),
    0 30px 60px -28px rgba(6, 8, 12, 0.6);
}

/* ── 3. Separation inside the cards ─────────────────────────────────── */

/* --mx / --my are the cursor's position on the card, -0.5 to 0.5 from
   its centre, written once per card by pages.js. Each child multiplies
   that by its own distance: the further forward a thing is meant to
   read, the further it travels. Ordered the way the eye reads them —
   numeral furthest forward, body copy nearly on the face.

   They move *with* the cursor while the card face turns away from it,
   and that opposition is what sells the depth. */
.page--deep .step-no {
  transform: translate3d(calc(var(--mx, 0) * 20px), calc(var(--my, 0) * 20px), 0);
  transition: transform 450ms var(--ease-out);
}

.page--deep .step-mark {
  transform: translate3d(calc(var(--mx, 0) * 13px), calc(var(--my, 0) * 13px), 0);
  transition:
    transform 450ms var(--ease-out),
    background-color 400ms var(--ease);
}

.page--deep .step-title {
  transform: translate3d(calc(var(--mx, 0) * 8px), calc(var(--my, 0) * 8px), 0);
  transition: transform 450ms var(--ease-out);
}

.page--deep .step-text {
  transform: translate3d(calc(var(--mx, 0) * 4px), calc(var(--my, 0) * 4px), 0);
  transition: transform 450ms var(--ease-out);
}

/* ── Scroll reveal ────────────────────────────────────────────────────
   Layer rules taken from divyashrma18/scroll-magic (Hero.css). The stack
   is two photographs of one room from one camera position, laid exactly
   on top of each other; room-reveal.js walks the clip edge of the upper
   one across, and the room furnishes itself.

   Everything here exists to keep the two frames registered. They share
   inset, size and object-fit, and neither carries a transform of its own
   — offset one by a pixel and the wipe stops being one room and becomes
   two photographs sliding past each other. */

.room-reveal {
  position: relative;
  width: 100%;
  /* The wipe is pinned for the length of one screen, so this has to be
     the screen the phone actually has — see the note on .hero. */
  height: 100svh;
  overflow: hidden;
  background: #000;
}

.room-reveal .layer {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  transform-origin: center center;
  will-change: transform, opacity, clip-path;
}

.room-reveal .unfurnished {
  z-index: 2;
}

/* Fully clipped at rest, so the bare room is what shows before the
   timeline is built — and stays showing if GSAP never arrives. */
.room-reveal .furnished {
  z-index: 3;
  clip-path: inset(0 100% 0 0);
}

/* ── Off switch ──
   No pin and no scrub on a lite device or under reduced motion, so the
   section is one screen that never moves. The furnished room is the one
   worth showing, so it is the one left uncovered. */
.is-lite .room-reveal .furnished,
.room-reveal.is-static .furnished {
  clip-path: inset(0 0% 0 0);
}

@media (prefers-reduced-motion: reduce) {
  .room-reveal .furnished {
    clip-path: inset(0 0% 0 0);
  }
}

/* ── Scroll choreography support ──────────────────────────────────────
   Structure for reveal-anim.js to animate. Deliberately no hidden states
   here: everything is visible at rest and the script sets its own
   from-states, so a page whose CDN scripts never arrive is a static page
   rather than a blank one. */

/* One word of a split heading. The wrapper clips, the inner span moves,
   so the word rises out from behind its own baseline. */
.sw {
  display: inline-block;
  overflow: hidden;
  vertical-align: top;
  /* Descenders sit below the baseline, and overflow: hidden would shave
     the tails off every g, y and p. The padding gives them somewhere to
     live and the negative margin takes the line-height back. */
  padding-bottom: 0.14em;
  margin-bottom: -0.14em;
}

/* No will-change here on purpose. A heading of six words is six of these,
   and the page has three headings — declaring will-change on all of them
   holds eighteen composited layers alive for the whole session, on a page
   that is already asking a lot of a weak GPU. GSAP promotes each span for
   the duration of its own tween and lets it go afterwards, which is what
   we actually want. */
.sw-i {
  display: inline-block;
}

/* ── Off switch ─────────────────────────────────────────────────────── */

/* The lite pass and reduced motion get the flat page back exactly as the
   other four pages have it. pages.js never starts the scroll loop in
   either case, so these only have to undo the resting state. */
.is-lite .page--deep .hero-media,
.is-lite .page--deep .hero-title,
.is-lite .page--deep .hero-lede,
.is-lite .page--deep .hero-actions,
.is-lite .page--deep .section--texture::before,
.is-lite .page--deep .step,
.is-lite .page--deep .step-no,
.is-lite .page--deep .step-mark,
.is-lite .page--deep .step-title,
.is-lite .page--deep .step-text,
.is-lite .page--deep .reel-panel {
  transform: none !important;
}

@media (prefers-reduced-motion: reduce) {

  .page--deep .hero-media,
  .page--deep .hero-title,
  .page--deep .hero-lede,
  .page--deep .hero-actions,
  .page--deep .section--texture::before,
  .page--deep .step,
  .page--deep .step:hover,
  .page--deep .step-no,
  .page--deep .step-mark,
  .page--deep .step-title,
  .page--deep .step-text,
  .page--deep .reel-panel {
    transform: none !important;
  }

  .page--deep .hero-inner {
    opacity: 1 !important;
  }
}
