/* Site-wide additions on top of the design-canvas output.
 *
 * Everything is !important because the sources set these properties as inline
 * style attributes, which no stylesheet selector can outrank on specificity.
 */

/* ---------------------------------------------------------------
   Contact call to action
   The sources ship name/organization/message fields and a
   type="button" with no form, no action and no handler — a visitor
   could type an inquiry, press send, and nothing would happen. The
   fields are replaced at build time with a real published address.
   --------------------------------------------------------------- */

.tdf-contact {
  display: flex;
  flex-direction: column;
  gap: 14px;
}

.tdf-contact-label {
  margin: 0;
  font-family: 'IBM Plex Mono', ui-monospace, monospace;
  font-size: 10px;
  font-weight: 600;
  letter-spacing: 0.18em;
  text-transform: uppercase;
  color: #6e6c66;
}

.tdf-contact-link {
  display: inline-flex;
  align-items: center;
  align-self: flex-start;
  min-height: 48px;
  padding: 13px 20px;
  background: #0a0a0a;
  color: #fff !important;
  border: 0;
  font-family: 'IBM Plex Mono', ui-monospace, monospace;
  font-size: 13px;
  font-weight: 600;
  letter-spacing: 0.08em;
  text-decoration: none;
  word-break: break-all;
  transition: background 0.18s cubic-bezier(0.23, 1, 0.32, 1);
}

.tdf-contact-link:hover {
  background: #6f58ff;
  color: #fff !important;
}

.tdf-contact-link:focus-visible {
  outline: 2px solid #6f58ff;
  outline-offset: 2px;
}

.tdf-contact-note {
  margin: 0;
  max-width: 46ch;
  font-family: Manrope, system-ui, sans-serif;
  font-size: 13px;
  line-height: 1.6;
  color: #5a5a5a;
}

/* 13px is a desktop size; on a phone this is body copy like any other. */
@media (max-width: 900px) {
  .tdf-contact-note {
    font-size: 14.5px;
    line-height: 1.65;
  }
}

/* ---------------------------------------------------------------
   Readability floors
   DESIGN_SYSTEM.md sets mono micro-type at 9-11px, but some elements
   compute as low as 7.5px, and a number of links and buttons are
   under 32px tall. These set a floor without touching the design's
   intended hierarchy.
   --------------------------------------------------------------- */

.tdf-microfloor {
  font-size: 9.5px !important;
}

/* Give in-flow links and buttons a real touch target on coarse
   pointers, where the 44px guideline actually matters. */
@media (pointer: coarse) {
  /* A link that is its own block is something you aim at, so it gets the 44px
     touch guideline. `.tdf-tap` is applied at build time to links and buttons
     whose computed display is not inline — a link inside a sentence is left
     alone, because giving it a height would break the line it sits in. */
  .tdf-tap {
    min-height: 44px;
  }

  /* A link that is the whole of its block is inline by default, so a height
     does nothing to it until it becomes a box of its own. */
  .tdf-tap-alone {
    display: inline-flex;
    align-items: center;
  }

  /* The header's brand link. The tap floor skips the header so it cannot
     stretch the nav row, which left the mark as a 30px target centred in a
     58px bar. Filling the bar's height is the floor and the same picture. */
  .tdf-brand {
    align-self: stretch;
    min-height: 44px;
  }

  /* Form controls are aimed at too. */
  input,
  select,
  textarea {
    min-height: 44px;
  }

  /* Where the target is a flex or grid row already, centre its label in the
     taller box rather than letting it sit at the top. */
  .tdf-tap[style*='display: flex'],
  .tdf-tap[style*='display: inline-flex'] {
    align-items: center;
  }

  main a,
  footer a,
  main button {
    min-height: 32px;
  }

  .tdf-navpanel a {
    min-height: 52px;
  }
}

/* ---------------------------------------------------------------
   System card showroom poster
   Deliberately not styled here. The build writes the poster's
   styles inline (build/build.js, step 2c-4b), because the state it
   covers — a <tdf-showroom> that has not been upgraded yet — is
   what a visitor sees before showroom.js runs, and permanently with
   JavaScript off. It must survive this stylesheet being stale,
   slow or absent. It shipped as a class once and returning
   visitors got a full-bleed icon.
   --------------------------------------------------------------- */

/* ---------------------------------------------------------------
   Section 04 plate
   The siting line rendered at 51.84px against the section heading's
   46.08px, so a location outranked the heading above it. It belongs
   with the plate labels, not competing with the h2.
   --------------------------------------------------------------- */

.tdf-plate-place {
  font-size: clamp(20px, 2vw, 26px) !important;
  letter-spacing: -0.01em !important;
}

/* ---------------------------------------------------------------
   Landing page closing contact line
   /contact/ appeared once on the landing page, as the tertiary
   button in the final section, and the address not at all. This
   lets a visitor act without another click.
   --------------------------------------------------------------- */

.tdf-inline-contact {
  margin: 18px 0 0;
  font-family: Manrope, system-ui, sans-serif;
  font-size: 14px;
  line-height: 1.6;
  color: #595959;
}

.tdf-inline-contact a {
  color: #0a0a0a;
  font-family: 'IBM Plex Mono', ui-monospace, monospace;
  font-size: 13px;
  text-decoration: underline;
  text-underline-offset: 3px;
  word-break: break-all;
}

.tdf-inline-contact a:hover {
  color: #6f58ff;
}

/* ---------------------------------------------------------------
   F1-F5 discipline tiles
   The artwork is 800x520. The tiles carried only a min-height, so
   their aspect ratio followed the column width and reached 2.49 at
   1920px, where `cover` rendered the drawing 62% oversized and cut
   38% of it away. Matching the tile to the artwork shows each
   drawing as it was composed; `contain` rather than `cover` so a
   drawing can never be cropped again, and the letterbox is
   invisible because each file paints its own #0A0A0A ground.
   --------------------------------------------------------------- */

.tdf-ftile {
  position: relative !important;
  aspect-ratio: 800 / 520 !important;
  min-height: 0 !important;
  background-size: contain !important;
}

/* Each tile reads as a drawing plate, which is the framed-viewport idiom
   DESIGN_SYSTEM.md already uses for the page itself. The rule also gives the
   subject a defined field to sit in: `npm run ftiles` reframes each drawing to
   fill 62% of the tile, and without a boundary that margin reads as the tile
   being half empty rather than the drawing being inset. */
.tdf-ftile::after {
  content: '';
  position: absolute;
  inset: 14px;
  border: 1px solid rgba(241, 240, 234, 0.18);
  pointer-events: none;
}

/* The caption was a full-width 74% white slab, the only light-on-dark panel on
   the site, with the index and the title pushed to opposite ends of the tile
   like a table row. It is now one group sitting on the plate rule, in the
   treatment the showroom's own tag uses. */
.tdf-ftile-label {
  left: 14px !important;
  right: auto !important;
  bottom: 14px !important;
  display: inline-flex !important;
  align-items: baseline !important;
  justify-content: flex-start !important;
  gap: 13px !important;
  padding: 9px 13px !important;
  background: rgba(5, 5, 5, 0.92) !important;
  color: #a7a59e !important;
}

.tdf-ftile-label > span {
  color: #f1f0ea !important;
}

/* The intro cell is as tall as a tile and bottom-aligned its three lines,
   leaving most of a white cell empty above them. */
.tdf-fintro {
  justify-content: center !important;
}

/* Five tiles one per row is a very long scroll, and one per row on a tablet
   wastes half the width. Two columns from 560px up to the desktop breakpoint,
   one below it, where a half-width drawing is too small to read. */
@media (min-width: 560px) and (max-width: 1239px) {
  .tdf-ftile-grid {
    grid-template-columns: repeat(2, minmax(0, 1fr)) !important;
  }
}

/* On a phone, five full-width tiles are 1172px of scroll — a screen and a half
   for a set of five labels. Two up costs 566px and the drawings stay legible:
   each is line art on black, reframed by `npm run ftiles` to fill 62% of its
   plate, so halving the width halves a drawing that was already generously
   inset rather than shrinking one that filled its frame. */
@media (max-width: 559px) {
  .tdf-ftile-grid {
    grid-template-columns: repeat(2, minmax(0, 1fr)) !important;
    gap: 12px !important;
  }

  /* aspect-ratio drives width from height, so a max-height alone narrowed the
     tile to 308px and left a gap beside it. Drop the ratio and set the height. */
  .tdf-ftile {
    aspect-ratio: auto !important;
    height: 132px !important;
    max-height: none !important;
  }

  .tdf-ftile::after {
    inset: 7px;
  }

  /* Five tiles in two columns leave a hole in the last row. The fifth runs the
     full width instead, which closes the grid and reads as the end of the set. */
  .tdf-ftile-grid > .tdf-fintro,
  .tdf-ftile-grid > .tdf-ftile:last-child {
    grid-column: 1 / -1 !important;
  }

  /* The label is positioned from the left with no right edge, so on a half-width
     tile a two-word discipline ran out past the plate rule and over the tile
     beside it. Bounded to the plate, and allowed to take a second line. */
  .tdf-ftile-label {
    left: 7px !important;
    bottom: 7px !important;
    max-width: calc(100% - 14px) !important;
    gap: 8px !important;
    padding: 6px 9px !important;
  }

  /* At full desktop size the longest discipline filled the half-width label
     edge to edge every time, so the caption read as a bar rather than a tag.
     A step down lets most of them sit on one line with air to the right. */
  .tdf-ftile-label > span {
    font-size: 11.5px !important;
    line-height: 1.35 !important;
  }
}

/* The span reset has to cover the whole range where this grid is not its
   authored 4 columns. Left only in the 900px tier, the intro cell's
   `grid-column: span 3` was still minting implicit columns between 901 and
   1239px, where the two-column rule above then produced three. */
@media (max-width: 1239px) {
  .tdf-ftile-grid > * {
    grid-column: auto !important;
  }
}

/* Swipe affordance. Only shown where the frame actually scrolls, which is
   phones and small tablets; on a desktop the drawing fits and the hint would
   be noise. */
.tdf-swipe {
  display: none;
}

@media (max-width: 900px) {
  .tdf-swipe {
    display: block;
    margin: 8px 0 0;
    font-family: 'IBM Plex Mono', ui-monospace, monospace;
    font-size: 9.5px;
    letter-spacing: 0.16em;
    color: #6f6f6f;
  }
}

/* ---------------------------------------------------------------
   Cross-link
   The declared inbound link to a page that is in the build but not
   in the primary nav. Secondary button treatment, matching the
   outlined call to action the sources already use.
   --------------------------------------------------------------- */

.tdf-crosslink {
  display: inline-flex;
  align-items: center;
  align-self: flex-start;
  min-height: 44px;
  margin-top: 24px;
  padding: 14px 22px;
  border: 1px solid rgba(10, 10, 10, 0.35);
  color: #0a0a0a;
  font-family: 'IBM Plex Mono', ui-monospace, monospace;
  font-size: 11px;
  font-weight: 600;
  letter-spacing: 0.16em;
  text-decoration: none;
  transition: border-color 0.18s, color 0.18s;
}

.tdf-crosslink:hover {
  border-color: #0a0a0a;
  color: #6f58ff;
}

.tdf-crosslink:focus-visible {
  outline: 2px solid #6f58ff;
  outline-offset: 2px;
}

/* ---------------------------------------------------------------
   Header motion
   The bar condenses once the page is scrolling: mobile.js sets
   data-scrolled on sticky headers and the geometry lives here, so
   reduced-motion can veto the transition and a page with no script
   simply keeps the resting height. The panel is a child of the
   header too, which is why the row selector must exclude it.
   --------------------------------------------------------------- */

header > div:not(.tdf-navpanel) {
  transition: height 0.34s cubic-bezier(0.23, 1, 0.32, 1);
}

header[data-scrolled] > div:not(.tdf-navpanel) {
  height: 48px !important;
}

/* ---------------------------------------------------------------
   Primary navigation
   The cells are inline-styled by the build; everything here is
   additive — position for the pseudo-elements, opacity for the
   hover dim, and the sliding hairline mobile.js drives. None of it
   needs to outrank an inline property.
   --------------------------------------------------------------- */

header nav[aria-label='Primary'] {
  position: relative;
}

header nav[aria-label='Primary'] a {
  position: relative;
  transition: opacity 0.22s;
}

/* The row reads as one instrument: the cell under the pointer holds full
   strength and the others step back, which is a colour-blind-safe emphasis
   that works over the dark header and the light ones alike. */
header nav[aria-label='Primary']:hover a:not(:hover) {
  /* 0.62, not lower: the dimmed labels still have to clear 4.5:1 on both the
     white bars (≈4.6:1) and the dark one (≈5.9:1). The step back reads fine;
     an AA failure that only exists while the pointer rests is still one. */
  opacity: 0.62;
}

/* The sliding hairline. mobile.js sets its offset and width; at rest it
   underlines the current page, on hover it follows the pointer. */
.tdf-nav-ind {
  position: absolute;
  left: 0;
  bottom: 0;
  width: 0;
  height: 2px;
  background: currentColor;
  opacity: 0;
  pointer-events: none;
  transition:
    transform 0.34s cubic-bezier(0.23, 1, 0.32, 1),
    width 0.34s cubic-bezier(0.23, 1, 0.32, 1),
    opacity 0.2s;
}

/* With JavaScript off the hairline never appears, so the current page keeps a
   static underline. html.tdf-js is set by the script that animates the live
   one; the two can never show together. */
html:not(.tdf-js) header nav[aria-label='Primary'] a[aria-current='page']::after {
  content: '';
  position: absolute;
  left: clamp(12px, 1.6vw, 20px);
  right: clamp(12px, 1.6vw, 20px);
  bottom: 0;
  height: 2px;
  background: currentColor;
}

/* The landing page's INDEX control, restored to the all-widths role v7 gave
   it: dropdown on desktop, full-screen index on a phone. The base tier hides
   generated menu buttons above the breakpoint; this one earns its place in
   the row. */
.tdf-navbtn--all {
  /* v7's row lays out brand | INDEX; the rebuilt nav joins with margin-left
     auto and would leave INDEX floating mid-bar. Flex order puts it at the far
     edge, where it sits on the phone. */
  order: 10;
  display: flex !important;
  align-items: center;
  gap: 10px;
  height: 100%;
  min-height: 44px;
  padding: 0 clamp(14px, 1.8vw, 22px);
  border: 0;
  border-left: 1px solid rgba(241, 240, 234, 0.22);
  background: transparent;
  color: inherit;
  font-family: 'IBM Plex Mono', ui-monospace, monospace;
  font-size: 10.5px;
  font-weight: 600;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  cursor: pointer;
}

/* The chapter readout inside it. Empty until mobile.js starts filling it, so
   nothing is promised that script has to keep. */
.tdf-nav-read {
  font-family: 'IBM Plex Mono', ui-monospace, monospace;
  font-size: 9.5px;
  letter-spacing: 0.14em;
  opacity: 0.55;
  font-variant-numeric: tabular-nums;
  min-width: 5ch;
  text-align: left;
}

.tdf-nav-read:empty {
  display: none;
}

/* While the sheet is open the control says CLOSE; a chapter gauge inside a
   close button, counting 00-06 above a sheet numbered 01-04, reads as two
   instruments fighting. The gauge belongs to the page, so it leaves with it. */
.tdf-navbtn[aria-expanded='true'] .tdf-nav-read {
  display: none;
}

/* The landing page ships a skip link parked at left:-9999px with no rule that
   ever brings it back, so keyboard users hit an invisible tab stop. On focus
   it takes the top-left corner, plated by its own inline styles. */
.scp0:focus,
.scp0:focus-visible {
  left: 0 !important;
  top: 0 !important;
}

/* ---------------------------------------------------------------
   Scroll reveal
   The build tags headings and the discipline plates data-reveal;
   mobile.js marks what is already on screen, sets html.tdf-reveal,
   and reveals the rest as they enter. Without that class — no
   script, stale script, script error — nothing here applies and
   the page is simply visible.
   --------------------------------------------------------------- */

html.tdf-reveal [data-reveal] {
  opacity: 0 !important;
  transform: translateY(18px) !important;
}

html.tdf-reveal [data-reveal].tdf-in {
  opacity: 1 !important;
  transform: translateY(0) !important;
  transition:
    opacity 0.65s cubic-bezier(0.23, 1, 0.32, 1),
    transform 0.65s cubic-bezier(0.23, 1, 0.32, 1);
}

@media (prefers-reduced-motion: reduce) {
  html.tdf-reveal [data-reveal] {
    opacity: 1 !important;
    transform: none !important;
    transition: none !important;
  }

  header > div:not(.tdf-navpanel),
  header nav[aria-label='Primary'] a,
  .tdf-nav-ind,
  header[data-tdf-hero-header] {
    transition: none !important;
  }
}

/* Print has no scroll, so the observer that reveals never fires; without this
   every heading and discipline tile prints at opacity 0. */
@media print {
  html.tdf-reveal [data-reveal] {
    opacity: 1 !important;
    transform: none !important;
  }
}

/* ---------------------------------------------------------------
   Tier-1 polish — build-tagged in step 2c-6; the review evidence
   lives with the tags. Everything here answers a named finding.
   --------------------------------------------------------------- */

/* The scrolled bar goes fully opaque. At rgba(...,0.92) whatever scrolls
   beneath ghosts through the nav cells as a grey wash — the most-seen
   component on the site looked smudged mid-scroll on every page. */
header[data-scrolled]:not([data-tdf-hero-header]) {
  background: #fff !important;
}

header[data-tdf-hero-header][data-scrolled] {
  background: rgb(10, 10, 10) !important;
}

/* The chapter rail is fixed at z-index 60 and used to draw its hairline
   straight through the footer's black field and wordmark. */
footer {
  position: relative;
  z-index: 61;
}

/* The closing destination. Interior pages ended on a disclaimer line and a
   void; this hands the reader the next page in the nav's own order, plus the
   address, in the site's plate grammar. */
.tdf-next {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  justify-content: space-between;
  gap: 22px 40px;
  padding: clamp(52px, 8vw, 104px) clamp(22px, 7.5vw, 110px);
  border-top: 1px solid rgba(10, 10, 10, 0.18);
  background: #fff;
}

.tdf-next-link {
  text-decoration: none;
  color: #0a0a0a;
}

.tdf-next-eyebrow {
  display: block;
  margin-bottom: 14px;
  font-family: 'IBM Plex Mono', ui-monospace, monospace;
  font-size: 10.5px;
  font-weight: 600;
  letter-spacing: 0.18em;
  color: #5c5a54;
}

.tdf-next-title {
  display: block;
  font-family: Archivo, sans-serif;
  font-weight: 600;
  font-size: clamp(38px, 6vw, 84px);
  line-height: 0.95;
  letter-spacing: -0.03em;
  text-transform: uppercase;
}

.tdf-next-arr {
  display: inline-block;
  transition: transform 0.28s cubic-bezier(0.23, 1, 0.32, 1);
}

.tdf-next-link:hover .tdf-next-title {
  color: #6f58ff;
}

.tdf-next-link:hover .tdf-next-arr {
  transform: translateX(0.18em);
}

.tdf-next-link:focus-visible {
  outline: 2px solid #6f58ff;
  outline-offset: 4px;
}

.tdf-next-mail {
  display: inline-flex;
  align-items: center;
  min-height: 44px;
  font-family: 'IBM Plex Mono', ui-monospace, monospace;
  font-size: 12px;
  font-weight: 600;
  letter-spacing: 0.06em;
  color: #0a0a0a;
  text-decoration: underline;
  text-underline-offset: 3px;
  word-break: break-all;
}

.tdf-next-mail:hover {
  color: #6f58ff;
}

/* The home footer's chapter index is a 6-column grid holding 7 cells: six in
   the first row and an orphan beside a dead tract. Seven across closes it. */
@media (min-width: 1200px) {
  .tdf-foot-index {
    grid-template-columns: repeat(7, minmax(0, 1fr)) !important;
  }
}

/* The home footer wordmark wrapped to two ragged lines stopping at 60% width;
   the interior pages fit the measure on one line. One treatment, sitewide. */
@media (min-width: 901px) {
  .tdf-foot-mark {
    font-size: clamp(34px, 8.6vw, 138px) !important;
    line-height: 0.95 !important;
    letter-spacing: -0.05em !important;
  }
}

/* Chapter-close statements. Three of the four bands move from 44px mono —
   a size the system reserves for Archivo — to the display face; the single
   ivory inversion keeps the mono voice. Dark bands also gain hairline edges:
   #000 on the #080808 canvas had no perceivable boundary. */
.tdf-stmt--archivo,
.tdf-stmt--archivo * {
  font-family: Archivo, sans-serif !important;
  font-weight: 600 !important;
  letter-spacing: -0.02em !important;
}

.tdf-stmt-band--dark {
  border-top: 1px solid rgba(241, 240, 234, 0.14) !important;
  border-bottom: 1px solid rgba(241, 240, 234, 0.14) !important;
}

/* Marquee strips: tokens dissolve at the frame edge instead of slicing. */
.tdf-ticker {
  -webkit-mask-image: linear-gradient(90deg, transparent, #000 28px, #000 calc(100% - 28px), transparent);
  mask-image: linear-gradient(90deg, transparent, #000 28px, #000 calc(100% - 28px), transparent);
}

/* Pan frames: the cut right edge fades, so a clipped column reads as a
   surface that continues rather than markup that broke; cells stop wrapping
   so hidden columns cannot distort the ruled row rhythm. */
.tdf-panx[data-scrollable] {
  -webkit-mask-image: linear-gradient(90deg, #000 calc(100% - 36px), transparent);
  mask-image: linear-gradient(90deg, #000 calc(100% - 36px), transparent);
}

/* Without script the fade cannot know whether the frame scrolls; below the
   desktop tier the wide frames always do, so the fade holds there. */
@media (max-width: 1239px) {
  html:not(.tdf-js) .tdf-panx {
    -webkit-mask-image: linear-gradient(90deg, #000 calc(100% - 36px), transparent);
    mask-image: linear-gradient(90deg, #000 calc(100% - 36px), transparent);
  }
}

.tdf-panx [style*='grid-template-columns'] > * {
  white-space: nowrap;
}

.tdf-swipe {
  margin: 0 0 8px;
}

/* Side rails hold their perch beside long columns instead of leaving a
   full-height void under a short card. */
@media (min-width: 1240px) {
  .tdf-rail {
    position: sticky !important;
    top: 108px;
    align-self: start;
  }
}

/* The hero contents panel is taller than the room a 900px viewport leaves
   it, so it trailed across the chapter rule into section 01. Tighter rows
   under a short viewport, and the chapter plates clip their own overflow so
   nothing crosses a section boundary again. */
@media (min-width: 1240px) and (max-height: 999px) {
  /* The panel's own wrapper carries 116px of hero foot padding; under a
     short viewport that is the difference between the sixth row clearing
     the fold and the panel arriving beheaded. */
  #ch0 div:has(> nav[aria-label='Contents']) {
    padding-bottom: 56px !important;
    row-gap: 10px !important;
  }

  /* The rest of the shave comes from above the panel: the eyebrow row's
     clamp(84px…) crown and a notch off the headline. Together the sixth
     contents row clears a 900px fold with room to spare. */
  #ch0 [style*='padding: clamp(84px'] {
    padding-top: 42px !important;
  }

  /* A 100svh hero below a 61px sticky bar ends 61px past the fold by
     construction — the panel is pinned to the hero's foot, so no amount of
     padding shaved above it moves it. The hero answers for the bar. */
  #ch0 [style*='min-height: 100svh'] {
    min-height: calc(100svh - 61px) !important;
  }

  /* The headline block's 56px vertical pads are the slack that lets the
     shortened hero still hold everything. */
  #ch0 div:has(> div > h1) {
    padding-top: 22px !important;
    padding-bottom: 22px !important;
  }


  nav[aria-label='Contents'] a {
    padding-top: 6px !important;
    padding-bottom: 6px !important;
    min-height: 0 !important;
  }

  nav[aria-label='Contents'] p {
    padding-top: 8px !important;
    padding-bottom: 6px !important;
  }
}

main > section[id^='ch'] {
  /* clip, not hidden: hidden would make each chapter a scroll container and
     silently kill the sticky side rails inside them. */
  overflow: clip;
}

/* The interlude held a seven-word line on a full desktop viewport; ~70vh is
   still unmistakably a pause without half a screen of empty vignette. */
@media (min-width: 901px) {
  .tdf-panel-tall {
    min-height: 72vh !important;
  }
}

/* Cross-links drew their underline as border-bottom at the foot of the 44px
   tap box, 20-40px below the words. The rule returns to the baseline; the
   target keeps its height. */
@media (pointer: coarse) {
  main a.tdf-tap[style*='border-bottom'] {
    border-bottom: 0 !important;
    text-decoration: underline !important;
    text-underline-offset: 5px;
    text-decoration-thickness: 1px;
  }
}

/* Becalmed controls: former toggle rows whose runtime is stripped at build.
   The pressed state's lavender fill was the site's only chroma; active rows
   fold back into the ink system as a filled row with an inset keyline. */
.tdf-calm--on {
  background: rgba(10, 10, 10, 0.06) !important;
  box-shadow: inset 2px 0 0 #0a0a0a !important;
  border-color: rgba(10, 10, 10, 0.3) !important;
}

/* Platform spec columns: each row rules off across the column and the set
   spreads to meet the panel beside it, so the space between rows is ruled
   field, not void. */
.tdf-spec {
  padding-bottom: 16px;
  border-bottom: 1px solid rgba(10, 10, 10, 0.14);
}

@media (min-width: 1024px) {
  .tdf-spec-col {
    justify-content: space-between;
  }
}

/* The inquiry card holds its perch beside the list it belongs to. */
@media (min-width: 1240px) {
  .tdf-stick {
    position: sticky !important;
    top: 108px;
  }
}

/* Pan rails on desktop: snap to card starts, and tighten the roadmap's
   stage cards so four fit with a clear peek of the fifth — an affordance
   stronger than any hint line. */
.tdf-panx {
  scroll-snap-type: x proximity;
}

.tdf-panx > * {
  scroll-snap-align: start;
}

@media (min-width: 1280px) {
  .tdf-panx > div[style*='width: 352px'] {
    /* 292, not a fit: four full cards and a ~65px peek of the fifth — the
       peek is the scroll affordance no hint line can match. */
    width: 292px !important;
  }
}

/* ---------------------------------------------------------------
   Material ground — the visualization pass's foundation.
   One film of milled grain over the whole page, softbox falloff on
   the dark chapters, and edge light on the inversion slabs: the
   flat vector surfaces gain the tooth of one continuous material.
   --------------------------------------------------------------- */

body::after {
  content: '';
  position: fixed;
  inset: 0;
  z-index: 82;
  pointer-events: none;
  opacity: 0.05;
  mix-blend-mode: overlay;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='160' height='160'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.9' numOctaves='2' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='160' height='160' filter='url(%23n)'/%3E%3C/svg%3E");
}

/* The dark chapters gain the studio's light logic: a soft overhead falloff
   so the ground reads lit, not filled. Under the content, over the canvas. */
#ch0::before,
#ch2::before,
#ch4::before,
#ch5::before {
  content: '';
  position: absolute;
  inset: 0;
  z-index: 0;
  pointer-events: none;
  background: radial-gradient(120% 90% at 50% 0%, rgba(241, 240, 234, 0.045), transparent 62%);
}

/* The inversion slabs read as machined material: a 1px arris catching the
   overhead light, and the same falloff across the face. */
.tdf-stmt-band--dark {
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.07) !important;
  background-image: radial-gradient(140% 120% at 50% 0%, rgba(241, 240, 234, 0.05), transparent 70%) !important;
}

/* ---------------------------------------------------------------
   Bracket-tick hover grammar
   Interactive plates answer the pointer with corner registration
   marks — the language the chapter corners already speak. Applied
   to build-tagged button-styled links.
   --------------------------------------------------------------- */

a.tdf-cta {
  position: relative;
}

a.tdf-cta::before,
a.tdf-cta::after {
  content: '';
  position: absolute;
  width: 8px;
  height: 8px;
  opacity: 0;
  transition: opacity 0.18s, transform 0.24s cubic-bezier(0.23, 1, 0.32, 1);
  pointer-events: none;
}

a.tdf-cta::before {
  top: -5px;
  left: -5px;
  border-top: 1px solid currentColor;
  border-left: 1px solid currentColor;
  transform: translate(3px, 3px);
}

a.tdf-cta::after {
  bottom: -5px;
  right: -5px;
  border-bottom: 1px solid currentColor;
  border-right: 1px solid currentColor;
  transform: translate(-3px, -3px);
}

a.tdf-cta:hover::before,
a.tdf-cta:hover::after,
a.tdf-cta:focus-visible::before,
a.tdf-cta:focus-visible::after {
  opacity: 1;
  transform: translate(0, 0);
}

@media (prefers-reduced-motion: reduce) {
  a.tdf-cta::before,
  a.tdf-cta::after {
    transition: none;
  }
}

/* ---------------------------------------------------------------
   The facility instrument
   Drafting-convention hatches by zone class, the wired layer
   board, the zone interrogator, the crosshair, the title block,
   and the print-style preview. All keyed on build-stamped facts.
   --------------------------------------------------------------- */

/* Section-hatch standard: controlled-access zones carry a fine 45-degree
   hatch, reserves a cross-hatch, people zones a sparse stipple. The legend
   chips already differentiate line style; the plate now agrees. */
.tdf-plan [data-sec] {
  background-image: repeating-linear-gradient(45deg, rgba(10, 10, 10, 0.07) 0 1px, transparent 1px 7px) !important;
}

.tdf-plan [data-res] {
  background-image:
    repeating-linear-gradient(45deg, rgba(10, 10, 10, 0.05) 0 1px, transparent 1px 9px),
    repeating-linear-gradient(-45deg, rgba(10, 10, 10, 0.05) 0 1px, transparent 1px 9px) !important;
}

.tdf-plan [data-cls='people']:not([data-sec]) {
  background-image: radial-gradient(rgba(10, 10, 10, 0.14) 0.8px, transparent 0.8px) !important;
  background-size: 9px 9px !important;
}

/* Layer board: a switched-off layer dims its members to ghost weight. */
.tdf-plan[data-off~='l1'] [data-ref][data-cls='program'] { opacity: 0.14 !important; }
.tdf-plan[data-off~='l1'] [data-ref][data-cls='people'] { opacity: 0.14 !important; }
.tdf-plan[data-off~='l2'] [data-ref][data-sec] { opacity: 0.14 !important; }
.tdf-plan[data-off~='l3'] [data-ref][data-flow] { opacity: 0.14 !important; }
.tdf-plan[data-off~='l4'] [data-ref][data-res] { opacity: 0.14 !important; }
.tdf-plan[data-off~='l5'] [data-bound] { opacity: 0.12 !important; }

.tdf-plan [data-ref] {
  transition: opacity 0.24s, box-shadow 0.18s;
}

/* Live rows: the board's switches and panel actions. */
button.tdf-live {
  display: flex;
  align-items: center;
  gap: 10px;
  width: 100%;
  min-height: 44px;
  border: 0;
  border-bottom: 1px solid rgba(10, 10, 10, 0.1);
  background: transparent;
  font-family: 'IBM Plex Mono', ui-monospace, monospace;
  font-size: 9.5px;
  letter-spacing: 0.14em;
  color: rgb(90, 90, 90);
  padding: 13px 16px;
  text-align: left;
  cursor: pointer;
}

button.tdf-live {
  /* The becalmed rows' inline style rides along in the attribute copy —
     including the old pressed-state lavender. The live board speaks ink. */
  background: transparent !important;
}

button.tdf-live[aria-pressed='true'] {
  background: rgba(10, 10, 10, 0.06) !important;
  box-shadow: inset 2px 0 0 #0a0a0a !important;
  color: #0a0a0a !important;
}

button.tdf-live[aria-pressed='true'] > span {
  background: #0a0a0a !important;
}

button.tdf-live:focus-visible {
  outline: 2px solid #6f58ff;
  outline-offset: -2px;
}

button.tdf-panel-act {
  justify-content: center;
  text-align: center;
  color: #0a0a0a;
  font-weight: 600;
}

/* The interrogator: the selected zone registers like a located part. */
.tdf-plan [data-ref][data-on] {
  border: 2px solid #0a0a0a !important;
  box-shadow: 0 0 0 4px rgba(10, 10, 10, 0.08) !important;
  z-index: 3 !important;
}

.tdf-keep-cols[data-on] {
  background: rgba(10, 10, 10, 0.05) !important;
  box-shadow: inset 2px 0 0 #0a0a0a;
}

.tdf-zone-detail .tdf-zd-ref {
  display: inline-block;
  padding: 1px 6px;
  margin-right: 6px;
  background: #0a0a0a;
  color: #f1f0ea;
}

/* Drafting crosshair: two hairlines and a grid readout that follow the
   pointer over the plan. Desktop, fine pointers only. */
.tdf-crosshair {
  position: absolute;
  inset: 0;
  pointer-events: none;
  opacity: 0;
  transition: opacity 0.2s;
  z-index: 4;
}

.tdf-crosshair[data-show] {
  opacity: 1;
}

.tdf-crosshair .tdf-cx-v,
.tdf-crosshair .tdf-cx-h {
  position: absolute;
  background: rgba(10, 10, 10, 0.35);
}

.tdf-crosshair .tdf-cx-v {
  top: 0;
  bottom: 0;
  width: 1px;
  left: var(--cx, -9px);
}

.tdf-crosshair .tdf-cx-h {
  left: 0;
  right: 0;
  height: 1px;
  top: var(--cy, -9px);
}

.tdf-crosshair .tdf-cx-cell {
  position: absolute;
  left: 12px;
  bottom: 10px;
  font-family: 'IBM Plex Mono', ui-monospace, monospace;
  font-size: 8.5px;
  letter-spacing: 0.14em;
  color: rgb(90, 90, 90);
  background: rgba(255, 255, 255, 0.85);
  padding: 3px 7px;
}

/* The title block, where a drawing files its identity. */
.tdf-titleblock {
  position: absolute;
  right: 0;
  bottom: 0;
  display: flex;
  flex-direction: column;
  border-top: 1px solid rgba(10, 10, 10, 0.45);
  border-left: 1px solid rgba(10, 10, 10, 0.45);
  background: #fff;
  z-index: 2;
}

.tdf-titleblock span {
  padding: 5px 12px;
  border-bottom: 1px solid rgba(10, 10, 10, 0.18);
  font-family: 'IBM Plex Mono', ui-monospace, monospace;
  font-size: 8.5px;
  letter-spacing: 0.15em;
  color: #3a3a3a;
}

.tdf-titleblock span:first-child {
  font-weight: 600;
  color: #0a0a0a;
}

.tdf-titleblock span:last-child {
  border-bottom: 0;
}

/* Print style: the plate stripped to line work — fills drop, every stroke
   goes full ink. A live preview of the drawing as a plate. */
.tdf-plan[data-print] {
  background-image: none !important;
}

.tdf-plan[data-print] [data-ref] {
  background-color: transparent !important;
  border-color: #0a0a0a !important;
  box-shadow: none !important;
}

.tdf-plan[data-print] [data-ref][data-on] {
  border-width: 2px !important;
}

@media (prefers-reduced-motion: reduce) {
  .tdf-plan [data-ref],
  .tdf-crosshair {
    transition: none !important;
  }
}

/* Gate decision glyphs: proceed, pivot, stop as schematic symbols. */
.tdf-gate-glyphs {
  display: inline-flex;
  gap: 7px;
  margin: 0 10px 4px 0;
  color: #5c5a54;
  vertical-align: middle;
}

/* The discipline tiles' inline figures, and their plotter pass. */
.tdf-ftile > svg.tdf-fdraw {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
}

/* The plotter IS the tile's reveal, so the generic fade-and-rise yields. */
html.tdf-reveal .tdf-ftile[data-reveal] {
  opacity: 1 !important;
  transform: none !important;
  transition: none !important;
}

html.tdf-reveal .tdf-ftile[data-reveal] .fd1 {
  stroke-dasharray: 1;
  stroke-dashoffset: 1;
}

html.tdf-reveal .tdf-ftile[data-reveal] .fd2 {
  visibility: hidden;
}

html.tdf-reveal .tdf-ftile[data-reveal].tdf-in .fd1 {
  stroke-dashoffset: 0;
  transition: stroke-dashoffset 1.05s cubic-bezier(0.23, 1, 0.32, 1);
}

html.tdf-reveal .tdf-ftile[data-reveal].tdf-in .fd2 {
  visibility: visible;
  transition: visibility 0s 0.8s;
}

@media (prefers-reduced-motion: reduce), print {
  html.tdf-reveal .tdf-ftile[data-reveal] .fd1 {
    stroke-dasharray: none !important;
    stroke-dashoffset: 0 !important;
    transition: none !important;
  }

  html.tdf-reveal .tdf-ftile[data-reveal] .fd2 {
    visibility: visible !important;
  }
}

/* The light table's controls. */
.tdf-lt-row {
  display: flex;
  gap: 1px;
  margin: 0 0 8px;
}

.tdf-lt-btn {
  min-height: 44px;
  padding: 10px 16px;
  border: 1px solid rgba(10, 10, 10, 0.3);
  background: #fff;
  font-family: 'IBM Plex Mono', ui-monospace, monospace;
  font-size: 10px;
  font-weight: 600;
  letter-spacing: 0.14em;
  color: #0a0a0a;
  cursor: pointer;
}

.tdf-lt-btn:focus-visible {
  outline: 2px solid #6f58ff;
  outline-offset: -2px;
}
