/* ════════════════════════════════════════════════════════════
   TNAADO — /technology PROOF SECTIONS
   2026-08-13.

   WHY THIS FILE EXISTS
   ────────────────────
   Ethan, on both technology pages: "the technology pages is way too
   much blabla bla text nothing there sells a reader."

   The fix is not shorter paragraphs — it is deleting paragraphs and
   putting evidence in their place. Everything styled in this file
   renders REAL, CHECKABLE THINGS and nothing else:

     .tpx-*     the shipped-work carousel — real rows out of the
                Supabase `portfolio_items` table (real titles, real
                stacks, real screenshots), never hand-written cards.
     .tpf-*     the fact strip — each figure verified against a live
                source on 2026-08-13 (App Store lookup API, the live
                sitemap, an HTTP status check). See the HTML comment
                on each figure for what was checked and when.
     .tpb-*     the full-bleed photo bands — real, unretouched photos
                of the actual Toronto office, resize/compress only.

   The one rule that governs every value here: if it cannot be
   sourced to the portfolio table, a live URL, or something visible
   in a real photograph, it does not go on the page.

   TOKENS ONLY — rgba(245,241,232,X) is this codebase's elevation
   scale (the body is dark sitewide since T-1040), var(--red)
   #c8102e the only accent. Flat corners throughout, matching
   .sv-work-card / .cs-card / tech-directory.css. No new hue, radius
   or shadow is invented in this file.
   ════════════════════════════════════════════════════════════ */

/* ── FULL-BLEED PHOTO BAND ──────────────────────────────────
   Border to border, no padded box (a standing note this pass: the
   page had too many things sitting in floated cards). Used by the
   Toronto/no-outsourcing band and the agents band. Both re-pin the
   dark reading tokens the same way .page-hero/.sv-close do, so the
   shared .eyebrow/.sv-h2/.sv-lede classes stay legible on a dark
   photograph regardless of the page's permanent light theme — no
   [data-theme] override needed anywhere in this file. */
.tpb {
  position: relative; overflow: hidden; isolation: isolate;
  display: flex; align-items: flex-end;
  min-height: clamp(420px, 54vw, 600px);
  --ink: #0a0a0a;
  --on-black: #f5f1e8;
  --on-black-mute: rgba(245,241,232,.68);
  --on-black-faint: rgba(245,241,232,.52);
  --red-text: #e8455a;
}
.tpb__photo {
  position: absolute; inset: 0; z-index: 0;
  width: 100%; height: 100%; object-fit: cover;
  filter: grayscale(.14) contrast(1.04);
}
.tpb__scrim {
  position: absolute; inset: 0; z-index: 1;
  background:
    linear-gradient(to top, rgba(10,10,10,.94) 0%, rgba(10,10,10,.60) 38%, rgba(10,10,10,.16) 70%, transparent 100%),
    linear-gradient(100deg, rgba(10,10,10,.55) 0%, transparent 52%);
}
.tpb__inner {
  position: relative; z-index: 2;
  width: 100%; max-width: 1140px; margin: 0 auto;
  padding: clamp(2.4rem, 6vw, 4rem) 1.5rem clamp(2.4rem, 6vw, 3.6rem);
}
.tpb__inner .sv-h2,
.tpb__inner .sv-lede { max-width: 38rem; }
.tpb__inner .sv-lede { margin-bottom: 0; }

/* ── FACT STRIP ─────────────────────────────────────────────
   Sits inside a band, under the headline. Every cell is a checked
   fact with its source named in the label — a number with no
   provenance is exactly the "claim, not proof" this pass removes. */
.tpf {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(158px, 1fr));
  gap: 0;
  margin-top: clamp(1.8rem, 4vw, 2.6rem);
  border-top: 1px solid rgba(245,241,232,.16);
}
.tpf__cell {
  padding: 1.05rem 1.1rem 1.05rem 0;
  border-right: 1px solid rgba(245,241,232,.10);
}
.tpf__cell:last-child { border-right: none; }
.tpf__n {
  display: block;
  font-family: var(--font-display); font-style: italic; font-weight: 400;
  font-size: clamp(1.5rem, 2.6vw, 2rem); line-height: 1;
  color: #e8455a; margin-bottom: .4rem;
  font-variant-numeric: tabular-nums;
}
.tpf__l {
  display: block;
  font-family: var(--font-mono); font-size: .64rem; line-height: 1.5;
  letter-spacing: .1em; text-transform: uppercase;
  color: rgba(245,241,232,.55);
}
@media (max-width: 560px) {
  .tpf { grid-template-columns: 1fr 1fr; }
  .tpf__cell:nth-child(2n) { border-right: none; }
  .tpf__cell:nth-child(-n+2) { border-bottom: 1px solid rgba(245,241,232,.10); }
}

/* ── LIVE LEDGER (S.04) ─────────────────────────────────────
   Rows, not another big-number strip. Each one is a real running
   product with a link a prospect can open and check, so the row IS
   the evidence and needs no figure over it. Deliberately the
   quietest, densest block on the page — it sits between two louder
   moments and gives the scroll somewhere to rest. */
.tpl { list-style: none; margin: clamp(1.8rem,4vw,2.6rem) 0 0; padding: 0; border-top: 1px solid rgba(245,241,232,.16); }
.tpl__row {
  display: grid;
  grid-template-columns: minmax(0, 13rem) minmax(0, 1fr) auto;
  gap: .5rem 1.2rem; align-items: baseline;
  padding: .85rem 0;
  border-bottom: 1px solid rgba(245,241,232,.09);
}
.tpl__name { font-family: var(--font-mono); font-size: .82rem; color: var(--on-black); }
.tpl__what { font-size: .82rem; line-height: 1.5; color: var(--on-black-mute); }
.tpl__go {
  font-family: var(--font-mono); font-size: .7rem;
  letter-spacing: .1em; text-transform: uppercase;
  color: var(--red-text); text-decoration: none;
  border-bottom: 1px solid transparent; padding-bottom: 1px;
  transition: border-color .2s ease, color .2s ease;
}
.tpl__go:hover, .tpl__go:focus-visible { border-bottom-color: currentColor; outline: none; }
@media (max-width: 620px) {
  .tpl__row { grid-template-columns: 1fr auto; }
  .tpl__what { grid-column: 1 / -1; }
}

/* ── SPLIT BAND (S.03 agents) ───────────────────────────────
   Edge to edge like everything else on this page, but deliberately a
   DIFFERENT composition and a lighter weight than the .tpb slab above
   it: copy on flat ink at the left, the photograph holding the right
   half as a real image rather than a darkened backdrop, and a shorter
   overall height. Two identical full-height photo slabs in one scroll
   is what reads as a template — S.01 is the lead and should dominate;
   this is the supporting mechanism and should not compete with it. */
.tps {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(0, 1.06fr);
  align-items: stretch;
  background: #0a0a0a;
  --ink: #0a0a0a;
  --on-black: #f5f1e8;
  --on-black-mute: rgba(245,241,232,.68);
  --on-black-faint: rgba(245,241,232,.52);
  --red-text: #e8455a;
}
.tps__copy {
  display: flex; flex-direction: column; justify-content: center;
  padding: clamp(2.6rem, 5.5vw, 4.2rem) clamp(1.5rem, 4vw, 3.4rem);
  /* Holds the copy on the same 1140 rail the rest of the page uses,
     so the left edge lines up with every section above and below
     instead of drifting with the column width. */
  margin-left: auto;
  width: 100%;
  max-width: calc((1140px / 2) + 3.4rem);
}
.tps__copy .sv-h2 { margin-bottom: .9rem; }
.tps__copy .sv-lede { max-width: 34rem; margin-bottom: 0; }
.tps__spec { list-style: none; margin: clamp(1.5rem,3vw,2.1rem) 0 0; padding: 0; }
.tps__spec li {
  position: relative; padding-left: 1.05rem;
  font-size: .84rem; line-height: 1.6;
  color: var(--on-black-mute);
}
.tps__spec li + li { margin-top: .55rem; }
.tps__spec li::before {
  content: ""; position: absolute; left: 0; top: .66em;
  width: 8px; height: 1px; background: var(--red);
}
.tps__spec span { color: var(--on-black); }
/* T-1095: 520px on a 740px window. --band-sm — this is the half of a two-up,
   so it is already the page's quietest photo slot and its height should say
   so. */
/* ── T-1120: THE CAPTION CAME OFF THE PHOTOGRAPH ────────────────────
   It used to be absolutely positioned over the bottom of the image under a
   `to top .90 / .55 / transparent` gradient. Two measured failures, both
   invisible to a modal-background contrast check because the block's
   COMMONEST ground is the dark bottom of that gradient:

   1. This caption runs to five lines at 1440 and four at 390. Only its
      last line lands on the .90 stop; the first two sit at .55 and above,
      and directly under them this photograph has a lit ultrawide monitor
      and a window. Sampled worst-case ground there was near rgb(150,160,175)
      against text compositing to rgb(179,176,170) — about 1.1:1. Not dim:
      gone.
   2. At 1440 the block was taller than the figure and the last line
      rendered BELOW the image, on the section's flat ink — a caption
      floating detached from the thing it captions.

   Both are the same root cause: a variable-length caption pinned over a
   variable photograph. So it is now in normal flow directly under the
   image, on flat ink, where its length cannot affect its legibility and it
   is unambiguously attached to the frame above it. Measured after:
   9.0:1 on every line. The photograph keeps its full frame with nothing
   over it, which is what "this photograph is evidence" was asking for in
   the first place. */
.tps__fig {
  position: relative; margin: 0;
  min-height: var(--band-sm);
  display: grid; grid-template-rows: minmax(0, 1fr) auto;
}
.tps__img { width: 100%; height: 100%; min-height: 0; object-fit: cover; object-position: 58% 50%; }
.tps__cap {
  margin: 0;
  padding: .85rem clamp(1.2rem,2.4vw,1.7rem) clamp(1rem,1.8vw,1.25rem);
  background: var(--ink);
  border-top: 1px solid var(--rule-on-black);
  font-family: var(--font-mono); font-size: .64rem; line-height: 1.6;
  color: rgba(245,241,232,.72);
  max-width: 62ch;
}
@media (max-width: 900px) {
  .tps { grid-template-columns: 1fr; }
  /* Photo first on narrow: the image is the evidence and it should not
     be pushed under a screen of type on a phone. */
  .tps__fig { order: -1; min-height: clamp(260px, 62vw, 380px); }
  .tps__copy { max-width: 100%; margin-left: 0; padding: clamp(2.2rem,7vw,3rem) 1.5rem; }
}

/* ── SHIPPED-WORK CAROUSEL ──────────────────────────────────
   A real horizontal rail, scroll-snapped, keyboard reachable, with
   native overflow scrolling — not a JS slideshow that hides most of
   its content behind a timer. Every card is one real row from
   `portfolio_items`; js/tech-proof.js renders them and never invents
   one. Cards are <a>s to the real case study.

   Scroll-snap + overflow is deliberate over a transform-based
   slider: it works with a trackpad, a touch swipe, a scrollbar drag
   and Tab focus on day one, and it degrades to a plain scrollable
   list if the script that wires the arrow buttons never runs. */
.tpx { position: relative; margin-top: clamp(2rem, 4.5vw, 3rem); }
.tpx__rail {
  display: flex; gap: 1.1rem;
  overflow-x: auto; overscroll-behavior-x: contain;
  scroll-snap-type: x mandatory;
  scroll-padding-left: 1px;
  padding: 2px 2px 1.1rem;
  -webkit-overflow-scrolling: touch;
  scrollbar-width: thin;
}
/* T-1120: WAS min(84%, 320px). Every card on this rail carries a real UI
   capture — an admin dashboard, a publishing calendar, a billing console —
   and at 320px they were rendering at a 2.0x downscale of an already
   downscaled thumbnail, which is texture, not evidence. Nothing on the card
   needs 320px; the SCREENSHOT does. 520px puts the render at ~1.5x of the
   served file and the dashboard headings back on the right side of legible.
   The rail is scroll-snapped with arrows, so showing two-and-a-peek instead
   of three-and-a-peek costs a swipe and buys the point of the section. */
.tpx__card {
  flex: 0 0 min(88%, 520px);
  scroll-snap-align: start;
  display: flex; flex-direction: column;
  border: 1px solid rgba(245,241,232,.14);
  background: rgba(245,241,232,.03);
  text-decoration: none; color: inherit;
  /* --press layers press feedback onto the same transform that lifts
     the card on hover, rather than fighting it (emil-design-eng:
     pressable things must confirm the press). */
  --press: 1; --lift: 0px;
  transform: translateY(var(--lift)) scale(var(--press));
  transition: transform .28s var(--ease-out-expo), border-color .28s ease, box-shadow .28s ease;
}
@media (hover: hover) and (pointer: fine) {
  .tpx__card:hover { --lift: -3px; border-color: rgba(200,16,46,.42); box-shadow: 0 14px 34px -18px rgba(0,0,0,.7); }
}
.tpx__card:focus-visible { outline: 2px solid var(--red); outline-offset: 3px; }
.tpx__card:active { --press: .985; transition-duration: 120ms, .28s, .28s; }
.tpx__media {
  position: relative; aspect-ratio: 16 / 10; overflow: hidden;
  background: rgba(245,241,232,.05);
  border-bottom: 1px solid rgba(245,241,232,.10);
}
.tpx__media img { width: 100%; height: 100%; object-fit: cover; display: block; }
/* A row with no image gets a typographic plate, never a blank grey
   box — same treatment case-studies.html already uses. */
.tpx__media--empty {
  display: flex; align-items: center; justify-content: center;
  font-family: var(--font-display); font-style: italic;
  font-size: 2.4rem; color: rgba(200,16,46,.32);
}
.tpx__body { padding: .95rem 1.05rem 1.15rem; display: flex; flex-direction: column; gap: .45rem; flex: 1; }
.tpx__class {
  font-family: var(--font-mono); font-size: .6rem;
  letter-spacing: .16em; text-transform: uppercase;
  color: var(--red-text); margin: 0;
}
.tpx__title {
  font-family: var(--font-display); font-weight: 400;
  font-size: 1.05rem; line-height: 1.25; margin: 0;
  color: var(--on-black);
}
/* The real stack, straight off the row's tech_stack column. This is
   the single most load-bearing thing on the card for an engineering
   buyer — it is checkable, specific, and impossible to fake. */
.tpx__stack { display: flex; flex-wrap: wrap; gap: .3rem; margin: .15rem 0 0; padding: 0; list-style: none; }
.tpx__stack li {
  font-family: var(--font-mono); font-size: .6rem; letter-spacing: .04em;
  color: var(--on-black-mute);
  border: 1px solid rgba(245,241,232,.18);
  padding: .16rem .38rem;
}
.tpx__arrow { margin-top: auto; padding-top: .5rem; font-size: .78rem; color: var(--red-text); }

/* Arrow buttons — real buttons, hidden from touch/narrow where the
   swipe is the better affordance and a floating chevron is clutter. */
.tpx__nav { display: none; gap: .5rem; margin-top: .2rem; }
@media (hover: hover) and (pointer: fine) {
  .tpx__nav { display: flex; justify-content: flex-end; }
}
.tpx__btn {
  width: 38px; height: 38px; padding: 0;
  display: inline-flex; align-items: center; justify-content: center;
  background: none; cursor: pointer;
  border: 1px solid rgba(245,241,232,.22);
  color: var(--on-black);
  transition: border-color .2s ease, background .2s ease, transform 120ms var(--ease-out-expo), opacity .2s ease;
}
.tpx__btn:hover:not(:disabled) { border-color: rgba(200,16,46,.5); background: rgba(200,16,46,.05); }
.tpx__btn:active:not(:disabled) { transform: scale(.94); }
.tpx__btn:disabled { opacity: .3; cursor: default; }
.tpx__btn:focus-visible { outline: 2px solid var(--red); outline-offset: 2px; }

/* Loading / empty / error — one element, three honest states. An
   empty rail that says nothing reads as "this firm has no work";
   every state here says which it is. */
.tpx__state {
  font-family: var(--font-mono); font-size: .76rem; line-height: 1.7;
  color: var(--on-black-mute);
  border-left: 2px solid rgba(200,16,46,.45);
  padding: .8rem 0 .8rem 1rem;
  margin: 0;
  max-width: 62ch;
}

/* Cards enter on a short stagger once rendered (30-80ms band per
   emil-design-eng). Default is VISIBLE — .is-pre only exists once JS
   has rendered the cards, so a no-JS/failed-fetch visitor never
   waits on a class that will not arrive. */
.tpx__card.is-pre { opacity: 0; transform: translateY(10px); }
.tpx__card.is-in { opacity: 1; transform: translateY(0); transition: opacity .5s var(--ease-out-expo), transform .5s var(--ease-out-expo); }

@media (prefers-reduced-motion: reduce) {
  .tpx__card, .tpx__card.is-pre, .tpx__card.is-in {
    transition: none !important; transform: none !important; opacity: 1 !important;
  }
  .tpx__rail { scroll-behavior: auto; }
}

/* ════════════════════════════════════════════════════════════
   .tcw — CLIENT WORK PHOTO, foot of S.02 "Shipped"
   2026-08-13. Photo supplied by Ethan: leadership walking a client
   through live software.

   EDGE TO EDGE. The <figure> sits outside .inner in the markup, so the
   image is the full viewport width with no padded box, no card and no
   border. Same rule as the Citadel capture in css/tech-citadel.css.

   AN IMAGE, NOT A BACKDROP. There is no scrim and no copy over it, so
   the crop is the only thing that can lose the argument. 2:1 anchored
   50% 35% keeps the cap, both faces and the hand on the trackpad;
   below 720px it goes to 3:2 anchored left, where the two people are.

   TOKENS ONLY. --rule-on-black and --font-mono, both already in
   site.css; no new colour, spacing or radius value.
   ════════════════════════════════════════════════════════════ */
.tcw {
  margin: clamp(2.2rem, 5vw, 3.4rem) 0 0;
  padding: 0;
  position: relative;
}
/* T-1095: 2/1 measured 720px on a 740px-tall window — 97% of the screen for
   one photograph, the single worst offender on this page. 3/1 at the same
   anchor keeps the cap, both faces and the hand on the trackpad (checked
   against the frame, not assumed) and lands at 480px / 65% instead.
   NOTE FOR THE MERGE with T-1094's laptop-screen overlay: that build pins a
   transform to the laptop screen's four corners IN THIS PHOTOGRAPH, so it is
   a function of this crop. If the overlay lands after this, its corner
   coordinates have to be re-measured against 3/1, not 2/1. */
.tcw__img {
  display: block;
  width: 100%; height: auto;
  aspect-ratio: 3 / 1;
  object-fit: cover;
  object-position: 50% 35%;
  border-top: 1px solid var(--rule-on-black);
  border-bottom: 1px solid var(--rule-on-black);
}
.tcw__cap {
  max-width: 1140px;
  margin: 0 auto;
  padding: .9rem clamp(1rem, 4vw, 2.5rem) 0;
  font-family: var(--font-mono); font-size: .62rem;
  letter-spacing: .08em;
  color: rgba(245,241,232,.56);
}

@media (max-width: 720px) {
  /* A 2:1 strip is 195px tall on a 390px phone and the faces stop
     reading as faces. 3:2 anchored left keeps the client in the
     foreground and the founder beside him, which is the whole frame. */
  .tcw__img { aspect-ratio: 3 / 2; object-position: 30% 45%; }
}

/* ════════════════════════════════════════════════════════════
   T-1149 — /technology TEMPLATE PASS
   2026-08-14. Ethan, four times: "I NEED WHITE BACKGROUNDS."
   Plus: "sections with text use white backgrounds... too hard to
   read so much of it", "with black shading and separations",
   "do more work like the bubble stuff but different patterns
   crisscross the site, more sharp", "no images going left to right
   border", "the design concept of little text white on black then
   big image needs to go", "sections are too long".

   THE SYSTEM, AND WHY IT IS BUILT THIS WAY.

   1. PER-SECTION, NOT ON <body>. A previous attempt flipped
      body{background:bone} and broke every full-bleed backdrop on
      the page (the hero photo, the closing video). Here .tp-lite is
      a class put on the four sections whose job is READING, and
      nothing else moves.

   2. IT RE-PINS THE TOKENS, NOT JUST THE BACKGROUND. The shared
      .sv-h2 / .sv-lede / .eyebrow / .tpx__card classes all resolve
      --on-black*, so flipping only `background` would have left
      cream type on bone — the failure mode that shipped an eyebrow
      at 1.00:1 before. Every token the shared classes read is
      re-pinned on .tp-lite itself, so the cascade does the work.

   3. --red-text GOES BACK TO THE BRAND RED on light. #e8455a is the
      lightened red that only exists because #c8102e cannot clear
      4.5:1 on ink. On --bone #faf8f3 the brand red measures 5.54:1
      (computed, sRGB relative luminance: 0.1285 vs 0.9393), so the
      compromise colour is not needed and the accent is correct again.
      Ink #0a0a0a on bone is 18.6:1.

   4. BLACK IS NOW PUNCTUATION. The dark grounds left on this page
      are the hero, the map, the run diagram and the closing CTA —
      each a band BETWEEN light blocks, plus the ink plates and phone
      wells that sit INSIDE the light sections as accents. The page
      alternates instead of being one dark scroll.
   ════════════════════════════════════════════════════════════ */

.tp-lite {
  background: var(--bone);
  color: var(--ink);
  --on-black: #0a0a0a;
  --on-black-mute: rgba(10,10,10,.74);
  --on-black-faint: rgba(10,10,10,.54);
  --red-text: #c8102e;
  --rule-on-black: rgba(10,10,10,.16);
}
.tp-lite .sv-h2,
.tp-lite .sv-h3    { color: var(--ink); }
.tp-lite .sv-h2 em { color: var(--red); }
.tp-lite .sv-lede  { color: rgba(10,10,10,.74); }
.tp-lite .eyebrow  { color: var(--red-text); }
/* .52 measured 3.92:1 against the painted bone ground at 10.9px — a
   real fail, found by masking the glyph pixels rather than walking the
   DOM (the DOM walk had also flagged the eyebrow RULE and the link
   UNDERLINE as failures; both are graphics, not type). .62 measures
   5.50:1 on the same pixels. */
.tp-lite .eyebrow .eyebrow-num { color: rgba(10,10,10,.62); }
/* The glow chains are luminous overlays authored for an ink ground.
   On bone they render as a grey smear, so they are switched off
   rather than restyled — the shapes below carry the interest now. */
.tp-lite .section-glow-overlay { display: none; }

/* SHORTER. "sections are too long." .sv-section ships at
   clamp(4rem,8vw,6.5rem); on the light blocks it comes down by about
   a third, which takes ~130px off each at 1440. */
.sv-section.tp-lite { padding: clamp(2.6rem, 5vw, 4.2rem) 0; }

/* ── SHAPE KIT ──────────────────────────────────────────────
   SOFT radii are Ethan's own, lifted verbatim from
   unwrinkled-react/app/globals.css so the bubble language on this
   page is the same one he approved there. SHARP counterparts are
   clip-path chamfers and angled cuts, and the rule for using them is
   ALTERNATION: a soft shape is never followed by another soft shape
   in the same scroll position. */
.tp-arch   { border-radius: 50% 50% 12% 12% / 34% 34% 8% 8%; }
.tp-blob-a { border-radius: 63% 37% 55% 45% / 56% 50% 50% 44%; }
.tp-blob-b { border-radius: 40% 60% 56% 44% / 55% 42% 58% 45%; }
/* chamfer, bottom-right */
.tp-cut-br { clip-path: polygon(0 0, 100% 0, 100% calc(100% - 38px), calc(100% - 38px) 100%, 0 100%); }
/* chamfer, top-left */
.tp-cut-tl { clip-path: polygon(38px 0, 100% 0, 100% 100%, 0 100%, 0 38px); }
/* opposed chamfers — a slab that has been sheared, not rounded */
.tp-cut-x  { clip-path: polygon(0 30px, 30px 0, 100% 0, 100% calc(100% - 30px), calc(100% - 30px) 100%, 0 100%); }
@media (max-width: 620px) {
  .tp-cut-br { clip-path: polygon(0 0, 100% 0, 100% calc(100% - 22px), calc(100% - 22px) 100%, 0 100%); }
  .tp-cut-tl { clip-path: polygon(22px 0, 100% 0, 100% 100%, 0 100%, 0 22px); }
  .tp-cut-x  { clip-path: polygon(0 18px, 18px 0, 100% 0, 100% calc(100% - 18px), calc(100% - 18px) 100%, 0 100%); }
}

/* ── S.01 "HOW WE OPERATE", REBUILT ─────────────────────────
   WAS: a full-bleed photograph of a laptop on a desk with a scrim
   and four lines of cream type bottom-left. That is exactly the
   pattern Ethan named — "little text white on black then big image"
   — and it was also the page's SECOND laptop-at-a-desk photograph
   (the hero carries the first, and the hero's is load-bearing: the
   tap-through desktop overlay is pinned to its screen).

   IS NOW: bone ground, ink type, and a DESIGNED element instead of a
   second stock-looking workstation frame — crossing sharp diagonals
   against a soft arch, in an ink plate with a chamfered corner. The
   address moves onto the plate, where it reads as a stamp rather
   than as a caption under a photo. */
.tp-op { padding: clamp(2.6rem, 5vw, 4.2rem) 0; position: relative; overflow: hidden; }
.tp-op__inner {
  max-width: 1140px; margin: 0 auto;
  padding: 0 clamp(1rem, 4vw, 2.5rem);
  display: grid; grid-template-columns: minmax(0, 1.35fr) minmax(0, .65fr);
  gap: clamp(1.6rem, 4vw, 3.2rem); align-items: center;
}
.tp-op__copy .sv-h2 { margin-bottom: .8rem; }
.tp-op__copy .sv-lede { max-width: 38rem; margin-bottom: 0; }
/* THE INK PLATE — this is the "black shading and separation" inside a
   light section. Chamfered bottom-right so it reads as cut, not as a
   rounded card; every rounded thing on this page is now deliberate. */
.tp-op__plate {
  background: var(--ink);
  padding: clamp(1.3rem, 2.6vw, 1.9rem);
  display: flex; flex-direction: column; gap: 1.1rem;
  position: relative; overflow: hidden;
}
.tp-op__mark { display: block; width: 100%; height: auto; }
.tp-op__addr {
  font-family: var(--font-mono); font-size: .66rem; line-height: 1.7;
  letter-spacing: .1em; text-transform: uppercase;
  /* rgba(245,241,232,.80) composited on #0a0a0a measures 10.4:1 */
  color: rgba(245,241,232,.80);
  margin: 0; padding-top: .9rem;
  border-top: 1px solid rgba(245,241,232,.2);
}
@media (max-width: 820px) {
  .tp-op__inner { grid-template-columns: 1fr; gap: 1.8rem; }
  .tp-op__plate { max-width: 320px; }
}

/* ── CONTAINED PHOTOGRAPH ───────────────────────────────────
   "no images going left to right border." .tcw shipped as a
   full-viewport 3:1 strip with borders top and bottom. It is now a
   bounded figure on the 1140 rail with real margin around it and an
   angled cut on two corners, so it reads as a placed image rather
   than as a backdrop that happens to have a photo in it.
   The SECTION ground stays full width; only the IMAGE is contained. */
/* MEASURED, NOT ASSUMED: the merged base already caps this image at
   min(100%, 820px) and 3:1 from site.css, so at 1440 it renders 820x273
   centred on the rail — already contained, already off the viewport
   edges. An earlier version of this rule tried to re-declare the width
   at 1140 and simply lost the cascade to that cap, which is why it is
   not re-declared here. What is added is the margin, the angled cut and
   the caption colour for a bone ground. */
.tcw { margin: clamp(2rem, 4vw, 3rem) auto 0; padding: 0; }
.tcw__img {
  object-position: 50% 35%;
  border: none;
  clip-path: polygon(0 30px, 30px 0, 100% 0, 100% calc(100% - 30px), calc(100% - 30px) 100%, 0 100%);
}
.tp-lite .tcw__cap {
  max-width: min(100%, 820px); margin: 0 auto; padding: .8rem 0 0;
  font-family: var(--font-mono); font-size: .62rem;
  letter-spacing: .08em;
  color: rgba(10,10,10,.62);
}
@media (max-width: 720px) {
  .tcw__img {
    object-position: 30% 45%;
    clip-path: polygon(0 20px, 20px 0, 100% 0, 100% calc(100% - 20px), calc(100% - 20px) 100%, 0 100%);
  }
}

/* ── S.03 SPLIT, ON LIGHT, WITH A BUILT PHONE ───────────────
   WAS: ink ground, cream type, and tech-liberty-village-desk.jpg —
   the page's THIRD laptop-at-a-desk photograph — filling the right
   half edge to edge.

   IS NOW: bone ground with ink type, and the right half holds a
   phone BUILT IN CSS whose screen is a real iOS capture from our own
   apps, tap-through. Ethan asked for this directly. It replaces a
   photograph of a workstation with the actual work, which is the
   stronger argument anyway, and it removes the repeat.
   The phone sits in an ink well — separation again — with a soft
   blob behind it, played against the sharp chamfer on the well. */
.tps.tp-lite { background: var(--bone); }
.tps { align-items: stretch; }
.tps__copy { padding-block: clamp(2.2rem, 4.5vw, 3.4rem); }
.tp-lite .tps__spec li { color: rgba(10,10,10,.72); }
.tp-lite .tps__spec span { color: var(--ink); }

.tph {
  position: relative;
  display: flex; flex-direction: column; align-items: center; justify-content: center;
  gap: 1rem;
  padding: clamp(1.8rem, 3.6vw, 2.8rem) clamp(1rem, 3vw, 2rem);
  background: var(--ink);
  overflow: hidden;
  clip-path: polygon(44px 0, 100% 0, 100% 100%, 0 100%, 0 44px);
}
/* the soft counterpart to the well's sharp cut */
/* The soft counterpart to the well's sharp cut. First cut of this was a
   74% disc of translucent red that filled the whole well and read as a
   dull maroon wash behind the device rather than as a shape. It is now
   smaller than the phone, offset behind its shoulder, and given a
   visible edge so the blob reads AS a blob. */
.tph__soft {
  position: absolute; z-index: 0;
  /* SECOND PASS: filled at .30 it rendered as a flat maroon slab with
     its left edge clipped by the well, which is a lump rather than a
     shape. Drawn as a RING instead — outlined soft against filled
     sharp is the contrast that makes the pair read as a pair. */
  width: min(34%, 210px); aspect-ratio: 1 / 1;
  left: 30%; top: 32%; transform: translate(-50%, -50%);
  background: rgba(200,16,46,.10);
  border: 2px solid rgba(200,16,46,.72);
  border-radius: 63% 37% 55% 45% / 56% 50% 50% 44%;
  pointer-events: none;
}
/* ...played against a SHARP one on the other shoulder, so the well has
   both languages in it rather than one repeated shape. */
.tph__hard {
  position: absolute; z-index: 0;
  width: min(30%, 170px); aspect-ratio: 1 / 1;
  right: 8%; bottom: 16%;
  background: rgba(245,241,232,.10);
  border-top: 1px solid rgba(245,241,232,.26);
  clip-path: polygon(0 34%, 66% 0, 100% 34%, 100% 100%, 34% 100%, 0 66%);
  pointer-events: none;
}
/* THE DEVICE. Height is capped at the site's UI-screenshot scale —
   clamp(280px,34vw,440px) — and the width follows from the 9:19.5
   aspect, so this can never grow into a full-height slab. */
.tph__device {
  position: relative; z-index: 1;
  height: clamp(300px, 34vw, 440px);
  aspect-ratio: 9 / 19.5;
  border-radius: 30px;
  padding: 7px;
  background: linear-gradient(160deg, #3a3a3a 0%, #141414 42%, #2c2c2c 100%);
  box-shadow: 0 26px 60px -30px rgba(0,0,0,.9), 0 0 0 1px rgba(245,241,232,.10);
}
.tph__screen {
  position: relative; width: 100%; height: 100%;
  border-radius: 23px; overflow: hidden;
  background: #000;
}
.tph__shot {
  position: absolute; inset: 0;
  width: 100%; height: 100%;
  object-fit: cover; object-position: 50% 0;
  opacity: 0; transition: opacity .34s var(--ease-out-expo);
}
.tph__shot.is-on { opacity: 1; }
.tph__notch {
  position: absolute; z-index: 2;
  top: 12px; left: 50%; transform: translateX(-50%);
  width: 34%; height: 15px; border-radius: 9px;
  background: #000;
}
/* TABS — real <button>s in a tablist, so the tap-through is keyboard
   reachable and not a hover trick. */
.tph__tabs {
  position: relative; z-index: 1;
  display: flex; flex-wrap: wrap; justify-content: center; gap: .4rem;
  max-width: 30rem;
}
.tph__tab {
  font-family: var(--font-mono); font-size: .58rem;
  letter-spacing: .1em; text-transform: uppercase;
  padding: .42rem .62rem; cursor: pointer;
  background: none; color: rgba(245,241,232,.62);
  border: 1px solid rgba(245,241,232,.20);
  transition: color .2s ease, border-color .2s ease, background .2s ease;
}
.tph__tab:hover { color: rgba(245,241,232,.92); border-color: rgba(245,241,232,.44); }
.tph__tab.is-on {
  color: #fff; border-color: var(--red); background: rgba(200,16,46,.22);
}
.tph__tab:focus-visible { outline: 2px solid var(--red); outline-offset: 2px; }
.tph__cap {
  position: relative; z-index: 1; margin: 0; text-align: center;
  font-family: var(--font-mono); font-size: .6rem; letter-spacing: .08em;
  color: rgba(245,241,232,.66);
  max-width: 30rem;
}
@media (max-width: 900px) {
  .tph { clip-path: polygon(28px 0, 100% 0, 100% 100%, 0 100%, 0 28px); }
  .tps.tp-lite .tps__copy { order: -1; }
}
@media (prefers-reduced-motion: reduce) {
  .tph__shot { transition: none; }
}

/* ── LIGHT-GROUND VARIANTS OF THE SHARED COMPONENTS ─────────
   Everything below exists because the shared class resolves a token
   that only had a dark-ground value. Nothing here invents a colour;
   each is the ink-ground rule with its alpha re-based on bone. */
.tp-lite .tpx__card {
  background: #ffffff;
  border-color: rgba(10,10,10,.14);
}
@media (hover: hover) and (pointer: fine) {
  .tp-lite .tpx__card:hover {
    border-color: rgba(200,16,46,.45);
    box-shadow: 0 14px 34px -20px rgba(10,10,10,.45);
  }
}
.tp-lite .tpx__media {
  background: rgba(10,10,10,.05);
  border-bottom-color: rgba(10,10,10,.10);
}
.tp-lite .tpx__media--empty { color: rgba(200,16,46,.34); }
.tp-lite .tpx__stack li { border-color: rgba(10,10,10,.20); }
.tp-lite .tpx__btn { border-color: rgba(10,10,10,.24); color: var(--ink); }
.tp-lite .tpx__btn:hover:not(:disabled) { border-color: rgba(200,16,46,.55); background: rgba(200,16,46,.06); }
.tp-lite .tpx__state {
  color: rgba(10,10,10,.74);
  border-left-color: rgba(200,16,46,.55);
}
.tp-lite .tpl { border-top-color: rgba(10,10,10,.22); }
.tp-lite .tpl__row { border-bottom-color: rgba(10,10,10,.12); }
/* The underline is border-bottom rgba(200,16,46,.35) from site.css,
   which on bone paints (233,167,174) — a pink bar 4px under the
   baseline that the descenders of "every"/"study" clip into. The glyph
   mask measured 2.97:1 on those overlap pixels. Ink hairline instead,
   and enough padding that no descender reaches it. */
.tp-lite .sv-row__link {
  color: var(--red-text);
  /* A real underline rather than a border. text-decoration-skip-ink is
     on by default, so the rule BREAKS around the descenders instead of
     running under them — which is both correct typography and the thing
     that clears the measurement: with a border there were always a few
     antialiased glyph pixels sitting on the rule (2.71:1 on those
     pixels), and skip-ink means there are none. */
  border-bottom: none;
  padding-bottom: 0;
  text-decoration: underline;
  text-decoration-color: rgba(10,10,10,.32);
  text-decoration-thickness: 1px;
  text-underline-offset: 1.05em;
}
.tp-lite .sv-row__link:hover,
.tp-lite .sv-row__link:focus-visible { text-decoration-color: var(--red); }

/* The S.04 ledger gets the shape treatment too, so the quietest
   block on the page is still designed: a chamfered ink rail down the
   left of the list rather than a plain hairline border-top. */
.tp-lite .tpl {
  position: relative;
  padding-left: clamp(.9rem, 2vw, 1.4rem);
  border-top: none;
}
.tp-lite .tpl::before {
  content: ""; position: absolute; left: 0; top: 0; bottom: 0; width: 6px;
  background: var(--ink);
  clip-path: polygon(0 0, 100% 10px, 100% calc(100% - 10px), 0 100%);
}

/* ── THE EMBER DIVIDERS ─────────────────────────────────────
   .ember is a 200px red radial glow that seats one DARK band against
   the next. Measured on the rendered page: the one above S.01 sits at
   y=714 with height 200 while S.01 starts at 814, i.e. it overlaps the
   top 100px of the section, and on bone it paints as a pink haze
   across the headline. Four of the six on this page now precede a
   light section, and two of those sit between two light sections where
   they are simply 200px of empty scroll — which is half of what "the
   sections are too long" is measuring.

   So: an ember immediately before a .tp-lite section is removed. The
   ember before .sv-close survives, because that join is still dark to
   dark and the glow is doing its job there. z-index is belt and braces
   for any ember that reaches a light section some other way — .ember
   sits at z-index 2. */
.ember:has(+ .tp-lite) { display: none; }
.tp-lite { position: relative; z-index: 3; }

/* ══════════════════════════════════════════════════════════════════════
   T-1182 — TWO PHOTOGRAPHS STILL RAN LEFT EDGE TO RIGHT EDGE ON A PHONE
   ──────────────────────────────────────────────────────────────────────
   Ethan, quoted verbatim thirty lines above this: "no images going left
   to right border." That was fixed at desktop and only at desktop.

   Measured at 390x844 on this branch: every heading, paragraph and list
   on this page sits inside a 16px gutter (24px inside .tps__copy), and
   two photographs did not — S.02's client-work band (.tcw) rendered
   0..390 and S.03's frame (.tps__frame) rendered 0..388. Both are
   full-bleed on a phone and only on a phone.

   Cause is the same in both cases: the desktop fix bounds them with
   `min(100%, 820px)` / `min(100%, 560px)`, and 100% of a section that
   carries no inline padding of its own is the whole viewport. The cap
   does nothing below 820px, which is every phone.

   So they are given the page's own gutter at the width where the cap
   stops working. Nothing changes at or above 720px — 100% is still
   smaller than both caps there, so this rule cannot move the desktop
   layout it is written under.

   .tps__frame needs the id to outrank css/tnaado-rail.css, which loads
   after this file and sets `.tps__frame { width: 100% }` under its own
   max-width:900px query at the same (0,1,0) specificity. .tcw only
   needs `width`, because site.css's !important is on max-width and
   margin-inline and a narrower width still wins under both. */
@media (max-width: 720px) {
  .tcw { width: calc(100% - 2rem); }
  #tech-speed .tps__frame { width: calc(100% - 2rem); margin-inline: auto; }
}
