/* ════════════════════════════════════════════════════════════════════════
   css/components/webbuild.css — THE WEBSITE BEING BUILT
   service.tnaado.ca · reusable · pairs with js/webbuild.js

   PORTED from the old live site's css/tech-webbuild.css. This is the
   animation Ethan named by hand:

     "the current live service.tnaado site has so many anaimtions and
      better dispalys like the tehcnology page ... like the wbesite being
      built snaimtion"

   WHAT IT SHOWS. One drawn browser window goes through three phases
   without ever changing object:

     SKETCH  eleven blocks draw themselves in, dashed and unfilled, over a
             red baseline grid. The layout being decided.
     DESIGN  the same eleven blocks take fill, colour, weight and radius,
             washing left to right. NOTHING MOVES — that is the point. The
             structure was already right; what arrives is the design.
     LIVE    a real client capture cross-fades onto the same rectangle and
             the address bar resolves to that client's domain.

   That is "we design the website, not only the software" as a thing the
   reader watches rather than a paragraph. When this lands, DELETE the
   paragraph.

   WHAT DID NOT COME ACROSS. The old section's headline, lede, worked
   example with its price arithmetic, and three phase columns. Those are
   page copy, they are another agent's, and two of the figures in them
   need checking against PRICES.md. This file is the mechanic only.

   ── THE SEQUENCE IS 100% CSS ──────────────────────────────────────────
   One class, `.is-on`, is added once by js/webbuild.js and never removed.
   Every phase, stagger and cross-fade after that is animation-delay.
   Nothing is on a timer and nothing is on the main thread, so the run
   cannot drop frames while the storm renderer is still settling — which
   matters, because this will sit on pages that also run a WebGL2 scene
   and a damped scroll follower.

   ══ THE FINISHED STATE IS THE DEFAULT. READ THIS BEFORE EDITING. ══
   Every element is authored in its FINAL appearance and the animations run
   BACKWARDS out of a `from` keyframe under `.is-on`, held through the
   delay by animation-fill-mode: both.

   It is deliberately not the other way round. Author the start state and
   animate to the end, and a script that 404s, is blocked, or throws before
   it reaches the observer leaves the reader an empty rectangle where the
   client work should be. Authored this way a script that never runs costs
   the animation and nothing else — which is also why prefers-reduced-
   motion is two lines at the foot instead of a parallel stylesheet that
   has to be kept in sync.

   ── COLOUR ────────────────────────────────────────────────────────────
   The browser window itself is LITERAL, not tokenised: it is a depiction
   of a white browser rendering a light page, and it must look the same in
   an ink band and a bone band or it stops being a browser. Its chrome —
   the rail, the tabs, the caption — IS tokenised, with light-ground
   overrides at the foot so it can drop into a --bone or --ivory band
   without anyone picking a new grey.

   Small red on the white view is #a4132c (6.88:1). Large red fills and
   rules are #c8102e. Never #ef5c6e on white — that is the ink-ground red.
   ════════════════════════════════════════════════════════════════════════ */

.wb {
  /* ── TOKEN BRIDGE. Every global is re-declared with a literal fallback so
     the component is drop-in on a page that links only css/scene.css and
     css/chrome.css, neither of which declares these. */
  --wb-mono:  var(--mono,  'JetBrains Mono', ui-monospace, SFMono-Regular, Menlo, monospace);
  --wb-serif: var(--serif, 'Fraunces', 'Times New Roman', Times, serif);
  --wb-sans:  var(--sans,  'IBM Plex Sans', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif);
  --wb-fg:    var(--bone,    #f5f1e8);
  --wb-dim:   var(--dim-ink, #b9b3a6);
  --wb-acc:   var(--red-ink, #ef5c6e);
  --wb-rule:  var(--hair-ink, rgba(245, 241, 232, .14));

  /* the browser's own palette — literal on purpose, see the header */
  --wb-e:      cubic-bezier(.23, 1, .32, 1);
  --wb-paper:  #ffffff;
  --wb-ivory:  #ece5d7;
  --wb-ink:    #0a0a0a;
  --wb-red:    #c8102e;
  --wb-red-sm: #a4132c;
  --wb-hair:   rgba(10, 10, 10, .16);
  --wb-hair-2: rgba(10, 10, 10, .09);

  position: relative;
  margin: clamp(1.5rem, 3.5vw, 2.25rem) 0 0;
}

/* ══ THE BROWSER ══════════════════════════════════════════════════════════
   Drawn, not photographed. A drawn frame is correct at every width, and a
   photograph of a screen would be a fourth laptop picture on an estate that
   already has too many. */
.wb__browser {
  border: 1px solid var(--wb-hair);
  background: var(--wb-paper);
  box-shadow: 0 18px 40px -26px rgba(10, 10, 10, .55),
              0 3px 10px -6px rgba(10, 10, 10, .30);
}

.wb__chrome {
  display: flex; align-items: center; gap: .55rem;
  padding: .5rem .7rem;
  background: var(--wb-ivory);
  border-bottom: 1px solid var(--wb-hair-2);
}
.wb__dots { display: flex; gap: .3rem; flex: 0 0 auto; }
.wb__dots i { width: 8px; height: 8px; border-radius: 50%; background: rgba(10, 10, 10, .18); }
.wb__url {
  flex: 1 1 auto; min-width: 0;
  font-family: var(--wb-mono); font-size: .68rem; letter-spacing: .02em;
  color: rgba(10, 10, 10, .62);
  background: var(--wb-paper);
  border: 1px solid var(--wb-hair-2); border-radius: 999px;
  padding: .22rem .62rem;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}

/* The rectangle everything happens on. 8/5 is the captures' own aspect, so
   a 1600x1000 screenshot lands with nothing cropped off it. */
.wb__view {
  position: relative;
  aspect-ratio: 8 / 5;
  overflow: hidden;
  background: var(--wb-paper);
}

/* ── the baseline grid the layout is decided on ───────────────────────────
   Finished state: gone. It exists only during the sketch phase. */
.wb__field {
  position: absolute; inset: 0; opacity: 0;
  background:
    repeating-linear-gradient(to right, rgba(200, 16, 46, .11) 0 1px, transparent 1px 8.3333%),
    repeating-linear-gradient(to bottom, rgba(10, 10, 10, .05) 0 1px, transparent 1px 12.5%);
}

/* ── the eleven blocks ────────────────────────────────────────────────────
   Finished state: gone, because the real capture has replaced them. On a
   .wb--wire instance there is no capture, so the painted wireframe IS the
   finished state and it stays. */
.wb__wire { position: absolute; inset: 0; opacity: 0; }
.wb--wire .wb__wire { opacity: 1; }

/* Authored PAINTED — the design phase's destination. wbPaint runs backwards
   out of the dashed sketch state. */
.wb__b {
  position: absolute; box-sizing: border-box;
  background-color: var(--fill, transparent);
  border: 1px solid var(--stroke, transparent);
  border-radius: var(--r, 0);
}

/* Percentages traced off real captures: the same skeleton five client sites
   share — wordmark left, links centre, CTA right, then a left type column
   (eyebrow, two headline lines, rule, lede, two buttons) against a right
   image panel. It is a true reduction of the work, not an invented layout,
   which is why it can resolve into its own source. */
.wb__b--mark   { left: 5.5%;  top: 2.6%;  width: 10.5%; height: 4.2%; }
.wb__b--nav    { left: 37%;   top: 4.0%;  width: 21%;   height: 1.6%; }
.wb__b--navcta { left: 84%;   top: 2.2%;  width: 10.5%; height: 5.0%; }
.wb__b--brow   { left: 5.5%;  top: 20.5%; width: 11%;   height: 1.9%; }
.wb__b--ha     { left: 5.5%;  top: 26.5%; width: 34%;   height: 9.2%; }
.wb__b--hb     { left: 5.5%;  top: 38.5%; width: 24%;   height: 9.2%; }
.wb__b--hr     { left: 5.5%;  top: 53.0%; width: 4%;    height: .8%;  }
.wb__b--lede   { left: 5.5%;  top: 58.0%; width: 27%;   height: 2.0%; }
.wb__b--btn1   { left: 5.5%;  top: 67.0%; width: 12.5%; height: 5.6%; }
.wb__b--btn2   { left: 19.5%; top: 67.0%; width: 12.0%; height: 5.6%; }
.wb__b--panel  { left: 60%;   top: 12.0%; width: 35%;   height: 76%;  }

/* --fill / --stroke / --r are what each block IS once designed. ONE shared
   keyframe reads them, so the paint pass is eleven declarations instead of
   eleven keyframe blocks. */
.wb__b--mark   { --fill: var(--wb-ink); }
.wb__b--navcta { --fill: var(--wb-red); --r: 2px; }
.wb__b--brow   { --fill: var(--wb-red-sm); }
.wb__b--ha,
.wb__b--hb     { --fill: var(--wb-ink); }
.wb__b--hr     { --fill: var(--wb-red); }
.wb__b--lede   { --fill: rgba(10, 10, 10, .34); }
.wb__b--btn1   { --fill: var(--wb-red); --r: 2px; }
.wb__b--btn2   { --stroke: rgba(10, 10, 10, .78); --r: 2px; }
.wb__b--panel  { --fill: var(--wb-ivory); --stroke: rgba(10, 10, 10, .12); --r: 10px; }

/* The nav links are four ticks inside ONE block, so the whole nav enters
   and paints as a single object rather than as four. */
.wb__b--nav { display: flex; gap: 8%; align-items: center; border: 0; padding: 0; }
.wb__b--nav i { flex: 1 1 auto; height: 100%; background: rgba(10, 10, 10, .55); }

/* ── the real captures ───────────────────────────────────────────────────
   The panel plate under the image is what a slow or failed capture shows: a
   correctly-sized rectangle, never a broken-image glyph and never a jump. */
.wb__shot {
  position: absolute; inset: 0;
  width: 100%; height: 100%;
  object-fit: cover; object-position: top center;
  background: var(--wb-ivory);
  opacity: 0; visibility: hidden;
  transform: scale(1.03);
  transition: opacity 420ms var(--wb-e), transform 620ms var(--wb-e);
}
/* The first capture carries is-on in the MARKUP, so the finished state is
   what renders with no script at all. */
.wb__shot.is-on { opacity: 1; visibility: visible; transform: none; }

/* ══ THE RUN ══════════════════════════════════════════════════════════════
   Everything below fires off ONE class. Timeline, in ms from .is-on:

      0 -  760   sketch   blocks draw in, 70ms apart, over the red grid
   1150 - 1990   design   the same blocks paint, 28ms apart, left to right
   2250 - 3010   live     the capture resolves, the wire and grid retire

   Held on `both`, so an element whose delay has not elapsed sits at its
   from-state and cannot flash its finished appearance first. */

@keyframes wbDraw {
  from { opacity: 0; transform: translateY(9px) scale(.965); }
  to   { opacity: 1; transform: none; }
}
@keyframes wbPaint {
  from { background-color: transparent; border-color: rgba(10, 10, 10, .30);
         border-style: dashed; border-radius: 0; }
  to   { background-color: var(--fill, transparent); border-color: var(--stroke, transparent);
         border-style: solid; border-radius: var(--r, 0); }
}
@keyframes wbTick   { from { background-color: rgba(10, 10, 10, .24); }
                      to   { background-color: rgba(10, 10, 10, .55); } }
@keyframes wbField  { from { opacity: 0; } 22% { opacity: 1; } 62% { opacity: 1; } to { opacity: 0; } }
@keyframes wbWire   { from { opacity: 1; } 68% { opacity: 1; } to { opacity: 0; } }
@keyframes wbWireHold { from { opacity: 0; } 1% { opacity: 1; } to { opacity: 1; } }
@keyframes wbReveal { from { opacity: 0; visibility: visible; transform: scale(1.035); }
                      to   { opacity: 1; visibility: visible; transform: none; } }
@keyframes wbSlide  { from { opacity: 0; transform: translateX(-8px); } to { opacity: 1; transform: none; } }
@keyframes wbLight  { from { color: var(--wb-dim); border-bottom-color: transparent; }
                      to   { color: var(--wb-fg); border-bottom-color: var(--wb-red); } }

.wb.is-on .wb__b {
  animation:
    wbDraw  520ms var(--wb-e) calc(var(--i) * 70ms) both,
    wbPaint 700ms var(--wb-e) calc(1150ms + var(--i) * 28ms) both;
}
.wb.is-on .wb__b--nav i { animation: wbTick 700ms var(--wb-e) 1178ms both; }
.wb.is-on .wb__field    { animation: wbField 3000ms var(--wb-e) 60ms both; }
.wb.is-on .wb__wire     { animation: wbWire 3020ms linear 0ms both; }
.wb.is-on .wb__shot--first { animation: wbReveal 760ms var(--wb-e) 2250ms both; }
.wb.is-on .wb__url span { animation: wbSlide 480ms var(--wb-e) 2340ms both; }

/* A wire-only instance stops at DESIGN: the grid still retires but the
   painted wireframe holds, because there is no capture to hand off to. */
.wb--wire.is-on .wb__wire  { animation: wbWireHold 2100ms linear 0ms both; }
.wb--wire.is-on .wb__field { animation: wbField 2400ms var(--wb-e) 60ms both; }

/* A tab click ends the intro. The animations are DROPPED rather than
   reversed, which lands on the authored finished state in one frame instead
   of rewinding through a wireframe the reader has already moved past. */
.wb.is-done .wb__wire,
.wb.is-done .wb__field,
.wb.is-done .wb__b,
.wb.is-done .wb__b--nav i,
.wb.is-done .wb__shot--first,
.wb.is-done .wb__url span,
.wb.is-done .wb__phase { animation: none; }

/* ── the phase rail ─────────────────────────────────────────────────────── */
.wb__rail {
  display: flex; align-items: center; flex-wrap: wrap;
  gap: clamp(.5rem, 1.4vw, .9rem);
  margin-top: .85rem;
}
.wb__phase {
  display: inline-flex; align-items: baseline; gap: .4rem;
  font-family: var(--wb-mono); font-size: .68rem;
  letter-spacing: .1em; text-transform: uppercase;
  color: var(--wb-fg);
  border-bottom: 1px solid var(--wb-red);
  padding-bottom: 2px;
}
.wb__phase b { font-weight: 500; color: var(--wb-acc); }
.wb.is-on .wb__phase--1 { animation: wbLight 420ms var(--wb-e) 120ms both; }
.wb.is-on .wb__phase--2 { animation: wbLight 420ms var(--wb-e) 1150ms both; }
.wb.is-on .wb__phase--3 { animation: wbLight 420ms var(--wb-e) 2250ms both; }

.wb__note {
  flex: 1 1 auto; min-width: 12ch; margin: 0; text-align: right;
  font-family: var(--wb-mono); font-size: .66rem; color: var(--wb-dim);
}

/* ── the client tabs ──────────────────────────────────────────────────────
   A real tablist: real <button>s, aria-selected, roving tabindex handled in
   js/webbuild.js. With no script they are inert buttons and the first
   client stays on screen — the reader loses the switcher, not the work. */
.wb__tabs { display: flex; flex-wrap: wrap; gap: .35rem; margin-top: 1rem; }
.wb__tab {
  font-family: var(--wb-mono); font-size: .68rem;
  letter-spacing: .08em; text-transform: uppercase;
  padding: .44rem .72rem;
  color: var(--wb-dim);
  background: transparent;
  border: 1px solid var(--wb-rule);
  cursor: pointer;
}
.wb__tab[aria-selected="true"] {
  color: var(--wb-fg);
  border-color: var(--wb-red);
  background: rgba(200, 16, 46, .10);
}
.wb__tab:focus-visible { outline: 2px solid var(--wb-acc); outline-offset: 1px; }
@media (hover: hover) {
  .wb__tab { transition: color .2s var(--wb-e), border-color .2s var(--wb-e), background-color .2s var(--wb-e); }
  .wb__tab:not([aria-selected="true"]):hover { color: var(--wb-fg); border-color: var(--wb-rule); }
}

/* ── the caption under the frame ──────────────────────────────────────────
   Every client's caption is in the DOM at all times. The inactive ones are
   NOT display:none — they are hidden from view and from the a11y tree by
   `hidden`, which the browser's own find-in-page can still reveal when it
   is written as hidden="until-found". So the text stays indexable and
   findable, which is the rebuild's rule for all collapsed content. */
.wb__meta { margin-top: .9rem; max-width: 62ch; }
.wb__meta[hidden] { display: none; }
.wb__meta-kind {
  display: block;
  font-family: var(--wb-mono); font-size: .66rem;
  letter-spacing: .08em; text-transform: uppercase;
  color: var(--wb-acc);
  margin-bottom: .4rem;
}
.wb__meta p {
  margin: 0; font-family: var(--wb-sans);
  font-size: .92rem; line-height: 1.6; color: var(--wb-dim);
}
.wb__meta p + p { margin-top: .4rem; }

/* ══ LIGHT-GROUND VARIANT ═════════════════════════════════════════════════
   So the chrome can drop into a --bone or --ivory band without anybody
   inventing a second palette. The browser window itself does not change. */
.fp-band--bone .wb,
.fp-band--ivory .wb,
.wb--on-bone {
  --wb-fg:   var(--ink,       #0a0a0a);
  --wb-dim:  var(--dim-bone,  #5c574f);
  --wb-acc:  var(--red-bone,  #a4132c);
  --wb-rule: var(--hair-bone, rgba(10, 10, 10, .16));
}

/* ══ REDUCED MOTION ═══════════════════════════════════════════════════════
   Two lines, because the finished state is already the authored default.
   The reader gets the client's real capture, the address bar resolved and
   the rail lit — the whole argument, standing still. The tabs keep working:
   someone who asked for less motion still gets to look at five clients. */
@media (prefers-reduced-motion: reduce) {
  .wb.is-on *,
  .wb.is-on *::before,
  .wb.is-on *::after { animation: none !important; }
  .wb__shot { transition: none; }
}

@media (max-width: 620px) {
  /* The note drops to its own line rather than fighting the three phase
     chips for the last few pixels of the rail. At 390 it was landing
     immediately after "03 LIVE" with about 8ch to work in, which read as a
     fourth chip that had run out of room. */
  .wb__note { flex: 1 0 100%; text-align: left; min-width: 0; margin-top: .2rem; }
  .wb__url  { font-size: .62rem; }
}
