/* =========================================================================
   ASSEMBLISTS
   Design language taken from the identity study at logo.assemblists.ai:
   monochrome, monospace UI, Helvetica Neue 800 uppercase display, full-bleed
   panels divided by 1px rules, zero radius, no shadows, no web fonts.
   The page is dark. There is no light theme and no toggle.
   ========================================================================= */

/* -------------------------------------------------------------------------
   1. Tokens
   ---------------------------------------------------------------------- */

:root {
  --paper: #000;
  --ink:   #fff;

  /* derived tints — the only greys in the system */
  --ink-70: rgba(255, 255, 255, .70);
  --ink-45: rgba(255, 255, 255, .45);
  --ink-12: rgba(255, 255, 255, .12);
  --grid:   rgba(255, 255, 255, .14);

  --mono: ui-monospace, "SF Mono", SFMono-Regular, Menlo, Consolas, "Liberation Mono", monospace;
  --display: "Helvetica Neue", Helvetica, Arial, sans-serif;

  --pad: clamp(16px, 4vw, 56px);
  --rule: 1px solid var(--ink);

  /* The only colour in the system. It marks structure (section numerals) and
     action (hover, focus, live data) — never decoration, never body text.
     Swap this one value to retheme the whole site.
       #FF4A00 safety orange · #D6FF00 acid yellow · #0033FF electric blue
     --signal-ink is what sits ON the colour, and must stay near-black for
     contrast, in both themes. */
  --signal: #FF4A00;
  --signal-ink: #000;

  color-scheme: dark;
}

/* Each section carries its own accent, set here and inherited by everything
   inside it — numerals, hover fills, glyph marks, diagram highlights. Nothing
   downstream needed changing; it all already reads var(--signal).

   The six are equiluminant (relative luminance ~0.225), chosen by solving each
   hue for that value rather than by eye. That band is not arbitrary: --signal
   has to clear 3:1 on the black page, 3:1 on the white one, and carry
   near-black text at 4.5:1. Verify before substituting a colour.

   The hero, the header and the footer stay on the root orange. */
/* The 1977 Apple stripes. Four are the original hex; red and purple are
   lifted, because at their real luminance black text on them fails 4.5:1.
   Forcing all six to one luminance was tried and rejected — it turns the red
   and purple to pastel and the palette stops reading as Apple's. */
:root {
  --sig-services: #F5821F;   /* orange */
  --sig-work:     #009DDC;   /* blue   */
  --sig-process:  #61BB46;   /* green  */
  --sig-why:      #BF64C0;   /* purple */
  --sig-team:     #00ABD6;   /* cyan   */
  --sig-faq:      #FDB827;   /* yellow */
  --sig-contact:  #E4565A;   /* red    */
}

/* Named once, above, because the header reads the same seven: a nav item takes
   the accent of the section it points at, and the bar sits outside every
   section, so it cannot inherit them. Two hard-coded lists would drift. */
#services { --signal: var(--sig-services); }
#work     { --signal: var(--sig-work);     }
#process  { --signal: var(--sig-process);  }
#why      { --signal: var(--sig-why);      }
#team     { --signal: var(--sig-team);     }
#faq      { --signal: var(--sig-faq);      }
#contact  { --signal: var(--sig-contact);  }

/* -------------------------------------------------------------------------
   2. Base
   ---------------------------------------------------------------------- */

* { margin: 0; padding: 0; box-sizing: border-box; }

/* A class that sets `display` outranks the user agent's [hidden] rule, so
   anything hidden by script stays on screen. Settle it once, here. */
[hidden] { display: none !important; }

html {
  -webkit-text-size-adjust: 100%;
  scroll-behavior: smooth;
  scroll-padding-top: 60px;
  overflow-x: hidden;
}

body {
  background: var(--paper);
  color: var(--ink);
  font-family: var(--mono);
  font-size: 14px;
  line-height: 1.5;
  letter-spacing: .02em;
  -webkit-font-smoothing: antialiased;
}

img, svg { display: block; max-width: 100%; }
a { color: inherit; text-decoration: none; }
button, input, select, textarea { font: inherit; color: inherit; letter-spacing: inherit; }
ul { list-style: none; }

::selection { background: var(--signal); color: var(--signal-ink); }
:focus-visible { outline: 2px solid var(--signal); outline-offset: 2px; }

/* -------------------------------------------------------------------------
   3. Type
   ---------------------------------------------------------------------- */

.d1, .d2, .d3 {
  font-family: var(--display);
  font-weight: 800;
  font-stretch: condensed;
  letter-spacing: -.03em;
  text-transform: uppercase;
  line-height: .86;
}

.d1 { font-size: clamp(40px, 11vw, 150px); }
.d2 { font-size: clamp(28px, 4.4vw, 56px); line-height: .9; }
.d3 { font-size: clamp(19px, 1.8vw, 25px); line-height: 1; }

.lead { font-size: 16px; line-height: 1.65; max-width: 46ch; }

p { max-width: 62ch; }

/* small uppercase mono label */
.lab {
  display: block;
  font-size: 14px;
  font-weight: 700;
  letter-spacing: .2em;
  text-transform: uppercase;
  color: var(--ink-70);
}

/* section numerals — the one piece of static colour on the page */
.num {
  font-family: var(--display);
  font-weight: 800;
  font-size: clamp(28px, 4.4vw, 56px);
  letter-spacing: -.03em;
  line-height: .9;
  color: var(--signal);
}

.link {
  border-bottom: 1px solid var(--ink-45);
  transition: border-color .15s linear, color .15s linear;
}
.link:hover { color: var(--signal); border-color: var(--signal); }

/* inline bordered chip */
.key { border: var(--rule); padding: 1px 5px; white-space: nowrap; }

/* -------------------------------------------------------------------------
   4. Layout — full bleed, divided by rules
   ---------------------------------------------------------------------- */

.pad { padding-inline: var(--pad); }

.sect { border-bottom: var(--rule); }

/* section head: number + title, tag pushed right */
.chead {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: 10px 16px;
  padding: 15px var(--pad);
  border-bottom: var(--rule);
  text-transform: uppercase;
}
.chead .tag {
  margin-left: auto;
  font-size: 14px;
  letter-spacing: .16em;
  color: var(--ink-70);
  text-align: right;
}

.body-pad { padding: clamp(26px, 4vw, 52px) var(--pad); }

/* Auto-fitting column grid with rules between cells. Every cell carries a
   right and bottom rule; the negative margins pull the trailing ones off the
   edge so they collapse into the section boundary at any wrap count. */
.cols {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(260px, 1fr));
  margin: 0 -1px -1px 0;
}
.cols > * {
  padding: 22px var(--pad);
  border-right: var(--rule);
  border-bottom: var(--rule);
  display: flex;
  flex-direction: column;
  gap: 10px;
}

/* Card glyphs. Sections 02 and 03 carry bullets and numerals; the two column
   sections had no marker at all, so these give them the same rhythm. Built
   from squares and rules to match the logo's construction language. */
.glyph {
  width: 36px;
  height: 36px;
  flex: none;
  color: var(--ink);
  margin-bottom: 4px;
  transition: color .12s linear;
}
.glyph .hot { fill: var(--signal); stroke: none; }
.cols > *:hover .glyph { color: var(--signal); }

/* The pillars carry the page's argument, so their glyphs are given room and a
   slow idle motion. Each runs on its own duration: three identical clocks
   beating in unison reads as a loading spinner, not as life. Motion is inside
   the mark only — nothing reflows. */
.cols .glyph { width: 64px; height: 64px; margin-bottom: 8px; }
.g-anim { transform-box: fill-box; }

.g-draw {
  transform-origin: left center;
  animation: asm-draw 4.4s ease-in-out infinite;
}
.g-draw--2 { animation-delay: .35s; }
.g-draw--3 { animation-delay: .7s; }
@keyframes asm-draw {
  0%, 6%    { transform: scaleX(0); }
  20%, 70%  { transform: scaleX(1); }
  86%, 100% { transform: scaleX(0); }
}

.g-cursor { animation: asm-cursor 4.4s ease-in-out infinite; }
@keyframes asm-cursor {
  0%, 20%   { transform: translateY(0); }
  30%, 45%  { transform: translateY(5px); }
  55%, 78%  { transform: translateY(10px); }
  92%, 100% { transform: translateY(0); }
}

/* non-scaling-stroke on the bars keeps the outline 2px while the body scales;
   without it a squashed bar draws a visibly thinner line than its neighbours. */
.g-bar {
  transform-origin: center bottom;
  animation: asm-bar 3.8s ease-in-out infinite;
}
.g-bar--2 { animation-delay: .3s; }
.g-bar--3 { animation-delay: .6s; }
@keyframes asm-bar {
  0%, 100% { transform: scaleY(.52); }
  50%      { transform: scaleY(1); }
}

.g-tip { animation: asm-tip 3.8s ease-in-out infinite .6s; }
@keyframes asm-tip {
  0%, 100% { transform: translateY(11px); opacity: .3; }
  50%      { transform: translateY(0); opacity: 1; }
}

.g-blink { animation: asm-blink 2.6s steps(1, end) infinite; }
@keyframes asm-blink { 0%, 62% { opacity: 1; } 63%, 100% { opacity: .18; } }

/* Each Work item owns a colour, carried by its card outline, its caption label
   and its slot in the navigator — the same colour that accents the inside of
   its screenshot, so the deck reads as eight products rather than eight views
   of one. Orange is absent on purpose; it belongs to section 01.
 */
.deckwrap [data-item="1"] { --item: #FF4E68; }   /* crimson Media */
.deckwrap [data-item="2"] { --item: #38A7FF; }   /* blue    Meetings */
.deckwrap [data-item="3"] { --item: #FF52C0; }   /* magenta Creative */
.deckwrap [data-item="4"] { --item: #21C960; }   /* green   Planning */
.deckwrap [data-item="5"] { --item: #10C9D0; }   /* teal    People */
.deckwrap [data-item="6"] { --item: #E5C21A; }   /* yellow  Product */
.deckwrap [data-item="7"] { --item: #BE86FF; }   /* violet  Events */
.deckwrap [data-item="8"] { --item: #74CE23; }   /* lime    Retail */

/* Work deck. Eight cards on one perspective plane, fanned either side of a
   centre line; hovering or tabbing to a card flattens it, lifts it toward the
   viewer and brings it to the top of the stack.

   --d is a card's signed offset from the centre of the fan and --z its resting
   stack order. Both are set inline per card, so the whole component works with
   JavaScript switched off — there is nothing to initialise.

   The focused card deliberately does NOT move sideways. A card that slides out
   from under the pointer loses :hover, springs back, and re-enters it: a
   flicker loop. Scale and translateZ grow about the card's own centre, so the
   pointer stays inside whatever it is pointing at. */
.deck {
  --step: clamp(64px, 8.6vw, 118px);
  position: relative;
  height: clamp(400px, 38vw, 500px);
  border-top: var(--rule);
  /* The fan is wider than the viewport on a small desktop. Without this the
     whole page gains a horizontal scrollbar. */
  overflow: hidden;
}
/* The perspective has to live on the cards' direct parent — it does not reach
   grandchildren, and with it on .deck every rotateY flattened into a plain
   horizontal squash. */
.deck__fan {
  position: absolute;
  inset: 0;
  list-style: none;
  perspective: 1150px;
}

.deck__item {
  --abs: max(var(--d), calc(var(--d) * -1));
  position: absolute;
  top: 50%;
  left: 50%;
  width: clamp(250px, 31vw, 420px);
  z-index: var(--z);
  transform:
    translate(-50%, -50%)
    translateX(calc(var(--d) * var(--step)))
    translateZ(calc(var(--abs) * -72px))
    rotateY(calc(var(--d) * -11deg));
  transition: transform .55s cubic-bezier(.2, .7, .3, 1);
}
/* The card the idle cycle is presenting. Unlike hover it travels to the middle
   of the fan: with nothing chasing it there is no pointer to slide out from
   under, and centring is what makes the showcase read as a carousel.
   Declared before the hover rules so that if both ever apply, the visitor's
   pointer wins the tie. */
.deck__item.is-active {
  z-index: 55;
  transform: translate(-50%, -50%) translateZ(90px) rotateY(0deg);
}
.deck__item.is-active figure { border-color: var(--item, var(--signal)); }
.deck__item.is-active figure::after { opacity: 0; }
.deck__item.is-active figcaption { opacity: 1; }
.deck__item.is-active .lab { color: var(--item, var(--signal)); }

/* The navigator. The fan overlaps by design, which left every card reachable
   only through a ~90px sliver behind two of its neighbours — and a card that
   travels to the centre when hovered slides out from under the pointer, so
   hovering the cards themselves could never be the answer. These eight are
   equal, always-reachable targets that never move. */
.deck__nav { display: none; }
.js .deck__nav { display: flex; flex-wrap: wrap; }
.deck__navbtn {
  flex: 1 1 0;
  min-width: 96px;
  padding: 12px 10px;
  border: 0;
  border-right: var(--rule);
  border-bottom: var(--rule);
  background: var(--paper);
  color: var(--ink-45);
  font-family: var(--mono);
  font-size: 12px;
  font-weight: 700;
  letter-spacing: .16em;
  text-transform: uppercase;
  cursor: pointer;
  transition: background .12s linear, color .12s linear;
}
.deck__navbtn:last-child { border-right: 0; }
.deck__navbtn:hover { color: var(--ink); }

/* A swatch on every button, so all eight colours are legible at once and the
   strip reads as a key to the deck rather than as eight anonymous tabs. */
.deck__navbtn::before {
  content: "";
  display: inline-block;
  width: 8px;
  height: 8px;
  margin-right: 8px;
  vertical-align: baseline;
  background: var(--item, var(--signal));
  opacity: .5;
  transition: opacity .12s linear;
}
.deck__navbtn:hover::before,
.deck__navbtn[aria-current="true"]::before { opacity: 1; }
.deck__navbtn[aria-current="true"] {
  color: var(--item, var(--signal));
  box-shadow: inset 0 3px 0 var(--item, var(--signal));
}
.deck__navbtn:focus-visible { outline: 2px solid var(--item, var(--signal)); outline-offset: -3px; }

/* An opaque card, not a floating image — overlapping cards have to occlude
   each other or the fan reads as transparent mush. */
.deck figure {
  position: relative;
  display: flex;
  flex-direction: column;
  gap: 10px;
  padding: 14px;
  background: var(--paper);
  border: 1px solid color-mix(in srgb, var(--item, var(--ink-12)) 42%, transparent);
}
/* Depth without transparency: a wash of the page colour laid over the card,
   heavier the further it sits from the centre of the fan. */
.deck figure::after {
  content: "";
  position: absolute;
  inset: 0;
  background: var(--paper);
  opacity: calc(var(--abs) * .12);
  pointer-events: none;
  transition: opacity .4s linear;
}

/* Only the card in focus carries its caption. At rest the front card
   introduces the set and steps aside the moment the visitor takes over.
   Opacity, not display, so the cards never change height and the fan geometry
   stays put. */
.deck figcaption {
  display: flex;
  flex-direction: column;
  gap: 6px;
  opacity: 0;
  transition: opacity .3s linear;
}
.deck__item[data-lead] figcaption { opacity: 1; }

.deck .lab { font-size: 11px; letter-spacing: .18em; color: var(--ink-45); }

.deck h3 { font-size: 17px; }
.deck p { font-size: 13px; line-height: 1.5; color: var(--ink-70); max-width: none; }

.shot {
  display: block;
  width: 100%;
  height: auto;
  aspect-ratio: 16 / 10;
  object-fit: cover;
  object-position: top center;
  border: 1px solid var(--ink-12);
  background: var(--grid);
}

/* Touch has no hover, and a fan needs room a phone does not have. Both fall
   back to a snapping scroller, which is the right control on a touchscreen
   anyway. */
@media (max-width: 760px), (hover: none) {
  .deck {
    height: auto;
    perspective: none;
    display: flex;
    gap: 14px;
    padding: 22px var(--pad);
    overflow-x: auto;
    overflow-y: hidden;
    scroll-snap-type: x mandatory;
  }
  .deck__fan { position: static; display: contents; perspective: none; }
  .deck__item {
    position: static;
    flex: 0 0 min(78vw, 330px);
    width: auto;
    opacity: 1;
    transform: none;
    scroll-snap-align: center;
  }
  .deck figcaption,
  .deck__item[data-lead] figcaption { opacity: 1; }
  .deck__item.is-active { transform: none; z-index: auto; }
  .js .deck__nav { display: none; }
  .deck figure::after { opacity: 0; }
}

/* Why diagrams: each one animates its own argument.

   Every base class is already the resting composition the animation returns
   to — the chosen option filled, the active slot lit, the sweep at full width.
   That is what makes the reduced-motion switch-off land somewhere sensible
   rather than on a blank frame. */

/* 1. Candidates are considered in turn, then one is chosen and carries flow. */
.w1-opt { animation: w1-blip 7s linear infinite backwards; }
.w1-opt--2 { animation-delay: .7s; }
.w1-opt--4 { animation-delay: 2.1s; }
@keyframes w1-blip {
  0%        { stroke: var(--ink-45); }
  2%, 9%    { stroke: var(--signal); }
  11%, 100% { stroke: var(--ink-45); }
}
.w1-choice { animation: w1-blip 7s linear infinite 1.4s backwards, w1-choose 7s linear infinite backwards; }
@keyframes w1-choose {
  0%, 38%   { fill: var(--paper); }
  44%, 93%  { fill: var(--signal); }
  98%, 100% { fill: var(--paper); }
}
.w1-choice-lbl { animation: w1-lbl 7s linear infinite; }
@keyframes w1-lbl {
  0%, 38%   { fill: var(--ink-45); }
  44%, 93%  { fill: var(--signal-ink); }
  98%, 100% { fill: var(--ink-45); }
}
.w1-wire { animation: w1-wire 7s linear infinite; }
@keyframes w1-wire {
  0%, 38%   { stroke: var(--ink-45); stroke-width: 1; }
  44%, 93%  { stroke: var(--signal); stroke-width: 1.6; }
  98%, 100% { stroke: var(--ink-45); stroke-width: 1; }
}
/* offset-path drives the dot along the chosen wire without scripting it. */
.w1-dot {
  fill: var(--signal);
  opacity: 0;
  offset-path: path("M138 130H196V151H250");
  animation: w1-dot 7s linear infinite;
}
@keyframes w1-dot {
  0%, 40%   { offset-distance: 0%; opacity: 0; }
  45%       { offset-distance: 0%; opacity: 1; }
  68%       { offset-distance: 100%; opacity: 1; }
  72%, 100% { offset-distance: 100%; opacity: 0; }
}

/* 2. The broken lane keeps marching into its cut; the whole lane below lights
      straight through, then the rule sweeps the full width. */
.w2-dash { animation: w2-march 1.6s linear infinite; }
@keyframes w2-march { to { stroke-dashoffset: -8; } }
.w2-cut { animation: w2-cut 2.8s ease-in-out infinite; }
@keyframes w2-cut { 0%, 55% { opacity: .4; } 70% { opacity: 1; } 100% { opacity: .4; } }

.w2-cell { fill: var(--signal); opacity: 0; animation: w2-cell 5.6s linear infinite backwards; }
.w2-cell--2 { animation-delay: .5s; }
.w2-cell--3 { animation-delay: 1s; }
.w2-cell--4 { animation-delay: 1.5s; }
@keyframes w2-cell {
  0%        { opacity: 0; }
  4%, 26%   { opacity: .85; }
  34%, 100% { opacity: 0; }
}
.w2-sweep {
  transform-box: fill-box;
  transform-origin: left center;
  animation: w2-sweep 5.6s cubic-bezier(.4, 0, .2, 1) infinite;
}
@keyframes w2-sweep {
  0%        { transform: scaleX(0); }
  40%, 88%  { transform: scaleX(1); }
  96%, 100% { transform: scaleX(0); }
}

/* 3. The plugged-in slot moves along the row: swapped per project, literally. */
.w3-slot { animation: w3-slot 8.4s linear infinite backwards; }
.w3-slot--2 { animation-delay: 2.1s; }
.w3-slot--3 { animation-delay: 4.2s; }
.w3-slot--4 { animation-delay: 6.3s; }
@keyframes w3-slot {
  0%        { fill: var(--paper); stroke: var(--ink-45); }
  2%, 22%   { fill: var(--signal); stroke: var(--signal); }
  25%, 100% { fill: var(--paper); stroke: var(--ink-45); }
}
.w3-lbl { animation: w3-lbl 8.4s linear infinite backwards; }
.w3-lbl--2 { animation-delay: 2.1s; }
.w3-lbl--3 { animation-delay: 4.2s; }
.w3-lbl--4 { animation-delay: 6.3s; }
@keyframes w3-lbl {
  0%        { fill: var(--ink-45); }
  2%, 22%   { fill: var(--signal-ink); }
  25%, 100% { fill: var(--ink-45); }
}
/* Dasharray interpolates numerically, so "solid" is a long dash with no gap
   rather than `none`, which would not animate at all. */
.w3-wire { animation: w3-wire 8.4s linear infinite backwards; }
.w3-wire--2 { animation-delay: 2.1s; }
.w3-wire--3 { animation-delay: 4.2s; }
.w3-wire--4 { animation-delay: 6.3s; }
@keyframes w3-wire {
  0%        { stroke: var(--ink-45); stroke-dasharray: 4 4; stroke-width: 1; }
  2%, 22%   { stroke: var(--signal); stroke-dasharray: 40 0; stroke-width: 1.6; }
  25%, 100% { stroke: var(--ink-45); stroke-dasharray: 4 4; stroke-width: 1; }
}

/* Why: a slideshow, one reason at a time, each with a diagram that argues the
   point rather than decorating it. Tabs are the control; without JavaScript
   they are meaningless, so they are hidden and all three panels simply stack.
   That is why no panel carries `hidden` in the markup — the script adds it. */
.why { border-top: var(--rule); }
.why__tabs { display: none; }
.js .why__tabs { display: flex; flex-wrap: wrap; }
.why__tab {
  flex: 1 1 auto;
  padding: 14px var(--pad);
  border: 0;
  border-right: var(--rule);
  border-bottom: var(--rule);
  background: var(--paper);
  color: var(--ink-45);
  font-family: var(--mono);
  font-size: 14px;
  font-weight: 700;
  letter-spacing: .16em;
  text-transform: uppercase;
  text-align: left;
  cursor: pointer;
  transition: background .12s linear, color .12s linear;
}
.why__tab:last-child { border-right: 0; }
.why__tab:hover { color: var(--ink); }
.why__tab[aria-selected="true"] { background: var(--ink); color: var(--paper); }
.why__tab:focus-visible { outline: 2px solid var(--signal); outline-offset: -3px; }

.why__panel {
  display: grid;
  gap: clamp(22px, 4vw, 46px);
  padding: clamp(26px, 4vw, 52px) var(--pad);
  align-items: center;
}
.why__panel + .why__panel { border-top: var(--rule); }
.js .why__panel + .why__panel { border-top: 0; }
@media (min-width: 900px) {
  .why__panel { grid-template-columns: minmax(0, 1.08fr) minmax(0, .92fr); }
}
.why__art {
  width: 100%;
  aspect-ratio: 400 / 260;
  border: var(--rule);
  background: var(--paper);
}
.why__art svg { display: block; width: 100%; height: 100%; }
.why__copy { display: grid; gap: 12px; align-content: center; }
.why__copy p { font-size: 15px; line-height: 1.6; color: var(--ink-70); max-width: 46ch; }

/* Only the incoming panel animates; it is a swap, not a slide, so a short
   settle is enough to show that something changed. */
.js .why__panel { animation: asm-swap .34s ease both; }
@keyframes asm-swap {
  from { opacity: 0; transform: translateY(7px); }
  to   { opacity: 1; transform: none; }
}

/* Team portraits. The artwork is light line work on transparency, so it needs
   a dark plate to read against — in both themes, not just the dark one, or it
   disappears entirely on the light page. That is why the plate is a literal
   colour rather than var(--paper). */
.portrait {
  display: block;
  width: 100%;
  height: auto;
  max-width: 190px;
  aspect-ratio: 1;
  object-fit: contain;
  margin-bottom: 6px;
}

.stack { display: grid; gap: 14px; align-content: start; }
.row { display: flex; flex-wrap: wrap; align-items: center; gap: 8px; }

/* -------------------------------------------------------------------------
   5. Components
   ---------------------------------------------------------------------- */

.btn {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  font-family: var(--mono);
  font-size: 14px;
  font-weight: 700;
  letter-spacing: .13em;
  text-transform: uppercase;
  background: var(--paper);
  color: var(--ink);
  border: var(--rule);
  padding: 9px 14px;
  cursor: pointer;
  white-space: nowrap;
  transition: background .12s linear, color .12s linear;
}
.btn:hover { background: var(--signal); color: var(--signal-ink); border-color: var(--signal); }
.btn svg { width: 9px; height: 9px; flex: none; }

.btn--solid { background: var(--ink); color: var(--paper); }
.btn--solid:hover { background: var(--signal); color: var(--signal-ink); border-color: var(--signal); }

.btn--sm { padding: 5px 10px; }

/* bulleted list, mono */
.list { display: grid; }
.list li {
  padding: 7px 0 7px 22px;
  border-bottom: 1px solid var(--ink-12);
  font-size: 14px;
  line-height: 1.55;
  color: var(--ink-70);
  position: relative;
  max-width: none;
}
.list li::before {
  content: "";
  position: absolute;
  left: 0; top: 13px;
  width: 6px; height: 6px;
  background: var(--ink-45);
}
.list li:hover { color: var(--ink); }
.list li:hover::before { background: var(--signal); }

@media (min-width: 700px) {
  .list--2 { grid-template-columns: 1fr 1fr; column-gap: 40px; }
}

/* numbered process rows */
.steps { border-top: var(--rule); }
.step {
  display: grid;
  gap: 10px 20px;
  padding: 16px var(--pad);
  border-bottom: var(--rule);
  align-items: center;
  transition: background .12s linear, color .12s linear;
}
@media (min-width: 900px) {
  .step {
    grid-template-columns: 46px 30px 168px minmax(170px, 1.1fr) minmax(0, 1.3fr);
  }
}

/* The phases are drawn as a schedule rather than a list: each bar sits where
   the phase sits in the project, and they overlap because real ones do —
   discovery is still open when design starts. --from and --to are percentages
   of the whole engagement, set inline per row. */
.step__glyph { width: 26px; height: 26px; margin-bottom: 0; }
.step:hover .glyph { color: inherit; }
.step:hover .glyph .hot { fill: var(--signal-ink); }

.step__track {
  position: relative;
  height: 14px;
  border-left: 1px solid var(--ink-12);
  border-right: 1px solid var(--ink-12);
  background-image: repeating-linear-gradient(
    to right, var(--grid) 0 1px, transparent 1px 25%);
}
.step__bar {
  position: absolute;
  top: 0;
  bottom: 0;
  left: calc(var(--from) * 1%);
  width: calc((var(--to) - var(--from)) * 1%);
  background: var(--ink);
  transform-origin: left center;
  transition: transform .55s cubic-bezier(.2, .7, .3, 1);
  transition-delay: calc(var(--n, 0) * 110ms);
}
.step:hover .step__bar { background: var(--signal-ink); }
.step:hover .step__track { border-color: rgba(0, 0, 0, .3); }

/* The chart draws itself as the section arrives, left to right, one phase
   after another. */
.js .steps [data-reveal] .step__bar { transform: scaleX(0); }
.js .steps [data-reveal].is-in .step__bar { transform: scaleX(1); }
.step:hover { background: var(--signal); color: var(--signal-ink); }
.step:hover .step__n, .step:hover p, .step:hover h3 { color: inherit; }
.step__n { font-size: 14px; font-weight: 700; letter-spacing: .2em; color: var(--ink-70); }
.step h3 {
  font-family: var(--display);
  font-weight: 800;
  font-size: 20px;
  letter-spacing: -.02em;
  text-transform: uppercase;
  line-height: 1;
}
.step p { font-size: 14px; color: var(--ink-70); max-width: 58ch; }

/* Long-form prose on the sub-pages. The FAQ construction is close but it is
   now built for <details>, so borrowing it left article text with no padding
   at all. Same ruled-row rhythm, its own rules. */
.prose > div {
  padding: 26px var(--pad);
  border-bottom: var(--rule);
}
.prose h3 {
  font-family: var(--display);
  font-weight: 800;
  font-size: 20px;
  letter-spacing: -.02em;
  text-transform: uppercase;
  line-height: 1.06;
  margin-bottom: 14px;
  max-width: 34ch;
}
.prose p {
  font-size: 15px;
  line-height: 1.7;
  color: var(--ink-70);
  max-width: 68ch;
}
.prose p + p { margin-top: 14px; }

/* FAQ. Questions first; the answer opens on click. Built on <details>, so it
   needs no JavaScript, is keyboard operable and announces its own state. The
   answers stay in the DOM either way, which is what the crawler and the
   FAQPage JSON-LD read — and current browsers open a closed <details> when
   find-in-page matches inside it. */
.faq details { border-bottom: var(--rule); }
.faq summary {
  display: flex;
  align-items: center;
  gap: 20px;
  padding: 18px calc(var(--pad) + 34px) 18px var(--pad);
  position: relative;
  cursor: pointer;
  list-style: none;
  transition: background .12s linear, color .12s linear;
}
.faq summary::-webkit-details-marker { display: none; }
.faq summary:hover { background: var(--signal); color: var(--signal-ink); }
.faq summary:focus-visible { outline: 2px solid var(--signal); outline-offset: -2px; }

/* A plus that becomes a minus: two bars, one of which rotates flat on open. */
.faq summary::before,
.faq summary::after {
  content: "";
  position: absolute;
  right: var(--pad);
  top: 50%;
  width: 14px;
  height: 2px;
  background: var(--signal);
  transition: transform .18s linear, background .12s linear;
}
.faq summary::before { transform: translateY(-50%); }
.faq summary::after { transform: translateY(-50%) rotate(90deg); }
.faq details[open] summary::after { transform: translateY(-50%) rotate(0); }
.faq summary:hover::before,
.faq summary:hover::after { background: var(--signal-ink); }

.faq h3 {
  font-family: var(--display);
  font-weight: 800;
  font-size: 18px;
  letter-spacing: -.02em;
  text-transform: uppercase;
  line-height: 1.06;
  max-width: 44ch;
}
.faq details > p {
  padding: 6px var(--pad) 24px;
  font-size: 14px;
  color: var(--ink-70);
  max-width: 68ch;
}

/* -------------------------------------------------------------------------
   6. Header & footer
   ---------------------------------------------------------------------- */

.skip {
  position: absolute;
  left: -9999px;
  z-index: 200;
  background: var(--ink);
  color: var(--paper);
  padding: 8px 12px;
}
.skip:focus { left: 6px; top: 6px; }

.bar {
  position: sticky;
  top: 0;
  z-index: 60;
  background: var(--paper);
  border-bottom: var(--rule);
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 8px 14px;
  padding: 9px var(--pad);
  text-transform: uppercase;
}

.brand {
  display: inline-flex;
  align-items: center;
  gap: 9px;
  font-family: var(--display);
  font-weight: 800;
  font-size: 16px;
  letter-spacing: -.03em;
}
/* Five pill-ended layers. The 8-unit gaps render just over a pixel at this
   size, so the layers separate on their own — the earlier non-scaling stroke
   is gone, since a 2px carve would have eaten nearly half of each 4.4px layer. */
.brand__mark { width: 28px; height: auto; flex: none; }

/* Fluid spacing rather than a fixed gap: generous on wide screens, tighter as
   the bar narrows. A fixed 26px gap needed 870px of the 880px breakpoint,
   which left no room for the mono fallback differing across platforms — the
   bar would wrap on a machine whose monospace runs slightly wider. */
.bar nav {
  display: none;
  gap: clamp(16px, 1.8vw, 28px);
  margin-left: clamp(18px, 2vw, 32px);
}
@media (min-width: 860px) { .bar nav { display: flex; } }
.bar nav a {
  position: relative;
  padding-block: 4px;
  font-size: 14px;
  font-weight: 700;
  /* tighter than other mono labels: five items at 14px uppercase need the room
     to stay on one line beside the brand and the CTA */
  letter-spacing: .08em;
  /* 45% was as much of the legibility problem as the size was */
  color: var(--ink-70);
  transition: color .12s linear;
}
/* Each item carries the accent of the section it points at, so hovering the
   bar previews where you are about to land and the current item agrees with
   the colour of the section under it. The bar is outside every section, so
   without these the whole nav would stay on the root orange. */
.bar nav a[href="#services"] { --signal: var(--sig-services); }
.bar nav a[href="#work"]     { --signal: var(--sig-work);     }
.bar nav a[href="#process"]  { --signal: var(--sig-process);  }
.bar nav a[href="#why"]      { --signal: var(--sig-why);      }
.bar nav a[href="#team"]     { --signal: var(--sig-team);     }
.bar nav a[href="#faq"]      { --signal: var(--sig-faq);      }
.bar nav a[href="#contact"]  { --signal: var(--sig-contact);  }

.bar nav a:hover { color: var(--signal); }

/* current section — the rule is a pseudo-element so switching it on and off
   never shifts the bar's layout. The label takes the colour too: every accent
   clears 4.5:1 on the black bar (5.78:1 at worst, red), so a 14px bold label
   in it is still AA — worth checking before adding an eighth colour. */
.bar nav a[aria-current] { color: var(--signal); }
.bar nav a[aria-current]::after {
  content: "";
  position: absolute;
  left: 0; right: 0; bottom: -2px;
  height: 2px;
  background: var(--signal);
}

.bar__end { margin-left: auto; display: flex; align-items: center; gap: 8px; }

/* The band under the last section. Its own rule above this one lost its
   selector once and was swallowed silently, which is what tools/check-css.js
   now guards against. */
.foot {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  /* Row gap carries the wrapped layout on a phone, where the three groups
     stack; the column gap opens up as there is room for one line. */
  gap: 18px clamp(24px, 4vw, 48px);
  padding: clamp(26px, 3.2vw, 38px) var(--pad);
  border-top: var(--rule);
  text-transform: uppercase;
  font-size: 14px;
  letter-spacing: .13em;
  color: var(--ink-70);
}
.foot .brand { color: var(--ink); }
/* 14px uppercase links sit closer than they measure, so they get more room
   between them than the footer's own gap, and enough height to be a
   comfortable tap target rather than a 21px line of text. */
.foot nav {
  display: flex;
  flex-wrap: wrap;
  gap: 4px clamp(26px, 3vw, 38px);
  margin-left: auto;
}
.foot nav a { padding-block: 10px; }
/* Once the footer wraps, "margin-left: auto" would strand the links against
   the right edge while everything above them is left-aligned. */
@media (max-width: 680px) {
  .foot nav { margin-left: 0; width: 100%; }
}
.foot a:hover { color: var(--signal); }

/* -------------------------------------------------------------------------
   7. Hero & diagram
   ---------------------------------------------------------------------- */

.hero {
  padding: clamp(38px, 8vw, 96px) var(--pad) clamp(26px, 5vw, 58px);
  border-bottom: var(--rule);
  background:
    linear-gradient(to right, var(--grid) 1px, transparent 1px) 0 0 / 28px 28px,
    linear-gradient(var(--grid) 1px, transparent 1px) 0 0 / 28px 28px;
}

.hero__grid { display: grid; gap: clamp(28px, 5vw, 56px); }
@media (min-width: 1000px) {
  .hero__grid { grid-template-columns: minmax(0, 1.05fr) minmax(0, .95fr); align-items: center; }
}

.hero__copy { display: grid; gap: 22px; align-content: start; }
.hero__copy .lead { color: var(--ink-70); }

/* The headline is sized against the column it lives in, not the viewport.
   Sized in vw it overflowed its grid column by ~74px at 1600px, spilling into
   the diagram's side and making the whole splash sit left.

   11.5cqw is the measured point where the longest line, "YOUR BUSINESS",
   exactly fills the column — it renders 870px wide at 100px type. Sitting a
   little under that holds the intended three-line block at every width and
   leaves slack for platforms whose sans fallback runs wider. */
.hero h1 { font-size: clamp(38px, 10vw, 132px); }
@media (min-width: 1000px) { .hero h1 { font-size: clamp(38px, 5.2vw, 170px); } }

@supports (container-type: inline-size) {
  .hero__copy { container-type: inline-size; }
  .hero h1,
  .hero__grid .hero h1 { font-size: clamp(38px, 10.8cqw, 170px); }
}

.hero__art {
  aspect-ratio: 400 / 300;
  width: 100%;
  max-width: 560px;   /* stacked layout only */
  border: var(--rule);
  background: var(--paper);
}

@media (min-width: 1000px) {
  /* fill the column instead of leaving a gap before the right margin, but
     stop growing on very wide screens and sit flush to the right edge so the
     composition stays balanced rather than stretching indefinitely */
  .hero__art { max-width: 720px; justify-self: end; }
}
@media (max-width: 639px) { .hero__art { display: none; } }
.hero__art svg { width: 100%; height: 100%; }

.art .wire {
  fill: none;
  stroke: var(--ink-45);
  stroke-width: 1;
  transition: stroke .15s linear, stroke-width .15s linear;
}
.art .wire.is-flow { stroke: var(--node, var(--signal)); stroke-width: 1.4; }
.art .wire.is-hot { stroke: var(--ink); stroke-width: 1.8; }

.art .blk { fill: var(--paper); stroke: var(--ink-45); stroke-width: 1; transition: fill .12s linear, stroke .12s linear; }
.art .blk-ink { fill: var(--ink); stroke: var(--ink); }
.art .lbl {
  font-family: var(--mono);
  font-size: 9px;
  font-weight: 700;
  letter-spacing: .08em;
  fill: var(--ink-45);
  transition: fill .12s linear;
}
.art .lbl-on { fill: var(--paper); }
.art .blk-hot { fill: var(--signal); stroke: var(--signal); }
/* Diagram furniture: column captions, the header hairline, registration marks
   at the corners, and the dots where wires meet a bus. */
.art .cap {
  font-family: var(--mono);
  font-size: 8px;
  font-weight: 700;
  letter-spacing: .18em;
  fill: var(--ink-45);
}
.art .rule { stroke: var(--ink-12); stroke-width: 1; fill: none; }
.art .mark { stroke: var(--ink-45); stroke-width: 1; fill: none; }
.art .dot { fill: var(--ink-45); }
.art .port { fill: var(--node, var(--ink-45)); }
.art .arrow { fill: var(--ink-45); }
.art .is-soft { opacity: .58; }
.art .is-loop { stroke: var(--ink-12); }
.art .lbl-hot { fill: var(--signal-ink); }
.art .is-faint { opacity: .35; }
.art .is-dash { stroke-dasharray: 4 4; }
.art .w2-cell { fill: var(--signal); }
.art .is-cut { fill: none; stroke: var(--signal); stroke-width: 1.6; }
.art .rule-on { stroke: var(--paper); stroke-width: 1; opacity: .3; }
.art .hot-bar { fill: var(--signal); }
.hero__art .packet { fill: var(--signal); }

/* the four processing squares inside the core read against the inverted block */
.hero__art .ind { fill: var(--paper); opacity: .3; animation: ind 2.1s steps(1) infinite; }
.hero__art .ind--2 { animation-delay: .7s; }
.hero__art .ind--3 { animation-delay: 1.4s; }
@keyframes ind {
  0%, 25%   { fill: var(--signal); opacity: 1 }
  26%, 100% { fill: var(--paper);  opacity: .3 }
}

/* interaction */
.hero__art .node { cursor: pointer; }
/* hover is a white invert; a landing packet is a colour hit */
.hero__art .node.is-hot rect.blk { fill: var(--ink); stroke: var(--ink); }
.hero__art .node.is-hot .lbl { fill: var(--paper); }
.hero__art .node--core.is-hot rect.blk-ink { fill: var(--ink-70); }

/* Every source system owns a colour, so the hero reads as many different
   things assembling into one rather than as one thing repeated. --node is set
   inline per node and per wire; anything without one falls back to the accent,
   which is why human review stays monochrome. */
.hero__art .wire { stroke: color-mix(in srgb, var(--node, var(--ink-45)) 62%, transparent); }
/* A tab of the source's own colour on the outer edge of each block, so the
   palette is legible at rest and not only while a packet is in flight. It
   flips dark when the block lights up, or it would vanish into the fill. */
.hero__art .tab { fill: var(--node, var(--ink-45)); }
.hero__art .node.is-live rect.blk { fill: var(--node, var(--signal)); stroke: var(--node, var(--signal)); }
.hero__art .node.is-live .lbl { fill: var(--signal-ink); }
.hero__art .node--core.is-live rect.blk-ink { fill: var(--signal); stroke: var(--signal); }
.hero__art .node--core.is-live .lbl-on { fill: var(--signal-ink); }

/* -------------------------------------------------------------------------
   8. Form
   ---------------------------------------------------------------------- */

.contact { display: grid; }
@media (min-width: 900px) { .contact { grid-template-columns: 1fr 1fr; } }
.contact > * { padding: clamp(26px, 4vw, 52px) var(--pad); }
@media (min-width: 900px) { .contact > :first-child { border-right: var(--rule); } }

.form { display: grid; gap: 14px; }
/* An author `display` beats the hidden attribute's UA `display:none`, so
   without this the submitted form stays on screen underneath the thank-you
   panel with "Sending…" frozen on the button. */
.form[hidden] { display: none; }
.form-row { display: grid; gap: 14px; }
@media (min-width: 560px) { .form-row { grid-template-columns: 1fr 1fr; } }

.field { display: grid; gap: 5px; }
.field label { font-size: 14px; font-weight: 700; letter-spacing: .16em; text-transform: uppercase; color: var(--ink); }

.input, .textarea, .select {
  width: 100%;
  padding: 10px 11px;
  background: var(--paper);
  color: var(--ink);
  border: 1px solid var(--ink-45);
  border-radius: 0;
  font-family: var(--mono);
  font-size: 14px;
  transition: border-color .12s linear;
}
.input::placeholder, .textarea::placeholder { color: var(--ink-45); }
.input:focus, .textarea:focus, .select:focus { outline: none; border-color: var(--ink); }
.textarea { min-height: 116px; resize: vertical; line-height: 1.55; }

.select {
  appearance: none;
  cursor: pointer;
  background-image: linear-gradient(45deg, transparent 50%, currentColor 50%),
                    linear-gradient(135deg, currentColor 50%, transparent 50%);
  background-position: calc(100% - 17px) 55%, calc(100% - 12px) 55%;
  background-size: 5px 5px, 5px 5px;
  background-repeat: no-repeat;
  padding-right: 34px;
}

.field__error { display: none; font-size: 14px; letter-spacing: .13em; text-transform: uppercase; }
.field[data-invalid] .field__error { display: block; }
.field[data-invalid] .field__error { color: var(--signal); }
.field[data-invalid] .input,
.field[data-invalid] .textarea,
.field[data-invalid] .select { border-color: var(--signal); border-width: 2px; }

.form-note { font-size: 14px; line-height: 1.6; color: var(--ink-70); }

.form-fail {
  border: 2px solid var(--signal);
  padding: 10px 12px;
  font-size: 14px;
  line-height: 1.5;
}
.form-fail[hidden] { display: none; }

.success { display: none; gap: 12px; border: var(--rule); padding: 22px; }
.success.is-visible { display: grid; }
.success svg { width: 26px; height: 26px; }

/* -------------------------------------------------------------------------
   9. Motion — minimal, and off entirely under reduced motion
   ---------------------------------------------------------------------- */

.js [data-reveal] { opacity: 0; transition: opacity .45s linear; }
.js [data-reveal].is-in { opacity: 1; }
.js [data-stagger] > * { transition-delay: calc(var(--n, 0) * 50ms); }

.js .cols > [data-reveal] { opacity: 1; }
.js .cols > [data-reveal] > * { opacity: 0; transition: opacity .45s linear; transition-delay: calc(var(--n, 0) * 50ms); }
.js .cols > [data-reveal].is-in > * { opacity: 1; }

.brand__mark rect { animation-duration: .5s; animation-timing-function: linear; }
.brand:hover .brand__mark rect { animation-name: shift; }
@keyframes shift { 40% { transform: translateX(-10px); } }

/* ripples upward from the base: 8 rects across 5 layers */
.brand__mark rect:nth-of-type(1) { animation-delay: .24s }  /* apex     */
.brand__mark rect:nth-of-type(2),
.brand__mark rect:nth-of-type(3) { animation-delay: .18s }  /* counter  */
.brand__mark rect:nth-of-type(4),
.brand__mark rect:nth-of-type(5) { animation-delay: .12s }  /* counter  */
.brand__mark rect:nth-of-type(6) { animation-delay: .06s }  /* crossbar */
.brand__mark rect:nth-of-type(7),
.brand__mark rect:nth-of-type(8) { animation-delay: 0s }    /* legs     */

@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }
  *, *::before, *::after { animation-duration: .001ms !important; transition-duration: .001ms !important; }
  .js [data-reveal], .js .cols > [data-reveal] > * { opacity: 1; }
  .brand:hover .brand__mark rect { animation-name: none; }
  .hero__art .ind { animation: none; opacity: .6; }
  /* The blanket .001ms duration leaves an infinite animation cycling far too
     fast to be still. These have to be switched off outright, and land on the
     state they animate toward. */
  .g-anim { animation: none !important; transform: none !important; opacity: 1 !important; }
  /* Same reason as .g-anim: these loop forever, so they must be stopped rather
     than sped up. Every base class is already the resting composition. */
  .art-anim { animation: none !important; }
  .w2-cell { opacity: 0 !important; }
}

/* -------------------------------------------------------------------------
   9. Optional depth
   -------------------------------------------------------------------------
   Everything in this block is gated on <html data-fx>. Remove that one
   attribute and the page returns exactly to the flat construction the rest of
   this sheet describes — nothing here is depended on by anything above.

   It is deliberately small. The house style is 1px rules and no shadows, and
   that flatness is the reason the site does not look like every other AI
   consultancy. So: light where light is plausible — behind a background grid,
   under a card already lifted in 3D, inside an illustration — and nowhere on
   a button, a rule, a border or a nav.
   ---------------------------------------------------------------------- */

/* The grid ran at full strength into all four edges, which is what made it
   read as a CSS background rather than as drawing. The falloff is layered
   over the grid in the page colour rather than applied as a mask, because a
   mask on .hero would fade the headline with it. */
:root[data-fx] .hero {
  background:
    radial-gradient(118% 92% at 14% 4%, transparent 22%, var(--paper) 88%),
    linear-gradient(to right, var(--grid) 1px, transparent 1px) 0 0 / 28px 28px,
    linear-gradient(var(--grid) 1px, transparent 1px) 0 0 / 28px 28px;
}

/* Each section casts its own accent down from the top and is gone before it
   reaches the content. This is what makes the six colours read as a system
   rather than as six numerals that happen to be different. Painted as a
   background layer, so it sits behind everything without a z-index. */
:root[data-fx] .sect {
  background-image: linear-gradient(to bottom,
    color-mix(in srgb, var(--signal) 10%, transparent) 0,
    transparent 160px);
}

/* The presented card is already lifted toward the viewer on a perspective
   plane, so a glow on it is the one place light is physically plausible.
   On the figure, never the <li>: filter establishes a containing block and
   would flatten the parent's 3D transform. */
:root[data-fx] .deck figure { transition: filter .4s linear; }
:root[data-fx] .deck__item.is-active figure {
  filter: drop-shadow(0 0 18px color-mix(in srgb, var(--item, var(--signal)) 24%, transparent));
}

/* Inside the diagram the rules are different — it is a drawing, and a lit
   block is meant to look lit. Lengths in a filter on an SVG child are user
   units, so 7 here is 7 of the 400 across the viewBox. */
:root[data-fx] .hero__art .node.is-live rect { filter: drop-shadow(0 0 7px var(--node, var(--signal))); }
:root[data-fx] .hero__art .packet { filter: drop-shadow(0 0 3px currentColor); }

@media print {
  .bar, .foot, .hero__art { display: none !important; }
  body { background: #fff; color: #000; }
}

/* -------------------------------------------------------------------------
   10. Chat — the floating funnel (assets/js/chat.js)
   The same vocabulary as the rest of the page: 1px rules, square corners,
   mono labels, one accent. Above the sticky bar (60), below the skip link.
   ---------------------------------------------------------------------- */

.chat-launch {
  position: fixed;
  right: clamp(16px, 2.4vw, 28px);
  /* --chat-lift is how much of the footer is on screen, so the button rides
     above the footer's links instead of covering them */
  bottom: calc(clamp(16px, 2.4vw, 28px) + var(--chat-lift, 0px));
  z-index: 70;
  display: inline-flex;
  align-items: center;
  gap: 10px;
  padding: 13px 16px;
  background: var(--paper);
  color: var(--ink);
  border: var(--rule);
  font: 700 13px/1 var(--mono);
  letter-spacing: .13em;
  text-transform: uppercase;
  cursor: pointer;
  transition: background .12s linear, color .12s linear, border-color .12s linear;
}
.chat-launch svg { width: 18px; height: 18px; flex: none; }
.chat-launch:hover,
.chat-launch[aria-expanded="true"] { background: var(--signal); color: var(--signal-ink); border-color: var(--signal); }
.chat-launch:focus-visible { outline: 2px solid var(--signal); outline-offset: 3px; }

.chat-panel {
  position: fixed;
  right: clamp(16px, 2.4vw, 28px);
  bottom: calc(clamp(16px, 2.4vw, 28px) + var(--chat-lift, 0px) + 58px);
  z-index: 71;
  width: min(380px, calc(100vw - 32px));
  height: min(560px, calc(100vh - 150px));
  height: min(560px, calc(100dvh - 150px));
  display: flex;
  flex-direction: column;
  background: var(--paper);
  color: var(--ink);
  border: var(--rule);
}

.chat-head {
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 14px 14px 14px 16px;
  border-bottom: var(--rule);
}
.chat-head__dot { width: 8px; height: 8px; flex: none; background: var(--signal); }
.chat-head__text { display: flex; flex-direction: column; gap: 5px; min-width: 0; margin-right: auto; }
.chat-head__text strong { font: 800 16px/1 var(--display); letter-spacing: -.01em; text-transform: uppercase; }
.chat-head__text span { font-size: 11px; letter-spacing: .12em; text-transform: uppercase; color: var(--ink-70); }
.chat-close {
  display: grid;
  place-items: center;
  width: 34px;
  height: 34px;
  flex: none;
  background: var(--paper);
  color: var(--ink);
  border: var(--rule);
  cursor: pointer;
}
.chat-close svg { width: 12px; height: 12px; }
.chat-close:hover { background: var(--ink); color: var(--paper); }
.chat-close:focus-visible { outline: 2px solid var(--signal); outline-offset: 2px; }

.chat-log {
  flex: 1;
  overflow-y: auto;
  overscroll-behavior: contain;
  display: flex;
  flex-direction: column;
  gap: 12px;
  padding: 18px 16px;
}
.chat-msg { max-width: 88%; font-size: 14px; line-height: 1.55; overflow-wrap: anywhere; }
.chat-msg--bot { align-self: flex-start; padding-left: 12px; border-left: 2px solid var(--signal); }
.chat-msg--you { align-self: flex-end; padding: 8px 12px; background: var(--ink); color: var(--paper); }
.chat-msg--small { font-size: 12px; color: var(--ink-70); border-left-color: var(--ink-12); }
.chat-msg--alert { border-left-color: var(--sig-contact); }

.chat-typing { display: flex; gap: 5px; padding-block: 7px; }
.chat-typing i { width: 6px; height: 6px; background: var(--ink-45); animation: chat-dot 1s steps(1) infinite; }
.chat-typing i:nth-child(2) { animation-delay: .2s; }
.chat-typing i:nth-child(3) { animation-delay: .4s; }
@keyframes chat-dot { 0%, 100% { background: var(--ink-45); } 50% { background: var(--ink); } }

.chat-summary {
  display: grid;
  grid-template-columns: auto 1fr;
  gap: 7px 14px;
  margin: 0;
  padding: 12px 14px;
  border: var(--rule);
  font-size: 13px;
  line-height: 1.45;
}
.chat-summary dt { font-size: 11px; font-weight: 700; letter-spacing: .14em; text-transform: uppercase; color: var(--ink-70); padding-top: 1px; }
.chat-summary dd { margin: 0; overflow-wrap: anywhere; }

.chat-actions { display: flex; flex-wrap: wrap; gap: 8px; padding: 0 16px 16px; }
.chat-actions:empty { display: none; }
.chat-choice {
  padding: 11px 13px;
  background: var(--paper);
  color: var(--ink);
  border: var(--rule);
  font: 700 12px/1.2 var(--mono);
  letter-spacing: .1em;
  text-transform: uppercase;
  text-align: left;
  cursor: pointer;
  transition: background .12s linear, color .12s linear, border-color .12s linear;
}
.chat-choice--solid { background: var(--ink); color: var(--paper); }
.chat-choice:hover:not(:disabled) { background: var(--signal); color: var(--signal-ink); border-color: var(--signal); }
.chat-choice:focus-visible { outline: 2px solid var(--signal); outline-offset: 2px; }
.chat-choice:disabled { opacity: .5; cursor: default; }

.chat-form { display: flex; border-top: var(--rule); }
.chat-field {
  flex: 1;
  min-width: 0;
  padding: 15px 16px;
  background: var(--paper);
  color: var(--ink);
  border: 0;
  border-radius: 0;
  font: 14px/1.3 var(--mono);
}
.chat-field::placeholder { color: var(--ink-45); }
.chat-field:focus { outline: 2px solid var(--signal); outline-offset: -2px; }
.chat-send {
  display: grid;
  place-items: center;
  width: 54px;
  flex: none;
  background: var(--ink);
  color: var(--paper);
  border: 0;
  border-left: var(--rule);
  cursor: pointer;
}
.chat-send svg { width: 14px; height: 14px; }
.chat-send:hover { background: var(--signal); color: var(--signal-ink); }
.chat-send:focus-visible { outline: 2px solid var(--signal); outline-offset: -4px; }

.chat-sr {
  position: absolute;
  width: 1px;
  height: 1px;
  overflow: hidden;
  clip: rect(0 0 0 0);
  white-space: nowrap;
}

/* On a phone the window becomes a sheet along the bottom, and the launcher
   steps aside while it is open: the sheet has its own close button. 16px text
   in the field stops iOS zooming the page when it takes focus. */
@media (max-width: 560px) {
  .chat-panel {
    left: 8px;
    right: 8px;
    bottom: 8px;
    width: auto;
    height: min(620px, calc(100vh - 72px));
    height: min(620px, calc(100dvh - 72px));
  }
  .chat-open .chat-launch { display: none; }
  .chat-field { font-size: 16px; }
}

@media (prefers-reduced-motion: reduce) {
  .chat-typing i { animation: none; }
}

@media print {
  .chat-launch, .chat-panel { display: none !important; }
}
