/* ==========================================================================
   project-detail.css
   The drawing-set layout for the 25 project detail pages. Template E1.

   Loaded by projects/*.html AFTER ../styles.css. Deliberately NOT merged into
   styles.css: that file is shared by every page on the site, so anything added
   there propagates to writing / travels / pictures / explore / archive / 404
   and the homepage. This is needed by the project pages only, so it lives with
   them.

   It reuses styles.css tokens (:root) and its shared chrome (.site-header,
   .crumb, .project-section-nav, .site-footer, .project-detail-nav). Everything
   declared here is scoped under .cs-* (the sheet set) or .pl-* (a plate).

   THE IDEA. A project page is a drawing SET. One sheet per section, each sheet
   headed SHEET nn OF nn / SUBJECT, and every image is a numbered PLATE with a
   title block strip under it. There is no drawing furniture for its own sake:
   no zone markings, no corner brackets, no repeated foot title block, no
   revision table. What varies sheet to sheet is the COMPOSITION, a two-column
   cover, a full-width text sheet, plates two or three up, because that variety
   is what keeps eight sheets from reading as one template.

   Three numbers carry the brief: the gutter between the left and right columns
   (up to 6.5rem), the measure of the body copy (66ch at 1.78), and the quiet
   around each sheet.

   Type is limited to the six :root tiers. Mono chrome is --fs-micro when it is
   inert and --fs-label only when it is pressable. A box means you can press it,
   and the only boxed things on a page are the plates (a drawing field is the
   established exception) and the source links.

   The markup is generated by tools/build_project_pages.py, which chooses the
   column layout of each sheet from how many images that section carries. After
   it has run the HTML is the source of truth and is hand-editable, which is how
   the rest of this site works.
   ========================================================================== */

.cs-main {
    /* Sheet padding, and the one number that carries the brief: the left/right
       separation. */
    --pad: clamp(1.1rem, 3vw, 3.2rem);
    --gutter: clamp(1.8rem, 6vw, 6.5rem);
    padding-top: var(--header-height);
    background: var(--bg-deep);
    /* clip, not hidden: hidden would make this a scroll container and break
       the sticky section nav. */
    overflow: clip;
}

.cs-main [id] { scroll-margin-top: calc(var(--header-height) + 4.6rem); }

/* --- Breadcrumb bar ----------------------------------------------------- */

.cs-topbar {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    justify-content: space-between;
    gap: 0.4rem 1.5rem;
    padding: 0.8rem var(--page-pad);
    border-bottom: 1px solid var(--line);
    background: var(--bg);
}

.cs-topbar .crumb { margin: 0; }

/* One piece of metadata, not four. */
.cs-topbar-meta {
    margin: 0;
    color: var(--muted);
    font: 700 var(--fs-micro)/1.5 var(--mono);
    letter-spacing: 0.13em;
    text-transform: uppercase;
}
.cs-topbar-meta b { color: var(--ink); font-weight: 700; }

/* --- The sheet ---------------------------------------------------------- */

.cs-set {
    display: grid;
    gap: clamp(0.9rem, 2vw, 1.6rem);
    padding: clamp(0.9rem, 2vw, 1.6rem) var(--page-pad) clamp(2.5rem, 5vw, 4.5rem);
}

/* One hairline. No inner frame, no registration brackets, no zone strip. The
   sheet reads as paper on a table because the page behind it is --bg-deep. */
.cs-sheet {
    width: 100%;
    max-width: 92rem;
    margin-inline: auto;
    padding: var(--pad);
    border: 1px solid var(--line);
    background: var(--bg);
}

/* Scoped with .cs-main purely for specificity: .pl and .cs-facts both reset
   their own margin further down the file, and at equal specificity the later
   rule would win and collapse the gap above every plate. */
.cs-main .cs-sheet > * + * { margin-top: clamp(1.7rem, 3.6vw, 3.2rem); }

/* --- Sheet header: the one piece of chrome that stayed ------------------ */

.cs-id {
    margin: 0;
    padding-bottom: 0.75rem;
    border-bottom: 1px solid var(--line);
    color: var(--muted);
    font: 700 var(--fs-micro)/1.5 var(--mono);
    font-variant-numeric: tabular-nums;
    letter-spacing: 0.18em;
    text-transform: uppercase;
}
.cs-id b { color: var(--ink); font-weight: 700; }
.cs-id i { margin: 0 0.55rem; font-style: normal; }

/* --- Headings ----------------------------------------------------------- */

.cs-sheet h1 {
    margin: 0;
    font: 900 var(--fs-display)/0.84 var(--display);
    letter-spacing: -0.062em;
    text-transform: uppercase;
}

.cs-sheet h2 {
    margin: 0;
    font: 900 var(--fs-section)/0.96 var(--display);
    letter-spacing: -0.042em;
    text-transform: uppercase;
    text-wrap: balance;
}

.cs-sheet h1 em,
.cs-sheet h2 em { color: var(--signal); font-style: normal; }

/* --- Sheet 01 cover ----------------------------------------------------- */

.cs-cover {
    display: grid;
    grid-template-columns: minmax(0, 1.12fr) minmax(0, 0.88fr);
    gap: var(--gutter);
    align-items: start;
}

.cs-kicker {
    display: flex;
    flex-wrap: wrap;
    gap: 0.3rem 1.5rem;
    margin: 0 0 1rem;
    color: var(--muted);
    font: 700 var(--fs-micro)/1.5 var(--mono);
    font-variant-numeric: tabular-nums;
    letter-spacing: 0.14em;
    text-transform: uppercase;
}
.cs-kicker b { color: var(--ink); font-weight: 700; }

.cs-deck {
    max-width: 54ch;
    margin: clamp(1.4rem, 2.6vw, 2.2rem) 0 0;
    color: var(--ink);
    font-size: var(--fs-body);
    line-height: 1.7;
}

/* --- Reading layout ----------------------------------------------------- */

/* Heading in its own column, copy in its own column, a very wide gutter
   between them. This and .cs-split are the only two text arrangements on the
   page, which is the point. */
.cs-read {
    display: grid;
    grid-template-columns: minmax(0, 0.76fr) minmax(0, 1fr);
    gap: var(--gutter);
    align-items: start;
}

.cs-body { max-width: 66ch; }

.cs-body p {
    margin: 0 0 1.25em;
    color: var(--muted-light);
    font-size: var(--fs-body);
    line-height: 1.78;
}
.cs-body p:first-child { color: var(--ink); }
.cs-body p:last-child { margin-bottom: 0; }

/* Subheads, for the rare section long enough to need skimming. Stays inside
   the three heading tiers at --fs-block. */
.cs-body h3 {
    margin: 2.2rem 0 0.8rem;
    color: var(--ink);
    font: 900 var(--fs-block)/1.05 var(--display);
    letter-spacing: -0.03em;
    text-transform: uppercase;
}
.cs-body > *:first-child { margin-top: 0; }

.cs-body strong { color: var(--ink); font-weight: 700; }

/* A list is a list, not a set of boxes. The arrow is the only ink it gets. */
.cs-body ul {
    display: grid;
    gap: 0.6rem;
    margin: 1.5rem 0;
    padding: 0;
    list-style: none;
}

.cs-body li {
    position: relative;
    padding-left: 1.6rem;
    color: var(--muted-light);
    font-size: var(--fs-body);
    line-height: 1.7;
}

.cs-body li::before {
    position: absolute;
    left: 0;
    color: var(--signal);
    content: "\2192";
}

/* A pulled claim. Inert, so no box: it earns emphasis from the rule and the
   size instead. */
.cs-pull {
    max-width: 30ch;
    margin: 1.8rem 0;
    padding-left: 1.2rem;
    border-left: 2px solid var(--signal);
    color: var(--ink);
    font: 650 var(--fs-block)/1.35 var(--display);
    letter-spacing: -0.015em;
}

/* Media one side, heading over copy the other. Same gutter, and the text
   column is given the larger share so the measure survives the split. */
.cs-split {
    display: grid;
    grid-template-columns: minmax(0, 1fr) minmax(0, 1.08fr);
    gap: var(--gutter);
    align-items: start;
}

.cs-main .cs-stack > * + * { margin-top: clamp(1.2rem, 2.4vw, 2rem); }

/* --- Schedules, as label and value. No item numbers, no header row. ------ */

.cs-facts-title {
    margin: 0 0 0.55rem;
    color: var(--muted);
    font: 700 var(--fs-micro)/1.5 var(--mono);
    letter-spacing: 0.18em;
    text-transform: uppercase;
}
.cs-facts-title b { color: var(--ink); font-weight: 700; }

.cs-facts {
    margin: 0;
    border-top: 2px solid var(--ink);
}
.cs-facts > div {
    display: grid;
    grid-template-columns: 8rem minmax(0, 1fr);
    gap: 0.3rem 1.3rem;
    padding: 0.7rem 0 0.8rem;
    border-bottom: 1px solid var(--line);
}
.cs-facts dt {
    color: var(--muted);
    font: 700 var(--fs-micro)/1.75 var(--mono);
    letter-spacing: 0.14em;
    text-transform: uppercase;
}
.cs-facts dd {
    margin: 0;
    color: var(--ink);
    font-size: var(--fs-small);
    line-height: 1.55;
}
/* The whole raspberry budget of the page: the one row that records what is
   NOT known. At most one per page, so it stays a signal. */
.cs-facts .is-alert dt,
.cs-facts .is-alert dd { color: var(--raspberry); }

/* Three columns of label over value, for a schedule that is neither one column
   nor two. Not emitted by the generator; here because the pages are
   hand-editable and a schedule sometimes wants it. */
.cs-facts--3 {
    display: grid;
    grid-template-columns: repeat(3, minmax(0, 1fr));
    column-gap: clamp(1.2rem, 3.5vw, 3.4rem);
}
.cs-facts--3 > div { grid-template-columns: minmax(0, 1fr); gap: 0.4rem; }

/* --- Plates ------------------------------------------------------------- */

/* A column, so that plates sharing a row can be the same height without one of
   them showing a bare strip under its title block: the caption field takes up
   the slack instead, which is what an oversized note box looks like on paper. */
.pl {
    position: relative;
    display: flex;
    flex-direction: column;
    margin: 0;
    border: 1px solid var(--line);
    background: var(--bg);
    isolation: isolate;
}

/* The registration tick the index plates carry, kept at rest so it works on a
   phone. Decorative, so it never eats a click. */
.pl::after {
    position: absolute;
    z-index: 3;
    top: 0;
    right: 0;
    width: 0.8rem;
    height: 0.8rem;
    border-top: 1px solid var(--signal);
    border-right: 1px solid var(--signal);
    content: "";
    opacity: 0.6;
    pointer-events: none;
}

.pl-field {
    position: relative;
    flex: 0 0 auto;
    aspect-ratio: var(--ar, 16 / 9);
    overflow: hidden;
    border-bottom: 1px solid var(--line);
    background: var(--bg-deep);
}
.pl-field img,
.pl-field video {
    display: block;
    width: 100%;
    height: 100%;
    object-fit: var(--fit, cover);
    /* --zoom pushes a render that sits in an empty viewport out to the edges of
       the plate. Hand-applied only, and only where nothing gets cut. */
    transform: scale(var(--zoom, 1));
}

/* A plate whose field ratio was set by the ROW it sits in rather than by its
   own picture. Contain, so the odd one out is letterboxed onto the field
   colour rather than cropped. Below 760px the row is one column again and the
   plate gets its own --ar-sm back, at which point contain fills exactly. */
.pl--fit { --fit: contain; }

/* Schematic and drawing exports: shown whole on their own paper colour, never
   cropped. Any letterbox lands on the cream the export was drawn on, so it
   reads as sheet margin rather than as a gap. */
.pl--doc { --fit: contain; }
.pl--doc .pl-field { max-height: min(70svh, 46rem); background: var(--paper); }

/* A portrait plate is a NARROW plate. Left at full sheet width a 3:4 photograph
   is over 1500px tall, and cropping it to landscape throws away half the
   picture, so the plate itself gets narrower instead and the picture stays
   whole. --pl-max is computed per plate from the real aspect ratio so every
   tall plate lands at about the same height. */
.pl--tall {
    max-width: min(100%, var(--pl-max, 32rem));
    margin-inline: auto;
}

/* Title block strip, three fields: plate number, title, view. Hairlines are the
   1px grid gap, so the wrap needs no first-child border bookkeeping. */
.pl-tb {
    display: grid;
    flex: 0 0 auto;
    grid-template-columns: auto minmax(0, 1fr) auto;
    gap: 1px;
    background: var(--line);
}
.pl-cell {
    display: grid;
    gap: 0.3rem;
    min-width: 0;
    padding: 0.5rem 0.8rem 0.55rem;
    background: var(--bg);
}
.pl-cell > span {
    color: var(--muted);
    font: 700 var(--fs-micro)/1 var(--mono);
    letter-spacing: 0.14em;
    text-transform: uppercase;
}
.pl-cell > b {
    color: var(--ink);
    font: 800 var(--fs-small)/1.2 var(--display);
    letter-spacing: -0.005em;
    text-transform: uppercase;
    overflow-wrap: break-word;
}

/* The caption says only what the title block does not. It is always drawn, even
   when there is nothing to add, because it is the field that evens a row out. */
.pl-cap {
    flex: 1 1 auto;
    margin: 0;
    padding: 0.6rem 0.8rem 0.7rem;
    border-top: 1px solid var(--line);
    color: var(--muted-light);
    font-size: var(--fs-small);
    line-height: 1.55;
}

/* --- Plate rows --------------------------------------------------------- */

.cs-2up {
    display: grid;
    grid-template-columns: repeat(2, minmax(0, 1fr));
    gap: clamp(0.9rem, 2vw, 1.5rem);
}

/* Three up is the tightest row on the site. It is what a sheet gets when the
   section carries three or more pictures, and the trade is that each plate is
   about a third of the sheet, so the title block cannot stay on one line. */
.cs-3up {
    display: grid;
    grid-template-columns: repeat(3, minmax(0, 1fr));
    gap: clamp(0.9rem, 2vw, 1.5rem);
}

/* Title over a two-field footer rather than three fields across. Nothing is
   dropped, it just wraps, which is what keeps a 22rem plate legible. */
.cs-3up .pl-tb { grid-template-columns: repeat(2, minmax(0, 1fr)); }
.cs-3up .pl-cell--title { grid-column: 1 / -1; order: -1; }

/* A figure placed inside the copy column by a [[media:]] or [[row:]] directive
   rather than by the sheet layout. */
.cs-body > .pl,
.cs-body > .cs-2up,
.cs-body > .cs-3up { margin: 1.8rem 0; }

/* Dimension callout, for a plate whose real dimensions are on the record.
   Hand-applied: the generator has no dimensions to draw. */
.pl-dim {
    position: absolute;
    z-index: 2;
    color: var(--ink);
    font: 700 var(--fs-micro)/1 var(--mono);
    letter-spacing: 0.1em;
}
.pl-dim::before,
.pl-dim::after { position: absolute; background: currentColor; content: ""; }
.pl-dim span {
    position: absolute;
    top: 50%;
    left: 50%;
    padding: 0.18rem 0.4rem;
    background: var(--bg);
    white-space: nowrap;
}
.pl-dim--x { height: 1px; background: currentColor; }
.pl-dim--x::before,
.pl-dim--x::after { top: -4px; width: 1px; height: 9px; }
.pl-dim--x::before { left: 0; }
.pl-dim--x::after { right: 0; }
.pl-dim--x span { transform: translate(-50%, -50%); }
.pl-dim--y { width: 1px; background: currentColor; }
.pl-dim--y::before,
.pl-dim--y::after { left: -4px; width: 9px; height: 1px; }
.pl-dim--y::before { top: 0; }
.pl-dim--y::after { bottom: 0; }
.pl-dim--y span { transform: translate(-50%, -50%) rotate(-90deg); }

/* --- The unissued plate -------------------------------------------------- */

/* A DESIGNED state, not a broken placeholder. Same vocabulary as
   .plate-field--empty on the projects index: ruled grid, cut diagonal, stamped
   REF. It says "this drawing has not been issued yet", which is true and
   deliberate, rather than looking like a failed image load. Naming the file it
   is waiting for is what makes filling it cheap: the page says exactly what to
   drop in. */

.pl--empty { border-style: dashed; }
.pl--empty::after { opacity: 0.35; }

.pl-field--empty {
    display: grid;
    place-items: center;
    /* width is stated because max-height plus an aspect ratio would otherwise
       shrink the field sideways to keep the ratio, leaving the ruled grid short
       of the plate edge. Here the ratio is a floor for narrow screens only. */
    width: 100%;
    aspect-ratio: var(--ar, 21 / 9);
    max-height: 26rem;
    background:
        repeating-linear-gradient(0deg, transparent 0 19px, var(--line) 19px 20px),
        repeating-linear-gradient(90deg, transparent 0 19px, var(--line) 19px 20px),
        var(--bg-deep);
}

.pl-field--empty::before {
    position: absolute;
    inset: 0;
    background: linear-gradient(103deg, transparent 49%, var(--line) 49% 49.5%, transparent 49.5%);
    content: "";
}

.pl-stamp {
    position: relative;
    display: grid;
    gap: 0.5rem;
    justify-items: center;
    padding: 0.9rem 1.1rem;
    box-shadow: 0 0 0 1px var(--line);
    background: var(--bg-deep);
    text-align: center;
}

.pl-stamp b {
    color: var(--muted);
    font: 700 var(--fs-body)/1 var(--mono);
    letter-spacing: 0.2em;
}

.pl-stamp span {
    max-width: 30ch;
    color: var(--muted);
    font: 700 var(--fs-micro)/1.6 var(--mono);
    letter-spacing: 0.08em;
    text-transform: uppercase;
}

.pl-stamp code {
    max-width: 34ch;
    color: var(--muted);
    font: 400 var(--fs-micro)/1.5 var(--mono);
    opacity: 0.7;
    overflow-wrap: anywhere;
}

/* --- Video plates -------------------------------------------------------- */

/* At most one autoplaying loop per page, marked data-autoplay; everything else
   is click to play. Under reduced motion nothing autoplays and the poster
   stands in, so the page still reads as a set of stills. app.js drives this
   against .pd-video and .pd-video-play, which is why those two class names
   survive the move to plate vocabulary. */

.pd-video-play {
    position: absolute;
    z-index: 3;
    inset: 0;
    display: grid;
    place-items: center;
    border: 0;
    background: rgba(17, 20, 24, 0.28);
    color: #fff;
    cursor: pointer;
    font: 700 var(--fs-label)/1 var(--mono);
    letter-spacing: 0.16em;
    text-transform: uppercase;
    transition: background 300ms var(--ease-out);
}

.pd-video-play span {
    padding: 0.75rem 1.1rem;
    box-shadow: 0 0 0 1px rgba(255, 255, 255, 0.6);
    background: rgba(17, 20, 24, 0.55);
}

.pd-video-play:hover,
.pd-video-play:focus-visible { background: rgba(17, 20, 24, 0.12); }

.pd-video.is-playing .pd-video-play { display: none; }

.pd-video .pd-video-poster {
    position: absolute;
    display: none;
    inset: 0;
}

/* --- Source links -------------------------------------------------------- */

.cs-links {
    display: flex;
    flex-wrap: wrap;
    gap: 0.7rem;
    margin: 0;
    padding: 0;
    list-style: none;
}

.cs-link {
    display: inline-flex;
    align-items: baseline;
    gap: 0.6rem;
    max-width: 100%;
    padding: 0.7rem 0.95rem;
    border: 1px solid var(--line-strong);
    color: var(--ink);
    font: 700 var(--fs-label)/1.5 var(--mono);
    letter-spacing: 0.04em;
    text-transform: uppercase;
    transition: color 200ms ease, border-color 200ms ease;
}
.cs-link:hover,
.cs-link:focus-visible { border-color: var(--signal); color: var(--signal); }
.cs-link i { color: var(--signal); font-style: normal; }

/* --- Responsive --------------------------------------------------------- */

/* Two fold points, because the two arrangements run out of room at different
   widths. A .cs-split puts the copy in half a sheet, so it hits the bottom of
   a 62ch measure first and folds early. A .cs-read gives the copy the larger
   track and holds two columns down to 1080. Both are folded well before 900. */
@media (max-width: 1240px) {
    .cs-split { grid-template-columns: minmax(0, 1fr); }
    .cs-main .cs-split > * + * { margin-top: clamp(1.4rem, 3.4vw, 2.4rem); }
}

@media (max-width: 1080px) {
    .cs-cover,
    .cs-read { grid-template-columns: minmax(0, 1fr); }
    .cs-main .cs-cover > * + *,
    .cs-main .cs-read > * + * { margin-top: clamp(1.4rem, 3.4vw, 2.4rem); }
    .cs-body { max-width: 68ch; }
    .cs-facts--3 { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}

@media (max-width: 900px) {
    /* Two boards side by side at this width are too small to compare, so they
       stack at full plate size instead. Three up has further to fall, so it
       drops to two first and only stacks at 760. */
    .cs-2up { grid-template-columns: minmax(0, 1fr); }
    .cs-3up { grid-template-columns: repeat(2, minmax(0, 1fr)); }
    .pl--doc .pl-field { max-height: none; }
}

@media (max-width: 760px) {
    .cs-main { --pad: 1rem; }
    .cs-sheet { border-inline: 0; margin-inline: calc(var(--page-pad) * -1); width: auto; }
    .cs-3up { grid-template-columns: minmax(0, 1fr); }
    /* One plate per row again, so each one gets its own ratio back and the
       contained ones fill their field exactly. */
    .pl-field { aspect-ratio: var(--ar-sm, var(--ar, 16 / 9)); }
    .pl--tall { max-width: 100%; }
    .cs-facts > div,
    .cs-facts--3 { grid-template-columns: minmax(0, 1fr); }
    .cs-facts > div { gap: 0.35rem; }
}

@media (max-width: 620px) {
    .pl-tb { grid-template-columns: repeat(2, minmax(0, 1fr)); }
    .pl-cell--title { grid-column: 1 / -1; order: -1; }
}

@media print {
    .pl,
    .cs-sheet { break-inside: avoid; }
}

@media (prefers-reduced-motion: reduce) {
    .cs-link,
    .pd-video-play { transition: none; }

    .pd-video video { display: none; }
    .pd-video .pd-video-poster { display: block; }
}
