/* ==========================================================================
   NETTORRA — homepage
   Editorial navy build: Space Grotesk set large and light with very tight
   tracking, Manrope for reading, DM Mono for every label and number. Structure
   is carried by hairlines rather than cards — nothing on this page has a
   shadow or a glass fill. Scoped to .page-home so the three showcase demos
   keep their own client palettes untouched.
   ========================================================================== */

.page-home {
  --ink: #0B1427;
  --surface: #101A31;
  --soft: #101B33;
  --line: rgba(174, 198, 248, .17);
  --muted: #ADB8D0;
  --white: #F2F5FF;

  /* Slightly deeper than the brand trio so they sit down into the navy
     instead of glowing off it. */
  --cyan: #27B8E8;
  --blue: #2A70ED;
  --violet: #8D43EB;

  --font-display: "Space Grotesk", "Helvetica Neue", Arial, sans-serif;
  --font-body: "Manrope", "Helvetica Neue", Arial, sans-serif;
  --font-mono: "DM Mono", ui-monospace, SFMono-Regular, Menlo, monospace;

  /* Two measures: the chrome and the hero run wide, the reading sections
     pull in so long lines never outrun the eye. */
  --wide: 1320px;
  --read: 1200px;

  background: var(--ink);
  color: var(--white);
  font-family: var(--font-body);
  font-size: 1rem;
  line-height: 1.65;
}

.page-home h1,
.page-home h2,
.page-home h3 {
  font-family: var(--font-display);
  font-weight: 400;
  letter-spacing: -.075em;
  text-wrap: balance;
}

.page-home ::selection { background: rgba(39, 184, 232, .3); color: #fff; }
.page-home :focus-visible { outline: 2px solid var(--cyan); outline-offset: 4px; }

/* ------------------------------------------------------------- the field

   The circuit texture that used to be a single band under the homepage hero is
   the ground the whole site stands on now. Three things had to be true at once
   and each one decided part of how this is built.

   It has to run the full height of any page. The source artwork is a root fan —
   a dense band roughly 2.9:1 that dies out below — and a phone viewport is
   0.46:1. One copy scaled to cover a phone puts the traces at six times their
   drawn weight; four copies stacked cover it at the right weight but cost
   something like 75MB of bitmap at desktop size. So the SVG is cropped to its
   dense band and tiled instead. /images/circuit-tile.svg is circuit-roots.svg
   with the fade mask taken off, the depth decay flattened, and the viewBox cut
   at y=1080 — the height where trace density either side of the join matches
   closely enough that the seam cannot be found. One rasterisation, repeated by
   the compositor.

   It has to survive a page change. js/router.js replaces only <main id="page">,
   so this sits outside it, fixed to the viewport, and simply never reloads —
   the field is continuous across a click in a way a per-page band never was.

   It must not animate a paint property. Moving background-position on a masked
   layer re-rasterises the mask every frame; that was measured at 30ms median
   when the old roots band was first built that way. Nothing here moves except
   two transforms on pseudo-elements, inside a layer that itself never moves.

   One structural requirement: no ancestor may create a containing block for a
   fixed child, so body must stay clear of transform, filter, contain and
   will-change. Checked, and worth re-checking before adding any of them. */
.page-home .field {
  position: fixed;
  inset: 0;
  z-index: -1;
  overflow: hidden;
  pointer-events: none;

  --fw: min(168vw, 2000px);
  --fo: .42;

  /* The wash that keeps the left of the screen clear for type. It belongs on
     this element rather than on the artwork: the artwork is about 2.6x the
     viewport wide, so a percentage gradient laid on it resolves against a box
     running far off-screen and "clear at the left" lands somewhere nobody can
     see. That was the first prototype, and the traces ran straight through the
     headlines. On a viewport-sized box, 0% is the left edge of the screen. */
  -webkit-mask-image: linear-gradient(90deg, rgba(0,0,0,.10) 0%, rgba(0,0,0,.26) 26%, rgba(0,0,0,.58) 52%, #000 80%);
  mask-image: linear-gradient(90deg, rgba(0,0,0,.10) 0%, rgba(0,0,0,.26) 26%, rgba(0,0,0,.58) 52%, #000 80%);
}

/* Twice the viewport tall so the slow drift below always has tile beneath it. */
.page-home .field__art,
.page-home .field__cur {
  position: absolute;
  left: 0;
  right: 0;
  top: 0;
  height: 200%;
  background-size: var(--fw) auto;
  background-repeat: repeat-y;
  background-position: right top;
  /* A sixth of a viewport of drift across the whole document: enough that the
     field is not nailed to the glass, small enough that it never reads as a
     second thing moving behind the one you are reading. */
  transform: translate3d(0, calc(var(--doc, 0) * -16vh), 0);
}

.page-home .field__art {
  background-image: url(/images/circuit-tile.svg);
  opacity: var(--fo);
}

/* The current: the same tile used as a mask over a band of light, so the light
   can only appear where there is a trace to carry it. */
.page-home .field__cur {
  overflow: hidden;
  -webkit-mask-image: url(/images/circuit-tile.svg);
  mask-image: url(/images/circuit-tile.svg);
  -webkit-mask-size: var(--fw) auto;
  mask-size: var(--fw) auto;
  -webkit-mask-repeat: repeat-y;
  mask-repeat: repeat-y;
  -webkit-mask-position: right top;
  mask-position: right top;
}

/* Two passes of one comet — a blurred halo and a sharp core over it. The band
   is 34% of a 200%-tall layer, so 68vh, and the offsets put it entirely above
   the fold at --doc 0 and entirely below it at --doc 1: one clean sweep per
   page, never already parked mid-screen when you arrive. */
.page-home .field__cur::before,
.page-home .field__cur::after {
  content: "";
  position: absolute;
  left: 0;
  right: 0;
  top: 0;
  height: 34%;
  transform: translate3d(0, calc(var(--doc, 0) * 247% - 100%), 0);
  will-change: transform;
}
.page-home .field__cur::before {
  background-image: linear-gradient(180deg,
    rgba(71,214,246,0) 0%, rgba(71,214,246,.20) 18%, rgba(71,214,246,.48) 37%,
    rgba(168,236,255,.90) 46%, #fff 51%, rgba(197,168,255,.86) 56%,
    rgba(123,59,233,.50) 67%, rgba(46,91,235,.16) 85%, rgba(46,91,235,0) 100%);
  filter: blur(3px);
}
.page-home .field__cur::after {
  background-image: linear-gradient(180deg,
    rgba(168,236,255,0) 34%, rgba(210,245,255,.85) 47%, #fff 51%,
    rgba(214,190,255,.7) 57%, rgba(123,59,233,0) 70%);
}

/* Nothing writes --doc when the scroll engine is off, so the artwork parks at
   the top of its own accord and stays a still texture, which is all it needs to
   be. The current is the part that would read as broken sitting still. */
@media (prefers-reduced-motion: reduce) {
  .page-home .field__cur { display: none; }
}

@media (max-width: 760px) {
  /* Wider and a touch stronger on a phone. The tile has to be blown up to keep
     the traces from turning into a mesh at this width, and the extra opacity
     buys back what the enlargement costs in contrast. */
  .page-home .field { --fw: max(215vw, 840px); --fo: .48; }

  /* The current comes down, though, and for the opposite reason to the art
     going up. On a desktop the wash keeps the left of the screen clear and the
     type sits in that clear part; on a phone the column is the whole width, so
     there is nowhere for a paragraph to stand that the light does not cross.
     Measured on process.html at 390px: at full strength the core washed out the
     last two lines of the third step. */
  .page-home .field__cur { opacity: .66; }
}

/* Section shells. Every band is measured from the same two widths. */
.page-home .statement,
.page-home .services,
.page-home .work,
.page-home .contact {
  width: min(100% - 48px, var(--read));
  margin-inline: auto;
}

/* --- Shared type ------------------------------------------------------- */

/* One label style for the whole page: mono, uppercase, tiny. The diamond only
   appears on the hero so the eyebrow there reads as the page opening rather
   than as one more section marker. */
.page-home .eyebrow,
.page-home .kicker {
  display: flex;
  align-items: center;
  gap: .55rem;
  margin: 0 0 0;
  font-family: var(--font-mono);
  font-size: .64rem;
  font-weight: 400;
  letter-spacing: .035em;
  text-transform: uppercase;
  color: #6DCDF3;
}
.page-home .eyebrow { color: #B9D9FB; }
.page-home .eyebrow::before { content: none; }
.page-home .eyebrow i {
  width: .42rem;
  height: .42rem;
  background: var(--cyan);
  box-shadow: 0 0 14px var(--cyan);
  clip-path: polygon(50% 0, 61% 39%, 100% 50%, 61% 61%, 50% 100%, 39% 61%, 0 50%, 39% 39%);
}

/* Every kicker is a node on the same circuit the tree and the roots are drawn
   from: a square pad, and a stub of trace running out of it. Dark until the
   section arrives, then the pad takes power and the trace draws out of it — so
   scrolling down the page reads as working down a board, section by section,
   rather than as five headings that happen to share a colour.

   The trace is a fixed short stub rather than a rule running to the margin. A
   full-width line would be a divider, and a divider says "the section above has
   ended". This says "power reaches here", which is the opposite claim and the
   true one. */
.page-home .kicker::before {
  content: "";
  flex: none;
  width: .4rem;
  height: .4rem;
  border-radius: 1px;
  background: currentColor;
  opacity: .45;
  transition: opacity .6s var(--ease-out) .1s, box-shadow .6s var(--ease-out) .1s;
}
.page-home .kicker::after {
  content: "";
  flex: none;
  width: 3.4rem;
  height: 1px;
  margin-left: .1rem;
  background: linear-gradient(90deg, currentColor, transparent);
  opacity: .5;
  transform: scaleX(0);
  transform-origin: 0 50%;
  transition: transform .85s var(--ease-out) .18s;
}
.page-home .kicker.is-in::before {
  opacity: 1;
  box-shadow: 0 0 11px 1px rgba(71, 214, 246, .8);
}
.page-home .kicker.is-in::after { transform: scaleX(1); }

/* The pad and the trace are the reveal on a reduced-motion pass — drawn on,
   not drawn in. Nothing here is load-bearing for reading the label. */
@media (prefers-reduced-motion: reduce) {
  .page-home .kicker::before { opacity: 1; }
  .page-home .kicker::after { transform: scaleX(1); }
}

.page-home .accent { font-style: normal; color: #B68CFF; text-shadow: 0 0 35px rgba(141, 67, 235, .27); }
.page-home .accent--cyan { color: var(--cyan); text-shadow: none; }
.page-home .accent--pale { color: #E1BBFF; text-shadow: none; }

/* --- Buttons ----------------------------------------------------------- */

.page-home .button,
.page-home .btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 1.1rem;
  min-height: 48px;
  padding: .8rem 1.05rem .8rem 1.25rem;
  border: 1px solid transparent;
  border-radius: .38rem;
  font-family: var(--font-body);
  font-size: .74rem;
  font-weight: 700;
  letter-spacing: 0;
  background: none;
  transition: transform .2s ease, box-shadow .2s ease, background .2s ease, color .2s ease;
}
.page-home .button:hover,
.page-home .btn:hover { transform: translateY(-2px); }

.page-home .button-primary {
  color: var(--ink);
  background: var(--white);
  box-shadow: 0 0 0 1px rgba(39, 184, 232, .78), 0 9px 34px rgba(42, 112, 237, .32);
}
.page-home .button-primary:hover,
.page-home .button-primary:focus-visible {
  background: #fff;
  box-shadow: 0 0 0 1px var(--white), 0 14px 38px rgba(39, 184, 232, .42);
}

.page-home .button-line {
  color: var(--white);
  border-color: rgba(255, 255, 255, .46);
}
.page-home .button-line:hover,
.page-home .button-line:focus-visible { color: var(--ink); background: var(--white); }

.page-home .button span { font-size: 1rem; }

.page-home .text-link {
  display: inline-flex;
  align-items: center;
  gap: .65rem;
  color: var(--white);
  font-size: .75rem;
  font-weight: 600;
  transition: color .2s ease, gap .2s ease;
}
.page-home .text-link:hover,
.page-home .text-link:focus-visible { color: #82D8F8; gap: .85rem; }

/* Twelve-pixel caps make a hit area twenty pixels tall. Measured on the phone,
   "Start a project" — which is the one text link on this page that is also the
   call to action — was 105x20 against a floor of 44 on iOS and 48 on Android.
   A target you have to aim at and miss twice is indistinguishable from a link
   that does not work, which is how it was reported.

   Padding to grow the target around the words rather than min-height, which
   would hang the extra room off one end of them, and a matching negative margin
   so the layout keeps the spacing it was designed with. Guarded on the pointer
   rather than the width, because this is about fingers, not screens. */
@media (pointer: coarse) {
  .page-home .text-link { padding-block: .75rem; margin-block: -.75rem; }
}

/* ==========================================================================
   HERO
   Art bleeds off the right, a hard navy wash carries it back to black under
   the type, and a masked grid ties the two halves together.
   ========================================================================== */

/* The hero is base.css's .reel — a pinned frame the visitor scrubs a film
   through — wearing this page's composition. Shorter travel than the showcase
   pages use: this is the page that has to earn an enquiry, and two full
   screens of scrolling before the site says anything past the headline is too
   much to ask of somebody deciding whether to call. */
.page-home .hero-reel {
  --reel-ground: var(--ink);
  --reel-accent: var(--cyan);
  /* This page's film runs to 0.82, not the 0.76 base.css is written around, so
     the pair has to be restated here or the frame begins contracting while the
     camera is still moving. --open-rate stays the reciprocal of what is left
     after --open-at: 1 / (1 - .82). Nothing depended on this while the copy was
     visible throughout; the landing depends on it entirely. */
  --open-at: .82;
  --open-rate: 5.556;
  height: 240svh;
}

/* This height, and the shorter one in the phone block, both out-specify the
   height:auto base.css applies for reduced motion. The correction is at the
   very bottom of this file, where it comes after both of them. */

/* Everything the old .hero did: the column that puts the copy in the middle
   and the facts strip along the floor, and the flat navy the washes ramp back
   to. The frame is the hero now. */
.page-home .hero-reel .reel__frame {
  display: flex;
  flex-direction: column;
  background: var(--ink);
}

/* The scrim base.css draws for a full-bleed film is wrong here and is turned
   off: this hero has its own two washes, tuned to a picture that only occupies
   the right of the frame, and stacking a second darkening pass on top of them
   flattens the left half to slate. */
.page-home .hero-reel .reel__scrim { display: none; }

/* The flight veil, removed — and the reasoning is worth keeping, because it was
   good reasoning right up until it stopped being true.

   There was a third wash here, on .reel__frame::after. The two washes below are
   tuned to the still, which is dark everywhere the type sits; the film is not.
   Halfway through it climbs past the glowing core, and for a second or so the
   brightest thing in the frame sat directly behind "momentum." and the facts
   strip. So a veil rode up through the flight and off again by the landing,
   holding the headline clear of the brightest frames.

   Every word of that depended on the headline being on screen during the
   flight. It is not any more — it waits for the film, along with everything
   else the veil was covering for. What was left was a wash sitting at 88% over
   the best moment in the shot, protecting nothing. Measured on a phone at the
   core, it was the difference between a dim smudge and a tree lighting up.

   The landing does not miss it. Between the copy arriving and the scroll
   ending, the film is on its final approach to the still, and the still is what
   the two washes below were tuned for in the first place — checked at the
   landing with the veil forced off, and the headline reads the same. */

/* The box is 16:9 because the flight and the still are 16:9, and a box that
   matches its picture is a box that cannot crop it. It used to be the full
   height of the pin — a 1.5:1 hole for a 1.78:1 picture, which under `cover`
   was throwing away a fifth of the frame's width before a scale(1.045) took
   another slice off every edge. The whole point of a scroll film is that you
   watch the shot, so the shot is now shown whole.

   Centred with auto block margins rather than a translate: the scroll already
   drives a transform on the still, and two things writing the same property is
   how the art ends up jumping. max-height is the guard for a short, wide window
   — if it ever bites, the box goes wider than 16:9 and `contain` letterboxes at
   the sides instead of cropping. */
.page-home .hero-art {
  position: absolute;
  z-index: -4;
  top: 0;
  bottom: 0;
  right: -4vw;
  width: min(93vw, 1370px);
  height: auto;
  aspect-ratio: 16 / 9;
  max-height: 100%;
  margin-block: auto;
  overflow: hidden;
}
/* Same plate, same crop, same grade, same geometry — and deliberately NOT the
   same frame, which is the one way this page departs from the poster rule the
   three showcases follow.

   Everywhere else the still is the film's opening frame, because a still under a
   scrub film is only ever seen in the instant before the film is ready and then
   never again; anything but frame 0 shows up as a jump on every load. Here the
   still is the film's LAST frame, the tree, and it stays that way because on
   this page the still is not only a pre-roll. The film is held in data-src until
   the script has checked for reduced motion and a metered connection, so anyone
   in either of those groups never loads it at all and the still is the entire
   hero for them. Frame 0 is a dark, near-empty circuit plain: correct as the
   first beat of a flight, wrong as the only picture a visitor gets, and a poor
   LCP into the bargain.

   So this one takes a soft cross-dissolve from the tree to the plain rather than
   the hard cut the showcases were getting. It is survivable because the two
   pictures share the box exactly — `contain` on both, one filter on both, no
   transform on either — so nothing moves, resizes or re-crops across the swap.
   Geometry is what read as a glitch; content alone reads as a transition.

   If the fallback ever stops mattering — the film preloads unconditionally, or
   the flight is re-cut to open on something with a subject in it — then this
   should go back to frame 0 and match the rest of the site. */
.page-home .hero-art img,
.page-home .hero-art video {
  width: 100%;
  height: 100%;
  object-fit: contain;
  filter: saturate(1.22) contrast(1.05) brightness(1.36);
}

/* Two washes: one across to kill the left half of the art so type can sit on
   it, one up from the floor so the hero hands off to the next section. */
.page-home .hero-overlay {
  position: absolute;
  z-index: -3;
  inset: 0;
  background:
    linear-gradient(90deg, #0B1427 0%, rgba(11, 20, 39, .95) 23%, rgba(11, 20, 39, .7) 40%, rgba(11, 20, 39, .26) 58%, rgba(11, 20, 39, .1) 76%, rgba(11, 20, 39, .32)),
    linear-gradient(0deg, rgba(11, 20, 39, .78) 0%, transparent 36%, rgba(11, 20, 39, .16) 100%);
}

.page-home .hero-ambient {
  position: absolute;
  z-index: -2;
  left: -10rem;
  top: -14rem;
  width: 43rem;
  height: 38rem;
  opacity: .58;
  filter: blur(45px);
  background: radial-gradient(circle, rgba(42, 112, 237, .29) 0%, rgba(141, 67, 235, .16) 37%, transparent 68%);
}

.page-home .hero-grid {
  position: absolute;
  z-index: -1;
  inset: 0;
  opacity: .16;
  background-image:
    linear-gradient(rgba(85, 173, 252, .16) 1px, transparent 1px),
    linear-gradient(90deg, rgba(85, 173, 252, .16) 1px, transparent 1px);
  background-size: 76px 76px;
  -webkit-mask-image: linear-gradient(90deg, black, transparent 58%);
  mask-image: linear-gradient(90deg, black, transparent 58%);
}

.page-home .hero-content {
  width: min(100% - 48px, var(--wide));
  margin: auto;
  padding: 9rem 0 5rem;
}

.page-home .hero-reel h1 {
  max-width: 760px;
  margin: 1.7rem 0 1.45rem;
  font-size: clamp(3.4rem, 6.35vw, 6.35rem);
  line-height: .96;
}

.page-home .hero-copy {
  width: min(100%, 430px);
  margin: 0 0 2rem;
  color: #E2EAF8;
  font-size: .87rem;
  line-height: 1.72;
}

.page-home .hero-actions {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 1.65rem;
}

/* ==========================================================================
   THE LANDING
   The three showcase pages hold their copy back until the flight ends, and this
   page now does the same. Through the film you get the eyebrow, the caption and
   the progress bar and nothing else; the headline arrives in the room the
   contraction just made.

   Nothing below touches layout. Every block keeps its full footprint for the
   whole flight, so the arrival is four things fading up into positions that
   were already theirs rather than a reflow. The void in the middle of the frame
   during the film is precisely the headline's own space, held open for it.

   A cascade rather than one fade. Four blocks stacked from the eyebrow down to
   the strip on the floor, crossfaded together, reads as a slide changing; taken
   in order it reads as the page assembling itself. Each step is a sixth of the
   opening window against a travel of nearly half, so they overlap heavily —
   a cascade, not a queue.
   ========================================================================== */

/* Guarded on html.js, which an inline script in the head sets before anything
   paints. If that script never runs then neither does the hold-back, and the
   hero is simply a finished page. The one screen that has to earn a phone call
   does not get to depend on JavaScript to show its own headline. */
.js .page-home .hero-reel h1,
.js .page-home .hero-copy,
.js .page-home .hero-actions {
  opacity: var(--land);
  transform: translateY(calc((1 - var(--land)) * 24px));
}

.js .page-home .hero-reel h1 { --land: clamp(0, calc(var(--open) * 2.2), 1); }
.js .page-home .hero-copy    { --land: clamp(0, calc((var(--open) - .16) * 2.2), 1); }
.js .page-home .hero-actions { --land: clamp(0, calc((var(--open) - .30) * 2.2), 1); }

/* The strip fades where it stands, and so does the caption. The strip sits on
   the floor of a frame that clips, so one travelling up into place would spend
   the first half of its arrival cut off by the very edge it is arriving at.

   The caption reads "01 — Two ways to build it", which is a label for the
   section under the hero rather than anything to do with the film, so it waits
   with everything else it belongs to. That leaves exactly two things on screen
   during the flight: who this is, and what to do. */
.js .page-home .facts,
.js .page-home .hero-caption { opacity: clamp(0, calc((var(--open) - .44) * 2.2), 1); }

/* The cue. Same job, same manners and the same timing as the cues on the three
   showcase pages, in this page's own colour: centred on the bottom edge of the
   frame, a rule either side, gone by the time the camera has travelled a fifth
   of the way — by which point scrolling has explained itself.

   Off --p rather than --open, deliberately. Every other thing in this hero is
   timed off the landing; the cue is timed off the departure, because an
   instruction should leave as soon as it has been followed rather than hang
   about until the film ends.

   It sits in the footprint the facts strip lands in. The two never overlap:
   this is gone by --p .2 and the strip does not begin arriving until --p is
   past .9. */
.page-home .hero-cue {
  position: absolute;
  z-index: 2;
  left: 0;
  right: 0;
  bottom: clamp(1.6rem, 5vh, 3.2rem);
  margin: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  gap: .7rem;
  color: rgba(238, 229, 246, .72);
  font-family: var(--font-mono);
  font-size: .58rem;
  letter-spacing: .18em;
  text-transform: uppercase;
  text-shadow: 0 1px 14px rgba(6, 11, 24, .9);
  opacity: clamp(0, calc(1 - var(--p, 0) * 5), 1);
  pointer-events: none;
}
.page-home .hero-cue::before,
.page-home .hero-cue::after {
  content: "";
  width: clamp(1.6rem, 5vw, 3.4rem);
  height: 1px;
  background: currentColor;
  opacity: .45;
}

.page-home .hero-caption {
  position: absolute;
  left: max(24px, calc((100vw - var(--wide)) / 2));
  bottom: 7.9rem;
  display: flex;
  align-items: center;
  gap: .7rem;
  color: rgba(238, 229, 246, .58);
  font-family: var(--font-mono);
  font-size: .56rem;
  letter-spacing: .05em;
  text-transform: uppercase;
}

/* The strip along the floor of the hero. Three facts rather than borrowed
   client logos — we would rather say something true than imply a roster. */
.page-home .facts {
  width: min(100% - 48px, var(--wide));
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 2rem;
  min-height: 6.6rem;
  margin: auto auto 0;
  border-top: 1px solid rgba(255, 255, 255, .13);
}
.page-home .facts > p {
  margin: 0;
  color: rgba(233, 223, 244, .54);
  font-family: var(--font-mono);
  font-size: .56rem;
  letter-spacing: .06em;
  text-transform: uppercase;
}
.page-home .facts-list {
  display: flex;
  flex: 1;
  align-items: center;
  justify-content: space-between;
  max-width: 660px;
  margin: 0;
}
.page-home .facts-list div {
  display: flex;
  align-items: baseline;
  gap: .6rem;
}
.page-home .facts-list b {
  font-family: var(--font-display);
  font-size: .82rem;
  font-weight: 500;
  letter-spacing: -.03em;
  color: rgba(240, 244, 255, .9);
}
.page-home .facts-list span {
  font-family: var(--font-mono);
  font-size: .56rem;
  letter-spacing: .05em;
  text-transform: uppercase;
  /* .5 composited to 4.32:1 on the ink — under AA for text this small. The
     extra alpha buys the contrast without touching the reference type scale. */
  color: rgba(232, 222, 242, .58);
}

/* ==========================================================================
   STATEMENT
   ========================================================================== */

.page-home .statement { padding: clamp(6rem, 11vw, 11rem) 0; }

.page-home .statement-grid {
  display: grid;
  grid-template-columns: 1.5fr .62fr;
  gap: 8rem;
  align-items: end;
  margin-top: 2rem;
}
.page-home .statement h2 {
  max-width: 790px;
  margin: 0;
  font-size: clamp(2.65rem, 5.3vw, 5.2rem);
  line-height: 1.02;
}
.page-home .statement-side { padding-bottom: .5rem; }
.page-home .statement-side p {
  margin: 0;
  color: var(--muted);
  font-size: .88rem;
  line-height: 1.75;
}
.page-home .statement-side .text-link { margin-top: 1.7rem; }

/* ==========================================================================
   SERVICES — three columns divided by hairlines
   ========================================================================== */

.page-home .services {
  padding: 6.5rem 0;
  border-top: 1px solid var(--line);
}

.page-home .section-head {
  display: flex;
  align-items: end;
  justify-content: space-between;
  gap: 2rem;
  margin: 0;
  text-align: left;
}
.page-home .section-head h2 {
  margin: .85rem 0 0;
  font-size: clamp(2.55rem, 4.8vw, 4.5rem);
  line-height: .98;
}
.page-home .section-head > p {
  width: min(100%, 350px);
  margin: 0;
  color: var(--muted);
  font-size: .88rem;
  line-height: 1.75;
}

.page-home .service-list {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  margin-top: 4rem;
  border-top: 1px solid var(--line);
}
/* The description row takes the slack, which drops every spec block onto the
   bottom of the column. The two builds carry identical rows, so that lands
   them on the same lines and the pair can genuinely be read across — left to
   its own flow, a one-line difference in the copy knocks them out of step. */
.page-home .service-list article {
  min-height: 280px;
  padding: 1.55rem 2rem 1rem 0;
  border-right: 1px solid var(--line);
  display: grid;
  grid-template-rows: auto auto 1fr auto;
}
.page-home .service-list article + article { padding-left: 2rem; }
.page-home .service-list article:last-child { border-right: 0; }

/* The label enumerates the builds, not the columns. There are exactly two
   websites to choose between, so they carry the count; the third column is a
   different kind of thing and says so, in the spec colour rather than the
   build colour. Numbering all three 01–03 would read as one sequence of
   equals, which is the opposite of what this section is saying. */
.page-home .service-number {
  color: #9F70F0;
  font-family: var(--font-mono);
  font-size: .66rem;
  letter-spacing: .05em;
  text-transform: uppercase;
}
.page-home .service-number--after { color: #77D5F5; }
.page-home .service-list :is(h2, h3) {
  margin: 5.3rem 0 1rem;
  font-size: 1.35rem;
}
.page-home .service-list p {
  max-width: 270px;
  margin: 0;
  color: var(--muted);
  font-size: .8rem;
  line-height: 1.7;
}

/* The two website builds carry the same three rows in the same order, so the
   pair can be read across rather than down — that comparison is the only
   reason both columns exist. The third column has no spec because there is
   nothing to compare it against. */
.page-home .service-spec {
  margin: 1.5rem 0 0;
  padding: 0;
  list-style: none;
  display: grid;
  gap: .1rem;
  border-top: 1px solid var(--line);
}
/* Every row reserves two lines of value whether it needs them or not. Without
   it a single wrapping value in one column shunts that column's rules out of
   step with the other, and the across-reading breaks on a line of copy. */
.page-home .service-spec li {
  display: grid;
  gap: .2rem;
  align-content: start;
  min-height: 5.5rem; /* clears a two-line value; the longest is Imagery on 01 */
  padding: .8rem 0;
  border-bottom: 1px solid var(--line);
}
.page-home .service-spec b {
  font-family: var(--font-mono);
  font-size: .56rem;
  font-weight: 400;
  letter-spacing: .05em;
  text-transform: uppercase;
  color: #77D5F5;
}
.page-home .service-spec span {
  max-width: 270px;
  color: var(--muted);
  font-size: .78rem;
  line-height: 1.6;
}

/* Sits on the same top hairline and row rhythm as .service-spec in the two
   build columns, so the three columns still line up across even though this
   one has a single row rather than three. */
.page-home .service-note {
  display: grid;
  gap: .2rem;
  margin: 1.5rem 0 0;
  padding: .8rem 0;
  border-top: 1px solid var(--line);
  border-bottom: 1px solid var(--line);
  font-family: var(--font-mono);
  font-size: .56rem;
  letter-spacing: .05em;
  text-transform: uppercase;
}
.page-home .service-note b {
  font-weight: 400;
  color: #77D5F5;
}
.page-home .service-note span {
  max-width: 270px;
  color: rgba(232, 222, 242, .5);
  line-height: 1.6;
}

/* --- care plan ---------------------------------------------------------- */

/* Runs the full width under the three columns because it applies to whichever
   one of them you bought. Deliberately on the same three-column grid and the
   same paddings as .service-list above, so the two verticals carry straight
   down through it and the band reads as the bottom rail of that table rather
   than as a separate block that happens to sit near it. Nothing here should be
   a new measurement — the alignment is the whole argument. */
.page-home .service-care {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  border-top: 1px solid var(--line);
}
.page-home .service-care > * {
  padding: 1.55rem 2rem 1rem 0;
  border-right: 1px solid var(--line);
}
.page-home .service-care > * + * { padding-left: 2rem; }
.page-home .service-care > *:last-child { border-right: 0; }

/* Far tighter than the columns above, where the heading is pushed down to sit
   on a common line. This is a footer band, not a fourth column, and spacing it
   like one would be claiming a rank it does not have. */
.page-home .service-care__lede h2 {
  margin: .85rem 0 .9rem;
  font-size: 1.35rem;
}
.page-home .service-care__lede p {
  max-width: 270px;
  margin: 0;
  color: var(--muted);
  font-size: .8rem;
  line-height: 1.7;
}

/* No row hairlines. The columns above earn theirs by having four rows to keep
   in step across three columns; two terms and one term do not, and a rule
   under each would leave three loose ends at three different heights. */
.page-home .service-care__spec {
  display: grid;
  gap: 1.4rem;
  align-content: start;
  margin: 0;
  list-style: none;
}
.page-home .service-care__spec li { display: grid; gap: .3rem; }
.page-home .service-care__spec b {
  font-family: var(--font-mono);
  font-size: .56rem;
  font-weight: 400;
  letter-spacing: .05em;
  text-transform: uppercase;
  color: #77D5F5;
}
.page-home .service-care__spec span {
  max-width: 270px;
  color: var(--muted);
  font-size: .78rem;
  line-height: 1.6;
}

/* ==========================================================================
   WORK — the three showcase demos
   ========================================================================== */

.page-home .work {
  position: relative;
  padding: 6.5rem 0 8rem;
}

/* The section head now opens directly under the hero, which is exactly where
   the root traces are at their brightest — and a mono link and a line of 13px
   copy laid over a lit circuit are a fight the copy loses. A wash across the
   top of the section only, running full width so it reads as the hero settling
   into the page rather than as a patch behind the type. The traces still show
   through it; they just stop competing. */
.page-home .work::before {
  content: "";
  position: absolute;
  z-index: 0;
  top: -3rem;
  left: 50%;
  width: 100vw;
  height: 30rem;
  transform: translateX(-50%);
  background: linear-gradient(180deg,
    rgba(11, 20, 39, .86) 0%,
    rgba(11, 20, 39, .8) 38%,
    rgba(11, 20, 39, .5) 68%,
    rgba(11, 20, 39, 0) 100%);
  pointer-events: none;
}
.page-home .work > * { position: relative; z-index: 1; }

.page-home .work-grid {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 1.2rem;
  margin-top: 3.6rem;
}

.page-home .project { display: block; }
.page-home .project-image {
  position: relative;
  display: grid;
  /* Taller than it was. This row is the first thing under the hero now, and
     three squares would read as a contact sheet; three tall frames read as
     three pieces of work. */
  min-height: 448px;
  overflow: hidden;
  place-items: center;
  background: #19101F;
  transition: transform .4s cubic-bezier(.2, .75, .2, 1);
}
.page-home .project:hover .project-image,
.page-home .project:focus-visible .project-image { transform: translateY(-5px); }

/* Each tile is built from its own demo's palette, so the row previews three
   separate client brands rather than three tiles of Nettorra navy. */
.page-home .project--lux .project-image { background: linear-gradient(145deg, #4A3C25, #14110C 52%, #8A6E44); }
.page-home .project--reno .project-image { background: linear-gradient(145deg, #5B3A22, #13100C 52%, #8C6A4A); }
.page-home .project--prod .project-image { background: linear-gradient(145deg, #16330E, #060D05 52%, #7A1440); }

/* The slot rides on top of the geometry and is transparent until art lands in
   it, so the CSS artwork below is what you see in the meantime. */
.page-home .project-image .ph {
  position: absolute;
  z-index: 2;
  inset: 0;
  aspect-ratio: auto;
  border: 0;
  border-radius: 0;
  background: none;
}
.page-home .project-image .ph::before,
.page-home .project-image .ph::after { content: none; }

/* Two geometric marks per tile, in the reference's language: one rotated
   outline, one blurred mass behind it. */
.page-home .project-image::before,
.page-home .project-image::after {
  position: absolute;
  content: "";
  border-radius: 50%;
  filter: blur(1px);
  pointer-events: none;
}
.page-home .project--lux::before {
  width: 58%; height: 85%;
  border: 1px solid rgba(255, 240, 214, .42);
  background: linear-gradient(145deg, rgba(224, 199, 162, .4), rgba(151, 122, 88, .18));
  transform: rotate(35deg);
}
.page-home .project--lux::after {
  width: 30%; height: 32%;
  background: #E0C7A2;
  opacity: .3;
  box-shadow: 0 0 80px 20px rgba(151, 122, 88, .8);
}
.page-home .project--reno::before {
  top: 9%; left: 18%;
  width: 70%; height: 70%;
  border: 30px solid rgba(19, 16, 12, .9);
  border-radius: 53% 47% 45% 55%;
  transform: rotate(-22deg);
}
.page-home .project--reno::after {
  bottom: -17%; right: -6%;
  width: 78%; height: 70%;
  background: rgba(214, 168, 122, .5);
}
.page-home .project--prod::before {
  width: 54%; height: 80%;
  border: 1px solid rgba(159, 232, 112, .5);
  background: linear-gradient(145deg, rgba(159, 232, 112, .34), rgba(225, 29, 107, .22));
  transform: rotate(35deg);
}
.page-home .project--prod::after {
  width: 32%; height: 34%;
  background: #E11D6B;
  opacity: .42;
  box-shadow: 0 0 80px 20px rgba(159, 232, 112, .55);
}

.page-home .project-image::before { z-index: 0; }
.page-home .project-image::after { z-index: 0; }

.page-home .project-mark {
  position: relative;
  z-index: 1;
  font-family: var(--font-display);
  font-size: clamp(4rem, 9vw, 7rem);
  font-weight: 400;
  letter-spacing: -.12em;
  color: rgba(255, 255, 255, .92);
}
.page-home .project-mark--word {
  font-size: clamp(2rem, 4.4vw, 3.4rem);
  letter-spacing: -.06em;
}

.page-home .project-meta {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: .5rem;
  padding-top: .85rem;
}
.page-home .project-meta span {
  color: var(--muted);
  font-family: var(--font-mono);
  font-size: .56rem;
  letter-spacing: .04em;
  text-transform: uppercase;
}
.page-home .project-meta h3 {
  margin: 0;
  font-size: 1rem;
  letter-spacing: -.04em;
}

/* An image dropped into a tile takes it over completely — the geometry was
   only ever standing in for photography. */
.page-home .project-image:has(.ph > img)::before,
.page-home .project-image:has(.ph > img)::after { content: none; }
.page-home .project-image:has(.ph > img) .project-mark { display: none; }
.page-home .project-image .ph > img { width: 100%; height: 100%; object-fit: cover; }

/* The photograph drifts inside its tile as the tile crosses the screen, so the
   row has depth rather than being three flat pictures sliding past at the same
   rate as everything else. The overscan is what makes it possible: the image is
   118% tall and hung 9% high, which leaves exactly the 4.5% of travel the
   transform asks for — any more drift than the overscan and the tile shows its
   own background at one edge.

   This has to sit after the rule above and not with the rest of the tile block,
   which is where it started. Both selectors are word-for-word identical, so
   specificity cannot separate them and the later one simply wins: the height
   went back to 100% while the -9% top stayed, and every tile carried a 36px
   band of bare tile along its bottom edge.

   --p is written by the scroll engine on .project-image. Read here rather than
   driven by data-px on the image itself, because apply-shots.py owns these
   <img> tags and rewrites them; keeping the motion in CSS means the effect
   survives the next batch of art landing on the page. */
.page-home .project-image .ph > img {
  /* base.css pins this to inset: 0 with an explicit height, so the height wins
     and the bottom edge is what gives — which would hang all 18% of the
     overscan below the tile and none above it. top pulls it back to centred. */
  top: -9%;
  height: 118%;
  transform: translateY(calc((var(--p, .5) - .5) * -9%));
  /* The hover push rides the standalone `scale` property, not `transform`. Put
     it in the transform above and the transition would have to cover the whole
     property, which would put half a second of lag on the parallax and undo the
     one thing this rule exists for. Overflow on the tile clips the growth, so
     no bare edge appears. */
  scale: var(--tile-zoom, 1);
  transition: scale .55s cubic-bezier(.2, .75, .2, 1);
}
.page-home .project:hover .project-image .ph > img,
.page-home .project:focus-visible .project-image .ph > img { --tile-zoom: 1.045; }

@media (prefers-reduced-motion: reduce) {
  .page-home .project-image .ph > img {
    top: 0;
    height: 100%;
    transform: none;
    scale: 1;
    transition: none;
  }
  .page-home .project:hover .project-image .ph > img { --tile-zoom: 1; }
}

/* --- Rail heads -------------------------------------------------------- */

/* The work section carries two rails now and they are not the same kind of
   thing, so each one says what it is before it starts. A hairline with a name
   on the left and a single line of plain English on the right — the same
   structure the service list already uses, borrowed rather than invented,
   because a second heading style this close to the section head would read as
   a second section.

   Order is the argument. Cinematic first because it is what someone is here to
   see; Signature second because it is what convinces them. */
.page-home .rail-head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 1.6rem;
  margin-top: 3.2rem;
  padding-top: .95rem;
  border-top: 1px solid var(--line);
}
.page-home .rail-head :is(h2, h3) {
  display: flex;
  align-items: center;
  gap: .55rem;
  margin: 0;
  font-size: 1.08rem;
  letter-spacing: -.05em;
  white-space: nowrap;
}
.page-home .rail-head p {
  width: min(100%, 440px);
  margin: 0;
  color: var(--muted);
  font-size: .8rem;
  line-height: 1.7;
  text-align: right;
}

/* The rail head already spaces the rail; the grid's own top margin was sized
   for a section head sitting straight on top of it. */
.page-home .rail-head + .work-grid { margin-top: 1.8rem; }

/* This heading carried a lit cyan pad, argued for on the grounds that on this
   rail the mark meant one thing — power reaches here. Neither site is
   launched, so it did not, and a glowing indicator over two builds that are
   not up is the most specific possible way to be wrong about them.

   Deleting it outright was the first move and it was too much: the ads rail
   below keeps its own mark, so the only head without one reads as unfinished
   rather than as accurate. The pad stays and is switched off instead — same
   square at the same size, drawn in the line colour with nothing behind it and
   no glow. A fitting with no power in it, which is the literal status of both
   sites and the one thing this rail now has to say. */
.page-home .rail-head--live :is(h2, h3)::after {
  content: "";
  flex: none;
  width: .38rem;
  height: .38rem;
  border-radius: 1px;
  border: 1px solid var(--muted);
  opacity: .55;
}

/* ==========================================================================
   CLIENT BUILDS — real work, shown in a window rather than as a URL
   ========================================================================== */

.page-home .live-grid {
  display: grid;
  grid-template-columns: repeat(2, 1fr);
  gap: 1.6rem;
  margin-top: 1.8rem;
}

/* <figure> now, not <a>, so the browser's own figure margin has to go. */
.page-home .site { display: block; margin: 0; }

/* The window frame is doing a job, not decorating. A bordered pane with a title
   bar is the one device that reads as a website at a glance instead of as
   another photograph, which is the single thing this rail needs to say. The
   three lights are the brand trio rather than the usual red, amber and green —
   the frame belongs to this studio, not to somebody's operating system.

   It survived these cards ceasing to be links because it was never claiming
   the site was reachable, only that the picture is of one. */
.page-home .site-frame {
  position: relative;
  display: block;
  overflow: hidden;
  border: 1px solid var(--line);
  border-radius: 12px;
  background: var(--surface);
  transition: transform .4s cubic-bezier(.2, .75, .2, 1), border-color .4s var(--ease-out);
}
.page-home .site-bar {
  display: flex;
  align-items: center;
  gap: .38rem;
  height: 30px;
  padding-inline: .85rem;
  border-bottom: 1px solid var(--line);
  background: #131F3B;
}
.page-home .site-bar i {
  width: 7px;
  height: 7px;
  border-radius: 50%;
  opacity: .55;
}
.page-home .site-bar i:nth-child(1) { background: var(--cyan); }
.page-home .site-bar i:nth-child(2) { background: var(--blue); }
.page-home .site-bar i:nth-child(3) { background: var(--violet); }

/* Shot at 1200x750 flat, so the ratio here is the picture's own — nothing is
   cropped away and the top of each homepage is what shows. */
.page-home .site-frame img {
  width: 100%;
  aspect-ratio: 8 / 5;
  object-fit: cover;
  object-position: 50% 0;
  transition: transform .55s cubic-bezier(.2, .75, .2, 1);
}

/* The lift and the scale-on-hover are gone with the anchor. A card that rises
   under the cursor is a card promising to do something when clicked, and these
   two no longer do — the tell would arrive before the reader found out, which
   is the worst order for it. The transition declarations stay on .site-frame
   and its img: nothing triggers them now, and leaving them costs nothing
   against the day one of these launches and the anchor comes back. */

.page-home .site-meta {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  justify-content: space-between;
  gap: .25rem .9rem;
  padding-top: .95rem;
}
.page-home .site-trade {
  flex: 1 0 100%;
  color: var(--muted);
  font-family: var(--font-mono);
  font-size: .56rem;
  letter-spacing: .04em;
  text-transform: uppercase;
}
.page-home .site-meta b {
  font-family: var(--font-display);
  font-size: 1.08rem;
  font-weight: 400;
  letter-spacing: -.045em;
}
/* Was .site-live, cyan, reading "Open the live site" with an arrow. It is a
   status now rather than an instruction, so it is set in the muted colour the
   trade line above it uses: cyan on this page is the colour of things that
   respond to a click, and spending it on a line that says the opposite is how
   a reader ends up clicking a caption. Renamed with it — a class called
   --live over the words "not yet launched" is the next person's wrong turn. */
.page-home .site-status {
  display: inline-flex;
  align-items: center;
  gap: .4rem;
  color: var(--muted);
  font-family: var(--font-mono);
  font-size: .58rem;
  letter-spacing: .04em;
  text-transform: uppercase;
}

@media (prefers-reduced-motion: reduce) {
  .page-home .site-frame,
  .page-home .site-frame img { transition: none; }
}

/* ==========================================================================
   CONTENT & ADS — film, shown in the shape it was cut for

   The other two rails are websites and are framed as websites: a 4:3 tile you
   click, a browser window with a title bar. There is nothing to open here, so
   borrowing either device would be a lie about what the work is. The frame
   instead takes the placement's own aspect — --ad-aspect is set from the file
   so a vertical or wide cut needs no new CSS — and says which placement it is.
   ========================================================================== */

.page-home .rail-head--ads :is(h2, h3)::after {
  content: "";
  flex: none;
  width: .38rem;
  height: .38rem;
  border-radius: 50%;
  background: var(--purple);
  box-shadow: 0 0 12px rgba(123, 59, 233, .95);
}

/* Stacked, not columned. A 16:9 film dropped into half a column comes out about
   375px tall, which is a thumbnail of a cinema frame — and this rail exists to
   argue that the studio can shoot. Full width it runs ~660px tall and closes the
   section on the strongest thing in it. The credits then read underneath, which
   is where a film's credits go. */
.page-home .ad-grid {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  gap: clamp(1.5rem, 3vw, 2.3rem);
  margin-top: 1.8rem;
}

.page-home .ad-film { margin: 0; }

.page-home .ad-frame {
  position: relative;
  overflow: hidden;
  aspect-ratio: var(--ad-aspect, 16 / 9);
  border: 1px solid var(--glass-brd);
  border-radius: var(--radius);
  background: #000;
}
/* contain, never cover. The frame and the film are both 16:9 so today these are
   the same picture, but the ratio lives in a variable and the film gets replaced
   — and the failure mode has to stay a letterbox on a black field nobody can
   see, not a silent crop. This site has already shipped one of those. */
.page-home .ad-vid {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: contain;
}

/* Sits on the film rather than under it: the placement is a property of the
   frame, and moving it outside would read as a caption about the watch. */
.page-home .ad-place {
  position: absolute;
  right: .85rem;
  bottom: .85rem;
  display: inline-flex;
  align-items: center;
  gap: .42rem;
  padding: .34rem .6rem;
  border: 1px solid rgba(255, 255, 255, .16);
  border-radius: 999px;
  background: rgba(5, 12, 30, .62);
  backdrop-filter: blur(10px);
  color: var(--text-soft);
  font-family: var(--font-mono);
  font-size: .55rem;
  letter-spacing: .05em;
  text-transform: uppercase;
}
.page-home .ad-place i {
  width: .3rem;
  height: .3rem;
  border-radius: 50%;
  background: var(--purple);
  box-shadow: 0 0 8px rgba(123, 59, 233, .9);
}

/* Under the film the three blocks sit in a row on one rule, which is the shape
   of a credit block rather than a product panel — the title on the left, what it
   is next to it, how it was made on the right. Ragged column heights are correct
   here: start-aligned text hangs off the rule the way credits do, and stretching
   them to match would turn the row back into cards. */
.page-home .ad-meta {
  display: grid;
  grid-template-columns: minmax(0, .85fr) minmax(0, 1.15fr) minmax(0, 1fr);
  align-items: start;
  gap: clamp(1.4rem, 3.5vw, 3rem);
  padding-top: 1.35rem;
  border-top: 1px solid var(--line);
}

.page-home .ad-brand {
  margin: 0;
  color: var(--purple);
  font-family: var(--font-mono);
  font-size: .62rem;
  letter-spacing: .18em;
  text-transform: uppercase;
}
.page-home .ad-meta :is(h3, h4) {
  margin: .5rem 0 0;
  font-size: clamp(1.5rem, 2.4vw, 2rem);
  line-height: 1.05;
  letter-spacing: -.03em;
}
.page-home .ad-desc {
  margin: 0;
  color: var(--muted);
  font-size: .84rem;
  line-height: 1.75;
}

/* Same three-row spec the two website builds carry, so a reader who has just
   come down the page reads this one the same way. No top rule: the row it sits
   in already has one, and a second would double it. */
.page-home .ad-spec {
  margin: 0;
  padding: 0;
  list-style: none;
  display: grid;
  gap: .1rem;
}
.page-home .ad-spec li {
  display: grid;
  gap: .2rem;
  padding: 0 0 .7rem;
  border-bottom: 1px solid var(--line);
}
.page-home .ad-spec li:last-child { border-bottom: 0; padding-bottom: 0; }
.page-home .ad-spec li + li { padding-top: .7rem; }
.page-home .ad-spec b {
  font-family: var(--font-mono);
  font-size: .56rem;
  font-weight: 400;
  letter-spacing: .05em;
  text-transform: uppercase;
  color: #B48CF7;
}
.page-home .ad-spec span {
  color: var(--muted);
  font-size: .78rem;
  line-height: 1.6;
}

/* ==========================================================================
   PROCESS — full-bleed
   ========================================================================== */

.page-home .process {
  position: relative;
  width: 100%;
  /* 100% not 100vw — vw counts the scrollbar, which would push this band's
     inner edge 7.5px off the sections above and below it. */
  padding: clamp(6rem, 11vw, 10rem) max(24px, calc((100% - var(--read)) / 2));
  overflow: hidden;
  /* No fill. This used to be --soft, a band one shade lighter than the page,
     and it earned that when the process sat in the middle of a long homepage
     with sections above and below to be separated from. It is a page of its
     own now: there is nothing to contrast with, and the only thing the fill
     still did was stop the circuit field halfway down and leave a hard step
     across the traces where the band ended. Every other section on every other
     sub-page is unfilled. This one is too. */
  background: none;
}
.page-home .process > *:not(.process-glow) { position: relative; z-index: 1; }
.page-home .process-glow {
  position: absolute;
  right: -7rem;
  top: -9rem;
  width: 45rem;
  height: 45rem;
  filter: blur(10px);
  background: radial-gradient(circle, rgba(42, 112, 237, .29) 0%, rgba(141, 67, 235, .15) 38%, transparent 70%);
}

.page-home .process-grid {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 5rem;
  margin-top: 2.2rem;
}
.page-home .process h2 {
  margin: .85rem 0 0;
  font-size: clamp(2.55rem, 4.8vw, 4.5rem);
  line-height: .98;
}
.page-home .process ol { margin: 0; padding: 0; list-style: none; }
.page-home .process li {
  display: grid;
  grid-template-columns: 3.8rem 1fr;
  gap: 1rem;
  padding: 1.45rem 0;
  border-top: 1px solid var(--line);
}
.page-home .process li:last-child { border-bottom: 1px solid var(--line); }
.page-home .process li > span {
  color: #77D5F5;
  font-family: var(--font-mono);
  font-size: .65rem;
}
.page-home .process li :is(h2, h3) { margin: 0 0 .65rem; font-size: 1.1rem; }
.page-home .process li p {
  max-width: 380px;
  margin: 0;
  color: var(--muted);
  font-size: .79rem;
  line-height: 1.68;
}

/* ==========================================================================
   CONTACT
   ========================================================================== */

.page-home .contact { position: relative; padding: clamp(7rem, 13vw, 11rem) 0; }

/* The copy rides above the plate. Everything except the plate itself, so a
   later addition to this section joins the readable layer by default rather
   than by being remembered. */
.page-home .contact > *:not(.contact-art) { position: relative; z-index: 1; }

/* The last four bands of this page — statement, services, process, contact —
   are text on navy, one after another, and by the fourth the page has stopped
   showing and started telling. This is the band where somebody decides whether
   to email, so it is the wrong place to be at the page's dimmest.

   Same plate and the same treatment as the sign-off band on the three
   showcases, which is the point: it is the mark the studio signs work with, so
   the homepage should sign off the same way its demos do.

   Not isolated, unlike .sc-outro. `screen` needs a real backdrop to blend
   against, and this section has no background of its own — it sits on the
   page's navy. Isolating it would hand the blend a transparent backdrop, which
   resolves to no blending at all and puts a black rectangle on the page. The
   one case where leaving the stacking context open is the correct choice. */
.page-home .contact-art {
  position: absolute;
  z-index: 0;
  top: 50%;
  right: -4%;
  width: min(46%, 30rem);
  aspect-ratio: 1500 / 1050;
  transform: translateY(-50%);
  opacity: .62;
  pointer-events: none;
  mix-blend-mode: screen;
  /* Fades in from the left so the headline keeps flat navy behind it. The
     buttons sit over the glow, which is the one place the plate reads as being
     behind something rather than beside it. */
  -webkit-mask-image: linear-gradient(90deg, transparent, #000 38%);
  mask-image: linear-gradient(90deg, transparent, #000 38%);
}
.page-home .contact-art img {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: contain;
}

/* The plate was centred on a band that held a kicker, a headline and two
   buttons. The band holds a form now and is more than twice as tall, so
   halfway down is no longer beside the call to action — it is across the
   middle of four fields. Above about 1180 that does not matter, because the
   form stops at 42rem and the plate starts after it. Below, they are fighting
   for the same 200px.

   So it goes up into the headline's corner instead. Its own left-hand fade
   already keeps it off the type — nothing is drawn in the first 38% of it —
   and the rail and the chips both end well before the point where it becomes
   visible. Dimmer and smaller too: this is the one place on the site where a
   mesh of hairlines would otherwise land on top of another mesh of hairlines,
   which is the same complaint the phone rule below settles a different way. */
@media (min-width: 761px) and (max-width: 1180px) {
  .page-home .contact-art {
    top: 0;
    width: min(42%, 22rem);
    opacity: .42;
    transform: none;
  }
}

/* Gone below this, rather than moved again. The plate was placed at 64% to fill
   the empty half of a band that held a kicker, a headline and two buttons. That
   empty half no longer exists: the band holds a three-step form, and at 390 it
   is 766px tall on the first step and taller on the last. Measured, 64% now puts
   the brain squarely across the chips.

   There is nowhere to move it to. The band's height changes as the visitor
   advances through the steps, so no percentage stays still relative to the
   content — a gap tuned for step one is on top of a field by step three. And
   the chips are themselves hairline-bordered rectangles, so a mesh of hairlines
   behind them is the same mesh-on-mesh complaint the rule above settles by going
   smaller and dimmer, except here it would sit behind the one thing on the page
   somebody has to tap.

   The band is not left bare by this. The circuit field runs behind the entire
   site and is plainly visible in this section, and the form is now the brightest
   thing in the band — which was the whole argument for the plate to begin with. */
@media (max-width: 760px) {
  .page-home .contact-art { display: none; }
}

.page-home .contact h2 {
  margin: .85rem 0 2.4rem;
  font-size: clamp(3rem, 6vw, 5.8rem);
  line-height: .98;
}
.page-home .contact-actions {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 1.1rem;
}
.page-home .contact-fine {
  margin: 2.2rem 0 0;
  color: var(--muted);
  font-family: var(--font-mono);
  font-size: .58rem;
  letter-spacing: .05em;
  text-transform: uppercase;
}
.page-home .contact-fine a {
  color: var(--white);
  border-bottom: 1px solid rgba(174, 198, 248, .3);
  transition: color .2s ease, border-color .2s ease;
}
.page-home .contact-fine a:hover,
.page-home .contact-fine a:focus-visible { color: var(--cyan); border-bottom-color: currentColor; }

/* ==========================================================================
   THE BRIEF

   What used to be here was a mailto and a phone number, which are both an
   instruction to go and compose something. A blank email is the most expensive
   thing you can ask a busy person for: they have to work out what you need to
   know, in what order, in their own words, on a phone, between jobs. Most of
   them mean to do it later and later never comes.

   So this asks the questions instead. Two taps and four fields, and the first
   two steps need no typing at all — which is the whole design. Everything
   below follows from that one decision.

   No new visual ideas. The chips are the site's own button at a smaller size,
   the fields are hairlines like every other structure on the page, and the
   progress rail is the pad-and-trace from .kicker::before scaled up: dark
   until power reaches it, then the pad takes and the trace draws out. Filling
   in a brief lights a circuit, which is the one thing this site has been
   saying since the tree.
   ========================================================================== */

.page-home .brief { position: relative; max-width: 42rem; }

/* The two panels swap by [hidden]. Stated rather than left to the user-agent
   sheet, because everything else in this block sets display on something. */
.page-home .brief [hidden] { display: none; }

/* Off-screen rather than display:none or hidden. Both of those are read by the
   bots this exists to catch, and a field they can see is a field they cannot
   resist. Netlify bins any submission that arrives with it filled in. */
.page-home .brief__pot {
  position: absolute;
  left: -9999px;
  width: 1px;
  height: 1px;
  overflow: hidden;
}

/* --- the rail --------------------------------------------------------- */

/* Every node visible before you start. That is the honest answer to the only
   question anybody has about a form, which is "how long is this".

   Columns are auto rather than a counted repeat(). The rail gained a fourth
   node when the look step went in, and a hard-coded three would have quietly
   stacked it onto a second row — a progress bar that wraps is worse than none.
   grid-auto-flow: column takes however many <li> it is given. */
.page-home .brief__rail {
  display: grid;
  grid-auto-flow: column;
  grid-auto-columns: minmax(0, 1fr);
  max-width: 32rem;
  margin: 0 0 2.5rem;
  padding: 0;
  list-style: none;
}
.page-home .brief__rail li {
  position: relative;
  padding: .9rem .8rem 0 0;
  border-top: 1px solid var(--line);
}

/* The lit trace sits on top of the dead one rather than replacing it, so the
   hairline never disappears mid-transition and the row cannot shift. */
.page-home .brief__rail li::after {
  content: "";
  position: absolute;
  top: -1px;
  left: 0;
  right: 0;
  height: 1px;
  background: linear-gradient(90deg, var(--cyan), rgba(42, 112, 237, .5));
  transform: scaleX(0);
  transform-origin: 0 50%;
  transition: transform .55s var(--ease-out);
}
.page-home .brief__rail i {
  position: absolute;
  top: -3px;
  left: 0;
  width: 5px;
  height: 5px;
  border-radius: 1px;
  background: rgba(174, 198, 248, .3);
  transition: background .45s var(--ease-out), box-shadow .45s var(--ease-out);
}
.page-home .brief__rail b {
  display: block;
  font-family: var(--font-mono);
  font-size: .56rem;
  font-weight: 400;
  letter-spacing: .1em;
  text-transform: uppercase;
  color: rgba(173, 184, 208, .55);
  transition: color .45s var(--ease-out);
}
.page-home .brief__rail li.is-lit::after { transform: scaleX(1); }
.page-home .brief__rail li.is-lit i { background: var(--cyan); box-shadow: 0 0 12px rgba(39, 184, 232, .8); }
.page-home .brief__rail li.is-lit b { color: var(--white); }

/* --- steps ------------------------------------------------------------- */

/* Only staged once the script has said so. Without JS every step is on the
   page at once and the whole thing is one ordinary form that still posts — a
   longer ask, but never a broken one. */
.page-home .brief__step + .brief__step { margin-top: 2.6rem; }
.page-home .brief--staged .brief__step { display: none; margin-top: 0; }

/* The parts that only mean anything once the form is staged, removed when it
   is not. Next and Back move between steps that are all already on the page,
   so unstaged they are controls that visibly do nothing — worse than absent.
   The rail is a progress indicator with no progress to report, and its nodes
   would sit unlit for the whole form, which reads as broken rather than plain.

   Keyed off .brief--staged rather than off html.js, because those are not the
   same failure. The .js class is set by an inline script in the head, so it is
   there even when site.js itself fails to arrive — and it is site.js that runs
   the steps. .brief--staged is added by initBrief and by nothing else, so it is
   the only honest answer to "is the staging actually working". */
.page-home .brief:not(.brief--staged) .brief__rail,
.page-home .brief:not(.brief--staged) [data-brief-next],
.page-home .brief:not(.brief--staged) [data-brief-back] { display: none; }

/* Every foot but the last then holds nothing, so it should not hold space
   either. :last-of-type rather than :has(), which this fallback should not
   depend on — the visitor without working JavaScript is the likeliest of all
   visitors to be on a browser that is behind. */
.page-home .brief:not(.brief--staged) .brief__step:not(:last-of-type) .brief__foot { display: none; }
.page-home .brief--staged .brief__step.is-on {
  display: block;
  animation: brief-rise .45s var(--ease-out) both;
}
@keyframes brief-rise {
  from { opacity: 0; transform: translateY(12px); }
}

.page-home .brief__q {
  margin: 0 0 1.15rem;
  font-family: var(--font-display);
  font-size: clamp(1.15rem, 2.1vw, 1.4rem);
  font-weight: 400;
  letter-spacing: -.035em;
  line-height: 1.25;
}
/* The question takes focus when a step changes, and the receipt takes it when
   the brief sends, so a screen reader reads the new thing rather than
   announcing nothing. Neither is a control and neither is in the tab order, so
   neither gets a ring: a cyan box thrown round the heading after every tap
   reads as an error, not as focus — and round the receipt it turns a thank-you
   into what looks like a failed validation. */
.page-home .brief__q:focus,
.page-home .brief__q:focus-visible,
.page-home .brief__done:focus,
.page-home .brief__done:focus-visible { outline: none; }

.page-home .brief__hint {
  margin: .9rem 0 0;
  color: var(--muted);
  font-size: .8rem;
}

/* --- chips ------------------------------------------------------------- */

.page-home .brief__chips { display: flex; flex-wrap: wrap; gap: .6rem; }

.page-home .brief__chip { position: relative; display: inline-flex; }

/* The control fills the chip and is invisible; the span is what you see. Not
   sr-only'd off-screen — a real hit area the size of the thing you are aiming
   at is what makes this work with a thumb. */
.page-home .brief__chip input {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  margin: 0;
  opacity: 0;
  cursor: pointer;
  -webkit-appearance: none;
  appearance: none;
}
.page-home .brief__chip span {
  display: inline-flex;
  align-items: center;
  min-height: 46px;
  padding: .6rem 1.05rem;
  border: 1px solid var(--line);
  border-radius: .38rem;
  color: var(--white);
  font-size: .84rem;
  font-weight: 500;
  line-height: 1.25;
  transition: color .22s ease, background .22s ease, border-color .22s ease, box-shadow .22s ease;
}
.page-home .brief__chip:hover span { border-color: rgba(39, 184, 232, .6); }
.page-home .brief__chip input:focus-visible + span { outline: 2px solid var(--cyan); outline-offset: 3px; }

/* Selected is the page's primary button, exactly — white plate, ink text, cyan
   ring. Nothing invented: an answer you have given looks like an action you
   would take. */
.page-home .brief__chip input:checked + span {
  color: var(--ink);
  background: var(--white);
  border-color: transparent;
  box-shadow: 0 0 0 1px rgba(39, 184, 232, .78), 0 8px 26px rgba(42, 112, 237, .3);
}

/* --- the look step ----------------------------------------------------- */

/* Seven answers of very different lengths. Left to wrap they came out as a
   ragged stack that read as an accident rather than a set — and these are a
   set: one question, one tap, pick the one that looks right. A grid makes the
   swatches line up in columns, which is most of what makes it scannable.
   auto-fit rather than a fixed count, so it is three across on a laptop and
   two on a phone without either being spelled out. */
.page-home .brief__chips--look {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(12.5rem, 1fr));
  gap: .6rem;
}
.page-home .brief__chips--look .brief__chip span { flex: 1; }

/* A palette shown rather than described. One element carrying three colours as
   hard gradient stops, not three nested spans: the colours are one answer, so
   they are one thing, and the markup does not need to know that. */
.page-home .brief__chip--look i {
  flex: none;
  width: 34px;
  height: 18px;
  margin-right: .6rem;
  border-radius: 3px;
  background: linear-gradient(90deg, var(--a) 0 34%, var(--b) 34% 67%, var(--c) 67%);
  /* The ring flips with the chip, and it has to. Three of these palettes open
     on a near-black and two close on a near-white, so a single fixed ring is
     invisible against one state or the other — and an invisible edge means the
     band merges into whatever is behind it and a palette of three looks like a
     palette of two. Light while the chip is dark, dark once it turns plate. */
  box-shadow: inset 0 0 0 1px rgba(242, 245, 255, .32);
}
.page-home .brief__chip input:checked + span i { box-shadow: inset 0 0 0 1px rgba(11, 20, 39, .3); }

/* --- photos ------------------------------------------------------------ */

.page-home .brief__up { margin-top: 1.7rem; }

/* Clipped rather than display:none. A file input that is not rendered cannot
   be reached by a keyboard and cannot be opened by its own label in every
   browser, and this one has to be both. */
.page-home .brief__up input[type="file"] {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  border: 0;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
}

/* Dashed, where an answer chip is solid. This sits directly under seven chips
   and it is not an eighth one — it is a place to put something. The border
   says so before the label is read. */
.page-home .brief__add {
  display: inline-flex;
  align-items: center;
  gap: .55rem;
  min-height: 46px;
  padding: .6rem 1.05rem;
  border: 1px dashed rgba(174, 198, 248, .38);
  border-radius: .38rem;
  color: var(--white);
  font-size: .84rem;
  font-weight: 500;
  cursor: pointer;
  transition: color .22s ease, background .22s ease, border-color .22s ease;
}
.page-home .brief__add svg { flex: none; width: 15px; height: 15px; color: var(--cyan); }
.page-home .brief__add em { font-style: normal; font-weight: 400; color: rgba(173, 184, 208, .6); }
.page-home .brief__add:hover { border-color: rgba(39, 184, 232, .6); background: rgba(39, 184, 232, .06); }
/* General sibling, not adjacent. The hidden shot-2 field sits between the
   picker and its label, so "+" matched nothing and a keyboard user tabbing
   onto Add photos saw no ring at all — the input itself is clipped to 1px, so
   there was nowhere else for the focus to show. "~" skips whatever is in
   between and keeps working if another field is ever added there. */
.page-home .brief__up input[type="file"]:focus-visible ~ .brief__add {
  outline: 2px solid var(--cyan);
  outline-offset: 3px;
}

/* What they picked, shown back at the size of a thumbnail. This is the whole
   reward for having done it — a filename in grey text is a receipt, a picture
   is proof the right one went. */
.page-home .brief__shots {
  display: flex;
  flex-wrap: wrap;
  gap: .6rem;
  margin: .95rem 0 0;
  padding: 0;
  list-style: none;
}
.page-home .brief__shots li {
  position: relative;
  width: 76px;
  height: 76px;
  border: 1px solid var(--line);
  border-radius: .38rem;
  overflow: hidden;
  animation: brief-rise .4s var(--ease-out) both;
}
.page-home .brief__shots img { display: block; width: 100%; height: 100%; object-fit: cover; }

/* The tile for a file that cannot be drawn. Dashed and empty of picture, so it
   never reads as a photograph that has failed to load — it is a document, and
   it says which one. The top padding is the remove button's room. */
.page-home .brief__shots li.is-file {
  display: grid;
  place-items: center;
  padding: 2rem .4rem .4rem;
  background: rgba(16, 27, 51, .72);
  border-style: dashed;
}
.page-home .brief__shots b {
  font-family: var(--font-mono);
  font-size: .56rem;
  line-height: 1.45;
  color: var(--muted);
  text-align: center;
  overflow-wrap: anywhere;
}
/* 26px, not the 22px this started at. WCAG 2.2 puts the floor for a target at
   24px square and the old one was under it — on a phone, with a thumbnail this
   small, "remove the wrong photo" was a fiddly tap. */
.page-home .brief__shots button {
  position: absolute;
  top: 4px;
  right: 4px;
  display: grid;
  place-items: center;
  width: 26px;
  height: 26px;
  border-radius: 50%;
  background: rgba(11, 20, 39, .84);
  color: var(--white);
  font-size: .82rem;
  line-height: 1;
  transition: background .2s ease, color .2s ease;
}
.page-home .brief__shots button:hover { background: var(--ink); color: var(--cyan); }

/* Said only when something has gone wrong with a picture — too big to send, or
   not a picture at all. Amber rather than the red the required fields use:
   nothing here is compulsory, so nothing here is a failure. */
.page-home .brief__up__note {
  margin: .8rem 0 0;
  color: #F2C879;
  font-size: .8rem;
  line-height: 1.55;
}

/* --- fields ------------------------------------------------------------ */

/* --- the question that only applies to some people ---------------------- */

/* Folded away only once the script is staging the form, on exactly the same
   reasoning as the steps above: unstaged there is nothing running the reveal,
   so a question hidden here would be a question nobody could ever reach. Left
   visible, it costs the fallback one optional line. */
.page-home .brief--staged .brief__extra { display: none; }
.page-home .brief--staged .brief__extra.is-on {
  display: block;
  margin-top: 1.5rem;
  animation: brief-rise .45s var(--ease-out) both;
}
.page-home .brief:not(.brief--staged) .brief__extra { margin-top: 1.5rem; }

/* Underlined rather than coloured. It leaves the site mid-form, so it should
   read as a footnote you may not need — not as the next thing to do. */
.page-home .brief__extra .brief__hint a {
  color: var(--white);
  border-bottom: 1px solid var(--line);
}
.page-home .brief__extra .brief__hint a:hover,
.page-home .brief__extra .brief__hint a:focus-visible {
  color: var(--cyan);
  border-bottom-color: currentColor;
}

.page-home .brief__fields {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 1.7rem 1.5rem;
}
.page-home .brief__field--wide { grid-column: 1 / -1; }

.page-home .brief__field label {
  display: block;
  margin: 0 0 .25rem;
  font-family: var(--font-mono);
  font-size: .56rem;
  letter-spacing: .1em;
  text-transform: uppercase;
  color: var(--muted);
}
.page-home .brief__field label em { font-style: normal; color: rgba(173, 184, 208, .55); }

.page-home .brief__field input,
.page-home .brief__field textarea {
  display: block;
  width: 100%;
  padding: .5rem 0 .6rem;
  background: none;
  border: 0;
  /* Stronger than --line, which is the hairline used for structure everywhere
     else on the site. An empty field has to announce itself as somewhere you
     can type, and it is doing that against the circuit texture — a rule at the
     same weight as the traces behind it reads as part of them. */
  border-bottom: 1px solid rgba(174, 198, 248, .34);
  border-radius: 0;
  color: var(--white);
  font-family: var(--font-body);
  font-size: 1rem;
  line-height: 1.5;
  -webkit-appearance: none;
  appearance: none;
  transition: border-color .25s var(--ease-out), box-shadow .25s var(--ease-out);
}
.page-home .brief__field textarea { min-height: 3.4rem; resize: vertical; }
.page-home .brief__field input::placeholder,
.page-home .brief__field textarea::placeholder { color: rgba(173, 184, 208, .42); }

/* The underline takes power on focus, like everything else here. The box-shadow
   thickens it without adding a pixel of height, so nothing below moves. The
   outline is deliberately left alone: a keyboard ring is the floor. */
.page-home .brief__field input:focus,
.page-home .brief__field textarea:focus {
  border-bottom-color: var(--cyan);
  box-shadow: 0 1px 0 var(--cyan);
}
.page-home .brief__field :focus-visible { outline-offset: 6px; border-radius: 3px; }

/* Red only after somebody has tried to send. Marking a field wrong before it
   has been filled in is telling somebody off for not having finished. */
.page-home .brief--check .brief__field input:invalid,
.page-home .brief--check .brief__field textarea:invalid {
  border-bottom-color: #FF9A9A;
  box-shadow: 0 1px 0 #FF9A9A;
}

/* --- controls ---------------------------------------------------------- */

.page-home .brief__foot {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: .4rem 1.4rem;
  margin-top: 2rem;
}
.page-home .brief--staged .brief__step:first-of-type .brief__foot { margin-top: 1.4rem; }

/* Back, and the quiet Next on the first step. That step's answer is a tap on a
   chip, so a filled button beside five outlined ones would be the heaviest
   thing on screen and it would be pointing away from the question. It is here
   for keyboard use — arrow keys select as they move, so the tap-to-continue
   shortcut cannot be theirs — and for anyone who would rather not say. On step
   two, where nothing is compulsory and Next is genuinely the way on, it goes
   back to being a real button. */
.page-home .brief__back,
.page-home .brief__go {
  display: inline-flex;
  align-items: center;
  gap: .5rem;
  padding: .5rem 0;
  color: var(--muted);
  font-size: .78rem;
  font-weight: 600;
  transition: color .2s ease, gap .2s ease;
}
.page-home .brief__back:hover,
.page-home .brief__go:hover { color: var(--white); gap: .75rem; }

.page-home .brief__err {
  margin: 1.1rem 0 0;
  max-width: 34rem;
  color: #FFB6B6;
  font-size: .86rem;
  line-height: 1.6;
}
.page-home .brief__err a { color: #fff; border-bottom: 1px solid rgba(255, 255, 255, .4); }

/* Sending. The label swaps to "Sending…" in the script; this only stops a
   second tap landing while the first is still in the air. */
.page-home .brief[data-sending] .brief__body { opacity: .55; pointer-events: none; }

/* --- the receipt ------------------------------------------------------- */

.page-home .brief__done { margin: 0; max-width: 34rem; animation: brief-rise .5s var(--ease-out) both; }
.page-home .brief__done b {
  display: block;
  margin-bottom: .55rem;
  font-family: var(--font-display);
  font-size: clamp(1.35rem, 2.6vw, 1.75rem);
  font-weight: 400;
  letter-spacing: -.04em;
}
.page-home .brief__done span { display: block; color: var(--muted); font-size: .95rem; line-height: 1.7; }
.page-home .brief__done i {
  display: inline-block;
  width: 6px;
  height: 6px;
  margin-bottom: .45rem;
  border-radius: 1px;
  background: var(--cyan);
  box-shadow: 0 0 14px rgba(39, 184, 232, .9);
}

@media (prefers-reduced-motion: reduce) {
  .page-home .brief--staged .brief__step.is-on,
  .page-home .brief--staged .brief__extra.is-on,
  .page-home .brief__shots li,
  .page-home .brief__done { animation: none; }
  .page-home .brief__rail li::after { transition: none; }
}

@media (max-width: 760px) {
  .page-home .brief__fields { grid-template-columns: minmax(0, 1fr); gap: 1.4rem; }
  .page-home .brief__field--wide { grid-column: auto; }
  .page-home .brief__rail { max-width: none; }
  .page-home .brief__rail b { font-size: .52rem; letter-spacing: .06em; }
  .page-home .brief__chip span { padding: .58rem .9rem; font-size: .8rem; }

  /* Two across, stated rather than left to auto-fit. The tracks a phone can
     actually fit are narrower than the 12.5rem minimum the grid asks for, so
     auto-fit would drop to a single column and turn seven answers into seven
     rows — the longest step in the form, for the easiest question in it. */
  .page-home .brief__chips--look { grid-template-columns: repeat(2, minmax(0, 1fr)); }

  /* The swatch and its gap come off the chip's text budget, so they come down
     with everything else at this width. */
  .page-home .brief__chip--look i { width: 28px; height: 16px; margin-right: .5rem; }
  .page-home .brief__add { padding: .58rem .9rem; font-size: .8rem; }

  /* 16px or the field is zoomed into on tap by iOS, which reflows the whole
     page mid-form and is the single most disorienting thing a phone form can
     do. This is the reason for the size, not the reading. */
  .page-home .brief__field input,
  .page-home .brief__field textarea { font-size: 16px; }
}

/* ==========================================================================
   RESPONSIVE
   ========================================================================== */

@media (max-width: 1040px) {
  .page-home .statement-grid { gap: 4rem; }
  .page-home .process-grid { gap: 3rem; }
  .page-home .service-list article { padding-right: 1.25rem; }
  .page-home .service-list article + article { padding-left: 1.25rem; }
}

@media (max-width: 860px) {
  .page-home .work-grid { grid-template-columns: 1fr 1fr; }
  .page-home .work-grid .project:first-child { grid-column: 1 / -1; }
  /* Two windows side by side at this width leaves each one about 380px wide,
     which is a homepage shrunk to the size of a stamp. One at a time from here
     down — there are only two, so the column costs nothing. */
  .page-home .live-grid { grid-template-columns: 1fr; gap: 2.2rem; }
  .page-home .rail-head p { font-size: .78rem; }

  /* The film is already full width; it is the credit row underneath that has to
     give. Three columns across ~700px puts the description on about 28 characters
     and the spec values on fewer, which stacks into a shape no wider than it is
     tall. Title and description pair up on the first line, spec takes the second
     — the spec is the only part that can carry a full-width row without looking
     stranded, because its own rules give it structure. */
  .page-home .ad-meta { grid-template-columns: minmax(0, 1fr) minmax(0, 1.2fr); }
  .page-home .ad-spec { grid-column: 1 / -1; margin-top: .3rem; }
}

@media (max-width: 760px) {
  .page-home .statement,
  .page-home .services,
  .page-home .work,
  .page-home .contact { width: calc(100% - 32px); }

  /* One column from here. A 16:9 film on a phone is only ~190px tall, so the
     credits are most of what is on screen and they need the full measure to read
     at all. */
  .page-home .ad-meta { grid-template-columns: minmax(0, 1fr); gap: 1.1rem; }
  .page-home .ad-spec { grid-column: auto; margin-top: 0; }

    /* Shorter still on a phone. The film is the same length either way, so
     less travel just means the same flight at a brisker pace — which suits a
     thumb, and gets the page moving sooner on the screen least patient with it. */
  /* The band's top edge and the type's top padding used to be two independently
     hand-tuned numbers that were describing the same line. The art was placed at
     `top: 9%` of the frame; the copy was pushed down by `min(25rem, 34svh -
     insets)`. Measured at 390x844 that is a band ending at 319px against a first
     line of type starting at 258 — sixty-one pixels of headline standing on the
     film, and the frame already full to its floor, so there was nowhere to push
     the type down to. Two formulas, one edge, and no way to notice they disagreed
     short of measuring both.
     They are one number now. The art reads these to place itself and the copy
     reads them to clear itself, so the gap between the picture and the type is
     stated once and cannot drift. */
  .page-home .hero-reel {
    height: 200svh;
    --band-top: 4.2svh;
    /* 100vw at 16:9. The art is flush to the viewport, so its height is not a
       choice — it falls out of the film's own shape. */
    --band-h: 56.25vw;
    --band-gap: clamp(.7rem, 2.2svh, 1.15rem);
  }
  .page-home .hero-reel .reel__pin { min-height: 640px; }
  /* Height comes off the 16:9 ratio here too — a fixed 62% of the pin turned the
     box back into a crop on the one screen with the least room to spare. Pinned
     to the top rather than centred, because the type below it needs the floor.

     The width used to be 160vw pushed 53vw off the right, which sounds like a
     bleed and worked like a crop: the visible window was the left two thirds of
     the frame, and the left two thirds of this frame is canopy. The trunk sits
     centre-right and the root fan runs off the right edge, so the phone was
     showing the one part of the flight with no tree in it.

     It then went to 120vw bled evenly, which kept a 263px band and gave up 8%
     at each edge instead of 37% at one — defensible, and still a crop.

     Flush 100vw now, because the whole point of the pass that removed the
     `cover` fallback from every other film on the site was that the visitor
     should see the entire frame, and a promise with an exception on the
     homepage is not a promise. 219px is a thin band and it is the honest one.
     The tree is centred and symmetrical, so the 8% this gives back was never
     the difference between a flight and a ribbon — the ribbon was always the
     shape of a 16:9 frame at phone width, and the 8% just moved where the edges
     fell. */
  .page-home .hero-art {
    top: var(--band-top);
    bottom: auto;
    right: 0;
    width: 100vw;
    height: auto;
    max-height: none;
    /* Was .75, to hold the headline off the picture. Nothing stands on the
       picture any more — the type clears it — so the dimming bought nothing and
       cost a quarter of the only moving thing on the screen. */
    opacity: 1;
  }

  /* Pinned to the top so the type below it has the floor, which is right the
     moment there is type and wrong for the whole flight before it. With the copy
     now waiting for the film, that floor was four hundred pixels of empty navy
     under a thin band of picture — on the screen least able to spare it. At
     desktop widths the art is centred in the frame already and none of this
     applies, which is why it lives in the phone block.

     So the band starts centred and walks up into its composed position as the
     copy arrives. The drop is whatever it takes to centre a 16:9 band in the
     pin: half the leftover height, less the top edge it has already been given.
     With A = --band-h = 56.25vw and H the pin, that is H/2 - A/2 - --band-top.

     Written against --band-top rather than restating it, because it was written
     against a restatement once and the restatement went stale: the drop read
     `.41H - 28.1vw`, where the .41 was 50% less the 9% the art was placed at.
     The art moved to 4.2svh and the .41 did not, so the band settled 40px below
     centre for the whole flight — a number wrong in a way nothing on screen
     names. Reading the variable means moving the band moves the drop.

     max() at zero so a landscape phone — where the band is taller than the
     frame and already overflowing — is left exactly as it is rather than shoved
     further off the top.

     Guarded like the copy is. If the script never runs the copy is there from
     the first frame, and the art has to be where the copy was composed against. */

  /* The rise happens before the landing, not during it.

     --open spends its whole range placing the copy: the headline is in by .45,
     the paragraph by .61, the buttons by .75, the strip by .89. Drive the band's
     travel off it and the band is still 150px low while the headline is at full
     strength, which is type standing on film — the exact fault this hero has been
     round three times, arriving as a transient instead of a state. Running it
     faster only shrinks the window; it does not close it.

     So the rise is timed off --p and finishes where --open begins. The last
     eighth of the flight lifts the band out of the middle of the screen, the
     camera settles, and only then does the page open underneath it. Three beats
     in the order the eye wants them, and no frame in which the two overlap.

     Stated against --open-at rather than as the .70 it currently works out to.
     The last number in this hero that was quietly a restatement of another one
     went stale the moment the thing it restated moved, and the symptom was a
     band sitting 40px off centre for the whole flight — which nothing on screen
     names as a wrong number. .12 of travel is the length of the move; the point
     it has to end at is the opening, wherever the opening is put.

     The wash rides the same number, because a wash is only there to bed type and
     during the flight there is no type: full-strength film, then the ground
     arrives, then the words that sit on it. */
  .js .page-home .hero-reel {
    --rise: clamp(0, calc((var(--p, 0) - (var(--open-at) - .12)) * 8.333), 1);
  }

  /* The eyebrow waits here too, and it is the rise that forces it.

     The landing block above leaves the eyebrow standing for the whole flight on
     purpose — "who this is, and what to do" — which is right on a desktop and is
     what PULP does with its brand line. It stops being right the moment the band
     moves. Measured on a 390x844 phone: the eyebrow is fixed at y 273-290, the
     band flies at 312-532 and lands at 35-255, so between --p .71 and .81 the
     picture rises straight through the line. Eleven percent of the travel, about
     186px of scrolling, with letterspaced caps sitting on the film — the fourth
     report of type on the film, and the same cause every time.

     Nothing can stand in the band's path, and the path runs from the middle of
     the screen to the top of it. That leaves no safe ground up there at all, so
     the eyebrow joins the cascade and the flight shows film, cue and bar —
     exactly what the Lantern House flight now shows, and for the same reason.

     Fading it, rather than moving it out of the way, so that the landed
     composition is untouched. It shares the h1's ramp: the two are one block
     arriving, and giving the eyebrow its own earlier step would only stretch a
     cascade that already reads as one gesture.

     Scoped to this width query rather than to the aspect one the showcase uses,
     because this is a fix for the travel and --rise is defined here. Above 760px
     the band does not move and the eyebrow is in nothing's way. */
  .js .page-home .eyebrow {
    opacity: var(--land);
    transform: translateY(calc((1 - var(--land)) * 24px));
    --land: clamp(0, calc(var(--open) * 2.2), 1);
  }

  .js .page-home .hero-art {
    --flight-drop: max(0px, calc(max(100svh, 640px) * .5 - var(--band-h) / 2 - var(--band-top)));
    transform: translateY(calc((1 - var(--rise)) * var(--flight-drop)));
    /* This used to ramp to .75 as the copy arrived, on the reasoning that the
       picture gives up a quarter of itself so the headline standing on it can be
       read. The headline no longer stands on it, so there is nothing to buy. */
    opacity: 1;
  }
  /* The side wash was .82 running the full width, which was safe while the art
     was pushed two thirds off the right — it was darkening empty ground. Flush
     and full-width, that same wash sits on the left half of the tree and takes
     the canopy and the trunk down with it, so the frame reads as one bright
     corner over a dark nothing.

     .55, and spent by 72%, so it still holds the headline off the picture
     without putting out the half of the picture the headline is standing on.
     The vertical wash below does the heavier lifting anyway — it is what the
     body copy and the buttons are actually sitting on. */
  /* Both washes existed to hold type off the picture, and the type is no longer
     on the picture.

     The vertical one ran from the top of the frame and was .47 dark by the foot
     of the band — measured, not estimated — so nearly half the film was being
     put out to protect a headline that sits below it. It is clear across the band
     now and starts its ramp where the band ends.

     The side wash is gone outright. It was there to darken the left of the tree
     so the headline could stand on it; the headline stands on the ground under
     the tree instead, which the vertical wash already carries to full ink. */
  .page-home .hero-overlay {
    background:
      linear-gradient(180deg,
        rgba(11, 20, 39, 0) 0%,
        rgba(11, 20, 39, 0) 34%,
        rgba(11, 20, 39, .58) 54%,
        var(--ink) 86%);
  }
  /* Those stops are measured against the composed frame, where the band ends at
     30% and the ramp starts at 34. Centred for the flight the band sits at 37-63%
     instead, straight across the ramp — measured, the wash took its lower half to
     .58 and its bottom edge to .76. So the wash arrives with the band's rise
     rather than standing over the flight it has no type to protect. .js-guarded
     for the same reason the rise is: no script, no landing, and the copy is
     composed from the first frame with the full wash under it. */
  .js .page-home .hero-overlay { opacity: var(--rise); }
  .page-home .hero-grid {
    -webkit-mask-image: linear-gradient(180deg, transparent, black 55%, transparent);
    mask-image: linear-gradient(180deg, transparent, black 55%, transparent);
  }
  /* The art sits in the upper half on a phone, so the type starts below it
     rather than on top of it. Only the block auto-margin goes — keep the inline
     one, or the whole gutter collects on the right and the type pins to 0. */
  .page-home .hero-content {
    width: calc(100% - 32px);
    margin-inline: auto;
    margin-block: 0;
    /* Read off the band rather than guessed at. This was `min(25rem, 34svh -
       insets)` — a number tuned to look about right, which is a different thing
       from a number that clears the picture, and at 390x844 it landed 61px
       inside it.

       Stated as the band's own top plus the band's own height plus one gap, it
       clears by construction at every phone shape: a taller screen moves the
       band down and the copy follows, a wider one makes the band deeper and the
       copy follows that too. */
    padding: calc(var(--band-top) + var(--band-h) + var(--band-gap)) 0 .15rem;
  }
  /* The strip is the floor of the hero and keeps its full height; when the
     screen is tight it is the block above that gives way, not this. */
  .page-home .facts { flex: none; }
  /* The hero is pinned, so everything below the band has one screen to live in
     and the frame clips whatever will not fit. Sizing the type off width alone
     ignores the axis that is actually running out: a 375x667 phone is only 4%
     narrower than a 390x844 one and 21% shorter. So each of these takes the
     smaller of its width rule and a height rule, which leaves the common tall
     phones exactly as they were and lets a short one give ground gradually
     instead of losing the bottom of the strip outright. */
  .page-home .hero-reel h1 {
    margin: clamp(.4rem, 1.2svh, 1.2rem) 0;
    /* The height term is a line through two measured points rather than a plain
       ratio, because a ratio cannot express this: at 390x844 the width rule has
       to win and give 54.6px, and at 375x667 — a screen only 4% narrower but 21%
       shorter — it has to lose and give 40px, which is the size at which "create
       momentum." stops wrapping and the headline comes down from three lines to
       two. That one break is worth 46px, which is most of what a short phone is
       short by. Any single `Nsvh` passing through that at 667 also caps 844 below
       54.6, so the two points are stated instead.

       36px, not the 40 this was first set to: the measure is not the viewport.
       .hero-content is the frame less its own 32px gutter, so at 375 the line has
       311px to fit in, not 343 — and at 40px "create momentum." still ran three
       lines. Measured, not estimated, after the first attempt missed by 4px. */
    font-size: clamp(2rem, min(14vw, calc(10.5svh - 34px)), 4.3rem);
  }
  .page-home .hero-copy {
    margin-bottom: clamp(.85rem, 2.4svh, 2rem);
    font-size: .82rem;
    /* 1.72 is the reading value and it is right on a tall screen. The band that
       actually bites is 745-780svh — an iPhone 12 through 17 in Safari with the
       toolbars showing, which is the commonest viewport there is, and which was
       clipping the foot of the facts strip by 5px. Three lines of copy at 1.6
       give most of that back without the paragraph reading tight. */
    line-height: 1.6;
  }
  .page-home .hero-caption { display: none; }

  .page-home .facts {
    flex-direction: column;
    align-items: flex-start;
    justify-content: center;
    gap: clamp(.4rem, 1.2svh, 1rem);
    width: calc(100% - 32px);
    /* min-height went with the padding. It was never binding on a tall phone —
       the content is taller than 120px on its own — and on a short one it was a
       floor under the single block best able to give way. */
    padding-block: clamp(.5rem, 1.7svh, 1.4rem);
  }
  .page-home .facts-list {
    flex-direction: column;
    align-items: flex-start;
    gap: clamp(.28rem, .75svh, .55rem);
    width: 100%;
  }

  .page-home .statement { padding: 5rem 0; }
  .page-home .statement-grid,
  .page-home .process-grid { grid-template-columns: 1fr; gap: 2.4rem; }
  .page-home .statement h2 { font-size: 2.65rem; }

  .page-home .services,
  .page-home .work { padding: 5rem 0; }
  .page-home .section-head { align-items: flex-start; flex-direction: column; gap: 1.45rem; }
  .page-home .section-head h2,
  .page-home .process h2 { font-size: 2.8rem; }

  .page-home .service-list { grid-template-columns: 1fr; margin-top: 2.5rem; }
  .page-home .service-list article,
  .page-home .service-list article + article {
    min-height: auto;
    padding: 1.4rem 0 2rem;
    border-right: 0;
    border-bottom: 1px solid var(--line);
  }
  .page-home .service-list :is(h2, h3) { margin-top: 2.7rem; }

  /* One column, so the verticals it was aligning to are gone and it has nothing
     left to hold up. Stacks exactly as the columns above stack here. */
  .page-home .service-care { grid-template-columns: 1fr; }
  .page-home .service-care > *,
  .page-home .service-care > * + * {
    padding: 1.4rem 0 1.6rem;
    border-right: 0;
  }
  .page-home .service-care > *:last-child { padding-bottom: 0; }

  .page-home .work-grid { grid-template-columns: 1fr; }
  .page-home .work-grid .project:first-child { grid-column: auto; }
  .page-home .project-image { min-height: 300px; }
  .page-home .project-meta { align-items: flex-start; flex-direction: column-reverse; gap: .45rem; }

  /* Name over sentence, both ranged left. Right-aligned copy needs a left edge
     to answer to, and in a single column there isn't one. */
  .page-home .rail-head {
    flex-direction: column;
    align-items: flex-start;
    gap: .6rem;
    margin-top: 2.6rem;
  }
  .page-home .rail-head p { width: 100%; text-align: left; }
  .page-home .rail-head + .work-grid,
  .page-home .live-grid { margin-top: 1.5rem; }
  .page-home .site-meta b { font-size: 1rem; }

  .page-home .process { padding: 5.5rem 16px; }
  .page-home .process-grid { margin-top: 1.8rem; }
  .page-home .process li { grid-template-columns: 2.8rem 1fr; }

  .page-home .contact h2 { font-size: 2.9rem; }
}

/* The art used to breathe: a 16s drift that pushed the still from scale(1.08)
   to scale(1.13). In a box that was already cropping the picture that was a
   second crop on top of a first, and between them the tree lost an eighth of
   itself at all times — which is the opposite of what a hero whose whole job is
   showing you a picture should do. The box now matches the plate exactly, so
   any scale above 1 cuts into the frame and the drift had to go.

   The hero is not short of movement without it: the flight scrubs on the
   scroll, the veil rises and falls through the shot, and the ambient glow
   behind it never stops. The still is what you see when you have stopped
   scrolling, and when you have stopped scrolling it should hold. */


/* The cue used to be hidden here, under (max-aspect-ratio: 1/1), paired with the
   --open: 1 base.css pinned on portrait: with the hero composed from the first
   paint the instruction had nothing left to instruct, and the frame's bottom edge
   — where it sits — is where the facts strip lands.

   Both are gone. A phone gets the flight now, and a flight with no cue is a still
   photograph as far as the reader is concerned: nothing on the screen says the
   picture is waiting for their thumb. It has its room back too, because during
   the flight the strip is at zero and does not arrive until --open .44, by which
   point the cue has been gone since --p .2. */

/* Paired with the width that sets the art to 100vw, because the centring below
   is only correct against that width — above 760px the art is min(93vw, 1370px)
   and a -50vw margin would throw it off its own axis. */
@media (max-width: 760px) {
  /* Centred on the frame rather than hung off its right edge.

     The art is 100vw and the frame, once inset, is 32px narrower — so anchored
     right it lost all 32 of those pixels off its left side and the tree sat
     16px left of centre. Centring it fixes that at both ends of the landing and
     everywhere in between: full bleed, the viewport's midpoint and the frame's
     are the same point; inset, the frame narrows evenly about that same point.
     Anchoring to an edge is what made it depend on which of the two it was.

     100vw is kept rather than letting the art track the narrower frame. It was
     originally so the roots fan below would meet this trunk; that band is gone,
     but full bleed is still what the picture wants, and centring at the
     viewport's midpoint — which is what left:50% resolves to here — is what
     keeps the tree on axis at both ends of the landing. */
  .page-home .hero-art { right: auto; left: 50%; margin-left: -50vw; }
}


/* ==========================================================================
   REDUCED MOTION — last, so it wins
   base.css collapses .reel to height:auto when there is no flight to make room
   for. Both of this page's hero heights are a class more specific than that and
   beat it, which left the pin static inside a section still 240svh tall — the
   hero, then a screen and a half of empty navy before anything else. Stated
   here at the foot of the file so it comes after the desktop height and the
   phone one, instead of after only whichever was written first.
   ========================================================================== */

@media (prefers-reduced-motion: reduce) {
  .page-home .hero-reel { height: auto; }

  /* The frame is taken out of flow so it can be animated between full bleed and
     inset. With no flight there is nothing to animate, and leaving it out of
     flow pinned it to the pin's minimum height while the copy and the facts
     strip needed more — so the strip was clipped again, this time for the
     visitors least able to go looking for it. Back in flow, sized by its own
     contents, with the landed inset kept as margin. */
  .page-home .hero-reel .reel__frame {
    position: relative;
    inset: auto;
    margin: var(--reel-inset-y) var(--reel-inset-x);
  }

  /* "01 — Two ways to build it" is a real label for what follows and stays.
     The cue is an instruction for a flight that is not happening here, so it
     goes — the same call base.css makes for .reel__cue. */
  .page-home .hero-cue { display: none; }

  /* The engine parks --p at 1 here, which puts --open at 1 and lands the copy
     on its own. Restated anyway, the way base.css restates it for .reel__open:
     a hero that hides its headline until a number arrives should not be relying
     on that number arriving. */
  .js .page-home .hero-reel h1,
  .js .page-home .hero-copy,
  .js .page-home .hero-actions,
  .js .page-home .eyebrow { opacity: 1; transform: none; }
  .js .page-home .facts,
  .js .page-home .hero-caption { opacity: 1; }

  /* And the two things the flight itself drives, for the same reason and with
     more at stake: those are opacities and these are a position. --rise is off
     --p, which the engine parks at 1 here — but if it ever did not, the band
     would sit three hundred pixels low with the wash off and the copy, forced on
     by the rule above, printed across it. The one arrangement of this hero that
     has to be impossible is type on film. */
  .js .page-home .hero-art { transform: none; }
  .js .page-home .hero-overlay { opacity: 1; }
}

/* ==========================================================================
   SUB-PAGES — Services, Work, Process, About

   These four were sections of the homepage and are now documents of their
   own. Two things change when a band stops being the fifth thing you scroll
   past and becomes the first thing you land on.

   Clearance. Every padding above was measured against a hero, and a hero
   clears the bar for whatever follows it. .hdr is position:fixed and 97px
   tall on a desktop, 77px on a phone — both measured, not assumed — so a
   band that now opens the document needs that height back before its first
   line. The 404 page has carried the same floor, for the same reason, since
   it was written.

   Rank. The heading that opens each page is the page's <h1> now rather than
   one <h2> among six. The sizes restated below are the <h2> sizes to the
   character: the heading has not changed, only what it is called, and a
   subsidiary page whose title outsizes the homepage's would be the tail
   wagging the dog.
   ========================================================================== */

.page-sub #page > *:first-child { padding-top: clamp(9rem, 13vw, 12rem); }

.page-sub .section-head h1,
.page-sub .process h1 {
  margin: .85rem 0 0;
  font-size: clamp(2.55rem, 4.8vw, 4.5rem);
  line-height: .98;
}

.page-sub .about h1 {
  max-width: 900px;
  margin: 0;
  font-size: clamp(2.65rem, 5.3vw, 5.2rem);
  line-height: 1.02;
}

/* The wash across the top of .work is the hero settling into the page: it
   exists to lift a mono link and a line of 13px copy off the root traces,
   which are at their brightest exactly there. On the work page there is no
   hero and there are no traces, so it is navy laid over navy — invisible if
   it behaves and a faint horizontal band if it does not. */
.page-sub .work::before { display: none; }

/* ---------------------------------------------------------------- ABOUT
   The one page with no artwork to carry it, so the type carries it instead:
   a statement set at hero scale, then the plain facts under it in two
   columns of reading width. Nothing here is decorated. A studio page that
   dresses itself up is answering a question nobody asked — the question is
   who is going to do the work, and where, and what happens if it goes
   wrong. */

.page-sub .about {
  width: min(100% - 48px, var(--read));
  margin-inline: auto;
  padding-bottom: clamp(5rem, 9vw, 8rem);
}

.page-sub .about-lede {
  max-width: 640px;
  margin: 2.4rem 0 0;
  color: var(--muted);
  font-size: .95rem;
  line-height: 1.8;
}

.page-sub .about-grid {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  margin-top: clamp(3.5rem, 6vw, 5.5rem);
  border-top: 1px solid var(--line);
}
.page-sub .about-grid > div {
  padding: 1.7rem 2rem 0 0;
  border-right: 1px solid var(--line);
}
.page-sub .about-grid > div + div { padding-left: 2rem; }
.page-sub .about-grid > div:last-child { border-right: 0; }

.page-sub .about-grid h2 {
  margin: 0 0 .8rem;
  font-size: 1.1rem;
  letter-spacing: -.045em;
}
.page-sub .about-grid p {
  margin: 0 0 .9rem;
  color: var(--muted);
  font-size: .82rem;
  line-height: 1.72;
}
.page-sub .about-grid p:last-child { margin-bottom: 0; }

/* The one row of hard facts on the page: where, how to reach it, what it
   costs to find out. Mono, because these are the numbers rather than the
   argument, and the same treatment the hero gives its facts strip. */
.page-sub .about-facts {
  display: flex;
  flex-wrap: wrap;
  gap: 2.6rem;
  margin-top: clamp(3.5rem, 6vw, 5rem);
  padding-top: 1.9rem;
  border-top: 1px solid var(--line);
}
.page-sub .about-facts div {
  display: grid;
  gap: .3rem;
}
.page-sub .about-facts b {
  font-family: var(--font-display);
  font-size: 1.32rem;
  font-weight: 400;
  letter-spacing: -.05em;
}
.page-sub .about-facts span {
  color: var(--muted);
  font-family: var(--font-mono);
  font-size: .62rem;
  letter-spacing: .16em;
  text-transform: uppercase;
}
.page-sub .about-facts a { color: inherit; text-decoration: none; }
.page-sub .about-facts a:hover { color: #77D5F5; }

@media (max-width: 860px) {
  .page-sub .about-grid { grid-template-columns: 1fr; }
  .page-sub .about-grid > div {
    padding: 1.5rem 0;
    border-right: 0;
    border-bottom: 1px solid var(--line);
  }
  .page-sub .about-grid > div + div { padding-left: 0; }
  .page-sub .about-grid > div:last-child { border-bottom: 0; }
}

@media (max-width: 760px) {
  .page-sub .about { width: calc(100% - 32px); }
  .page-sub .about-facts { gap: 1.6rem 2.2rem; }

  /* The phone rules further up bump these headings to 2.8rem, but they were
     written when every one of them was an <h2>. On these four pages the same
     heading is the page's <h1> now, so it missed the rule and fell back to the
     bottom of its own clamp — 2.55rem, which reads smaller on a phone than the
     identical heading does on the homepage. Restating the h2 size, not a new
     one: the heading did not change, only its rank did.

     .about h1 is deliberately absent. Its clamp floor is already 2.65rem, the
     exact size .statement h2 takes here, so it lands where it should on its
     own — measured at 390px, not assumed. */
  .page-sub .section-head h1,
  .page-sub .process h1 { font-size: 2.8rem; }
}

/* The homepage rail is a taster, so it needs a way out of itself. Centred
   rather than trailing the last card: it is a link about the whole row, not
   about the third demo. */
.page-home .work-more {
  margin: 3rem 0 0;
  text-align: center;
}
