/* ════════════════════════════════════════════════════════════
   TNAADO — /case-studies · THE IN-HOUSE SOFTWARE CONSOLE
   T-1150, 2026-08-14.

   WHY THIS REPLACES THE RULED LIST
   ────────────────────────────────
   Ethan: "rebuild the portfolio page to show the same technology
   page laptop showcase of internal tools giving them a better
   showcase then the current dry showcase".

   The previous #csInhouse rendered ten real operating systems as
   four-column table rows. The most impressive thing the firm owns
   was presented as a spreadsheet. cs-inhouse.css argued for that
   treatment on the grounds that five of eight rows carried no
   image — that premise is now dead: there are 28 real UI captures
   in assets/gallery/systems, and the systems with 3-6 of them can
   show a WORKFLOW MOVING THROUGH STATES (queue -> decision ->
   audit log), which is the actual argument for the software.

   WHY A DRAWN FRAME AND NOT THE LAPTOP PHOTOGRAPH
   ──────────────────────────────────────────────
   /technology registers its slideshow onto the measured screen of
   a real MacBook in bts-claude-code-desk-wide.jpg (a 4-point
   homography — see js/tech-laptop.js). The INTERACTION is the part
   worth copying; the photograph is not. Ethan has twice said the
   desk photo is overused, and T-1044 removed two of its three
   instances. A third page carrying it would be a regression.

   So the device here is DRAWN: thin bezel, one chrome bar, a real
   shadow. Three things follow that the photograph could not give:
     1. It reads as "the software", not "a stock photo of a laptop".
     2. It works at EVERY breakpoint. The homography deliberately
        refuses below 1180px because a misregistered overlay is
        worse than none; a drawn frame has no registration to lose.
     3. Every capture in assets/gallery/systems is exactly 8:5
        (1600x1000, and 1280x800 for the two plates), so the glass
        is authored at that ratio and NOTHING is ever cropped.

   READING HAPPENS ON LIGHT. The section is .t-lite — bone ground,
   ink type. The banned pattern here is a dark band holding one
   sentence and one photo; this is the opposite of that, and the
   two dark things on the page (the chrome bar and the index rail's
   active row) are punctuation, not ground.

   TOKENS ONLY. Colour, type and easing come from site.css and
   tpl-t1149.css. The only new values are the frame's own geometry.
   ════════════════════════════════════════════════════════════ */

.csc {
  position: relative;
  padding: clamp(3rem, 6vw, 5rem) 0;
}
.csc[hidden] { display: none; }

/* The whole showcase gets a real max-width — "images never run edge
   to edge" applies to the console as much as to a photograph. */
.csc__rail {
  max-width: 78rem;
  margin: 0 auto;
  padding: 0 clamp(1rem, 4vw, 2.5rem);
  position: relative;
  z-index: 1;
}

.csc__head { max-width: 60ch; }
.csc__head .sv-lede { margin-bottom: 0; }

/* ── LAYOUT ────────────────────────────────────────────────────
   Index left, console right. The index is the 8-second hit: nine
   real systems, named, all visible at once without interacting.
   The console is the depth behind it. */
.csc__body {
  display: grid;
  grid-template-columns: minmax(0, 17rem) minmax(0, 1fr);
  gap: clamp(1.6rem, 3vw, 2.8rem);
  align-items: start;
  margin-top: clamp(2rem, 4vw, 3rem);
}

/* ── THE INDEX ─────────────────────────────────────────────────
   A numbered list of the systems, not a pill row: the count is the
   point, and nine names stacked reads as an inventory. */
.csc__index {
  list-style: none;
  margin: 0;
  padding: 0;
  border-top: 1px solid rgba(10, 10, 10, .14);
}

.csc__tab {
  display: grid;
  grid-template-columns: auto minmax(0, 1fr);
  gap: 0 .8rem;
  width: 100%;
  text-align: left;
  padding: .78rem .9rem .78rem .7rem;
  background: none;
  border: 0;
  border-bottom: 1px solid rgba(10, 10, 10, .14);
  cursor: pointer;
  color: inherit;
  font: inherit;
  position: relative;
  /* transform is listed explicitly rather than `all` — the :active
     press must not drag the border-colour transition with it. */
  transition:
    background-color .18s cubic-bezier(0.23, 1, 0.32, 1),
    transform .12s cubic-bezier(0.23, 1, 0.32, 1);
}

/* The red spine marks the active system. It scales on the Y axis
   from the top so it draws downward rather than fading in — a
   position change, not an appearance. */
.csc__tab::before {
  content: "";
  position: absolute;
  left: 0; top: 0; bottom: 0;
  width: 2px;
  background: var(--red);
  transform: scaleY(0);
  transform-origin: 0 0;
  transition: transform .22s cubic-bezier(0.23, 1, 0.32, 1);
}
.csc__tab[aria-selected="true"]::before { transform: scaleY(1); }

@media (hover: hover) and (pointer: fine) {
  .csc__tab:hover { background-color: rgba(10, 10, 10, .035); }
}
.csc__tab:active { transform: scale(.99); }
.csc__tab:focus-visible {
  outline: 2px solid var(--red);
  outline-offset: -2px;
}

.csc__tabnum {
  font-family: var(--font-mono);
  font-size: .6rem;
  letter-spacing: .12em;
  color: #5c5c58;
  padding-top: .25rem;
  font-variant-numeric: tabular-nums;
}
.csc__tab[aria-selected="true"] .csc__tabnum { color: var(--red); }

.csc__tabname {
  font-family: var(--font-display);
  font-weight: 400;
  font-size: 1rem;
  line-height: 1.25;
  color: var(--ink);
}
.csc__tabsector {
  grid-column: 2;
  font-family: var(--font-mono);
  font-size: .55rem;
  letter-spacing: .18em;
  text-transform: uppercase;
  color: #5c5c58;
  margin-top: .22rem;
}
/* Shot count doubles as an affordance: "4 screens" says there is
   something to click through before anything has been clicked. */
.csc__tabcount {
  grid-column: 2;
  font-family: var(--font-mono);
  font-size: .55rem;
  letter-spacing: .1em;
  color: #5c5c58;
  margin-top: .2rem;
}

/* ── THE DEVICE ────────────────────────────────────────────────
   Bezel, chrome bar, glass, chin. Drawn, not photographed. */
.csc__device {
  position: relative;
  background: #e9e5dc;
  border: 1px solid rgba(10, 10, 10, .16);
  border-radius: 10px;
  padding: 6px;
  /* Two-stop shadow: a tight contact shadow so the device sits ON
     the bone rather than floating, and a wide soft one for depth.
     Both tinted with the page's ink rather than pure black. */
  box-shadow:
    0 1px 2px rgba(10, 10, 10, .10),
    0 18px 44px -16px rgba(10, 10, 10, .30);
}

/* The chrome bar. One slim strip — it is what sells "an application
   is running" rather than "an image has been pasted here", and it
   is the honest frame: these captures really were taken in a
   browser viewport. */
.csc__chrome {
  display: flex;
  align-items: center;
  gap: .6rem;
  height: 30px;
  padding: 0 .7rem;
  background: var(--ink);
  border-radius: 5px 5px 0 0;
}
.csc__dots {
  flex: 0 0 auto;
  display: flex;
  gap: 5px;
}
.csc__dots i {
  width: 8px; height: 8px;
  border-radius: 50%;
  background: rgba(245, 241, 232, .22);
}
.csc__chrometitle {
  font-family: var(--font-mono);
  font-size: .58rem;
  letter-spacing: .16em;
  text-transform: uppercase;
  color: rgba(245, 241, 232, .62);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

/* THE GLASS. 8:5, matching every capture exactly, so no shot is
   ever cropped and the box never has to guess a height. */
.csc__glass {
  position: relative;
  aspect-ratio: 8 / 5;
  overflow: hidden;
  background: #14141a;
  border-radius: 0 0 5px 5px;
}

/* The slide track. A transition (not keyframes) so hammering the
   next button RETARGETS the motion instead of restarting it — the
   same reason js/tech-laptop.js uses one. */
.csc__track {
  display: flex;
  list-style: none;
  margin: 0;
  padding: 0;
  height: 100%;
  transition: transform .26s cubic-bezier(0.16, 1, 0.3, 1);
  will-change: transform;
}
.csc__slide {
  flex: 0 0 100%;
  min-width: 0;
  height: 100%;
  position: relative;
}
/* THE 440px CAP, AND WHY IT IS OVERRIDDEN HERE.
   site.css (T-1135) caps every img under /gallery/systems/ at
   clamp(280px, 34vw, 440px). That rule is correct where it was
   written — a screenshot dropped straight into a page flow can
   otherwise render taller than the viewport and read as "half cut
   off". Here the image is already bounded by the glass, so the cap
   only shortened it: measured, the slide came out 838.8 x 524.3
   and the image inside it 838.8 x 440, leaving an 84px dead band
   of glass background under every screenshot.

   Overridden with two classes rather than one so it does not
   depend on this file being linked after site.css — several agents
   are editing this repo and the head order is not guaranteed. */
.csc__glass .csc__slide img {
  display: block;
  width: 100%;
  height: 100%;
  max-height: none;
  /* The box and the file are both 8:5, so cover and contain are
     identical here; cover guards a sub-pixel rounding seam. */
  object-fit: cover;
  object-position: center top;
}

/* System change fades the glass rather than sliding it — a slide
   would imply the new system is "next to" the old one, which the
   index says it is not. Opacity only; no scale, because the frame
   must never look like it is being applied to the page. */
.csc__glass { transition: opacity .2s cubic-bezier(0.23, 1, 0.32, 1); }
.csc--swapping .csc__glass { opacity: 0; }

/* ── THE CHIN: controls, in the device, under the glass ────────
   Deliberately NOT overlaid on the screenshot. An overlay bar
   occludes exactly the region several of these captures use (the
   marketplace withhold decision runs to the bottom edge), and a
   control that covers the evidence defeats the section. In the
   chin it still reads as part of the machine. */
.csc__chin {
  display: flex;
  align-items: center;
  gap: .9rem;
  padding: .6rem .2rem .1rem;
}

.csc__steps {
  display: flex;
  align-items: center;
  gap: .3rem;
  flex: 0 0 auto;
}
/* Numbered step buttons, not anonymous dots: with up to six screens
   a dot row stops being countable at a glance. */
.csc__step {
  width: 1.6rem;
  height: 1.6rem;
  display: grid;
  place-items: center;
  padding: 0;
  border: 1px solid rgba(10, 10, 10, .18);
  border-radius: 4px;
  background: transparent;
  cursor: pointer;
  font-family: var(--font-mono);
  font-size: .6rem;
  font-variant-numeric: tabular-nums;
  color: #5c5c58;
  transition:
    background-color .16s cubic-bezier(0.23, 1, 0.32, 1),
    border-color .16s cubic-bezier(0.23, 1, 0.32, 1),
    color .16s cubic-bezier(0.23, 1, 0.32, 1),
    transform .12s cubic-bezier(0.23, 1, 0.32, 1);
}
@media (hover: hover) and (pointer: fine) {
  .csc__step:hover { border-color: rgba(10, 10, 10, .4); color: var(--ink); }
}
.csc__step:active { transform: scale(.94); }
.csc__step[aria-current="true"] {
  background: var(--ink);
  border-color: var(--ink);
  color: var(--bone);
}
.csc__step:focus-visible { outline: 2px solid var(--red); outline-offset: 2px; }
/* One screen means no sequence to step through. */
.csc__steps[data-single="1"] { display: none; }

.csc__arrows {
  margin-left: auto;
  display: flex;
  gap: .3rem;
  flex: 0 0 auto;
}
.csc__arrow {
  width: 1.9rem;
  height: 1.6rem;
  display: grid;
  place-items: center;
  padding: 0;
  border: 1px solid rgba(10, 10, 10, .18);
  border-radius: 4px;
  background: transparent;
  cursor: pointer;
  color: var(--ink);
  font-size: .8rem;
  line-height: 1;
  transition:
    background-color .16s cubic-bezier(0.23, 1, 0.32, 1),
    border-color .16s cubic-bezier(0.23, 1, 0.32, 1),
    opacity .16s cubic-bezier(0.23, 1, 0.32, 1),
    transform .12s cubic-bezier(0.23, 1, 0.32, 1);
}
@media (hover: hover) and (pointer: fine) {
  .csc__arrow:hover:not([disabled]) { border-color: rgba(10, 10, 10, .4); background: rgba(10, 10, 10, .04); }
}
.csc__arrow:active:not([disabled]) { transform: scale(.94); }
.csc__arrow[disabled] { opacity: .3; cursor: default; }
.csc__arrow:focus-visible { outline: 2px solid var(--red); outline-offset: 2px; }
.csc__arrows[data-single="1"] { display: none; }

/* ── THE CAPTION ───────────────────────────────────────────────
   Under the device, on bone, at reading size. This is where the
   meaning of the current screen lives, and on a phone — where a
   1600px dashboard cannot be legible at 350px wide whatever the
   frame does — it is carrying the section on its own. It is
   therefore body copy, not a 10px mono label.

   min-height holds two lines so stepping through a sequence never
   reflows the page under the reader's cursor. */
.csc__caption {
  margin: 1rem 0 0;
  max-width: 62ch;
  font-size: .92rem;
  line-height: 1.6;
  color: #3d3d3a;
  min-height: 2.95em;
  transition: opacity .18s cubic-bezier(0.23, 1, 0.32, 1);
}
.csc--swapping .csc__caption { opacity: 0; }

.csc__stepmeta {
  display: block;
  font-family: var(--font-mono);
  font-size: .58rem;
  letter-spacing: .16em;
  text-transform: uppercase;
  color: var(--red);
  margin-bottom: .32rem;
}

/* ── THE SYSTEM PANEL — what this system IS ────────────────────
   Sits under the caption. Its copy is the portfolio_items row. */
.csc__about {
  margin-top: clamp(1.4rem, 2.5vw, 2rem);
  padding-top: clamp(1.1rem, 2vw, 1.5rem);
  border-top: 1px solid rgba(10, 10, 10, .14);
}
.csc__aboutname {
  font-family: var(--font-display);
  font-weight: 400;
  font-size: clamp(1.15rem, 1.8vw, 1.45rem);
  line-height: 1.2;
  color: var(--ink);
  margin: 0 0 .5rem;
}
.csc__aboutdesc {
  margin: 0;
  max-width: 66ch;
  font-size: .95rem;
  line-height: 1.65;
  color: #3d3d3a;
}
.csc__stack {
  margin: .8rem 0 0;
  font-family: var(--font-mono);
  font-size: .62rem;
  letter-spacing: .12em;
  text-transform: uppercase;
  color: #5c5c58;
}

/* ── DECORATIVE SHAPES: soft against sharp ─────────────────────
   Ethan asked for "more work like the bubble stuff but different
   patterns, crisscross, more sharp" — so the two marks here are
   deliberately opposed: a soft blob behind the device and a sharp
   crosshatch wedge behind the index. Both are behind content and
   neither ever sits under running text. */
.csc__mark {
  position: absolute;
  pointer-events: none;
  z-index: 0;
}
.csc__mark--soft {
  width: 26rem; height: 26rem;
  right: -7rem; top: 5rem;
  background: linear-gradient(150deg, rgba(200, 16, 46, .10), rgba(200, 16, 46, .02));
  border-radius: 63% 37% 55% 45% / 56% 50% 50% 44%;
}
/* GEOMETRY, NOT TASTE. The rail is centred at max-width 78rem (1248px),
   so the empty gutter beside it is (100vw - 1248) / 2 and the first text
   pixel is 40px further in again. At 1440 that puts running text at
   x=136, so a mark anchored at left:-4rem must be no wider than 12.5rem
   or it lands under the index — which is what the first build did, with
   the hatch showing through "TNAADO Labs Admin Backend".

   Below 1440 the gutter is too small for any of it to clear, so the mark
   is simply not drawn. The section still alternates soft against sharp
   at those widths: the device is a hard rectangle sitting against the
   blob. A decorative flourish is not worth putting texture under a
   system's name to keep. */
.csc__mark--sharp {
  width: 12rem; height: 16rem;
  left: -4rem; bottom: 2rem;
  background:
    repeating-linear-gradient(45deg,  rgba(10, 10, 10, .085) 0 1px, transparent 1px 9px),
    repeating-linear-gradient(-45deg, rgba(200, 16, 46, .14) 0 1px, transparent 1px 14px);
  clip-path: polygon(0 0, 100% 18%, 74% 100%, 0 78%);
}
@media (max-width: 1439px) { .csc__mark--sharp { display: none; } }
@media (max-width: 900px)  { .csc__mark { display: none; } }

/* ── BREAKPOINTS ───────────────────────────────────────────────
   The index goes horizontal before the device gets too narrow to
   read, not after. At 1000px a 17rem rail is still taking a fifth
   of the glass. */
@media (max-width: 1000px) {
  .csc__body { grid-template-columns: minmax(0, 1fr); }

  /* Horizontal scroller. The list keeps its semantics; only the
     axis changes. */
  .csc__index {
    display: flex;
    gap: .5rem;
    overflow-x: auto;
    border-top: 0;
    border-bottom: 1px solid rgba(10, 10, 10, .14);
    padding-bottom: .7rem;
    margin-bottom: .3rem;
    scrollbar-width: thin;
    -webkit-overflow-scrolling: touch;
    scroll-snap-type: x proximity;
  }
  .csc__index::-webkit-scrollbar { height: 3px; }
  .csc__index::-webkit-scrollbar-thumb { background: rgba(10, 10, 10, .2); }

  /* DISPLAY:FLEX, NOT THE GRID FROM ABOVE.
     The wide layout's `grid-template-columns: auto minmax(0, 1fr)` has a
     zero minimum on its content column. Inside a `flex: 0 0 auto` chip
     that resolves the whole track to 0, so every chip collapsed to its
     padding and all nine system names overlapped in one illegible stack
     — which is exactly what shipped to the first 390px capture. A column
     flex sizes to its content, so the chip is as wide as its longest
     line and no wider. */
  .csc__tab {
    flex: 0 0 auto;
    scroll-snap-align: start;
    display: flex;
    flex-direction: column;
    align-items: flex-start;
    gap: .15rem;
    border-bottom: 0;
    border: 1px solid rgba(10, 10, 10, .16);
    border-radius: 5px;
    padding: .55rem .85rem;
  }
  /* The spine becomes a top edge when the row becomes a chip. */
  .csc__tab::before {
    left: 0; right: 0; bottom: auto;
    width: auto; height: 2px;
    transform: scaleX(0);
    transform-origin: 0 0;
    transition: transform .22s cubic-bezier(0.23, 1, 0.32, 1);
  }
  .csc__tab[aria-selected="true"]::before { transform: scaleX(1); }
  .csc__tab[aria-selected="true"] { border-color: rgba(10, 10, 10, .34); }

  .csc__tabnum { display: none; }
  .csc__tabname { font-size: .88rem; white-space: nowrap; }
  .csc__tabsector { display: none; }
  .csc__tabcount { white-space: nowrap; margin-top: 0; }
}

@media (max-width: 560px) {
  .csc__chrome { height: 26px; }
  .csc__chrometitle { font-size: .52rem; letter-spacing: .12em; }
  .csc__device { padding: 4px; border-radius: 8px; }
  .csc__chin { gap: .5rem; flex-wrap: wrap; padding-bottom: .25rem; }
  /* Bigger, not smaller, on touch: these are the only controls in the
     component and a 24px target is not one. */
  .csc__step  { width: 2rem; height: 2rem; font-size: .66rem; }
  .csc__arrow { width: 2.4rem; height: 2rem; }
  .csc__steps { gap: .35rem; }
  /* The caption is doing most of the work at this width, so it gets
     the room rather than being shrunk with everything else. */
  .csc__caption { font-size: .9rem; min-height: 0; }
}

/* ── REDUCED MOTION ────────────────────────────────────────────
   Fully usable and readable with motion off. The track still moves
   between slides — it has to, that is the mechanism — but it does
   so instantly rather than travelling, and every decorative
   transition is dropped. Nothing here is animation-gated. */
@media (prefers-reduced-motion: reduce) {
  .csc__track,
  .csc__glass,
  .csc__caption,
  .csc__tab,
  .csc__tab::before,
  .csc__step,
  .csc__arrow { transition: none; }
  .csc__tab:active,
  .csc__step:active,
  .csc__arrow:active { transform: none; }
  .csc--swapping .csc__glass,
  .csc--swapping .csc__caption { opacity: 1; }
}

/* ── NO JS ─────────────────────────────────────────────────────
   Without script the section never unhides, exactly as the ruled
   list behaved: the markup is built from the Supabase fetch, so an
   empty shell is the only alternative and a headed contentless
   band is worse than nothing. */
