/* ══════════════════════════════════════════════════════════════════════════
   /tech — THE EXPERTISE DIRECTORY (body). 2026-08-13.

   WHAT THIS FILE IS
   -----------------
   The whole body of /tech below the "THE STACK" hero. It replaces the four
   sections the page used to carry (a four-group capability catalogue with
   eleven chips, a three-beat "method" of pure claim prose, a sister-firm
   paragraph, and two prose sections) with ONE directory of five expertise
   areas, each of which OPENS to run the actual mechanic in front of the
   reader instead of describing it.

   Ethan, on this page and its sibling: "sick cool animations and more design
   creativity with the condensed displays and detail menus", and — about the
   copy on both tech pages — "way too much blabla bla text, nothing there
   sells a reader." Those are the same instruction. An animation that shows
   the real mechanic IS the proof, so it is allowed to replace the paragraph
   that used to claim the same thing. Every paragraph deleted here was
   replaced by something the reader can watch happen.

   FULL BORDER TO BORDER — the standing sitewide complaint
   -------------------------------------------------------
   Nothing in this file is a card, a bordered box, or an inset column. Every
   section and every directory row is exactly 100% of the viewport wide, and
   every hairline rule runs the full width of the screen. Content is inset
   from the edges by --tdx-gut (a PADDING on a full-width element) and long
   text runs are capped by a `ch` MEASURE (--tdx-measure), which is a
   typographic limit on the line, not a container the section sits inside.
   The old body used .inner { max-width: 1100px }, so at 2560px every section
   was a 1100px column floating in the middle of the screen. That is the
   thing being fixed. If you edit this file: never add max-width to a
   section, a row, or a panel.

   TYPE SCALE — deliberately one notch under the page it replaces
   --------------------------------------------------------------
   Same complaint, second half: "everything is too big and screaming at you."
   Measured against css/tech-page.css, which this file supersedes:
     section h2      clamp(1.9rem, 3.4vw, 2.8rem)  ->  clamp(1.2rem, 2.2vw, 1.72rem)
     body copy       clamp(.95rem, 1.1vw, 1.08rem) ->  clamp(.85rem, .92vw, .94rem)
     row title       (n/a — group name 1.45rem)    ->  clamp(1.05rem, 1.9vw, 1.5rem)
     card title      1.15rem                       ->  1.02rem
     proof figure    clamp(2.1rem, 4.2vw, 3.1rem)  ->  deleted outright (see §1)

   MOTION — the expand/collapse is this page's core interaction
   -----------------------------------------------------------
   Applied from the emil-design-eng brief, deliberately and specifically:
     - CSS TRANSITIONS, never keyframes, for the disclosure. Keyframes
       restart from zero; a reader who opens 03 then immediately opens 05
       must see 03 retarget smoothly from wherever it is, not snap shut.
     - Custom ease-out (--tdx-ease), never ease-in. ease-in delays the first
       50ms, which is exactly the moment the reader is watching.
     - Under 300ms: 260ms open, 150ms close. Exit is faster than entry
       (the system responding should be quicker than the user deciding).
     - No scale(0) entrances anywhere. The panel content enters at
       translateY(-6px) + opacity 0 — it comes from the trigger, not from
       nothing — with transform-origin pinned to the top-left corner, which
       is where the button the reader just pressed physically is.
     - The +/- mark ROTATES; it never scales to nothing.
     - Press feedback on the row is a 2px marker nudge, not scale(0.97):
       scaling a full-bleed row would visibly shear the hairline rules above
       and below it.
     - Every :hover rule is behind @media (hover: hover) and (pointer: fine)
       so a tap on a phone does not leave a row stuck in its hover state.
     - prefers-reduced-motion: all of it off, panels open instantly, and the
       five demos are frozen on their most informative frame (see the JS).

   TOKENS ONLY. Palette is ink #0a0a0a / on-black #f5f1e8 / red #c8102e from
   site.css, plus --red-text for small red type (--red measures 3.37:1 on ink
   and fails AA below ~18px; site.css documents this). No new hue.

   There is no [data-theme] anywhere in this file. The light-theme toggle was
   removed sitewide on 2026-08-13 (T-1040); header and footer are permanently
   light, the page body is permanently ink. Do not reintroduce a theme hook.
   ══════════════════════════════════════════════════════════════════════════ */

.tdx-page {
  /* Gutter, not a container: this is padding-inline on full-width elements. */
  --tdx-gut: clamp(1.15rem, 5vw, 4.5rem);
  /* Typographic measure in ch — caps the LINE, never the section. */
  --tdx-measure: 62ch;

  --tdx-line: rgba(245,241,232,.10);
  --tdx-line-soft: rgba(245,241,232,.06);
  --tdx-dim: rgba(245,241,232,.52);
  --tdx-mute: rgba(245,241,232,.62);

  /* Strong ease-out. The stock CSS easings are too weak to read as
     intentional at these durations. */
  --tdx-ease: cubic-bezier(0.23, 1, 0.32, 1);
  --tdx-open: 260ms;
  --tdx-close: 150ms;
}

/* ── shared full-bleed section shell ───────────────────────────────────── */
.tdx-sec {
  position: relative;
  width: 100%;
  background: var(--ink);
  border-top: 1px solid var(--tdx-line);
  padding: clamp(3.2rem, 6vw, 5.2rem) 0;
}
.tdx-sec > * { padding-inline: var(--tdx-gut); }

.tdx-eyebrow {
  display: flex; align-items: center; gap: .7rem;
  font-family: var(--font-mono);
  font-size: .6rem; letter-spacing: .22em; text-transform: uppercase;
  color: var(--red-text);
  margin: 0 0 1rem;
}
.tdx-eyebrow::before {
  content: ""; display: block; width: 26px; height: 1px;
  background: var(--red); flex: 0 0 auto;
}
.tdx-h2 {
  font-family: var(--font-display); font-weight: 400;
  font-size: clamp(1.2rem, 2.2vw, 1.72rem);
  line-height: 1.16; letter-spacing: -.015em;
  color: var(--on-black);
  margin: 0 0 .9rem; max-width: 24ch;
}
.tdx-h2 em { font-style: italic; color: var(--red-text); }
.tdx-lede {
  font-family: var(--font-body);
  font-size: clamp(.85rem, .92vw, .94rem);
  line-height: 1.7; color: var(--tdx-mute);
  margin: 0; max-width: var(--tdx-measure);
}
.tdx-lede em { font-style: italic; color: var(--on-black); }
.tdx-lede + .tdx-lede { margin-top: .9rem; }

/* ══════════════════════════════════════════════════════════════════════
   §0 · THE HANDOVER BAND — Ethan's frame of a client being walked through
   a build, supplied 2026-08-13.

   Full bleed and UNCAPPED in height: 16:9 at 100vw. That is 810px at
   1440 and 219px at 390. The height is not a mistake and should not be
   "fixed" with a max-height — the frame's subjects fill it vertically, so
   every cap tight enough to shorten the band meaningfully clips either the
   cap at the top or the hands and laptop at the bottom. Cropping the right
   is no help either: taking width off a 16:9 frame makes the band TALLER,
   not shorter.

   No border, no inset, no rounded corner, no frame. The only chrome is the
   caption's own gradient, which is what guarantees the caption's contrast
   over a bright photograph rather than hoping the pixels underneath are
   dark enough.
   ══════════════════════════════════════════════════════════════════════ */
.tdx-live { padding-top: 0; padding-bottom: clamp(1.6rem, 3vw, 2.4rem); border-top: 0; }
.tdx-live__figure { margin: 0; padding-inline: 0 !important; position: relative; }
/* T-1095: this ran at its native 16:9, which is 810px on a 740px-tall window
   and 1080px on a 1080px one — a literal full screen of one photograph, and
   the loudest instance of what Ethan called out. --band-lg with the crop
   anchored at 50% 42%, checked against the frame: it keeps the cap, the
   founder, the client in the foreground and the laptop between them, and
   drops ceiling and floor. It is still the biggest photograph on the page. */
.tdx-live__figure img {
  display: block; width: 100%;
  height: var(--band-lg);
  object-fit: cover; object-position: 50% 42%;
}
.tdx-live__cap {
  position: absolute; left: 0; right: 0; bottom: 0;
  padding: 3.2rem var(--tdx-gut) .95rem;
  background: linear-gradient(to top, rgba(10,10,10,.88) 0%, rgba(10,10,10,.42) 55%, rgba(10,10,10,0) 100%);
  font-family: var(--font-mono); font-size: .62rem;
  letter-spacing: .14em; text-transform: uppercase;
  color: rgba(245,241,232,.78);
}
/* One line, and only one. The photograph carries this section; the sentence
   names what the reader is looking at and stops. */
.tdx-live__line {
  margin: clamp(1.3rem, 2.6vw, 2rem) 0 0;
  font-family: var(--font-body);
  font-size: clamp(.9rem, 1.05vw, 1.06rem);
  line-height: 1.62; color: var(--on-black);
  max-width: 54ch;
}

/* ══════════════════════════════════════════════════════════════════════
   §1 · REMOVED — the counter strip.

   This slot used to hold .tdx-record: four big figures counting our own
   internal plumbing (200 migrations, 502 RLS policies, 49 server-side
   functions, 4 iOS apps). Ethan, on the same four numbers when they showed
   up on /technology: "PISSING ME OFF WITH THESE RANDOM STATS NO ONE CARES
   ABOUT."

   The rule generalises past those four: counting your own artifacts —
   migrations, policies, functions, commits, lines — is vanity, because it
   answers a question no prospect asked.

   Its first replacement, a "work you can open right now" strip of three
   live products, was then cut too — under the page charter a general
   proof/trust exhibit is /technology's job, not this page's. The two live
   App Store apps survive inside expertise area 03 (.tdx-openable, §2),
   where they are evidence for one named capability instead of a standalone
   trust section. Do not reintroduce either a figure grid or a portfolio
   strip to this page.
   ══════════════════════════════════════════════════════════════════════ */

/* ══════════════════════════════════════════════════════════════════════
   §2 · THE DIRECTORY — five rows, one open at a time.

   Closed, a row is one line: index, name, and a single clause. Open, it
   runs the mechanic. The row IS the full width of the screen; the button
   inside it carries the gutter as padding, so the hairline between rows
   never stops short of the edge.
   ══════════════════════════════════════════════════════════════════════ */
.tdx-dir { padding-bottom: 0; }
.tdx-dir__head { margin-bottom: clamp(1.8rem, 3.4vw, 2.6rem); }
.tdx-dir__list { list-style: none; margin: 0; padding: 0; }
.tdx-sec > .tdx-dir__list { padding-inline: 0; }   /* rows carry their own gutter */

.tdx-row { border-top: 1px solid var(--tdx-line); }
.tdx-row:last-child { border-bottom: 1px solid var(--tdx-line); }

/* ── the trigger ───────────────────────────────────────────────────────── */
.tdx-row__h { margin: 0; font-weight: inherit; font-size: inherit; }
.tdx-row__btn {
  position: relative;
  display: grid;
  grid-template-columns: 3.4rem minmax(0, 15.5rem) minmax(0, 1fr) 1.6rem;
  align-items: baseline;
  gap: 0 clamp(.9rem, 2vw, 1.8rem);
  width: 100%;
  padding: clamp(1.15rem, 2.1vw, 1.6rem) var(--tdx-gut);
  background: none; border: 0; text-align: left;
  color: inherit; font-family: inherit;
  transition: background-color 160ms var(--tdx-ease);
}
.tdx-row__btn:focus-visible { outline: 2px solid var(--red-text); outline-offset: -2px; }

/* Left signal bar. Grows from the bottom of the row on hover, holds at full
   height while open. transform only — nothing here paints or lays out. */
.tdx-row__btn::before {
  content: ""; position: absolute; left: 0; top: 0; bottom: 0; width: 2px;
  background: var(--red);
  transform: scaleY(0); transform-origin: 50% 100%;
  transition: transform 220ms var(--tdx-ease);
}
.tdx-row[data-open="1"] .tdx-row__btn::before { transform: scaleY(1); }

.tdx-row__num {
  font-family: var(--font-mono); font-size: .62rem; letter-spacing: .16em;
  color: var(--red-text);
}
.tdx-row__name {
  font-family: var(--font-display); font-weight: 400;
  font-size: clamp(1.05rem, 1.9vw, 1.5rem);
  line-height: 1.15; letter-spacing: -.01em;
  color: var(--on-black);
  transition: color 160ms var(--tdx-ease);
}
.tdx-row__line {
  font-family: var(--font-body);
  font-size: clamp(.8rem, .88vw, .88rem); line-height: 1.55;
  color: var(--tdx-dim);
  transition: color 160ms var(--tdx-ease);
}

/* The +/- mark. Two 1px rules; the vertical one ROTATES flat rather than
   scaling to nothing, so there is never a frame where an element has no
   size (emil: nothing in the real world appears from or vanishes to zero). */
.tdx-row__mark {
  position: relative; justify-self: end; align-self: center;
  width: 13px; height: 13px;
  transition: transform 200ms var(--tdx-ease);
}
.tdx-row__mark::before,
.tdx-row__mark::after {
  content: ""; position: absolute; left: 0; top: 6px;
  width: 13px; height: 1px; background: var(--on-black); opacity: .55;
  transition: transform 240ms var(--tdx-ease), opacity 160ms var(--tdx-ease);
}
.tdx-row__mark::after { transform: rotate(90deg); }
.tdx-row[data-open="1"] .tdx-row__mark::after { transform: rotate(0deg); }
.tdx-row[data-open="1"] .tdx-row__mark::before,
.tdx-row[data-open="1"] .tdx-row__mark::after { opacity: 1; }

/* Press feedback. A full-bleed row must not scale — it would shear the
   hairlines above and below it — so the mark takes the press instead. */
.tdx-row__btn:active .tdx-row__mark { transform: translateY(2px); }

@media (hover: hover) and (pointer: fine) {
  .tdx-row__btn:hover { background-color: rgba(245,241,232,.028); }
  .tdx-row__btn:hover .tdx-row__name { color: #fff; }
  .tdx-row__btn:hover .tdx-row__line { color: var(--tdx-mute); }
  .tdx-row__btn:hover::before { transform: scaleY(1); }
  .tdx-row__btn:hover .tdx-row__mark::before,
  .tdx-row__btn:hover .tdx-row__mark::after { opacity: 1; }
}

@media (max-width: 900px) {
  .tdx-row__btn {
    grid-template-columns: 2.6rem minmax(0, 1fr) 1.4rem;
    grid-template-areas: "num name mark" ".   line line";
    row-gap: .4rem;
  }
  .tdx-row__num  { grid-area: num; }
  .tdx-row__name { grid-area: name; }
  .tdx-row__mark { grid-area: mark; }
  .tdx-row__line { grid-area: line; }
}

/* ── the panel ─────────────────────────────────────────────────────────── */
.tdx-row__panel {
  display: grid;
  grid-template-rows: 0fr;
  transition: grid-template-rows var(--tdx-close) var(--tdx-ease);
}
.tdx-row[data-open="1"] .tdx-row__panel {
  grid-template-rows: 1fr;
  transition-duration: var(--tdx-open);
}
.tdx-row__clip { overflow: hidden; min-height: 0; }

/* Closed values are the EXIT transition (faster, no delay); the open block
   carries the slower entry with a small delay so the height has started
   before the content fades up. transform-origin sits at the top-left, which
   is where the button the reader just pressed is. */
.tdx-row__body {
  transform-origin: 0 0;
  opacity: 0; transform: translateY(-6px);
  transition: opacity 130ms var(--tdx-ease), transform 150ms var(--tdx-ease);
  padding: 0 var(--tdx-gut) clamp(2rem, 4vw, 3.2rem);
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(0, 19rem);
  gap: clamp(1.6rem, 3.4vw, 3.4rem);
  align-items: start;
}
.tdx-row[data-open="1"] .tdx-row__body {
  opacity: 1; transform: none;
  transition: opacity 200ms var(--tdx-ease) 60ms, transform 260ms var(--tdx-ease) 40ms;
}
@media (max-width: 1000px) { .tdx-row__body { grid-template-columns: 1fr; } }

/* Right column: the spec list and the one true proof line. No box. */
.tdx-spec { list-style: none; margin: 0; padding: 0; }
.tdx-spec li {
  display: flex; align-items: baseline; gap: .6rem;
  padding: .5rem 0;
  border-bottom: 1px solid var(--tdx-line-soft);
  font-family: var(--font-mono); font-size: .655rem;
  letter-spacing: .045em; line-height: 1.5;
  color: var(--tdx-dim);
}
.tdx-spec li::before {
  content: ""; flex: 0 0 auto;
  width: 5px; height: 5px; background: var(--red);
  transform: translateY(-1px) rotate(45deg);
}
.tdx-spec li b { font-weight: 500; color: var(--on-black); }
.tdx-proofline {
  margin: 1.1rem 0 0;
  font-family: var(--font-body); font-size: .8rem; line-height: 1.6;
  color: var(--tdx-mute); max-width: 44ch;
}
.tdx-proofline em { font-style: italic; color: var(--on-black); }
.tdx-caveat {
  margin: .7rem 0 0;
  font-family: var(--font-mono); font-size: .62rem; letter-spacing: .05em;
  line-height: 1.65; color: rgba(245,241,232,.52); max-width: 46ch;
}

/* Openable evidence — one or two real, running systems per expertise area,
   named INSIDE the capability they demonstrate rather than collected into a
   portfolio section. That distinction is the page charter: /tech answers
   what we can build for you, /technology answers who we are (and its hero
   is becoming a rotating showcase of these same projects, so a second
   showcase here would just be that page again).

   Everything linked was checked on 2026-08-13:
     The Dispatch    id 6784010543, v1.0.8,  CA App Store, TNAADO INC.
     Mr. Indecisive  id 6784115836, v1.0.13, CA App Store, TNAADO INC.
     shoot / admin / links / tnaado.ca/live-map / tools  all HTTP 200
   Those two are the ONLY TNAADO apps published on the CA App Store — an
   iTunes search for the seller returns exactly them. Other iOS builds are
   TestFlight-only and Citadel is on hold, so none of them appear here:
   the whole value of this list is that the reader can go and look.
   photos.tnaado.ca was checked too and did not resolve, so it is not
   listed. Re-verify before adding or editing an entry.

   Sign-in-walled systems say so, so the list never implies a reader can
   see more than they can. Not a card: one hairline per item, no box. */
.tdx-openable { list-style: none; margin: .9rem 0 0; padding: 0; }
.tdx-openable li { padding: .75rem 0; border-top: 1px solid var(--tdx-line-soft); }
.tdx-openable a {
  display: flex; flex-wrap: wrap; align-items: baseline; gap: .3rem .7rem;
  font-family: var(--font-mono); font-size: .68rem; letter-spacing: .03em;
  color: var(--on-black);
  transition: color 160ms var(--tdx-ease);
}
.tdx-openable a b { font-weight: 500; border-bottom: 1px solid rgba(232,69,90,.45); padding-bottom: 1px; }
.tdx-openable a span { color: rgba(245,241,232,.55); font-size: .6rem; letter-spacing: .1em; }
.tdx-openable a::after { content: "↗"; color: var(--red-text); font-size: .7rem; }
.tdx-openable em {
  display: block; margin-top: .35rem; font-style: normal;
  font-family: var(--font-body); font-size: .755rem; line-height: 1.55;
  color: var(--tdx-dim);
}
.tdx-openable a:focus-visible { outline: 2px solid var(--red-text); outline-offset: 3px; }
@media (hover: hover) and (pointer: fine) {
  .tdx-openable a:hover { color: #fff; }
  .tdx-openable a:hover b { border-bottom-color: var(--red-text); }
}

/* ══════════════════════════════════════════════════════════════════════
   §3 · THE STAGE — shared chrome for all five demos.

   No border, no card, no plate. The demo elements carry their own weight
   on the page's own ink, so a panel never reads as a box dropped into the
   page. The only chrome is a mono caption rail across the top, which is
   the demo's own instrument label.
   ══════════════════════════════════════════════════════════════════════ */
.tdx-stage { min-width: 0; }
.tdx-stage__rail {
  display: flex; flex-wrap: wrap; align-items: baseline;
  gap: .5rem 1rem; padding-bottom: .6rem;
  border-bottom: 1px solid var(--tdx-line);
  font-family: var(--font-mono); font-size: .6rem;
  letter-spacing: .12em; text-transform: uppercase;
  color: rgba(245,241,232,.55);
}
.tdx-stage__rail b { font-weight: 500; color: var(--red-text); letter-spacing: .12em; }
.tdx-stage__rail code {
  font-family: var(--font-mono); text-transform: none; letter-spacing: .02em;
  color: var(--on-black);
}
.tdx-stage__note {
  margin: .9rem 0 0;
  font-family: var(--font-mono); font-size: .62rem;
  letter-spacing: .05em; line-height: 1.6;
  color: rgba(245,241,232,.52);
}

/* ── DEMO 01 · ROW-LEVEL SECURITY ──────────────────────────────────────
   The mechanic, literally: the reader changes who is asking, and the rows
   that the policy denies physically leave the table while the survivors
   close the gap. Rows are a fixed --rh tall and the viewport is a fixed
   height, so a filter is pure transform on the rows — no reflow, no layout
   read, nothing repaints but the row that changed. */
.rls { --rh: 30px; padding-top: 1rem; }
.rls__who {
  display: flex; flex-wrap: wrap; gap: .4rem; margin-bottom: .9rem;
}
.rls__who button {
  font-family: var(--font-mono); font-size: .62rem; letter-spacing: .06em;
  padding: .34rem .6rem;
  color: var(--tdx-dim); background: none;
  border: 1px solid var(--tdx-line);
  transition: color 160ms var(--tdx-ease), border-color 160ms var(--tdx-ease), background-color 160ms var(--tdx-ease);
}
.rls__who button[aria-pressed="true"] {
  color: var(--on-black); border-color: rgba(200,16,46,.62);
  background-color: rgba(200,16,46,.09);
}
@media (hover: hover) and (pointer: fine) {
  .rls__who button:hover { color: var(--on-black); border-color: rgba(245,241,232,.28); }
}
.rls__who button:focus-visible { outline: 2px solid var(--red-text); outline-offset: 1px; }

.rls__policy {
  font-family: var(--font-mono); font-size: .655rem; line-height: 1.7;
  color: rgba(245,241,232,.5); margin: 0 0 1rem;
  overflow-x: auto; white-space: nowrap; padding-bottom: .2rem;
}
.rls__policy b { color: var(--on-black); font-weight: 500; }
.rls__policy i { font-style: normal; color: var(--red-text); }

.rls__tablehead,
.rls__row {
  display: grid;
  grid-template-columns: minmax(0, 7.5rem) minmax(0, 1fr) minmax(0, 5rem) minmax(0, 4.5rem);
  gap: .8rem;
  font-family: var(--font-mono); font-size: .64rem;
  align-items: center;
}
.rls__tablehead {
  height: 24px; color: rgba(245,241,232,.52);
  letter-spacing: .12em; text-transform: uppercase;
  border-bottom: 1px solid var(--tdx-line);
}
.rls__body {
  position: relative;
  height: calc(var(--rh) * var(--n, 6));
  overflow: hidden;
  transition: height 320ms var(--tdx-ease);
}
/* anon returns nothing at all. An empty frame is the honest result and the
   most persuasive one, so it is labelled rather than padded out. */
.rls__empty {
  position: absolute; inset: 0;
  display: flex; align-items: center;
  font-family: var(--font-mono); font-size: .62rem; letter-spacing: .06em;
  color: rgba(245,241,232,.58);
  opacity: 0; transition: opacity 200ms var(--tdx-ease);
  pointer-events: none;
}
.rls[data-empty="1"] .rls__empty { opacity: 1; }
.rls__row {
  position: absolute; left: 0; right: 0; top: 0;
  height: var(--rh);
  color: rgba(245,241,232,.72);
  border-bottom: 1px solid var(--tdx-line-soft);
  /* index position, set once in JS */
  transform: translateY(calc(var(--i) * var(--rh)));
  transition: transform 320ms var(--tdx-ease), opacity 200ms var(--tdx-ease);
}
/* will-change ONLY while the row is open. ART-GUIDE §2.3 caps a page at
   ~3 promoted layers and the hero already spends several; leaving six
   table rows and the payment token permanently promoted would keep seven
   compositor layers alive for panels nobody has opened. */
.tdx-row[data-open="1"] .rls__row { will-change: transform; }
.rls__row[data-vis="0"] {
  opacity: 0;
  transform: translateY(calc(var(--i) * var(--rh))) translateX(-18px);
  transition: transform 260ms var(--tdx-ease), opacity 140ms var(--tdx-ease);
}
.rls__row[data-vis="1"] { transform: translateY(calc(var(--k) * var(--rh))); opacity: 1; }
.rls__row em { font-style: normal; color: var(--red-text); }
.rls__row span { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.rls__count {
  margin: .85rem 0 0;
  font-family: var(--font-mono); font-size: .62rem; letter-spacing: .08em;
  color: rgba(245,241,232,.58);
}
.rls__count b {
  color: var(--on-black); font-weight: 500;
  font-variant-numeric: tabular-nums;
}
.rls__count i { font-style: normal; color: var(--red-text); }
@media (max-width: 620px) {
  .rls__tablehead, .rls__row { grid-template-columns: minmax(0, 5.6rem) minmax(0, 1fr) minmax(0, 4rem); font-size: .6rem; }
  .rls__col--amt { display: none; }
}
/* The policy is nowrap + horizontally scrollable on wide screens so the SQL
   reads as one statement. On a phone that clipped it mid-keyword ("...for
   sel") against the right edge, which looks like a rendering fault rather
   than a scroll affordance, so it wraps there instead. */
@media (max-width: 760px) {
  .rls__policy { white-space: normal; overflow-x: visible; font-size: .6rem; }
}

/* ── DEMO 02 · PAYMENT FLOW ────────────────────────────────────────────
   A real Stripe PaymentIntent walking its real states, including the
   decline branch and the retry that follows it. The token is one absolutely
   positioned dot moved by transform; the rail and the nodes never move. */
.pay { padding-top: 1.4rem; }
.pay__track {
  position: relative;
  height: 104px;
  margin-bottom: .6rem;
}
.pay__line {
  position: absolute; left: 0; right: 0; top: 22px; height: 1px;
  background: var(--tdx-line);
}
.pay__nodes {
  position: absolute; left: 0; right: 0; top: 0;
  display: flex; justify-content: space-between; align-items: flex-start;
}
.pay__node {
  position: relative;
  display: flex; flex-direction: column; align-items: center;
  gap: .5rem; width: 20%;
}
.pay__dot {
  width: 9px; height: 9px; margin-top: 18px;
  background: var(--ink);
  border: 1px solid rgba(245,241,232,.3);
  border-radius: 50%;
  transition: border-color 200ms var(--tdx-ease), background-color 200ms var(--tdx-ease), transform 200ms var(--tdx-ease);
}
.pay__node[data-done="1"] .pay__dot { border-color: rgba(245,241,232,.6); background: rgba(245,241,232,.6); }
.pay__node[data-at="1"] .pay__dot { border-color: var(--red); background: var(--red); transform: scale(1.35); }
.pay__label {
  font-family: var(--font-mono); font-size: .555rem;
  letter-spacing: .05em; text-align: center; line-height: 1.4;
  color: rgba(245,241,232,.52);
  transition: color 200ms var(--tdx-ease);
}
.pay__node[data-at="1"] .pay__label,
.pay__node[data-done="1"] .pay__label { color: rgba(245,241,232,.72); }

/* The decline branch, hanging below the rail. */
.pay__fail {
  position: absolute; top: 64px; left: 0;
  display: flex; align-items: center; gap: .5rem;
  opacity: 0; transform: translateY(-6px);
  transition: opacity 180ms var(--tdx-ease), transform 220ms var(--tdx-ease);
}
.pay[data-branch="1"] .pay__fail { opacity: 1; transform: none; }
.pay__fail span {
  font-family: var(--font-mono); font-size: .62rem; letter-spacing: .05em;
  color: var(--red-text); white-space: nowrap;
}
.pay__drop {
  position: absolute; top: 23px; width: 1px; height: 40px;
  background: linear-gradient(to bottom, var(--red), rgba(200,16,46,0));
  transform: scaleY(0); transform-origin: 50% 0;
  transition: transform 220ms var(--tdx-ease);
}
.pay[data-branch="1"] .pay__drop { transform: scaleY(1); }

.pay__token {
  position: absolute; top: 18px; left: 0;
  width: 17px; height: 17px; margin-left: -8.5px;
  border-radius: 50%;
  background: rgba(200,16,46,.16);
  border: 1px solid var(--red);
  transform: translate(var(--tx, 0px), 0);
  transition: transform 460ms var(--tdx-ease), background-color 300ms var(--tdx-ease);
}
.tdx-row[data-open="1"] .pay__token { will-change: transform; }
.pay[data-branch="1"] .pay__token { transform: translate(var(--tx, 0px), 42px); }
.pay__token::after {
  content: ""; position: absolute; inset: 4px;
  border-radius: 50%; background: var(--red);
}

.pay__log {
  display: grid; grid-template-columns: auto minmax(0, 1fr) auto;
  gap: .5rem .9rem; align-items: baseline;
  border-top: 1px solid var(--tdx-line);
  padding-top: .8rem;
  font-family: var(--font-mono); font-size: .65rem; letter-spacing: .03em;
}
.pay__log dt { color: rgba(245,241,232,.52); }
.pay__log dd { margin: 0; color: var(--on-black); min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.pay__log dd.is-fail { color: var(--red-text); }
.pay__amt { font-variant-numeric: tabular-nums; color: rgba(245,241,232,.6); }
@media (max-width: 700px) {
  .pay__label { font-size: .48rem; }
  .pay__track { height: 112px; }
}
@media (max-width: 460px) {
  .pay__label { display: none; }
  .pay__track { height: 92px; }
  .pay__fail { top: 56px; }
}

/* ── DEMO 03 · IDEA TO SCREENSHOTS ─────────────────────────────────────
   One phone, four stages, crossfading in place: the written brief, the
   grey wireframe, the built interface, and the App Store screenshot. The
   rail underneath is the same four stages as a progress track. Entering
   stages start at scale(.985), never at zero. */
.app { display: grid; grid-template-columns: 186px minmax(0, 1fr); gap: clamp(1.2rem, 3vw, 2.4rem); padding-top: 1.2rem; align-items: start; }
.app__phone {
  position: relative;
  width: 186px; height: 380px;
  border: 1px solid rgba(245,241,232,.22);
  border-radius: 22px;
  background: #0d0d0d;
  overflow: hidden;
  flex: 0 0 auto;
}
.app__phone::before {
  content: ""; position: absolute; top: 8px; left: 50%; width: 54px; height: 5px;
  margin-left: -27px; border-radius: 3px; background: rgba(245,241,232,.18); z-index: 4;
}
.app__screen {
  position: absolute; inset: 0;
  padding: 26px 14px 14px;
  opacity: 0; transform: scale(.985);
  transition: opacity 260ms var(--tdx-ease), transform 300ms var(--tdx-ease);
}
.tdx-row[data-open="1"] .app__screen { will-change: transform, opacity; }
.app__screen[data-on="1"] { opacity: 1; transform: none; }

.app__screen--brief { display: flex; align-items: center; }
.app__brief {
  font-family: var(--font-mono); font-size: .585rem; line-height: 1.85;
  color: rgba(245,241,232,.66);
}
.app__brief b { display: block; color: var(--red-text); font-weight: 500; letter-spacing: .12em; margin-bottom: .7rem; font-size: .53rem; }

.app__wire { display: flex; flex-direction: column; gap: 7px; }
.app__wire i {
  display: block; background: rgba(245,241,232,.09);
  border: 1px dashed rgba(245,241,232,.16);
}
.app__wire i:nth-child(1) { height: 15px; width: 62%; }
.app__wire i:nth-child(2) { height: 76px; }
.app__wire i:nth-child(3) { height: 34px; }
.app__wire i:nth-child(4) { height: 34px; }
.app__wire i:nth-child(5) { height: 34px; }
.app__wire i:nth-child(6) { height: 26px; width: 48%; margin-top: 4px; }

.app__built { display: flex; flex-direction: column; gap: 7px; }
.app__built .app__built-label {
  display: block;
  font-family: var(--font-display); font-weight: 400; font-size: .82rem;
  color: var(--on-black); margin: 0 0 3px;
}
.app__built i {
  display: block; height: 34px;
  background: rgba(245,241,232,.055);
  border-left: 2px solid var(--red);
}
.app__built i:first-of-type { height: 76px; background: rgba(200,16,46,.11); }
.app__built u {
  display: block; margin-top: 4px; height: 26px; width: 48%;
  background: var(--red); text-decoration: none;
}

.app__shot { padding-top: 30px; }
.app__shot strong {
  display: block; font-family: var(--font-display); font-weight: 400;
  font-size: .95rem; line-height: 1.2; color: var(--on-black); margin-bottom: .35rem;
}
.app__shot small {
  display: block; font-family: var(--font-body); font-size: .58rem;
  line-height: 1.5; color: rgba(245,241,232,.55); margin-bottom: .8rem;
}
.app__shotui { display: flex; flex-direction: column; gap: 6px; }
.app__shotui i { display: block; height: 30px; background: rgba(245,241,232,.06); border-left: 2px solid var(--red); }
.app__shotui i:first-child { height: 64px; background: rgba(200,16,46,.13); }

.app__steps { list-style: none; margin: 0; padding: 0; }
.app__step {
  position: relative;
  padding: .62rem 0 .62rem 1.5rem;
  border-bottom: 1px solid var(--tdx-line-soft);
  font-family: var(--font-mono); font-size: .645rem; letter-spacing: .04em;
  color: rgba(245,241,232,.52);
  transition: color 220ms var(--tdx-ease);
}
.app__step::before {
  content: ""; position: absolute; left: 0; top: 1.02rem;
  width: 7px; height: 7px; border: 1px solid rgba(245,241,232,.3);
  transform: rotate(45deg) scale(.85);
  transition: transform 240ms var(--tdx-ease), background-color 240ms var(--tdx-ease), border-color 240ms var(--tdx-ease);
}
.app__step[data-on="1"] { color: var(--on-black); }
.app__step[data-on="1"]::before { background: var(--red); border-color: var(--red); transform: rotate(45deg) scale(1); }
.app__step[data-done="1"] { color: rgba(245,241,232,.6); }
.app__step[data-done="1"]::before { background: rgba(245,241,232,.55); border-color: rgba(245,241,232,.55); }
.app__step { max-width: 74ch; }
.app__step b { display: block; font-weight: 400; color: inherit; }
.app__step em {
  display: block; font-style: normal; margin-top: .2rem;
  font-size: .62rem; color: rgba(245,241,232,.52);
}
.app__step[data-on="1"] em { color: rgba(245,241,232,.8); }
.app__step[data-done="1"] em { color: rgba(245,241,232,.58); }
@media (max-width: 560px) {
  .app { grid-template-columns: 1fr; justify-items: start; }
  .app__phone { width: 168px; height: 344px; }
}

/* ── DEMO 04 · MIGRATIONS ──────────────────────────────────────────────
   Real filenames from TNAADO Inc's own admin repo, applying in order, one
   at a time, with the schema version climbing as each one lands. This is
   what "every schema change is a numbered step" looks like; the sentence
   that used to make that claim in prose has been deleted. */
.mig { padding-top: 1.2rem; }
.mig__head {
  display: flex; justify-content: space-between; align-items: baseline;
  gap: 1rem; padding-bottom: .55rem;
  border-bottom: 1px solid var(--tdx-line);
  font-family: var(--font-mono); font-size: .6rem;
  letter-spacing: .12em; text-transform: uppercase;
  color: rgba(245,241,232,.52);
}
.mig__ver { color: var(--on-black); font-variant-numeric: tabular-nums; letter-spacing: .06em; }
.mig__list { list-style: none; margin: 0; padding: 0; }
.mig__item {
  position: relative;
  display: grid; grid-template-columns: 1.1rem minmax(0, 1fr) auto;
  gap: .7rem; align-items: center;
  padding: .46rem 0 .46rem .55rem;
  border-bottom: 1px solid var(--tdx-line-soft);
  font-family: var(--font-mono); font-size: .625rem; letter-spacing: .01em;
  color: rgba(245,241,232,.52);
  transition: color 260ms var(--tdx-ease);
}
.mig__item::before {
  content: ""; position: absolute; left: 0; top: 0; bottom: 0; width: 2px;
  background: var(--red);
  transform: scaleX(0); transform-origin: 0 50%;
  transition: transform 240ms var(--tdx-ease);
}
.mig__item[data-state="applied"] { color: rgba(245,241,232,.9); }
.mig__item[data-state="applied"]::before { transform: scaleX(1); }
.mig__tick {
  width: 11px; height: 11px; position: relative;
  border: 1px solid rgba(245,241,232,.22);
  transition: border-color 240ms var(--tdx-ease);
}
.mig__tick::after {
  content: ""; position: absolute; inset: 2px;
  background: var(--red);
  transform: scale(.4); opacity: 0;
  transition: transform 240ms var(--tdx-ease), opacity 200ms var(--tdx-ease);
}
.mig__item[data-state="applied"] .mig__tick { border-color: rgba(200,16,46,.65); }
.mig__item[data-state="applied"] .mig__tick::after { transform: scale(1); opacity: 1; }
.mig__name { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.mig__ms {
  font-variant-numeric: tabular-nums; color: rgba(245,241,232,.52);
  opacity: 0; transition: opacity 240ms var(--tdx-ease);
}
.mig__item[data-state="applied"] .mig__ms { opacity: 1; }
@media (max-width: 560px) { .mig__item { font-size: .585rem; } }

/* ── DEMO 05 · SCHEDULED WORK ──────────────────────────────────────────
   A 24-hour clock with a playhead. Each lane is a real server-side
   function; each marker is when it actually runs. The log underneath
   prints as the playhead crosses. */
/* --job-lab is the width of the function-name column PLUS the grid gap. The
   ruler, the lane tracks and the playhead must all start at exactly that
   offset or the clock lies: before this was a variable the ruler ran the
   full width while the tracks began after the labels, so the 03:00 tick sat
   about 165px left of the 03:00 marker it was supposed to be labelling. If
   you change .job__lane's first column, change this with it — one number. */
.job { padding-top: 1.2rem; --job-lab: 10.3rem; }
.job__ruler {
  position: relative; height: 16px;
  margin-left: var(--job-lab);
  border-bottom: 1px solid var(--tdx-line);
  margin-bottom: .5rem;
}
.job__ruler span {
  position: absolute; bottom: 2px;
  font-family: var(--font-mono); font-size: .58rem; letter-spacing: .08em;
  color: rgba(245,241,232,.55); transform: translateX(-50%);
}
.job__ruler span:first-child { transform: none; }
.job__ruler span:last-child { transform: translateX(-100%); }

.job__lanes { position: relative; }
.job__lane {
  position: relative;
  display: grid; grid-template-columns: minmax(0, 9.5rem) minmax(0, 1fr);
  gap: .8rem; align-items: center;
  height: 30px;
  border-bottom: 1px solid var(--tdx-line-soft);
}
.job__fn {
  font-family: var(--font-mono); font-size: .625rem; letter-spacing: .01em;
  color: rgba(245,241,232,.6);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.job__track { position: relative; height: 100%; }
.job__hit {
  position: absolute; top: 50%; width: 7px; height: 7px; margin: -3.5px 0 0 -3.5px;
  border-radius: 50%;
  background: rgba(245,241,232,.16);
  left: var(--at);
  transition: background-color 220ms var(--tdx-ease), transform 260ms var(--tdx-ease);
}
.job__hit[data-fired="1"] { background: var(--red); transform: scale(1.5); }
.job__hit::after {
  content: ""; position: absolute; inset: -5px;
  border: 1px solid var(--red); border-radius: 50%;
  opacity: 0; transform: scale(.6);
  transition: opacity 420ms var(--tdx-ease), transform 420ms var(--tdx-ease);
}
.job__hit[data-fired="1"]::after { opacity: 0; transform: scale(2.1); }
.job__hit[data-flash="1"]::after { opacity: .9; transform: scale(.6); transition: none; }

.job__head {
  position: absolute; top: 0; bottom: 0; width: 1px;
  background: rgba(245,241,232,.34);
  left: 0; transform: translateX(var(--px, 0px));
  pointer-events: none;
}
.job__headwrap { position: absolute; left: var(--job-lab); right: 0; top: 0; bottom: 0; pointer-events: none; }
.job__log {
  margin: .9rem 0 0; min-height: 1.7rem;
  font-family: var(--font-mono); font-size: .62rem; letter-spacing: .03em;
  color: rgba(245,241,232,.5);
}
.job__log b { color: var(--on-black); font-weight: 500; }
.job__log i { font-style: normal; color: var(--red-text); }
@media (max-width: 620px) {
  .job { --job-lab: 7.8rem; }        /* 7rem column + .8rem gap */
  .job__lane { grid-template-columns: minmax(0, 7rem) minmax(0, 1fr); }
  .job__fn { font-size: .585rem; }
}

/* ══════════════════════════════════════════════════════════════════════
   §4 · HOW WE BUILD — the responsible-AI / "vibe coder" statement.

   Named by Ethan. Firm voice: TNAADO is a FIRM, never an "agency", and
   never a "studio" (that word belongs to TNAADO Labs). The term "vibe
   coders" is owned here rather than apologised for — it is on Ethan's own
   whiteboard as a story line. The claim is falsifiable and specific: the
   tool drafts, a named senior engineer is accountable for what merges.
   ══════════════════════════════════════════════════════════════════════ */
.tdx-build__cols {
  display: grid; grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: 0 clamp(1.4rem, 3vw, 3rem);
  margin-top: clamp(1.8rem, 3.4vw, 2.6rem);
  border-top: 1px solid var(--tdx-line);
}
.tdx-build__col { padding: 1.3rem 0 0; }
.tdx-build__k {
  font-family: var(--font-mono); font-size: .58rem; letter-spacing: .16em;
  text-transform: uppercase; color: var(--red-text);
  display: block; margin-bottom: .6rem;
}
.tdx-build__col h3 {
  font-family: var(--font-display); font-weight: 400;
  font-size: 1.02rem; line-height: 1.25; color: var(--on-black);
  margin: 0 0 .45rem;
}
.tdx-build__col p {
  font-family: var(--font-body); font-size: .8rem; line-height: 1.65;
  color: var(--tdx-dim); margin: 0;
}
@media (max-width: 820px) {
  .tdx-build__cols { grid-template-columns: 1fr; gap: 0; }
  .tdx-build__col { border-bottom: 1px solid var(--tdx-line-soft); padding-bottom: 1.2rem; }
  .tdx-build__col:last-child { border-bottom: 0; }
}

/* ══════════════════════════════════════════════════════════════════════
   §5 · THE PHOTOGRAPH — full bleed, inside §4.

   It is deliberately NOT its own section. A standalone "the office" /
   "where the work happens" section is firm-story content, and the page
   charter puts firm story on /technology. Here the frame is evidence for
   §4's three rules: this is what the tooling looks like in use.

   assets/bts/bts-claude-code-desk-wide.jpg is Ethan's own frame. Source
   5712x4284; the wide file is a centre 2:1 crop resampled to 2400x1200
   (306 KB, quality 42), matching how this repo sizes its other real
   photography (street photos 1800px, BTS 1400px). The original 4:3 file
   is kept and served under 768px via <picture>, because a 2:1 strip at
   390px wide is 195px tall and shows nothing.

   No frame and no border — the photograph runs edge to edge.

   T-1120: THE CAPTION CAME OFF THE PHOTOGRAPH. It was pinned over the
   image's bottom edge under a `to top .86 / .40 / transparent` gradient.
   This caption is three lines at 390 and two at 768, uppercase mono at
   .585rem — and the bottom third of THIS frame is the lit MacBook screen
   it is describing. Only the last line ever reached the .86 stop; the
   lines above it sat at .40 and less over white-on-dark terminal text,
   where sampled worst-case ground runs past rgb(190,195,200) against type
   compositing to rgb(166,163,157) — roughly 1.2:1, i.e. unreadable, on the
   exact caption whose whole job is naming what is on that screen.

   It is now in normal flow under the image, on flat ink: 8.0:1 on every
   line, at every width, regardless of how long the caption gets or how
   bright the frame is. The photograph is left completely unobstructed.
   ══════════════════════════════════════════════════════════════════════ */
.tdx-room__figure { margin: clamp(1.8rem, 3.4vw, 2.6rem) 0 0; padding-inline: 0 !important; position: relative; }
.tdx-room__figure img {
  display: block; width: 100%;
  /* T-1095: max-height:70vh did exactly what it says and gave 518px on a 740px
     window. --band-md, a step below the client photograph above it, so the two
     photo bands on this page do not read at the same weight. */
  height: var(--band-md); object-fit: cover; object-position: 58% 50%;
}
.tdx-room__cap {
  margin: 0;
  padding: .85rem var(--tdx-gut) 0;
  font-family: var(--font-mono); font-size: .585rem; line-height: 1.7;
  letter-spacing: .14em; text-transform: uppercase;
  color: rgba(245,241,232,.66);
  max-width: 70ch;
}

/* ══════════════════════════════════════════════════════════════════════
   §6 · HANDOFF — the link out to /technology.

   REMOVED here, 2026-08-13: a three-card client-portfolio grid
   (.tdx-ship__*) and a three-product "work you can open" strip. Both
   failed the page charter — /tech answers WHAT WE CAN BUILD FOR YOU;
   /technology answers WHO WE ARE AND WHY US, and a portfolio is squarely
   the second. The two live App Store apps did not disappear with it: they
   moved into expertise area 03, where they are evidence for a specific
   capability rather than a general trust exhibit. Do not add a portfolio
   grid, an office section or an origin story to this page.

   The /technology link below is load-bearing (ART-GUIDE §5). It is the
   only thing on the page that names the boundary between the two, and
   losing it is how they converged into near-duplicates before.
   ══════════════════════════════════════════════════════════════════════ */
.tdx-handoff { padding-block: clamp(2.4rem, 4.4vw, 3.4rem); }
.tdx-more {
  display: flex; flex-wrap: wrap; align-items: center;
  justify-content: space-between; gap: .8rem 2rem;
  padding: 1.2rem 0;
  border-top: 1px solid var(--tdx-line);
}
.tdx-more p {
  margin: 0; font-family: var(--font-body); font-size: .82rem;
  line-height: 1.6; color: var(--tdx-dim); max-width: 62ch;
}
.tdx-more p b { color: var(--on-black); font-weight: 500; }
.tdx-link {
  font-family: var(--font-mono); font-size: .655rem; letter-spacing: .12em;
  text-transform: uppercase; color: var(--red-text);
  border-bottom: 1px solid rgba(232,69,90,.4); padding-bottom: 2px;
  white-space: nowrap;
  transition: border-color 160ms var(--tdx-ease), color 160ms var(--tdx-ease);
}
.tdx-link:hover, .tdx-link:focus-visible { border-bottom-color: var(--red-text); outline: none; }

/* ══════════════════════════════════════════════════════════════════════
   REDUCED MOTION — the exact kill block ART-GUIDE §2.5 requires: the
   global nuke plus an explicit per-selector kill listing every animated
   thing this file adds. The disclosure still opens (it must, or the
   content is unreachable) — it just opens instantly. The five demos are
   frozen by the JS on their most informative frame, so nothing is left
   half-drawn.
   ══════════════════════════════════════════════════════════════════════ */
@media (prefers-reduced-motion: reduce) {
  .tdx-row__panel,
  .tdx-row__body,
  .tdx-row__btn,
  .tdx-row__btn::before,
  .tdx-row__mark,
  .tdx-row__mark::before,
  .tdx-row__mark::after,
  .tdx-row__name,
  .tdx-row__line,
  .rls__row, .rls__who button, .rls__body, .rls__empty,
  .pay__token, .pay__dot, .pay__label, .pay__fail, .pay__drop,
  .app__screen, .app__step, .app__step::before,
  .mig__item, .mig__item::before, .mig__tick, .mig__tick::after, .mig__ms,
  .job__hit, .job__hit::after, .job__head,
  .tdx-openable a, .tdx-openable a b, .tdx-link {
    transition: none !important;
    animation: none !important;
    will-change: auto !important;
  }
  .pay__drop { transform: scaleY(1); }
  .job__hit::after { opacity: 0; }
}
