/* ==========================================================================
   NEXUS — Projects sheet (T5, T6)

   The shared dark ground lives in dark.css and loads before this file. What
   remains here is only what the Projects section adds on top of it: the
   evidence stamp, the index grid, and the project detail layout.

   THE ONE NEW COMPONENT: the evidence stamp.
   Several of these machines are documented only in the NEXUS factory. Showing a
   build photograph without saying so would let a reader take it for production
   evidence — a claim NEXUS cannot support. The stamp is therefore content, not
   decoration, and it is styled to be read rather than to be pretty.

   Loaded via `pageStyles: ["dark", "projects"]`.
   ========================================================================== */

/* ==========================================================================
   The evidence stamp — the one component this section adds
   ========================================================================== */

.stamp {
  display: inline-flex;
  align-items: center;
  gap: 7px;
  flex: 0 0 auto;
  font-family: var(--font-label);
  font-size: 0.625rem;
  font-style: normal;
  letter-spacing: 0.05em;
  text-transform: uppercase;
  line-height: 1;
  padding: 5px 9px;
  border-radius: var(--radius-dark-ui);
  border: 1px solid currentColor;
  white-space: nowrap;
}
.stamp::before {
  content: "";
  inline-size: 5px;
  block-size: 5px;
  border-radius: 50%;
  background: currentColor;
}
/* On-site production evidence takes the accent. Factory build and test takes
   the muted grey — visible, legible, and deliberately not celebrated. */
.stamp--onsite  { color: var(--c-accent-on-dark); }
.stamp--factory { color: var(--c-muted); }
.band--invert .stamp--onsite  { color: var(--c-blue); }
.band--invert .stamp--factory { color: var(--c-muted-on-invert); }
/* Over a photograph the stamp needs its own ground to stay readable. */
.pj__frame .stamp,
.lead__frame + .lead__caption .stamp { background: rgba(0, 0, 0, 0.72); }

/* ==========================================================================
   Projects index
   ========================================================================== */

/* --- The selection table ------------------------------------------------- */

/* Two columns, not three, and a tighter row. This band is a route into the six
   cards below it, not a second telling of them: the related-product column was
   repeating information the card already carries as its solution label, and the
   product page is one click away from the card. */
.picker { inline-size: 100%; border-collapse: collapse; margin-top: var(--s-6); }
.picker th, .picker td { text-align: start; vertical-align: top; padding: var(--s-4) var(--s-5) var(--s-4) 0; }
.picker thead th {
  font-family: var(--font-label);
  font-size: var(--fs-eyebrow);
  font-weight: var(--fw-regular);
  letter-spacing: var(--ls-eyebrow);
  text-transform: uppercase;
  color: var(--c-muted);
  padding-block: 0 var(--s-4);
}
.picker tbody tr { border-top: 1px solid var(--c-line-dark); }
.picker tbody tr:last-child { border-bottom: 1px solid var(--c-line-dark); }
.picker tbody th {
  font-weight: var(--fw-regular);
  font-size: var(--fs-body);
  line-height: 1.5;
  inline-size: 52%;
}
.picker tbody th a { color: var(--c-invert); text-decoration: none; }
.picker tbody th a:hover { text-decoration: underline; text-underline-offset: 4px; }
.picker tbody td { font-size: var(--fs-body); line-height: 1.5; color: var(--c-muted); }

@media (max-width: 819px) {
  /* At 390px the table became six stacked blocks of need + response — 1478px of
     page saying what the six cards below say again. It becomes ONE column of
     six production-need links: the same six routes into the same six pages, in
     the same order. The response text is carried by each card's descriptor and
     in full on the project page, so nothing leaves the site.

     Two columns was tried first and made each need wrap to three lines at a
     size that had to be shrunk to fit. One column keeps public body text at
     ~16px on a 1.55 line height, which is the rule that decides this: density
     is never bought by making the reading size smaller. */
  .picker, .picker tbody, .picker thead { display: block; inline-size: auto; }
  .picker thead { position: absolute; inline-size: 1px; block-size: 1px; overflow: hidden; clip-path: inset(50%); }
  .picker tbody tr { display: block; border-top: 1px solid var(--c-line-dark); }
  .picker tbody tr:last-child { border-bottom: 1px solid var(--c-line-dark); }
  .picker tbody th {
    display: block;
    inline-size: auto;
    padding: var(--s-4) 0;
    font-size: var(--fs-body);
    line-height: 1.55;
  }
  /* the whole row reads as one link */
  .picker tbody th a { display: block; }
  .picker tbody th a::after { content: " →"; color: var(--c-accent-on-dark); }
  .picker tbody td { display: none; }
}

/* --- The project grid ---------------------------------------------------- */

.pgrid {
  list-style: none;
  margin: var(--s-7) 0 0;
  padding: 0;
  display: grid;
  gap: var(--gap-media);
  grid-template-columns: minmax(0, 1fr);
}
.pgrid > * { min-width: 0; }
/* one column at mobile, two at intermediate widths, three at large desktop */
@media (min-width: 820px)  { .pgrid { grid-template-columns: var(--grid-even); } }
@media (min-width: 1200px) { .pgrid { grid-template-columns: var(--grid-cards); } }

.pj {
  display: flex;
  flex-direction: column;
  block-size: 100%;
  background: var(--c-raised);
  border-radius: var(--radius-dark-card);
  overflow: hidden;
  transition: background-color var(--dur-reveal) var(--ease-expo-out);
}
.pj:hover { background: var(--c-raised2); }
.pj__frame { position: relative; background: #101010; aspect-ratio: var(--ratio-card); }
/* Two sources cannot be cropped to 4:3 without losing what they are for: the
   portrait dual-track image would lose a track, and the drainage detail is a
   video frame with no resolution to spare. They are contained INSIDE the same
   4:3 stage, so the card height never changes.

   The stage was white, which turned the pillarbox beside the portrait into two
   bright bars that read louder than the photograph between them. It is now the
   card system's own dark neutral: the letterbox recedes, the picture is the
   only lit thing in the frame, and the six cards read as one set. */
.pj--portrait .pj__frame,
.pj--contain .pj__frame {
  padding: var(--s-4);
  background: var(--c-raised2, #1F1F1F);
}
.pj__frame .stamp { position: absolute; inset-block-start: var(--s-4); inset-inline-start: var(--s-4); }

.pj__body {
  padding: var(--s-5) var(--s-6) var(--s-6);
  display: flex;
  flex-direction: column;
  gap: var(--s-3);
  flex: 1;
}
@media (min-width: 820px) {
  /* Reserved line counts, so the title, the description, the divider and the
     product label all start at the same height across a row. Two columns gave
     each card 632px and two lines was enough; at three columns a card is 416px
     and the longest industry line wraps to three, which put one title 18px
     below its neighbours and its description 25px below. The reservation is per
     breakpoint because it is a function of the card width, not of the words. */
  .pj .eyebrow { min-block-size: calc(2 * 1.5em); align-items: flex-start; }
  .pj__title   { min-block-size: calc(2 * 1.28em); }
}
@media (min-width: 1200px) {
  .pj .eyebrow { min-block-size: calc(3 * 1.5em); }
  .pj__title   { min-block-size: calc(3 * 1.28em); }
}
.pj__title { margin: 0; font-size: clamp(1.1875rem, 1rem + 0.75vw, 1.5rem); letter-spacing: -0.03em; line-height: 1.28; }
.pj__title a { color: var(--c-invert); text-decoration: none; }
.pj__title a::after { content: ""; position: absolute; inset: 0; }
.pj { position: relative; }
.pj__line { margin: 0; font-size: var(--fs-body-sm); line-height: 1.55; color: var(--c-muted); }
.pj__solution {
  margin: var(--s-4) 0 0;
  /* pinned to the foot of the card, so the rules align across the grid */
  margin-block-start: auto;
  padding-top: var(--s-3);
  border-top: 1px solid var(--c-line-dark);
  font-family: var(--font-label);
  font-size: var(--fs-eyebrow);
  letter-spacing: var(--ls-eyebrow);
  text-transform: uppercase;
  color: var(--c-muted);
}

/* ==========================================================================
   Project detail
   ========================================================================== */

.lead {
  display: grid;
  gap: var(--s-5);
  margin: 0;
  max-inline-size: calc(var(--w-wide) + (var(--gutter) * 2));
  margin-inline: auto;
  padding-inline: var(--gutter);
  color: var(--c-muted);
}
.lead > * { min-width: 0; }
@media (min-width: 900px) {
  .lead { grid-template-columns: var(--grid-asym); column-gap: var(--gap-text); }
  /* Landscape and technical leads stack image over caption in the dominant
     column, so the picture shares both edges with the H1 above it. */
  .lead__frame, .lead__caption { grid-column: 1; }

  /* A portrait frame is 420px inside a 760px column, so the column beside it
     would otherwise be empty twice over. The summary and the confirmed facts
     move in beside the picture. */
  .lead--portrait { grid-template-columns: minmax(0, var(--lead-w-port)) minmax(0, 1fr); }
  .lead--portrait .lead__frame { grid-column: 1; grid-row: 1 / span 2; }
  .lead--portrait .lead__caption { grid-column: 1; grid-row: 3; }
  .lead--portrait .lead__aside { grid-column: 2; grid-row: 1; align-self: start; }
}
.lead__aside { display: grid; gap: var(--s-5); max-inline-size: 44ch; }
.lead__aside .pfacts { margin: 0; grid-template-columns: minmax(0, 1fr); }
.lead__aside .pfact { border-top-color: var(--c-line-dark); padding-block: var(--s-4); }
.lead__aside .pfact dd { color: var(--c-invert); }
.lead__summary { font-size: var(--fs-lead); line-height: var(--lh-lead); color: var(--c-muted); margin: 0; }
.lead__frame { justify-self: start; }

/* Each variant is a FIXED STAGE, not a cap. The image is contained inside it,
   so six leads occupy the same rectangle whatever their source proportions —
   which is what makes the six pages read as one set. A 4:3 source and a 16:9
   source both land in the same box; neither one resizes the page.

   Landscape: important, but sized against the H1 and the text beside it, never
   against the viewport, and never full-bleed. */
.lead--landscape .lead__frame {
  inline-size: min(100%, var(--lead-w));
  block-size: var(--lead-h);
}
/* Portrait: narrower and taller, so both tracks stay readable. Never widened
   to match a landscape frame. */
.lead--portrait .lead__frame {
  inline-size: min(100%, var(--lead-w-port));
  block-size: var(--lead-h-port);
}
/* Technical: a still lifted from video. Deliberately below the landscape
   scale — it is evidence, not photography — and never enlarged to fill the
   space beside it. */
.lead--technical .lead__frame {
  inline-size: min(100%, var(--lead-w-tech));
  block-size: var(--lead-h-tech);
}
.lead__caption {
  display: grid;
  gap: var(--s-4);
  justify-items: start;
  align-content: start;
  margin: 0;
  max-inline-size: 40ch;
}
.lead__caption { max-inline-size: 60ch; }

/* --- The story ----------------------------------------------------------- */

.story__grid { display: grid; gap: var(--s-7); margin-top: var(--s-8); }
.story__grid > * { min-width: 0; }
@media (min-width: 900px) {
  .story__grid { grid-template-columns: var(--grid-even); column-gap: var(--gap-text); }
  /* One block: one column, held to a readable measure. No empty second column
     is reserved — the Dual Track project has an application and no recorded
     requirement, and a hole beside its only paragraph read as something
     missing rather than as a composition. */
  .story__grid--single { grid-template-columns: minmax(0, 1fr); max-inline-size: 62ch; }
}
.story__grid--single .story__block p { max-width: none; }
.story__block p { margin: var(--s-4) 0 0; font-size: var(--fs-body); line-height: 1.65; color: var(--c-muted); max-width: 54ch; }
.story__block--wide { grid-column: 1 / -1; padding-top: var(--s-6); border-top: 1px solid var(--c-line-dark); }
.story__block--wide p { color: var(--c-invert); font-size: var(--fs-lead); line-height: var(--lh-lead); max-width: 68ch; }

/* --- Evidence ------------------------------------------------------------ */

.egrid {
  list-style: none;
  margin: var(--s-6) 0 0;
  padding: 0;
  display: grid;
  gap: var(--s-6) var(--gap-media);
  grid-template-columns: minmax(0, 1fr);
}
.egrid > * { min-width: 0; }
/* The column count follows the picture count. Three images on a two-column grid
   left the third alone on the lower left, which reads as a missing fourth
   rather than as a set of three. */
@media (min-width: 820px) {
  .egrid { grid-template-columns: var(--grid-even); }
  .egrid--n3 { grid-template-columns: repeat(3, minmax(0, 1fr)); }
  .egrid--n4 { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}

.ev { margin: 0; }
.ev__frame {
  background: var(--c-raised);
  border-radius: var(--radius-dark-card);
  aspect-ratio: var(--ratio-evidence);
}
/* A contained gallery sits on the light stage, so the letterbox reads as a
   deliberate frame rather than as a gap. */
.egrid--contain .ev__frame { background: var(--c-invert); padding: var(--s-4); }
.ev figcaption { display: grid; gap: var(--s-3); justify-items: start; margin-top: var(--s-4); color: var(--c-muted); }

/* A quiet rule under the swipe row, so it reads as a track rather than as a
   card that happens to be cut off — plus a hint, because a row that scrolls
   sideways has to say so. The hint is authored in the template and only shown
   where the row is actually a swipe track. */
@media (max-width: 767px) {
  .evidence-set .egrid--swipe { border-block-end: 1px solid var(--c-line-dark); }
}
.egrid-hint { display: none; }
@media (max-width: 767px) {
  .evidence-set--swipe .egrid-hint {
    display: flex;
    align-items: center;
    gap: var(--s-2);
    margin: var(--s-3) 0 0;
    font-family: var(--font-label);
    font-size: var(--fs-eyebrow);
    letter-spacing: var(--ls-eyebrow);
    text-transform: uppercase;
    color: var(--c-muted);
  }
  .evidence-set--swipe .egrid-hint::after { content: "→"; color: var(--c-accent-on-dark); }
}

.ev-note {
  margin: var(--s-7) 0 0;
  padding: var(--s-5) var(--s-6);
  background: var(--c-raised);
  border-radius: var(--radius-dark-ui);
  font-size: var(--fs-body-sm);
  line-height: 1.6;
  color: var(--c-muted);
  max-width: 78ch;
}

/* --- Confirmed facts, on the one inverted band ---------------------------- */

.pfacts { display: grid; gap: 0; margin: var(--s-6) 0 0; }
@media (min-width: 820px) { .pfacts { grid-template-columns: var(--grid-even); column-gap: var(--gap-text); } }
.pfact { padding-block: var(--s-5); border-top: 1px solid #D6D6D6; }
.pfact dd { margin: var(--s-3) 0 0; font-size: var(--fs-body); line-height: 1.55; color: var(--c-ink); }

/* --- Equipment used ------------------------------------------------------ */

/* Level 4 of the frame hierarchy. This section existed to present a single
   190x143 thumbnail and was spending 570px of page to do it — a 64px heading, a
   56px lead-in and a full section's padding above a tertiary object. The frame
   is unchanged; only the chrome around it is now proportionate to it. */
.equipment { --section-dark: clamp(32px, 2.6vw, 44px); }
/* Level 4 of the frame hierarchy carries level 4 chrome. This section hands the
   reader one small card and a link; its heading steps below the other
   supporting sections so it cannot overpower the card it introduces. */
.page--dark .equipment .t-statement {
  font-size: clamp(1.25rem, 1rem + 0.9vw, 1.5rem);
  margin-top: var(--s-2);
}
.eqlist { list-style: none; margin: var(--s-5) 0 0; padding: 0; display: grid; gap: var(--s-4); grid-template-columns: minmax(0, 1fr); max-inline-size: 640px; }
.eqlist > * { min-width: 0; }
.eq {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  block-size: 100%;
  background: var(--c-raised);
  border-radius: var(--radius-dark-card);
  overflow: hidden;
  text-decoration: none;
  color: inherit;
  transition: background-color var(--dur-reveal) var(--ease-expo-out);
}
.eq:hover { background: var(--c-raised2); }
@media (min-width: 640px) {
  .eq { grid-template-columns: var(--product-w) minmax(0, 1fr); align-items: start; }
}
/* A stretched grid row was inflating this thumbnail to 222px on the two pages
   with longer product text, because aspect-ratio derived width from the row
   height. It is now a fixed box, aligned to the top of the row. */
.eq__frame {
  inline-size: var(--product-w);
  block-size: calc(var(--product-w) * 3 / 4);
  align-self: start;
  background: #101010;
}
.eq__text { padding: var(--s-5) var(--s-6); display: grid; gap: var(--s-2); align-content: center; }
.eq__name { font-size: var(--fs-h3); font-weight: var(--fw-light); letter-spacing: -0.02em; }
.eq__line { font-size: var(--fs-body-sm); line-height: 1.55; color: var(--c-muted); }

/* --- Pager --------------------------------------------------------------- */

.pager { padding-block: var(--s-7); border-top: 1px solid var(--c-line-dark); }
.pager__inner { display: flex; flex-wrap: wrap; gap: var(--s-6); justify-content: space-between; }
.pager__link { display: grid; gap: var(--s-3); text-decoration: none; color: var(--c-invert); max-inline-size: 40ch; }
.pager__link--next { text-align: end; justify-items: end; }
.pager__title { font-size: var(--fs-h3); font-weight: var(--fw-light); letter-spacing: -0.02em; line-height: 1.3; }
.pager__link:hover .pager__title { opacity: 0.72; }

/* --- Close --------------------------------------------------------------- */

.closing__heading { max-width: 16ch; }
.closing__body { margin-top: var(--s-6); max-width: 56ch; color: var(--c-ink); }
.closing__action { margin-top: var(--s-6); }

/* --- Mobile -------------------------------------------------------------- */

@media (max-width: 767px) {
  .page--dark .site-header { position: static; }

  /* Responsive composition. At 390px the section rhythm and the sub-heading
     scale were both inherited from the desktop composition, and together they
     cost roughly 900px a page: six sections at 56+56 padding, and six headings
     at the desktop statement size each running to three lines.

     Scoped to this sheet, so the frozen homepage tokens are untouched — it
     loads home.css, never this file. */
  .page--dark .section--major,
  .page--dark .section--band { padding-block: 40px; }
  /* the tertiary section keeps its tertiary padding here too */
  .page--dark .equipment { padding-block: 32px; }
  .page--dark .t-statement { font-size: 1.75rem; line-height: 1.15; max-width: 24ch; }
  .page--dark .page-head { padding-block: var(--s-6) 40px; }
  .story__grid { gap: var(--s-6); margin-top: var(--s-6); }
  .story__block--wide { padding-top: var(--s-5); }
  .pj__body { padding: var(--s-5); }

  /* At 390px a fixed-height lead stage left up to 300px of empty box under a
     wide picture. The stages become ratios instead — same hierarchy, no void. */
  .lead--landscape .lead__frame { block-size: auto; aspect-ratio: 3 / 2; }
  .lead--portrait  .lead__frame { block-size: auto; aspect-ratio: 9 / 16; }
  .lead--technical .lead__frame { block-size: auto; aspect-ratio: 16 / 9; }
  .lead--landscape .lead__frame,
  .lead--portrait  .lead__frame,
  .lead--technical .lead__frame { inline-size: 100%; }

  /* Three or four evidence tiles stacked is a long scroll for supporting
     material. They become one swipeable row instead, which also keeps evidence
     visibly secondary to the lead above it. */
  .egrid--swipe {
    grid-auto-flow: column;
    grid-auto-columns: 78%;
    grid-template-columns: none;
    overflow-x: auto;
    scroll-snap-type: x mandatory;
    scroll-padding-inline: var(--gutter);
    /* break the shell so the row can run to the screen edge and back */
    margin-inline: calc(var(--gutter) * -1);
    padding-inline: var(--gutter);
    padding-block-end: var(--s-3);
    overscroll-behavior-x: contain;
    scrollbar-width: thin;
  }
  .egrid--swipe > li { scroll-snap-align: start; }
  .egrid--swipe .ev figcaption { min-block-size: 5.5em; align-content: start; }
  .pager__link--next { text-align: start; justify-items: start; }
  /* A 24px pager title twice over, plus a 24px fact row six times, was buying
     very little at 390px. Both step down to body size. */
  .pager { padding-block: var(--s-6); }
  .pager__inner { gap: var(--s-5); }
  .pager__title { font-size: var(--fs-body); line-height: 1.4; }
  .pfact { padding-block: var(--s-4); }
  .pfact dd { margin-top: var(--s-2); }
  .eq__text { padding: var(--s-5); }
  .ev figcaption { margin-top: var(--s-3); }
  .egrid { gap: var(--s-6) var(--gap-media); }
}

@media (prefers-reduced-motion: reduce) {
  .page--dark *, .page--dark *::before, .page--dark *::after {
    transition-duration: 0.01ms !important;
    animation-duration: 0.01ms !important;
  }
}


/* --- The case video (13 Sep 2026) ----------------------------------------
   One wide click-to-load stage straight after the lead photograph, on pages
   whose project file names a verified video. Component: dark.css → videos.
   Aligned to the same left edge as the lead and capped at 900px, so it reads
   as the second lead, not as a full-bleed hero. */
.page--dark .pj-video { padding-block: var(--s-6) 0; }
@media (max-width: 767px) { .page--dark .pj-video { padding-block: var(--s-5) 0; } }
