/* ============================================================================
   Direction C · UNIVERS — sub-page shell + typographic primitives
   Loaded after c-base.css on every text page.

   The whole system is the Lovers Tour section's grammar, generalised. That block
   is three vertical stops and two rule strengths:

     hang    −.5 × --gut   the display headline (.hang-l)
     edge    the content column   labels, rows, lists
     indent  +--ind        the mono line under the headline

   and one sequence: label → monument → mono fact → hairline rows → label → list.
   Small→huge→small→medium→small. "Playful" is produced by SKIPPING steps of that
   ladder and by moving type between the three stops — never by ornament. There is
   no colour accent, no quote glyph, no bullet, no arrow anywhere below. The ONE box is .cta, the
   donate call-to-action, which inherits c-base.css's already-sanctioned header chip.

   Weight semantics, one job each (all already in the page's font request):
     Inter Tight 800  place      city monuments, nowhere else
     Inter Tight 700  title      page + chapter titles, pull-quotes
     Inter Tight 600  value      row values, work titles
     --sans      300  voice      ledes, statements, dialogue, stairs
     --sans      400  prose      body
     --sans      600  label      .eyebrow (tracked caps)
     mono        400  fact       years, dates, counts, provenance
     mono        500  marked     the acrostic capitals, nowhere else

   Rule strengths, only two: --hair between rows · --ink above a chapter.
   ============================================================================ */

/* ── SHELL ────────────────────────────────────────────────────────────────── */

/* page header — the Lovers Tour opening with h1 in place of h2. No eyebrow above the
   title on any sub-page, so it opens higher than the homepage gives #upcoming. */
.phead{padding-block:clamp(2.6rem,6vh,5rem) clamp(2.5rem,6vh,4.5rem)}
.phead .body{grid-column:3/10}
/* BACK, above the page title. A real href to the homepage section that owns this page — not
   history.back(), which has nothing to go back to on a page opened cold from a link or a bookmark.
   It carries .hang-l so it starts on the title's own left edge, not the body column's: the h1 hangs
   half a gutter left of the text, and a back link indented from it would look like a mistake.
   Deliberately quiet — soft grey going ink on hover: it is navigation chrome above a 76px title, not
   content. No .reveal, so it is there the moment the page paints. */
.back{display:inline-block;margin-bottom:clamp(.9rem,2.2vh,1.5rem);font-family:var(--mono);
  font-size:.78rem;letter-spacing:.05em;color:var(--ink-soft);transition:color .3s}
.back:hover{color:var(--ink)}
.back .ar{display:inline-block;margin-right:.5em;transition:transform .3s}
.back:hover .ar{transform:translateX(-.2em)}
.back:focus-visible{outline:1px solid var(--ink);outline-offset:.4rem}
/* the mono line under a display title. .season is the homepage's instance; both
   sit in the headline's indent and carry METADATA, never prose. */
.subline{margin-top:.9rem;font-size:.84rem;letter-spacing:.12em;text-transform:uppercase;padding-left:var(--ind)}
/* on-page view switch — left column, indented to the same stop as the subline.
   NOT the right margin: a right-hand off-grid label was ruled "completely lost". */
.phead .seg{margin-top:clamp(1.8rem,4vh,2.8rem);margin-left:var(--ind);max-width:19rem}
.seg.caps button{text-transform:uppercase;letter-spacing:.14em;font-size:.62rem;padding:.6rem .5rem}

/* text-page band rhythm — the homepage cadence is far too tall for reading pages */
.band.tight{padding-block:var(--pad-s)}

/* ── PRIMITIVES ───────────────────────────────────────────────────────────── */

/* reading prose. Pair with .measure (52ch) — the liner-notes column. */
.read{font-size:var(--read);line-height:1.78}
.read p + p{margin-top:1.15em}

/* display lede — a first sentence spoken in the Light voice.
   Deliberately NOT Inter Tight: that face is only loaded 600/700/800. */
.dlede{font-weight:300;font-size:clamp(1.55rem,3.1vw,2.7rem);line-height:1.14;letter-spacing:-.014em;
  max-width:30ch;text-wrap:balance}

/* THE EMPHASIS VOICE — one size for everything a page lifts out of running prose. No rule, no
   quote mark: the whitespace and the gutter hang do the work; a box would read as a callout.
   ONE recipe across the site, per the client ("ako se nesto istice, mozda uvek istim vecim
   fontom"); only .lead sits above it. Projects' 2018 values lines are the sole user — the
   manifesto sets every lifted sentence as prose instead, so this voice appears once site-wide.
   Adding a second user is a design decision, not a styling one. */
.stair.caps > *{font-family:"Inter Tight",var(--sans);font-weight:700;
  text-transform:uppercase;font-size:clamp(1.6rem,3.6vw,3rem);line-height:1.04;letter-spacing:.005em}

/* row modifiers (the base rule lives in c-base.css beside .gig).
   .row is sub-page-only, so anything set here leaves the homepage's .gig — which shares the base
   rule — at its approved size. Projects sizes its own concert and programme rows in its style
   block; note that if you ever give a .row a second grid ROW, the base `gap` is both axes and
   will open a 2.5rem gutter inside the row. */
.row.light .v{font-family:var(--sans);font-weight:300;font-size:clamp(1.25rem,2.4vw,2.1rem);
  line-height:1.16;letter-spacing:-.012em;text-transform:none}
.row.tight{padding:1.1rem 0}
body.panels-dark .field .row + .row{border-top-color:rgba(251,251,250,.16)}
body.panels-dark .field .row .k,body.panels-dark .field .row .t{color:rgba(251,251,250,.62)}

/* roster — a running chorus, never a column of <li>. The city is promoted from a
   parenthetical to tracked caps, so the places become the block's visible pulse. */
.roster{font-size:.98rem;line-height:1.9;text-wrap:pretty}
.roster.cols{columns:2;column-gap:clamp(1.6rem,4vw,3.4rem)}
/* ONE tag voice for both cities. The home city was tracked caps and the second city was mono, so
   the same place rendered in two unrelated faces and never looked like itself (client: "npr.
   berlin/munich"). The tracking matches .eyebrow, so the page now has a single micro-caps style
   rather than three near-identical ones. */
.roster .ct,.roster .now{font-size:.66rem;text-transform:uppercase;letter-spacing:.26em;
  font-weight:600;color:var(--ink-soft);white-space:nowrap}
.roster .ct{margin-left:.45em}
.roster .now{margin-left:.5em}
/* the second city is the same voice, set apart by a thin rule rather than a different font */
.roster .now::before{content:"";display:inline-block;width:.5em;height:1px;vertical-align:.28em;
  margin-right:.5em;background:currentColor;opacity:.55}
/* a name never breaks away from its place, and the tracked-caps place IS the delimiter —
   no dots needed between entries, and no justification: justify stretches the space inside
   a person's name, which looks broken. */
.roster .e{white-space:nowrap;margin-right:1.15em}
body.panels-dark .field .roster .ct,body.panels-dark .field .roster .now{color:rgba(251,251,250,.62)}

/* chapter — a monument in the label column under a full-ink rule. That rule is the
   only structural device on a page carrying six or nineteen chapters. */
.chap{border-top:1px solid var(--ink);padding-top:1.2rem;margin-top:var(--pad-s)}
/* A CHAPTER'S YEAR IS SMALLER THAN THE PAGE TITLE — it is a step DOWN the ladder, not a second h1
   (client, 2026-08-03). Lives here rather than per page so both year-keyed pages take one value;
   Projects states the identical clamp in its own block, which now just agrees with this. */
.chap .lead{font-size:clamp(1.7rem,3.4vw,2.9rem)}
.chap .n{grid-column:1/3;align-self:stretch}
.chap .n > span{position:sticky;top:6.4rem;display:block}   /* parks under the 5.2rem header + air */
.chap .n.num > span{font-family:var(--mono);font-weight:400;font-variant-numeric:tabular-nums;
  font-size:clamp(2.6rem,7vw,6rem);line-height:.82;letter-spacing:-.02em}
.chap .b{grid-column:3/12}
.chap .lbl{grid-column:1/4}

/* composer statement — the one block using all three stops at once: work title hangs
   left, name on the edge, the statement inset to the indent. */
.stmt{border-top:1px solid var(--hair);padding-block:clamp(2.4rem,5vh,4rem)}
.stmt .who{font-size:.66rem;text-transform:uppercase;letter-spacing:.26em;font-weight:600;color:var(--ink-soft)}
.stmt .work{margin-left:calc(-.5 * var(--gut));margin-top:.6rem;font-weight:300;
  font-size:clamp(1.4rem,2.7vw,2.3rem);line-height:1.1;letter-spacing:-.014em}
.stmt .say{margin-left:var(--ind);margin-top:1.2rem;max-width:52ch;font-size:var(--read);line-height:1.78}
.stmt .say p + p{margin-top:1.15em}

/* ── THE TEXT PAGE ─────────────────────────────────────────────────────────────────────────
   ONE composition for every long-form page under ABOUT — Manifesto, Mission, NBO CV, and any
   text-heavy page added later. Opt in with <body class="sub textpage">.

   It is deliberately a SHORT set of rules, because the page's whole argument is that a reading
   page needs almost nothing (client, 2026-07-30: "Manifesto stranu bih još pojednostavio").

   ONE BODY VOICE.  Every sentence is the same face, size, weight and colour — .read, and nothing
     else. The page has exactly two other voices: the title (.lead) and the micro-caps .eyebrow
     that names a section. What used to be three more sizes — a display lede, a lifted-out
     emphasis line, a bigger question — is gone: an opening sentence is emphatic because it is
     first, not because it is large.
   ONE COLUMN.  Every text block shares one left edge, cols 3/10, including the title. The photo
     row is the only thing on the page allowed to leave it.
   ONE RHYTHM, two tiers.  --gap-b separates blocks (prose → photo, prose → a new labelled
     section). --gap-t, on .cont, joins two paragraphs of the SAME argument, where --gap-b's 59px
     read as a change of subject. A row contributes no padding of its own, so a join is never the
     sum of two sections' padding.
   TWO ANIMATIONS, and no more.  The TITLE arrives on load — alone, so the page settles once and
     then holds still while you read. The FOOTER arrives on scroll, which is the only reason the
     page is not wholly inert. Nothing between them moves, the opening paragraph included: every
     paragraph carries .reveal-opt, which is inert until ?reveal=all switches the scroll-in on for
     a side-by-side look. Photos never move either — no reveal, and no [data-par]: parallax is a
     homepage device, and on a page you read it drifts the one thing you are looking at.
   ──────────────────────────────────────────────────────────────────────────────────────────── */
body.textpage{--gap-t:clamp(1.7rem,3.4vh,3rem);      /* prose → prose, the same argument continuing */
              --gap-b:clamp(3rem,6.6vh,5.2rem)}      /* block → block: prose to a photo, to a new section */

/* the one column band — the page title sits on it too, so the title's left edge IS the text's */
.textpage .phead .body,.textpage .tcol{grid-column:3/10}

/* the one rhythm */
.textpage .erow{padding-block:0}
.textpage .erow + .erow{margin-top:var(--gap-b)}
/* .cont — "this row continues the argument above it". Same class count as the rule it overrides,
   so it has to stay AFTER it to win. A row that opens a new labelled section is never .cont. */
.textpage .erow.cont{margin-top:var(--gap-t)}

/* ── THE FOOTER SITS ON THE BOTTOM, EVEN WHEN THE PAGE IS SHORT ──────────────────────────────
   Donate is 900px of content in a 900px viewport and left 203px of bare paper UNDER the footer;
   Board (1048), Team (1125), Discography (1503) and NBO CV (1507) do the same on any tall monitor.
   A footer floating mid-screen with blank paper below it reads as a page that failed to finish
   loading rather than as a page that ended.
   body.sub — NOT body: index.html is not a sub-page, carries no .sitefoot, and its hero/panel
   composition must not be turned into flex items. On a long page margin-top:auto resolves to 0, so
   this is inert on all ten pages that already overflow the viewport.
   ⚠️ svh, not vh. On mobile vh is the LARGEST viewport (browser chrome retracted), so 100vh
   overshoots by the toolbar's height and puts a scrollbar on a page that actually fits.
   ⚠️ AND `width:100%` on the sections is LOAD-BEARING, for a reason that is easy to miss:
   `.wrap` carries `margin-inline:auto`, and an AUTO MARGIN IN THE CROSS AXIS DISABLES
   `align-self:stretch`. Without this line every section stopped filling the body and shrink-wrapped
   to its own content, which moved the one text edge off 322px on all ten pages and changed their
   heights. Scoped to `> .wrap` and NOT `> *`: `body.sub > *` would outrank `.pdlg` on specificity
   and blow up Projects' modal width. */
body.sub{display:flex;flex-direction:column;min-height:100svh}
body.sub > .wrap{width:100%}
body.sub .sitefoot{margin-top:auto}

/* ── THE THREE SHORT LINES, AND WHY NONE OF THEM IS A SECOND VOICE ─────────────────────────
   A text page needs three kinds of short line that are not paragraphs: a SECTION NAME, a QUOTE
   ATTRIBUTION, and — on the list pages — a CREDIT above a person's name. All three are plain
   prose, and ONE mechanism tells them apart: a short line welded to its block by a gap tighter
   than any paragraph join, plus, for the credit, ink dropped to the page's soft grey.
     ink,  welded DOWN → it names what follows        → .slabel, a section heading
     grey, welded DOWN → it credits the name below it → .qual, a role on Team / Board
     ink,  welded UP   → it answers what precedes     → .cite, an attribution
   The grey is the page's own voice dimmed, not a new style — and it is what lets a column of
   names form one unbroken vertical edge the eye can run down, which is the whole job of a list
   page. Nothing else is added: no rule, no hairline, no display size, no tracked caps.

   The first two were set in the .eyebrow micro-caps — and that was a real failure,
   because the two jobs then rendered byte-identically: "ERHARD BUSEK" under a quoted passage
   looked exactly like "CULTURAL CONTEXT" over a new section, so an attribution read as a heading
   and nothing at all marked the passage above it as someone else's words (client, 2026-07-30).
   Both are now plain prose — same face, size, weight and ink as every sentence on the page, which
   is the rule this template exists to hold. What separates them is POSITION, made unambiguous by
   ONE gap: a short line welded to its block by a gap TIGHTER than any paragraph join on the page.
     welded DOWNWARD → it names what follows      → a section heading
     welded UPWARD   → it answers what precedes   → an attribution
   .5em is below the .85em list join and well below the 1.15em paragraph join, so a welded line can
   never be misread as a stray one-line paragraph. The attribution also keeps the em-dash the source
   writes it with (mission.md:27) — that dash is the only mark the client's own page gives the quote,
   and it is what tells a reader at a glance which of the two kinds of short line this one is. */
/* THE SECTION HEADING takes the site's TITLE voice — Inter Tight 700 uppercase, the same face and
   weight as the page title and every chapter title on Projects — but at the PROSE SIZE. A different
   style, not a bigger one (client, 2026-07-30: "a bit of a different font style than the rest, it
   should stand out a little bit"). That makes it the gentlest possible step in a hierarchy the site
   already has: PROJECTS 77px → 2025 46px → TURNING POINT TOUR 32px → a section heading at 17px.
   It has to differ from the CREDIT, not just from prose: both are short lines that name something, so
   if they shared a treatment we would be back to the collision that made "ERHARD BUSEK" read as a
   heading. They now differ on every axis — title face, full ink, prose size, caps VERSUS the credit's
   micro-caps grey. The hairline still separates sections; this labels them.
   Tracking is .04em rather than .lead's .005em: uppercase at 17px sets far too tight at a display
   face's tracking, which is calibrated for 46–77px. */
.textpage .slabel{margin-bottom:.5em;font-family:"Inter Tight",var(--sans);font-weight:700;
  text-transform:uppercase;letter-spacing:.04em}
/* `+ *`, not `+ p`: a heading welds down to whatever opens its section — a paragraph on Mission,
   a name list on Partners. Same 8px either way. */
.textpage .read .slabel + *{margin-top:0}
/* THE CREDIT sits UNDER the name and is the one short line that is also SMALLER (client, 2026-07-30:
   above the name and at full size it competed with the name instead of serving it, and on Board it
   made a third stacked line between the section heading and the person). Under and smaller, it is
   unmistakably subordinate — you read the name, then what they do.
   It carries .eyebrow IN THE MARKUP rather than restating that recipe here, so the site keeps ONE
   micro-caps label and this cannot drift from it. That recipe is already doing exactly this job
   elsewhere: `.stmt .who` names the speaker under a composer statement. It is the only place a text
   page uses a second size, and it buys the thing a list page needs most — names as the one thing at
   full size, so the column of them reads as the content and the roles as annotation. */
.textpage .qual,.textpage .cite{margin-top:.5em}   /* both welded UP: they answer the line above */

/* a list of one-line statements — the manifesto's twelve questions. Identical face, size and
   weight to prose; only the line gap says "list" rather than "paragraph".
   The list is a <div>, so .read's `p + p` join does NOT reach it and the first question would sit
   flush against the sentence that introduces it, reading as that sentence's last line. It takes the
   paragraph join instead — wider than the .85em between questions, so where the list starts is
   unambiguous while every gap on the page stays one of the values the page already uses. */
/* `*:not(.slabel) +` and not a bare `.qlist`: a list that OPENS a section must take the heading's
   own 8px weld, not this join, and the two rules would otherwise tie on specificity and be decided
   by their order in this file — which is exactly the kind of fragility to write out rather than
   inherit. The :not() lifts this rule above the tie and excludes the case it shouldn't win. */
.textpage .read *:not(.slabel) + .qlist{margin-top:1.15em}
.textpage .qlist p + p{margin-top:.85em}
/* ── PARTNERS' NAME LISTS: TWO COLUMNS, ALIGNED TO THE SHEET ABOVE ────────────────────────────
   Client, 2026-08-05: "sami nazivi organizacija da budu manji font. I da budu u dve kolone
   uskladjeno sa logoima gore". They were full prose size in ONE column running the whole 10-track
   content width, with nothing tying them to the 600px sheet they sit under — so a season's logos and
   its names read as two unrelated blocks.
   ⚠️ THE SIZE IS `.roster`'s .98rem, NOT A NEW VALUE. That is already this site's "a list of names"
   voice (Musicians' 161-city roster), and `.roster.cols` is already its two-column form — so this
   invents no type size and no new column recipe, which is the doctrine the page leans on everywhere
   else. 600px is `.textpage .sheet`'s own fixed width, so the list's right edge lands on the sheet's.
   ⚠️ Scoped to `.cgroup` — `.qlist` also sets Manifesto's twelve questions, which are prose at the
   reading size and must not shrink or split. Only Partners uses `.cgroup`. */
.textpage .cgroup .qlist{max-width:600px;columns:2;column-gap:clamp(1.6rem,4vw,3.4rem);
  font-size:.98rem;line-height:1.9}
/* a column break between a name and its city would read as two entries */
.textpage .cgroup .qlist p{break-inside:avoid}
/* ⚠️ AND THE PER-ITEM MARGIN GOES, because `.roster`'s 1.9 line-height already IS the separation.
   Keeping `.qlist p + p`'s .85em on top of it double-spaced the list — ~43px a row, so ten names
   occupied more height in two columns than they had in one, which is the opposite of the ask. */
.textpage .cgroup .qlist p + p{margin-top:0}

/* ── THE CLICKABLE MONO LINE ─────────────────────────────────────────────────────────────────
   Shared: Projects' "Project Details" control and Donate's PayPal submit are the same object —
   a line of mono you can press. Full ink plus a STANDING hairline underline, because a soft
   grey label with only a hover underline read as a second footnote and nothing said it could be
   clicked. Grey stays the passive voice everywhere on this site; hover darkens the rule.
   It carries no button chrome at all, so a <button> and an <a> look identical. */
.lnbtn{display:block;margin-top:clamp(1.6rem,4vh,2.6rem);background:none;border:0;padding:0;
  font:inherit;color:inherit;text-align:left;cursor:pointer;appearance:none;-webkit-appearance:none}
/* ONE recipe for both controls, and it is deliberately NOT the footnote's. The commission note sits
   directly above this button in the same mono at the same size in --ink-soft, so a soft label with
   only a hover underline read as a SECOND FOOTNOTE — nothing said it could be clicked. Full ink plus
   a standing hairline underline is this page's own "you can click this" grammar (the year index marks
   its current tour the same way), and it keeps grey as the passive voice. Hover darkens the rule. */
.lnlbl,.pdx{font-family:var(--mono);font-size:.78rem;letter-spacing:.05em;color:var(--ink);
  padding-bottom:.15rem;border-bottom:1px solid var(--hair);transition:border-color .3s}
.pdx{flex:none;margin-top:.2rem;background:none;border-width:0 0 1px;border-style:solid;
  border-color:var(--hair);cursor:pointer;appearance:none;-webkit-appearance:none}
.lnbtn:hover .lnlbl,.pdx:hover{border-bottom-color:currentColor}
.lnbtn:focus-visible,.pdx:focus-visible{outline:1px solid var(--ink);outline-offset:.4rem}
/* ON A CREDIT ROW (Team / Board's Bio control) the base margin-top is WRONG, and measurably so.
   .lnbtn is tuned for Projects' facts column, where 26–42px of air separates it from a block of
   metadata. Inside a two-line credit that same gap lands within a few px of --gap-t (31px), the
   rhythm BETWEEN two people on .erow.cont — so the control reads as belonging to the next name as
   much as to its own. 14px is unambiguous: wider than the .5em weld that .qual and .slabel use to
   say "this annotates the line above" (a Bio is an affordance, not an annotation), and less than
   half the distance to the next person. */
.lnbtn.weld{margin-top:.88rem}

/* ── THE DONATE CTA ──────────────────────────────────────────────────────────────────────────
   The one box in this file, and it is the box c-base.css ALREADY sanctions — the header's .donate
   chip, whose own comment reads "Donate = the page's one call-to-action — hairline box sets it
   apart from wayfinding". This is that exception at PAGE scale, not a second one.
   Donate stops sharing .lnbtn's underlined-mono-line recipe because the two do different jobs: a
   "Project Details" line is a disclosure you may ignore, while this is the only action the page
   exists to produce, and an underlined line of mono gave the page no visible action at all.
   Same type recipe as the header chip (sans, .82rem, 500, uppercase, .18em) so the promotion adds
   NO new vocabulary; square, because the tweaks gear is the site's only radius.
   Hover FILLS rather than fading opacity like the header's items: a chip among six can afford to
   fade, but a lone CTA fading on hover reads as going away instead of as responding.
   ?cta=solid shows the filled reading side by side — kept behind a flag because starting filled
   would be a second departure from this file's "no box, no ornament" rule, not just a louder one. */
.cta{display:inline-block;margin-top:clamp(1.6rem,4vh,2.6rem);
  font-family:var(--sans);font-size:.82rem;font-weight:500;
  text-transform:uppercase;letter-spacing:.18em;color:var(--ink);
  padding:1.05rem 2.2rem;border:1px solid var(--ink);background:none;
  cursor:pointer;appearance:none;-webkit-appearance:none;transition:background .3s,color .3s}
.cta:hover{background:var(--ink);color:var(--paper)}
.cta:focus-visible{outline:1px solid var(--ink);outline-offset:.4rem}
body.cta-solid .cta{background:var(--ink);color:var(--paper)}
body.cta-solid .cta:hover{background:none;color:var(--ink)}

/* The CTA needs clearance from the footer — an action pressed against the socials row reads as part
   of the page furniture rather than as the thing the page is asking for. Its own property, not a
   third template tier: every other text page ends in prose, which needs nothing. This also holds
   the gap on a SHORT viewport, where the sticky footer above has no slack left to give. */
.textpage .cta-row{padding-bottom:clamp(2.5rem,6vh,4.5rem)}

/* ── THE STORE BADGE ROW ─────────────────────────────────────────────────────────────────────
   Six vendor badges are the ONE place this site shows another brand's artwork — the client's own
   badges were chosen over text links. They need a row of their own because c-base.css sets every
   img to display:block site-wide, so without this the six stack one per line.
   Vendored locally, like the photos: hotlinked they were http:// on an https site, which browsers
   block as mixed content, and they sat in the client's theme folder — gone the moment they
   reskin. Sized to 110px display, 2x for retina, and nothing larger. */
/* THREE max-content columns, the same choice the year index makes — and for the same reason: it needs
   no hand-placed spans and no width arithmetic. Six badges at 110px plus gaps come to 805px against a
   692px reading column, so a flex row wrapped 5+1, which reads as an accident. 3×2 reads as a block. */
.textpage .badges{display:grid;grid-template-columns:repeat(3,max-content);align-items:center;
  gap:clamp(.9rem,2.2vh,1.6rem) clamp(.9rem,2.2vw,1.8rem);margin-top:clamp(1.6rem,3.5vh,2.6rem)}
.textpage .badges a{display:block;transition:opacity .3s}
.textpage .badges a:hover{opacity:.62}
.textpage .badges img{width:110px;height:auto}
/* the album cover — a mounted print object, capped at its own display size */
.textpage .cover{max-width:20rem}
.textpage .cover .img{aspect-ratio:1;background-size:cover}

/* ── THE YEAR INDEX ─────────────────────────────────────────────────────────────────────────
   Shared, because two pages now carry it: Projects and Photos. Only its LOOK lives here — each page
   places it in its own rail, because those rails are different widths. Three max-content columns, so
   adding a year needs no edit anywhere; the active state is painted in c-base.js.
   Mono, soft grey going ink: this is the page's "thing you can click", not a heading. */
/* ⚠ justify-items:start so an entry is only as wide as its own label. Without it a grid item
   STRETCHES to its column, and .active's underline then runs past the end of the word (client,
   2026-08-03). Invisible on Projects and Photos, whose entries are all four-digit years of equal
   width — it only showed on Partners, where "2012 & 2013" and "Initiation" set the column and made
   every shorter year's underline too long. An underline belongs to its word, not to its column. */
.yrnav{display:none;grid-template-columns:repeat(3,max-content);justify-items:start;
  gap:.45rem clamp(.6rem,1.5vw,1.1rem)}
.yrnav a{font-family:var(--mono);font-size:.82rem;letter-spacing:.05em;color:var(--ink-soft);
  padding-bottom:.15rem;border-bottom:1px solid transparent;transition:color .3s,border-color .3s}
.yrnav a:hover{color:var(--ink)}
/* the two-year chapter reads "2012 & 2013" on one line here and stacks on a phone — `.yy` goes from
   inline to block in the 880px block, so the ampersand and the line break never both show */
.yrnav a.active{color:var(--ink);font-weight:500;border-bottom-color:currentColor}
/* Partners' rail — the SAME index as Projects and Photos (client, 2026-08-03: "let's have that
   navigation also the same as we have in the projects and in the gallery"), and it fills what was an
   open question: the chapter rules used to run the page's whole width with nothing under their left
   third. Now the rail holds the index and the chapters sit in their own column, so each chapter's
   rule spans the CONTENT rather than the page — "not full width", as asked.
   ⚠️ ONE index for the whole page, not one per chapter (client: "just have one of those elements,
   not in every section, one element that is sticky"). Projects repeats its index inside every
   chapter because each chapter is its own grid, so a sticky rail could only travel within one of
   them. Here the rail is a SIBLING of all the chapters and stretches beside them, so a single
   element sticks down the page on its own — less markup, and the years change as you scroll because
   c-base.js's painter observes `.chap[id]` and simply has one copy to light instead of five.
   ⚠️ ONE COLUMN, not Projects' three: that grid folds fourteen years into a 177px rail. Five
   entries, two of them words ("2012 & 2013", "Initiation"), need the width more than the rows.
   ⚠️ Scoped to .chaps so it cannot reach Photos' own .prail (which spans an explicit 2-row grid) or
   Projects, whose index is gated behind body.rail-index and toggled by ?rail=monument — Projects is
   `body.sub panels-dark`, never .textpage. */
.textpage .chaps{align-items:start}
.textpage .chaps > .prail{grid-column:1/3;grid-row:1;align-self:stretch}
.textpage .chaps > .prail .yrnav{display:grid;grid-template-columns:max-content;
  position:sticky;top:6.6rem}
.textpage .chaps > .cbody{grid-column:3/13}
/* a season's two groups — partners, then friends. No rule on either (client: "we don't need
   underlines under the years in that right section"): the chapter's own full-ink rule already
   marks the season, and a hairline under the year read as a second underline belonging to it.
   The block gap alone separates the two groups, which is what the rhythm is for. */
.textpage .cgroup + .cgroup{margin-top:calc(var(--gap-b) + 20px)}
/* Partners' group labels only (client, 2026-08-15): 50% over the prose size, and a wider gap to
   their content — 20px against the site's .5em (~8px) weld. Scoped to .cgroup so Mission's and the
   other textpages' section headings keep the prose-size weld the 2026-07-30 decision set. */
.textpage .cgroup > .slabel{font-size:1.75em;margin-bottom:20px}
/* the season heading and what it introduces — 22px sat the logo sheet almost against the heading's
   baseline (client, 2026-08-05: "a bit too close to the logos"), and `--gap-t`'s ~34px was still too
   tight (client, 2026-08-06: "i izmedju godine i sponzora").
   ⚠️ THE INVARIANT IS THE vh TERM, NOT THE PIXEL VALUE. 5vh sits strictly below `--gap-b`'s 6.6vh, so
   this gap can never overtake the break BETWEEN two groups at any window height — which is the whole
   reason the heading still reads as belonging to the logos under it rather than floating between them
   and the rule above. It lands ~50px against the block break's ~66px. A fixed third token would hold
   that relationship at one viewport and lose it at others; a smaller fraction of the same axis holds
   it everywhere. ⚠️ Do not "simplify" this to `var(--gap-b)` — equal gaps make the year read as one
   more item in the list instead of the head of it. */
.textpage .chap > .lead + .cgroup{margin-top:clamp(2.4rem,5vh,4rem)}
/* ⚠ NO RULE ABOVE THE FIRST YEAR (client, 2026-08-03). A chapter rule exists to SPLIT one season from
   the one before it, so the first has nothing to be split from — over it the rule read as a lid on the
   page rather than a divider, and it sat immediately under the page title doing that title's job
   twice. Projects has always drawn the same distinction (`.chaps > .chap:first-child`). */
/* ⚠️ A SEASON MUST OPEN WIDER THAN THE GROUPS INSIDE IT (client, 2026-08-05: "vise prostora izmedju
   godina. Sada je sve nekako nabijeno"). The gap was INVERTED and that is the whole reason the page
   read as crammed: `.chap`'s shared `--pad-s` gave two seasons 38–72px, while `.cgroup + .cgroup`
   gives Partners → Friends *within one season* `--gap-b`'s 48–83px. So the smaller break was the
   more important one, and five seasons ran together as a single column of names. This clears
   `--gap-b` at both ends of the clamp; with `.chap`'s own 19.2px padding-top a new year now starts
   90–137px below the last line of the one before it. ⚠️ Asked again and widened again (client,
   2026-08-06: "malo vise prostora izmedju godina"): 12vh puts the upper gap at ~120px, so a season
   now opens ~186px below the last line of the one before it (120 above the rule + `--gap-b` below).
   The two ratios that make the page legible both still hold, and they hold by construction:
   12vh > 6.6vh keeps the rule closer to the season it introduces than to the one it closes, and
   6.6vh > 5vh keeps a season's own break bigger than the gap under its heading.
   ⚠️ Not a change to `.chap` itself — that rule is Projects' too, and Projects is `body.sub
   panels-dark`, never `.textpage`, so scoping it here is what keeps fourteen chapters untouched. */
.textpage .cbody > .chap{margin-top:clamp(6rem,12vh,10rem)}
/* ⚠️ AND THE RULE HAS TO SIT BETWEEN THE TWO SEASONS, NOT AGAINST THE LOWER ONE (client, 2026-08-05:
   "it still looks like the next year is too close to the separator line"). Widening the margin alone
   fixed the wrong half: it bought 81px ABOVE the rule while `.chap`'s base `padding-top:1.2rem` left
   19px BELOW it, so the divider hung four times closer to the season it was introducing than to the
   one it was closing — and a rule that close to a heading reads as that heading's underline.
   `--gap-b` puts the lower gap at 48–83px against the upper 70–118px: clearly the smaller of the two,
   so the rule still belongs downward, but no longer welded to the year.
   ⚠️ `:not(:first-child)` — the padding exists to CLEAR THE RULE, and the first season has none, so
   on 2021 it would be 59px of nothing under the page title. */
.textpage .cbody > .chap:not(:first-child){padding-top:var(--gap-b)}
.textpage .cbody > .chap:first-child{border-top:none;margin-top:0}

/* ── ONE RAIL PER PAGE ────────────────────────────────────────────────────────────────────────
   Photos' rail, and the shape all three indexed pages now share (client, 2026-08-03: "only one rail
   is sticky on the left, not in every section — it's actually better").
   THE PATTERN, and why it is this and not something simpler: `position:sticky` only travels inside
   its own PARENT's box. A rail that lives in a section can therefore never outlast that section, which
   is why Projects and Photos both used to repeat the whole index in every one of them — fourteen and
   eight copies of the same markup. So the rail becomes a SIBLING of all the sections and the two of
   them are the only children of a 12-column grid: rail in the left band, everything else in one
   content column beside it. `align-self:stretch` then makes the rail as tall as the whole content, and
   a single sticky element rides the page. c-base.js needed no change on any page — its painter
   observes `.chap[id], .yrsec[id]` and simply has one copy to light instead of fourteen.
   ⚠️ THE CONTENT COLUMN RE-DIVIDES. Its own inner grids see a NARROWER 12 tracks, so anything that
   named a page column inside it has to be restated — see .gbody / .cbody below. That is the whole
   cost of this pattern, and it is why each page states its overrides right here rather than inheriting
   a guess.
   ⚠️ It also SHORTENS THE RULES, which is the other half of what was asked: a chapter rule drawn on
   the content column spans the content, not the page. */
.textpage .prail{align-self:stretch}
.textpage .prail .yrnav{display:grid;position:sticky;top:6.6rem}

/* PHOTOS — rail in cols 1/3, the gallery in 3/13. Inside .gbody the year label and the photo groups
   restate their columns against the narrower grid: both start at its col 1, which IS the page's col 3. */
.textpage .gal > .prail{grid-column:1/3}
.textpage .gal > .gbody{grid-column:3/13}
.textpage .gbody .yrsec .tcol{grid-column:1/8}
.textpage .gbody .pgroup{grid-column:1/13}
/* .grid sets row-gap:0, so the photo groups would otherwise sit flush under the year label */
.textpage .yrsec .pgroup{margin-top:clamp(1.6rem,3.5vh,2.6rem)}

/* ── THE PHOTO ARCHIVE ──────────────────────────────────────────────────────────────────────
   Two levels, and NEITHER adds a type voice, because both already exist:
     YEAR    → .slabel on a .sect — the title voice at prose size, under a hairline. Exactly what
               Board and Partners use for a section.
     CONCERT → .eyebrow welded DOWN over its grid. Projects already does this (`.eyebrow blk` over
               its Concerts and Programme lists), so a grey micro-caps label naming the group below
               it is established grammar, not a new invention.
   Both come from the CLIENT'S own structure — their page is organised year → concert → photos — so
   the page is a transcription of their archive, not a scheme imposed on it.
   Square cells: their gallery's own tiles are square crops, so the grid matches what their page
   shows. Four across at ~228px is a contact sheet, which is the honest form for 207 uncaptioned
   photos — the archive IS the argument, and 12 years of it should read as a body of work. */
.textpage .pgroup{grid-column:3/13}
.textpage .gl{margin-bottom:.9rem}
.textpage .pgrid{display:grid;grid-template-columns:repeat(4,1fr);gap:clamp(.5rem,1.1vw,1rem)}
/* ⚠️ `picture` must be display:block. It is inline by default, so as a grid item it sizes to its
   content rather than to the track, the img's width:100% then resolves against the wrong box, and
   every cell renders oversized — it took the page from 19,352px to 42,500px tall. Both selectors are
   needed: a photo with no WebP ships as a bare img and is a grid item itself. */
.textpage .pgrid picture{display:block;min-width:0}
/* ⚠️ The link wrap (2026-08-14, tiles with a large source) makes an `a` the grid item — same trap
   as `picture` above, so it takes the same treatment. Kirby-only: the static site never links its
   tiles, and this file legitimately diverges from prototypes/ sub.css after cutover. */
.textpage .pgrid a.plink{display:block;min-width:0}
/* ⚠️ `height:auto` is REQUIRED, not tidiness. The tiles carry width/height attributes so the browser
   can reserve the space before the file arrives (207 lazy images would otherwise reflow the page as
   they land). But that height attribute is a presentational hint of `height:600px`, and `aspect-ratio`
   only governs when one dimension is auto — so without this the cells rendered 238×600 and the page
   was 42,500px tall instead of 19,352px. */
.textpage .pgrid img{display:block;width:100%;height:auto;aspect-ratio:1;object-fit:cover;
  background:#e8e8e6}
.textpage .pgrid + .gl{margin-top:clamp(2rem,4.5vh,3.2rem)}

/* ── THE VIDEO GRID ─────────────────────────────────────────────────────────────────────────
   Spans from the TEXT EDGE to the right edge, so the grid's left edge IS the page's one text edge and
   the title above it lines up with the first tile. Two across, and that is not a taste call: nine of
   the thirteen videos are old enough that the largest 16:9 still YouTube holds for them is 640px wide
   and one caps at 480px, so a ~486px tile is the honest maximum. Three across would win a row back and
   spend the picture to get it.

   A TILE IS A FACADE, NOT A LIVE EMBED. Thirteen iframes would load thirteen third-party players on
   open and paint the entire grid in YouTube's chrome — red button, watch-later, channel bug — which
   would be far and away the loudest thing on this site. Pressing a tile replaces it with the real
   embed, autoplaying, so it *is* an embedded video; just not thirteen at once. See c-base.js.
   The still comes from the video itself. The 4:3 stills are letterboxed 16:9 pictures, and `cover` on
   a 16:9 box crops off almost exactly the black bars, so no per-video framing is needed. */
.textpage .vgrid{grid-column:3/13;display:grid;grid-template-columns:repeat(2,1fr);
  column-gap:clamp(1rem,2vw,2rem);row-gap:clamp(2.4rem,5.5vh,4rem)}
.textpage .vtile{position:relative;display:block;width:100%;padding:0;border:0;cursor:pointer;
  aspect-ratio:16/9;background-color:#e8e8e6;background-size:cover;background-position:center}
.textpage .vtile::after{content:"";position:absolute;inset:0;background:var(--ink);opacity:0;
  transition:opacity .4s}
.textpage .vtile:hover::after{opacity:.14}
/* the one glyph on the page: a play triangle, no circle and no box around it. The site already uses a
   functional arrow (.back's ←), so a functional triangle is in grammar where a decorative one is not.
   Paper-white with a shadow because it has to hold over a dark still and a light one alike. */
.textpage .vtile .pl{position:absolute;left:50%;top:50%;z-index:1;
  transform:translate(-50%,-50%);width:0;height:0;border-style:solid;
  border-width:.8rem 0 .8rem 1.38rem;border-color:transparent transparent transparent var(--paper);
  filter:drop-shadow(0 1px 7px rgba(0,0,0,.5));opacity:.92;transition:opacity .3s}
.textpage .vtile:hover .pl{opacity:1}
.textpage .vtile:focus-visible{outline:1px solid var(--ink);outline-offset:.4rem}
.textpage .vitem .vt{margin-top:.9rem}
.textpage .vitem:hover .vt{text-decoration:underline;text-underline-offset:.22em}
/* the player itself, inside the modal */
.vframe{display:block;width:100%;height:100%;border:0}

/* ── THE VIDEO MODAL — .pdlg, WIDENED FOR A PLAYER ───────────────────────────────────────────────
   Client, 2026-08-05: videos open like Projects' descriptions do. Only four things differ from the
   base modal, because everything else about it should be the same object.
   ⚠️ WIDER THAN 736px, and that is the one real departure. A dialog sized for a 592px reading measure
   puts a 16:9 player at 416px tall, which is smaller than the tile you clicked to enlarge it. 1040px
   gives ~585px of picture and still leaves the 94vw guard for narrow screens.
   ⚠️ THE BODY KEEPS `overflow:auto` FROM THE BASE. A short viewport (a laptop at 700px, or a phone
   in landscape) cannot fit head + 16:9 + controls inside `max-height:88svh`, and with the scroll
   removed the controls would simply be cut off with no way to reach them.
   ⚠️ `padding:0` on the body so the picture goes edge to edge — the padding exists for prose. The
   nav row restates it so the two buttons keep the head's own inset instead of touching the corners.
   ⚠️ The slot carries the ratio and a BLACK ground: an embed paints its own black anyway, and
   without it the box flashes paper-white between one video and the next. */
.pdlg.vdlg{width:min(94vw,1040px)}
.vdlg .pdbody{padding:0}
.vdlg .vslot{aspect-ratio:16/9;background:#000}
.vdlg .vnav{display:flex;justify-content:space-between;gap:1rem;
  padding:clamp(1rem,2.4vh,1.4rem) clamp(1.5rem,4vw,4.5rem) clamp(1.4rem,3vh,2rem)}
/* ⚠️ AND `.lnbtn`'s OWN margin-top has to go, the same way `.lnbtn.weld` kills it on a credit row.
   The base 26–42px is tuned for Projects' facts column, where the control follows a block of
   metadata. In a nav row the row's padding already sets the distance, so the margin was pure dead
   paper — 36px of it under the player, which read as the modal having a blank strip at the bottom. */
.vdlg .vnav .lnbtn{margin-top:0}
/* ── OUR CHROME FADES WHILE YOU WATCH ────────────────────────────────────────────────────────
   The head and the prev/next row go after a few idle seconds so the picture is the only thing on
   screen; c-base.js decides when (see "THE VIDEO MODAL"). ⚠️ `pointer-events:none` goes with the
   opacity — a control faded to nothing but still clickable is worse than one that never hid, since
   the click lands on something invisible. ⚠️ The DIALOG's own background must not fade with them:
   only the two chrome rows carry the class, so the paper behind the player stays put and the box
   does not appear to change size. Off under reduced motion, in the JS rather than here, because
   what is being suppressed is the hiding — not a transition. */
.vdlg .pdhead,.vdlg .vnav{transition:opacity .35s ease}
.vdlg.chrome-off .pdhead,.vdlg.chrome-off .vnav{opacity:0;pointer-events:none}

/* A SECTION RULE — what tells a heading from a list item on a page where every line is short.
   The heading itself stays plain prose. On Mission a heading is legible without help because it sits
   over a PARAGRAPH: a short line against a block of text reads as a heading by contrast alone. On a
   list page every neighbour is also a short line, that contrast is gone, and "NBO Board" read as the
   first name in the list (client, 2026-07-30). So the mark is STRUCTURAL rather than typographic —
   the direction's own hairline, the same device that already separates rows and chapters. No new type
   voice is introduced anywhere, which is the whole point of solving it this way.
   It spans the text column, so the rule starts exactly on the page's one edge. The block gap sits
   ABOVE it (59px) and the padding below it (24px), so the rule is nearer the heading it introduces
   than the section it closes — a divider has to belong to what follows it. */
.textpage .tcol.sect{border-top:1px solid var(--hair);padding-top:clamp(1.5rem,3.2vh,2.4rem)}
/* and once a rule marks the section, the heading STOPS welding: the 8px weld exists to bind a heading
   to what follows when nothing else does, and at 8px against a list's own 14px joins it read as
   attached to the first name rather than to the list. The rule does the binding now, so the heading
   takes the ordinary paragraph join and sits clear of its list. */
.textpage .tcol.sect .slabel{margin-bottom:1.15em}

/* (A `.tcol.chapt` full-ink rule lived here from 2026-08-03 to give Partners a rank above .slabel.
   It lasted one afternoon: the page was then reorganised BY YEAR, so the year's own .chap rule does
   that job and nothing referenced .chapt any more. Deleted rather than left dead — but the reasoning
   is worth keeping, because it is the rule the site now uses in two places: a rank is bought with the
   RULE'S WEIGHT, never with a second type size. Full ink opens a chapter, a hairline separates a
   section inside one, an 8px weld binds a label to its content. Three ranks, one size.) */

/* A SECOND DISPLAY TITLE MID-PAGE — Board's "Advisory Council", and only that (client, 2026-08-03:
   "staviti veliko Advisory Council kao što je gore Board"). Board holds two bodies that are not one
   list with two labels, and the client's own site titles them as peers. So the second one takes the
   PAGE TITLE's voice — .lead, at .hang-l's own x, identical to the h1 above it — and the first group
   needs no label at all, because the page title already names it. That is why "NBO Board" came off:
   with the two groups ranked as peers, a small grey-weight label over the first one ranked it BELOW
   the second and read as a third kind of heading.
   ⚠ It takes the band gap, not the row gap. A 4.8rem monument under a 14px credit needs the space a
   NEW PAGE SECTION gets, and --gap-b (the block gap) is calibrated for prose-to-photo. */
.textpage .gtitle{margin-top:var(--pad)}
.textpage .gtitle + .erow{margin-top:clamp(2rem,4.5vh,3.4rem)}
/* ⚠ WIDER THAN THE TEXT COLUMN, and that is the whole point of this line. A display title is not
   prose and must not be measured like it: "ADVISORY COUNCIL" needs ~740px at the 4.8rem size and
   .tcol's 3/10 offers ~735 from the hang, so it broke to two lines by about five pixels (client,
   2026-08-03: "doesn't have to be in two lines"). Running it to col 13 gives the line room to set
   as one, exactly as the h1 above it does. It still wraps below 880px, which is correct — at a
   phone's 343px no 38px display line fits, and forcing nowrap there would overflow the page. */
.textpage .gtitle .tcol{grid-column:3/13}

/* A SUPPLIED SHEET — a graphic that is the content, not an illustration of it. Partners' two
   supporter composites are the only ones: every brand on them exists only as pixels, so there is no
   text version to set and nothing to lay out around them.
   ⚠ 600px EXACTLY, AT EVERY VIEWPORT — not a max-width, and this is the whole trick. Three things
   forced it, and only the third was visible in a screenshot rather than in a number:
     1. The smaller sheet is a 600px ORIGINAL. A width:100% in the 692px column stretched it 8% and
        softened every logo on it.
     2. Each sheet carries its own baked-in rule spanning its full width, so at their own widths the
        two rules stacked at DIFFERENT lengths and the pair read as ragged. One width, one right edge
        — the page's second vertical edge after the text edge.
     3. A percentage width made them ILLEGIBLE ON A PHONE. These are fixed-layout rasters: at 342px
        the second sheet's four-column grid put "Embassy of Switzerland in the Republic of Serbia" at
        about 4px tall. Small is acceptable, unreadable is not, and no amount of scaling fixes a
        bitmap whose layout cannot reflow.
   So the sheet is ALWAYS exactly 600px and is never resampled up anywhere. The 1200px one downscales
   by a clean half (sharp, and its logos are drawn twice as large, so the two land at comparable
   scale); the 600px one renders 1:1. Narrow screens PAN it inside .sheetwrap instead of shrinking it,
   the ordinary treatment for a diagram that cannot reflow. That scroller is why the page still has no
   horizontal overflow at 390px — the sheet overflows its own box, not the document.
   ⚠ MULTIPLY IS LOAD-BEARING, not an effect. Both sheets have a pure-white (255,255,255) ground and
   the paper is #fbfbfa, so without it each one reads as a white BOX sitting on the page — the site
   has no cards, and two of them under the page title looked like a rendering fault. Multiplying
   white against the paper returns the paper exactly, so the ground vanishes and the logos sit
   directly on the page. The cost is ~1.6% darkening of the coloured marks (the EU and US flags),
   which is below the threshold of visibility; the gain is that these stop looking pasted on.
   It also depends on the sheets being TRIMMED to their ink — see pull-supporter-sheets.py, because
   once white is invisible any white margin inside the file becomes phantom page rhythm. */
.textpage .sheetwrap{overflow-x:auto;overscroll-behavior-x:contain}
/* ⚠ max-width:none is REQUIRED, not redundant — c-base.css sets `img{max-width:100%}` globally, which
   silently caps width:600px at the column's width and shrinks the sheet right back into illegibility
   on a phone. The fixed width does nothing without it. */
.textpage .sheet{display:block;width:600px;max-width:none;height:auto;mix-blend-mode:multiply}

/* A GRID OF INDIVIDUAL LOGOS — 2023, 2024 and 2025 (pull-leaflet-logos.py).
   ⚠ THE OPPOSITE CASE TO .sheet ABOVE, AND IT IS WORTH BEING CLEAR WHY BOTH EXIST. A sheet is one
   flattened raster whose layout we do not own: it cannot reflow, so it is pinned to 600px and PANS on
   a phone. These three seasons come from the tour leaflets, which are vector PDFs, so each logo is
   its own file — and once the marks are separate the row is ours to lay out. So this one REFLOWS,
   which is what the sheets could never do, and it needs no scroller at any width.
   ⚠ IT ALSO CARRIES NO BAKED-IN WORDS. The leaflet's own group headings ("MAIN DONORS", "MOSTAR")
   stayed behind on the panel when the logos were cut out, so each group takes a .slabel in our type.
   That retires, for these three years only, the defect the sheets still have: "Partners" printing
   twice over every one of them, once in our face and once inside the picture.
   ⚠ NO max-width, AND IT HAD ONE FOR A ROUND. Capping it at 600px to "match .sheet and .qlist" was
   wrong twice over: the rows are ragged-right by nature — eight different logo widths, no edge to
   hold — so nothing ever reached that edge to align to it, and meanwhile 2024's four Supporters
   need 603px, so the cap silently wrapped the fourth one onto a line of its own. An orphaned logo,
   bought for an edge that was never visible. The 600px on .sheet is load-bearing (a fixed raster
   that must not be resampled); here it was decoration with a side effect.
   The row now uses its column, so wrapping happens only when a screen is genuinely too narrow —
   which is what wrapping is for. Widest row is 603px against a 1001px column at 1440.
   ⚠ EACH LOGO'S SIZE IS SET BY width/height ON THE IMG, computed for EQUAL AREA per mark rather than
   equal height (see LOGO_AREA in the script). Equal height would give a 6.9:1 wordmark ten times the
   paper of a 0.87:1 shield beside it. So there is deliberately no sizing rule here: the flex row just
   places boxes whose dimensions already carry that decision.
   ⚠ align-items:center, NOT baseline or stretch. These are pictures at eight different heights with
   no shared baseline to find, and a stretch would distort every one of them.
   ⚠ MULTIPLY, for the same load-bearing reason as .sheet — the crops keep the leaflet panel's pure
   white ground, and the paper is #fbfbfa. Trimming each logo to its own ink is what makes that safe:
   once white is invisible, white inside the file becomes page rhythm nothing asked for. */
.textpage .lgrid{list-style:none;display:flex;flex-wrap:wrap;align-items:center;
  gap:clamp(1.5rem,3.4vh,2.3rem) clamp(1.6rem,3.6vw,2.6rem)}
.textpage .lgrid img{height:auto;mix-blend-mode:multiply}
/* THE MAIN DONOR ON A ROW OF ITS OWN (client, 2026-08-06). One .cgroup can now hold two `.lgrid`
   rows: several leaflet headings print under one "Partners" label, and the donor still opens it
   alone. ⚠️ THE GAP DELIBERATELY EQUALS `.lgrid`'S OWN row-gap — the two rows must read as ONE wall
   that happens to break after the donor, not as two groups. Anything larger re-introduces the
   division the client just asked us to remove, one level down, and this time with no label on it. */
.textpage .lgrid + .lgrid{margin-top:clamp(1.5rem,3.4vh,2.3rem)}

/* A PORTRAIT ON THE TEXT EDGE, its credit beside it — Team and Board.
   ⚠ MOVED OUT OF THE LEFT RAIL on 2026-08-03 (client: "slike su suviše levo … na svim ostalim npr.
   Manifesto itd je više u desno"). It used to sit at cols 1/3, which put a 177px square at x=116 —
   left of the page title (279), left of the section rules and left of every name (322), ending 29px
   short of the text column. At that size it read as stranded in the margin rather than placed there.
   The comparison the client drew is exact: Manifesto's photo starts at 116 too, but it is 383px wide
   and CROSSES the text edge, so it reads as a deliberate break-out. A 177px square cannot do that,
   and it cannot be made bigger — seven of the eight Board uploads are 170px ORIGINALS, so any growth
   is upscaling.
   So the pair moved right instead. Cols 3/5 measure 177px at full size — exactly the portrait's
   natural width — so the face now lands FLUSH ON THE TEXT EDGE at its true size, still sized by the
   grid and still never upscaled. The credit takes cols 5/13, which is Manifesto's own body-text stop
   (.figrow .b, x=529): a name on Team now begins exactly where a paragraph on Manifesto begins.
   ⚠ .tcol.beside GOES ON EVERY CREDIT ROW, including the three people on Team who have no portrait.
   That is the whole reason it exists as a separate class: grid placement is explicit, so an unphotographed
   name still starts at col 5 and the column of names stays ONE unbroken vertical edge. Put a credit in
   a plain .tcol on these two pages and it drops back to 322 while its neighbours sit at 529.
   Each photo sets its own background-position inline: these are not a matched pair (one is a
   three-quarter studio portrait, one a tight headshot), so a single crop rule cannot serve both. */
.textpage .pcell{grid-column:3/5;align-self:start}
.textpage .pcell .img{aspect-ratio:1;background-size:cover;max-width:177px}
.textpage .tcol.beside{grid-column:5/13}

/* THE PHOTO ROW — the one gesture that leaves the reading column, breaking a full column left of
   it while the text beside it runs to the far edge. Position is all it has: no entrance, no drift. */
.textpage .figrow > .grid{align-items:center}
.textpage .figrow .figwrap{grid-column:1/5}
.textpage .figrow .figwrap .img{aspect-ratio:4/5}
.textpage .figrow .b{grid-column:5/13}

/* .reveal-opt carries NO styles on purpose: it is a marker, not a treatment. The paragraphs that
   hold it are simply visible. c-base.js promotes them to real .reveal only under ?reveal=all. */

@media(max-width:880px){
  .textpage .phead .body,.textpage .tcol,.textpage .tcol.beside,
  .textpage .figrow .figwrap,.textpage .figrow .b,.textpage .pcell{grid-column:1/-1}
  .textpage .figrow .b{margin-top:var(--gap-b)}
  /* the rail is gone, so the portrait stacks above its credit — but it must not grow to the full
     phone width: max-width already caps it at the size the asset can actually carry.
     ⚠ .5em, NOT the 1.15em paragraph join. A portrait and its name are ONE unit, and at 1.15em the
     two gaps came out level (~37px face→name against ~37px name→next face), which left every name
     equally close to the person above and the person below it. .5em is the value this site already
     uses to say "these belong together" — the .slabel weld and the .qual credit both take it — so
     the face now hugs its own name and the row gap alone separates people. */
  .textpage .pcell{margin-bottom:.5em}
  .textpage .vgrid{grid-column:1/-1;grid-template-columns:1fr}
  .textpage .pgroup{grid-column:1/-1}
  .textpage .pgrid{grid-template-columns:repeat(2,1fr)}
  /* three 110px badges plus gaps need ~359px against 343px of content at 390px wide. body has
     overflow-x:hidden, so a third column does not report as overflow — it just gets clipped. */
  .textpage .badges{grid-template-columns:repeat(2,max-content)}

  /* ── THE YEAR RAIL KEEPS ITS STICKY LEFT COLUMN ON A PHONE (client, 2026-08-05) ─────────────
     ⚠️ IT HAD NEVER BEEN GIVEN ONE, and the result was not a squeeze but an overlap: at 390px
     `grid-column:1/3` is **44px** of a 12-track grid, while the labels measure **94px**
     ("2012 & 2013") and ~85px ("Initiation"). Every rail overflowed its own column by ~50px and
     printed straight through the text beside it, on Partners and Photos alike. Three tracks (73px)
     with the mono a step smaller fits the longest label, and the content still keeps 253px.
     ⚠️ ONE COLUMN, STATED. Photos inherits `.yrnav`'s base `repeat(3,max-content)`, which inside a
     73px rail stacks three years into the same 34px of width. Partners already sets one column for
     its own rail; saying it here covers every page that carries one. */
  .textpage .chaps > .prail,.textpage .gal > .prail{grid-column:1/4}
  .textpage .chaps > .cbody,.textpage .gal > .gbody{grid-column:4/13}
  .textpage .prail .yrnav{grid-template-columns:max-content;row-gap:.55rem}
  .yrnav a{font-size:.72rem}
  /* one year per line: "2012 & 2013" stacks, and the ampersand goes with the space it needed to sit
     in. The chapter is still ONE jump target — a single link, two lines, one active state. */
  .yrnav .ysep{display:none}
  .yrnav .yy{display:block}
  /* ⚠️ AND THE ACTIVE MARK MOVES ONTO EACH YEAR. `.yrnav a` is a grid item, so it is a BLOCK here:
     a `border-bottom` on it draws ONE rule under the bottom of the box — under 2013 alone — which
     reads as 2013 being the active year and 2012 being a label above it. Both years are the same
     chapter, so both take the rule. */
  .yrnav a.active{border-bottom-color:transparent}
  .yrnav a.active .yy{border-bottom:1px solid currentColor;padding-bottom:.15rem}
  /* ⚠️ AND THE NAME LISTS GO BACK TO ONE COLUMN. Two columns is right at 600px beside a logo sheet;
     inside the 253px the rail leaves on a phone each column is ~118px, and "PortoMontenegro, Tivat"
     and "Month of Contemporary Music, Berlin" broke over two and three lines each — a list of names
     where you cannot tell where one name ends. The two-column rule arrived before the rail did. */
  .textpage .cgroup .qlist{columns:1}
}

/* ── THE RAIL DROPS ITS WORD-LABEL ON A PHONE ────────────────────────────────────────────────
   Measured across widths rather than guessed (client: "check when it overlaps"). "Initiation" is a
   fixed 75px — the only entry on any rail that is a word, not a year — while the rail is 3 of 12
   tracks and so scales with the screen:
       320px  rail 56px  → the label runs 3px INTO the text
       375px  rail 70px  → clears the text, but 5px wider than its own column
       390px  rail 74px  → 1px wider than its own column
       414px  rail 79px  → fits
   So a true overlap only happens on a 320px phone, but the label outgrows its column on every
   handset below ~400px, and it is the one entry doing it. 420px hides it across that whole range
   and keeps it everywhere it fits. ⚠️ A CLASS, not `a[href="#initiation"]` — the rule says why it
   is hidden, and it does not stop working silently if the anchor is ever renamed.
   ⚠️ Only the RAIL ENTRY goes. The chapter keeps its heading, its 17 organisations and its id, so
   nothing is unreachable — it is one scroll below the last year in the index. */
@media(max-width:420px){
  .yrnav .ylong{display:none}
}

/* ── FOUR EXPRESSIVE VOICES ───────────────────────────────────────────────── */

/* STAIR — successive lines each step further right. Extends .lead .l2, the site's
   existing signature, from two lines to n. */
.stair > *{display:block;font-weight:300;font-size:clamp(1.45rem,3.2vw,2.7rem);line-height:1.12;
  letter-spacing:-.014em;margin-left:calc(var(--n,0) * clamp(1.1rem,3vw,2.75rem))}
/* .stair.caps takes the shared emphasis voice — see the .pull block above */

/* DIALOGUE — two voices, two stops, two greys. The colour move that isn't colour.
   One grid PER TURN with a fixed speaker column: a single grid over all the spans lets
   auto-placement spread turns sideways, and an `auto` first track can't align across rows. */
/* The speaker labels are gone (client, 2026-07-28) — they read as machinery beside the poem. What
   they were doing typographically was holding a fixed left track so every line started on one
   edge; without them the lines simply own the column and that alignment is free. The two GREYS
   below now carry the alternation on their own. */
.dlg .turn + .turn{margin-top:clamp(.4rem,1vh,.85rem)}
.dlg .ln{font-weight:300;font-size:clamp(1.1rem,2.1vw,1.7rem);line-height:1.24;letter-spacing:-.012em}
.dlg .turn.a .ln{color:var(--ink-soft)}   /* the questioner recedes */
/* the poem resolves into the tour title — a step up from the other turns, not a jump */
.dlg .turn:last-child .ln{font-size:clamp(1.2rem,2.4vw,1.95rem)}

/* ACROSTIC — mono, so 1ch is exactly one character: per-line offsets stack the
   capitals into one column, with a hairline down its left edge. The ALIGNMENT is
   the reveal; no size gimmick needed. */
.acro{list-style:none;padding:0;position:relative;font-family:var(--mono);font-weight:400;
  font-size:clamp(.82rem,1.6vw,1.3rem);line-height:2}
.acro li{margin-left:calc(var(--i,0) * 1ch)}
.acro b{font-weight:500}
.acro::before{content:"";position:absolute;left:15ch;top:.2em;bottom:.2em;border-left:1px solid var(--hair)}

/* REFRAIN — clauses flush RIGHT, the repeated phrase flush LEFT in an auto column
   so every instance starts at the same x. The refrain crescendos; when the clauses
   run out the page empties into pure pulse. */
.refrain{display:grid;grid-template-columns:1fr auto;column-gap:clamp(.6rem,1.6vw,1.2rem);
  row-gap:clamp(.5rem,1.2vh,.95rem);align-items:baseline}
.refrain .cl{grid-column:1;text-align:right;color:var(--ink-soft);font-size:var(--read)}
.refrain .bt{grid-column:2;font-family:"Inter Tight",var(--sans);font-weight:700;line-height:1.02;
  letter-spacing:-.01em;font-size:calc(clamp(1rem,1.9vw,1.5rem) * var(--s,1))}

/* ANAPHORA — hanging indent, so every repeated opening starts flush and the
   continuations indent: the repetition becomes a visible vertical edge. */
.anaph p{text-indent:calc(-1 * var(--ind));padding-left:var(--ind);font-size:var(--read);line-height:1.78}
.anaph p + p{margin-top:.5em}

/* ── PROJECTS: PLATES + THE GRID VIEW ─────────────────────────────────────── */

/* a plate is a designed print object — MOUNTED, never cropped. C's .fig .img
   defaults to cover; cropping a poster would be a real design error. */
/* --ar is the plate's real ratio, set per tile: contain + the true ratio means a designed print
   object is mounted whole, never cropped. Without the ratio the box has no height at all. */
.plate .img{background-size:contain;background-repeat:no-repeat;background-position:center;
  width:100%;aspect-ratio:var(--ar,1)}
.plate figcaption{font-family:var(--mono);font-size:.7rem;color:var(--ink-soft);margin-top:.85rem;letter-spacing:.03em}

/* Text ↔ Grid is ONE DOM re-laid out by CSS — no second content tree, so the two views cannot
   drift apart. display:contents chains the chapter and its plate row open, so each YEAR's plate
   becomes a direct child of .chaps and can take its own span — a two-year chapter yields two
   tiles. The chapter box disappears with it, so its full-ink rule doesn't survive into the wall. */
.pl .cap{display:none}
body.view-grid .chaps{display:grid;grid-template-columns:repeat(12,1fr);
  column-gap:clamp(1rem,2vw,2rem);row-gap:clamp(3.5rem,8vh,7rem);align-items:start}
body.view-grid .chap,body.view-grid .plates{display:contents}
body.view-grid .grid.txt{display:none}
body.view-grid .pl .cap{display:block;font-family:var(--mono);font-size:.72rem;color:var(--ink-soft);
  letter-spacing:.03em;margin-top:.8rem;line-height:1.5}
body.view-grid .pl .cap .tn{display:block;color:var(--ink)}
/* scattered plates, varied spans and staggered drops — C's .media .gal grammar applied to
   posters, not a flush tile wall */
body.view-grid .g1{grid-column:2/7}
body.view-grid .g2{grid-column:8/12;margin-top:clamp(3rem,7vh,6rem)}
body.view-grid .g3{grid-column:1/5}
body.view-grid .g4{grid-column:6/9;margin-top:clamp(2rem,5vh,4rem)}
body.view-grid .g5{grid-column:10/13;margin-top:clamp(5rem,11vh,9rem)}
body.view-grid .g6{grid-column:2/5}
body.view-grid .g7{grid-column:6/11;margin-top:clamp(2rem,5vh,4rem)}
body.view-grid .g8{grid-column:2/8;margin-top:clamp(3rem,7vh,6rem)}
/* the year with no visual becomes a TYPE tile — a missing poster joins the wall as a
   typographic member rather than leaving a hole. */
body.view-grid .typetile{display:grid;place-items:start;aspect-ratio:1;border:1px solid var(--hair);padding:1.2rem}
body.view-grid .typetile .ty{font-family:var(--mono);font-size:clamp(1.6rem,3.4vw,2.6rem);line-height:1}
body.view-grid .typetile .tt{font-family:"Inter Tight",var(--sans);font-weight:600;
  font-size:clamp(1rem,1.7vw,1.3rem);line-height:1.1;text-transform:uppercase;align-self:end}
.typetile{display:none}

/* ── RESPONSIVE ───────────────────────────────────────────────────────────── */

@media(max-width:880px){
  .phead .body,.chap .n,.chap .b,.chap .lbl{grid-column:1/-1}
  .phead .seg{margin-left:0}
  .chap .n{margin-bottom:1.4rem}
  .chap .n > span{position:static}
  .chap .n.num > span{font-size:clamp(2.2rem,9vw,3.4rem)}
  .roster.cols{columns:1}
  /* a name + city + diaspora city is wider than a phone column, so the entry has to be
     allowed to break; the name and each tag stay unbreakable individually */
  .roster .e{white-space:normal}
  .stmt .work{margin-left:0}
  .stmt .say{margin-left:0}
  /* 15ch of offset cannot survive a phone: the acrostic reads by weight, not by column */
  .acro li{margin-left:0}
  .acro b{font-size:1.5em;line-height:0}
  .acro::before{display:none}
  /* the beat indents instead of aligning — the pulse survives, the geometry doesn't */
  .refrain{grid-template-columns:1fr}
  .refrain .cl{text-align:left}
  .refrain .bt{grid-column:1;margin-left:var(--ind)}
  .stair > *{margin-left:calc(var(--n,0) * .8rem)}
  .anaph p{text-indent:0;padding-left:0}
  body.view-grid .chaps > *{grid-column:1/-1!important;margin-top:0!important}
}

/* ══ THE MODAL — .pdlg ══════════════════════════════════════════════════════════════════════
   PROMOTED HERE from build-projects.py (2026-08-04) when Team and Board grew bio dialogs.
   Its old home carried the comment "Lives here because Projects is the only page with a
   .pdlg" — three pages now have one, so that reason expired.
   ⚠️ THE CONTROLLER MOVED WITH IT, into c-base.js. Moving this block without its JS is how
   the controller was lost once before: the markup, the CSS and every count stayed green
   while a click opened nothing. probe-modal.mjs is the guard — run it after touching either.
   ⚠️ Read the note on `body.sub > .wrap` above: it is scoped to > .wrap and NOT > * precisely
   so it cannot outrank .pdlg on specificity and blow up the dialog width. */
/* ── PROJECT DETAILS, IN A MODAL ─────────────────────────────────────────────────────────────
   A native <dialog> shown with showModal(). It renders in the top layer, so no ancestor transform,
   overflow or z-index on the page can reach it — which is the whole reason the in-place disclosure
   needed care and this doesn't.
   THE READING MEASURE IS HELD AT 592px — the width the inline liner had at 6/12 of the chapter grid
   at 1440 (12 tracks of 74.2px, gaps of 28.8px, inside a 1207px content box). Every hand-set block
   in there is measured against that band: the acrostic's 15ch rule and its 1ch step offsets, the
   refrain's flush-right clauses, the values stair. Holding the width means none of them re-wrap
   just because their container changed. It is also WIDER than the band at 1280/1024, where the
   acrostic was verified — so those cases only get safer.
   BOTH AXES SCROLL, deliberately: .stmt .work hangs half a gutter into the left margin, and an
   overflow-x:hidden here would clip it. The inline padding is ≥ that hang (4.5rem vs 3rem at the
   top of both clamps), so the hang lands inside the padding, nothing overflows and no horizontal
   bar appears. */
/* margin:auto is what CENTRES a modal dialog, and it is the UA's — c-base.css's reset (*{margin:0})
   wipes it, which parks the dialog in the top-left corner. It has to be written back by hand. */
.pdlg{margin:auto;width:min(94vw,736px);max-width:none;max-height:88svh;padding:0;border:0;
  background:var(--paper);color:var(--ink);box-shadow:0 24px 70px rgba(0,0,0,.24)}
.pdlg::backdrop{background:rgba(16,18,20,.62)}
/* THE DIALOG IS A FLEX COLUMN and the head is OUTSIDE the scroll area, so it stays put through
   2000px of prose without needing position:sticky (which would have done nothing anyway — sticky
   needs the scroll container as an ANCESTOR, and .pdhead is the scroller's sibling). The head also
   names the chapter you opened, because inside a modal the page's own context is gone.
   .pdbody needs BOTH flex:1 and min-height:0: a flex item's min-height defaults to auto, which
   refuses to shrink below its content — so without it the prose spills straight past the dialog's
   max-height and overflow:auto never engages. */
.pdlg[open]{display:flex;flex-direction:column}
.pdhead{flex:none;display:flex;align-items:flex-start;justify-content:space-between;
  gap:clamp(1rem,3vw,2rem);background:var(--paper);border-bottom:1px solid var(--hair);
  padding:clamp(1.4rem,3vh,2rem) clamp(1.5rem,4vw,4.5rem)}
.pdt{font-family:"Inter Tight",var(--sans);font-weight:700;text-transform:uppercase;
  font-size:clamp(1.15rem,2.2vw,1.6rem);line-height:1.06;letter-spacing:-.01em}
.pdsub{margin-top:.45rem;font-size:.9rem;letter-spacing:.1em}
/* the reading area takes focus on open (autofocus in the markup). Without it showModal() focuses the
   first tabbable thing, which is Close — so the modal opened with a focus ring drawn round the exit.
   tabindex="-1" keeps it out of the tab sequence while still letting the arrow keys scroll it, and
   the ring is suppressed because a scroll container is not a control. */
.pdbody{flex:1 1 auto;min-height:0;overflow:auto;overscroll-behavior:contain;
  padding:clamp(2rem,5vh,3.2rem) clamp(1.5rem,4vw,4.5rem) clamp(2.6rem,7vh,4.5rem)}
.pdbody:focus,.pdbody:focus-visible{outline:none}
/* BOTTOM FADE — the scroll edge used to cut a sentence mid-word against a hard line, which reads as
   broken rather than as "more below" (macOS overlay scrollbars fade out, so there was no other hint).
   It sits on the DIALOG, not the scroller: a mask on .pdbody would fight the negative-gutter hang.
   Shown only while something is still below (see pdFade), so the last line is never left under a
   veil once you reach the end. The gradient ends on --paper AT ALPHA 0 rather than `transparent`,
   which some engines interpolate through transparent-black and grey the fade. */
.pdlg::after{content:"";position:absolute;left:0;right:0;bottom:0;height:clamp(2.4rem,7vh,4.5rem);
  background:linear-gradient(to top,var(--paper),rgba(251,251,250,0));
  opacity:0;transition:opacity .3s;pointer-events:none}
.pdlg.pd-fade::after{opacity:1}
.pdbody .liner{max-width:592px}
/* a .stmt opens with its own hairline, which would double the head's rule */
.pdbody .liner > .stmt:first-child{border-top:0;padding-top:0}
/* one-shot entrance in the page's own curve. Closing is immediate — a dialog that lingers on the
   way out reads as lag, not as motion. */
.pdlg[open]{animation:pdIn .42s cubic-bezier(.16,1,.3,1)}
.pdlg[open]::backdrop{animation:pdBd .42s ease}
@keyframes pdIn{from{opacity:0;transform:translateY(14px)}}
@keyframes pdBd{from{opacity:0}}
@media(prefers-reduced-motion:reduce){.pdlg[open],.pdlg[open]::backdrop{animation:none}}
/* the page must not scroll behind the modal. overflow:hidden collapses the scrollbar, which shifts
   the layout by its width wherever one reserves space (not macOS, which overlays) — --sbw is
   measured before the lock and hands that width back. */
html.pdlg-open,html.pdlg-open body{overflow:hidden}
html.pdlg-open body{padding-right:var(--sbw,0px)}
