/* ==========================================================================
   OOGLE V5 — ART DIRECTION LAYER

   tokens.css defines the system. styles.css builds the components. This file
   is what sits on top: the grounds, the two motifs, and the small amount of
   motion the site is allowed.

   WHAT THIS FILE USED TO BE

   It was 1,050 lines. It had signal fields, node topologies, capability
   drawings, a resolver, an era ribbon, cropped background words, editorial
   topic artwork, parallax, ambient dash-travel and four background systems.
   Each piece was defensible. The sum was a company that looked like it sold
   network monitoring.

   It is now about a fifth of that, and the rule is a hard one:

     TWO MOTIFS.
       1. the blue keyline — a 2px rule, in styles.css, already the site's
          most repeated shape
       2. the pixel field — squares on a lattice, three placements, no more

   FOUR GROUNDS, and no pattern behind anything else:
       white · soft gray · deep navy · Oogle blue

   Everything here could be deleted and the site would still be a complete,
   accessible, well-typeset document. That was true before and it is the one
   thing worth keeping from the previous version of this file.
   ========================================================================== */


/* ==========================================================================
   1. THE PIXEL FIELD
   ========================================================================== */
.art-px {
  display: block; width: 100%; height: 100%;
  overflow: hidden;
  color: var(--blue-light); opacity: 0.85;
  /* V5.11: 90deg. The house rule in visual.py says "no diagonal -- a diagonal
     would be somebody else's idea", and this mask had been contradicting it
     by ten degrees on six plates. */
  -webkit-mask-image: linear-gradient(90deg, transparent 0%, #000 62%);
  mask-image: linear-gradient(90deg, transparent 0%, #000 62%);
}
.px      { fill: currentColor; opacity: 0.16; }
.px-mid  { fill: currentColor; opacity: 0.26; }
.px-hi   { fill: currentColor; opacity: 0.42; }

/* The layer that holds it. Absolutely positioned inside a clipping parent,
   masked away from the half the reading sits on — a pattern under a paragraph
   is not texture, it is a pattern under a paragraph, and low opacity does not
   fix that. */
/* .art-layer marks anything decorative: it is aria-hidden, never focusable,
   and interact.py asserts both plus a hard cap of two per page. The V5.10
   artwork carries the class for exactly that reason. */
.art-layer {
  position: absolute; inset: 0; z-index: 0;
  pointer-events: none; overflow: hidden;
}
.art-layer > svg { position: absolute; inset: 0; width: 100%; height: 100%; }


/* ==========================================================================
   2. THE FOUR GROUNDS
   White and soft gray come from styles.css (.on-paper). The two
   strong ones are here.
   ========================================================================== */

/* --- Oogle blue ----------------------------------------------------------
   The loudest thing in the system, so it is rationed to one appearance per
   page. Its gradient never goes lighter than #0E61D1, which is what keeps
   white on it at 5.75:1 everywhere in the field. */
.bluefield {
  position: relative; overflow: clip; isolation: isolate;
  background-color: var(--blue);
  /* V5.11: 180deg, not 162. Every ground in the system is orthogonal now.
     An 18-degree ramp is not a design idea, it is a default nobody chose. */
  background-image: linear-gradient(180deg, var(--blue) 0%, var(--blue-deep) 76%, var(--blue-far) 100%);
  color: #fff;
}
.bluefield > * { position: relative; z-index: 2; }
.bluefield .art-layer { color: var(--navy); opacity: 0.45; }
.bluefield .eyebrow { color: #E6F0FF; }
.bluefield .eyebrow::before { background: var(--cyan-soft); }
.bluefield h2, .bluefield h3, .bluefield .display, .bluefield .mega { color: #fff; }
.bluefield p, .bluefield .lead, .bluefield li { color: #E6F0FF; }
.bluefield .mono { color: #DCEBFF; }   /* 4.76:1 on --blue; #CFE2FF was 4.38 */
.bluefield a:not(.btn) { color: #fff; text-decoration: underline; text-underline-offset: 3px; }
.bluefield a:not(.btn):hover { text-decoration-color: var(--cyan-soft); }
.bluefield .btn-ghost { border-color: rgba(255, 255, 255, 0.62); color: #fff; }  /* 3.18:1 boundary */
.bluefield .btn-ghost:hover { border-color: #fff; background: rgba(255, 255, 255, 0.10); color: #fff; }
.bluefield .btn-solid, .bluefield .btn:not(.btn-ghost) {
  background: #fff; color: var(--blue-deep); border-color: #fff;
}
.bluefield .btn:not(.btn-ghost):hover { background: #E6F0FF; color: var(--blue-deep); }
.bluefield :focus-visible { outline-color: #fff; }
/* Anything that brings its own white surface keeps its own ink. */
.bluefield .panel, .bluefield .panel p, .bluefield .panel li { color: var(--ink-3); }
.bluefield .panel {
  background: var(--paper); border-color: transparent;
  box-shadow: 0 30px 70px -34px rgba(4, 20, 56, 0.55);
}
.bluefield .panel .mono { color: var(--ink-4); }
.bluefield .caps li, .bluefield .chip {
  background: rgba(255, 255, 255, 0.94); border-color: transparent; color: var(--ink-3);
}


/* ==========================================================================
   3. THE HERO

   V5.9.5 put a headline, a lead and two buttons on the left of a 1440px band
   and filled the remaining 31% with 220 five-pixel squares at an effective
   8-21% alpha. That measured out at under half a percent ink: it was not a
   device, it was dust, and at 390px it was nothing at all.

   What is there now is one object. The period in the Oogle wordmark, at
   display scale, bleeding off the right edge and off the top and bottom, so
   it reads as a fragment of something larger. Its leading edge comes apart
   into squares that step toward the headline and stop short of it. The mark
   and the motif are finally the same shape.

   The hero closes on a full-bleed ruled band carrying the four facts that
   answer "are these people real" before the visitor has scrolled at all.
   ========================================================================== */
.hero {
  /* The section is now a light page ground with a contained object on it.
     The .on-dark token re-point moved off this selector and onto .hero-panel
     in tokens.css, because the navy is the panel's, not the page's. */
  isolation: isolate;
  position: relative;
  padding-block: clamp(14px, 2vw, 28px) 0;
}

/* The panel. This is the object: one navy surface, one large radius, its own
   generous padding, and `overflow: clip` so the artwork is cut by the
   panel's corners instead of by the viewport. V5.10 let the object leave the
   frame on three sides on the theory that containment "would make it an
   illustration". It made the page look like a deck slide instead. */
.hero-panel {
  position: relative;
  /* `clip`, not `hidden`. Both crop the artwork to the panel's corners, but
     `hidden` makes the panel a scroll container -- which silently breaks any
     position: sticky inside it, and at 200% text zoom leaves overflow the
     reader cannot scroll to. */
  overflow: clip;
  border-radius: var(--radius-feature);
  background: var(--navy);
  box-shadow: var(--panel-lift);
}

.hero-stage {
  position: relative;
  padding: clamp(56px, 7.5vw, 108px) clamp(28px, 4.5vw, 80px) clamp(52px, 6.5vw, 92px);
}

/* The grid reserves the artwork's territory with an empty cell rather than
   letting the copy and the object share the same space and hope. Nothing can
   collide with the object at any width because nothing is ever placed there. */
.hero-grid {
  position: relative; z-index: 2;
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(0, 0.78fr);
  gap: clamp(32px, 5vw, 64px);
  align-items: center;
  min-height: clamp(320px, 34vw, 460px);
}
.hero-blank { min-height: 1px; }

/* The field's slot. Full panel height, anchored right, clipped by the panel's
   own radius. It is a background system now, not an object placed in a box. */
.hero-art {
  position: absolute;
  z-index: 1;
  inset: 0 0 0 auto;
  translate: none;
  width: clamp(300px, 48%, 640px);
  min-height: 0;
  pointer-events: none;
  /* Plane one of two. The pointer parallax writes a transform straight onto
     this element and onto .sf-cells; the easing is a CSS transition, so there
     is no rAF loop, nothing to schedule and nothing to tear down. Six pixels
     of travel, which is meant to be felt rather than seen. */
  transition: transform 520ms var(--e-out), opacity var(--m-scene) var(--e-out);
}

/* ==========================================================================
   3a. THE SIGNAL FIELD

   Four layers on one lattice. The lattice is `--cell`, and --cell is the ONLY
   number that positions anything: the grid's pitch, the bands' left edge and
   width, and every lit cell's x and y are all expressed in it. A pixel
   therefore cannot be anywhere except on a grid intersection, which is the
   structural answer to V5.11's squares looking sprayed on.

   Nothing here animates a layout or paint property. The bands, the glow and
   the two parallax planes animate `transform`; the cells animate `opacity`.
   Both are interpolated on the compositor thread, so the field costs the main
   thread nothing per frame and keeps running at full rate even while the page
   is busy. No canvas, no shader, no library, no requestAnimationFrame.

   Markup comes from `visual.py::signal_field()` -- positions and phases only,
   deterministic from a seed, so the artwork is reviewable in a diff.
   ========================================================================== */
.sfield {
  /* --cell is a LENGTH, and it has to be.
     It shipped as `calc(100% / 13)` for one build, which looks right and is
     not: an unresolved percentage resolves against the WIDTH for `left` and
     `width`, and against the HEIGHT for `top` and `height`. So every lattice
     square and every lit cell came out a portrait rectangle whose aspect
     tracked the panel's -- 1:1.27 at 1440 and 1:1.86 at 900, where the field
     read as an equaliser rather than as pixels. The brand's shape is a square.
     `cqw` resolves against the field's inline size on both axes, so thirteen
     columns fill the field exactly AND the cell is square. The clamp is the
     fallback where container queries are missing: not an exact thirteen, but
     square, which is the property that matters. */
  --cell: clamp(26px, 3.2vw, 47px);
  position: absolute; inset: 0;
  /* `clip`, not `hidden`: same rule as every other panel on the site. */
  overflow: clip;
  /* The field fades in from the left so it is at full strength only where the
     type is not. A pulsing element behind body copy cannot pass contrast, and
     no amount of restraint fixes that -- so it is masked, not dimmed. */
  -webkit-mask-image: linear-gradient(90deg, transparent 0%, #000 42%, #000 100%);
  mask-image: linear-gradient(90deg, transparent 0%, #000 42%, #000 100%);
}

@supports (container-type: inline-size) {
  .hero-art { container-type: inline-size; }
  .sfield { --cell: calc(100cqw / 13); }
}

/* --- the lattice. Two gradients, painted once, never animated. ------------ */
.sf-grid {
  position: absolute; inset: -1px;
  background-image:
    linear-gradient(90deg, rgba(120, 170, 230, 0.13) 1px, transparent 1px),
    linear-gradient(180deg, rgba(120, 170, 230, 0.10) 1px, transparent 1px);
  background-size: var(--cell) var(--cell);
}

/* --- the bands. The prism's DNA, cleaned up: no tear, no hard stops.
   ONE repeating gradient rather than three placed elements. Placed bands are
   anchored to a fixed origin, so at any box the lattice does not exactly fill
   they end up crowded into one half of the field with bare grid beside them.
   A repeating gradient has no origin to be wrong about: it fills whatever it
   is given, at any width, and it is one composited layer instead of three. */
.sf-bands {
  position: absolute; inset: -12% -100% -12% -100%;
  /* Soft shoulders, not hard stops. The first build used hard stops and one
     of them landed 90px in from the panel's right edge, drawing a full-height
     vertical seam and leaving the field terminating in a dark strip instead of
     bleeding off the corner. Every edge here ramps over 3% of the repeat --
     enough to remove the seam, not enough to become a blur. */
  background-image: linear-gradient(90deg,
      rgba(14, 97, 209, 0.26) 0%, rgba(14, 97, 209, 0.26) 20%,
      transparent 23%, transparent 35%,
      rgba(0, 191, 255, 0.13) 38%, rgba(0, 191, 255, 0.13) 51%,
      transparent 54%, transparent 66%,
      rgba(20, 50, 186, 0.28) 69%, rgba(20, 50, 186, 0.28) 97%,
      rgba(14, 97, 209, 0.26) 100%);
  background-size: calc(var(--cell) * 6.5) 100%;
  /* Vertical falloff so the bands are a field, not a barcode. */
  -webkit-mask-image: linear-gradient(180deg, transparent 0%, #000 24%, #000 76%, transparent 100%);
  mask-image: linear-gradient(180deg, transparent 0%, #000 24%, #000 76%, transparent 100%);
}

/* --- the light. One soft field for depth, no blur filter. ---------------- */
.sf-glow {
  position: absolute; inset: -25%;
  background: radial-gradient(46% 38% at 62% 42%,
              rgba(0, 191, 255, 0.16) 0%,
              rgba(14, 97, 209, 0.10) 38%,
              transparent 72%);
}

/* --- the cells. The group pulses; the cell only sits still and differs.
   Four groups instead of fifteen loose cells: fifteen independently animated
   elements is fifteen composited layers and 27MB of layer memory for a
   decoration. Each cell keeps its own base opacity, so a group brightens a set
   of unequal squares rather than a block of identical ones. */
.sf-cells {
  position: absolute; inset: 0;
  /* Plane two of the parallax. The transform is written by JS directly onto
     this element, not inherited from a custom property on the panel: writing
     --px on .hero-panel dirtied 43 elements per pointer move. */
  transition: transform 640ms var(--e-out);
  /* The resting value, and the value a reduced-motion visitor sees. The
     keyframes swing either side of it, so switching the motion off leaves a
     complete lit grid rather than a blank one. */
  opacity: 0.26;
}
.sf-cells i {
  position: absolute;
  left: calc(var(--x) * var(--cell));
  top: calc(var(--y) * var(--cell));
  width: calc(var(--cell) - 1px);
  height: calc(var(--cell) - 1px);
  background: var(--cyan);
  opacity: var(--o, 0.8);
}

@media (prefers-reduced-motion: no-preference) {
  /* One pitch of travel, `linear infinite`: the gradient repeats every 6.5
     cells, so translating exactly that distance loops seamlessly and needs no
     `alternate`. */
  .sf-bands { animation: sfDrift 30s linear infinite; }
  .sf-glow  { animation: sfGlow 26s var(--e-inout) infinite alternate; }
  .sf-cells {
    animation: sfPulse var(--t) var(--e-inout) infinite;
    /* Negative delay: every group starts part-way through its own cycle, so
       the field is already mid-breath on the first frame. */
    animation-delay: calc(var(--d) * -1);
  }
  /* Nothing animates while it cannot be seen. The compositor otherwise ticks
     every one of these sixty times a second for the whole life of the tab,
     including after the visitor has scrolled a thousand pixels past them:
     3.2% of a core, indefinitely, for a picture nobody is looking at. */
  .is-still .sf-bands,
  .is-still .sf-glow,
  .is-still .sf-cells,
  .is-still .closer-art > img { animation-play-state: paused; }
}
@keyframes slideIn {
  from { transform: scale(1.05) translateY(-1.6%); }
  to   { transform: none; }
}
@keyframes sfDrift {
  from { transform: translate3d(0, 0, 0); }
  to   { transform: translate3d(calc(var(--cell) * -6.5), 0, 0); }
}
@keyframes sfGlow {
  from { transform: translate3d(-5%, -4%, 0); }
  to   { transform: translate3d(6%, 5%, 0); }
}
@keyframes sfPulse {
  /* 0.20-0.36, not 0.12-0.46. A 3.8x swing on the brightest mark in the
     composition is a blink; 1.8x is a breath, which is what was asked for. */
  0%, 100% { opacity: 0.20; }
  50%      { opacity: 0.36; }
}


.hero-copy { max-width: none; position: relative; }
.hero-copy .eyebrow { color: var(--cyan); }
.hero-copy h1 { margin-top: var(--s6); }
/* Two colours in one headline halved its authority: the light blue second
   line sat at a lower value than the first and the sentence came apart. One
   colour, one weight. The period is the only thing that changes. */
.hero h1 em { font-style: normal; color: inherit; }
.hero h1 em::after { content: ""; }
.hero-copy .lead {
  color: var(--snow-2); max-width: 32ch; margin-top: var(--s7);
}
.hero-cta { margin-top: var(--s9); }

/* ==========================================================================
   3b. THE FACT BAR
   Its own component, sitting under the hero with a deliberate gap. V5.11 had
   it as a rail welded to the bottom of the hero panel, which made four
   verifiable facts read as small print under a headline instead of as a
   statement of terms.
   ========================================================================== */
/* The gap is the whole argument for the component existing, so it is bigger
   than either radius. It shipped at 18px against a 32px hero corner: two dark
   rounded panels of identical width with their curves nearly touching read as
   one surface with a crack in it, not as two objects. */
.factbar { padding-block: clamp(28px, 3.4vw, 52px) 0; }
.facts {
  display: grid;
  grid-template-columns: repeat(4, minmax(0, 1fr));
  list-style: none; margin: 0; padding: 0;
  /* Darker than the hero's navy on purpose: two navy surfaces at the same
     value read as one surface interrupted. */
  background: #040914;
  border: 1px solid var(--line-dark);
  /* Matching the hero's radius, not one step under it. 32 above 24 with a thin
     gap between them looks like an oversight rather than a decision. */
  border-radius: var(--radius-feature);
  overflow: clip;
}
.facts li { min-width: 0; display: flex; }
/* The cell is a link. The bar had a hover lift on four <li> that went nowhere,
   which is a false affordance -- a surface that lights under the cursor is
   telling the visitor it is clickable. Every one of these four facts is
   verifiable on a page of this site, so the honest fix was to make the claim
   go to its evidence rather than to delete the interaction the brief asked
   for. */
.facts a {
  display: block; width: 100%;
  overflow-wrap: anywhere;
  padding: clamp(22px, 2.4vw, 32px) clamp(18px, 2vw, 30px);
  transition: background-color var(--m-state) var(--e-inout);
}
/* Inset, not full-bleed. A divider that runs edge to edge between four equal
   columns of two left-aligned lines is a table rule; the same line stopped
   short of the padding is a separator. */
.facts li + li { position: relative; }
.facts li + li::before {
  content: ""; position: absolute; left: 0; top: 22px; bottom: 22px;
  width: 1px; background: var(--line-dark);
}
.facts b {
  display: block;
  /* 18px, not 15. This is the bar's subject line, and at 15px against a 13px
     letterspaced uppercase mono the second line was wider, more textured and
     landed first -- the hierarchy read backwards. */
  font-size: var(--t-h4); font-weight: var(--w-bold); line-height: 1.2;
  color: var(--snow); letter-spacing: var(--tr-h4);
}
.facts span {
  display: block; margin-top: 9px;
  font-family: var(--font-mono); font-size: var(--t-mono);
  letter-spacing: var(--tr-mono); text-transform: uppercase;
  color: var(--snow-3); line-height: 1.45;
}
/* Informational, not a toy: the surface lifts one step and the second line
   gains contrast. Nothing moves. */
.facts a:hover { background: rgba(110, 160, 220, 0.07); }
.facts a:hover b { color: #fff; }
.facts a:hover span { color: var(--snow-2); }

@media (max-width: 900px) {
  .facts { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .facts li:nth-child(odd)::before { content: none; }
  .facts li:nth-child(n+3) { border-top: 1px solid var(--line-dark); }
}
/* Phones: the bar keeps its breathing room but stops spending it twice. The
   hero and the four facts together are 115% of a 390x844 viewport at the
   generous padding, and these four lines exist to be read before the visitor
   scrolls. */
/* Phones: the bar keeps its breathing room but stops spending it twice. */
@media (max-width: 560px) {
  .facts a { padding: var(--s5); }
  .facts b { font-size: var(--t-sm); }
  .facts span { margin-top: 6px; line-height: 1.35; }
  .facts li + li::before { top: 16px; bottom: 16px; }
}
@media (max-width: 600px) { .facts { border-radius: var(--radius-panel); } }

@media (max-width: 64em) {
  .hero-grid { grid-template-columns: minmax(0, 1fr) minmax(0, 0.62fr); }
  .hero-art { width: clamp(260px, 40%, 460px); }
  .hero-copy .lead { max-width: 40ch; }
}
/* Below 820 the field stops being a column beside the type and becomes the
   panel's own ground, present only in its lower half.

   The first V5.12 build gave it a 150px band under the CTAs. That cost the
   thing the facts exist for: hero plus fact bar went from 97.7% of a 390x844
   phone to 117%, so the four facts that answer "are these people real" before
   the visitor scrolls were below the fold on every phone. Seventeen percent of
   a viewport is not available for decoration.

   The lattice and the light stay, masked to fade in downward, so the panel has
   texture and depth and costs no height at all. The cells go: a pulsing square
   is a detail for a screen with room for details, and a cell that renders
   nothing while still ticking sixty times a second is cost with no picture. */
@media (max-width: 51.25em) {
  .hero-grid { grid-template-columns: minmax(0, 1fr); min-height: 0; }
  .hero-blank { display: none; }
  .hero-art { inset: 0; width: auto; transform: none; }
  .sfield {
    --cell: clamp(26px, 7.5vw, 40px);
    -webkit-mask-image: linear-gradient(180deg, transparent 44%, #000 94%);
    mask-image: linear-gradient(180deg, transparent 44%, #000 94%);
  }
  /* display:none, not opacity:0 -- an animation on a display:none element does
     not run, which is the point.
     The bands go too. With the cells gone they were the only thing left, and a
     band gradient striped by vertical gridlines at a 28px pitch behind the
     lead and both buttons does not read as digital infrastructure. It reads as
     gradient banding: a rendering fault, not a design. The grid and the light
     are enough for texture on a phone. */
  .sf-cells, .sf-bands { display: none; }
}
@media (max-width: 600px) {
  .hero-panel { border-radius: var(--radius-panel); }
  .hero-copy .lead { max-width: 30ch; }
  /* Two up, not four stacked: the same four facts in half the height. */
}


/* ==========================================================================
   3c. THE INTERNAL PAGE HERO SYSTEM (V5.13)

   Five families, one component. Every internal page used to open with an
   eyebrow, a headline, a paragraph and a bordered note -- fifty-one times.
   The families differ by GROUND and by what sits in the second column; the
   grid, the radius, the padding and the entrance are shared, so the system is
   one set of rules rather than fifty-one designs.

     .phero-service    navy, the page's own diagram
     .phero-industry   pale, bordered, same diagram grammar
     .phero-company    the warm ground, used once on /about/
     .phero-portfolio  navy, real client screenshots in browser frames
     .phero-editorial  typography and a hairline, no artwork at all

   The fifth family is the point of the other four: hierarchy means a legal
   page and a contact form do NOT get a production, and giving every page the
   same intensity is how a site ends up looking like a template again.
   ========================================================================== */
.phero { padding-block: clamp(14px, 2vw, 28px) 0; }
.phero-panel {
  position: relative;
  isolation: isolate;
  overflow: clip;
  border-radius: var(--radius-feature);
  padding: clamp(34px, 4vw, 66px) clamp(26px, 4vw, 68px);
  box-shadow: var(--panel-lift);
}
.phero-grid {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(0, 0.92fr);
  gap: clamp(28px, 4.5vw, 72px);
  align-items: center;
}
.phero-copy { min-width: 0; position: relative; z-index: 2; }
/* `break-word`, and one value at every width.
   V5.13 set `normal` here so a headline's final full stop could not break
   onto a line of its own, on the reasoning that "the column has room and the
   panel clips nothing". Neither half held. Above 1000px the hero is two
   columns, and 33 pages join their last two words with &nbsp; -- on
   /who-we-help/education/ at 1920 the last line ran 108px PAST the copy
   column and printed its full stop on top of the artwork panel. Below 44em
   the fallback was `anywhere`, which broke words mid-token: at 375 the
   /website-maintenance/ headline set "are" and "that" on lines of their own
   and then a 10px line containing nothing but ".".
   `break-word` is the value that does what both were reaching for: it breaks
   only when a word genuinely cannot fit, so nothing leaves the column and
   nothing is split that did not have to be. An orphaned full stop is a
   typographic irritation; a headline sitting on the artwork is a broken
   page. */
/* V5.22: A FLOOR THAT GUARANTEES THE LONGEST WORD FITS ITS OWN COLUMN.
   --t-display bottoms out at 40px, but the column an internal h1 sits in is
   set by the panel and the hero grid, not by the viewport: 216px inside a
   320px screen, and 424px at 1024 where the copy column is roughly half a
   capped track. At those two widths eleven-letter words were wider than the
   line they were on and `overflow-wrap: break-word` split them mid-word --
   measured: "WooCommerc / e", "maintenan / ce", "emergenci / es",
   "cooperatin / g.", "informatio / n", "Sometime / s".

   `cqi` rather than `vw` because the CONTAINER is the constraint and the
   viewport is not: 12.5cqi solves at 320 and at 1024 with the same number,
   where no single vw value can (8.8vw at 320 is 5.4vw at 1024). The homepage
   .mega solved the same problem with min(--t-mega, 12.5vw) because its column
   really does track the viewport; this one does not.

   It only bites where it has to. At 375 and up it is within a couple of
   percent of --t-display and at 1440 it is above it, so nothing moves; at 320
   the headline sets at 31px instead of 40, which is the correct size for a
   line that is 248px wide. */
.phero-copy { container-type: inline-size; }
.phero-copy h1 {
  margin-top: var(--s6);
  max-width: 17ch;
  overflow-wrap: break-word;
  font-size: min(var(--t-display), 12.5cqi);
}
.phero-copy .lead { margin-top: var(--s7); max-width: 40ch; }
.phero-act { margin-top: var(--s8); align-items: center; }
/* The aside every service and audience page already carried. It stays in the
   copy column: the second column is the drawing now, and dropping a sentence
   of real content to make room for decoration is the wrong trade. */
.phero-note {
  margin-top: var(--s8); padding-left: var(--s5);
  border-left: 2px solid var(--keyline);
  max-width: 46ch;
}
.phero-note .mono { display: block; margin-bottom: var(--s3); }
.phero-note p { font-size: var(--t-sm); line-height: 1.6; }

/* The drawing's frame. A fixed aspect so a family reads the same from page to
   page whatever the copy length, and the lattice ground the whole site uses. */
/* The drawing sits on its own surface, and that surface is the full height of
   the panel. It was a fixed 640/500 box centred in whatever height the copy
   produced, which on a long page left 310px of empty navy above the drawing
   and 310px below it -- a thin wire graphic adrift in a rectangle. A defined
   area at panel height is a composition at any copy length. */
.phero-art {
  position: relative; z-index: 1;
  min-width: 0;
  align-self: stretch;
  min-height: 300px;
  display: grid; place-items: center;
  padding: clamp(12px, 1.6vw, 26px);
  border-radius: var(--radius-panel);
  background: var(--phero-art-bg);
  border: 1px solid var(--phero-art-edge);
}
.phero-art > .m { width: 100%; max-height: 100%; display: block; }
.phero-service, .phero-portfolio {
  --phero-art-bg: rgba(140, 180, 230, 0.035);
  --phero-art-edge: rgba(140, 180, 230, 0.12);
}
.phero-industry { --phero-art-bg: rgba(255, 255, 255, 0.62); --phero-art-edge: var(--surface-edge); }
.phero-company  { --phero-art-bg: rgba(255, 255, 255, 0.46); --phero-art-edge: var(--sand-2); }

/* --- the grounds ---------------------------------------------------------
   Each family sets its own ground and its own drawing palette in one place.
   A motif never knows which family it is in. */
.phero-service .phero-panel,
.phero-portfolio .phero-panel { background: var(--navy); }
.phero-industry .phero-panel {
  background: var(--paper-2);
  border: 1px solid var(--surface-edge);
  box-shadow: var(--lift-1);
}
.phero-company .phero-panel {
  background: var(--sand);
  border: 1px solid var(--sand-2);
  box-shadow: var(--lift-1);
}

/* The same lattice as the homepage field, at a fifth of the presence, masked
   to the drawing's side. It is what makes an internal panel feel like part of
   the same site rather than a flat rectangle with a picture in it. */
.phero-panel::before {
  content: ""; position: absolute; inset: 0; z-index: 0; pointer-events: none;
  background-image:
    linear-gradient(90deg, var(--phero-grid-line) 1px, transparent 1px),
    linear-gradient(180deg, var(--phero-grid-line) 1px, transparent 1px);
  background-size: 46px 46px;
  -webkit-mask-image: linear-gradient(90deg, transparent 30%, #000 78%);
  mask-image: linear-gradient(90deg, transparent 30%, #000 78%);
}
.phero-service .phero-panel, .phero-portfolio .phero-panel {
  --phero-grid-line: rgba(120, 170, 230, 0.10);
}
.phero-industry .phero-panel { --phero-grid-line: rgba(30, 70, 130, 0.07); }
.phero-company .phero-panel  { --phero-grid-line: rgba(90, 70, 45, 0.06); }

/* --- the drawing palette, per ground ------------------------------------- */
.phero-service, .phero-portfolio {
  --phero-plane-solid: #0B1A2E;
  --m-stroke: rgba(155, 195, 240, 0.46);
  --m-hair:   rgba(155, 195, 240, 0.28);
  --m-fill:   rgba(155, 195, 240, 0.14);
  --m-bar:    rgba(185, 215, 248, 0.36);
  --m-key:    var(--blue-lift);
  --m-key-fill: rgba(42, 114, 220, 0.24);
  --m-hot:    var(--cyan);
}
.phero-industry {
  --phero-plane-solid: #FFFFFF;
  --m-stroke: rgba(20, 55, 105, 0.28);
  --m-hair:   rgba(20, 55, 105, 0.16);
  --m-fill:   rgba(20, 55, 105, 0.07);
  --m-bar:    rgba(20, 55, 105, 0.22);
  --m-key:    var(--blue);
  --m-key-fill: rgba(14, 97, 209, 0.14);
  --m-hot:    var(--blue-deep);
}
.phero-company {
  --phero-plane-solid: #FBFAF8;
  --m-stroke: rgba(70, 58, 42, 0.30);
  --m-hair:   rgba(70, 58, 42, 0.17);
  --m-fill:   rgba(70, 58, 42, 0.08);
  --m-bar:    rgba(60, 50, 38, 0.26);
  --m-key:    var(--blue);
  --m-key-fill: rgba(14, 97, 209, 0.16);
  --m-hot:    var(--blue-deep);
}

/* --- the drawing vocabulary ---------------------------------------------
   Fourteen diagrams, twenty-one classes. Every motif is built from these, so
   a new drawing needs no new CSS. */
.m-frame  { stroke: var(--m-stroke); stroke-width: 1.5; }
.m-frame-key { stroke: var(--m-key); stroke-width: 1.5; }
.m-plane  { stroke: var(--m-stroke); stroke-width: 1.5; fill: var(--m-fill); }
.m-plane-top { stroke: var(--m-key); fill: var(--m-key-fill); }
.m-line, .m-tick, .m-ring { stroke: var(--m-hair); stroke-width: 1; }
/* 2px. A 1px tick renders at 0.58px at 1024 and is simply not there. */
.m-tick2 { stroke: var(--m-hair); stroke-width: 2; }
.m-frame-hot { stroke: var(--m-hot); stroke-width: 1.5; }
.m-hot-ring { fill: none; stroke: var(--m-hot); stroke-width: 2; }
/* The front window in a two-window drawing is FILLED, or the back window's
   content shows straight through it and the two read as one muddle. */
.m-plane-solid { fill: var(--phero-plane-solid); }
.m-edge   { stroke: var(--m-key); stroke-width: 1.5; }
.m-split  { stroke: var(--m-hot); stroke-width: 2; }
.m-scanline { stroke: var(--m-hot); stroke-width: 2; }
.m-fill, .m-track, .m-row, .m-tile { fill: var(--m-fill); }
.m-fill-key, .m-row-key { fill: var(--m-key-fill); }
.m-bar, .m-dot, .m-cell, .m-off { fill: var(--m-bar); }
.m-bar-off { fill: var(--m-bar); }
.m-bar-key { fill: var(--m-key); }

.m-key { fill: var(--m-key); }
.m-cell-hot, .m-hot-fill, .m-hot-dot { fill: var(--m-hot); }
/* The broken half of the repair diagram is drawn a hair off the lattice. It
   is the only place on the site where something is deliberately misaligned,
   and it is misaligned because that is the subject. */
.m-repair .m-bar-off { transform: translateX(6px); }
.m-repair .m-bar-off:nth-of-type(even) { transform: translateX(-4px); }

/* --- the motion. One thing per drawing, and it is always the subject. ----- */
@media (prefers-reduced-motion: no-preference) {
  .m-scan     { animation: mScan 11s var(--e-inout) infinite alternate; }
  .m-ping-cell{ animation: mPing 5s var(--e-inout) infinite; }
  .m-fillbar  { animation: mFill 9s var(--e-inout) infinite alternate; }
  /* The highlight MOVES between positions rather than teleporting. V5.13
     first used steps(1, end), so a rank change happened in zero frames and
     read as a rendering glitch. */
  .m-rank     { animation: mRank 14s var(--e-inout) infinite; }
  .m-ping     { animation: mPing 6s var(--e-inout) infinite; }
  .m-packet   { animation: mPacket 8s var(--e-inout) infinite alternate; }
  .m-packet-lg{ animation: mPacketLg 11s var(--e-inout) infinite alternate; }
  .m-packet-sm{ animation: mPacketSm 9s var(--e-inout) infinite alternate; }
  .m-cta      { animation: mPing 7s var(--e-inout) infinite; }
  /* The era drawing's baseline draws once, on arrival, and then it is simply
     a line. A twenty-seven year timeline that loops would be a gimmick. */
  .m-nowline  { transform-origin: left center;
                transition: transform var(--m-demo) var(--e-out) 300ms; }
  .phero[data-anim="wait"] .m-nowline { transform: scaleX(0.02); }
  /* Nothing runs while it cannot be seen: the same class the homepage field
     uses, applied by the same observer. */
  .is-still .m-scan, .is-still .m-fillbar, .is-still .m-rank,
  .is-still .m-ping, .is-still .m-ping-cell, .is-still .m-packet,
  .is-still .m-cta { animation-play-state: paused; }
}
/* Rest states, declared OUTSIDE the motion query, so a reduced-motion visitor
   gets a finished composition rather than a paused one. The scan line parked
   at translateY(0) read as a stray cyan rule stuck to the top of the box; the
   packet parked outside the left-hand system, touching nothing. */
.m-scan   { transform: translateY(100px); }
.m-packet { transform: translateX(80px); }
.m-packet-lg { transform: translateX(120px); }
.m-packet-sm { transform: translateY(30px); }
.m-rank   { transform: translateY(68px); }
.m-fillbar { transform-origin: 60px center; transform: scaleX(0.72); }
.m-ping, .m-ping-cell, .m-cta { opacity: 1; }
@keyframes mScan   { from { transform: translateY(0); } to { transform: translateY(200px); } }
@keyframes mFill   { from { transform: scaleX(0.16); } to { transform: scaleX(1); } }
@keyframes mRank   { 0%, 20% { transform: translateY(68px); }
                     30%, 50% { transform: translateY(0); }
                     60%, 80% { transform: translateY(136px); }
                     90%, 100% { transform: translateY(68px); } }
@keyframes mPing   { 0%, 100% { opacity: 0.35; } 50% { opacity: 1; } }
/* The packet crosses the WHOLE bridge. It used to stop half way, inside the
   middle box, on a diagram that promises three systems talking. */
@keyframes mPacketLg { from { transform: translateX(0); } to { transform: translateX(400px); } }
@keyframes mPacketSm { from { transform: translateY(0); } to { transform: translateY(62px); } }
@keyframes mPacket { from { transform: translateX(0); } to { transform: translateX(160px); } }

/* --- the entrance. Same settle-on-load the homepage hero uses. ----------- */
@media (prefers-reduced-motion: no-preference) {
  .phero[data-anim="wait"] .phero-copy > *:not(h1) {
    opacity: 0; transform: translateY(var(--m-rise));
  }
  .phero[data-anim] .phero-copy > *:not(h1) {
    transition: opacity var(--m-enter) var(--e-out),
                transform var(--m-enter) var(--e-out);
  }
  .phero[data-anim="run"] .phero-copy > .lead { transition-delay: 70ms; }
  .phero[data-anim="run"] .phero-act { transition-delay: 140ms; }
  .phero[data-anim="run"] .phero-note { transition-delay: 210ms; }
  /* The drawing arrives last and only fades. It is the supporting element and
     it should never be the first thing that moves. */
  .phero[data-anim="wait"] .phero-art { opacity: 0; }
  .phero[data-anim] .phero-art { transition: opacity var(--m-scene) var(--e-out) 200ms; }
}

/* --- the portfolio family: real screenshots, real frames ----------------- */
.phero-shots {
  position: relative;
  aspect-ratio: 640 / 500;
  display: block;
  /* min-height and aspect-ratio fight: .phero-art sets a 300px floor for the
     drawing surface, and an aspect-ratio then computes the WIDTH from it. At
     320px under 200% text zoom that made this element 500px wide inside a
     216px column. An explicit aspect owns its own sizing. */
  min-height: 0;
  padding: 0; border: 0; background: none;
}
.ws {
  position: absolute; display: block; overflow: clip;
  border-radius: 10px;
  border: 1px solid var(--line-dark-2);
  background: var(--navy-2);
  box-shadow: 0 20px 50px -24px rgba(0, 0, 0, 0.75);
}
/* One aspect for all three, cropped from the top: these are windows onto
   pages, not full-page captures, and three frames of different heights read
   as an accident. */
.ws img { display: block; width: 100%; height: auto; aspect-ratio: 16 / 10;
          object-fit: cover; object-position: 50% 0; }
.ws { padding-top: 22px; }
.ws-lead { left: 4%; right: 4%; top: 14%; z-index: 3; }
.ws .ws-bar {
  position: absolute; inset: 0 0 auto 0; height: 22px;
  border-bottom: 1px solid var(--line-dark-2);
  display: flex; align-items: center; gap: 5px; padding-left: 10px;
}
.ws .ws-bar i { width: 5px; height: 5px; background: var(--snow-4); opacity: 0.5; }
.ws-b1 { left: 16%; right: 22%; top: 2%;  z-index: 2; }
.ws-b2 { left: 30%; right: 0;   bottom: 2%; z-index: 1; }
/* The two back frames used to drift 7.8px over 26 and 34 seconds. That is
   0.3px a second on the heaviest images on the page: code with no output. */

/* --- the editorial tier -------------------------------------------------
   The intake forms, the legal pages, 404 and every article. No panel, no
   drawing: one hairline lattice fading out under the headline, and the same
   settle-on-load entrance the other four families use, so the tier is
   recognisably part of the system without pretending to be a production.

   This is the half of the system that makes the other half work. If a privacy
   policy opened with a diagram, nothing on the site would mean anything. */
.phero-editorial { padding-block: clamp(28px, 4vw, 60px) clamp(20px, 3vw, 44px); }
.phero-editorial .phero-copy { max-width: var(--track-text); }
.phero-editorial h1 { max-width: 18ch; }
.phero-editorial .lead { max-width: 56ch; }
/* The quiet tier gets the same lattice as the panel families, at the same
   pitch, faded out downward. It is what makes "no drawing" read as a register
   rather than as an unfinished page. */
.phero-editorial { position: relative; isolation: isolate; }
.phero-editorial > * { position: relative; z-index: 1; }
.phero-editorial::before {
  content: ""; position: absolute; inset: 0; z-index: 0; pointer-events: none;
  left: 50%; transform: translateX(-50%);
  width: min(100%, calc(var(--track) + var(--gutter) * 2));
  background-image:
    linear-gradient(90deg, rgba(30, 70, 130, 0.055) 1px, transparent 1px),
    linear-gradient(180deg, rgba(30, 70, 130, 0.045) 1px, transparent 1px);
  background-size: 46px 46px;
  -webkit-mask-image: linear-gradient(180deg, transparent 0%, #000 22%, transparent 92%);
  mask-image: linear-gradient(180deg, transparent 0%, #000 22%, transparent 92%);
}

.phead, .art-head { position: relative; isolation: isolate; }
.phead > *, .art-head > * { position: relative; z-index: 1; }
.phead::before, .art-head::before {
  content: ""; position: absolute; z-index: 0; pointer-events: none;
  /* Inside the 1280 column the rest of the site obeys, and faded at the top
     edge. It shipped full-bleed with a hard horizontal cut 14px under the
     breadcrumb, which is the one place on the site where a background did not
     belong to the grid. */
  inset: 0;
  left: 50%; transform: translateX(-50%);
  width: min(100%, calc(var(--track) + var(--gutter) * 2));
  background-image:
    linear-gradient(90deg, rgba(30, 70, 130, 0.055) 1px, transparent 1px),
    linear-gradient(180deg, rgba(30, 70, 130, 0.045) 1px, transparent 1px);
  background-size: 46px 46px;
  -webkit-mask-image: linear-gradient(180deg, transparent 0%, #000 22%, transparent 92%);
  mask-image: linear-gradient(180deg, transparent 0%, #000 22%, transparent 92%);
}
@media (prefers-reduced-motion: no-preference) {
  .phead[data-anim="wait"] h1, .art-head[data-anim="wait"] h1,
  .phead[data-anim="wait"] .lead, .art-head[data-anim="wait"] .lead,
  .phead[data-anim="wait"] .phead-act, .phead[data-anim="wait"] .phead-side,
  .art-head[data-anim="wait"] .art-meta {
    opacity: 0; transform: translateY(var(--m-rise));
  }
  .phead[data-anim] h1, .art-head[data-anim] h1,
  .phead[data-anim] .lead, .art-head[data-anim] .lead,
  .phead[data-anim] .phead-act, .phead[data-anim] .phead-side,
  .art-head[data-anim] .art-meta {
    transition: opacity var(--m-enter) var(--e-out),
                transform var(--m-enter) var(--e-out);
  }
  .phead[data-anim="run"] .lead, .art-head[data-anim="run"] .lead { transition-delay: 70ms; }
  .phead[data-anim="run"] .phead-act { transition-delay: 140ms; }
  .phead[data-anim="run"] .phead-side,
  .art-head[data-anim="run"] .art-meta { transition-delay: 210ms; }
}

/* --- responsive ----------------------------------------------------------
   The drawing does not shrink to a stamp, it moves under the copy at a wider
   aspect and then leaves entirely on a phone. A diagram at 320px is a smudge,
   and a smudge is worse than nothing. */
/* `em`, not `px`. A two-column hero at 200% text zoom needs 1630px of minimum
   content width inside a 1165px panel, at every viewport, and the panel clips.
   An em breakpoint tracks the reader's own text size -- 976px at the default
   root, 1952px at 200% -- so the hero collapses for the reader who needs it to
   and nobody else. Same rule the header, the lanes and the related-links grid
   already follow. */
@media (max-width: 61em) {
  .phero-grid { grid-template-columns: minmax(0, 1fr); gap: clamp(24px, 4vw, 40px); }
  .phero-copy h1, .phero-copy .lead { max-width: 30ch; }
  .phero-copy .lead { max-width: 52ch; }
  .phero-art, .phero-shots { aspect-ratio: 16 / 9; }
  .phero-panel::before {
    -webkit-mask-image: linear-gradient(180deg, transparent 30%, #000 82%);
    mask-image: linear-gradient(180deg, transparent 30%, #000 82%);
  }
}
@media (max-width: 39em) {
  .phero-panel { border-radius: var(--radius-panel); padding: var(--s9) var(--s7); }
  .phero-art { display: none; }
  .phero-shots { display: block; aspect-ratio: 5 / 3; }
  .ws-b2 { display: none; }
}
/* V5.22: at 320 the panel's own 32px of inline padding sat on top of the
   wrap's 20px, so a headline had 216px of a 320px screen -- 67.5% -- to set
   in. Halving it at the two narrowest widths returns 32px to the line without
   touching the panel anywhere it has room. */
@media (max-width: 27em) {
  .phero-panel { padding-inline: var(--s5); }
}

/* ==========================================================================
   4. THE MOVEMENT

   The second axis. Every homepage movement hangs its eyebrow and its keyline
   in a narrow left column and starts the headline on a second axis further in.
   V5.9.5 began five consecutive sections with a left-aligned headline at the
   same x and the same size, which is why the page read as a stack rather than
   as a composition: there was only ever one axis to read along.

   Below 900px the label column lifts above the content and the keyline stays
   with it, so the device survives on a phone instead of collapsing to nothing.
   ========================================================================== */
.mvt {
  display: grid;
  grid-template-columns: minmax(0, 0.26fr) minmax(0, 1fr);
  gap: clamp(24px, 3.4vw, 64px);
  align-items: start;
}
.mvt-label { position: sticky; top: calc(var(--hdr-h) + var(--s8)); }
/* A movement that continues after a full-bleed break keeps the same second
   axis without repeating its label. */
.mvt-body-2 { grid-column: 2; }
@media (max-width: 56.25em) { .mvt-body-2 { grid-column: 1; } }
.mvt-label .eyebrow { margin-bottom: var(--s5); }
.mvt-label p { font-size: var(--t-sm); color: var(--text-faint); max-width: 24ch; }
.mvt-body > * + * { margin-top: var(--s8); }
@media (max-width: 56.25em) { .mvt-body > * + * { margin-top: var(--s6); } }
.mvt-head { max-width: 16ch; }
/* A second heading inside a movement. Used once, over the two published
   plans in the services movement, so they read as a named thing rather than
   as a fifth lane. */
.mvt-sub { max-width: 22ch; }
.mvt-lead { max-width: 46ch; margin-top: var(--s6) !important; }

@media (max-width: 900px) {
  .mvt { grid-template-columns: minmax(0, 1fr); gap: var(--s7); }
  .mvt-label { position: static; }
}

/* Section rhythm is set by the movement's job, not by one global value.
   V5.9.5 wrapped every section in an identical 144px frame while the content
   inside ranged 488px to 1633px, which flattened the music. */
/* These were clamp(88,11vw,176) / clamp(72,9vw,144) / clamp(56,7vw,104), which
   at 1440 resolved to 158 / 130 / 101 and at 390 to 88 / 72 / 56 — three values
   inside a 57px band, so the page still read as one metronome. The spread is
   real now: the proof movement gets nearly twice the air the quiet one does. */
.sec-proof  { padding-block: clamp(52px, 12vw, 184px); }
.sec-quiet  { padding-block: clamp(36px, 5vw, 84px); }
.sec-band   { padding-block: clamp(44px, 6vw, 96px); }

/* V5.11: a dark section is a contained panel now, not a full-bleed slab. Every
   coloured surface in the body follows the same grammar -- navy hero, navy
   feature, blue closer, sand movement, all objects on a light page. The only
   full-bleed grounds left are white, soft gray and the footer. */
.darkpanel {
  position: relative;
  isolation: isolate;
  border-radius: var(--radius-feature);
  overflow: clip;
  padding: clamp(40px, 5vw, 84px) clamp(28px, 4vw, 72px);
  box-shadow: var(--panel-lift);
}
@media (max-width: 600px) {
  .darkpanel { border-radius: var(--radius-panel); padding: var(--s9) var(--s7); }
}


/* ==========================================================================
   5. THE FOUR LANES

   The four words that are the company. In V5.9.5 they were set at 30px, which
   was 1.4x the 21px paragraph beside them and smaller than every section
   heading on the page. They take the new 34px step now and they carry the
   weight.

   The tag lists under each lane are gone. /services/ and the mobile nav both
   own that architecture; repeating six terms here bought nothing and cost the
   row its shape.
   ========================================================================== */
/* V5.11: one panel, four rows. Boxing each lane would be four identical tiles,
   which is the card grid this release exists to avoid -- but leaving four bare
   rows on white made the section that names the entire company the flattest
   thing on the page. The GROUP is the surface; the rows keep their hairlines
   and their hover keyline. */
.lanes {
  border: 1px solid var(--surface-edge);
  border-radius: var(--radius-panel);
  background: var(--bg);
  padding-inline: clamp(20px, 2.6vw, 40px);
}
.lane {
  position: relative;
  border-bottom: 1px solid var(--rule);
}
.lane:last-child { border-bottom: 0; }
.lane-in {
  display: grid;
  /* Wide enough that "AI & Automation" sets on one line at 34px. It is one of
     the four words the whole company is described in; it does not wrap. */
  /* A FIXED third column. It was `auto`, so "Build services", "Support
     services", "Growth services" and "All services" each sized their own
     track and pushed the description column to four different left edges --
     a 17px rag down the most prominent panel on the homepage. */
  /* V5.20: a floor on the first track and a cap on the gap. Both were
     `vw`-driven after .wrap had already capped at 1280, so the headline
     column got NARROWER as the viewport got wider -- 263px at 1440, 250px at
     1920 -- and "AI & Automation" wrapped at every desktop width, which is
     the one thing the comment above says it must not do. The 150px third
     track was 3px short of "Support services" at every width, so the arrow
     glyph painted into the panel's right padding. */
  grid-template-columns: minmax(280px, 0.62fr) minmax(0, 1fr) 156px;
  gap: clamp(16px, 2.2vw, 32px);
  align-items: baseline;
  padding: clamp(22px, 2.6vw, 34px) 0;
}
.lane::after {
  content: ""; position: absolute; left: 0; bottom: -1px; height: 2px; width: 100%;
  background: var(--keyline);
  transform: scaleX(0); transform-origin: left center;
  transition: transform var(--m-panel) var(--e-inout);
}
.lane:hover::after, .lane:focus-within::after { transform: scaleX(1); }
.lane h3 {
  font-size: var(--t-h3); line-height: var(--lh-h3);
  color: var(--text); margin: 0;
}
.lane h3 a { color: inherit; }
.lane h3 a::after { content: ""; position: absolute; inset: 0; }
.lane:hover h3 a { color: var(--link); }
.lane p {
  font-size: var(--t-lead); color: var(--text-dim); line-height: 1.45;
  max-width: 40ch; margin: 0;
}
/* The words people actually type. V5.10 first cut these on the grounds that
   /services/ owns the architecture — but the architecture was not the point:
   this line is the homepage's only answer to "can they fix MY problem", and
   "malware", "migration" and "checkout" are what a visitor is scanning for. */
.lane-ex {
  display: block; margin-top: var(--s3);
  font-family: var(--font-mono); font-size: var(--t-mono);
  letter-spacing: var(--tr-mono); text-transform: uppercase;
  color: var(--text-faint); word-spacing: var(--ws-caps);
}
/* V5.22: this was `white-space: nowrap` in a fixed 156px column, so at 150%
   text zoom and above "Support services" needed 206px and at 200% it needed
   264px -- 41% of the label ran off the panel edge. body{overflow: hidden auto}
   meant there was no way to scroll to the cut-off text either, which is a
   WCAG 1.4.4 failure rather than a cosmetic one. Two short words are allowed
   to wrap; the arrow glyph stays with the last word via the pseudo-element,
   so nothing is orphaned. (.workcap .arrow keeps nowrap: it does not overflow
   and it sits on one line by design.) */
.lane .arrow { white-space: normal; }
/* `em`, not `px`: at 200% text zoom the four lane rows still had three tracks
   at 1024 and 1280 and the arrow column overflowed the panel. An em breakpoint
   tracks the reader's text size, which is what the header breakpoints already
   do for the same reason. 56em is 896px at the default root. */
@media (max-width: 56em) {
  .lane-in { grid-template-columns: minmax(0, 1fr); gap: var(--s2); align-items: start;
             padding: var(--s5) 0; }
  .lane p { font-size: var(--t-body); }
  /* The affordance moves INTO the heading rather than costing a fourth line.
     Hiding the arrow outright (as V5.10 first did) left four black headings
     with nothing to say they were tappable; giving it a row of its own cost
     40px x 4 on the page that could least afford it. */
  .lane .arrow { display: none; }
  .lane h3 a { display: inline-block; padding-block: 8px; }
  .lane h3 a::before {
    content: ""; float: right; width: 15px; height: 8px; margin: 0.55em 0 0 10px;
    background: currentColor; color: var(--link);
    -webkit-mask: var(--arrow-mask) no-repeat center / contain;
    mask: var(--arrow-mask) no-repeat center / contain;
  }
}


/* ==========================================================================
   6. THE WORK

   Oogle builds websites, so the websites are the argument. V5.9.5 put three of
   them behind a bordered plate with a caption bar under it — a frame around
   things that already have frames — and did not reach the first one until 33%
   of the way down the page.

   Now the first project is a full-bleed image with its caption hanging in the
   margin, two more sit in-track beneath it, and the remaining six are a ruled
   list of names. No plates. No caption bars. The screenshots carry the colour
   for this whole movement, which is why the section around them stays white.
   ========================================================================== */
.workfeat { position: relative; display: block; }
/* V5.11: contained. V5.10 emitted this at section level so the image touched
   both edges of the screen, then had to set `border-radius: 0` on it to stop
   the corners looking wrong at 100vw — a square-cornered full-width picture is
   a slab, not a designed object. It sits in the track now, at the feature
   radius, with the depth the rest of the system uses. */
.workfeat-lead { display: block; margin-block: clamp(28px, 3.5vw, 56px) clamp(20px, 2.5vw, 36px); }
/* The overflow-x: clip that used to be here existed only to contain the lead
   screenshot's 100vw bleed. The lead is in the track now, nothing in this
   section extends past it, and the clip was cutting 25px of real content at
   320px under 200% text zoom. */
.workfeat-lead .workfeat-media { border-radius: var(--radius-feature); box-shadow: var(--panel-lift); }
.workfeat-lead .wrap { display: block; }
.workfeat-lead .workcap { margin-top: var(--s5); }
.workfeat-media {
  border: 1px solid var(--rule);
  display: block; overflow: hidden;
  border-radius: var(--radius-card);
  background: none;
}
.workfeat-media img {
  display: block; width: 100%; height: auto;
  /* The captures are 1600x1000 and their bottom edge cuts a section in half.
     Cropping from the top shows the part of a home page that argues for
     itself — the masthead and the first screen — and drops the seam. */
  aspect-ratio: 16 / 10; object-fit: cover; object-position: 50% 0%;
  transition: transform var(--m-panel) var(--e-inout);
}
.workfeat-lead .workfeat-media img { aspect-ratio: 2 / 1; }
/* Native ratio: a 3/2 crop cut 6% off the bottom, which landed exactly
   through the x-height of a line of type in two of the three captures. */
.worktwo .workfeat-media img { aspect-ratio: 16 / 10; }
.workfeat:hover .workfeat-media img { transform: scale(1.014); }

.workcap {
  display: grid;
  /* minmax(0, auto) on the trailing track, not auto: an `auto` track cannot
     shrink below its content, so at 200% text zoom the domain link pushed the
     caption past the viewport. This lets it wrap instead. */
  grid-template-columns: auto minmax(0, 1fr) minmax(0, auto);
  gap: var(--s5);
  align-items: baseline;
  min-width: 0;
  margin-top: var(--s6);
  padding-top: var(--s5);
  border-top: 1px solid var(--rule);
}
.workcap-n {
  font-family: var(--font-mono); font-size: var(--t-mono); font-weight: 500;
  letter-spacing: var(--tr-mono); color: var(--mark);
}
/* The name was inheriting the caption's 1.65 line-height and 100% width, so
   a 32px name sat 3px off a 15px span. It takes the display recipe like every
   other heading-scale string on the page. */
.workcap b {
  font-size: var(--t-h3); font-weight: var(--w-black);
  letter-spacing: var(--tr-display); font-stretch: var(--wd-display);
  line-height: var(--lh-h3); color: var(--text);
  margin-right: 0.4em;
}
.workcap span { font-size: var(--t-sm); color: var(--text-faint); }
.workcap > * { min-width: 0; overflow-wrap: break-word; }
.workcap .arrow { white-space: nowrap; }

/* Two supporting projects, in-track. */
.worktwo {
  display: grid; grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: clamp(22px, 3vw, 48px);
  margin-top: clamp(32px, 4vw, 64px);
}
.worktwo .workcap { grid-template-columns: auto minmax(0, 1fr); }
.worktwo .workcap b { font-size: var(--t-h4); }

/* The rest, as names. A list of clients is proof; six more thumbnails are
   inventory. */
/* Six names as six full-width rows was 668px of near-empty page immediately
   after the last screenshot — the longest dead stretch on the site. Two
   columns halve it and give the list a shape. */
.worklist {
  margin-top: clamp(32px, 4vw, 56px);
  display: grid; grid-template-columns: repeat(2, minmax(0, 1fr));
  column-gap: clamp(24px, 3vw, 56px);
  border-top: 1px solid var(--rule);
}
.worklist-row { display: block; border-bottom: 1px solid var(--rule); }
.worklist-in {
  display: grid; grid-template-columns: auto minmax(0, 1fr) minmax(0, auto);
  gap: var(--s5); align-items: baseline;
  padding: var(--s4) 0;
}
.worklist-row i { overflow-wrap: anywhere; }
.worklist-row b { font-size: var(--t-h4); font-weight: var(--w-black);
                  letter-spacing: var(--tr-display); color: var(--text); }
.worklist-row:hover b { color: var(--link); }
.worklist-in > span, .worklist-in > i {
  font-family: var(--font-mono); font-size: var(--t-mono); font-style: normal;
  letter-spacing: var(--tr-mono); text-transform: uppercase; color: var(--text-faint);
}
@media (max-width: 43.75em) {
  .worktwo { grid-template-columns: minmax(0, 1fr); gap: 0; }
  .worklist-in { grid-template-columns: auto minmax(0, 1fr); gap: var(--s4);
                 padding: var(--s3) 0; }
  .workcap { grid-template-columns: auto minmax(0, 1fr); }
  /* Full row, not the second track. At 200% text zoom a domain like
     "semarineinc.com" is 273px of unbreakable text and the second track is
     228px, which was the last horizontal overflow on the site. */
  .workcap .arrow { grid-column: 1 / -1; }
  .workcap b { display: block; }
  .worklist-row i { display: none; }
  /* A 1600px desktop capture shown 350px wide is not evidence, it is a
     texture. On a phone ONE screenshot carries the argument at a size where
     the work is actually legible, and the two supporting projects become
     named rows like the six below them. Nothing is hidden: every link, name
     and platform survives, only the unreadable thumbnails go. */
  .workfeat-lead .workfeat-media img { aspect-ratio: 16 / 10; }
  .worktwo .workfeat-media { display: none; }
  .worktwo .workfeat { margin: 0; }
  .worktwo .workfeat { border-bottom: 1px solid var(--rule); }
  .worktwo .workfeat:first-child { border-top: 1px solid var(--rule); }
  .worktwo { gap: 0; margin-top: var(--s8); }
  .worktwo .workcap {
    margin-top: 0; padding: var(--s4) 0; border-top: 0;
    grid-template-columns: auto minmax(0, 1fr);
    padding: var(--s3) 0;
  }
  .worklist { margin-top: 0; border-top: 0; grid-template-columns: minmax(0, 1fr); }
  .workcap { margin-top: var(--s4); padding-top: var(--s4); gap: var(--s3); }
}


/* ==========================================================================
   7. THE STACK ILLUSTRATION

   The movement used to carry a wireframe "before and after" of a page whose
   stylesheet had stopped loading. It was drawn in CSS, it was not a real
   client site, and a fabricated transformation is a claim this site does not
   get to make. It is gone -- markup, stylesheet and its IntersectionObserver
   module in main.js with it.

   What sits here now is a supplied raster illustration of the stack the
   movement actually claims: design, development, hosting, email, DNS and
   security answered by one team. It carries its own alpha channel, so it
   goes STRAIGHT ONTO THE PANEL -- no frame, no card, no plate, no white
   rectangle. The artwork is the visual.

   The glow behind it is the one thing this stylesheet draws. The supplied
   file was cut to a hard alpha mask, which took the halo off with the
   background, and without it the artwork reads as pasted on rather than lit
   by the panel. It is a radial gradient with no edge anywhere in it, so it
   cannot be mistaken for a background box.
   ========================================================================== */
.split-art-fig {
  max-width: 560px;
  margin-inline: auto;
  /* The glow is painted BY the figure, not by a pseudo-element inset outside
     it. The first version used ::before at inset: -14% -11%, which looked
     right on a wide panel and, once the figure filled the whole column at
     600px, put 54px of itself outside .darkpanel -- and .darkpanel is
     overflow: clip, so audit.py correctly reported the panel as cropping its
     own content at 200% text zoom. A background reaches full transparency at
     74% of a 58%-wide radius, so it finishes well inside the box and there is
     nothing to overflow at any width. */
  background: radial-gradient(58% 52% at 52% 47%,
              rgba(74, 160, 235, 0.15) 0%,
              rgba(74, 160, 235, 0.055) 46%,
              transparent 74%);
}
/* width and height are on the <img> itself, so the box is reserved before a
   byte of the file arrives and the movement never shifts under the reader. */
.split-art-fig img { display: block; width: 100%; height: auto; }

/* the three beats beside the illustration */
/* Parity is not a design constraint. The roster grid is two up; with an odd
   number of names the last row left an empty cell and a rule that stopped
   halfway. It spans instead. */
.worklist-row:last-child:nth-child(odd) { grid-column: 1 / -1; }

.beats { list-style: none; margin: 0; padding: 0; }
.beats li {
  display: grid; grid-template-columns: auto minmax(0, 1fr); gap: var(--s4);
  padding: var(--s5) 0; border-top: 1px solid var(--rule);
}
.beats li:last-child { border-bottom: 1px solid var(--rule); }
.beats i {
  font-family: var(--font-mono); font-style: normal; font-size: var(--t-mono);
  letter-spacing: var(--tr-mono); color: var(--mark); line-height: 1.7;
}
.beats b { font-weight: var(--w-bold); color: var(--text); }


/* ==========================================================================
   7b. SINCE 1999

   Longevity, without a timeline. One numeral at display scale, the eras as a
   single ruled line of mono, and the person who has been running it the whole
   time. The warm ground is the only place on the site that is not cool, which
   is what makes this the human movement rather than another band.
   ========================================================================== */
.sandfield { background: var(--sand); }
/* V5.11: the warm ground is a contained panel rather than a full-width stripe.
   The one place on the site that is not cool should be a THING you arrive at,
   not a band the page passes through. */
.era-panel {
  border-radius: var(--radius-feature);
  border: 1px solid var(--sand-2);
  padding: clamp(36px, 4.6vw, 80px);
  overflow: clip;
}
@media (max-width: 600px) { .era-panel { border-radius: var(--radius-panel); padding: var(--s8) var(--s7); } }
.sandfield .lead, .sandfield p { color: var(--ink-3); }

.era-in {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(0, 0.72fr);
  gap: clamp(32px, 5vw, 88px);
  align-items: center;
}
.era-year {
  display: block;
  /* The vw cap is not styling, it is a floor guard: at 200% text zoom the rem
     minimum resolves to 160px, which is wider than the panel it now sits in at
     390 and 430. min() keeps the normal rendering identical (21vw exceeds the
     clamp at every width the site is read at) and only bites under zoom. */
  font-size: min(clamp(5rem, 3.1rem + 8.4vw, 11rem), 21vw);
  font-weight: var(--w-black); letter-spacing: -0.055em; line-height: 0.82;
  font-stretch: var(--wd-display);
  color: var(--ink); font-variant-numeric: tabular-nums;
}
.era-since {
  display: block; margin-bottom: var(--s3);
  font-family: var(--font-mono); font-size: var(--t-mono);
  letter-spacing: var(--tr-mono); text-transform: uppercase; color: var(--blue);
}
.era-copy { max-width: 44ch; margin-top: var(--s6) !important; }
/* The link is its own line rather than trailing the last sentence. The
   paragraph now ends on a deliberate beat ("Taking care of our clients
   doesn't."), and a link sharing that line takes the weight off it. */
.era-more { margin-top: var(--s5); }
.era-list {
  display: flex; flex-wrap: wrap; gap: var(--s2) var(--s5);
  list-style: none; margin: var(--s7) 0 0; padding: var(--s5) 0 0;
  border-top: 1px solid var(--sand-2);
  font-family: var(--font-mono); font-size: var(--t-mono);
  letter-spacing: var(--tr-mono); text-transform: uppercase; color: var(--ink-4);
}
.era-portrait {
  border-radius: var(--radius-card); overflow: hidden; background: var(--sand-2);
}
.era-portrait img { display: block; width: 100%; height: auto; }
.era-name {
  margin-top: var(--s4); font-size: var(--t-sm); color: var(--ink-4);
}
.era-name b { color: var(--ink-2); font-weight: var(--w-bold); }
@media (max-width: 900px) {
  .era-in { grid-template-columns: minmax(0, 1fr); gap: var(--s7); }
  .era-portrait { max-width: 260px; }
  .era-copy { margin-top: var(--s5) !important; }
  .era-list { margin-top: var(--s5); }
}


/* ==========================================================================
   8. EDITORIAL PLATES
   Article cards need an image and there are no photographs. The plate is the
   pixel field on one of three grounds — enough to give a row of cards a beat,
   and it is not a diagram of anything.
   ========================================================================== */
.ed-art {
  position: relative; display: block; overflow: hidden;
  border: 1px solid var(--rule); border-radius: var(--radius-card);
  /* The base variant was --paper-3 with --blue pixels: near-white on a white
     page with a handful of faint marks in it, which read as a failed image
     load rather than as a plate. All three variants carry weight now. */
  background: var(--navy-3); color: var(--blue-light); border-color: transparent;
}
.ed-art > svg { position: absolute; inset: 0; width: 100%; height: 100%; opacity: 0.9; }
.ed-feature .ed-art { aspect-ratio: 3 / 2; }
.ed-card .ed-art { aspect-ratio: 16 / 9; margin-bottom: var(--s5); }
.ed-grid > *:nth-child(3n+2) .ed-art { background: var(--navy-2); color: var(--blue-light); border-color: transparent; }
.ed-grid > *:nth-child(3n+3) .ed-art { background: var(--blue); color: var(--navy); border-color: transparent; }
.ed-cat {
  display: block; font-family: var(--font-mono); font-size: var(--t-mono);
  letter-spacing: 0.14em; text-transform: uppercase; color: var(--mark);
  margin-bottom: var(--s3);
}


/* ==========================================================================
   9. MICROINTERACTIONS
   Hover should look designed for the element. No transform: scale anywhere
   except a photograph, where it is a crop rather than a bounce.
   ========================================================================== */
a.card { transition: border-color var(--d-state) var(--ease), background var(--d-state) var(--ease); }
a.card:hover, a.card:focus-visible {
  transform: none; box-shadow: none; background: var(--film);
  border-color: color-mix(in srgb, var(--mark) 35%, var(--rule));
}
a.card:focus-visible::before { transform: scaleY(1); }
a.card:hover .arrow::after { transform: translateX(4px); }

/* The header's links are .nav-lnk. The art direction pass wrote this rule
   against .nav-main, a class that does not exist anywhere on the site, so the
   navigation underline has never once appeared. Neither harness could catch
   it: a hover style that silently does nothing is invisible to a contrast
   check and to a behaviour assertion. */
.nav-lnk { position: relative; }
.nav-lnk::after {
  content: ""; position: absolute; left: 0; right: 100%; bottom: 2px;
  height: 1.5px; background: var(--blue);
  transition: right var(--d-state) var(--ease);
}
.nav-lnk:hover::after, .nav-lnk:focus-visible::after,
.nav-lnk[aria-current]::after, .nav-lnk[data-active]::after { right: 0; }

a.shot { transition: border-color var(--d-state) var(--ease), box-shadow var(--d-state) var(--ease); }
a.shot:hover, a.shot:focus-visible { transform: none; box-shadow: var(--lift-3); border-color: var(--line-2); }

.index a, .index-row { position: relative; }
.index a::before, .index-row::before {
  content: ""; position: absolute; left: 0; top: -1px; height: 2px; width: 100%;
  background: var(--mark); transform: scaleX(0); transform-origin: left center;
  transition: transform var(--d-state) var(--ease);
}
.index a:hover::before, .index a:focus-visible::before,
.index-row:hover::before, .index-row:focus-visible::before { transform: scaleX(1); }


/* ==========================================================================
   10. MOTION

   Two bugs shipped in V5.9.5 and both are fixed here.

   (a) @keyframes revealIn declared only a `to` frame, so its implicit `from`
       resolved from the element's BASE style — opacity 1, transform none. Every
       scroll reveal on the site interpolated 1 -> 1 and cut in over a single
       frame. Reveals are transitions now: a transition has no implicit-from
       hazard and cannot fail that way again.

   (b) .hero[data-anim] hid the headline, the lead, both buttons and the
       credential strip at opacity 0, and only JavaScript brought them back. With
       JavaScript off the entire hero was invisible, and the browser picked a
       phone number in the header as the LCP element. The hidden state is applied
       by JS now, exactly as the reveal system already did it.

   The amplitude rule: travel is >= 24px or the motion is deleted. V5.9.5 moved a
   104px headline by 12px and a 628px card by 14px, which is not a move, it is a
   fade wearing a transform. All distances come from --m-rise / --m-rise-lg, and
   prefers-reduced-motion collapses them to 0 in tokens.css — no component
   repeats that media query.

   Motion resolves toward order. Nothing bounces, floats, drifts or loops.
   ========================================================================== */
@media (prefers-reduced-motion: no-preference) {

  /* A block arrives ------------------------------------------------------- */
  [data-reveal] {
    transition: opacity var(--m-enter) var(--e-out),
                transform var(--m-enter) var(--e-out);
  }
  [data-reveal="wait"] { opacity: 0; transform: translateY(var(--m-rise-lg)); }

  /* A group's children arrive in sequence --------------------------------- */
  [data-rise] > * {
    transition: opacity var(--m-enter) var(--e-out),
                transform var(--m-enter) var(--e-out);
  }
  [data-rise="wait"] > * { opacity: 0; transform: translateY(var(--m-rise)); }
  [data-rise="in"] > *:nth-child(2) { transition-delay: calc(var(--m-step) * 1); }
  [data-rise="in"] > *:nth-child(3) { transition-delay: calc(var(--m-step) * 2); }
  [data-rise="in"] > *:nth-child(4) { transition-delay: calc(var(--m-step) * 3); }
  [data-rise="in"] > *:nth-child(5) { transition-delay: calc(var(--m-step) * 4); }
  [data-rise="in"] > *:nth-child(6) { transition-delay: calc(var(--m-step) * 5); }

  /* The keyline draws. The site's most repeated brand shape finally moves,
     and it moves the way a rule should: left to right, once. */
  [data-reveal] .eyebrow::before,
  [data-rise] .eyebrow::before {
    transition: transform var(--m-scene) var(--e-out) calc(var(--m-step) * 1);
  }
  [data-reveal="wait"] .eyebrow::before,
  [data-rise="wait"] .eyebrow::before { transform: scaleX(0); }

  /* A screenshot uncovers top to bottom, the way a page paints in. This
     replaces a 14px nudge on a 628px object, which nobody could see. */
  /* data-wipe is gone: the homepage feature is data-slide now, and a clip
     wipe on a full-track photograph read as the picture being uncovered
     rather than as the page it shows arriving. */

  /* The hero settles once on load. The hidden state is set by JS.

     The h1 is deliberately NOT in the stagger. It is the LCP element, and
     fading it in pushed Largest Contentful Paint from 144ms to 880ms on
     desktop for 620ms of decoration — the browser records LCP when the element
     reaches its final paint, so an entrance animation on the biggest thing on
     the page is a Core Web Vitals cost with nothing bought. The headline is
     simply there, and everything around it settles. */
  .hero[data-anim="wait"] .hero-copy > *:not(h1) {
    opacity: 0; transform: translateY(var(--m-rise));
  }
  .hero[data-anim] .hero-copy > *:not(h1) {
    transition: opacity var(--m-enter) var(--e-out),
                transform var(--m-enter) var(--e-out);
  }
  .hero[data-anim="run"] .hero-copy > .lead { transition-delay: 80ms; }
  .hero[data-anim="run"] .hero-cta { transition-delay: 160ms; }
  /* The field activates last and only fades: it is decoration, so it arrives
     after everything a visitor came to read. */
  .hero[data-anim="wait"] .hero-art { opacity: 0; }
  .hero[data-anim] .hero-art {
    transition: opacity var(--m-scene) var(--e-out) 240ms;
  }
}

/* --------------------------------------------------------------------------
   THE VOCABULARY (V5.12)

   Five named motions, each with one job. A page that fades everything up four
   times in a row has no motion design, it has a default.

     reveal   text arrives                  opacity + rise
     build    pixels, grids and interfaces   a clip that assembles in place
     slide    screenshots                    travel inside their own frame
     drift    background layers              slow, continuous, decorative only
     focus    hover                          one step, no travel

   reveal, build and slide RESOLVE: they play once and stop. drift is the only
   thing on the site allowed to loop, it is always decoration, it is always
   behind a mask or at single-digit alpha, and it is always the first thing
   `prefers-reduced-motion` switches off.
   -------------------------------------------------------------------------- */
@media (prefers-reduced-motion: no-preference) {

  /* --- build ------------------------------------------------------------
     The cell assembles from its leading edge rather than fading up: the whole
     surface arrives, border and divider included, left to right. Used where
     the subject is an interface or a set of facts being stated. */
  /* `:is(*, .m)` is a universal selector carrying class weight -- :is() takes
     the specificity of its most specific argument. Without it this rule is
     (0,1,0) and any component that declares its own transition on the same
     children beats it: `.facts li { transition: background-color }` is (0,1,1),
     so for one build the fact bar's own hover rule silently cancelled the only
     new motion in the vocabulary and the bar cut in rather than assembling.
     A component must be able to state its hover without deleting its entrance. */
  [data-build] > :is(*, .m) {
    transition: clip-path var(--m-scene) var(--e-snap),
                opacity var(--m-enter) var(--e-out),
                background-color var(--m-state) var(--e-inout);
  }
  /* Both ends explicit. `inset()` to `none` is a DISCRETE transition -- it
     cannot tween, so the stagger delays fired on an animation with no duration
     and the whole bar popped at once. */
  [data-build="wait"] > :is(*, .m) { clip-path: inset(0 100% 0 0); opacity: 0; }
  [data-build="in"] > :is(*, .m)   { clip-path: inset(0 0 0 0); }
  [data-build="in"] > *:nth-child(2) { transition-delay: 70ms; }
  [data-build="in"] > *:nth-child(3) { transition-delay: 140ms; }
  [data-build="in"] > *:nth-child(4) { transition-delay: 210ms; }
  [data-build="in"] > *:nth-child(5) { transition-delay: 280ms; }

  /* --- slide ------------------------------------------------------------
     A screenshot is a picture of a page, so it arrives the way a page does:
     it travels a little inside its own frame and settles. The frame never
     moves, which is what separates this from a fade-up wearing a transform. */
  /* An animation, not a transition. A transition on `transform` here is
     declared for the life of the element, so it also owned the HOVER on the
     three biggest interactive objects on the page: the portfolio screenshots'
     1.4% hover zoom took 1500ms instead of 240 and was imperceptible. A
     one-shot animation with no fill-mode reverts the moment it ends and hands
     the property back. */
  [data-slide] { overflow: clip; }
  [data-slide="in"] img { animation: slideIn var(--m-demo) var(--e-out) 1; }

  /* --- drift ------------------------------------------------------------
     Continuous, decorative, masked. The dark feature panel gets the same
     lattice as the hero at a fifth of the alpha, moving one cell every
     eighty seconds -- slow enough that it is texture rather than motion, and
     the only reason it moves at all is so the panel is not a flat rectangle
     of navy behind the stack illustration. */
  .darkpanel > * { position: relative; z-index: 1; }
  .darkpanel::before {
    content: ""; position: absolute; inset: -60px 0;
    pointer-events: none; z-index: 0;
    background-image:
      linear-gradient(90deg, rgba(120, 170, 230, 0.055) 1px, transparent 1px),
      linear-gradient(180deg, rgba(120, 170, 230, 0.04) 1px, transparent 1px);
    background-size: 46px 46px;
    -webkit-mask-image: radial-gradient(120% 90% at 78% 40%, #000 0%, transparent 72%);
    mask-image: radial-gradient(120% 90% at 78% 40%, #000 0%, transparent 72%);
    /* No animation. It had one: 46px over 80 seconds, which measured a mean
       change of 0.10 of 255 over forty seconds -- invisible, and a permanent
       composited layer plus a compositor tick for the life of the tab to
       deliver it. The lattice earns its place standing still. */
  }
  /* The closer's pixel edge breathes sideways rather than sitting still. It is
     already the quietest thing on the panel; this keeps it from reading as a
     printed border. */
    /* Halved. At -2.5% the closer's pixel band travelled 32px under the one
     call to action on the page -- 59 times more perceptible than the panel
     drift above, and calibrated in exactly the wrong direction. */
  .closer-art > img { animation: sfSlide 52s var(--e-inout) infinite alternate; }

  /* --- reveal, applied where a line is the subject ------------------------
     The rule above the eras draws itself rather than appearing: scaleX on the
     border, which is one composited transform and not a stroke repaint.
     It is on a ::before, not on .era-list itself -- .era-list is a row of
     eight mono words, and scaling THAT on X squashes type. */
  .era-list { position: relative; }
  .era-list::before {
    content: ""; position: absolute; left: 0; right: 0; top: -1px; height: 2px;
    background: var(--keyline); transform-origin: left center;
    transition: transform var(--m-demo) var(--e-out) 200ms;
  }
  [data-reveal="wait"] .era-list::before { transform: scaleX(0.02); }
}
@keyframes sfSlide {
  from { transform: translate3d(-1.2%, 0, 0); }
  to   { transform: translate3d(0, 0, 0); }
}

/* Hover is a transform, never a box-model property. V5.9.5 animated
   padding-left on .lane and .index-row, which is layout on every hover. */
.lane, .index-row, .worklist-row {
  transition: background-color var(--m-state) var(--e-inout);
}
.lane-in, .index-row-in, .worklist-in {
  transition: transform var(--m-state) var(--e-inout);
}
.lane:hover .lane-in,
.index-row:hover .index-row-in,
.worklist-row:hover .worklist-in { transform: translateX(var(--m-nudge)); }


/* ==========================================================================
   11. MOBILE
   ========================================================================== */
@media (max-width: 600px) {
  .hero-copy { max-width: none; }
}


/* ==========================================================================
   12. PRINT
   ========================================================================== */
@media print {
  .art-layer, .ed-art { display: none !important; }
  .bluefield { background: #fff !important; color: #000 !important; }
  .bluefield * { color: #000 !important; }
}


/* ==========================================================================
   13. TWO SMALL LAYOUTS
   ========================================================================== */
/* A heading with its link on the same line, so a section does not need an
   eyebrow above it and a link below it. */
.split-head {
  display: flex; flex-wrap: wrap; align-items: flex-end;
  justify-content: space-between; gap: var(--s5) var(--s8);
}
.split-head > p { margin: 0; }

/* ==========================================================================
   14. NEEDS
   A two-column list of what something has to get right. Replaces the pattern
   this site was using five different ways: a heading, a paragraph, and a grid
   of bordered cards.
   ========================================================================== */
/* V5.11: each item is a surface. This is the most repeated component on the
   site -- five of them, twice a page, on thirty-one pages -- and it was a 2px
   keyline doing the entire job of separating one idea from the next. It is ONE
   grid, not four, which is the difference between intentional boxes and a card
   grid: the page gains definition without gaining a pattern. */
.needs { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: var(--s5); }
/* White on the soft-gray section ground, not --surface-2: every .needs on the
   site sits inside an .on-paper section, and --surface-2 resolves to that same
   #F7F9FC, so the "surface" shipped at 1.00:1 against its own ground and was a
   bare outline. White gives it 1.05:1 -- deliberately near zero, so the eye
   finds the EDGE, which is what reads as precision rather than as a card
   floating above the page. */
.needs > div {
  background: var(--bg);
  border: 1px solid var(--surface-edge);
  border-radius: var(--radius-card);
  padding: var(--s7);
}
/* Five items in two columns leaves a hole every time. Parity is not a design
   constraint -- the same rule that fixed .worklist and .showcase. */
.needs > div:last-child:nth-child(odd) { grid-column: 1 / -1; }
/* The keyline is a fixed 34px mark, not the heading's own top border. Inside a
   box the border ran the full width of whatever the heading happened to be, so
   five items in a row drew five different lengths -- an accident reading as a
   decision. Same mark, same length, as .eyebrow::before. */
.needs h3 {
  font-size: var(--t-h3); letter-spacing: var(--tr-h3);
  font-weight: var(--w-semi); color: var(--text);
}
.needs h3::before {
  content: ""; display: block; width: 34px; height: 2px;
  background: var(--keyline); margin-bottom: var(--s5);
}
.needs p { font-size: var(--t-body); color: var(--text-dim); margin-top: var(--s4); line-height: 1.6; max-width: 40ch; }
@media (max-width: 47.5em) { .needs { grid-template-columns: minmax(0, 1fr); } }

/* A bare list, used where the items are short enough to need no explanation. */
.plainlist { display: grid; gap: var(--s3); max-width: 62ch; }
.plainlist li { position: relative; padding-left: 22px; color: var(--text-dim); font-size: var(--t-body); }
.plainlist li::before {
  content: ""; position: absolute; left: 0; top: 0.7em;
  width: 10px; height: 1px; background: var(--mark);
}

/* The expectations column on a service page. V5.11 gives it the surface it was
   always behaving like: it sits in a c5 beside prose, which is an aside, and an
   aside with a fill reads as an aside without having to be labelled one. */
.expect {
  background: var(--bg);
  border: 1px solid var(--surface-edge);
  border-radius: var(--radius-panel);
  padding: var(--s8);
}
.expect > :first-child::before {
  content: ""; display: block; width: 34px; height: 2px;
  background: var(--keyline); margin-bottom: var(--s5);
}
.expect ol { display: grid; gap: var(--s4); counter-reset: e; }
.expect ol li {
  position: relative; padding-left: 30px; counter-increment: e;
  font-size: var(--t-body); color: var(--text-2); line-height: 1.5;
}
.expect ol li::before {
  content: counter(e); position: absolute; left: 0; top: 0;
  font-family: var(--font-mono); font-size: var(--t-sm);
  color: var(--mark); font-variant-numeric: tabular-nums;
}


/* ==========================================================================
   15. THE WORK PAGE
   Image on top at full track, facts underneath. V5 alternated a 7/5 split,
   framed every capture in browser chrome, varied the crop every third row and
   dropped an oversized numeral behind each one. The screenshot is the
   argument; everything around it was decoration competing with it.
   ========================================================================== */
.case { display: block; }
.case + .case { margin-top: clamp(56px, 8vw, 120px); }
.case .shot {
  display: block; border: 1px solid var(--rule); border-radius: var(--radius-card);
  overflow: hidden; background: var(--surface-2); box-shadow: var(--lift-2);
}
.case .shot img {
  display: block; width: 100%; height: auto;
  aspect-ratio: 16 / 9; object-fit: cover; object-position: 50% 0%;
}
/* Two columns, four rows, every item placed. The first version left the
   service chips and the live link on auto rows, which put them back into rows
   already occupied by the description — they rendered on top of each other. */
.case-meta {
  display: grid;
  grid-template-columns: minmax(0, 5fr) minmax(0, 7fr);
  grid-template-rows: auto auto auto;   /* no 1fr: the services row is
                                           empty on most projects and a
                                           flexible track stretched it */
  gap: var(--s4) var(--s10); align-items: start;
  margin-top: var(--s8);
}
.case-meta h3 {
  grid-column: 1; grid-row: 1;
  font-size: var(--t-h2); letter-spacing: var(--tr-h2);
}
.case-tags {
  grid-column: 1; grid-row: 2;
  font-family: var(--font-mono); font-size: var(--t-mono);
  letter-spacing: 0.08em; text-transform: uppercase; color: var(--text-faint);
}
.case-meta > p:not(.case-tags):not(.mt-6) {
  grid-column: 2; grid-row: 1 / 3;
  font-size: var(--t-lead); color: var(--text-dim); line-height: 1.5; max-width: 46ch;
}
.case-meta > .case-serv { grid-column: 2; grid-row: 3; margin-top: var(--s5); }
.case-meta > .mt-6 { grid-column: 1; grid-row: 3; margin-top: var(--s5); }
.case-serv { display: flex; flex-wrap: wrap; gap: 6px; }
.case-serv a {
  font-size: var(--t-xs); border: 1px solid var(--rule); border-radius: var(--radius-pill);
  padding: 5px 11px; color: var(--text-dim);
}
.case-serv a:hover { border-color: var(--mark); color: var(--link); }
@media (max-width: 860px) {
  .case-meta { grid-template-columns: minmax(0, 1fr); gap: var(--s4); }
  .case-meta > * { grid-column: 1 !important; grid-row: auto !important; }
  .case-meta h3 { font-size: var(--t-h3); }
}

/* --------------------------------------------------------------------------
   V5.11: THE WORK PAGE IS TIERED
   One lead project on a full-track surface at the feature radius, two cases at
   the card radius, then a repeating grid. The tier comes from the item's index
   in build.py, never from a hand-placed position, so the page holds its shape
   at eight projects and at twenty-five. V5.10 gave all nine the same treatment,
   which was count-agnostic but ran to 5,800px at nine and would run to 18,000
   at twenty-five -- past which the statement panel and the closer, the only two
   conversion surfaces on the page, are unreachable.
   -------------------------------------------------------------------------- */
.case-lead .shot-lead {
  border-radius: var(--radius-feature);
  box-shadow: var(--panel-lift);
}

/* The two supporting projects are a PAIR, so the lead is unambiguously the
   lead. V5.11 first shipped the lead at 2:1 and the two below it at 16:9 --
   which made the item flagged as the feature 80px shorter than the two that
   followed it, inverting the hierarchy it was meant to establish. */
.workpair {
  display: grid; grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: clamp(28px, 3.4vw, 56px);
  margin-top: clamp(48px, 7vw, 104px);
}
.workpair .case + .case { margin-top: 0; padding-top: 0; border-top: 0; }
.workpair .case-meta { grid-template-columns: minmax(0, 1fr); gap: var(--s4); margin-top: var(--s6); }
/* !important, the same way the 860px collapse does it: the placement rules
   above are `.case-meta > p:not(.case-tags):not(.mt-6)`, which outranks any
   descendant selector this class can write. Without it the description stayed
   in a column-2 that no longer exists, the browser created an implicit track,
   and the project name wrapped one letter at a time. */
.workpair .case-meta > * { grid-column: 1 !important; grid-row: auto !important; }
.workpair .case-meta h3 { font-size: var(--t-h3); }
.workpair .case-meta > p:not(.case-tags):not(.mt-6) { font-size: var(--t-body); }
.workpair .case-meta > .case-serv, .workpair .case-meta > .mt-6 { margin-top: var(--s5); }
@media (max-width: 47.5em) {
  .workpair { grid-template-columns: minmax(0, 1fr); gap: var(--s10); }
}
.case-lead .shot-lead img { aspect-ratio: 16 / 9; }
.case-lead .case-meta { margin-top: var(--s9); }
@media (max-width: 43.75em) {
  .case-lead .shot-lead { border-radius: var(--radius-panel); }
  .case-lead .shot-lead img { aspect-ratio: 16 / 10; }
}

/* The one repeating card grid on the site, and it earns it: the items are
   genuinely the same kind of thing, it is the only grid on the page, and it is
   what lets the portfolio grow without a redesign or a line of copy changing. */
.workgrid-head { margin-top: clamp(56px, 9vw, 128px); }
.workgrid {
  display: grid; grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: var(--s8) var(--s6); margin-top: var(--s8);
}
.wcard { display: block; }
.wcard-media {
  display: block; overflow: hidden; border-radius: var(--radius-card);
  border: 1px solid var(--rule); background: var(--surface-2);
  box-shadow: var(--lift-1);
  transition: box-shadow var(--d-state) var(--ease), border-color var(--d-state) var(--ease);
}
.wcard-media img { display: block; width: 100%; aspect-ratio: 16 / 10; object-fit: cover; object-position: 50% 0%; }
.wcard:hover .wcard-media { box-shadow: var(--lift-2); border-color: var(--line-2); }
.wcard-name {
  display: block; margin-top: var(--s5); font-size: var(--t-h4);
  font-weight: var(--w-semi); letter-spacing: var(--tr-h4); color: var(--text);
}
.wcard:hover .wcard-name { color: var(--link); }
.wcard-tags {
  display: block; margin-top: var(--s2); font-size: var(--t-sm);
  color: var(--text-dim); line-height: 1.5;
}
.wcard .arrow { margin-top: var(--s4); font-size: var(--t-sm); }
@media (max-width: 67.5em) { .workgrid { grid-template-columns: repeat(2, minmax(0, 1fr)); } }
@media (max-width: 40em)  { .workgrid { grid-template-columns: minmax(0, 1fr); gap: var(--s8); } }


/* ==========================================================================
   16. THE SERVICES INDEX
   ========================================================================== */
.disc { display: block; }
.disc + .disc { margin-top: clamp(56px, 7vw, 104px); padding-top: clamp(56px, 7vw, 104px); border-top: 1px solid var(--rule); }
.disc-head { display: grid; grid-template-columns: minmax(0, 5fr) minmax(0, 7fr); gap: var(--s6) var(--s10); align-items: baseline; }
.disc-head h2 { font-size: clamp(1.875rem, 1.3rem + 2.4vw, 3rem); letter-spacing: var(--tr-h2); }
.disc-head p { font-size: var(--t-lead); color: var(--text-dim); line-height: 1.5; max-width: 46ch; }
.disc .index { margin-top: var(--s10); }
@media (max-width: 53.75em) { .disc-head { grid-template-columns: minmax(0, 1fr); gap: var(--s4); } }

/* the audience index, without the topology marks it used to carry */
.audrow {
  display: grid; grid-template-columns: minmax(0, 1fr) minmax(0, 40%) auto;
  gap: var(--s6); align-items: baseline;
  padding: var(--s7) 0; border-top: 1px solid var(--rule);
  position: relative; transition: translate var(--d-state) var(--ease);
}
.audrow:last-of-type { border-bottom: 1px solid var(--rule); }
.audrow::before {
  content: ""; position: absolute; left: 0; top: -1px; height: 2px; width: 100%;
  background: var(--mark); transform: scaleX(0); transform-origin: left center;
  transition: transform var(--d-state) var(--ease);
}
.audrow:hover::before, .audrow:focus-visible::before { transform: scaleX(1); }
.audrow:hover { translate: var(--s3); }   /* was padding-left: reflowed 3 columns */
.audrow h3 { font-size: var(--t-h3); letter-spacing: var(--tr-h3); color: var(--text); }
.audrow:hover h3 { color: var(--link); }
.audrow > p { font-size: var(--t-body); color: var(--text-dim); line-height: 1.5; }
.audrow-n { font-family: var(--font-mono); font-size: var(--t-mono); color: var(--text-faint); letter-spacing: 0.1em; }
@media (max-width: 860px) {
  .audrow { grid-template-columns: minmax(0, 1fr); gap: var(--s3); }
  .audrow-n { display: none; }
}


/* ==========================================================================
   18. THE CONTRAST MOVEMENT LAYOUT
   Copy on the left, the stack illustration on the right, and the link out
   sitting under the copy rather than under the picture. Three named areas
   rather than two columns, because the mobile order is not the desktop order
   read top to bottom: the illustration belongs AFTER the three beats and
   BEFORE the link, and areas are the only way to say that without moving the
   link out of the copy it belongs to in the markup.
   ========================================================================== */
.split-art {
  display: grid;
  /* The figure gets the larger share. .mvt-body is not the panel -- the
     eyebrow column takes ~250px off the left before this grid starts -- so at
     1440 there are 885px to divide, and an even split left the artwork at
     429px with labels smaller than any type on the page. 1 : 1.22 puts it at
     462 and still leaves the beats two lines each. */
  grid-template-columns: minmax(0, 1fr) minmax(0, 1.22fr);
  grid-template-rows: auto 1fr;
  grid-template-areas:
    "copy fig"
    "cta  fig";
  column-gap: clamp(24px, 3vw, 48px);
  row-gap: var(--s8);
  align-items: start;
}
.split-art-copy { grid-area: copy; }
.split-art-fig  { grid-area: fig; align-self: center; }
.split-art-cta  { grid-area: cta; margin: 0; }
.split-art-copy > * + * { margin-top: var(--s7); }
.split-art .beats { margin-top: var(--s9); }

@media (max-width: 900px) {
  .split-art {
    grid-template-columns: minmax(0, 1fr);
    grid-template-rows: auto auto auto;
    grid-template-areas: "copy" "fig" "cta";
    row-gap: var(--s8);
  }
  .split-art-fig { max-width: 520px; }
  .split-art-copy > * + * { margin-top: var(--s6); }
  .split-art .beats { margin-top: var(--s6); }
  .beats li { padding: var(--s4) 0; }
}
/* The labels drawn inside the artwork are the smallest type in the movement,
   so on a phone the figure takes the whole column and no cap.

   It briefly took the panel's inline padding back too, with a -32px inline
   margin, for about 22% more width on a 390px screen. audit.py's 200% text
   zoom guard caught it: .darkpanel is overflow: clip, so anything past its
   padding box is content the panel is cropping, and the panel's own 24px
   corner radius really was cutting the artwork there. Sixty pixels is not
   worth an element that reports itself as clipped. */
@media (max-width: 600px) {
  .split-art-fig { max-width: none; }
}


/* ==========================================================================
   19. SHOWCASE, STEPS AND THE STATEMENT
   These three components left the homepage in V5.10 but they are still used
   on /approach/ (.steps3), /work/ (.statement) and the three audience pages
   that carry a project showcase. The V5.10 dead-class sweep read the
   homepage after it had already been rewritten and removed them; restored
   here verbatim from V5.9.5.
   ========================================================================== */



/* ==========================================================================
   5. SELECTED WORK
   The screenshots are the proof, so they are the biggest thing on the page
   and they carry three words of context each.
   ========================================================================== */
.showcase { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: clamp(20px, 2.6vw, 36px); }

/* Full-width only at one item or three-plus. At exactly two -- which is
   already the case on /who-we-help/professional-services/ -- the wide rule
   left one item stretched and one half-width beside an empty cell. */
.showcase > :first-child:not(:nth-last-child(2)) { grid-column: 1 / -1; }

.sc {
  display: block; position: relative;
  background: var(--surface-2); border: 1px solid var(--rule);
  border-radius: var(--radius-card); overflow: hidden;
  transition: border-color var(--d-state) var(--ease);
}

.sc-media { display: block; overflow: hidden; }

.sc-media img {
  display: block; width: 100%; height: auto;
  aspect-ratio: 16 / 10; object-fit: cover; object-position: 50% 0%;
  transition: transform 800ms var(--ease);
}

.showcase > :first-child:not(:nth-last-child(2)) .sc-media img { aspect-ratio: 21 / 9; }

.sc-cap {
  display: flex; flex-wrap: wrap; align-items: baseline; gap: 4px var(--s5);
  padding: var(--s5) var(--s6) var(--s6);
}

.sc-cap b { font-size: var(--t-h4); font-weight: var(--w-semi);
            letter-spacing: var(--tr-h4); color: var(--text); }

.sc-cap span { font-size: var(--t-sm); color: var(--text-faint); }

.sc:hover { border-color: var(--line-2); }

.sc:hover b { color: var(--link); }
@media (prefers-reduced-motion: no-preference) {
  .sc:hover .sc-media img, .sc:focus-visible .sc-media img { transform: scale(1.03); }}
@media (max-width: 720px) {
  .showcase { grid-template-columns: minmax(0, 1fr); }
  .showcase > :first-child .sc-media img { aspect-ratio: 16 / 10; }}



/* ==========================================================================
   6. THREE STEPS
   The whole of the approach, on the homepage and on /approach/. It used to be
   three sections of methodology with a drawing that got progressively more
   ordered. It is three sentences.
   ========================================================================== */
.steps3 { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: var(--s9) var(--s8); }

.steps3 > li { position: relative; padding-top: var(--s7); border-top: 2px solid var(--keyline); }

.steps3 h3 {
  font-size: clamp(1.375rem, 1.1rem + 1.2vw, 1.875rem);
  letter-spacing: var(--tr-h3); color: var(--text); font-weight: var(--w-bold);
}

.steps3 p { font-size: var(--t-body); color: var(--text-dim); margin-top: var(--s4); line-height: 1.6; max-width: 34ch; }
@media (max-width: 50em) { .steps3 { grid-template-columns: minmax(0, 1fr); gap: var(--s8); }}



/* ==========================================================================
   7. THE STATEMENT
   One large sentence on its own ground, used where a section would otherwise
   be a heading, a paragraph and three cards. Sometimes the best section is a
   headline and nothing else.
   ========================================================================== */
/* The statement panel on /work/. Contained, orthogonal ramp, generous padding:
   the same object grammar as the hero and the closer, in the third ground. */
.statement-panel {
  border-radius: var(--radius-feature);
  padding: clamp(40px, 5vw, 84px);
  box-shadow: var(--panel-lift);
}
@media (max-width: 600px) { .statement-panel { border-radius: var(--radius-panel); padding: var(--s8) var(--s7); } }
.statement {
  font-size: clamp(1.75rem, 1.1rem + 2.6vw, 3.25rem);
  line-height: 1.14; letter-spacing: var(--tr-h2);
  font-weight: var(--w-bold); max-width: 20ch; text-wrap: balance;
}

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


/* ==========================================================================
   20. THE CLOSER'S OBJECT

   Declared here, not in styles.css, and this is not cosmetic. `.art-layer`
   sets `inset: 0` and lives in this file, which loads last — so when the
   component was written in styles.css its `top: 50%` / `bottom: 0` were both
   overridden by `inset: 0` at equal specificity. The square then pinned to the
   top of the band and `translate: -50%` lifted it clean out of the section, so
   on phones the artwork sat behind the headline and left 190px of empty blue
   underneath it. Anything that positions inside an .art-layer has to be
   declared after it.
   ========================================================================== */
/* The dissolve. A detail, not an object: it runs along the panel's bottom
   boundary at single-digit alpha and is the same drawing at every width. */
.closer-art {
  position: absolute; z-index: 0;
  inset: auto 0 0 0; top: auto;
  height: clamp(64px, 8vw, 116px);
  pointer-events: none;
}
/* `cover`, never `fill`: a pixel is a square, and stretching the band to the
   panel's width would flatten every one of them on a phone. */
.closer-art > img {
  position: static; display: block; width: 105%; height: 100%;
  object-fit: cover; object-position: 50% 100%;
}

