/* ════════════════════════════════════════════════════════════
   TNAADO — /case-studies · S.01 THE PORTFOLIO DISPLAYS
   T-1169, 2026-08-14.

   WHY THIS REPLACES THE CARD GRID
   ───────────────────────────────
   Ethan: "portfolio page should be loading big displays from db
   of projects".

   The grid it replaces rendered every project as a card in a
   3-up (then 2-up) track running on the page gutter. Measured on
   the deploy at 1440: the card was 467 wide and its picture 465.
   Every one of those pictures is a full-page website capture at
   1600x1000, so the client's site was being shown at 29% scale —
   a thumbnail. Headlines survived; nothing else did.

   Here one project is one display, the full width of the shared
   rail. At 1440 that is a 1136px window on a 1600px capture —
   71% — where the client's navigation, hero copy and section
   headings are all readable. Eleven of them make a long page;
   that is the correct trade. A portfolio's job is to make one
   piece of work look good, not to fit twelve on a screen.

   WHY A MOUNT AND NOT A DEVICE
   ────────────────────────────
   S.02 directly below is the in-house software console: a drawn
   laptop with a dark chrome bar, stepped through screen by
   screen. It is good and it stays. If S.01 also drew a device
   the two sections would argue.

   So the two sections are deliberately opposed:
     S.01 — the CLIENT'S finished work, presented as a framed
            exhibit. Paper mount, hairline, hard corners, a
            specimen plate at the head, a label underneath.
     S.02 — the FIRM'S own software, presented as a machine
            that runs. Rounded bezel, dark chrome, controls.

   Reading happens on light: everything here is ink on paper on
   bone. There is no dark band holding one sentence over a big
   picture anywhere in this file — that is the pattern Ethan
   banned outright and this section is its opposite.

   SEPARATION IS THE COMPLAINT BEING SOLVED
   ────────────────────────────────────────
   "all the screenshot placements aren't clearly separated and
   defined, lots of cluster." Three things carry it:
     1. Every display has a real edge — 1px rule, paper ground
        against the bone, two-stop shadow. It is an object.
     2. The gap between two displays is ~5x the gap between a
        display and its own label, so a label can never be read
        as belonging to the picture below it.
     3. Nothing is full bleed. The mount stops on the shared
        rail, both sides, at every width.

   ONE RAIL. Every heading and paragraph in this section starts
   on --rail-max / --rail-pad, the same line as the hero, the
   section head and the pills. The label sits UNDER its mount
   rather than inside it for exactly that reason: inset caption
   padding would have invented a second left edge.

   TOKENS ONLY — colour, type and easing from site.css.
   ════════════════════════════════════════════════════════════ */

/* ── THE COLUMN ────────────────────────────────────────────────
   The rail, restated rather than inherited: tnaado-rail.css puts
   .inner on it, and this stack is a sibling of .inner, not a
   child. Same two tokens, so they can never drift apart. */
.csd-stack {
  max-width: var(--rail-max, 1240px);
  margin-inline: auto;
  padding-inline: var(--rail-pad, clamp(1.15rem, 4vw, 2.5rem));
  box-sizing: border-box;
  display: flex;
  flex-direction: column;
  gap: clamp(3.25rem, 6.5vw, 5.5rem);
  margin-top: clamp(2rem, 4vw, 3rem);
}

/* The count line under the pills. Mono, on the rail, same
   register as S.02's "9 systems · 27 screens". */
.csd-count {
  margin: 1rem 0 0;
  font-family: var(--font-mono);
  font-size: .62rem;
  letter-spacing: .16em;
  text-transform: uppercase;
  --ink-true: #5c5c58;
}

/* ── ONE DISPLAY ───────────────────────────────────────────────
   The whole thing is the link. Hover moves the object, never the
   picture inside it: a 1136px screenshot on a scale transform
   reads as a stock-photo effect, not as software. */
.csd {
  position: relative;
  display: block;
  text-decoration: none;
  color: inherit;
  transition:
    transform .26s var(--ease-out-expo, cubic-bezier(0.16, 1, 0.3, 1)),
    opacity .34s var(--ease-out-expo, cubic-bezier(0.16, 1, 0.3, 1));
}

/* Entrance. Written here rather than reusing site.css's .cs-card
   state so the two can be reasoned about separately. The class is
   flipped by the same render pass that writes the markup, so a
   display can only be in this state if the script that clears it
   has already been scheduled. */
html.js-motion .csd { opacity: 0; transform: translateY(16px); }
html.js-motion .csd.is-in { opacity: 1; transform: none; }

.csd:focus-visible { outline: 2px solid var(--red); outline-offset: 4px; }

/* ── THE MOUNT ─────────────────────────────────────────────────
   Paper on bone. #fff against the bone #faf8f3 is a small step,
   so the edge is carried by the rule and the contact shadow, not
   by the fill alone. Hard corners: the console below is the
   rounded thing, and Ethan asked for "more sharp". */
.csd__mount {
  position: relative;
  z-index: 1;
  background: #fff;
  border: 1px solid rgba(10, 10, 10, .17);
  padding: clamp(.5rem, .85vw, .75rem);
  box-shadow:
    0 1px 2px rgba(10, 10, 10, .07),
    0 22px 48px -20px rgba(10, 10, 10, .28);
  transition:
    border-color .22s var(--ease-out-expo, cubic-bezier(0.16, 1, 0.3, 1)),
    box-shadow .26s var(--ease-out-expo, cubic-bezier(0.16, 1, 0.3, 1));
}

@media (hover: hover) and (pointer: fine) {
  .csd:hover { transform: translateY(-4px); }
  .csd:hover .csd__mount {
    border-color: rgba(200, 16, 46, .42);
    box-shadow:
      0 2px 4px rgba(10, 10, 10, .09),
      0 34px 64px -22px rgba(10, 10, 10, .34);
  }
}
.csd:active { transform: translateY(-1px); transition-duration: 110ms; }

/* ── THE PLATE ─────────────────────────────────────────────────
   A specimen strip at the head of the mount: the number, the
   classification, and the row's status. It is what makes the
   frame read as a mounted, catalogued thing rather than an image
   with a border. Deliberately NOT a browser chrome bar and
   deliberately not a URL — no hostname appears on this page. */
.csd__plate {
  display: flex;
  align-items: center;
  gap: .7rem;
  min-height: 2.05rem;
  padding: 0 .7rem;
  background: #f4f1ea;
  border-bottom: 1px solid rgba(10, 10, 10, .1);
  font-family: var(--font-mono);
  font-size: .58rem;
  letter-spacing: .18em;
  text-transform: uppercase;
  line-height: 1.4;
}
/* "num" in the class name is load-bearing. site.css's white-ground
   rule is `main > section:not()x7 :where(…span:not([class*="num"]))`
   at specificity 0,7,1 — a two-class selector here cannot beat it,
   so a red number would have been repainted ink. The rule excludes
   any class containing "num", which is the escape hatch the
   codebase already uses. */
.csd__num {
  flex: 0 0 auto;
  color: var(--red);
  font-variant-numeric: tabular-nums;
}
/* Everything else in the plate is repainted by that same 0,7,1
   rule, which sets `color: var(--ink-true, #0a0a0a)`. Rather than
   fight it, the token it reads is redefined per element — the
   "redefine tokens, not elements" rule this codebase learned the
   hard way on /pricing. No !important anywhere in this file. */
.csd__kind { flex: 0 0 auto; --ink-true: #262624; }
.csd__status {
  margin-left: auto;
  flex: 0 0 auto;
  text-align: right;
  --ink-true: #6a6a64;
}
.csd__plate .csd__dot {
  flex: 0 0 auto;
  width: 4px; height: 4px;
  border-radius: 50%;
  background: rgba(10, 10, 10, .26);
}

/* ── THE WINDOW ────────────────────────────────────────────────
   8:5 — the shape of every populated capture in the table today
   (1600x1000, confirmed by fetching each row's real source file;
   the two exceptions are 1600x900 and 1600x1666). Cover, anchored
   to the top, so a page taller than 8:5 loses its footer rather
   than being pillarboxed into a sliver. */
.csd__glass {
  position: relative;
  aspect-ratio: 8 / 5;
  overflow: hidden;
  background: #eeeae1;
}
/* TWO CLASSES, ON PURPOSE. site.css caps UI screenshots with
   `img[src*="/gallery/systems/"]` — specificity 0,1,1, the same as
   a one-class descendant rule — and more of those attribute caps
   have been added over time. Nothing here matches one today, but
   the override is authored at 0,2,1 so it does not depend on this
   file being linked after site.css. Several agents are editing
   this repo and head order is not guaranteed. */
.csd .csd__glass img {
  display: block;
  width: 100%;
  height: 100%;
  max-height: none;
  object-fit: cover;
  object-position: center top;
}

/* No capture on the row. A full-height 8:5 box holding one letter
   is a 710px hole in the page, so an unphotographed project gets a
   short typographic plate instead — still a defined object, still
   in the sequence, obviously a record rather than a broken image.
   It has to render: rows come from the CMS and one without media
   must still appear. */
.csd__glass--empty {
  aspect-ratio: 16 / 5;
  display: grid;
  align-content: center;
  padding: clamp(1.2rem, 3vw, 2.2rem);
  background:
    repeating-linear-gradient(45deg, rgba(10, 10, 10, .05) 0 1px, transparent 1px 10px),
    #f7f4ee;
}
/* Watermark, not a headline. The name is already set at full size in the
   label directly beneath the mount; at full contrast here the page showed
   it twice, stacked. */
.csd__glass--empty .csd__plateword {
  font-family: var(--font-display);
  font-weight: 400;
  font-size: clamp(1.7rem, 4.4vw, 3.2rem);
  line-height: 1.1;
  letter-spacing: -.01em;
  /* Measured 1.42:1 at #d5cfc0, which read as a rendering fault rather
     than a watermark. #bdb5a0 was meant to be 2.2:1 but composites to
     1.86:1 against the plate's actual ground (#f7f4ee under the 45deg
     hatch) — still inside "looks broken" territory. #8c8571 composites to
     3.31:1, which clears the WCAG bar for text this size (51px display,
     so 3:1) and reads as a deliberate watermark. Deliberately NOT taken
     to 4.5:1: the name is already set at full contrast in the label
     directly under the mount and a second full-strength headline is the
     problem the note above describes. */
  --ink-true: #8c8571;
}

/* ── THE LABEL ─────────────────────────────────────────────────
   Under the mount, on the rail, at reading size. Its gap to its
   own picture is roughly a fifth of the gap to the next display,
   which is what makes the pairing unambiguous without drawing a
   box around both. */
.csd__label {
  margin-top: clamp(.95rem, 1.6vw, 1.25rem);
  max-width: 68ch;
}
.csd__title {
  font-family: var(--font-display);
  font-weight: 400;
  font-size: clamp(1.45rem, 2.6vw, 2.05rem);
  line-height: 1.15;
  margin: 0;
}
.csd__meta {
  margin: .5rem 0 0;
  font-family: var(--font-mono);
  font-size: .6rem;
  letter-spacing: .16em;
  text-transform: uppercase;
  --ink-true: #6a6a64;
}
.csd__desc {
  margin: .75rem 0 0;
  max-width: 62ch;
  font-size: .97rem;
  line-height: 1.65;
  --ink-true: #3d3d3a;
}
.csd__cue {
  display: inline-flex;
  align-items: baseline;
  gap: .45rem;
  margin: 1rem 0 0;
  font-family: var(--font-mono);
  font-size: .62rem;
  letter-spacing: .14em;
  text-transform: uppercase;
  --ink-true: #a4132c;
}
.csd__cue i {
  font-style: normal;
  transition: transform .2s var(--ease-out-expo, cubic-bezier(0.16, 1, 0.3, 1));
}
@media (hover: hover) and (pointer: fine) {
  .csd:hover .csd__cue i { transform: translateX(5px); }
}

/* ── THE MARKS: soft against sharp, alternating ────────────────
   "different patterns, crisscross, more sharp" — so odd displays
   get a soft blob off the right shoulder and even ones a sharp
   crosshatch wedge off the left. Both sit BEHIND the mount, which
   is opaque, so the only part that shows is the part in the
   gutter, and no mark is ever under running text.

   GEOMETRY, NOT TASTE. The stack is centred at 1240 with a 40px
   inner pad, so at 1440 the mount runs x=140..1300 and the free
   gutter beyond it is 140px. A mark pushed 6rem (96px) out stops
   at 1396, inside the viewport. Below 1400 there is no gutter to
   put anything in, so nothing is drawn — the section still reads
   as soft against sharp because the hard mount is sitting on the
   soft page. A flourish is not worth a horizontal scrollbar. */
.csd__mark {
  position: absolute;
  z-index: 0;
  pointer-events: none;
  display: none;
}
@media (min-width: 1400px) {
  .csd__mark { display: block; }
  .csd:nth-child(odd) .csd__mark {
    width: 15rem; height: 15rem;
    right: -6rem; top: 12%;
    background: linear-gradient(150deg, rgba(200, 16, 46, .13), rgba(200, 16, 46, .02));
    border-radius: 63% 37% 55% 45% / 56% 50% 50% 44%;
  }
  .csd:nth-child(even) .csd__mark {
    width: 11rem; height: 17rem;
    left: -6rem; bottom: 16%;
    background:
      repeating-linear-gradient(45deg,  rgba(10, 10, 10, .09) 0 1px, transparent 1px 9px),
      repeating-linear-gradient(-45deg, rgba(200, 16, 46, .15) 0 1px, transparent 1px 14px);
    clip-path: polygon(0 0, 100% 16%, 76% 100%, 0 80%);
  }
  /* Every third one swaps the soft blob for a chamfered slab, so
     the alternation is a pattern rather than a two-beat loop. */
  .csd:nth-child(6n + 3) .csd__mark {
    width: 13rem; height: 13rem;
    right: -6rem; top: 8%;
    border-radius: 0;
    background: linear-gradient(200deg, rgba(10, 10, 10, .16), rgba(200, 16, 46, .05));
    clip-path: polygon(6% 0, 100% 0, 100% 94%, 94% 100%, 0 100%, 0 6%);
  }
}

/* ── THE FILTER PILLS ON A LIGHT GROUND ────────────────────────
   The pills work and keep working; they were just authored for
   the dark page this section used to be. site.css gives them
   `border: 1px solid rgba(245,241,232,.18)` — bone at 18% — which
   on a bone ground is an invisible edge, so the resting pills read
   as four loose words. Scoped to this section's list only. */
#csPills .filter-pill {
  border-color: rgba(10, 10, 10, .2);
  --ink-true: #5c5c58;
  color: #5c5c58;
  cursor: pointer;
}
#csPills .filter-pill:hover {
  border-color: rgba(10, 10, 10, .45);
  color: #0a0a0a;
}
/* Active: ink, not the red fill. site.css sets the active pill to
   `background: var(--red)` with `color: var(--on-black)`, and
   --on-black is flipped to ink inside a .t-lite section — near
   black type on #c8102e, measured 3.5:1. Ink on bone with bone
   type is 17:1 and matches the console's active step below. */
#csPills .filter-pill.is-active {
  background: #0a0a0a;
  border-color: #0a0a0a;
  color: #faf8f3;
}

/* ── THE SECTION ITSELF ────────────────────────────────────────
   Nothing may leave the viewport sideways. */
#cs-root { overflow-x: clip; }

/* NOT A CHANGE TO S.02 — a containment fix for a measured defect.
   .csc__mark--soft is `right: -7rem` against .csc, which is the
   full-width section, so the page's scrollWidth measured 1552 at a
   1440 viewport and 1136 at 1024: a 112px horizontal scrollbar on
   every width above mobile. Clipping the section keeps the mark's
   intended bleed and drops the scroll. The console's layout,
   spacing and behaviour are untouched. */
.csc { overflow-x: clip; }

/* AND THE 4px. The console's rail is its own — max-width 78rem with a
   1rem minimum gutter — while every other block on this page is on the
   shared rail from css/tnaado-rail.css: 1240px, 1.15rem minimum. Measured
   left edges on the deploy: S.02's heading at x=136 against x=140 for the
   hero, the S.01 head and every display, and 16 against 18 at 390. Four
   pixels is not a design choice, it is the exact class of drift behind
   "nothing is aligned nothing makes sense", and it is fixed by pointing
   the console at the same two tokens rather than by retuning anything.
   Geometry only — the console's layout, controls and behaviour are as
   T-1150 left them. Written with the section id so it does not depend on
   this file being linked after cs-console.css. */
#csConsole .csc__rail {
  max-width: var(--rail-max, 1240px);
  padding-inline: var(--rail-pad, clamp(1.15rem, 4vw, 2.5rem));
}

/* ── BREAKPOINTS ───────────────────────────────────────────────
   There is no column count to collapse — a display is already one
   per row at every width. Only the frame's own weight changes. */
@media (max-width: 700px) {
  .csd__mount { padding: 5px; }
  .csd__plate {
    min-height: 1.85rem;
    gap: .5rem;
    padding: 0 .5rem;
    font-size: .52rem;
    letter-spacing: .13em;
  }
  /* Three mono runs on one line at 390px is a wrap waiting to
     happen; the status is the one that can go. */
  .csd__status { display: none; }
  .csd__glass--empty { aspect-ratio: 16 / 7; }
  .csd__label { margin-top: .8rem; }
  .csd__desc { font-size: .93rem; }
}

/* ── REDUCED MOTION ────────────────────────────────────────────
   The entrance state is the only thing here that could strand
   content, so it is cleared outright rather than merely sped up. */
@media (prefers-reduced-motion: reduce) {
  html.js-motion .csd,
  .csd { opacity: 1; transform: none; transition: none; }
  .csd__mount, .csd__cue i { transition: none; }
  .csd:hover { transform: none; }
}
