/* =============================================================================
   Work grid cards — stack strip, direct actions, and the in-place "Why" panel.

   Written to the grid's existing vocabulary, not a generic card idiom:

     - squared corners; the archive has no border-radius anywhere
     - monospace uppercase micro-type with letter-spacing, like .kicker,
       .status, .card-heading > span and .filters button
     - 1px hairline rules in ink
     - wine as a flat fill for the primary state, exactly as
       .filters button[aria-pressed="true"] already does

   The one deliberate deviation: the archive sets these labels at 9px, which
   RESPONSIVE_PRINCIPLES P1.2 identifies as a defect. Controls here sit at 12px,
   the documented floor.

   The card is no longer one big <a>: an anchor cannot legally contain a button,
   and doing so breaks keyboard navigation. The media and the title are the
   links; the actions sit beside them as real controls.
   ========================================================================== */

/* --- Stack strip --------------------------------------------------------- */

/* A grid, not a wrapping flex row. auto-fit collapses the empty tracks, so a
   three-tool project still spreads across the full width, while an eleven-tool
   one wraps into even columns and its last row packs left instead of leaving
   one mark floating in the centre. */
.tech-stack {
  /* Flex, not grid. A grid's justify-content centres the whole track block,
     so a trailing part-row still hangs off the left edge; flex applies
     justify-content per wrapped line, which is the only way to centre just the
     last row. Fixed 96px basis keeps every mark on the same rhythm — the
     leftover collects as an even margin at both ends instead of stretching
     the columns. */
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: 16px 12px;
  margin: 2px 0 0;
  padding: 0;
  list-style: none;
}

/* Sits on its own line so the technologies below it can never be split
   awkwardly by a wrap (the label alone costs ~110px of the row). */
/* Each technology is a small vertical unit: mark above, name beneath. That
   lets the mark grow without the row running out of horizontal room.

   `flex: 1 1 72px` gives every technology an identical basis and an equal
   share of the leftover width, so the marks sit on an even rhythm regardless
   of how much wider "TypeScript" is than "React". Below the 72px basis the
   row wraps rather than crushing a name. */
.tech-stack li {
  flex: 0 0 96px;
  min-width: 0;
  display: flex;
  flex-direction: column;
  align-items: center;           /* P3.5 — alignment re-declared for column */
  gap: 7px;
  overflow-wrap: anywhere;
  color: var(--ink);
  font: 12px/1 ui-monospace, SFMono-Regular, Menlo, monospace;
  letter-spacing: .02em;
  text-align: center;
}

.tech-icon {
  width: 30px;
  height: 30px;
  flex: 0 0 auto;
  /* Marks are area-normalised, so a wide wordmark renders wider than the box
     and needs to bleed rather than be clipped. The grid cell is ~98px, so a
     few pixels either side costs nothing. */
  overflow: visible;
  fill: var(--tech-color, currentColor);
}

/* Tools with no official mark carry the neutral glyph instead. Dimmed a
   little so the row reads brand-marks-first, and so the placeholder never
   competes with a real logo beside it. */
.tech-stack li.is-generic { opacity: .72; }

/* Marks are official multi-colour logos from devicon, whose paths carry their
   own fills — a path's own fill attribute beats the `fill` inherited from the
   <svg>, so those render in brand colour untouched.

   The four below have no devicon mark and come from simple-icons instead;
   those are single-path with no fill, so they inherit this brand hex. */
.tech-stack {
  --t-webgl: #990000;
  --t-drizzle: #C5F74F;
  --t-gemini: #8E75B2;
  --t-svg: #FFB13B;
  --t-mediapipe: #0097A7;
  --t-vimeo: #1AB7EA;
  --t-lottie: #00DDB3;
}

/* --- Card actions -------------------------------------------------------- */

.card-actions {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
  /* The fallback, and the only alignment available without subgrid: pin the
     buttons to the bottom of the card. It stays in force in a no-subgrid
     engine, with JS off, and whenever `.cards-aligned` is not applied — and is
     reset to 0 inside the subgrid block below, where the shared row does the
     job properly and an auto margin would bottom-align a short action row. */
  margin-top: auto;
  padding-top: 20px;
}

.card-btn {
  display: inline-flex;
  flex: 0 0 auto;                /* never shrink below the label's width */
  align-items: center;
  justify-content: center;
  gap: 8px;
  min-height: 44px;              /* P2.1 */
  padding: 0 15px;
  border: 1px solid var(--ink);
  background: transparent;
  color: inherit;
  cursor: pointer;
  font: 12px/1 ui-monospace, SFMono-Regular, Menlo, monospace;
  letter-spacing: .1em;
  text-decoration: none;
  text-transform: uppercase;
  white-space: nowrap;
  transition: background .18s ease, color .18s ease, border-color .18s ease;
}

.card-btn--primary {
  background: var(--wine);
  border-color: var(--wine);
  color: var(--paper);
}

.card-btn:hover,
.card-btn:focus-visible {
  background: var(--ink);
  border-color: var(--ink);
  color: var(--paper);
}

/* An explicit ring in the site's wine, so keyboard focus never falls back to
   the browser's blue default over a cream card. */
.card-btn:focus-visible {
  outline: 2px solid var(--wine);
  outline-offset: 3px;
}

.card-btn__caret { display: inline-block; transition: transform .2s ease; }
.card-btn[aria-expanded="true"] .card-btn__caret { transform: rotate(180deg); }

/* --- The "Why" panel ----------------------------------------------------- */

.card-why {
  margin-top: 20px;
  padding-top: 18px;
  border-top: 1px solid var(--ink);
}

.card-why[hidden] { display: none; }

.card-why dl { margin: 0 0 20px; display: grid; gap: 14px; }

.card-why dt {
  margin-bottom: 5px;
  color: var(--wine);
  font: 12px/1 ui-monospace, SFMono-Regular, Menlo, monospace;
  letter-spacing: .12em;
  text-transform: uppercase;
}

/* P1.2 / item 6 — card and dialog body copy floors at 15px, definitions at
   16px. Nothing here was below the 12px hard floor, but 14px is below the
   sustained-reading floor the rest of the site now holds. */
.card-why dd { margin: 0; font-size: 16px; line-height: 1.5; }

.card-why ol {
  display: grid;
  gap: 16px;
  margin: 0;
  padding: 0;
  list-style: none;
  counter-reset: why;
}

.card-why li { counter-increment: why; }

.card-why li h4 {
  display: flex;
  gap: 10px;
  margin: 0 0 5px;
  font: 600 15px/1.25 Syne, Inter, sans-serif;
  letter-spacing: -.01em;
}

.card-why li h4::before {
  content: "0" counter(why);
  color: var(--wine);
  font: 12px/1.4 ui-monospace, SFMono-Regular, Menlo, monospace;
  letter-spacing: .08em;
}

.card-why li p {
  margin: 0;
  max-width: 52ch;               /* P1.3 */
  font-size: 15px;
  line-height: 1.5;
}

@media (hover: hover) and (pointer: fine) and (prefers-reduced-motion: no-preference) {
  .card-why { animation: card-why-in .28s cubic-bezier(.22, .61, .36, 1); }
  @keyframes card-why-in {
    from { opacity: 0; transform: translateY(-6px); }
    to   { opacity: 1; transform: none; }
  }
}

/* --- Card frame ---------------------------------------------------------- */

/* The base state, and the fallback for engines with no subgrid: a flex column.
   `.card-actions { margin-top: auto }` pushes the buttons to the bottom, which
   lines up that one row and nothing else. The subgrid block below replaces it. */
.project-card { display: flex; flex-direction: column; }
.project-card > .project-card__media { display: block; text-decoration: none; }

/* The archive sets `.project-card a { display:flex; flex-direction:column;
   height:100% }` for the media link. That selector also catches the title link
   and the action buttons, which are neither columns nor full-height. Both are
   restored here at a specificity that beats it. */
.project-card .card-heading h3 a {
  /* P2.1 — a 44px hit area without a 44px type size. `align-items: center`
     centres the existing cap-height inside the taller box, so the type and the
     optical position of the baseline are unchanged; only the target grows. */
  display: inline-flex;
  flex-direction: row;
  align-items: center;
  min-height: 44px;
  height: auto;
  max-width: 100%;
  color: inherit;
  text-decoration: none;
}

.project-card .card-heading h3 a:hover,
.project-card .card-heading h3 a:focus-visible {
  text-decoration: underline;
  text-underline-offset: 6px;
}

.project-card .card-actions .card-btn {
  display: inline-flex;
  flex-direction: row;
  height: auto;
}

/* --- Row alignment ------------------------------------------------------- */

/* Every card sizing itself independently is what makes a row read as broken:
   a two-line title, a longer description or a three-row stack shifts that one
   card's tech row and buttons out of line with its neighbours.

   Subgrid is the correct tool. The grid owns the rows; each card adopts them
   with `grid-template-rows: subgrid`, so card N's tech row and card N+1's tech
   row are literally the same track and cannot disagree.

   `.cards-aligned` is added by work-cards.js only once every card has had its
   `.card-why` panel lifted into a dialog and every card is confirmed to hold
   the same number of children. That matters: a subgrid may not create implicit
   tracks, so a card with one child more than `--card-rows` would stack that
   child on top of the previous row. With no JS the class never lands and the
   flex fallback above stays in force (P6.4). */
.project-grid { --card-gap: clamp(58px, 7vw, 100px); }

@supports (grid-template-rows: subgrid) {
  /* The parent's row-gap would otherwise be inserted between every *internal*
     card row as well as between card rows, so the gutter moves onto the card as
     padding and the grid pulls the trailing one back off the bottom. */
  .project-grid.cards-aligned {
    row-gap: 0;
    margin-bottom: calc(var(--card-gap) * -1);
  }

  .project-grid.cards-aligned > .project-card {
    display: grid;
    grid-template-rows: subgrid;
    grid-row: span var(--card-rows, 5);
    row-gap: 0;                  /* the children's own margins set the rhythm */
    padding-bottom: var(--card-gap);
  }

/* [hidden] must outrank the subgrid display above. The UA sheet's
   [hidden]{display:none} is weaker than any author `display`, and
   cinematic-work.css's .project-card[hidden] (0,2,0) loses to the (0,3,0)
   rule above -- so without this the archive filter marks cards hidden and
   every one of them keeps rendering. */
.project-grid.cards-aligned > .project-card[hidden] { display: none; }

  /* A shared row is as tall as the tallest card's content in it. Without this
     the shorter cards stretch to fill, which spreads a one-row tech stack over
     the height of a three-row one and un-aligns the very thing being aligned. */
  .project-grid.cards-aligned > .project-card > * { align-self: start; }
  .project-grid.cards-aligned > .project-card > .card-actions { margin-top: 0; }
}

/* --- Learn-more modal ---------------------------------------------------- */

/* A plain centred modal: title bar, meta, decisions, action. No banner image —
   the card that opened it already shows that image.

   Still a <dialog>, so focus trapping, Esc, and the backdrop come from the
   platform. showModal() centres it, so nothing here positions it by hand.

   Bounded by the viewport, never by its content: from 375px up the panel is
   short enough that nothing scrolls, and at 320x568 the densest project
   (ClassiFind, nine tools) scrolls *inside* the panel with
   `overscroll-behavior: contain`, so reaching the bottom of the reasoning never
   starts scrolling the archive behind it. */
.card-dialog {
  /* svh, not vh: on iOS `100vh` is the *large* viewport, so a 92vh dialog would
     be taller than the visible area while the URL bar is showing (P3.2). */
  width: min(720px, calc(100vw - 32px));
  max-width: calc(100vw - 32px);
  max-height: min(92svh, 820px);
  padding: 0;
  border: 1px solid var(--ink);
  background: var(--paper);
  color: var(--ink);
  box-shadow: 0 30px 70px -30px rgba(23, 19, 16, .5);
  overflow: auto;
  overscroll-behavior: contain;
}

.card-dialog::backdrop { background: rgba(23, 19, 16, .58); }

.card-dialog__head {
  display: flex;
  gap: 18px;
  align-items: center;
  justify-content: space-between;
  padding: 16px clamp(20px, 3.5vw, 30px);
  border-bottom: 1px solid var(--ink);
}

.card-dialog__eyebrow {
  margin: 0 0 7px;
  color: var(--ink);
  font: 12px/1 ui-monospace, SFMono-Regular, Menlo, monospace;
  letter-spacing: .12em;
  opacity: .55;
  text-transform: uppercase;
}

.card-dialog__title {
  margin: 0;
  font: 700 clamp(26px, 3.4vw, 34px)/1 Syne, Inter, sans-serif;
  letter-spacing: -.05em;
}

.card-dialog__close {
  flex: 0 0 auto;
  display: inline-flex;
  align-items: center;
  min-height: 44px;              /* P2.1 */
  padding: 0 15px;
  border: 1px solid var(--ink);
  background: transparent;
  color: inherit;
  cursor: pointer;
  font: 12px/1 ui-monospace, SFMono-Regular, Menlo, monospace;
  letter-spacing: .1em;
  text-transform: uppercase;
  transition: background .18s ease, color .18s ease;
}

.card-dialog__close:hover { background: var(--ink); color: var(--paper); }

/* An explicit focus ring in the site's wine, so keyboard focus does not fall
   back to the browser's blue default over a cream panel. */
.card-dialog__close:focus-visible,
.card-dialog__foot .card-btn:focus-visible {
  outline: 2px solid var(--wine);
  outline-offset: 3px;
}

.card-dialog .card-why {
  margin: 0;
  padding: 18px clamp(20px, 3.5vw, 30px);
  border-top: 0;
}

/* Stacked label/value pairs, as in the first version. */
.card-dialog .card-why dl {
  display: grid;
  gap: 12px;
  margin: 0 0 16px;
  padding-bottom: 14px;
  border-bottom: 1px solid var(--rule);
}

.card-dialog .card-why dt,
.card-dialog .card-why dd { text-align: left; }
.card-dialog .card-why dd { font-size: 16px; }
.card-dialog .card-why ol { gap: 11px; }
.card-dialog .card-why li p { font-size: 15px; line-height: 1.5; }
.card-dialog .tech-icon { width: 26px; height: 26px; }

.tech-stack--dialog { margin: 8px 0 0; }

.card-dialog__foot {
  position: sticky;
  bottom: 0;
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
  padding: 12px clamp(20px, 3.5vw, 30px);
  border-top: 1px solid var(--ink);
  background: var(--paper);
}

@media (max-width: 599px) {
  .card-actions { flex-direction: column; align-items: stretch; }
  /* A fixed 96px basis only fits two marks in a 284px card at 320px, so an
     eight-tool project wrapped to four rows. A basis derived from the card's
     own width packs exactly three per row at every phone size, holds the even
     rhythm the fixed basis was there for, and cannot overflow (P3.1). At 320px
     that is 86.7px per cell, which still clears the longest name in the grid
     ("WebAssembly", 82px at this size) on one line.

     Card only. The dialog's stack sits inside 40px of panel padding, where a
     third column would be 76px and would break "WebAssembly" and "JavaScript"
     mid-word; it keeps the 96px basis and packs two. */
  .project-card .tech-stack li { flex-basis: calc((100% - 24px) / 3); }
  /* A nine-tool project wraps to three rows at this width, which pushed the
     densest modal 17px past the viewport. Tighter rows plus a little more
     height keeps every card clear of scrolling. */
  .card-dialog { max-height: 93svh; }
  .card-dialog .tech-stack { gap: 12px 10px; }
  .card-dialog .tech-icon { width: 26px; height: 26px; }
  .card-dialog__foot .card-btn { width: 100%; }
}

@media (max-width: 680px) {
  /* Matches the archive's own 1-up row-gap, now carried as card padding. */
  .project-grid { --card-gap: 52px; }
}

/* --- Archive overrides --------------------------------------------------- */

/* The filter bar no longer follows the scroll. It pinned itself under the
   fixed header and then sat over the grid for the whole page. */
.filter-bar {
  position: static;
  z-index: auto;
  backdrop-filter: none;
  -webkit-backdrop-filter: none;
}

/* The archive footer is authored with `margin-inline: calc(var(--edge) * -1)`,
   which is correct while it sits inside `.archive` (padding-inline: var(--edge))
   and bleeds it to the viewport edge. It is now a direct child of <body>, where
   there is no edge padding to cancel, so the same negative margin pushes it
   43px past both viewport edges and puts 43px of horizontal overflow on the
   page at desktop (P3.1). Below 680px `html, body { overflow-x: clip }` hides
   it; from 681px up it is a real scrollbar.

   Scoped to `body >` so it only fires in the escaped position: if the footer is
   moved back inside `.archive` this rule stops matching and the original
   full-bleed margins take over again unchanged. */
body > .archive-footer {
  margin-inline: 0;
  margin-block-end: 0;
  max-width: 100%;
}

/* "All Work." is the page's main heading and was marked up as an <h2>, leaving
   the route with no <h1> at all. Promoted to h1; the archive stylesheet only
   ever selected h2 here. */
.archive-heading h1 {
  font: 700 clamp(48px, 6vw, 88px)/.82 Syne, Inter, sans-serif;
  letter-spacing: -.075em;
  margin: 12px 0 0;
}

@media (max-width: 680px) {
  .archive-heading h1 { font-size: 14vw; }
}
