/* ==========================================================================
   Webessy Studios — site styles
   Palette taken from the hero footage. Orange is the only warm colour and is
   reserved for the call to action and the words "unfair advantage".
   ========================================================================== */

/* ---------- fonts (self-hosted, SIL Open Font License — see fonts/OFL-*.txt) ---------- */
/* latin files hold the basics; the small -ext files carry the ₹ sign and only load when it is used */
@font-face {
  font-family: "Fraunces"; font-style: normal; font-weight: 100 900; font-display: swap;
  src: url("../fonts/fraunces-opsz-normal.woff2") format("woff2");
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
  font-family: "Fraunces"; font-style: normal; font-weight: 100 900; font-display: swap;
  src: url("../fonts/fraunces-opsz-normal-ext.woff2") format("woff2");
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+1E00-1E9F, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
@font-face {
  font-family: "Fraunces"; font-style: italic; font-weight: 100 900; font-display: swap;
  src: url("../fonts/fraunces-opsz-italic.woff2") format("woff2");
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
  font-family: "Instrument Sans"; font-style: normal; font-weight: 400; font-display: swap;
  src: url("../fonts/instrument-sans-400.woff2") format("woff2");
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
  font-family: "Instrument Sans"; font-style: normal; font-weight: 400; font-display: swap;
  src: url("../fonts/instrument-sans-400-ext.woff2") format("woff2");
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+1E00-1E9F, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
@font-face {
  font-family: "Instrument Sans"; font-style: normal; font-weight: 500; font-display: swap;
  src: url("../fonts/instrument-sans-500.woff2") format("woff2");
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
  font-family: "Instrument Sans"; font-style: normal; font-weight: 500; font-display: swap;
  src: url("../fonts/instrument-sans-500-ext.woff2") format("woff2");
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+1E00-1E9F, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
@font-face {
  font-family: "JetBrains Mono"; font-style: normal; font-weight: 400; font-display: swap;
  src: url("../fonts/jetbrains-mono-400.woff2") format("woff2");
}
/* Bodoni Moda: the hero's big title cards only. Built from the full font (not
   Google's copy, which drops them) so the small caps are real small caps. */
@font-face {
  font-family: "Bodoni Moda"; font-style: normal; font-weight: 400 900; font-display: swap;
  src: url("../fonts/bodoni-moda-opsz-normal.woff2") format("woff2");
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}

:root {
  --bg: #05060F;
  --deep: #1B1F5E;
  --violet: #5B3FD9;
  --lavender: #8E7CFF;
  --blue: #3D7BFF;
  --ink: #ECEAFB;
  --muted: rgba(236, 234, 251, .62);
  --orange: #FF561D;
  --hair: rgba(236, 234, 251, .12);
  --surface: #0B0C18;

  /* section palette (below the hero). The dark "ink" is --ink-dark here,
     because --ink above is the hero's and the nav's light text colour. */
  --bone: #ECE7DA;
  --ink-dark: #0C0C0A;
  --ultramarine: #1B1F5E;
  --green: #79A643;
  --orange-deep: #E84A12; /* the orange accent word on bone — large headings only */

  --font-display: "Fraunces", Georgia, "Times New Roman", serif;
  --font-body: "Instrument Sans", system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;
  --font-mono: "JetBrains Mono", ui-monospace, Menlo, Consolas, monospace;
  --font-hero: "Bodoni Moda", "Bodoni 72", Didot, Georgia, serif;

  --t-display: clamp(44px, 6.2vw, 96px);
  --t-h3: clamp(24px, 2.4vw, 34px);
  --t-price: clamp(56px, 8vw, 120px);
  --t-body: 16px;
  --t-small: 14px;
  --t-label: 14px; /* 11.5px from 720px: no text on a phone is under 14px */

  --gutter: clamp(20px, 5vw, 64px);
  --gap: clamp(16px, 2vw, 28px);
  --section-pad: clamp(96px, 14vh, 180px);
}
@media (min-width: 720px) { :root { --t-body: 17px; --t-label: 11.5px; } }
* { margin: 0; padding: 0; box-sizing: border-box; }
html, body { background: var(--bg); color: var(--ink); }
body { font-family: var(--font-body); overflow-x: hidden; -webkit-font-smoothing: antialiased; }
a { color: inherit; text-decoration: none; }
html.intro-lock, html.intro-lock body { overflow: hidden; height: 100%; }

/* ---------- section colours ----------
   Every section below the hero names its colour in the HTML: data-theme="bone".
   The theme sets the tokens that section CSS uses:
     --page     background              --fg       text
     --fg-soft  secondary text          --line     hairlines
     --accent   the one italic accent word in a heading
     --btn-bg / --btn-fg                the main button
   Every text colour here passes WCAG AA on its own background.
   :root holds the black theme, so things outside a section have values too. */
:root, [data-theme="black"] {
  --page: var(--bg); --fg: var(--bone); --fg-soft: rgba(236, 231, 218, .62); --line: rgba(236, 231, 218, .14);
  --accent: var(--orange); --btn-bg: var(--orange); --btn-fg: var(--ink-dark);
}
[data-theme="bone"] {
  --page: var(--bone); --fg: var(--ink-dark); --fg-soft: rgba(12, 12, 10, .66); --line: rgba(12, 12, 10, .16);
  --accent: var(--orange-deep); --btn-bg: var(--orange); --btn-fg: var(--ink-dark);
}
[data-theme="ultramarine"] {
  --page: var(--ultramarine); --fg: var(--bone); --fg-soft: rgba(236, 231, 218, .66); --line: rgba(236, 231, 218, .16);
  --accent: var(--orange); --btn-bg: var(--orange); --btn-fg: var(--ink-dark);
}
[data-theme="orange"] {
  /* orange can't be the accent on orange, so the accent word stays ink, and the main button turns ink */
  --page: var(--orange); --fg: var(--ink-dark); --fg-soft: rgba(12, 12, 10, .80); --line: rgba(12, 12, 10, .24);
  --accent: var(--ink-dark); --btn-bg: var(--ink-dark); --btn-fg: var(--bone);
}

/* Without JavaScript each section paints its own colour, with hard edges.
   sections.js moves the theme onto <html> instead (each section keeps its own
   colour in data-band) and fades one fixed layer, .page-bg, behind the page. */
body [data-theme] { background: var(--page); color: var(--fg); }
[data-band] { color: var(--fg); }
html.theme-live body { background: transparent; }
.page-bg { position: fixed; left: 0; top: 0; z-index: -1; width: 100%; height: 100vh; height: 100lvh; pointer-events: none; }
.page-bg > div { position: absolute; inset: 0; }

/* The nav over bone and orange sections: links at full strength, and over bone
   a darker glass, so the links pass AA. Nothing else in the nav changes, and
   over the hero, 03 and black sections it looks exactly as before.
   The 04 panels too: 04 is black, but its panels are bone, orange, violet and
   green. While one is behind the nav, sections.js sets data-nav="panel" on
   <html> and the nav takes its look over bone, which passes on all four. */
html[data-theme="bone"] #nav.glass,
html[data-nav="panel"] #nav.glass {
  background:
    linear-gradient(180deg, rgba(255, 255, 255, .10), rgba(255, 255, 255, .02)),
    rgba(12, 13, 32, .75);
}
html[data-theme="bone"] #nav .links,
html[data-theme="orange"] #nav .links,
html[data-nav="panel"] #nav .links { color: var(--ink); }
/* and the nav's neon edge keeps its line and inner glow there, but drops the
   outer glow: on a light background a glow looks like a smudge */
html[data-theme="bone"] #nav .neon,
html[data-theme="orange"] #nav .neon,
html[data-nav="panel"] #nav .neon { --neon-o: 0px; }

/* ---------- liquid glass ---------- */
.glass {
  /* smoked glass: a dark tint inside the blur, so text stays readable over bright footage */
  background:
    linear-gradient(180deg, rgba(255, 255, 255, .10), rgba(255, 255, 255, .02)),
    rgba(12, 13, 32, .52);
  -webkit-backdrop-filter: blur(22px) saturate(140%);
  backdrop-filter: blur(22px) saturate(140%);
  border: 1px solid rgba(255, 255, 255, .14);
  box-shadow:
    inset 0 1px 0 rgba(255, 255, 255, .38),
    inset 0 -1px 0 rgba(255, 255, 255, .06),
    0 18px 50px rgba(3, 4, 12, .45);
}

/* ---------- neon edge ----------
   A thin line of light on the glass edge, with a soft glow either side. Only on
   the nav and the three pricing cards: each has one empty <span class="neon">.
   The shape is a mask, drawn once: four rounded corners and four straight edges,
   each a gradient that fades away from the line. Inside it are three flat
   colours: violet underneath, blue and orange fading in and out above it. Only
   their opacity changes, so the graphics chip mixes them and nothing is redrawn
   or re-blurred while it runs.
   One round takes 20s: violet, blue, violet, orange, violet. Never blue straight
   to orange: half way between those two is a muddy mauve. Every edge on the page
   changes together.
   The violet is the lighter #8E7CFF: #5B3FD9 only reaches 2.25:1 on ultramarine,
   dim beside the blue (3.9:1) and the orange (4.7:1). #8E7CFF is 4.6:1.
   The glow reaches 6px inside, and the card padding is at least 24px, so it never
   sits under text. Hosts set --neon-r (their corner radius), --neon-o (glow
   outside) and --neon-i (glow inside). With reduced motion it stays violet. */
.neon {
  --neon-r: 16px; --neon-o: 8px; --neon-i: 6px;
  --neon-c: calc(var(--neon-r) + var(--neon-o));          /* a corner piece is this square */
  --neon-w: calc(var(--neon-o) + 1px + var(--neon-i));    /* an edge piece is this thick */
  /* the fade across an edge, from the outside in: glow, the 1px line (on the glass border), glow */
  --neon-edge:
    rgba(0, 0, 0, 0) 0, rgba(0, 0, 0, .03) calc(var(--neon-o) * .25), rgba(0, 0, 0, .12) calc(var(--neon-o) * .5),
    rgba(0, 0, 0, .28) calc(var(--neon-o) * .75), rgba(0, 0, 0, .5) calc(var(--neon-o) - .4px),
    #000 calc(var(--neon-o) + .2px), #000 calc(var(--neon-o) + .8px), rgba(0, 0, 0, .42) calc(var(--neon-o) + 1.4px),
    rgba(0, 0, 0, .24) calc(var(--neon-o) + 1px + var(--neon-i) * .25), rgba(0, 0, 0, .1) calc(var(--neon-o) + 1px + var(--neon-i) * .5),
    rgba(0, 0, 0, .025) calc(var(--neon-o) + 1px + var(--neon-i) * .75), rgba(0, 0, 0, 0) var(--neon-w);
  /* the same fade around a corner, measured out from the corner's centre */
  --neon-arc:
    rgba(0, 0, 0, 0) calc(var(--neon-r) - 1px - var(--neon-i)), rgba(0, 0, 0, .025) calc(var(--neon-r) - 1px - var(--neon-i) * .75),
    rgba(0, 0, 0, .1) calc(var(--neon-r) - 1px - var(--neon-i) * .5), rgba(0, 0, 0, .24) calc(var(--neon-r) - 1px - var(--neon-i) * .25),
    rgba(0, 0, 0, .42) calc(var(--neon-r) - 1.4px), #000 calc(var(--neon-r) - .8px), #000 calc(var(--neon-r) - .2px),
    rgba(0, 0, 0, .5) calc(var(--neon-r) + .4px), rgba(0, 0, 0, .28) calc(var(--neon-r) + var(--neon-o) * .25),
    rgba(0, 0, 0, .12) calc(var(--neon-r) + var(--neon-o) * .5), rgba(0, 0, 0, .03) calc(var(--neon-r) + var(--neon-o) * .75),
    rgba(0, 0, 0, 0) var(--neon-c);
  /* behind the host's text (z-index -1 inside its stacking context), covering its edge plus the outside glow */
  position: absolute; inset: calc(-1px - var(--neon-o)); z-index: -1; pointer-events: none;
  background: var(--lavender);
  -webkit-mask-image:
    radial-gradient(circle at 100% 100%, var(--neon-arc)), radial-gradient(circle at 0 100%, var(--neon-arc)),
    radial-gradient(circle at 100% 0, var(--neon-arc)), radial-gradient(circle at 0 0, var(--neon-arc)),
    linear-gradient(to bottom, var(--neon-edge)), linear-gradient(to top, var(--neon-edge)),
    linear-gradient(to right, var(--neon-edge)), linear-gradient(to left, var(--neon-edge));
  mask-image:
    radial-gradient(circle at 100% 100%, var(--neon-arc)), radial-gradient(circle at 0 100%, var(--neon-arc)),
    radial-gradient(circle at 100% 0, var(--neon-arc)), radial-gradient(circle at 0 0, var(--neon-arc)),
    linear-gradient(to bottom, var(--neon-edge)), linear-gradient(to top, var(--neon-edge)),
    linear-gradient(to right, var(--neon-edge)), linear-gradient(to left, var(--neon-edge));
  -webkit-mask-position: 0 0, 100% 0, 0 100%, 100% 100%, var(--neon-c) 0, var(--neon-c) 100%, 0 var(--neon-c), 100% var(--neon-c);
  mask-position: 0 0, 100% 0, 0 100%, 100% 100%, var(--neon-c) 0, var(--neon-c) 100%, 0 var(--neon-c), 100% var(--neon-c);
  -webkit-mask-size:
    var(--neon-c) var(--neon-c), var(--neon-c) var(--neon-c), var(--neon-c) var(--neon-c), var(--neon-c) var(--neon-c),
    calc(100% - 2 * var(--neon-c)) var(--neon-w), calc(100% - 2 * var(--neon-c)) var(--neon-w),
    var(--neon-w) calc(100% - 2 * var(--neon-c)), var(--neon-w) calc(100% - 2 * var(--neon-c));
  mask-size:
    var(--neon-c) var(--neon-c), var(--neon-c) var(--neon-c), var(--neon-c) var(--neon-c), var(--neon-c) var(--neon-c),
    calc(100% - 2 * var(--neon-c)) var(--neon-w), calc(100% - 2 * var(--neon-c)) var(--neon-w),
    var(--neon-w) calc(100% - 2 * var(--neon-c)), var(--neon-w) calc(100% - 2 * var(--neon-c));
  -webkit-mask-repeat: no-repeat;
  mask-repeat: no-repeat;
}
.neon::before, .neon::after { content: ""; position: absolute; inset: 0; opacity: 0; }
.neon::before { background: var(--blue); animation: neon-blue 20s ease-in-out infinite; }
.neon::after { background: var(--orange); animation: neon-orange 20s ease-in-out infinite; }
@keyframes neon-blue { 0%, 40%, 100% { opacity: 0; } 15%, 25% { opacity: 1; } }
@keyframes neon-orange { 0%, 50%, 90%, 100% { opacity: 0; } 65%, 75% { opacity: 1; } }
#nav .neon { --neon-r: 28px; --neon-o: 5px; --neon-i: 4px; } /* the pill: 56px tall, so its corners are 28px */
@media (prefers-reduced-motion: reduce) { .neon::before, .neon::after { animation: none; } }

/* ---------- nav ---------- */
#nav {
  position: fixed; top: 14px; left: 14px; right: 14px; height: 56px; z-index: 40;
  display: flex; align-items: center; justify-content: space-between;
  padding: 0 8px 0 22px; border-radius: 999px;
  opacity: 0; transition: opacity .7s ease;
}
html.nav-in #nav { opacity: 1; }
#navMark { height: 22px; width: auto; display: block; visibility: hidden; filter: drop-shadow(0 1px 5px rgba(0, 0, 0, .45)); }
html.nav-done #navMark { visibility: visible; }
/* the 0400 home link: 44px tall to tap. The padding grows the link round the logo, which
   the nav keeps centred, so the logo doesn't move or change size (approved 11 October 2026) */
#nav > a { padding-block: 11px; }
#nav .links { display: flex; align-items: center; gap: 26px; font-size: 13px; letter-spacing: .04em; color: var(--muted); }
#nav .links a:not(.cta):hover { color: var(--ink); }
/* The call to action, "Start your project": the page's main button (.btn--primary), same type,
   colours and pill. Always orange here, even over Contact, where the page's own button turns
   ink: the nav's glass stays dark. 40px tall, so it sits 8px in from the pill's edge all round. */
#nav .cta {
  color: var(--ink-dark); background: var(--orange); padding: 11px 20px; border-radius: 999px;
  font: 500 15px/1.2 var(--font-body); letter-spacing: normal;
}
#nav .cta:hover { filter: brightness(1.08); }
#nav .cta:focus-visible { outline: 2px solid var(--ink); outline-offset: 3px; }
@media (max-width: 720px) {
  #nav { padding: 0 6px 0 18px; }
  #nav .links a:not(.cta) { display: none; }
}

/* ---------- hero ----------
   The hero scrolls 312vh: its 412vh height minus the 100vh stage. hero.js keeps
   its scroll map in the same units, so change the two together.
   Phones (a touch screen under 720px wide: portrait phones) scroll it in 190vh:
   290vh tall. hero.js plays the same steps in the same order, as shares of
   whatever the hero scrolls, so they simply go by faster. */
#hero { position: relative; height: 412vh; }
@media (hover: none) and (pointer: coarse) and (max-width: 719.98px) { #hero { height: 290vh; } }
#heroStage { position: sticky; top: 0; height: 100vh; height: 100svh; overflow: hidden; background: var(--bg); }
#frames { position: absolute; inset: 0; width: 100%; height: 100%; display: block; transform-origin: 0 0; }
/* the dark layer the room dims to before the statement. Darkest along the bottom,
   where the small-caps line sits over the floor's reflections (the orange words
   need 4.5:1 there). It never covers the bottom-right corner, where the Veo
   watermark is. */
#heroDim {
  position: absolute; inset: 0; opacity: 0; pointer-events: none;
  background: linear-gradient(to top, rgba(5, 6, 15, .93), rgba(5, 6, 15, .9) 28%, rgba(5, 6, 15, .62) 52%, rgba(5, 6, 15, .55));
  -webkit-mask-image: radial-gradient(circle at 100% 100%, transparent 72px, #000 200px);
  mask-image: radial-gradient(circle at 100% 100%, transparent 72px, #000 200px);
}
/* the 0400 and its glass pane. hero.js scales this group to step the 0400 back. */
#heroMark { position: absolute; inset: 0; pointer-events: none; }
#chrome { position: absolute; left: 0; top: 0; display: block; opacity: 0; } /* hero.js sizes it to a box around the 0400 */
#glassPane {
  position: absolute; left: 0; top: 0; opacity: 0; pointer-events: none;
  background: linear-gradient(160deg, rgba(255, 255, 255, .10), rgba(255, 255, 255, .02) 55%, rgba(142, 124, 255, .06));
  -webkit-backdrop-filter: blur(10px) saturate(150%);
  backdrop-filter: blur(10px) saturate(150%);
}
.grain {
  position: absolute; inset: 0; pointer-events: none; opacity: .28;
  background-image: radial-gradient(rgba(236, 234, 251, .08) 1px, transparent 0);
  background-size: 3px 3px;
}

/* The welcome: the first screen after the intro. A picture of the earth's edge
   from space, fine stars in front of it, and the words straight on the picture,
   no box. The words wait behind their masks until the 0400 lands in the nav
   (html.nav-done), then rise in. hero.js lifts them away as you scroll and
   dissolves the picture into the first frame. Nothing here sits in the
   bottom-right corner, where the frames carry the Veo watermark: the words are
   centred and there are no stars over the planet. */
#welcome { position: absolute; inset: 0; overflow: hidden; pointer-events: none; color: var(--ink); }
.wl-scene { position: absolute; inset: 0; }
/* larger than the stage, so the picture can drift without showing an edge */
.wl-pic { position: absolute; inset: -3%; }
.wl-pic img { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover; will-change: transform; }
.wl-stars { position: absolute; inset: 0; }
/* two layers of stars, each larger than the stage by the furthest it drifts */
.wl-layer { position: absolute; will-change: transform; }
.wl-far { inset: calc(-3vw - 24px); }
.wl-near { inset: calc(-5vw - 34px); }
.wl-layer canvas { display: block; width: 100%; height: 100%; }

/* the words, centred in the dark sky above the planet */
.wl-words {
  position: absolute; left: var(--gutter); right: var(--gutter); top: 88px;
  bottom: calc(26svh + env(safe-area-inset-bottom, 0px));
  display: flex; flex-direction: column; align-items: center; justify-content: center;
  gap: clamp(10px, 2.2svh, 24px); text-align: center; container-type: inline-size;
}
/* each line is a mask its words rise through; the padding (cancelled by the
   negative margin) gives the letters room to lean without being clipped */
.wl-row { overflow: hidden; overflow: clip; padding: .12em .2em .14em; margin: -.12em -.2em -.14em; }
.wl-in { display: block; }
/* "Webessy" fills the width: Bodoni Moda capitals measure 4.55em, plus the
   letter-spacing (8 × .03em, counting the padding that centres it) and 1% to spare.
   Capped on wide, short screens. */
.wl-title {
  font-family: var(--font-hero); font-weight: 400; text-transform: uppercase; line-height: .92;
  font-size: min(calc(100cqi / 4.84), 36svh); letter-spacing: .03em;
}
.wl-title .wl-in { padding-left: .03em; } /* balances the space letter-spacing leaves after the last letter */
.wl-ch { display: inline-block; transform-origin: 50% 88%; } /* letters lean from their baseline */
.wl-kicker, .wl-sub {
  font: 500 clamp(11px, .55vw + 6px, 13px)/1.6 var(--font-body); letter-spacing: .32em; text-transform: uppercase;
  color: rgba(236, 234, 251, .82); text-shadow: 0 0 18px rgba(5, 6, 15, .9);
}
.wl-kicker .wl-in, .wl-sub .wl-in { padding-left: .32em; }
.wl-sub { max-width: 60em; text-wrap: balance; }
/* "Scroll to enter", with a thin line under it and a light running down it,
   pointing at the planet. It stays in the dark sky: lower down, the city
   lights would sit behind it. */
.wl-cue {
  margin-top: calc(clamp(8px, 3svh, 30px) - .12em);
  font: 500 13px/1.4 var(--font-body); letter-spacing: .04em;
  color: rgba(236, 234, 251, .82); text-shadow: 0 0 14px rgba(5, 6, 15, .9);
}
/* phones: no text under 14px (approved 11 October 2026); everything else as above */
@media (max-width: 719.98px) {
  .wl-kicker, .wl-sub, .wl-cue { font-size: 14px; }
}
.wl-arrow {
  position: relative; display: block; width: 1px; height: clamp(28px, 6svh, 56px); margin: 12px auto 0;
  overflow: hidden; background: rgba(236, 234, 251, .2);
}
.wl-arrow::after {
  content: ""; position: absolute; inset: 0;
  background: linear-gradient(to bottom, rgba(236, 234, 251, 0), var(--ink));
  transform: translateY(-100%); animation: wl-arrow 2.2s cubic-bezier(.65, 0, .35, 1) infinite;
}
@keyframes wl-arrow { 0% { transform: translateY(-100%); } 70%, 100% { transform: translateY(100%); } }

/* the entrance: each line rises from behind its mask once the 0400 has landed.
   hero.js holds them (.wl-wait) until Bodoni Moda has loaded. */
.wl-in { transition: transform 1.3s cubic-bezier(.16, 1, .3, 1), opacity .9s ease-out; }
.wl-title .wl-in { transition-delay: .1s; }
.wl-sub .wl-in { transition-delay: .35s; }
.wl-cue .wl-in { transition-delay: .8s; }
html:not(.nav-done) .wl-in, #welcome.wl-wait .wl-in { transform: translate3d(0, 110%, 0); opacity: 0; transition: none; }

/* stars float slowly, each layer its own way */
.wl-far canvas { animation: wl-float-far 38s ease-in-out infinite alternate; }
.wl-near canvas { animation: wl-float-near 27s ease-in-out infinite alternate; }
@keyframes wl-float-far { from { transform: translate3d(14px, 8px, 0); } to { transform: translate3d(-14px, -8px, 0); } }
@keyframes wl-float-near { from { transform: translate3d(-22px, 10px, 0); } to { transform: translate3d(22px, -10px, 0); } }

/* Phones and tablets: no cursor, so the picture and the stars drift on their
   own, slowly, the nearer stars further. CSS animations of transform only, so
   they run off the main thread and the scroll stays smooth. On desktop hero.js
   moves the same three things against the cursor instead. */
#hero.lite .wl-pic img { animation: wl-drift 24s ease-in-out infinite alternate; }
#hero.lite .wl-far { animation: wl-drift-far 24s ease-in-out infinite alternate; }
#hero.lite .wl-near { animation: wl-drift-near 24s ease-in-out infinite alternate; }
@keyframes wl-drift { from { transform: translate3d(1.6vw, .8svh, 0); } to { transform: translate3d(-1.6vw, -.8svh, 0); } }
@keyframes wl-drift-far { from { transform: translate3d(2.9vw, 1.4svh, 0); } to { transform: translate3d(-2.9vw, -1.4svh, 0); } }
@keyframes wl-drift-near { from { transform: translate3d(4.6vw, 2.3svh, 0); } to { transform: translate3d(-4.6vw, -2.3svh, 0); } }

/* once the picture has dissolved, nothing in the welcome keeps animating */
#welcome.wl-off .wl-pic img, #welcome.wl-off .wl-layer, #welcome.wl-off .wl-layer canvas,
#welcome.wl-off .wl-arrow::after { animation-play-state: paused; }

@media (prefers-reduced-motion: reduce) {
  .wl-in { transition: none; }
  .wl-pic img, .wl-layer, .wl-layer canvas, #hero.lite .wl-pic img, #hero.lite .wl-layer { animation: none; }
  .wl-arrow { background: rgba(236, 234, 251, .55); }
  .wl-arrow::after { animation: none; display: none; }
}
/* short screens (a phone on its side): a smaller title and tighter gaps, so
   the words fit between the nav and the horizon */
@media (max-height: 560px) {
  .wl-words { bottom: calc(20svh + env(safe-area-inset-bottom, 0px)); gap: 8px; }
  .wl-title { font-size: min(calc(100cqi / 4.84), 24svh); }
  .wl-cue { margin-top: calc(4px - .12em); }
  .wl-arrow { height: 20px; margin-top: 6px; }
}

/* The statement that ends the hero: three lines of Bodoni Moda capitals, no box.
   Each line's size is the width divided by that line's own length in Bodoni Moda
   (6.76em, 3.99em and 2.32em, measured, plus 1% to spare), so every line fills
   the width. On wide screens that would be too tall, so lines 2 and 3 are capped
   to the height that's left after line 1, the nav and the line under them.
   hero.js fades it in; without JavaScript the <noscript> style shows it. */
#statement {
  --st-top: 96px; /* room for the nav */
  --st-bottom: calc(max(6svh, 40px) + env(safe-area-inset-bottom, 0px));
  --st-foot: 132px; /* the small-caps line and the button, with the gap above them */
  --st-avail: calc(100svh - var(--st-top) - var(--st-bottom) - var(--st-foot));
  position: absolute; left: var(--gutter); right: var(--gutter); bottom: var(--st-bottom);
  container-type: inline-size; color: var(--ink);
  opacity: 0; visibility: hidden; pointer-events: none;
}
/* a flex column, so the lines' negative margins cancel their padding instead of collapsing */
.st-title {
  display: flex; flex-direction: column; align-items: flex-start;
  font-family: var(--font-hero); font-weight: 400; text-transform: uppercase;
  --l1: min(calc(100cqi / 6.86), calc(var(--st-avail) / 2.76));
  --cap: calc((var(--st-avail) / .92 - var(--l1)) / 2);
}
/* each line is a mask the words rise through; the padding (cancelled by the
   negative margin) gives the letters room to move without being clipped */
.st-line {
  display: block; white-space: nowrap; line-height: .92;
  overflow: hidden; overflow: clip; padding: .12em .2em .14em; margin: -.12em -.2em -.14em;
}
.st-in, .st-ch { display: inline-block; }
.st-1 { font-size: var(--l1); }
.st-2 { font-size: min(calc(100cqi / 4.05), var(--cap)); }
.st-3 { font-size: min(calc(100cqi / 2.36), var(--cap)); }

.st-foot { display: flex; flex-wrap: wrap; align-items: center; gap: 18px 32px; margin-top: clamp(20px, 3.4svh, 36px); }
/* true small caps: the font's own smcp glyphs, not shrunken capitals */
.st-sub {
  max-width: 32ch; font-family: var(--font-hero); font-size: clamp(17px, 1.3vw, 20px);
  font-variant-caps: small-caps; letter-spacing: .04em; line-height: 1.35; text-wrap: balance;
}
.st-sub b { font-weight: inherit; color: var(--orange); }
/* the call to action, "Start your project": the page's main button (.btn .btn--primary). It keeps
   the Explore button's class, which hero.js shows with the line above it and ends the thin line at. */
.st-explore { flex: none; pointer-events: auto; }
/* the thin line that draws itself through the statement (path built by hero.js) */
#statementPath { position: absolute; inset: 0; width: 100%; height: 100%; overflow: visible; pointer-events: none; visibility: hidden; }
#statementPath path { fill: none; stroke: var(--lavender); stroke-width: 1.3; stroke-linecap: round; stroke-linejoin: round; }
#statementPath circle { fill: var(--ink); }
/* (hero.js still adds html.smooth-scroll for a moment when .st-explore is clicked: the Explore
   button scrolled to Work. The button now opens WhatsApp, so the class has no rule any more.) */

@media (max-width: 719.98px) {
  #statement { --st-foot: 176px; }
}

/* Phones and tablets (hero.js adds .lite): no live blur anywhere in the hero.
   Blurring footage that changes every frame is the costliest thing a phone can
   be asked to do, so the glass pane gets a plain tint. */
#hero.lite #glassPane {
  -webkit-backdrop-filter: none; backdrop-filter: none;
  background: linear-gradient(160deg, rgba(255, 255, 255, .15), rgba(255, 255, 255, .05) 55%, rgba(142, 124, 255, .10));
}

/* ==========================================================================
   Sections 02 to 06 — shared pieces
   Colours come from the section's theme (see "section colours" at the top):
   use --fg, --fg-soft, --line, --accent, never fixed colours.
   ========================================================================== */
.container { width: 100%; max-width: calc(1240px + 2 * var(--gutter)); margin-inline: auto; padding-inline: var(--gutter); }
.label {
  font-family: var(--font-mono); font-weight: 400; font-size: var(--t-label); line-height: 1.2;
  letter-spacing: .14em; text-transform: uppercase; color: var(--fg-soft);
}
.display {
  font-family: var(--font-display); font-weight: 400; font-size: var(--t-display);
  line-height: 1; letter-spacing: -.025em; font-variation-settings: "opsz" 144;
}
.h3 {
  font-family: var(--font-display); font-weight: 400; font-size: var(--t-h3);
  line-height: 1.15; letter-spacing: -.015em;
}
.display em, .h3 em { font-style: italic; }
/* pretty: no single word left alone on the last line, where the browser can avoid it */
.prose p { font-size: var(--t-body); line-height: 1.6; color: var(--fg-soft); max-width: 62ch; text-wrap: pretty; }
.prose p + p { margin-top: 1em; }

/* buttons — pill. The primary button is the one call to action, "Start your project", which
   opens WhatsApp: the same button in the nav, at the end of the hero, on each plan card, in
   Contact and floating on phones. Orange with ink text, except on the orange section, where the
   theme turns it ink with bone text. Every other action is a text link (.link), apart from a
   form's own Send. While a form sends, its button is disabled. */
.btn {
  display: inline-flex; align-items: center; justify-content: center; gap: 8px;
  padding: 14px 22px; border-radius: 999px; border: 1px solid transparent;
  font: 500 15px/1.2 var(--font-body); cursor: pointer;
  transition: filter .2s ease, border-color .2s ease;
}
.btn:focus-visible { outline: 2px solid var(--fg); outline-offset: 3px; }
.btn:disabled { cursor: progress; }
.btn--primary { background: var(--btn-bg); color: var(--btn-fg); }
.btn--primary:not(:disabled):hover { filter: brightness(1.08); }
.btn--ghost { background: transparent; color: var(--fg); border-color: var(--line); }
.btn--ghost:not(:disabled):hover { border-color: var(--fg-soft); }

/* a secondary action: plain underlined text in the section's text colour, never a button shape.
   Works on a <button> too (Write a review). The padding, cancelled by the margin, makes it
   at least 44px tall to tap without taking more room. */
.link {
  display: inline-block; padding: 12px 0; margin: -12px 0; border: 0; border-radius: 0;
  background: none; color: var(--fg); cursor: pointer; -webkit-appearance: none; appearance: none;
  font: 500 15px/1.4 var(--font-body); text-align: left;
  text-decoration: underline; text-decoration-thickness: 1px; text-underline-offset: 4px;
}
.link:hover { text-decoration-thickness: 2px; }
.link:focus-visible { outline: 2px solid var(--fg); outline-offset: 3px; }

/* sticky WhatsApp button — phones only, bottom-left so the hero's bottom-right stays clear.
   sections.js adds .is-on after the hero and removes it again from Contact to the end of the page. */
.wa-float { display: none; }
@media (max-width: 719.98px) {
  .wa-float {
    display: inline-flex; position: fixed; z-index: 30;
    left: 16px; bottom: calc(16px + env(safe-area-inset-bottom, 0px));
    box-shadow: 0 10px 28px rgba(3, 4, 12, .55);
    opacity: 0; visibility: hidden; transform: translateY(12px);
    transition: opacity .3s ease, transform .3s ease, visibility 0s linear .3s;
  }
  .wa-float.is-on { opacity: 1; visibility: visible; transform: none; transition: opacity .3s ease, transform .3s ease; }
}
@media (prefers-reduced-motion: reduce) { .wa-float, .wa-float.is-on { transition: none; transform: none; } }

/* ---------- text reveal (js/reveal.js) ----------
   While text is split, .rv-sr holds the original for screen readers (hidden
   from sight) and .rv-vis is the aria-hidden copy that animates. Each .rv-m
   is a mask and the .rv-i inside it rises. The masks' padding is cancelled by
   negative margins, so nothing moves in the layout, but descenders and italic
   letters have room and aren't clipped. */
.rv-sr {
  position: absolute; width: 1px; height: 1px; margin: -1px; padding: 0; border: 0;
  overflow: hidden; clip-path: inset(50%); white-space: nowrap;
}
.rv-m { display: inline-block; vertical-align: top; overflow: hidden; padding: .14em .3em .24em; margin: -.14em -.3em -.24em; }
.rv-i { display: inline-block; }
.rv-w { display: inline-block; vertical-align: top; white-space: nowrap; } /* letter by letter: keeps each word's letters together */

/* ---------- 02 Work ---------- */
/* overflow-x: clip, so things sliding in from the sides never make the page scroll sideways
   (clip, not hidden: the sticky title still works) */
.work { position: relative; padding-block: var(--section-pad); overflow-x: clip; }
.work__top { display: grid; gap: clamp(48px, 8vh, 80px); }
.work__title { margin-top: 18px; }
.work__project { margin-top: clamp(28px, 5vh, 56px); padding-top: 20px; border-top: 1px solid var(--line); max-width: 34ch; }
.work__meta { margin-top: 10px; font-size: var(--t-small); line-height: 1.5; color: var(--fg-soft); }
.tag { display: inline-block; margin-top: 16px; padding: 7px 10px 6px; border: 1px solid var(--line); }

.work__media { display: flex; flex-direction: column; gap: clamp(40px, 9vh, 112px); overflow-x: clip; }
.shot { margin: 0; }
/* the screenshots load lazily: until a file arrives, a faint tint from the text colour holds its shape */
.shot img { display: block; width: 100%; height: auto; background: color-mix(in srgb, var(--fg) 6%, transparent); }
.shot-pair, .shot-row { display: grid; gap: clamp(40px, 9vh, 112px); }
.shot--phone { width: 62%; }

/* the case study: four blocks, each with its own voice. Every block opens with its
   mono label under a hairline. */
.case { margin-top: clamp(96px, 16vh, 200px); }
.case__block:not(:first-child) { margin-top: clamp(112px, 18vh, 208px); }
.case__label { padding-top: 20px; border-top: 1px solid var(--line); }

/* The problem: a huge headline, then the text in two narrow columns */
.problem__head {
  margin-top: clamp(28px, 5vh, 56px); max-width: 9em;
  font-family: var(--font-display); font-weight: 400; font-size: clamp(48px, 8vw, 128px);
  line-height: .96; letter-spacing: -.03em; font-variation-settings: "opsz" 144; text-wrap: balance;
}
.problem__cols { margin-top: clamp(40px, 7vh, 80px); }

/* The decision: one upright line that exactly fills the width. The line is 12.29em
   long (measured in Fraunces), so 100cqi / 12.42 fills 99% of the block at any width.
   On phones one line would be tiny, so there it breaks into big lines. */
.decision { container-type: inline-size; }
.decision__line {
  margin-top: clamp(24px, 4vh, 44px);
  font-family: var(--font-display); font-weight: 400; font-variation-settings: "opsz" 144;
  font-size: 15vw; font-size: min(17cqi, 88px);
  line-height: .98; letter-spacing: -.03em; text-wrap: balance;
}
.decision__line em { font-style: italic; color: var(--accent); }
.decision__body { margin-top: clamp(28px, 5vh, 48px); }

/* What was built: a build sheet, like an architect's drawing (see the comment in index.html).
   Ink on bone, square corners: an outer line, a fainter inner line just inside it with tick
   marks between the two, and the title block in the bottom-right corner. No glass, no glow.
   Phones and tablets (the base rules): views and notes follow each other in one column.
   Computers (960px and up): the views sit side by side, the notes and the title block in a
   strip along the bottom.
   sections.js turns the steps on with .is-live, plus .is-steps (computers) or .is-stack
   (phones, tablets, shorter screens). Without those classes everything is drawn and shown.
   Only transform and opacity change: CSS transitions, switched by classes. */
.sheet-run { margin-top: clamp(24px, 4vh, 40px); }
.sheet {
  --trim: 6px;  /* from the outer line to the inner one: the tick marks sit here */
  --pad: 12px;  /* from the inner line to the content */
  --rule: color-mix(in srgb, var(--fg) 45%, transparent);
  position: relative; border: 1px solid var(--fg); padding: calc(var(--trim) + 1px + var(--pad));
  margin-inline: calc(var(--gutter) / -2); /* phones: half into the gutter, so the views stay big */
  background:
    repeating-linear-gradient(90deg, var(--rule) 0 1px, transparent 1px 25%) 0 0 / 100% var(--trim) no-repeat,
    repeating-linear-gradient(90deg, var(--rule) 0 1px, transparent 1px 25%) 0 100% / 100% var(--trim) no-repeat,
    repeating-linear-gradient(180deg, var(--rule) 0 1px, transparent 1px 120px) 0 0 / var(--trim) 100% no-repeat,
    repeating-linear-gradient(180deg, var(--rule) 0 1px, transparent 1px 120px) 100% 0 / var(--trim) 100% no-repeat;
}
.sheet::after { content: ""; position: absolute; inset: var(--trim); border: 1px solid var(--rule); pointer-events: none; }

.sheet__part + .sheet__part { margin-top: 44px; }
.sheet__view { margin: 0; }
/* the picture's box keeps the picture's shape (--ar), so the pins on it stay in place.
   In one column a view is never taller than 44% of the screen, so a held view leaves
   room for its note below it. */
.sheet__pic {
  position: relative; aspect-ratio: var(--ar);
  width: min(100%, calc(44vh * var(--ar))); width: min(100%, calc(44svh * var(--ar)));
  background: color-mix(in srgb, var(--fg) 8%, transparent); /* holds the space until the file arrives */
}
.sheet__pic img { display: block; width: 100%; height: 100%; object-fit: cover; }
/* the phone picture is tall: at 34% it leaves note 4 room to be read below it */
.sheet__part--d .sheet__pic { width: min(100%, calc(34vh * var(--ar))); width: min(100%, calc(34svh * var(--ar))); }
/* screens under 600px tall: smaller views, or a held view leaves its note too little room */
@media (max-height: 599.98px) {
  .sheet__pic { width: min(100%, calc(34vh * var(--ar))); width: min(100%, calc(34svh * var(--ar))); }
  .sheet__part--d .sheet__pic { width: min(100%, calc(28vh * var(--ar))); width: min(100%, calc(28svh * var(--ar))); }
}
.sheet__name { margin-top: 10px; line-height: 14px; white-space: nowrap; } /* "Phone / Home" just fits D at its narrowest (102px) */

/* A note: its number disc, a title (24px and up, so the accent word may be orange), one line */
.sheet__note { display: grid; grid-template-columns: 22px minmax(0, 1fr); align-content: start; column-gap: 14px; margin-top: 22px; }
.sheet__no {
  grid-column: 1; grid-row: 1; width: 22px; height: 22px; margin-top: calc((var(--t-h3) * 1.15 - 22px) / 2);
  border-radius: 50%; background: var(--fg); color: var(--page);
  font: 400 14px/22px var(--font-mono); text-align: center; /* 11px from 720px */
}
.sheet__title { grid-column: 2; grid-row: 1; text-wrap: balance; }
.sheet__title em { color: var(--accent); }
.sheet__text { grid-column: 2; margin-top: 8px; max-width: 52ch; font-size: var(--t-body); line-height: 1.5; color: var(--fg-soft); text-wrap: pretty; }
.sheet__link { grid-column: 2; margin-top: 10px; font: 400 14px/1.4 var(--font-mono); } /* 13px from 720px */
/* at least 44px tall to tap: the padding, cancelled by the margin, takes no room. Where the address
   doesn't fit on one line it breaks after "github.io/" (a <wbr> in the HTML), never at the hyphen. */
.sheet__link a {
  display: inline-block; padding-block: calc((45px - 1.4em) / 2); margin-block: calc((1.4em - 45px) / 2);
  text-decoration: underline; text-decoration-thickness: 1px; text-underline-offset: 4px; overflow-wrap: anywhere;
}
.sheet__keep { white-space: nowrap; }
.sheet__link a:hover { text-decoration-thickness: 2px; }
.sheet__link a:focus-visible { outline: 2px solid var(--fg); outline-offset: 3px; }

/* The title block. On phones it closes the sheet: note 5 comes first, and the block sits
   in the bottom-right corner against the inner line. */
.sheet__part--t { display: flex; flex-direction: column; }
.sheet__part--t .sheet__note { margin-top: 0; }
.titleblock {
  order: 1; align-self: flex-end; width: min(100% + var(--pad), 300px);
  margin: 36px calc(-1 * var(--pad)) calc(-1 * var(--pad)) 0;
  border: solid var(--rule); border-width: 1px 0 0 1px;
}
.titleblock__row { display: grid; grid-template-columns: 76px minmax(0, 1fr); align-items: center; column-gap: 12px; min-height: 38px; padding: 0 12px; }
.titleblock__row + .titleblock__row { border-top: 1px solid var(--rule); }
.titleblock dt { line-height: 14px; }
.titleblock dd { display: flex; align-items: center; font: 400 14px/1.2 var(--font-mono); letter-spacing: .12em; text-transform: uppercase; } /* 13px from 720px */

/* Pins: an ink circle with a bone edge, a line drawn in ink between two bone hairlines, and
   an ink dot. The bone keeps them clear on the dark screenshots, the ink on the paper. */
.pin { position: absolute; inset: 0; pointer-events: none; }
.pin__line {
  position: absolute; left: var(--x); top: var(--y); width: var(--len); height: 3px; margin-top: -1.5px;
  background: linear-gradient(var(--page) 0 1px, var(--fg) 1px 2px, var(--page) 2px);
  transform-origin: 0 50%; transform: rotate(var(--ang));
}
.pin__dot {
  position: absolute; left: var(--tx); top: var(--ty); width: 8px; height: 8px; margin: -4px 0 0 -4px;
  border-radius: 50%; background: var(--fg); box-shadow: 0 0 0 1.5px var(--page);
}
.pin__num {
  position: absolute; left: var(--x); top: var(--y); width: 24px; height: 24px; margin: -12px 0 0 -12px;
  border-radius: 50%; background: var(--fg); color: var(--page); box-shadow: 0 0 0 1.5px var(--page);
  font: 400 14px/24px var(--font-mono); letter-spacing: 0; text-align: center; /* 11px from 720px */
}
/* the ring around the pin of the current step */
.pin__num::after {
  content: ""; position: absolute; inset: -6px; border-radius: 50%; opacity: 0;
  border: 1px solid var(--fg); box-shadow: 0 0 0 1px var(--page), inset 0 0 0 1px var(--page);
}
a.pin__num { pointer-events: auto; }
a.pin__num::before { content: ""; position: absolute; inset: -10px; border-radius: 50%; } /* 44px to tap */
a.pin__num:hover::after { opacity: 1; }
/* pin 5 sits in the title block, after "Live": the dot, the line, then its circle */
.pin--live { --ang: 0deg; position: relative; inset: auto; flex: 1; display: flex; align-items: center; min-width: 72px; height: 24px; margin-left: 10px; }
.pin--live .pin__dot, .pin--live .pin__line, .pin--live .pin__num { position: relative; left: auto; top: auto; flex: none; margin: 0; }
.pin--live .pin__line { flex: 1; width: auto; margin-inline: -1px; transform-origin: 100% 50%; }

/* The steps (sections.js). A pin waits dimmed; on its step the circle lights up, the line
   draws from the circle to the spot (it grows: scaleX) and the dot lands on it. */
.built.is-live .pin__num { opacity: .3; transform: scale(.8); transition: opacity .3s ease, transform .3s ease; }
.built.is-live .pin.is-on .pin__num { opacity: 1; transform: none; transition: opacity .3s ease, transform .55s cubic-bezier(.34, 1.56, .64, 1); }
.built.is-live .pin:not(.is-on) .pin__num { pointer-events: none; }
.built.is-live .pin__line { transform: rotate(var(--ang)) scaleX(0); transition: transform .25s ease-in; }
.built.is-live .pin.is-on .pin__line { transform: rotate(var(--ang)) scaleX(1); transition: transform .55s cubic-bezier(.65, 0, .35, 1) .12s; }
.built.is-live .pin__dot { transform: scale(0); transition: transform .2s ease-in; }
.built.is-live .pin.is-on .pin__dot { transform: none; transition: transform .4s cubic-bezier(.34, 1.56, .64, 1) .6s; }
.built.is-live .pin__num::after { transition: opacity .3s ease; }
.built.is-live .pin.is-current .pin__num::after { opacity: 1; transition-delay: .25s; }
.built.is-setting * { transition: none !important; } /* while a mode starts: jump straight to the right step */

/* .is-stack: each view (and the title block) stays under the nav while its note rises up
   below it; the note slides under it, and the next view, right behind the note, pushes it
   away. The space above each note (26% of the screen) is the scroll the view is held for.
   The band of bone above and below a held view hides the notes as they slide under.
   A note fades in when it arrives, and stays. */
.built.is-stack .sheet__view,
.built.is-stack .titleblock { position: sticky; top: 82px; z-index: 2; background: var(--page); box-shadow: 0 -12px 0 var(--page); }
.built.is-stack .sheet__view { padding-bottom: 12px; }
.built.is-stack .sheet__note {
  align-content: start; padding-top: 26vh; padding-top: 26svh; padding-bottom: 16px;
  opacity: 0; transform: translateY(14px); transition: opacity .3s ease, transform .3s ease;
}
.built.is-stack .sheet__note.is-on { opacity: 1; transform: none; transition: opacity .5s ease .15s, transform .7s cubic-bezier(.2, .7, .2, 1) .15s; }
.built.is-stack .sheet__part + .sheet__part { margin-top: 0; }
.built.is-stack .sheet__part--t .sheet__note { margin-top: 22px; }
.built.is-stack .titleblock { order: 0; align-self: stretch; width: auto; margin: 0; border-width: 1px; }

@media (min-width: 960px) {
  /* The views on a grid: A, then B over C, then the phone (D). Their heights are worked out
     so the bottoms of A, C and D line up: H is A's height, from the sheet's width (100cqi):
     A = 1.7778 H wide, D = 0.4621 H, and B + C stack to H minus their labels and the gap
     (56px) at width (H - 56) / 1.00785. Then A + B + D + two gaps = 100cqi.
     Every label takes two lines (38px), so they all line up too. */
  .built:not(.is-stack) .sheet {
    --trim: 7px; --pad: 28px; --tb-w: 300px; container-type: inline-size; margin-inline: 0;
    background-size: 100% var(--trim), 100% var(--trim), var(--trim) 100%, var(--trim) 100%;
    background-image:
      repeating-linear-gradient(90deg, var(--rule) 0 1px, transparent 1px 12.5%),
      repeating-linear-gradient(90deg, var(--rule) 0 1px, transparent 1px 12.5%),
      repeating-linear-gradient(180deg, var(--rule) 0 1px, transparent 1px 25%),
      repeating-linear-gradient(180deg, var(--rule) 0 1px, transparent 1px 25%);
  }
  .built:not(.is-stack) .sheet__in {
    --g: 30px;
    --H: calc((100cqi + 55.564px - 2 * var(--g)) / 3.232073);
    display: grid; column-gap: var(--g); row-gap: 18px;
    grid-template-columns: minmax(0, 1fr) calc((var(--H) - 56px) / 1.007851) calc(var(--H) * .462085);
    grid-template-areas: "a b d" "a c d" "n1 n2 n2" "n3 n4 n4" "n5 t t";
  }
  /* the line across the sheet above the notes, from inner line to inner line */
  .built:not(.is-stack) .sheet__in::before {
    content: ""; grid-row: 3; grid-column: 1 / -1; align-self: start;
    margin: 6px calc(-1 * var(--pad)) 0; border-top: 1px solid var(--rule);
  }
  .built:not(.is-stack) .sheet__part { display: contents; }
  .built:not(.is-stack) .sheet__pic { width: 100%; }
  .built:not(.is-stack) .sheet__id,
  .built:not(.is-stack) .sheet__kind { display: block; }
  .built:not(.is-stack) .sheet__dash { display: none; }
  .built:not(.is-stack) .sheet__part--a .sheet__view { grid-area: a; align-self: end; }
  .built:not(.is-stack) .sheet__part--b .sheet__view { grid-area: b; align-self: start; }
  .built:not(.is-stack) .sheet__part--c .sheet__view { grid-area: c; align-self: end; }
  .built:not(.is-stack) .sheet__part--d .sheet__view { grid-area: d; align-self: end; }
  /* without motion: the five notes two by two, the title block last, in the corner */
  .built:not(.is-stack) .sheet__part .sheet__note { margin-top: 34px; }
  .built:not(.is-stack) .sheet__part--a .sheet__note { grid-area: n1; }
  .built:not(.is-stack) .sheet__part--b .sheet__note { grid-area: n2; }
  .built:not(.is-stack) .sheet__part--c .sheet__note { grid-area: n3; }
  .built:not(.is-stack) .sheet__part--d .sheet__note { grid-area: n4; }
  .built:not(.is-stack) .sheet__part--t .sheet__note { grid-area: n5; }
  .built:not(.is-stack) .titleblock { grid-area: t; justify-self: end; align-self: end; width: var(--tb-w); margin-top: 34px; }

  /* .is-steps: the sheet stays in the middle of the screen (sections.js sets its top) for
     1.75 screen-heights of scroll: the space after it in .sheet-run. Its width follows the
     screen height, so it always fits between the nav and the bottom edge: H may be at most
     the screen height minus 380px (nav, margins, labels, strip), and the width that gives
     that H is 323.2 svh - 1148px. Only the current note shows, in the strip on the left;
     the title block fills the strip's right end. */
  .built.is-steps .sheet-run::after { content: ""; display: block; height: 175vh; height: 175svh; }
  .built.is-steps .sheet {
    position: sticky; top: 86px;
    width: min(100%, calc(323.2vh - 1148px)); width: min(100%, calc(323.2svh - 1148px));
  }
  .built.is-steps .sheet__in { grid-template-areas: "a b d" "a c d" "s s s"; }
  .built.is-steps .sheet__part .sheet__note {
    grid-area: s; align-self: start; margin-top: 30px; max-width: calc(100% - var(--tb-w) + var(--pad) - 40px);
    opacity: 0; transform: translateY(10px); pointer-events: none; transition: opacity .2s ease, transform .2s ease;
  }
  .built.is-steps .sheet__part .sheet__note.is-current {
    z-index: 1; opacity: 1; transform: none; pointer-events: auto;
    transition: opacity .45s ease .2s, transform .65s cubic-bezier(.2, .7, .2, 1) .2s;
  }
  .built.is-steps .titleblock { grid-area: s; align-self: stretch; display: grid; grid-auto-rows: 1fr; margin-top: 6px; border-top: 0; }
}

/* What it proves: the paragraph, then two big lines. sections.js slides the first
   in from the left and the second from the right. */
.proves__body { margin-top: clamp(24px, 4vh, 44px); }
.proves__lines {
  margin-top: clamp(56px, 10vh, 120px);
  font-family: var(--font-display); font-weight: 400; font-size: clamp(40px, 6.6vw, 100px);
  line-height: 1.02; letter-spacing: -.03em; font-variation-settings: "opsz" 144;
}
.proves__line { display: block; }
.proves__line + .proves__line { margin-top: .1em; text-align: right; } /* the second line sits on the right */

/* kicker lines in the two blocks after the case study */
.prose .case__kicker { font-family: var(--font-display); font-size: clamp(20px, 1.8vw, 26px); line-height: 1.3; margin-top: 28px; color: var(--fg); max-width: 28ch; }

.work__live { margin-top: clamp(96px, 16vh, 200px); padding-top: clamp(32px, 5vh, 56px); border-top: 1px solid var(--line); display: grid; gap: 28px; }
.work__live-title { font-size: clamp(34px, 4.6vw, 64px); line-height: 1.04; max-width: 16ch; }
.work__live .case__kicker { margin-top: 12px; }

.work__early { margin-top: clamp(120px, 22vh, 260px); display: grid; gap: clamp(32px, 6vh, 64px); }
.early-line { font-size: clamp(50px, 10.5vw, 168px); line-height: .95; letter-spacing: -.035em; }
.early-line__part { display: block; }
.early-body .case__kicker { font-size: var(--t-h3); }

@media (min-width: 720px) {
  .shot-pair { grid-template-columns: 1fr 1fr; gap: var(--gap); }
  .shot-row { grid-template-columns: 4fr 8fr; gap: var(--gap); align-items: end; }
  .shot--phone { width: auto; }

  /* the problem text flows down the first column, then the second, like a newspaper */
  .problem__cols { width: min(100%, 620px); margin-left: auto; columns: 2; column-gap: var(--gap); }
  .decision__line { white-space: nowrap; font-size: min(7.1vw, 97px); font-size: calc(100cqi / 12.42); }
  .proves__body { width: min(100%, 620px); margin-left: auto; }
  /* the build sheet's small mono type at its designed size (14px on phones) */
  .sheet__no, .pin__num { font-size: 11px; }
  .sheet__link, .titleblock dd { font-size: 13px; }
}
@media (min-width: 960px) {
  /* label and title stay pinned on the left while the screenshots scroll past on the right */
  .work__top { grid-template-columns: repeat(12, minmax(0, 1fr)); column-gap: var(--gap); }
  .work__pin { grid-column: 1 / span 4; position: sticky; top: 14vh; align-self: start; }
  .work__media { grid-column: 6 / -1; }
  /* the decision text starts under "wrong": 64% along the line (measured), x 0.99 */
  .decision__body { margin-left: 63.4%; }
  .work__live, .work__early { grid-template-columns: repeat(12, minmax(0, 1fr)); column-gap: var(--gap); }
  .work__live-title { grid-column: 1 / span 8; }
  .work__live .prose { grid-column: 5 / span 7; }
  .early-line { grid-column: 1 / -1; }
  .early-body { grid-column: 7 / -1; }
}

/* ---------- 03 What we do ---------- */
/* overflow-x: clip, so the cards at the sides of the ring (they reach past the screen edge on
   phones) never make the page scroll sideways. Clip, not hidden: the ring's stage still sticks. */
.services { --card-pad: clamp(24px, 3vw, 44px); padding-block: var(--section-pad); overflow-x: clip; }
.services__title { margin-top: 18px; }
.services__sub {
  font-family: var(--font-display); font-weight: 400; font-size: clamp(34px, 4.6vw, 64px);
  line-height: 1.04; letter-spacing: -.02em; font-variation-settings: "opsz" 144; text-wrap: balance;
}

/* one static glow behind the pricing glass, so the glass has colour to blur. Never animated. */
.glow-field { position: relative; isolation: isolate; }
.glow-field::before {
  content: ""; position: absolute; inset: -10% -5%; z-index: -1; pointer-events: none;
  background:
    radial-gradient(40% 50% at 30% 40%, rgba(91, 63, 217, .30), transparent 70%),
    radial-gradient(30% 40% at 75% 65%, rgba(255, 86, 29, .16), transparent 70%);
  filter: blur(40px);
}

/* The four plan cards: Starter, Business, Advanced, Boss. Each shows only its name (a label at
   the top), its starting price at the bottom and one button; Boss has a line where the price
   would be. Laid out here as a plain grid: stacked on phones, two by two from tablets, four in
   a row on wide screens. sections.js puts them on a ring instead (.is-orbit, further down). */
.plans { margin-top: clamp(48px, 8vh, 96px); }
.plans__ring { list-style: none; display: grid; gap: var(--gap); }
.plan, .plan-card { position: relative; isolation: isolate; border-radius: 16px; padding: var(--card-pad); } /* isolation keeps the neon edge inside the card */
.plan { display: flex; min-height: 300px; }
.plan .card-body { flex: 1; display: flex; flex-direction: column; container-type: inline-size; }
.plan__head { flex: 1; display: flex; flex-direction: column; font-family: var(--font-display); font-weight: 400; }
.plan__name { color: var(--fg); }
/* "from" is the heading's one italic accent word. Every card's numeral is the same size:
   26cqi lets the widest, ₹18,499 (3.5em), fill nine tenths of the card. */
.plan__price { display: block; margin-top: auto; padding-top: clamp(40px, 7vh, 72px); }
.plan__price em { display: block; font-style: italic; font-size: var(--t-h3); line-height: 1.15; letter-spacing: -.015em; color: var(--accent); }
.plan__num {
  display: block; margin-top: 8px; font-size: clamp(48px, 6vw, 88px); font-size: min(var(--t-price), 26cqi);
  line-height: .95; letter-spacing: -.03em; font-variation-settings: "opsz" 144; font-variant-numeric: lining-nums; white-space: nowrap;
}
.plan__talk {
  margin-top: clamp(40px, 7vh, 72px); font-family: var(--font-display); font-weight: 400;
  font-size: clamp(28px, 2.6vw, 38px); font-size: min(clamp(28px, 2.6vw, 38px), 11.5cqi);
  line-height: 1.12; letter-spacing: -.02em; text-wrap: balance;
}
.plan__cta { align-self: flex-start; margin-top: clamp(22px, 3.6vh, 32px); white-space: nowrap; }
/* the one line under the plans */
.plans__note {
  margin-top: clamp(28px, 4.6vh, 44px); font-family: var(--font-display); font-weight: 400;
  font-size: var(--t-h3); line-height: 1.2; letter-spacing: -.015em;
}

/* 45 days and Care: the two cards under the plans, over a still glow of their own. Side by
   side from tablets; on computers a 7/5 split, with the promise lines lined up under them. */
.extras { display: grid; gap: var(--gap); margin-top: clamp(72px, 12vh, 140px); }
.plan-card p { font-size: var(--t-body); line-height: 1.6; color: var(--fg-soft); max-width: 44ch; text-wrap: pretty; }
.plan-card p + p { margin-top: 10px; }
.plan-card .plan-card__lead {
  display: block; margin: 18px 0 12px; font-family: var(--font-display); font-weight: 400;
  font-size: clamp(22px, 2vw, 28px); line-height: 1.2; letter-spacing: -.015em; color: var(--fg); text-wrap: balance;
}
/* the glass cards' entrance (except on the ring): the same 16px rise and fade as .reveal (0.8s,
   power3.out), but as a CSS transition, which phones run off the main thread (sections.js adds
   .is-waiting, then .is-in) */
.plan.is-waiting, .plan-card.is-waiting { opacity: 0; transform: translateY(16px); }
.plan.is-waiting.is-in, .plan-card.is-waiting.is-in {
  opacity: 1; transform: none;
  transition: opacity .8s cubic-bezier(.165, .84, .44, 1), transform .8s cubic-bezier(.165, .84, .44, 1);
}

/* the two lines that protect the client — small, directly under the cards, lined up with the card text */
.promises { display: grid; gap: 12px var(--gap); margin-top: clamp(24px, 4vh, 36px); }
.promises p { padding-inline: var(--card-pad); font-size: var(--t-small); line-height: 1.55; color: var(--fg-soft); }
.promises strong { font-weight: 500; color: var(--fg); }

/* what we build — plain text in four groups: no cards, no icons, no prices */
.build { margin-top: clamp(96px, 16vh, 200px); }
.build__groups { display: grid; gap: clamp(36px, 5vh, 48px) var(--gap); margin-top: clamp(32px, 6vh, 64px); }
.build__group { padding-top: 18px; border-top: 1px solid var(--line); }
.build__group ul { list-style: none; margin-top: 20px; display: grid; gap: 10px; }
.build__group li { font-family: var(--font-display); font-size: clamp(19px, 1.45vw, 21px); line-height: 1.25; letter-spacing: -.01em; }
.audience { margin-top: clamp(64px, 11vh, 128px); }
.audience__list { margin-top: 16px; font-family: var(--font-display); font-size: var(--t-h3); line-height: 1.2; letter-spacing: -.015em; text-wrap: balance; }
.audience__note { margin-top: 12px; font-size: var(--t-body); line-height: 1.6; color: var(--fg-soft); max-width: 62ch; text-wrap: pretty; }

/* what a website usually costs — full width, no card, no radius. Only the "Us" row is bright. */
.costs { margin-top: clamp(96px, 16vh, 200px); }
.costs__table {
  width: 100%; margin-top: clamp(28px, 5vh, 48px); border-collapse: collapse;
  font-size: var(--t-small); line-height: 1.5; color: var(--fg-soft); text-wrap: pretty;
}
.costs__table th, .costs__table td { font-weight: 400; text-align: left; vertical-align: baseline; }
.costs__table tbody th, .costs__table strong { font-weight: 500; }
.costs__table thead th { font-family: var(--font-mono); font-size: var(--t-label); line-height: 1.2; letter-spacing: .14em; text-transform: uppercase; }
/* prices in Fraunces: Instrument Sans has no ₹ sign, Fraunces does */
.costs__pay { font-family: var(--font-display); font-size: var(--t-body); letter-spacing: -.005em; font-variant-numeric: lining-nums; white-space: nowrap; }
.costs__us { color: var(--fg); }
.costs__note {
  margin-top: clamp(40px, 7vh, 72px); font-family: var(--font-display); font-size: clamp(24px, 2.8vw, 40px);
  line-height: 1.2; letter-spacing: -.015em; max-width: 30ch; text-wrap: pretty;
}

/* phones: each table row becomes a stacked block (name and price on top, what you get below). No sideways scroll. */
@media (max-width: 719.98px) {
  .costs__table, .costs__table tbody, .costs__table tr, .costs__table th, .costs__table td { display: block; }
  .costs__table thead { position: absolute; width: 1px; height: 1px; overflow: hidden; clip-path: inset(50%); white-space: nowrap; }
  .costs__table tbody tr {
    display: grid; grid-template-columns: 1fr auto; align-items: baseline; column-gap: 16px;
    padding-block: 16px; border-top: 1px solid var(--line);
  }
  .costs__table tbody tr:last-child { border-bottom: 1px solid var(--line); }
  .costs__table .costs__pay { text-align: right; }
  .costs__table td:last-child { grid-column: 1 / -1; margin-top: 6px; }
}
@media (min-width: 720px) {
  .plans__ring, .extras, .promises { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .build__groups { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .costs__table th, .costs__table td { padding: 20px 24px 20px 0; }
  .costs__table thead th { padding-block: 0 14px; }
  .costs__table tbody tr > * { border-top: 1px solid var(--line); }
  .costs__table tbody tr:last-child > * { border-bottom: 1px solid var(--line); }
  .costs__table thead td { width: 22%; }
  .costs__table thead th:nth-child(2) { width: 26%; }
}
@media (min-width: 960px) {
  /* 7/5 split: 45 days on the left, Care on the right, each promise line under its card */
  .extras, .promises { grid-template-columns: repeat(12, minmax(0, 1fr)); }
  .extras > :first-child, .promises p:first-child { grid-column: 1 / span 7; }
  .extras > :last-child, .promises p:last-child { grid-column: 8 / -1; }
  .costs { display: grid; grid-template-columns: repeat(12, minmax(0, 1fr)); column-gap: var(--gap); }
  .costs > * { grid-column: 1 / -1; }
  .costs .costs__note { grid-column: 5 / -1; }
}
@media (min-width: 1100px) {
  .build__groups { grid-template-columns: repeat(4, minmax(0, 1fr)); }
}
@media (min-width: 1280px) {
  /* four in a row: a little less padding, so "Start your project" fits on one line */
  .plans__ring { grid-template-columns: repeat(4, minmax(0, 1fr)); }
  .plans:not(.is-orbit) .plan { --card-pad: clamp(24px, 2vw, 36px); }
}

/* The ring. sections.js adds .is-orbit to .plans (screens at least 500px tall, without reduced
   motion: phones, tablets and computers) and turns the four cards on a ring as you scroll.
   The browser holds the stage (the ring and the line about startups) in place, by CSS sticky,
   so it never jumps on a phone, while the space after it, 4.4 screen-heights, scrolls past
   (sections.js spreads the three quarter turns over it).
   It holds in the middle of what's left of the screen under the nav (--stick, its bottom
   edge), above the WhatsApp button on phones (--dock). sections.js sets --stick and
   --stage-h, the stage's height. The stage has room at the top for the cards behind, which
   reach slightly higher than the front one.
   All four cards share one grid cell, so they get one size: --card-w wide, nearly square. The
   cards at the sides reach past the screen edge on phones, and .services clips them. The text
   of each card sits in .card-body, its own layer, so the cards behind can fade their text
   (and on computers blur it) and keep the glass and the edge sharp. */
.plans.is-orbit { --stick: 70px; --dock: 24px; --card-w: min(82vw, 330px); }
.plans.is-orbit::after { content: ""; display: block; height: 440vh; }
.plans.is-orbit .plans__stage {
  position: sticky; top: var(--stick);
  top: max(var(--stick), var(--stick) + (100vh - var(--stick) - var(--dock) - var(--stage-h, 0px)) / 2);
  display: flex; flex-direction: column; align-items: center;
  padding-top: calc(var(--card-w) * .1);
}
.plans.is-orbit .plans__ring { grid-template-columns: var(--card-w); }
.plans.is-orbit .plan { grid-area: 1 / 1; min-height: calc(var(--card-w) * .96); will-change: transform; }
/* the text on its own layer: without it, every change of its opacity makes the browser rebuild
   its layers, on every frame of a turn (measured 1.4ms a frame on a phone, against 0.02ms) */
.plans.is-orbit .card-body { will-change: opacity; }
.plans.is-orbit .plans__note { text-align: center; }
@supports (height: 100svh) {
  .plans.is-orbit::after { height: 440svh; }
  .plans.is-orbit .plans__stage { top: max(var(--stick), var(--stick) + (100svh - var(--stick) - var(--dock) - var(--stage-h, 0px)) / 2); }
}
@media (max-width: 719.98px) {
  .plans.is-orbit { --dock: calc(76px + env(safe-area-inset-bottom, 0px)); } /* 16px from the edge, the button's 48px, 12px clear */
}
@media (min-width: 720px) {
  .plans.is-orbit { --card-w: min(52vw, 420px); }
}
@media (min-width: 1200px) {
  .plans.is-orbit { --card-w: clamp(400px, 32vw, 460px); }
}
@media (hover: hover) and (pointer: fine) {
  .plans.is-orbit .card-body { will-change: filter, opacity; }
}

/* ---------- 04 How it works ----------
   A black section. The 8 steps sit two to a panel in four solid panels: bone, orange,
   violet, green. Each panel names its colour in data-theme: bone and orange are the
   section themes, violet and green exist only here. Soft text on each passes AA: ink 66%
   on bone (5.9:1), ink 80% on orange (4.9), bone 92% on violet (4.8), ink 82% on green
   (5.4). The big numbers are 70% of the text colour: 3.4:1 or better, and they're large.
   Each panel has its own layout inside, so no two look alike.
   Without JavaScript, and with reduced motion, the panels simply follow each other.
   Otherwise sections.js picks how they stack, with a class on .how__stack:
     .is-stack  computers and tablets. Each panel sticks near the top, 18px below the one
                before, and all four are sized so their bottoms line up near the bottom of
                the screen: they leave together at the end.
     .is-flow   phones, and any screen too short for the stack. A panel is taller than the
                screen there, so it scrolls up normally and stops when its bottom edge is
                just above the WhatsApp button. The next one then slides up over it.
   The slot is what sticks; the panel inside it is what shrinks and dims. */
.how { padding-block: var(--section-pad); }
.how__title { margin-top: 18px; }
.how__intro {
  margin-top: clamp(20px, 3vh, 28px); max-width: 24ch;
  font-family: var(--font-display); font-weight: 400; font-size: clamp(22px, 2.1vw, 30px);
  line-height: 1.25; letter-spacing: -.015em; text-wrap: pretty;
}

.how__stack { --stack-gap: 16px; margin-top: clamp(56px, 10vh, 112px); }
.how__slot + .how__slot { margin-top: var(--stack-gap); }
.how__slot:nth-child(2) { --i: 1; }
.how__slot:nth-child(3) { --i: 2; }
.how__slot:nth-child(4) { --i: 3; }
.how__panel {
  --pad-x: clamp(22px, 3.6vw, 52px); --pad-y: clamp(26px, 5vh, 52px);
  position: relative; border-radius: 16px; padding: var(--pad-y) var(--pad-x);
}
.how__panel[data-theme="violet"] {
  --page: var(--violet); --fg: var(--bone); --fg-soft: rgba(236, 231, 218, .92); --line: rgba(236, 231, 218, .26);
}
.how__panel[data-theme="green"] {
  --page: var(--green); --fg: var(--ink-dark); --fg-soft: rgba(12, 12, 10, .82); --line: rgba(12, 12, 10, .22);
}

/* a step: the big number, the title, the text */
.step__num {
  display: block; font-family: var(--font-display); font-weight: 400; font-variation-settings: "opsz" 144;
  font-variant-numeric: lining-nums; line-height: 1; letter-spacing: -.04em;
  color: var(--fg-soft); color: color-mix(in srgb, var(--fg) 70%, transparent);
}
.step__title {
  font-family: var(--font-display); font-weight: 400; font-size: var(--t-h3);
  line-height: 1.15; letter-spacing: -.015em; text-wrap: balance;
}
.step__sr { position: absolute; width: 1px; height: 1px; margin: -1px; padding: 0; border: 0; overflow: hidden; clip-path: inset(50%); white-space: nowrap; }
.step__text { margin-top: 14px; }
.step__text p { font-size: var(--t-body); line-height: 1.6; color: var(--fg-soft); max-width: 62ch; text-wrap: pretty; }
.step__text p + p { margin-top: .7em; }
.step strong { font-weight: 500; color: var(--fg); }

/* 1–2, bone: big numbers on top. From tablets up, two equal columns with a hairline
   between, and both "Costs you nothing." lines level at the bottom. */
.how__panel--columns .step__num { font-size: clamp(64px, min(9vw, 15vh), 136px); line-height: .9; margin-bottom: clamp(12px, 2.4vh, 24px); }
.how__panel--columns .step + .step { margin-top: 44px; }
.step__free { margin-top: 22px; padding-top: 14px; border-top: 1px solid var(--line); font-size: var(--t-body); line-height: 1.4; }

/* 3–4, orange: each number hangs beside its title. On computers step 3 sits top left
   and step 4 lower right. The line about your date gets a thick rule beside it. */
.how__panel--stagger .step {
  display: grid; grid-template-columns: auto minmax(0, 1fr); column-gap: clamp(12px, 1.4vw, 20px); align-items: baseline;
}
.how__panel--stagger .step__num { font-size: calc(var(--t-h3) * 1.7); }
.how__panel--stagger .step > :not(.step__num) { grid-column: 2; }
.how__panel--stagger .step + .step { margin-top: 44px; }
.step__text .step__note { margin-top: 22px; padding-left: 18px; border-left: 3px solid var(--fg); text-wrap: balance; } /* a narrow column on phones: balanced, it never ends on one word */
.step__note strong {
  display: block; margin-bottom: 6px; font-family: var(--font-display); font-weight: 400;
  font-size: clamp(21px, 1.9vw, 27px); line-height: 1.2; letter-spacing: -.015em; text-wrap: balance;
}

/* 5–6, violet: a ledger. On phones each number sits at the end of its title's line; from
   tablets up each step is a row (number, title, text) and a hairline splits the panel in two. */
.how__panel--ledger .step { display: grid; grid-template-columns: minmax(0, 1fr) auto; column-gap: 16px; align-items: baseline; }
.how__panel--ledger .step__num { grid-column: 2; grid-row: 1; font-size: calc(var(--t-h3) * 1.5); }
.how__panel--ledger .step__title { grid-column: 1; grid-row: 1; }
.how__panel--ledger .step__text { grid-column: 1 / -1; }
.how__panel--ledger .step + .step { margin-top: 28px; padding-top: 24px; border-top: 1px solid var(--line); }
.step__domain { font-family: var(--font-mono); font-size: .92em; color: var(--fg); }

/* 7–8, green: step 7 small, step 8 big. From tablets up step 7 takes a narrow column on
   the left, and step 8 ends on its last line at the bottom of the panel. */
.how__panel--finale .step__num { font-size: calc(var(--t-h3) * 1.5); margin-bottom: 8px; }
.how__panel--finale .step + .step { margin-top: 40px; padding-top: 28px; border-top: 1px solid var(--line); }
.how__panel--finale .step + .step .step__title {
  font-size: clamp(40px, 5.4vw, 80px); line-height: 1; letter-spacing: -.025em; font-variation-settings: "opsz" 144;
}
.step__text .step__last {
  margin-top: 20px; font-family: var(--font-display); font-size: var(--t-h3);
  line-height: 1.2; letter-spacing: -.015em; color: var(--fg); text-wrap: balance;
}

.how__close {
  margin-top: clamp(64px, 12vh, 140px); max-width: 32ch;
  font-family: var(--font-display); font-weight: 400; font-size: clamp(24px, 2.8vw, 40px);
  line-height: 1.22; letter-spacing: -.015em; text-wrap: pretty;
}

@media (min-width: 720px) {
  .how__stack { --stack-gap: 24px; }

  .how__panel--columns { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); column-gap: calc(2 * var(--pad-x)); }
  .how__panel--columns .step { display: flex; flex-direction: column; }
  .how__panel--columns .step + .step { position: relative; margin-top: 0; }
  .how__panel--columns .step + .step::before { content: ""; position: absolute; top: 0; bottom: 0; left: calc(-1 * var(--pad-x)); width: 1px; background: var(--line); }
  .how__panel--columns .step__text { margin-bottom: 28px; }
  .how__panel--columns .step__free { margin-top: auto; }

  .how__panel--stagger { display: flex; flex-direction: column; }
  .how__panel--stagger .step + .step { margin: auto 0 0 16%; padding-top: 44px; }

  .how__panel--ledger { display: flex; flex-direction: column; }
  .how__panel--ledger .step { flex: 1 1 0; grid-template-columns: repeat(12, minmax(0, 1fr)); column-gap: var(--gap); }
  .how__panel--ledger .step__num { grid-column: 1 / span 2; font-size: clamp(52px, min(6vw, 11vh), 96px); line-height: .9; }
  .how__panel--ledger .step__title { grid-column: 3 / span 4; font-size: clamp(26px, 2.6vw, 38px); }
  .how__panel--ledger .step__text { grid-column: 7 / -1; grid-row: 1; margin-top: 0; }
  .how__panel--ledger .step + .step { margin-top: clamp(20px, 3vh, 32px); padding-top: clamp(20px, 3vh, 32px); }

  .how__panel--finale { display: grid; grid-template-columns: repeat(12, minmax(0, 1fr)); column-gap: var(--gap); }
  .how__panel--finale .step:first-child { grid-column: 1 / span 5; }
  .how__panel--finale .step + .step { grid-column: 7 / -1; display: flex; flex-direction: column; margin-top: 0; padding-top: 0; border-top: 0; }
  .how__panel--finale .step + .step .step__text { flex: 1; display: flex; flex-direction: column; }
  .how__panel--finale .step + .step .step__last { margin-top: auto; padding-top: 24px; }
}
@media (min-width: 960px) {
  .how__head { display: grid; grid-template-columns: repeat(12, minmax(0, 1fr)); column-gap: var(--gap); align-items: end; }
  .how__head .label { grid-column: 1 / -1; }
  .how__title { grid-column: 1 / span 7; }
  .how__intro { grid-column: 8 / -1; margin-top: 0; }

  .how__panel--stagger { display: grid; grid-template-columns: repeat(12, minmax(0, 1fr)); column-gap: var(--gap); }
  .how__panel--stagger .step:first-child { grid-column: 1 / span 5; align-self: start; }
  .how__panel--stagger .step + .step { grid-column: 7 / -1; align-self: end; margin: 0; padding-top: 0; }

  .how__panel--finale .step:first-child { grid-column: 1 / span 4; }
  .how__panel--finale .step + .step { grid-column: 6 / -1; }

  .how__close { margin-left: calc((100% + var(--gap)) / 12 * 5); }
}

/* the two ways of stacking (sections.js picks one) */
.how__stack.is-stack .how__slot,
.how__stack.is-flow .how__slot { position: sticky; }
.how__stack.is-stack .how__panel,
.how__stack.is-flow .how__panel { will-change: transform; }
/* .is-stack: the first panel sticks below the nav (on a very tall screen the stack is
   centred instead), each next one 18px lower. --front is the front panel's height: what
   is left of the screen under the stack, 820px at most. The panels behind are 18px taller
   each, so all four bottoms line up and the stack leaves as one. */
.how__stack.is-stack {
  --cap: 820px;
  --top: max(12vh, 86px, (100vh - var(--cap) - 54px - var(--stack-gap)) / 2);
  --front: min(100vh - var(--top) - 54px - var(--stack-gap), var(--cap));
}
.how__stack.is-stack .how__slot { top: calc(var(--top) + var(--i, 0) * 18px); }
.how__stack.is-stack .how__panel { height: calc(var(--front) + (3 - var(--i, 0)) * 18px); }
/* .is-flow: a panel stops with its bottom edge --dock above the bottom of the screen.
   sections.js sets --h, the slot's height. Every panel is at least that tall, so the
   next one covers the last one completely. On phones it stops above the WhatsApp
   button: 16px from the edge, plus the button's 48px, plus 12px clear. */
.how__stack.is-flow { --dock: var(--stack-gap); }
.how__stack.is-flow .how__slot { top: calc(100vh - var(--dock) - var(--h, 100vh)); }
.how__stack.is-flow .how__panel { min-height: calc(100vh - var(--dock)); }
@media (max-width: 719.98px) {
  .how__stack.is-flow { --dock: calc(76px + env(safe-area-inset-bottom, 0px)); }
}
/* svh: the screen with the browser's bars showing, which doesn't change as they slide away */
@supports (height: 100svh) {
  .how__stack.is-stack {
    --top: max(12svh, 86px, (100svh - var(--cap) - 54px - var(--stack-gap)) / 2);
    --front: min(100svh - var(--top) - 54px - var(--stack-gap), var(--cap));
  }
  .how__stack.is-flow .how__slot { top: calc(100svh - var(--dock) - var(--h, 100svh)); }
  .how__stack.is-flow .how__panel { min-height: calc(100svh - var(--dock)); }
}

/* ---------- 05 About ----------
   A 5/7 split, mirrored: the label, the heading and the founder's name on the left, the
   words on the right, the first line of the words level with the top of the heading. The
   last line ("Based in Bengaluru…") closes the words, under a hairline. The one-line
   paragraph is set larger: it's the turn in the story. Phones and tablets stack it all:
   label, heading, name, text. (There was a photo on the left until 11 October 2026.)
   Nothing here pins, scrubs or sticks. The words come in once, as they reach the screen
   (js/reveal.js), and only transform and opacity move, which keeps it smooth on phones. */
.about { padding-block: var(--section-pad); }
.about__title { margin-top: 18px; }
/* the founder, under the heading: the name in Fraunces at the block-title size, the role a
   label under it (ink 66%: 5.9:1) */
.about__by { margin-top: clamp(28px, 5vh, 48px); }
.about__name, .about__role { display: block; }
.about__name {
  font-family: var(--font-display); font-weight: 400; font-size: var(--t-h3);
  line-height: 1.15; letter-spacing: -.015em; color: var(--fg);
}
.about__role { margin-top: 10px; }
.about__copy { margin-top: clamp(48px, 8vh, 80px); container-type: inline-size; }
.about__copy p { text-wrap: pretty; }
/* the turn: a line for each half. "Meanwhile the competition down the road" is 18.5 to
   18.8em wide in Fraunces (measured; it varies a little with the optical size), so at
   100cqi / 19 it fits on one line in any column 460px or wider. In a narrower column
   (phones, and 960 to 1024px) it can't fit even at 24px, so it breaks before "down the
   road", which stays together: plain wrapping fills the first line, where balance or
   pretty could break after "Meanwhile the". */
.prose .about__turn {
  margin-top: clamp(28px, 4.5vh, 44px); max-width: none;
  font-family: var(--font-display); line-height: 1.16; letter-spacing: -.018em;
  font-size: clamp(24px, 2.4vw, 34px); font-size: clamp(24px, 100cqi / 19, 34px);
  color: var(--fg); text-wrap: wrap;
}
.about__turn-line { display: block; }
.about__keep { white-space: nowrap; }
.prose .about__turn + p { margin-top: clamp(28px, 4.5vh, 44px); }
.about__where {
  margin-top: clamp(40px, 6vh, 64px); padding-top: 14px; border-top: 1px solid var(--line);
  container-type: inline-size;
}
/* one line where it fits (53 characters of the label face with its tracking: 39.2em, 451px at
   11.5px); otherwise a line for each sentence, balanced where one sentence doesn't fit either
   (labels are 14px on phones). The em here is the label's own size. */
@container (max-width: 39.6em) {
  .about__where-line { display: block; text-wrap: balance; }
  .about__where-line + .about__where-line { margin-top: 5px; }
}

@media (min-width: 960px) {
  /* Two rows on the right: the text, then the last line. The left column spans both. The
     text starts one label line and the heading's 18px gap down, level with the heading. */
  .about__grid {
    display: grid; grid-template-columns: repeat(12, minmax(0, 1fr)); grid-template-rows: auto auto;
    column-gap: var(--gap); align-items: start;
  }
  .about__head { grid-column: 1 / span 5; grid-row: 1 / span 2; }
  .about__copy { grid-column: 7 / -1; grid-row: 1; margin-top: calc(var(--t-label) * 1.2 + 18px); }
  .about__where { grid-column: 7 / -1; grid-row: 2; }
}

/* ---------- Reviews ----------
   Its own section between About and Contact, bone like About, with no number in its label.
   Full width, unlike its neighbours (About's photo split, Contact's 7/5 split): the label on
   a hairline (it marks where About ends, as both are bone), the heading, and the Write a
   review link at the end of the heading's last line (under it on phones), then the cards.
   js/sections.js builds the cards from js/reviews.js and keeps one of the two headings.
   The text is ink from About to Contact (bone and orange both use it), so it never changes
   colour here, even when Contact's orange arrives behind the last cards.
   Smooth on phones: only the cards blur, over a glow that never moves; nothing is tied to
   scroll; only transform and opacity animate. */
.reviews { padding-block: clamp(40px, 6vh, 72px) var(--section-pad); }
.reviews__label { padding-top: 14px; border-top: 1px solid var(--line); }
.reviews__head { margin-top: 18px; }
.reviews__title { text-wrap: balance; }
.reviews__title em { font-style: italic; color: var(--accent); }
.reviews__write { margin-top: clamp(20px, 3.5vh, 32px); scroll-margin: 120px 0 32px; }

/* The cards: side by side where they fit (340px or wider each, three to a row at most),
   stacked on phones. No carousel. The row is only as wide as its cards (--n, set by
   sections.js). Light glass: the smoked .glass turns grey on bone, and bone text on it
   fails AA (3.3:1), so here the tint inside the same blur is bone-white, with ink text, a
   white rim and a softer shadow. Like .glass, the glass's own whites are fixed; the text
   uses the tokens. */
.reviews__list {
  list-style: none; display: grid; gap: var(--gap);
  grid-template-columns: repeat(auto-fill, minmax(min(100%, 340px), 1fr));
  width: min(100%, var(--n, 3) * 400px + (var(--n, 3) - 1) * var(--gap));
  margin-top: clamp(56px, 9vh, 104px);
}
/* Each card has its own still glow behind it, so the glass has colour to blur however many
   cards there are. It sits exactly under its card, with the same corners: it only shows
   through the glass, never spills on to the bone or on to the next card, and where it is cut
   off, the card's edge covers the cut. Soft gradients, no blur filter: a filter on every card
   is more for a phone to draw. Never animated. Alternate cards turn it round, so a row
   doesn't repeat itself. */
.reviews__item { position: relative; isolation: isolate; display: flex; }
.reviews__item::before {
  content: ""; position: absolute; inset: 0; z-index: -1; pointer-events: none; border-radius: 16px;
  background:
    radial-gradient(62% 56% at 22% 24%, rgba(91, 63, 217, .26), rgba(91, 63, 217, .18) 30%, rgba(91, 63, 217, .07) 65%, rgba(91, 63, 217, 0)),
    radial-gradient(56% 50% at 80% 80%, rgba(255, 86, 29, .18), rgba(255, 86, 29, .12) 30%, rgba(255, 86, 29, .04) 65%, rgba(255, 86, 29, 0));
}
.reviews__item:nth-child(even)::before {
  background:
    radial-gradient(62% 56% at 78% 22%, rgba(91, 63, 217, .26), rgba(91, 63, 217, .18) 30%, rgba(91, 63, 217, .07) 65%, rgba(91, 63, 217, 0)),
    radial-gradient(56% 50% at 20% 82%, rgba(255, 86, 29, .18), rgba(255, 86, 29, .12) 30%, rgba(255, 86, 29, .04) 65%, rgba(255, 86, 29, 0));
}
/* the cards' entrance: the same 16px rise and fade as .reveal (0.8s, power3.out), but as a CSS
   transition, which phones run off the main thread (sections.js adds .is-waiting, then .is-in) */
.reviews__item.is-waiting { opacity: 0; transform: translateY(16px); }
.reviews__item.is-waiting.is-in {
  opacity: 1; transform: none;
  transition: opacity .8s cubic-bezier(.165, .84, .44, 1), transform .8s cubic-bezier(.165, .84, .44, 1);
}
.review { position: relative; display: flex; flex-direction: column; width: 100%; border-radius: 16px; padding: clamp(24px, 2.6vw, 36px); }
.review.glass {
  background: linear-gradient(180deg, rgba(255, 255, 255, .62), rgba(255, 255, 255, .34));
  border-color: rgba(255, 255, 255, .72);
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, .9), 0 18px 50px rgba(12, 12, 10, .1);
}
/* the photo, if they sent one: a small square with square corners, like a print */
.review__photo {
  display: block; width: 72px; height: 72px; object-fit: cover; margin-bottom: 22px;
  background: color-mix(in srgb, var(--fg) 8%, transparent); /* holds its place until the file arrives */
}
.review__quote { quotes: "\201C" "\201D"; }
.review__quote p {
  font-family: var(--font-display); font-style: italic; font-size: 22px; line-height: 1.35;
  letter-spacing: -.01em; color: var(--fg); text-wrap: pretty;
}
.review__quote p::before { content: open-quote; }
.review__quote p::after { content: close-quote; }
.review__by { display: grid; gap: 6px; margin-top: auto; padding-top: 28px; }
.review__name { color: var(--fg); }

@media (min-width: 720px) {
  .reviews__line { display: block; } /* "Be the first" / "to write a review" */
}
@media (min-width: 960px) {
  /* the link sits on the baseline of the heading's last line (where last baseline isn't known,
     at the bottom of the heading) */
  .reviews__head { display: flex; align-items: flex-end; align-items: last baseline; justify-content: space-between; gap: var(--gap); }
  .reviews__write { flex: none; margin-top: -8px; }
  .review__photo { width: 80px; height: 80px; }
}

/* The review panel: a <dialog> the Write a review button opens. Solid black (its own
   data-theme), not glass: glass is only for the nav, the hero, the pricing cards and the
   review cards. Nothing behind it is blurred either: a blur over the whole screen is the
   heaviest thing a phone can be asked to draw. Phones: it fills the screen and slides up.
   From 720px: a tall panel on the right, over the dimmed page. Only transform and opacity
   animate: sections.js adds .is-open just after opening, and waits for the slide out before
   closing. While it's open the page behind is locked, so a thumb scrolls the form, not the
   page. Where locking takes a scrollbar away (Windows), sections.js pads the page by the
   scrollbar's width, so nothing behind shifts or reflows. (Not scrollbar-gutter: on the
   page it changes what vw measures, so every heading sized in vw would shrink a little.)
   The fields use the Contact form's .field rules: 16px text, so iPhones don't zoom in. */
.rpanel {
  position: fixed; inset: 0; width: auto; height: auto; max-width: none; max-height: none;
  margin: 0; padding: 0; border: 0; overflow: hidden;
  transform: translateY(100%); transition: transform .5s cubic-bezier(.2, .7, .2, 1);
}
.rpanel:not([open]) { display: none; }
.rpanel.is-open { transform: none; }
/* the dim layer: one flat colour, no blur. Fixed values: older Safari doesn't pass the tokens on to ::backdrop */
.rpanel::backdrop { background: rgba(5, 6, 15, .62); opacity: 0; transition: opacity .5s ease; }
.rpanel.is-open::backdrop { opacity: 1; }
html.rpanel-lock body { overflow: hidden; }
.rpanel__box {
  height: 100%; overflow-y: auto; overscroll-behavior: contain;
  padding: calc(14px + env(safe-area-inset-top, 0px)) var(--gutter) calc(40px + env(safe-area-inset-bottom, 0px));
}
.rpanel__top { display: flex; align-items: center; justify-content: space-between; gap: 16px; }
.rpanel__title {
  font-family: var(--font-display); font-weight: 400; font-size: var(--t-h3);
  line-height: 1.15; letter-spacing: -.015em; outline: none;
}
.rpanel__close { min-height: 44px; padding: 10px 18px; }
.rform { display: grid; gap: 20px; margin-top: clamp(28px, 5vh, 44px); }
.rform[hidden], .rform__done[hidden], .rform__fail[hidden] { display: none; }
.rform textarea {
  display: block; width: 100%; min-height: 152px; padding: 12px 14px; border-radius: 4px; resize: vertical;
  border: 1px solid var(--fg-soft); border-color: color-mix(in srgb, var(--fg) 56%, transparent);
  background: transparent; color: var(--fg); font: 400 16px/1.5 var(--font-body);
  -webkit-appearance: none; appearance: none; transition: border-color .2s ease, box-shadow .2s ease;
}
.rform textarea:hover { border-color: var(--fg); }
.rform textarea:focus, .rform textarea[aria-invalid="true"] { outline: none; border-color: var(--fg); box-shadow: 0 0 0 1px var(--fg); }
.rform__send { justify-self: start; min-height: 48px; margin-top: 8px; padding-inline: 28px; }
.rform__fail { font-size: var(--t-small); line-height: 1.5; }
.rform__fail a { text-decoration: underline; text-decoration-thickness: 1px; text-underline-offset: 4px; }
.rform__fail a:hover { text-decoration-thickness: 2px; }
.rform__fail a:focus-visible { outline: 2px solid var(--fg); outline-offset: 3px; }
.rform__done { margin-top: clamp(28px, 5vh, 44px); outline: none; }
.rform__done-title {
  font-family: var(--font-display); font-weight: 400; font-size: var(--t-display);
  line-height: 1; letter-spacing: -.025em; font-variation-settings: "opsz" 144;
}
.rform__done p + p { margin-top: 14px; font-size: var(--t-body); line-height: 1.6; color: var(--fg-soft); max-width: 40ch; }
.rpanel__photos { margin-top: 32px; padding-top: 24px; border-top: 1px solid var(--line); }

@media (min-width: 720px) {
  .rpanel { left: auto; width: min(560px, 100%); transform: translateX(100%); }
  .rpanel__box { padding: clamp(28px, 4vh, 44px) clamp(28px, 3vw, 48px) calc(48px + env(safe-area-inset-bottom, 0px)); }
}
@media (prefers-reduced-motion: reduce) {
  .rpanel, .rpanel::backdrop, .rform textarea { transition: none; }
  .rpanel { transform: none; }
}

/* ---------- 06 Contact ----------
   7/5 split: "Let's talk", the copy, the call to action ("Start your project", ink on orange)
   and the three steps on the left, the form lower on the right. */
.contact { padding-block: var(--section-pad); overflow-x: clip; }
/* when the Tab key scrolls a link or a field into view, keep it (and a field's label)
   clear of the fixed nav at the top and of the screen's bottom edge */
.contact a, .contact input, .contact button, .foot a { scroll-margin: 120px 0 32px; }
.contact__grid { display: grid; gap: clamp(72px, 12vh, 128px) var(--gap); }
.contact__main { container-type: inline-size; }

/* The title fills its column. "Let's talk" is 3.01em wide in Fraunces (opsz 144, -.03em
   tracking), but the italic k reaches on to 3.11em (measured), so 100cqi / 3.12 ends the
   ink just inside the column. */
.contact__title {
  margin-top: 18px; font-family: var(--font-display); font-weight: 400; font-variation-settings: "opsz" 144;
  font-size: clamp(72px, 14vw, 200px); font-size: calc(100cqi / 3.12); line-height: .92; letter-spacing: -.03em;
}
.contact__title em { font-style: italic; color: var(--accent); }

.contact__copy { margin-top: clamp(40px, 7vh, 72px); }
/* the call to action, under the copy, with "what happens after you message" right under it */
.contact__cta { margin-top: clamp(28px, 4.5vh, 40px); }
.contact__lead {
  font-family: var(--font-display); font-weight: 400; font-size: clamp(22px, 2.1vw, 30px);
  line-height: 1.25; letter-spacing: -.015em; max-width: 30ch; text-wrap: pretty;
}
.contact__copy p + p { margin-top: 16px; font-size: var(--t-body); line-height: 1.6; color: var(--fg-soft); max-width: 62ch; text-wrap: pretty; }

/* what happens after you message: three small numbered lines. The numbers are decoration
   (the list already gives the order), so screen readers skip them where "/ ''" works. */
.next { list-style: none; counter-reset: next; margin-top: clamp(32px, 5vh, 48px); max-width: 30rem; }
.next li {
  display: flex; align-items: baseline; counter-increment: next;
  padding-block: 12px; border-top: 1px solid var(--line); font-size: var(--t-small); line-height: 1.5; text-wrap: pretty;
}
.next li:last-child { border-bottom: 1px solid var(--line); }
.next li::before {
  content: counter(next, decimal-leading-zero); content: counter(next, decimal-leading-zero) / "";
  flex: 0 0 3.4em; font-family: var(--font-mono); font-size: var(--t-label); letter-spacing: .14em; color: var(--fg-soft);
}

.contact__email { margin-top: clamp(32px, 5vh, 48px); font-size: var(--t-body); line-height: 1.5; }
.contact__email a, .cform__fail a { text-decoration: underline; text-decoration-thickness: 1px; text-underline-offset: 4px; }
/* the email and the forms' "Message on WhatsApp": at least 44px tall to tap; the padding,
   cancelled by the margin, takes no room */
.contact__email a, .cform__fail a, .rform__fail a {
  display: inline-block; padding-block: calc((45px - 1.5em) / 2); margin-block: calc((1.5em - 45px) / 2);
}
.contact__email a:hover, .cform__fail a:hover { text-decoration-thickness: 2px; }
.contact__email a:focus-visible, .cform__fail a:focus-visible, .foot a:focus-visible { outline: 2px solid var(--fg); outline-offset: 3px; }
.contact__reply { margin-top: 6px; font-size: var(--t-small); line-height: 1.5; color: var(--fg-soft); }

/* the form: labels above, 48px inputs, 4px corners, no glass. Borders are ink at 56%
   (3.05:1 on orange; a form border needs 3:1). Messages are ink, never red: red fails on orange. */
.contact__or {
  font-family: var(--font-display); font-weight: 400; font-size: clamp(22px, 2.1vw, 30px);
  line-height: 1.25; letter-spacing: -.015em; max-width: 20ch; text-wrap: balance;
}
.cform { display: grid; gap: 20px; margin-top: clamp(24px, 4vh, 36px); }
.cform[hidden], .cform__done[hidden], .cform__fail[hidden] { display: none; }
.field label { display: block; margin-bottom: 8px; font: 500 15px/1.3 var(--font-body); }
.field input {
  display: block; width: 100%; height: 48px; padding: 0 14px; border-radius: 4px;
  border: 1px solid var(--fg-soft); border-color: color-mix(in srgb, var(--fg) 56%, transparent);
  background: transparent; color: var(--fg); font: 400 16px/1.25 var(--font-body);
  -webkit-appearance: none; appearance: none; transition: border-color .2s ease, box-shadow .2s ease;
}
.field input:hover { border-color: var(--fg); }
.field input:focus, .field input[aria-invalid="true"] { outline: none; border-color: var(--fg); box-shadow: 0 0 0 1px var(--fg); }
/* the browser's autofill paints its own pale fill: paint the section colour back over it */
.field input:-webkit-autofill { -webkit-text-fill-color: var(--fg); caret-color: var(--fg); box-shadow: inset 0 0 0 48px var(--page); }
.field input:-webkit-autofill:focus { box-shadow: inset 0 0 0 48px var(--page), 0 0 0 1px var(--fg); }
.field__msg { margin-top: 8px; font-size: var(--t-small); line-height: 1.4; }
.field__msg:empty { display: none; }
.cform__send { justify-self: start; min-height: 48px; margin-top: 8px; padding-inline: 28px; }
/* Send is the quieter, outlined button, so it doesn't compete with "Start your project". Its
   border is the fields' own, ink 56%: 3.05:1 on orange (a control's edge needs 3:1). */
.cform__send.btn--ghost { border-color: var(--fg-soft); border-color: color-mix(in srgb, var(--fg) 56%, transparent); }
.cform__send.btn--ghost:not(:disabled):hover { border-color: var(--fg); }
.cform__fail { font-size: var(--t-small); line-height: 1.5; }
.cform__done { margin-top: clamp(24px, 4vh, 36px); outline: none; }
.cform__done-title {
  font-family: var(--font-display); font-weight: 400; font-size: var(--t-display);
  line-height: 1; letter-spacing: -.025em; font-variation-settings: "opsz" 144;
}
.cform__done p + p { margin-top: 14px; font-size: var(--t-body); line-height: 1.6; color: var(--fg-soft); }

@media (min-width: 960px) {
  .contact__grid { grid-template-columns: repeat(12, minmax(0, 1fr)); column-gap: var(--gap); align-items: end; }
  .contact__main { grid-column: 1 / span 7; }
  .contact__side { grid-column: 8 / -1; }
}
@media (prefers-reduced-motion: reduce) {
  .field input { transition: none; }
}

/* ---------- Footer ----------
   One screen tall: the small row at the top, then the full logo, centred in what is left.
   A full screen, so the background has turned black (it does when the footer's top passes
   the middle of the screen) before the logo draws itself in, as it did in the intro. */
.foot { overflow: hidden; overflow: clip; }
.foot__inner {
  --foot-top: clamp(96px, 14vh, 140px); --foot-row: 40px;
  --foot-gap: clamp(40px, 7vh, 88px); --foot-bottom: clamp(40px, 7vh, 88px);
  display: flex; flex-direction: column; min-height: 100vh; min-height: 100svh;
  padding-top: var(--foot-top); padding-bottom: var(--foot-bottom);
}
.foot__row { display: grid; grid-template-columns: 1fr 1fr; gap: 12px var(--gap); padding-top: 18px; border-top: 1px solid var(--line); }
/* phones: one column (in 14px labels, "Bengaluru, India" barely fits half the row, and the two
   link pairs run together), with the rows 28px apart, so the links' 44px tap areas don't overlap */
@media (max-width: 479.98px) { .foot__row { grid-template-columns: 1fr; row-gap: 28px; } }
.foot__name, .foot__row a { color: var(--fg); }
/* at least 44px tall to tap: the padding, cancelled by the margin, takes no room */
.foot__row a { display: inline-block; padding-block: calc((45px - 1.2em) / 2); margin-block: calc((1.2em - 45px) / 2); }
.foot__row a:hover { text-decoration: underline; text-underline-offset: 3px; }
.foot__mark { flex: 1; display: grid; place-items: center; padding-top: var(--foot-gap); }
/* centred; as wide as the column, 680px at most, and never taller than the screen has room
   for. The artwork is 521.6 x 323.5, so width = height x 1.6125. */
.lockup {
  display: block; height: auto; overflow: visible;
  width: min(100%, 680px);
  width: min(100%, 680px, max(260px, calc((100svh - var(--foot-top) - var(--foot-row) - var(--foot-gap) - var(--foot-bottom)) * 1.6125)));
}
@media (min-width: 720px) {
  .foot__row { display: flex; justify-content: space-between; }
}
.foot__row a[aria-current="page"] { text-decoration: underline; text-underline-offset: 3px; }

/* ---------- Launch pages: privacy.html, terms.html, 404.html ----------
   Plain pages with no JavaScript: nothing moves but the nav's neon edge. Each names its
   theme on <html> (bone for the two legal pages, black for 404), so the nav takes its look
   for that colour (see "section colours" at the top) and the whole page, overscroll
   included, is painted in it. The nav and the footer are the main page's, their links
   pointing back to index.html; here the footer logo is simply there. */
html.page-doc, html.page-doc body { background: var(--page); color: var(--fg); }

/* The legal pages: the date, the title and one lead line across the top, then the text in
   one column, 62ch at most. From 960px the list of sections stays beside it (CSS sticky). */
.doc { padding-block: var(--section-pad); }
.doc__title { margin-top: 18px; }
.doc__title em, .lost__title em { color: var(--accent); }
.doc__lead {
  margin-top: clamp(24px, 4vh, 40px); max-width: 34ch; text-wrap: pretty;
  font-family: var(--font-display); font-weight: 400; font-size: clamp(22px, 2.1vw, 30px);
  line-height: 1.25; letter-spacing: -.015em;
}
.doc__grid { margin-top: clamp(56px, 9vh, 96px); padding-top: 18px; border-top: 1px solid var(--line); }
.doc__toc { display: none; }
.doc__body { counter-reset: doc; }
.doc__body > section { counter-increment: doc; scroll-margin-top: 96px; }
.doc__body > section + section { margin-top: clamp(48px, 7vh, 72px); }
.doc__body h2 {
  font-family: var(--font-display); font-weight: 400; font-size: var(--t-h3);
  line-height: 1.15; letter-spacing: -.015em; max-width: 26ch; text-wrap: balance;
}
/* section numbers in the mono label face. The order already gives them, so screen readers
   skip them where "/ ''" works (as in Contact's three steps). */
.doc__body h2::before, .doc__toc li::before {
  content: counter(doc, decimal-leading-zero); content: counter(doc, decimal-leading-zero) / "";
  font-family: var(--font-mono); font-size: var(--t-label); letter-spacing: .14em; color: var(--fg-soft);
}
.doc__body h2::before { display: block; margin-bottom: 14px; line-height: 1.2; }
.doc__body p, .doc__body li { font-size: var(--t-body); line-height: 1.6; max-width: 62ch; text-wrap: pretty; }
.doc__body h2 + * { margin-top: 20px; }
.doc__body p + p, .doc__body p + ul, .doc__body ul + p { margin-top: 1em; }
.doc__body ul { list-style: none; }
.doc__body li { position: relative; padding-left: 1.5em; }
.doc__body li + li { margin-top: .75em; }
.doc__body li::before { content: ""; position: absolute; left: 0; top: .8em; width: .7em; border-top: 1px solid currentColor; }
.doc__body strong { font-weight: 500; }
.doc__body a { text-decoration: underline; text-decoration-thickness: 1px; text-underline-offset: 3px; }
.doc__body a:hover { text-decoration-thickness: 2px; }
.doc a:focus-visible { outline: 2px solid var(--fg); outline-offset: 3px; }
.doc__toc ol { list-style: none; counter-reset: doc; }
.doc__toc li { counter-increment: doc; display: flex; align-items: baseline; gap: 12px; font-size: var(--t-small); line-height: 1.4; }
.doc__toc li + li { margin-top: 10px; }
.doc__toc li::before { flex: 0 0 2.4em; }
.doc__toc a { color: var(--fg-soft); }
.doc__toc a:hover { color: var(--fg); text-decoration: underline; text-underline-offset: 3px; }
@media (min-width: 960px) {
  .doc__grid { display: grid; grid-template-columns: repeat(12, minmax(0, 1fr)); column-gap: var(--gap); align-items: start; }
  .doc__toc { display: block; grid-column: 1 / span 3; position: sticky; top: 104px; }
  .doc__body { grid-column: 5 / -1; }
}
/* rupee amounts in Fraunces: Instrument Sans has no ₹ sign */
.inr { font-family: var(--font-display); }

/* 404: one black screen. The words sit low on the left, like the end of a section, with
   the way home under them. */
.lost {
  display: flex; flex-direction: column; justify-content: flex-end;
  min-height: 100vh; min-height: 100svh; padding-block: 120px clamp(56px, 12vh, 120px);
}
.lost__title { margin-top: 18px; }
.lost__text { margin-top: 24px; max-width: 40ch; font-size: var(--t-body); line-height: 1.6; color: var(--fg-soft); text-wrap: pretty; }
.lost__home { margin-top: 36px; }

/* ---------- intro (Ignition) ---------- */
#intro { position: fixed; inset: 0; z-index: 60; overflow: hidden; }
#introBg { position: absolute; inset: 0; background: #000; }
#introStage { position: absolute; }
#cam { position: absolute; inset: 0; will-change: transform; }
#grid {
  position: absolute; inset: 0; opacity: 0;
  background-image:
    linear-gradient(rgba(223, 217, 201, .028) 1px, transparent 1px),
    linear-gradient(90deg, rgba(223, 217, 201, .028) 1px, transparent 1px);
  background-size: 56px 56px;
}
#glow {
  position: absolute; width: 620px; height: 620px; left: 0; top: 0; margin: -310px 0 0 -310px; pointer-events: none;
  background: radial-gradient(closest-side, rgba(223, 217, 201, .09), rgba(142, 124, 255, .04) 44%, transparent 72%);
}
#dust { position: absolute; inset: 0; pointer-events: none; }
#mark { position: absolute; inset: 0; width: 100%; height: 100%; overflow: visible; }
[id="Line 1"] { transform-box: fill-box; transform-origin: left center; }
#wave {
  position: absolute; left: 50%; top: 43%; width: 60px; height: 60px; margin: -30px 0 0 -30px;
  border-radius: 50%; border: 1px solid rgba(223, 217, 201, .55); opacity: 0; pointer-events: none;
}
#lbar {
  position: absolute; left: 45.55%; top: 52.19%; width: 2px; height: 13.25%; opacity: 0; pointer-events: none;
  background: #DFD9C9; box-shadow: 0 0 16px 3px rgba(223, 217, 201, .6);
}
#flash { position: absolute; inset: 0; background: #DFD9C9; opacity: 0; pointer-events: none; mix-blend-mode: screen; }
#introFx { position: absolute; inset: 0; pointer-events: none; }
#introFx .vig { position: absolute; inset: 0; background: radial-gradient(ellipse at 50% 45%, transparent 42%, rgba(0, 0, 0, .55) 100%); }
#skipHint {
  position: absolute; left: 50%; bottom: 26px; transform: translateX(-50%);
  font-size: 11px; letter-spacing: .28em; text-transform: uppercase; color: rgba(223, 217, 201, .55);
}
/* phones: 14px. Its colour was .35 (2.46:1) until 11 October 2026; .55 passes AA on the intro's black */
@media (max-width: 719.98px) { #skipHint { font-size: 14px; } }

/* ---------- dev readout ---------- */
#hud { /* hidden unless the address ends in ?debug (hero.js) */
  display: none; position: fixed; left: 14px; bottom: 14px; z-index: 50; pointer-events: none;
  font: 11px/1.4 ui-monospace, Menlo, Consolas, monospace; color: #9d99c4;
  background: rgba(5, 6, 15, .75); border: 1px solid rgba(142, 124, 255, .2); border-radius: 8px; padding: 7px 10px;
}
