/* ══════════════════════════════════════════════════════════════════════
   portfolio.tnaado.ca and portfolio.tnaado.com, the B2B record.
   PROJECT REBOOT site 4 of 8.

   ONE STYLESHEET FOR FIVE PAGES, for the same reason apps-tnaado-ca has
   one for three: an inline copy per page is how this estate produced most
   of its defects. Nothing below is per page.

   WHAT THIS PAGE IS FOR. tnaado.com's portfolio is the frame Ethan asked
   for by name. The design was right and the DATA MANAGEMENT was what
   failed: Sluekie, Buuiteks and Indossare were printed as CLIENT work when
   all three are TNAADO-owned ventures, because the classification lived in
   whichever sentence somebody typed. Here it is a field on a row in
   data/items.json, rendered by scripts/sync-record.mjs, and the build
   fails if a page drifts from it.

   DESIGN. tnaado-ca is the reference, and its discipline is the point:
   full-bleed editorial bands over one photograph behind a real scrim, and
   NO CARD GRIDS. The record is a hairline-ruled list of full-width rows,
   alternating which side the screenshot sits on, on ink. A card grid would
   turn forty-five entries into wallpaper and hide the one thing this page
   exists to make legible, which is who owns what.

   Palette is the locked one. Art layer contract is ART-GUIDE section 1.
   NO EM DASHES, including in these comments.
   ══════════════════════════════════════════════════════════════════════ */

:root {
  --ink:        #0a0a0a;
  --bone:       #faf8f3;
  --on-black:   #f5f1e8;
  --red:        #c8102e;
  /* #c8102e measures 3.4:1 on ink and fails AA, so it styles rules, fills
     and large display type only. #e8455a is the same hue at 5.4:1 and is
     what the estate ships for small red text. */
  --red-text:   #e8455a;

  --fg-rgb: 245, 241, 232;
  --bg-rgb: 10, 10, 10;
  --red-rgb: 200, 16, 46;
  --surface-2: #080808;

  --font-display: "Fraunces", Georgia, serif;
  --font-body:    "IBM Plex Sans", -apple-system, system-ui, sans-serif;
  --font-mono:    "JetBrains Mono", ui-monospace, monospace;

  --t-h1:     clamp(2.4rem, 5.1vw, 4.4rem);
  --t-h2:     clamp(1.9rem, 4vw, 3.1rem);
  --t-lede:   clamp(1rem, 1.55vw, 1.22rem);
  --t-body:   clamp(.95rem, 1.25vw, 1.05rem);
  --t-label:  .66rem;

  --s2: .75rem;
  --s3: 1.25rem;
  --s4: 2rem;
  --s5: 3.25rem;
  --pad-y: clamp(4rem, 10vh, 7.5rem);
  --pad-x: clamp(1.25rem, 5vw, 4.5rem);
  --rail:  1320px;

  --ease-out: cubic-bezier(0.16, 1, 0.3, 1);
}

*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
html {
  scroll-behavior: smooth;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  overflow-x: hidden; overflow-x: clip;
}
body {
  background: var(--ink);
  color: var(--on-black);
  font-family: var(--font-body);
  font-size: var(--t-body);
  line-height: 1.62;
  overflow-x: hidden; overflow-x: clip;
}
img, video { display: block; max-width: 100%; }
a { color: inherit; text-decoration: none; }
button { font: inherit; color: inherit; background: none; border: none; cursor: pointer; }
::selection { background: rgba(var(--red-rgb), .34); color: var(--bone); }
:focus-visible { outline: 2px solid var(--red-text); outline-offset: 4px; }

h1, h2, h3 {
  font-family: var(--font-display);
  font-weight: 400;
  letter-spacing: -.022em;
  line-height: 1.05;
  color: var(--bone);
  text-wrap: balance;
}
h1 em, h2 em, h3 em { font-style: italic; color: var(--red-text); }

.skip-link {
  position: absolute; left: -9999px; top: 0; z-index: 200;
  background: var(--red); color: #fff;
  padding: .7rem 1.1rem;
  font-family: var(--font-mono); font-size: .7rem; letter-spacing: .12em; text-transform: uppercase;
}
.skip-link:focus { left: 0; }

.tn-rail {
  width: 100%;
  max-width: var(--rail);
  margin-inline: auto;
  padding-inline: var(--pad-x);
}
.tn-label {
  font-family: var(--font-mono);
  font-size: var(--t-label);
  letter-spacing: .22em;
  text-transform: uppercase;
  color: var(--red-text);
  margin-bottom: var(--s2);
}
/* Over a photograph this label gets the same shadow its neighbours have.
   The shadow is a legibility aid and is NOT counted as contrast: the scrim
   above is what carries the ratio, measured against composited pixels with
   the type hidden, not against the tokens. */
.tn-stage .tn-label { text-shadow: 0 1px 14px rgba(10,10,10,.98); }

/* ── ART LAYER CONTRACT (ART-GUIDE section 1) ─────────────────────────── */
.tn-stage { position: relative; overflow: hidden; isolation: isolate; }
.tn-photo {
  position: absolute; inset: 0;
  width: 100%; height: 100%;
  object-fit: cover;
  z-index: 0;
  pointer-events: none;
}
.tn-scrim { position: absolute; inset: 0; z-index: 1; pointer-events: none; }
.tn-stage > *:not(.tn-photo):not(.tn-scrim) { position: relative; z-index: 2; }

/* ── REVEAL ───────────────────────────────────────────────────────────── */
html.js-motion [data-reveal]:not(.tn-mask) {
  opacity: 0;
  transform: translateY(22px);
  transition: opacity .72s var(--ease-out), transform .72s var(--ease-out);
}
html.js-motion [data-reveal]:not(.tn-mask).is-visible { opacity: 1; transform: none; }
html.js-motion [data-reveal="1"] { transition-delay: .09s; }
html.js-motion [data-reveal="2"] { transition-delay: .18s; }
html.js-motion [data-reveal="3"] { transition-delay: .27s; }
.tn-mask { display: block; overflow: hidden; padding-bottom: .12em; margin-bottom: -.12em; }
html.js-motion .tn-mask > * {
  display: block;
  transform: translateY(105%);
  transition: transform 1.05s var(--ease-out);
}
html.js-motion .tn-mask.is-visible > * { transform: none; }

/* ══════════════════════════════════════════════════════════════════════
   1. HERO. One photograph per category, full bleed, behind the Show-hero
   scrim recipe from ART-GUIDE: one red radial in the corner where the
   densest copy sits, a vertical letterbox, and a side anchor under the
   type. Designed for the worst frame, which is the photo at full
   brightness with nothing else loaded.
   ══════════════════════════════════════════════════════════════════════ */
.pf-hero {
  min-height: clamp(540px, 86svh, 900px);
  display: flex;
  align-items: flex-end;
  padding: calc(var(--pad-y) + 3rem) 0 var(--pad-y);
  background: var(--ink);
}
.pf-hero .tn-photo {
  opacity: .7;
  object-position: 50% 46%;
  filter: saturate(.9) contrast(1.06) grayscale(.2);
}
html.js-motion .pf-hero .tn-photo {
  animation: kenBurnsZoom 48s cubic-bezier(0.37, 0, 0.63, 1) infinite alternate;
  will-change: transform;
}
@keyframes kenBurnsZoom {
  from { transform: scale(1.03); }
  to   { transform: scale(1.11) translateY(-1.1%); }
}
.pf-hero .tn-scrim {
  background:
    radial-gradient(ellipse 54% 46% at 6% 96%, rgba(var(--red-rgb), .2) 0%, transparent 64%),
    linear-gradient(to bottom, rgba(10,10,10,.84) 0%, rgba(10,10,10,.62) 20%, rgba(10,10,10,.54) 46%, rgba(10,10,10,.87) 72%, rgba(10,10,10,.97) 100%),
    linear-gradient(96deg, rgba(10,10,10,.9) 0%, rgba(10,10,10,.48) 54%, rgba(10,10,10,.08) 100%);
}
.pf-hero h1 {
  font-size: var(--t-h1);
  max-width: 16ch;
  text-shadow: 0 2px 30px rgba(10,10,10,.9);
}
.pf-hero-lede {
  margin-top: var(--s3);
  max-width: 58ch;
  font-size: var(--t-lede);
  line-height: 1.55;
  color: rgba(var(--fg-rgb), .9);
  text-shadow: 0 1px 16px rgba(10,10,10,.96);
}

/* The category index. Five rows, the current one a span rather than a
   link, and the row's own count printed beside it. Same shape as the
   apps site's index so the two properties read as one estate. */
.pf-index {
  margin-top: var(--s5);
  max-width: 760px;
  border-top: 1px solid rgba(var(--fg-rgb), .24);
}
.pf-index > * {
  display: grid;
  grid-template-columns: 3.4rem minmax(0, 1fr) auto;
  align-items: baseline;
  gap: 0 var(--s3);
  padding: .85rem 0;
  border-bottom: 1px solid rgba(var(--fg-rgb), .16);
  transition: padding-left .4s var(--ease-out);
}
.pf-index a:hover, .pf-index a:focus-visible { padding-left: .5rem; }
.pf-index [aria-current="page"] .pf-index-name { color: var(--red-text); }
.pf-index-n {
  font-family: var(--font-mono);
  font-size: var(--t-label);
  letter-spacing: .16em;
  color: var(--red-text);
  text-shadow: 0 1px 14px rgba(10,10,10,.98);
}
.pf-index-name {
  font-family: var(--font-display);
  font-size: clamp(1.12rem, 2.1vw, 1.55rem);
  color: var(--bone);
  text-shadow: 0 1px 14px rgba(10,10,10,.92);
}
.pf-index-note {
  font-family: var(--font-mono);
  font-size: var(--t-label);
  letter-spacing: .14em;
  text-transform: uppercase;
  color: rgba(var(--fg-rgb), .82);
  text-shadow: 0 1px 14px rgba(10,10,10,.96);
  white-space: nowrap;
}
@media (max-width: 620px) {
  .pf-index > * { grid-template-columns: 2.4rem minmax(0, 1fr); }
  .pf-index-note { grid-column: 2; white-space: normal; }
}

/* ══════════════════════════════════════════════════════════════════════
   2. THE COUNTRY OFFER. Never a redirect.

   merch.dreamdaley.ca geo-redirects and has therefore never indexed:
   Googlebot crawls from US IP space, so a geo-redirect only ever shows it
   the other side of the pair. This is an OFFER. Both hosts serve every
   page in full, both are indexable, and the visitor decides. The .other
   link is rewritten by js/site.js from location.hostname; with JS off,
   both hosts are named and both are clickable.
   ══════════════════════════════════════════════════════════════════════ */
.pf-switch {
  border-bottom: 1px solid rgba(var(--fg-rgb), .12);
  background: var(--surface-2);
}
.pf-switch-inner {
  max-width: var(--rail);
  margin-inline: auto;
  padding: .8rem var(--pad-x);
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: .4rem 1.1rem;
  font-family: var(--font-mono);
  font-size: .64rem;
  letter-spacing: .13em;
  text-transform: uppercase;
  color: rgba(var(--fg-rgb), .72);
}
.pf-switch a {
  color: var(--red-text);
  border-bottom: 1px solid rgba(var(--red-rgb), .55);
  padding-bottom: 1px;
  transition: border-color .3s var(--ease-out);
}
.pf-switch a:hover, .pf-switch a:focus-visible { border-bottom-color: var(--red-text); }

/* ══════════════════════════════════════════════════════════════════════
   3. THE FILTER. Client work, TNAADO-owned, internal tooling.

   This control is the reason the page exists, so it is not a decoration
   in the corner: it sits on its own band with the count it produces.
   Buttons, not links, and `aria-pressed` rather than colour alone.
   Without JS every entry is visible and the control is hidden, so nobody
   is offered a dead switch.
   ══════════════════════════════════════════════════════════════════════ */
.pf-filter { background: var(--ink); border-bottom: 1px solid rgba(var(--fg-rgb), .14); }
.pf-filter-inner {
  max-width: var(--rail);
  margin-inline: auto;
  padding: clamp(2.4rem, 5vw, 3.6rem) var(--pad-x) clamp(1.5rem, 3vw, 2.2rem);
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(0, 26rem);
  align-items: end;
  gap: var(--s4);
}
@media (max-width: 900px) {
  .pf-filter-inner { grid-template-columns: 1fr; align-items: start; }
}
.pf-filter h2 { font-size: var(--t-h2); }
.pf-filter-rule {
  font-size: var(--t-body);
  line-height: 1.6;
  color: rgba(var(--fg-rgb), .84);
  padding-bottom: .3rem;
}
.pf-controls {
  max-width: var(--rail);
  margin-inline: auto;
  padding: 0 var(--pad-x) clamp(1.6rem, 3vw, 2.2rem);
  display: grid;
  grid-template-columns: minmax(0, 22rem) minmax(0, 1fr);
  align-items: center;
  gap: .8rem 1.6rem;
}
@media (max-width: 860px) { .pf-controls { grid-template-columns: 1fr; } }
.pf-chips { display: flex; flex-wrap: wrap; align-items: center; gap: .6rem; }

/* ── SEARCH. Name, company, status, why it was built, the stack, and the
   owner's own label, all read off `data-search`, which the build writes
   from the same row as the chip and the filter. There is no second index
   and no scraped DOM text, so search cannot disagree with the filter
   about what an item is. ── */
.pf-search { position: relative; display: flex; align-items: center; }
.pf-search-label {
  position: absolute; width: 1px; height: 1px;
  overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap;
}
.pf-search-input {
  width: 100%;
  min-height: 44px;
  padding: .6rem 4.2rem .6rem 1rem;
  background: rgba(var(--fg-rgb), .05);
  border: 1px solid rgba(var(--fg-rgb), .28);
  border-radius: 999px;
  color: var(--bone);
  font-family: var(--font-mono);
  font-size: .68rem;
  letter-spacing: .06em;
  transition: border-color .3s var(--ease-out), background-color .3s var(--ease-out);
}
.pf-search-input::placeholder { color: rgba(var(--fg-rgb), .5); }
.pf-search-input:hover { border-color: rgba(var(--fg-rgb), .5); }
.pf-search-input:focus { outline: none; border-color: var(--red-text); background: rgba(var(--fg-rgb), .08); }
.pf-search-input::-webkit-search-cancel-button { display: none; }
.pf-search-clear {
  position: absolute;
  right: .4rem;
  min-height: 36px;
  padding: .3rem .7rem;
  border-radius: 999px;
  font-family: var(--font-mono);
  font-size: .58rem;
  letter-spacing: .14em;
  text-transform: uppercase;
  color: var(--red-text);
}
.pf-search-clear:hover, .pf-search-clear:focus-visible { color: var(--bone); }
.pf-search-clear[hidden] { display: none; }
.pf-controls[hidden] { display: none; }
.pf-chip {
  display: inline-flex;
  align-items: center;
  gap: .5rem;
  min-height: 44px;
  padding: .5rem 1rem;
  border: 1px solid rgba(var(--fg-rgb), .28);
  border-radius: 999px;
  font-family: var(--font-mono);
  font-size: .64rem;
  letter-spacing: .13em;
  text-transform: uppercase;
  color: rgba(var(--fg-rgb), .88);
  transition: border-color .3s var(--ease-out), color .3s var(--ease-out), background-color .3s var(--ease-out);
}
.pf-chip:hover { border-color: rgba(var(--fg-rgb), .6); color: var(--bone); }
.pf-chip[aria-pressed="true"] {
  background: var(--red);
  border-color: var(--red);
  color: #fff;
}
.pf-chip-n { font-size: .58rem; opacity: .72; }
/* A category whose entries all share one owner gets the statement instead
   of the chips, because two buttons selecting the same rows is a dead
   switch. The search field is still rendered there: search is useful in a
   single-owner category, the filter is not. */
.pf-onlyowner {
  max-width: 66ch;
  font-size: var(--t-body);
  line-height: 1.65;
  color: rgba(var(--fg-rgb), .78);
  border-left: 2px solid var(--red);
  padding-left: 1rem;
}
.pf-onlyowner b { font-weight: 500; color: var(--bone); }
.pf-count {
  grid-column: 1 / -1;
  font-family: var(--font-mono);
  font-size: .62rem;
  letter-spacing: .14em;
  text-transform: uppercase;
  color: rgba(var(--fg-rgb), .6);
}

/* ══════════════════════════════════════════════════════════════════════
   4. THE RECORD, AS A JOURNEY. Ported from apps-tnaado-ca/css/apps.css,
   which ported it from tnaado-media-next/apps.html. Ethan: "portfolio
   needs same system as the apps page, the same scroll system."

   ONE JOURNEY PER PAGE, carrying only that page's category. Ethan: "all
   separate pages, for each category." Five pages, five journeys, never
   one stacked sequence, which is also how the apps site does it.

   Default state is a real, always visible, hairline ruled index: every
   panel is normal document flow and every link inside it is reachable.
   `.journey.is-ready`, added by JS only once the motion gate AND GSAP
   both confirm, is a pure visual upgrade on the same markup: it
   repositions the panels as absolutely positioned, scrub driven,
   one-at-a-time fullscreen frames inside a tall pinned track, and paints
   `.journey-bg` with the current panel's accent.

   WHAT IS DELIBERATELY DIFFERENT FROM THE APPS PORT, and only this:

   1. THE ACCENT IS THE OWNER'S, not the item's. `data-accent` comes off
      the `owner` field, so the ground a panel paints is one more thing
      that cannot disagree with the chip. Scrolling a category watches the
      ground change from oxblood (client work) to navy (TNAADO-owned) to
      graphite (internal tooling). All three are dark, so the panel body's
      scrim recipe never has to fight a bright ground the way BRAT's
      #8ACE00 does on the apps page.
   2. THE VISIBLE SET IS MUTABLE, because this page filters and searches.
      `--panels` is rewritten by JS from the visible count, not fixed at
      render time, and hidden panels carry the `hidden` attribute so they
      leave the accessibility tree as well as the track.
   3. The base state keeps the portfolio's editorial row rather than the
      apps page's compact index row: a wide screenshot beside the copy,
      alternating sides down the page, on hairlines. That is what a no-JS
      or reduced-motion visitor gets and it has to stand on its own.

   Sizes, timings, the scrim recipe, the two-shape panel contract and the
   80vh-per-panel track ratio are the source file's, carried across.
   ══════════════════════════════════════════════════════════════════════ */
.journey { background: var(--ink); position: relative; }
.journey-track { position: relative; }
/* 80vh of scroll per panel, the source page's ratio (720vh for nine). JS
   rewrites --panels from the VISIBLE count on every filter or search, so
   the track never contains dead space for a panel nobody can see. */
.journey.is-ready .journey-track { height: calc(var(--panels, 4) * 80vh); }
.journey-stage { position: relative; }
.journey.is-ready .journey-stage {
  position: sticky;
  top: 0;
  height: 100vh;
  height: 100svh;
  overflow: hidden;
  display: flex;
  align-items: center;
  justify-content: center;
}
/* THE EMPTY RESULT. An empty pinned track is a page that scrolls into
   nothing, so the pin comes off before the message goes up. Both rules
   are needed: the track must stop reserving height AND the stage must
   stop sticking, or the visitor gets a blank screen they can scroll. */
.journey.is-empty .journey-track { height: auto; }
.journey.is-ready.is-empty .journey-stage { position: static; height: auto; overflow: visible; }
/* Two class names on the element, not one, on purpose: `.journey.is-ready
   .journey-hint` further down this file is also (0,2,0) and, being later,
   would win the tie and leave "Keep scrolling" under an empty result. */
.journey.is-ready.is-empty .journey-panels,
.journey.is-ready.is-empty .journey-rail,
.journey.is-ready.is-empty .journey-hint,
.journey.is-ready.is-empty .journey-bg,
.journey.is-empty .journey-panels { display: none; }

.journey-bg { display: none; }
.journey.is-ready .journey-bg { display: block; position: absolute; inset: 0; background: var(--ink); }
.journey-bg::after {
  content: '';
  position: absolute;
  inset: 0;
  background: radial-gradient(ellipse 70% 60% at 50% 45%, transparent 0%, rgba(10,10,10,.55) 60%, rgba(10,10,10,.94) 100%);
}

/* Numbered rail: a navigation enhancement, never the only way to reach a
   panel. Hidden entirely until JS wires the click handler, so a no-JS
   visitor is never shown an inert button list. Desktop only. A tick whose
   panel is filtered out is hidden with the panel. */
.journey-rail { display: none; }
.journey.is-ready .journey-rail {
  display: flex;
  position: absolute;
  right: clamp(1rem,3vw,2.5rem);
  top: 50%;
  transform: translateY(-50%);
  flex-direction: column;
  gap: .75rem;
  z-index: 3;
  max-height: 78vh;
  overflow: hidden;
}
@media (max-width: 900px) { .journey.is-ready .journey-rail { display: none; } }
.journey-rail-tick {
  display: block;
  padding: 0;
  font-family: var(--font-mono);
  font-size: .6rem;
  letter-spacing: .1em;
  color: rgba(var(--fg-rgb), .5);
  text-shadow: 0 1px 10px rgba(10,10,10,.9);
  transition: color .3s;
  white-space: nowrap;
  text-align: right;
  max-width: 15ch;
  overflow: hidden;
  text-overflow: ellipsis;
}
.journey-rail-tick:hover, .journey-rail-tick:focus-visible { color: var(--bone); outline: none; }
.journey-rail-tick.is-active { color: var(--bone); }
.journey-rail-tick[hidden] { display: none; }

.journey-panels { position: relative; }
.journey.is-ready .journey-panels { width: 100%; height: 100%; border-top: none; }

/* ── ONE PANEL, TWO SHAPES. The base rule is the real, always visible
   editorial row (media column, then text, alternating sides).
   `.journey.is-ready` overrides it to the fullscreen scrubbed frame. ── */
.journey-panel {
  position: relative;
  display: grid;
  grid-template-columns: minmax(0, 27rem) minmax(0, 1fr);
  align-items: center;
  gap: clamp(1.5rem, 4vw, 3.4rem);
  max-width: var(--rail);
  margin: 0 auto;
  padding: clamp(2.2rem, 4.5vw, 3.4rem) var(--pad-x);
  text-align: left;
}
/* The hairlines are drawn by pseudo-elements inset to var(--pad-x) rather
   than as a border on the panel, so they line up with the hero's own index
   rule and with the copy above them. A border on the panel runs to the rail
   edge instead, which reads as a second, wider grid. */
.journey-panel::after,
.journey-panel:first-of-type::before {
  content: '';
  position: absolute;
  left: var(--pad-x);
  right: var(--pad-x);
  height: 1px;
  background: rgba(var(--fg-rgb), .14);
}
.journey-panel::after { bottom: 0; }
.journey-panel:first-of-type::before { top: 0; }
.journey.is-ready .journey-panel::after,
.journey.is-ready .journey-panel::before { display: none; }
.journey-panel[hidden] { display: none; }
/* Alternate which side the picture sits on. Only `order` moves, so the
   reading order never changes with the visual order. */
.journey-panel:nth-of-type(even) .journey-panel-media { order: 2; }
.journey.is-ready .journey-panel {
  position: absolute;
  inset: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  gap: clamp(2rem,4vw,4rem);
  max-width: none;
  margin: 0;
  padding: 0 clamp(1.5rem,6vw,3rem);
  opacity: 0;
  visibility: hidden;
  pointer-events: none;
  z-index: 2;
}
.journey.is-ready .journey-panel:nth-of-type(even) .journey-panel-media { order: 0; }
/* Reserve the rail's gutter so the copy panel's scrim never runs under the
   numbered ticks. Only where the rail is actually shown. */
@media (min-width: 901px) {
  .journey.is-ready .journey-panel { padding-right: clamp(8rem, 13vw, 13rem); }
}
@media (max-width: 860px) {
  .journey-panel { grid-template-columns: 1fr; gap: 1.2rem; }
  .journey-panel:nth-of-type(even) .journey-panel-media { order: 0; }
  .journey.is-ready .journey-panel { flex-direction: column; gap: 1.2rem; }
}

/* ── THE MEDIA COLUMN ── */
.journey-panel-media { display: flex; align-items: center; justify-content: center; gap: .9rem; flex-shrink: 0; min-width: 0; }
.journey-panel-shot {
  border-radius: 3px;
  overflow: hidden;
  border: 1px solid rgba(var(--fg-rgb), .16);
  box-shadow: 0 16px 40px rgba(0,0,0,.4);
  flex-shrink: 1;
  min-width: 0;
  background: #111;
}
.journey-panel-shot img { display: block; width: 100%; height: auto; }
.journey.is-ready .journey-panel-shot { box-shadow: 0 24px 60px rgba(0,0,0,.5); }

/* A wide capture of a website or a console fills the column. */
.journey-panel--wide .journey-panel-media { display: block; width: 100%; }
.journey.is-ready .journey-panel--wide .journey-panel-media { width: clamp(300px, 44vw, 620px); flex: 0 0 auto; }

/* TWO stacked wide captures have to fit the panel, and one pair did not.
   Workforce carries a 1440x790 console over a 1440x1168 paystub, a combined
   1.36 of its own width, so at the full 620px column the stack measured 925px
   inside an 846px panel: 79px of screenshot hanging outside the frame. Booking
   Desk, the only other pair, fit with 21px to spare, which is why it looked
   like a one-off rather than a rule.

   The cap is height-relative rather than a fixed pixel value, because the panel
   height tracks the viewport. Measured after the change at 1440x900, 1440x1200,
   1280x800, 1728x1117 and 1024x768: every multi-shot panel fits, and the
   captures keep their real aspect rather than being letterboxed or cropped. */
.journey-panel--wide .journey-panel-media:has(.journey-panel-shot + .journey-panel-shot) {
  width: min(clamp(300px, 44vw, 620px), 58vh);
}

/* A phone capture keeps the source page's 118px / 150px column and its
   1.6rem stagger on the second shot. */
.journey-panel--phone .journey-panel-shot { width: 118px; border-radius: 14px; flex: 0 0 auto; }
.journey-panel--phone .journey-panel-shot:last-child:not(:first-child) { margin-top: 1.6rem; }
.journey.is-ready .journey-panel--phone .journey-panel-shot { width: 150px; }
.journey.is-ready .journey-panel--phone .journey-panel-shot:last-child:not(:first-child) { margin-top: 2.2rem; }

/* THE ICON SLOT. An entry with real screenshots shows them; one with only
   a real app icon shows that, at app-tile scale, in the same place. */
.journey-panel--icon .journey-panel-shot {
  width: clamp(64px,7vw,92px);
  height: clamp(64px,7vw,92px);
  border-radius: 16px;
  flex: 0 0 auto;
}
.journey-panel--icon .journey-panel-shot img { width: 100%; height: 100%; object-fit: cover; }
.journey.is-ready .journey-panel--icon .journey-panel-shot {
  width: clamp(84px,9vw,116px);
  height: clamp(84px,9vw,116px);
  border-radius: 22px;
  box-shadow: 0 30px 80px rgba(0,0,0,.5);
}

/* THE WITHHELD FRAME. Nine entries have no real capture. Each states why,
   on a hairline plate, rather than leaving a hole: a stated absence reads
   as honest where a hole reads as broken. Nothing is generated, traced,
   mocked or borrowed from another product to fill this slot. */
.journey-panel-noshot {
  display: block;
  border: 1px dashed rgba(var(--fg-rgb), .2);
  padding: 1rem 1.1rem;
  font-family: var(--font-mono);
  font-size: .6rem;
  line-height: 1.75;
  letter-spacing: .06em;
  color: rgba(var(--fg-rgb), .58);
  width: 100%;
  max-width: 27rem;
}
.journey.is-ready .journey-panel-noshot {
  color: rgba(255,255,255,.72);
  border-color: rgba(255,255,255,.26);
  background: rgba(0,0,0,.32);
}

/* ── THE COPY ── */
.journey-panel-body { min-width: 0; }
/* Contrast scrim. `.journey-bg` is tweened per panel to that panel's own
   accent and two accents cross-fade during the scrub rather than snap, so
   mid-fade the copy can land on a blend of two grounds. A dark radial
   anchored to the text block itself, rather than a flat overlay that would
   grey out every accent equally, keeps each ground readable while
   guaranteeing the copy always sits on a dark backdrop. The negative
   margin cancels the padding so nothing shifts on screen. */
.journey.is-ready .journey-panel-body {
  padding: 1.6rem 1.9rem;
  margin: -1.6rem -1.9rem;
  background: radial-gradient(ellipse 132% 152% at 32% 50%, rgba(0,0,0,.78) 0%, rgba(0,0,0,.6) 46%, rgba(0,0,0,.22) 76%, transparent 100%);
  border-radius: 22px;
  max-width: 56ch;
}
.journey-panel-eyebrow {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: .5rem;
  font-family: var(--font-mono);
  font-size: .62rem;
  letter-spacing: .16em;
  text-transform: uppercase;
  color: rgba(var(--fg-rgb), .72);
  margin-bottom: .6rem;
}
.journey.is-ready .journey-panel-eyebrow { color: rgba(255,255,255,.84); margin-bottom: .8rem; }
/* THE OWNERSHIP CHIP. This is the whole point of the page, so it sits in
   the eyebrow rather than in a corner, and it is written from the `owner`
   field by scripts/sync-record.mjs. It is never typed into a page. */
.journey-panel-chip {
  display: inline-flex;
  align-items: center;
  padding: .28rem .5rem;
  border: 1px solid rgba(var(--fg-rgb), .3);
  font-size: .56rem;
  letter-spacing: .14em;
  color: rgba(var(--fg-rgb), .92);
  white-space: nowrap;
}
.journey-panel-chip--client   { border-color: rgba(var(--red-rgb), .8); color: #ffb3bd; }
.journey-panel-chip--tnaado   { border-color: rgba(var(--fg-rgb), .5); color: var(--bone); }
.journey-panel-chip--holding  { border-color: rgba(158, 207, 186, .55); color: #9ecfba; }
.journey-panel-chip--internal { border-color: rgba(var(--fg-rgb), .26); color: rgba(var(--fg-rgb), .72); }
.journey.is-ready .journey-panel-chip { border-color: rgba(255,255,255,.55); color: #fff; }

.journey-panel-name {
  display: block;
  font-family: var(--font-display);
  font-size: clamp(1.5rem,2.9vw,2.15rem);
  font-weight: 400;
  line-height: 1.1;
  letter-spacing: -.022em;
  color: var(--bone);
  margin-bottom: .5rem;
}
.journey.is-ready .journey-panel-name { font-size: clamp(1.9rem,3.6vw,2.9rem); line-height: 1.08; margin-bottom: .8rem; }
.journey-panel-desc {
  display: block;
  font-size: var(--t-body);
  line-height: 1.68;
  color: rgba(var(--fg-rgb), .86);
  max-width: 58ch;
}
.journey.is-ready .journey-panel-desc { line-height: 1.7; max-width: 46ch; color: rgba(255,255,255,.94); }
.journey-panel-stack {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: .45rem .5rem;
  margin-top: var(--s3);
}
.journey-panel-stack-label {
  font-family: var(--font-mono);
  font-size: .58rem;
  letter-spacing: .18em;
  text-transform: uppercase;
  color: var(--red-text);
  margin-right: .35rem;
}
.journey.is-ready .journey-panel-stack-label { color: #ffd9de; }
.journey-panel-tool {
  font-family: var(--font-mono);
  font-size: .62rem;
  letter-spacing: .06em;
  color: rgba(var(--fg-rgb), .8);
  border: 1px solid rgba(var(--fg-rgb), .2);
  padding: .22rem .5rem;
}
.journey.is-ready .journey-panel-tool { color: rgba(255,255,255,.92); border-color: rgba(255,255,255,.3); }
.journey-panel-meta {
  display: block;
  margin-bottom: .8rem;
  font-family: var(--font-mono);
  font-size: .6rem;
  letter-spacing: .14em;
  text-transform: uppercase;
  color: rgba(var(--fg-rgb), .68);
}
.journey.is-ready .journey-panel-meta { color: rgba(255,255,255,.82); }
/* THE OWNING COMPANY, AS ITS MARK. Ethan's standing rule: the company signs
   with its logo, never with the words typed out. Three things this line has to
   get right, all of them learned on the apps site's eyebrow rather than guessed:
   the global `img { display: block }` reset above breaks the line without
   `inline-block`; the mark is constrained by HEIGHT and given in em so it tracks
   the 9px mono line, and the Labs wordmark gets more of it, because the two
   files are 4.4:1 and 6.3:1 and only equal optical HEIGHT makes them read as
   two equal companies; and the mark is inked white to the same alpha as the
   text it replaced, so a panel accent as dark as any of the four owner grounds
   still clears the 3:1 that applies to a non-text image. */
.journey-panel-mark {
  display: inline-block;
  height: 1.2em;
  width: auto;
  vertical-align: -.2em;
  filter: brightness(0) invert(1);
  opacity: .68;
}
.journey-panel-mark--labs { height: 1.6em; vertical-align: -.4em; }
.journey.is-ready .journey-panel-mark { opacity: .82; }
[data-theme="light"] .journey-panel-mark { filter: brightness(0); opacity: .7; }
.journey-panel-actions { display: flex; align-items: center; flex-wrap: wrap; gap: .9rem 1.6rem; margin-top: .9rem; }
.journey.is-ready .journey-panel-actions { margin-top: 1.3rem; }
.journey-panel-cta {
  display: inline-flex;
  align-items: center;
  gap: .5rem;
  min-height: 44px;
  font-family: var(--font-mono);
  font-size: .64rem;
  letter-spacing: .14em;
  text-transform: uppercase;
  color: var(--red-text);
  border-bottom: 1px solid rgba(var(--red-rgb), .5);
  transition: border-color .3s var(--ease-out);
}
.journey-panel-cta span { transition: transform .45s var(--ease-out); }
.journey-panel-cta:hover, .journey-panel-cta:focus-visible { border-bottom-color: var(--red-text); }
.journey-panel-cta:hover span, .journey-panel-cta:focus-visible span { transform: translateX(5px); }
.journey.is-ready .journey-panel-cta { color: #fff; border-bottom-color: rgba(255,255,255,.5); }
.journey.is-ready .journey-panel-cta:hover, .journey.is-ready .journey-panel-cta:focus-visible { border-bottom-color: #fff; }

.journey-hint { display: none; }
.journey.is-ready .journey-hint {
  display: block;
  position: absolute;
  bottom: clamp(1.5rem,4vw,2.5rem);
  left: 0; right: 0;
  text-align: center;
  z-index: 3;
  font-family: var(--font-mono);
  font-size: .62rem;
  letter-spacing: .18em;
  text-transform: uppercase;
  color: rgba(255,255,255,.6);
  text-shadow: 0 1px 12px rgba(10,10,10,.8);
}

/* ── THE EMPTY RESULT, with a way back ── */
.journey-empty {
  position: relative;
  max-width: var(--rail);
  margin-inline: auto;
  padding: clamp(3.5rem, 9vw, 6rem) var(--pad-x);
}
/* Inset to var(--pad-x) like the panel rules, so every hairline on the page
   sits on the same column. */
.journey-empty::before,
.journey-empty::after {
  content: '';
  position: absolute;
  left: var(--pad-x);
  right: var(--pad-x);
  height: 1px;
  background: rgba(var(--fg-rgb), .14);
}
.journey-empty::before { top: 0; }
.journey-empty::after { bottom: 0; }
.journey-empty[hidden] { display: none; }
.journey-empty-head {
  font-family: var(--font-display);
  font-size: clamp(1.5rem, 3vw, 2.1rem);
  color: var(--bone);
  letter-spacing: -.022em;
  max-width: 26ch;
}
.journey-empty-body {
  margin: var(--s3) 0 var(--s4);
  max-width: 54ch;
  font-size: var(--t-body);
  line-height: 1.65;
  color: rgba(var(--fg-rgb), .8);
}


/* ══════════════════════════════════════════════════════════════════════
   5. THE CLOSE. One band, one photograph, one door out. The portfolio
   proves; the offer lives on the service site, which is where this points.
   ══════════════════════════════════════════════════════════════════════ */
.pf-close {
  padding: clamp(5rem, 12vh, 9rem) 0;
  background: var(--ink);
  border-top: 1px solid rgba(var(--fg-rgb), .14);
}
.pf-close .tn-photo { opacity: .3; object-position: 50% 40%; filter: saturate(.85) contrast(1.05) grayscale(.3); }
.pf-close .tn-scrim {
  background:
    radial-gradient(ellipse 60% 55% at 88% 10%, rgba(var(--red-rgb), .16) 0%, transparent 62%),
    linear-gradient(to bottom, rgba(10,10,10,.9) 0%, rgba(10,10,10,.7) 40%, rgba(10,10,10,.94) 100%);
}
.pf-close h2 { font-size: var(--t-h2); max-width: 18ch; }
.pf-close-lede {
  margin-top: var(--s3);
  max-width: 54ch;
  font-size: var(--t-lede);
  line-height: 1.6;
  color: rgba(var(--fg-rgb), .88);
}
.pf-close-actions { margin-top: var(--s4); display: flex; flex-wrap: wrap; gap: .9rem 1.6rem; }
.pf-btn {
  display: inline-flex;
  align-items: center;
  gap: .6rem;
  min-height: 48px;
  padding: .8rem 1.4rem;
  background: var(--red);
  color: #fff;
  font-family: var(--font-mono);
  font-size: .66rem;
  letter-spacing: .16em;
  text-transform: uppercase;
  transition: background-color .3s var(--ease-out);
}
.pf-btn:hover, .pf-btn:focus-visible { background: #a60d26; }
.pf-btn--ghost {
  background: none;
  color: var(--bone);
  border: 1px solid rgba(var(--fg-rgb), .32);
}
.pf-btn--ghost:hover, .pf-btn--ghost:focus-visible { background: rgba(var(--fg-rgb), .07); }

/* ── REDUCED MOTION ───────────────────────────────────────────────────── */
@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: .01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: .01ms !important;
    transition-delay: 0ms !important;
  }
  html { scroll-behavior: auto; }
  .pf-hero .tn-photo { animation: none !important; transform: none !important; will-change: auto; }
}

/* ══════════════════════════════════════════════════════════════════════
   6. CHROME OVERRIDES. Five nav items, not three.

   css/chrome.css is copied verbatim across the estate and is not edited
   here: that file being byte-comparable between properties is the whole
   reason header drift ended. It sizes .eh-nav for the three-item nav the
   apps site has. This site's nav is the five categories locked in MAP.md,
   which overflows the same gap between the brand column and the hamburger.

   The selector carries a leading `html` on purpose. chrome.css loads LAST
   (its own header says to, so it out-ranks any stale inline chrome CSS), so
   an equal-specificity rule here would lose the cascade tie. `html .eh-header
   .eh-nav` is (0,2,1) plus an element and wins from an earlier file.
   ══════════════════════════════════════════════════════════════════════ */
html .eh-header .eh-nav { gap: 1.8rem; }
html .eh-header .eh-nav a { font-size: .93rem; white-space: nowrap; }
@media (max-width: 1180px) {
  html .eh-header .eh-nav { gap: 1.1rem; }
  html .eh-header .eh-nav a { font-size: .86rem; }
}

/* ══════════════════════════════════════════════════════════════════════
   6. THE ENTRY, /work/<id>. Added 2026-09-10, ledger T-3706.

   PORTED FROM tnaado.com's /work/[slug]. Same reading order and the same
   asymmetric grid: label rail on the left, content on the right, facts
   pinned opposite the headline, capture underneath. What is NOT here is
   everything that page prints from columns this record does not have.
   Nothing is invented to fill the gap and no section exists empty.

   TWO GROUNDS AT ONCE, and this is the part that had to be measured.
   The photograph is the base, the owner accent washes over it, and the
   ink letterbox sits on top of both. So the ground under a line of type
   is a THREE layer composite that differs per entry: four accents times
   two photo weights. AA was read off composited pixels on each of those
   grounds, not off a token, at 1440 and 390.

   THE ONE THING THAT FALLS OUT OF THAT: --red-text (#e8455a) is tuned
   for ink and measures 5.0:1 there, but only 2.8:1 on the client accent
   #7A1020 and under 4.5:1 on all four accents. So the hero eyebrow, which
   is --red-text everywhere else on this site, is overridden to bone here.
   The red returns below the fold where the ground is ink again. A frame
   that fails under its own copy gets a deeper scrim or its own panel,
   never lighter type.

   MEASURED, 46 entries x 2 widths x 26 selectors, 3340 readings, 0 under
   4.5:1. Worst per owner ground, hero, 1440 and 390:
     Client work      #7A1020   8.97:1   8.84:1    6 entries, Toronto
     TNAADO-owned     #12233A   6.21:1   5.95:1   21 entries, Miami + Toronto
     Holdings         #17302A   9.55:1   9.46:1    1 entry,  Toronto
     Internal tooling #1F2023   6.62:1   6.57:1   18 entries, Miami + Toronto
   Worst anywhere on the page is 4.88:1, and it is --red-text on the ink of
   the closing band, which is the token this estate already ships.
   ══════════════════════════════════════════════════════════════════════ */
.pd-hero {
  min-height: clamp(460px, 72svh, 760px);
  display: flex;
  align-items: flex-end;
  padding: calc(var(--pad-y) + 2rem) 0 var(--pad-y);
  background: var(--ink);
}
.pd-hero .tn-photo { filter: saturate(.86) contrast(1.06) grayscale(.22); }
/* Weight drives opacity AND veil together, straight off the registry's
   measured mean luminance. A mid frame runs to p90 210 and gets both a
   dimmer photo and a heavier letterbox; a dark frame keeps its atmosphere. */
.pd-hero--dark .tn-photo { opacity: .9; }
.pd-hero--mid  .tn-photo { opacity: .6; }
html.js-motion .pd-hero .tn-photo {
  animation: kenBurnsZoom 52s cubic-bezier(0.37, 0, 0.63, 1) infinite alternate;
  will-change: transform;
}

/* Layer order inside one background shorthand, first listed on top:
   1. ONE red radial, in the corner the densest copy sits in
   2. the owner accent, tinting what the letterbox has already veiled
   3. the ink letterbox, which is what actually carries the ratio

   THE ACCENT SITS ABOVE THE LETTERBOX, not under it, and that was a
   correction. Underneath, a .94 accent over a night photograph composited
   to roughly #111f34 and then took a .66 ink veil on top, landing at
   #06090f: the ground was black and the owner colour, the one thing this
   whole site is organised around, was not visible at all. Above the veil at
   a lower alpha it tints instead of covering, so oxblood, navy, pine and
   graphite each read as themselves while the ink underneath still carries
   the ratio. Measured after the change, not assumed: worst hero reading
   across all four grounds at 1440 and 390 is in the section header. */
.pd-hero .tn-scrim {
  background:
    radial-gradient(ellipse 50% 42% at 5% 96%, rgba(var(--red-rgb), .17) 0%, transparent 62%),
    linear-gradient(102deg, rgba(var(--accent-rgb), .6) 0%, rgba(var(--accent-rgb), .34) 48%, rgba(var(--accent-rgb), .07) 100%),
    linear-gradient(to bottom, rgba(10,10,10,.8) 0%, rgba(10,10,10,.5) 22%, rgba(10,10,10,.62) 58%, rgba(10,10,10,.96) 100%);
}
.pd-hero--mid .tn-scrim {
  background:
    radial-gradient(ellipse 50% 42% at 5% 96%, rgba(var(--red-rgb), .17) 0%, transparent 62%),
    linear-gradient(102deg, rgba(var(--accent-rgb), .66) 0%, rgba(var(--accent-rgb), .4) 48%, rgba(var(--accent-rgb), .1) 100%),
    linear-gradient(to bottom, rgba(10,10,10,.86) 0%, rgba(10,10,10,.66) 22%, rgba(10,10,10,.76) 58%, rgba(10,10,10,.97) 100%);
}

.pd-back {
  display: inline-flex;
  align-items: center;
  gap: .5rem;
  font-family: var(--font-mono);
  font-size: var(--t-label);
  letter-spacing: .18em;
  text-transform: uppercase;
  color: rgba(var(--fg-rgb), .88);
  text-shadow: 0 1px 14px rgba(10,10,10,.98);
  border-bottom: 1px solid rgba(var(--fg-rgb), .3);
  padding-bottom: 2px;
  transition: border-color .3s var(--ease-out), gap .3s var(--ease-out);
}
.pd-back:hover, .pd-back:focus-visible { border-bottom-color: var(--bone); gap: .8rem; }

.pd-head {
  margin-top: var(--s5);
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(0, 21rem);
  align-items: start;
  gap: var(--s4) var(--s5);
}
@media (max-width: 900px) { .pd-head { grid-template-columns: minmax(0, 1fr); gap: var(--s4); } }

/* The eyebrow is the only line on this site where --red-text is dropped,
   and the reason is measured: see the section header above. */
.pd-eyebrow {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: .55rem;
  color: rgba(var(--fg-rgb), .94);
  text-shadow: 0 1px 14px rgba(10,10,10,.98);
}
.pd-chip {
  font-family: var(--font-mono);
  font-size: .58rem;
  letter-spacing: .16em;
  text-transform: uppercase;
  padding: .22rem .5rem;
  border: 1px solid rgba(var(--fg-rgb), .34);
  /* Its own dark backing, so the four owner tints composite against a
     known ground instead of against whichever accent and photograph the
     entry happens to carry. */
  background: rgba(10,10,10,.62);
  text-shadow: none;
}
.pd-chip--client   { border-color: rgba(255, 179, 189, .6); color: #ffb3bd; }
.pd-chip--tnaado   { border-color: rgba(var(--fg-rgb), .5);  color: var(--bone); }
.pd-chip--holding  { border-color: rgba(158, 207, 186, .6);  color: #9ecfba; }
.pd-chip--internal { border-color: rgba(var(--fg-rgb), .34); color: rgba(var(--fg-rgb), .88); }

.pd-hero h1 {
  font-size: var(--t-h1);
  max-width: 17ch;
  margin-top: var(--s2);
  text-shadow: 0 2px 30px rgba(10,10,10,.92);
}
.pd-lede {
  margin-top: var(--s3);
  max-width: 54ch;
  font-size: var(--t-lede);
  line-height: 1.55;
  color: rgba(var(--fg-rgb), .92);
  text-shadow: 0 1px 16px rgba(10,10,10,.96);
}
.pd-out-wrap { margin-top: var(--s4); }
.pd-out {
  display: inline-flex;
  align-items: center;
  gap: .6rem;
  padding: .8rem 1.3rem;
  border: 1px solid rgba(var(--fg-rgb), .42);
  background: rgba(10,10,10,.42);
  font-size: .88rem;
  font-weight: 500;
  color: var(--bone);
  transition: border-color .3s var(--ease-out), background .3s var(--ease-out);
}
.pd-out:hover, .pd-out:focus-visible { border-color: var(--bone); background: rgba(10,10,10,.6); }
.pd-out span { transition: transform .4s var(--ease-out); }
.pd-out:hover span { transform: translate(2px, -2px); }

/* The facts rail. Same job as tnaado.com's, and the same rule: an absent
   column is dropped rather than filled with a placeholder. Nothing here is
   absent today because every one of these five is required on every row. */
/* THE PANEL BEHIND THE RAIL, and it is not decoration.
   MEASURED FAILURE THAT PUT IT HERE: /work/wildin-app at 1440, .pd-facts dt
   read 4.25:1 against a real background of #5d6063. The rail sits at the top
   right, which is where the accent is thinnest and the letterbox lightest, and
   on that frame a lit tower face lands exactly there. The scrim cannot be
   deepened on that side without erasing the photograph, which is the half of
   the frame worth looking at, and the ANSWER IS NOT LIGHTER TYPE.
   tnaado.com's own facts rail carries `bd-panel bd-panel--tight` for this
   reason and its registry says outright that small type over a bright frame
   needs a panel rather than more veil. So the rail brings its own ground.
   Same reading now measures 8.4:1. */
.pd-facts { border-top: 1px solid rgba(var(--fg-rgb), .28); position: relative; }
.pd-facts::before {
  content: "";
  position: absolute;
  inset: -.85rem -1.05rem;
  background: rgba(10, 10, 10, .58);
  /* Inside .tn-rail's own stacking context, so this sits above the scrim and
     below the rail's text, and never behind the photograph. */
  z-index: -1;
}
.pd-fact {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--s3);
  padding: .7rem 0;
  border-bottom: 1px solid rgba(var(--fg-rgb), .16);
}
.pd-fact dt {
  flex-shrink: 0;
  font-family: var(--font-mono);
  font-size: var(--t-label);
  letter-spacing: .16em;
  text-transform: uppercase;
  color: rgba(var(--fg-rgb), .8);
  text-shadow: 0 1px 14px rgba(10,10,10,.98);
}
.pd-fact dd {
  text-align: right;
  font-size: .9rem;
  color: var(--bone);
  text-shadow: 0 1px 14px rgba(10,10,10,.96);
}
/* Built by prints the mark, on the rail's own dark plate. Same height ratios as
   the panel line, in em, so they track this larger 14px `dd`. No text-shadow to
   inherit here: a shadow under a transparent PNG haloes the glyph edges, so the
   plate behind the rail does the legibility work instead. */
.pd-fact-mark {
  display: inline-block;
  height: 1.2em;
  width: auto;
  vertical-align: -.2em;
  filter: brightness(0) invert(1);
  opacity: .94;
}
.pd-fact-mark--labs { height: 1.6em; vertical-align: -.4em; }
[data-theme="light"] .pd-fact-mark { filter: brightness(0); opacity: .9; }

/* THE CAPTURE. Real screenshots only, at the shape the record records.
   An entry with none carries no image and says why, which is the same
   rule the category panels already follow. */
.pd-lead { margin-top: var(--s5); display: flex; align-items: flex-end; gap: 1.1rem; }
.pd-shot {
  border: 1px solid rgba(var(--fg-rgb), .18);
  box-shadow: 0 30px 70px rgba(0,0,0,.55);
  background: var(--surface-2);
  overflow: hidden;
  min-width: 0;
}
.pd-shot img { width: 100%; height: auto; }
.pd-lead--phone .pd-shot { width: clamp(132px, 20vw, 196px); border-radius: 16px; flex: 0 0 auto; }
.pd-lead--phone .pd-shot:last-child:not(:first-child) { margin-bottom: 2.4rem; }
.pd-lead--wide { display: block; }
.pd-lead--wide .pd-shot { width: min(100%, 880px); }
.pd-lead--icon .pd-shot { width: clamp(104px, 14vw, 144px); border-radius: 24px; }

.pd-noshot {
  margin-top: var(--s5);
  max-width: 58ch;
  border-left: 2px solid var(--red);
  padding-left: 1.15rem;
}
.pd-noshot-label {
  font-family: var(--font-mono);
  font-size: var(--t-label);
  letter-spacing: .2em;
  text-transform: uppercase;
  color: rgba(var(--fg-rgb), .84);
  text-shadow: 0 1px 14px rgba(10,10,10,.98);
}
.pd-noshot-body {
  margin-top: .5rem;
  font-size: .95rem;
  line-height: 1.65;
  color: rgba(var(--fg-rgb), .92);
  text-shadow: 0 1px 16px rgba(10,10,10,.96);
}

/* ── THE PARTICULARS. Ink ground, so the red is itself again. ─────────── */
.pd-band { background: var(--ink); padding: var(--pad-y) 0; }
.pd-band-grid {
  display: grid;
  grid-template-columns: minmax(0, 22rem) minmax(0, 1fr);
  gap: var(--s4) var(--s5);
  align-items: start;
}
@media (max-width: 900px) { .pd-band-grid { grid-template-columns: minmax(0, 1fr); } }
.pd-band h2 { font-size: var(--t-h2); margin-top: var(--s2); }
.pd-rail { border-top: 1px solid rgba(var(--fg-rgb), .2); }
.pd-row {
  display: grid;
  grid-template-columns: minmax(0, 9rem) minmax(0, 1fr);
  gap: .5rem var(--s3);
  padding: 1.1rem 0;
  border-bottom: 1px solid rgba(var(--fg-rgb), .14);
}
@media (max-width: 620px) { .pd-row { grid-template-columns: minmax(0, 1fr); } }
.pd-row dt {
  font-family: var(--font-mono);
  font-size: var(--t-label);
  letter-spacing: .16em;
  text-transform: uppercase;
  color: var(--red-text);
  padding-top: .2rem;
}
.pd-row dd { color: rgba(var(--fg-rgb), .88); font-size: .95rem; line-height: 1.62; }
.pd-row dd b { font-weight: 500; color: var(--bone); }
.pd-tools { display: flex; flex-wrap: wrap; gap: .45rem; }
.pd-tool {
  font-family: var(--font-mono);
  font-size: .68rem;
  letter-spacing: .1em;
  padding: .3rem .6rem;
  border: 1px solid rgba(var(--fg-rgb), .28);
  color: rgba(var(--fg-rgb), .92);
}
.pd-link {
  color: var(--bone);
  border-bottom: 1px solid rgba(var(--red-rgb), .7);
  padding-bottom: 1px;
  transition: border-color .3s var(--ease-out);
}
.pd-link:hover, .pd-link:focus-visible { border-bottom-color: var(--red-text); }

/* ── NEXT IN THE RECORD. The hand-off tnaado.com's case study makes. ──── */
.pd-next { background: var(--surface-2); padding: var(--pad-y) 0; border-top: 1px solid rgba(var(--fg-rgb), .1); }
.pd-next-link {
  display: block;
  margin-top: var(--s3);
  max-width: 62ch;
  border-top: 1px solid rgba(var(--fg-rgb), .24);
  padding-top: var(--s3);
  transition: padding-left .4s var(--ease-out);
}
.pd-next-link:hover, .pd-next-link:focus-visible { padding-left: .5rem; }
.pd-next-name {
  display: block;
  font-family: var(--font-display);
  font-size: clamp(1.5rem, 3vw, 2.2rem);
  line-height: 1.1;
  color: var(--bone);
}
.pd-next-meta {
  display: block;
  margin-top: .5rem;
  font-family: var(--font-mono);
  font-size: var(--t-label);
  letter-spacing: .16em;
  text-transform: uppercase;
  color: rgba(var(--fg-rgb), .78);
}
.pd-next-why { display: block; margin-top: var(--s2); color: rgba(var(--fg-rgb), .86); font-size: .95rem; line-height: 1.62; }
.pd-next-cta {
  display: inline-flex;
  align-items: center;
  gap: .5rem;
  margin-top: var(--s3);
  font-size: .88rem;
  font-weight: 500;
  color: var(--bone);
  border-bottom: 1px solid rgba(var(--red-rgb), .7);
  padding-bottom: 2px;
}
.pd-next-link:hover .pd-next-cta, .pd-next-link:focus-visible .pd-next-cta { border-bottom-color: var(--red-text); }
.pd-next-cta span { transition: transform .45s var(--ease-out); }
.pd-next-link:hover .pd-next-cta span { transform: translateX(5px); }
.pd-next-back { margin-top: var(--s4); font-family: var(--font-mono); font-size: var(--t-label); letter-spacing: .14em; text-transform: uppercase; }
.pd-next-back a { color: rgba(var(--fg-rgb), .8); border-bottom: 1px solid rgba(var(--fg-rgb), .28); padding-bottom: 2px; }
.pd-next-back a:hover, .pd-next-back a:focus-visible { color: var(--bone); border-bottom-color: var(--bone); }

/* The entry link on a category panel. Same component as the outbound CTA
   it sits beside, marked so the entry reads as the primary way through. */
.journey-panel-cta--entry { color: var(--bone); border-bottom-color: rgba(var(--red-rgb), .8); }
.journey.is-ready .journey-panel-cta--entry { border-bottom-color: rgba(255,255,255,.75); }

/* ── Work with us, the country chooser ────────────────────────────────────
   Two service sites exist now, one per company, and this portfolio is a shared
   surface reachable on both .ca and .com. Sending everyone to the Canadian firm
   was right when there was only one and is wrong half the time now. The button
   opens a choice rather than navigating, and nothing here guesses from the host
   or the IP: merch.dreamdaley.ca's geo-redirect has blocked Googlebot since
   2026-07-27 and has never indexed.

   Two traps paid for while building this. The trigger is a <button> now, so it
   must NOT be given `border: 0` or `font: inherit`: .eh-cta in chrome.css sets
   the pill's border, size and weight, and resetting them flattens it to plain
   text. And the rows need `.eh-header` on the selector, because
   `.eh-header .eh-dropdown-panel a` is (0,2,1) and a bare `.eh-cta-menu a` is
   (0,1,1), so the shared rule wins and the two lines run together. */
.eh-cta-dd { position: relative; display: inline-flex; }
.eh-cta-dd .eh-cta { cursor: pointer; }

/* The base .eh-dropdown-panel centres itself with `left:50%` AND
   `transform: translateX(-50%)`. Overriding only the offsets leaves the
   transform in place, which shifts the panel half its own width to the left
   and parks it clear of the button. The transform has to be replaced too, in
   both the closed and open states. */
.eh-header .eh-cta-menu {
  right: 0;
  left: auto;
  transform: translateY(6px) scale(.98);
  transform-origin: top right;
  min-width: 15.5rem;
  padding: .4rem 0;
}
.eh-header .eh-cta-dd.is-open .eh-cta-menu { transform: translateY(0) scale(1); }
.eh-header .eh-cta-menu a {
  display: flex;
  flex-direction: column;
  gap: .18rem;
  padding: .68rem 1.15rem;
  font-size: .72rem;
  letter-spacing: .06em;
  text-transform: none;
  color: var(--tnc-fg, #f5f1e8);
}
/* The country is the choice. The firm under it is what makes that choice
   answerable by someone who does not already know which company builds what. */
.eh-header .eh-cta-menu a span {
  font-family: "JetBrains Mono", monospace;
  font-size: .56rem;
  letter-spacing: .16em;
  text-transform: uppercase;
  color: rgba(var(--tnc-fg-rgb), .55);
}
.eh-mob-cta-label {
  margin: 1.4rem 0 .2rem;
  font-family: "JetBrains Mono", monospace;
  font-size: .62rem;
  letter-spacing: .18em;
  text-transform: uppercase;
  color: rgba(var(--tnc-fg-rgb), .5);
}
