/* ════════════════════════════════════════════════════════════════════════
   network+ — shared design system
   One owner (foundation builder). Page builders consume; do not fork.
   Section map: tokens → reset/base → type → layout → buttons → poster →
   community card → nav → footer → motion → responsive → a11y.
   ════════════════════════════════════════════════════════════════════════ */

/* ── 1. Tokens ─────────────────────────────────────────────────────────── */
:root{
  --bg0:#1A1A20; --bg1:#202027; --bg2:#26262E; --bg3:#30303A;
  --fg1:#F5F4F1; --fg2:#A8A8B0; --fg3:#6E6E78; --fgInv:#0A0A0C;
  --border1:rgba(255,255,255,.09); --border2:rgba(255,255,255,.16);
  --glass:rgba(255,255,255,.055); --glassStrong:rgba(28,28,34,.72); --glassHi:rgba(255,255,255,.22);
  --accent:#FF6B3D; --accentSoft:rgba(255,107,61,.14); --onAccent:#160905;
  --ember:linear-gradient(135deg,#FFC46B 0%,#FF7847 48%,#F03B2E 100%);
  --emberDeep:linear-gradient(160deg,#FF9A4D 0%,#E8492E 55%,#9C1F1D 100%);
  --horizon:linear-gradient(180deg,#FFA94D 0%,#D96A38 28%,#6B3A44 52%,#1E2B4E 78%,#0C1830 100%);
  --success:#3DD68C; --warning:#FFC24B; --danger:#FF4D4D; --info:#4DA3FF;
  --ease:cubic-bezier(.22,1,.36,1);
  --r:8px; --r-sm:6px;
  --shadow-card:0 4px 12px rgba(0,0,0,.25);
  --shadow-raised:0 8px 20px rgba(0,0,0,.35);
  --glow:0 4px 24px rgba(255,107,61,.35);
  --maxw:1180px;
  /* Section rhythm (TEARDOWN §9.10) — one token, used uniformly. Both references hold a
     constant 120-160px GAP between major sections; density lives inside a section, never
     in the gap between sections. Don't hand-tune per section.
     This is top+bottom padding on EVERY section, so two adjacent sections stack
     padding-bottom + padding-top = 2×this value as the visible gap between them — the
     token is half the target gap, not the gap itself (was 140px, stacking to a 280px
     void; fixed 2026-08-09, see rematch notes below). */
  --section-space:70px;
  --section-space-sm:44px;

  --ember-1:#FFC46B; --ember-2:#FF7847; --ember-3:#F03B2E;
  /* Named stops lifted verbatim from --horizon, so category hues below introduce
     zero new hex values — every one already exists in the frozen palette (BRIEF §2). */
  --horizon-1:#FFA94D; --horizon-2:#D96A38; --horizon-3:#6B3A44; --horizon-4:#1E2B4E; --horizon-5:#0C1830;

  /* Category hues (BRIEF §2 + rematch fix #3) — low-luminance washes over --bg2, never a
     saturated fill. Each vertical is pinned to ONE hue family top-to-bottom so no two
     categories collapse into the same muddy brown at low opacity (the failure mode a
     blind critic measured with a pixel sampler: Move and Travel were the same two hues
     run in opposite directions, and Play's dark end matched Move's dark end). */
  --move-1:var(--info); --move-2:var(--horizon-4);      /* blue → navy, no amber anywhere */
  --play-1:var(--ember-3); --play-2:var(--horizon-3);   /* red → crimson */
  /* Travel retune (rematch fix #6) — same five stops as --horizon, zero new hex, just
     re-percentaged: the old 28→78% run spent HALF the gradient dragging through the
     purple-brown "bruise" stop. Now that stop is a brief 24→36% pivot and navy→black
     gets the long, clean 62→100% run the critic liked on Move/Play. Top-of-card hue
     (orange, ~29°) is unchanged. */
  --travel-wash:linear-gradient(180deg,var(--horizon-1) 0%,var(--horizon-2) 24%,
    var(--horizon-3) 36%,var(--horizon-4) 62%,var(--horizon-5) 100%);
  --orgs-1:var(--success); --orgs-2:var(--bg1);          /* green → near-black */
}

/* ── 2. Reset + base ───────────────────────────────────────────────────── */
*,*::before,*::after{box-sizing:border-box;margin:0;padding:0}
html{scroll-behavior:smooth;-webkit-text-size-adjust:100%}
body{
  background:var(--bg0);color:var(--fg1);overflow-x:hidden;
  font-family:'Manrope',system-ui,-apple-system,sans-serif;font-weight:500;
  -webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale;
  background-image:
    radial-gradient(60% 46% at 10% 2%, rgba(255,255,255,.06), transparent 70%),
    radial-gradient(58% 42% at 92% 0%, rgba(255,120,71,.07), transparent 70%),
    radial-gradient(70% 40% at 50% 100%, rgba(120,110,200,.06), transparent 70%);
  background-attachment:fixed;
}
img,svg{max-width:100%;display:block}
a{color:inherit}
button{font:inherit;color:inherit;background:none}

/* ── 3. Type scale ─────────────────────────────────────────────────────── */
h1,h2,h3,.disp{font-family:'Space Grotesk',system-ui,sans-serif;font-weight:600;letter-spacing:-.02em;text-wrap:balance}
.disp-xl{font-size:clamp(52px,7.4vw,104px);line-height:.98;letter-spacing:-.035em}
h1,.disp-lg{font-size:clamp(40px,5.4vw,64px);line-height:1.03;letter-spacing:-.03em}
h2,.disp-md{font-size:clamp(30px,4vw,44px);line-height:1.1;letter-spacing:-.025em}
h3,.disp-sm{font-size:20px;line-height:1.25;letter-spacing:-.015em}
.mono{font-family:'JetBrains Mono',ui-monospace,monospace;font-weight:500}
.eyebrow{font-family:'JetBrains Mono',ui-monospace,monospace;font-size:11px;line-height:1.4;
  letter-spacing:.1em;text-transform:uppercase;color:var(--fg2);margin-bottom:18px;display:block}
.eyebrow b{color:var(--accent);font-weight:500}
.lede{font-size:clamp(17px,1.5vw,19px);line-height:1.6;color:var(--fg2);max-width:56ch;text-wrap:pretty}
.body-sm{font-size:14.5px;line-height:1.58;color:var(--fg2)}
.small{font-size:13px;line-height:1.5;color:var(--fg2)}
/* .grad-text — TEARDOWN §9.4: Luma applies its gradient to exactly ONE phrase on the
   whole homepage, and that restraint is why it reads expensive. Use this on one phrase
   per page. Spraying it across headlines dilutes the accent — don't. */
.grad-text{background:var(--ember);-webkit-background-clip:text;background-clip:text;color:transparent}

/* ── 4. Layout ─────────────────────────────────────────────────────────── */
.wrap{max-width:var(--maxw);margin:0 auto;padding:0 40px}
section{padding:var(--section-space) 0;position:relative}
.section-tight{padding:64px 0}

.grid-2{display:grid;grid-template-columns:1fr 1fr;gap:24px}
.grid-3{display:grid;grid-template-columns:repeat(3,1fr);gap:20px}
.grid-4{display:grid;grid-template-columns:repeat(4,1fr);gap:20px}
.grid-cards{display:grid;grid-template-columns:repeat(auto-fit,minmax(240px,1fr));gap:20px}
.grid-posters{display:grid;grid-template-columns:repeat(auto-fit,minmax(220px,1fr));gap:22px}

/* ── 5. Buttons ────────────────────────────────────────────────────────── */
.btn{font-family:'Manrope',sans-serif;font-weight:700;font-size:15px;border-radius:var(--r);
  padding:0 20px;min-height:44px;cursor:pointer;text-decoration:none;border:1px solid transparent;
  display:inline-flex;align-items:center;justify-content:center;gap:8px;
  transition:transform .12s var(--ease),background .12s var(--ease),color .12s var(--ease),
    opacity .12s var(--ease),border-color .12s var(--ease)}
.btn:active{transform:scale(.98)}
.btn-sm{font-size:13.5px;min-height:44px;padding:0 16px}
.btn-lg{font-size:16.5px;min-height:52px;padding:0 26px}

.btn-glass{background:var(--glassStrong);border-color:var(--border1);color:var(--fg1);backdrop-filter:blur(20px)}
.btn-glass:hover{background:var(--bg3);border-color:var(--border2)}

.btn-ghost{color:var(--fg2);padding:0 14px}
.btn-ghost:hover{color:var(--fg1)}

/* Ember primary — radius + overflow on the wrapper, gradient inside as an absolute
   child, shadow on the outer wrapper. NEVER radius directly on the gradient. */
.cta{position:relative;overflow:hidden;isolation:isolate;border-radius:var(--r);
  display:inline-flex;align-items:center;justify-content:center;gap:10px;
  padding:0 30px;min-height:52px;font-family:'Manrope',sans-serif;font-weight:700;font-size:16.5px;
  color:var(--onAccent);text-decoration:none;cursor:pointer;border:none;box-shadow:var(--glow);
  transition:transform .12s var(--ease),box-shadow .22s var(--ease)}
.cta .g{position:absolute;inset:0;background:var(--ember);z-index:-2}
.cta .n{position:absolute;inset:0;background:url('/grain.png') repeat;opacity:.35;
  mix-blend-mode:overlay;z-index:-1;pointer-events:none}
.cta:hover{transform:translateY(-2px);box-shadow:0 8px 34px rgba(255,107,61,.48)}
.cta:active{transform:scale(.98)}
.cta.btn-sm{padding:0 20px;min-height:44px;font-size:14.5px}
.cta.btn-lg{padding:0 34px;min-height:56px;font-size:17.5px}
.cta .ico{width:18px;height:18px}

.ico{width:20px;height:20px;flex:none;fill:none;stroke:currentColor;
  stroke-width:1.75;stroke-linecap:round;stroke-linejoin:round}

/* ── 6. Poster component ──────────────────────────────────────────────────
   .np-poster — the template poster card. 1:1 square (matches the mobile app's cover
   convention — communities/club-rail covers are square there too), brand-token-only,
   grain overlay, oversized display title, mono metadata.

   THREE-BAND REWRITE (lost the second blind comparison; see docs/site-refs/BRIEF.md
   §2/§3/§0a). The verdict: a background motif was allowed to physically overlap the
   foreground title — dice through "Board", a crate through "Skills workshop". The fix
   is structural, not per-card: the card is split into three horizontal bands and
   overlap between them is now impossible by construction, not by tuning.
     ┌─────────────────────────────┐
     │  TOP BAND  (0–44% height)   │  mark + motif live here ONLY, sharing space
     │  motif / mark               │  with each other and nothing else
     ├─────────────────────────────┤
     │  dead gradient strip        │  pure wash + grain, no content is ever placed
     ├─────────────────────────────┤  here — it's what makes the band split visible
     │  tag / title / meta         │  .np-poster__body, pinned to the bottom by
     │  BOTTOM BAND                │  flex (margin-top:auto); never touched by the
     └─────────────────────────────┘  icon layer at any breakpoint

   How it's enforced (not by convention — by CSS that can't be violated):
     1. `--band-h` (default 44%) caps .np-poster__mark and .np-poster__motif with
        `max-height:var(--band-h)` + `overflow:hidden`. Whatever size a page passes via
        --mark-size / --motif-size (old vw-based values included), the band is a hard
        ceiling — it clips before it can reach the title band. This is the "structurally
        impossible" guarantee, and it's why the three untouched vertical pages get the
        fix for free: they consume this same class API from site.css.
     2. The --tl/--tr/--bl/--br/--mid position modifiers now place the mark/motif at a
        corner of the TOP BAND, not the whole card (rematch compatibility requirement —
        same class names, same modifiers, new geometry). --bl/--br sit at the band's own
        floor, `bottom:calc(100% - var(--band-h))`, which is the band boundary itself —
        they can graze it, never cross it.
     3. Default sizing uses `cqw` (container query width, `container-type:inline-size`
        below) instead of the old `vw`. `vw` is a viewport unit — a mark sized off it has
        no idea it's sitting in a 2-up mobile card vs. a 4-up desktop one, which is the
        root cause of "32 sits directly on 'long ride'" at 390px. `cqw` is 1% of the
        poster's OWN width, so mark/motif scale with the card at every breakpoint.

   .np-poster__mark — one oversized Space Grotesk numeral/word per card, the dominant
   graphic (not a caption). .np-poster__motif — a hard-edged, real-opacity vector shape,
   one per event from a small per-category vocabulary. Fusion (rematch fix #5): three
   composition modifiers control how mark and motif relate to EACH OTHER inside the top
   band — variety comes from that relationship, not from moving the title (a previous
   iteration tried relocating the title and broke scan rhythm; the title stays
   bottom-left, always):
     --fuse-behind  (default, no class needed) — motif and mark sit independently at
       their own corners, the original "floating" relationship, now band-safe.
     --fuse-nest    — motif shrinks and sits UNDER the mark at the SAME corner (mark is
       later in DOM so it always paints on top), reading as sitting inside the numeral's
       counter-space. Pair with a matching --tl/--tr/etc. on both elements; for a literal
       "dice inside the O of GO" result, hand-position the motif with inline top/left (see
       _foundation-preview.html's GO card for the worked example — this level of per-card
       fit is bespoke by nature, same as tuning --motif-size always was).
     --fuse-horizon — motif becomes a full-width strip sitting at the band's own floor;
       the mark stands above it like a numeral standing on a horizon line. Overrides the
       motif's own corner modifier (two classes beat one, no !important needed).

   Restraint pass (2026-08-09, client verbatim: "too much glow and gradient... every
   card carries the same colored bloom"): the category-hue halo every card used to carry
   (TEARDOWN §9.1) is GONE — a grid of twelve cards each throwing a 52px colored bloom
   read as twelve light sources, not twelve objects. .np-poster now takes a plain
   neutral --shadow-card/--shadow-raised (below), same trick every other surface on the
   site uses. The CTA's --glow is the only halo left on the page — that's the point.
   Category identity still lives in the wash hue + tone modifiers, just without the glow.
   Both references' hero cards are DECORATIVE ONLY and not clickable
   (TEARDOWN §10) — ours are real links. Use an <a>, not an <article>: whole card is the
   hit target, no nested interactive elements, and the halo/focus ring both render fine
   despite the card's own overflow:hidden, because box-shadow and outline paint outside
   an element's border box and are never clipped by that same element's overflow — only
   the element's own children are.

   Anatomy (unchanged from the first rewrite — same markup, same class names, every
   consuming page keeps working untouched):
     <a class="np-poster np-poster--move" data-category="move" href="/login">
       <div class="np-poster__wash"></div>              (tinted gradient wash)
       <div class="np-poster__grain"></div>              (grain overlay)
       <svg class="np-poster__motif np-poster__motif--bl" aria-hidden="true" style="--motif-size:70%">
         <use href="#i-route"/>
       </svg>
       <span class="np-poster__mark np-poster__mark--tr" aria-hidden="true" style="--mark-size:clamp(72px,9vw,170px)">5K</span>
       <div class="np-poster__scrim"></div>
       <div class="np-poster__body">
         <span class="np-poster__tag">Move · Weekly</span>   (category + cadence)
         <h3 class="np-poster__title">Sunrise founder run</h3>
         <p class="np-poster__meta">5K · EASY PACE · COFFEE AFTER</p>
       </div>
     </a>

   Mark modifier (pick one): default = solid fill; --outline = transparent fill,
   1.5px stroke (for "SKI", "9", "48H"-style marks that want to read as line art).
   Mark/motif position (pick one each, independent of each other): --tl --tr --bl --br
   (--mid also valid for the mark) — now corners of the TOP BAND, see above.
   Bleed anchor (rematch fix #3): default new authoring to --tr for the mark — twelve
   marks bleeding off assorted corners reads as randomized test data, not rhythm. --tl is
   still valid (compat) but treat it as the exception, not the norm.
   Category modifier (pick one, required — drives the wash AND the glow):
   --move --play --travel --orgs.
   Tonal modifier (optional, rematch: within-category variation) — pick zero or one:
   --tone-b (lighter/brighter) --tone-c (deeper/darker) --tone-d (flatter/muted). A
   vertical page's six same-hue cards cycle these so the row reads as six related cards,
   not one card repeated — see the tonal-variation block below for why this can't drift
   into a fifth hue.
   --art (rematch: bleed the art, never the label) — for a collage card that bleeds off
   an edge or sits behind a neighbour. Hides .np-poster__body the same way `.sr-only`
   does (visually hidden, still in the accessible tree — the link keeps a real name, it
   just isn't a visible caption on top of a half-visible card). Use the full card
   (no --art) everywhere it's fully on-screen.
   Title position is fixed: bottom-left on every card, no exceptions (BRIEF fix #4 — the
   old --stack/--split/--edge layout variants that moved the title around are deleted;
   variety now comes from the mark + motif, not from relocating the caption).
   Optional: wrap a set in `.np-scatter` and add one `.np-tilt-1`..`.np-tilt-5` per card
   for a collage arrangement (see §10, motion).
   Photo layer (opt-in, --photo) — swaps the generated wash/mark/motif for a client
   photo. See the dedicated comment right below the poster rules for markup + scrim
   details. ────────────────────────────────────────────────────────────────────── */
.np-poster{
  position:relative;aspect-ratio:1/1;border-radius:var(--r);overflow:hidden;
  background:var(--bg2);border:1px solid var(--border1);
  display:flex;flex-direction:column;text-decoration:none;color:var(--fg1);
  box-shadow:var(--shadow-card);
  container-type:inline-size; /* enables cqw sizing below — mark/motif scale with the
    card's OWN width, not the viewport, at every breakpoint */
  --band-h:44cqw; /* the top band ceiling — see the anatomy comment above. cqw, not %:
    the card is square, so 1cqw (1% of the card's own width) is also 1% of its height,
    and only a length (not a bare %) can drive the mark's font-size below */
  z-index:1;
  transition:transform .22s var(--ease),border-color .22s var(--ease),box-shadow .22s var(--ease),z-index 0s;
}
.np-poster:hover,.np-poster:focus-visible{transform:translateY(-4px);border-color:var(--border2);
  box-shadow:var(--shadow-raised);z-index:5}
/* Square-ratio band correction — 1:1 gives a narrow card ~20% less absolute height
   than the old 4:5 did at the same width, but the title/tag/meta block's height is
   driven by fixed px type sizes, not by the card, so it eats a much bigger SHARE of a
   square card. Below ~260px a 44% band can sit BELOW the body's own top edge, i.e.
   overlapping it — but how much smaller the band needs to be scales with card width,
   not a single cliff: the ~258px steady-state desktop grid card (this maxw/gap/column
   math nets a 4-up card just under the old 260px trigger) has plenty of room and only
   needed ~30%; only the true ~166px 2-up mobile floor needs the aggressive ~9% (measured
   the worst-case 2-line-clamped body height against the card at each width, band = 100%
   minus that). Two container-query tiers instead of one. Targets __mark/__motif, not
   .np-poster
   — a container can't query its own size in the rules it establishes context for, only
   its descendants can. */
@container (max-width:260px){
  .np-poster__mark,.np-poster__motif{--band-h:26cqw}
}
@container (max-width:220px){
  .np-poster__mark,.np-poster__motif{--band-h:9cqw}
}
/* Inner top highlight (TEARDOWN §9.5) — the dark-mode "mat" line; same trick as .fcard::before. */
.np-poster::before{content:'';position:absolute;top:0;left:14px;right:14px;height:1px;
  background:var(--glassHi);z-index:2;pointer-events:none}

.np-poster__wash{position:absolute;inset:0;opacity:.44}
.np-poster__grain{position:absolute;inset:0;background:url('/grain.png') repeat;opacity:.35;mix-blend-mode:overlay}

/* Category washes — tinted, low-luminance, ONE hue family per vertical top-to-bottom
   (BRIEF fix #3 — see the token block for why these specific stops). Ember at full
   saturation stays CTA-only.
   --wash-angle / --wash-stop (rematch fix #2, BRIEF §4): per-card gradient angle and
   stop-position variance so a same-hue grid doesn't read as one card printed N times —
   set inline per poster instance, e.g. style="--wash-angle:150deg;--wash-stop:88%".
   Defaults reproduce the original fixed values exactly, so any card that doesn't set
   them (every existing index.html/orgs.html poster) renders pixel-identical. */
.np-poster--move .np-poster__wash{background:linear-gradient(var(--wash-angle,165deg),var(--move-1) 0%,var(--move-2) var(--wash-stop,100%))}
.np-poster--play .np-poster__wash{background:linear-gradient(var(--wash-angle,165deg),var(--play-1) 0%,var(--play-2) var(--wash-stop,100%))}
.np-poster--travel .np-poster__wash{background:linear-gradient(var(--wash-angle,180deg),var(--horizon-1) 0%,var(--horizon-2) 24%,var(--horizon-3) 36%,var(--horizon-4) 62%,var(--horizon-5) 100%);opacity:.5}
.np-poster--orgs .np-poster__wash{background:linear-gradient(var(--wash-angle,165deg),var(--orgs-1) 0%,var(--orgs-2) var(--wash-stop,100%))}

/* Within-category tonal variation (rematch addition) — a vertical page shows six
   same-hue posters in a row; without this they read as one card printed six times.
   These shift DEPTH (brightness/saturation of the existing wash), never hue, so "the
   blue ones" stays identifiable next to a red or green card at a glance — the taxonomy
   the critic praised is untouched, this is variance INSIDE one hue family. Zero new hex:
   `filter` only transforms pixels the wash already painted. Default (no modifier) is
   pixel-identical to before, so index/move/orgs render unchanged until a page builder
   opts a card in. */
.np-poster--tone-b .np-poster__wash{filter:brightness(1.18) saturate(1.1)}  /* lighter steel */
.np-poster--tone-c .np-poster__wash{filter:brightness(.78) contrast(1.08)} /* deeper navy */
.np-poster--tone-d .np-poster__wash{filter:saturate(.72);opacity:.4}       /* flat/muted */

/* Vector motif — hard-edged, real opacity, clipped by the card. Replaces the deleted
   low-opacity blob. Sized in cqw so it scales with the card; --motif-size (legacy % or
   px values included) still tunes it per instance, but max-height:var(--band-h) is a
   hard ceiling no size can push past — see the anatomy comment. */
.np-poster__motif{position:absolute;z-index:1;color:var(--fg1);opacity:.5;pointer-events:none;
  width:var(--motif-size,54cqw);height:var(--motif-size,54cqw);
  max-height:var(--band-h);overflow:hidden;
  /* Real Lucide symbols (docs/site-refs/ICON-SPRITE.svg) are stroke-based, not
     filled — bolder than the 1.75 UI-icon stroke so a big decorative glyph still
     reads at a glance instead of thinning to a hairline. Legacy filled shapes are
     unaffected: every one of their paths sets its own fill/stroke attribute,
     which wins over this inherited default. */
  fill:none;stroke:currentColor;stroke-width:2.25;stroke-linecap:round;stroke-linejoin:round}
.np-poster__motif--tl{top:-6%;left:-8%}
.np-poster__motif--tr{top:-6%;right:-8%}
.np-poster__motif--bl{bottom:calc(100% - var(--band-h));left:-8%}
.np-poster__motif--br{bottom:calc(100% - var(--band-h));right:-8%}

/* Type-as-graphic mark — the fix for "cover the title and it's an indistinguishable
   gradient swatch." One oversized numeral/word per card, real text (not an image). Sits
   above the motif (later in DOM), below the legibility scrim. cqw-based default; capped
   to the band the same way the motif is. font-size is additionally clamped to --band-h
   itself (not just max-height) so a mark SHRINKS to fit a small band instead of clipping
   into a sliver — at line-height .82 a font-size equal to the band leaves ~18% headroom,
   so the max-height/overflow below stops being the thing doing the work and is just a
   backstop. */
.np-poster__mark{position:absolute;z-index:1;pointer-events:none;white-space:nowrap;
  max-height:var(--band-h);overflow:hidden;
  font-family:'Space Grotesk',sans-serif;font-weight:600;letter-spacing:-.05em;line-height:.82;
  font-size:min(var(--mark-size,clamp(34px,30cqw,120px)),var(--band-h));color:var(--fg1);opacity:.92}
.np-poster__mark--outline{color:transparent;-webkit-text-stroke:1.5px var(--fg1);opacity:1}
.np-poster__mark--tl{top:2%;left:3%;text-align:left}
.np-poster__mark--tr{top:2%;right:3%;text-align:right}
.np-poster__mark--bl{bottom:calc(100% - var(--band-h));left:3%;text-align:left}
.np-poster__mark--br{bottom:calc(100% - var(--band-h));right:3%;text-align:right}
.np-poster__mark--mid{top:calc(var(--band-h)/2);left:50%;transform:translate(-50%,-50%);text-align:center}

/* Fusion composition modes (rematch fix #5) — see the anatomy comment for the full
   explanation. --fuse-behind is the implicit default (no CSS needed: it's just the
   base rules above). These two are additive modifiers on .np-poster. */
.np-poster--fuse-nest .np-poster__motif{max-height:calc(var(--band-h)*.5);opacity:.85}
.np-poster--fuse-horizon .np-poster__mark{top:5%}
.np-poster--fuse-horizon .np-poster__motif{
  top:auto;bottom:calc(100% - var(--band-h));left:-4%;right:-4%;
  width:auto;height:auto;max-height:calc(var(--band-h)*.64);opacity:.65;
}

/* Legibility scrim — guarantees the bottom-left title reads over whatever the mark/motif
   are doing above it, regardless of category hue. Same trick as a gig-poster's baseline
   band; keeps "one title position" honest instead of relying on luck per card. */
.np-poster__scrim{position:absolute;z-index:1;inset:auto 0 0 0;height:52%;pointer-events:none;
  background:linear-gradient(180deg,transparent 0%,rgba(10,10,12,.5) 65%,rgba(10,10,12,.72) 100%)}

.np-poster__body{position:relative;z-index:2;margin-top:auto;padding:16px 18px 18px;display:flex;flex-direction:column;gap:8px}
.np-poster__tag{font-family:'JetBrains Mono',monospace;font-size:10.5px;letter-spacing:.09em;
  text-transform:uppercase;color:var(--fg2)}
.np-poster__title{font-family:'Space Grotesk',sans-serif;font-weight:600;letter-spacing:-.025em;
  font-size:clamp(19px,2.4vw,26px);line-height:1.08;text-wrap:balance;
  /* Capped at 2 lines (rematch, square-ratio fix) — an uncapped title's height is
     unbounded, and the three-band system's overlap guarantee (below) is only provable
     against a KNOWN worst-case body height. Every title in the canonical set (BRIEF §4)
     fits comfortably in two lines except at the narrowest mobile card width, where this
     clips the rare third line instead of letting the body creep into the art band. */
  display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical;overflow:hidden}
.np-poster__meta{font-family:'JetBrains Mono',monospace;font-size:10.5px;letter-spacing:.06em;
  text-transform:uppercase;color:var(--fg2);line-height:1.5}

/* Narrow-card type corrections. Deliberately AFTER the .np-poster__title and
   .np-poster__mark--outline declarations above: a container query adds no
   specificity, so an identical single-class selector only wins on source order —
   the same block up at the --band-h tiers silently lost to these two rules. */
@container (max-width:220px){
  /* At the 2-up mobile floor (a 166px card, 128px of text) the desktop 19px title
     needs three lines for the longer names in the canonical set, and the 2-line
     clamp turns that into "Customer appreciation…" / "Grand opening blo…" — copy
     that reads as a bug. Drop the type, not the words — and where even 16.5px still
     needs a third line ("Customer appreciation night"), give it the third line
     rather than the ellipsis. Verified against the card box at 166px: tag + a
     3-line title + a 2-line meta + padding still clears the square. */
  .np-poster__title{font-size:16.5px;-webkit-line-clamp:3}
  /* 1.5px is a hairline on an 80px desktop mark and a smear on the ~15px mark this
     card allows: adjacent strokes merge and the glyph reads as a doubled digit. */
  .np-poster__mark--outline{-webkit-text-stroke-width:.75px}
}

/* --art (rematch: "bleed the art, never the label") — for a collage card that bleeds
   off an edge or sits partly behind a neighbour, where a half-visible title reads as
   clipped/broken rather than intentional ("Skills workshop" → "kills workshop"). Hides
   the body the same way .sr-only does: visually gone, still in the a11y tree, so the
   link keeps a real accessible name instead of going nameless. No title-band ceiling
   applies once there's no title to protect, so art cards can use the full card height. */
.np-poster--art{--band-h:100cqw}
.np-poster--art .np-poster__body{
  position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;
  clip:rect(0,0,0,0);white-space:nowrap;border:0;
}
.np-poster--art .np-poster__scrim{display:none}

/* Photo layer — opt-in via .np-poster--photo. Client-supplied photography replaces the
   generated wash/mark/motif; the grain and the category glow halo (the brand ties) stay
   as-is. Default (no --photo class, no <img>) is untouched, so a page renders generated
   art forever until a builder adds both the class AND a real file — no page can ever
   reference an image that doesn't exist yet.

   Markup (drops __wash/__mark/__motif vs. the generated-art anatomy above; everything
   else, including the category/tone/--art modifiers, is unchanged):
     <a class="np-poster np-poster--move np-poster--photo" data-category="move" href="/login">
       <img class="np-poster__photo" src="/img/templates/sunrise-founder-run.jpg"
            alt="" loading="lazy" decoding="async" />
       <div class="np-poster__grain"></div>
       <div class="np-poster__scrim"></div>
       <div class="np-poster__body">…unchanged…</div>
     </a>
   - alt="" — decorative; the card's accessible name already comes from .np-poster__title
     (or the --art aria-label), so the photo must not double-announce.
   - loading/decoding — "lazy"/"async" for every card below the fold (template grids).
     A hero card (index.html's above-the-fold collage) should use loading="eager"
     instead; nothing in CSS depends on the attribute, it's a per-instance call.
   - .np-poster--photo also force-hides __wash/__mark/__motif even if a builder leaves
     them in the markup by mistake — the photo is the artwork now, belt-and-suspenders.

   Unsupported combinations: --tone-b/-c/-d (targets __wash) and --fuse-nest/--fuse-horizon
   plus --outline (target __mark/__motif) become silent no-ops on a --photo card, since
   the elements they style are hidden. --art still works (full-bleed photo, no visible
   title, same as it does for generated art). Category (--move/--play/--travel/--orgs),
   hover lift, :focus-visible, and the 1:1 ratio are unaffected. */
.np-poster__photo{position:absolute;inset:0;width:100%;height:100%;object-fit:cover}
.np-poster--photo .np-poster__wash,
.np-poster--photo .np-poster__mark,
.np-poster--photo .np-poster__motif{display:none}
/* Taller, stronger bottom scrim — a client photo can be bright, busy or light-coloured,
   unlike the tuned generated wash, so title legibility can't rely on luck. Re-tuned for
   1:1 (rematch, square-ratio fix): a square card's body block starts as high as ~17% of
   card height at the narrow end of the grid (2-line-clamped title + tag + meta on a
   ~166px mobile card — see .np-poster__title's clamp), well above where the OLD 4:5-era
   scrim was still nearly transparent. Full-height now, staying clear over roughly the
   top tenth (per IMAGE-SPEC.md's "keep the busiest part of the photo up top") and ramping
   to strong opacity well before the body's earliest realistic start line. Verified against
   a plain near-white JPG and a busy high-contrast JPG at both 1440 and 390
   (_foundation-preview.html, web/img/templates/_placeholder-*.jpg). */
.np-poster--photo .np-poster__scrim{
  height:100%;
  background:linear-gradient(180deg,transparent 0%,transparent 8%,
    rgba(10,10,12,.55) 18%,rgba(10,10,12,.85) 42%,rgba(10,10,12,.96) 100%);
}

/* ── 7. Community card ──────────────────────────────────────────────────
   .np-community — wider, roster-forward. Deliberately not a poster.

   BRIEF fix #5: these are templates, not real communities, and a visitor can't tell
   the difference. "128 members · Next: Tue 6:00a" reads as invented traction the moment
   real communities ship alongside template ones. The foot line now carries FORMAT
   instead of fabricated numbers — cadence, typical group size, what actually happens —
   which is what a prospective host needs to picture themselves running it, and costs
   nothing to be honest about.

   <article class="np-community np-community--move">
     <div class="np-community__wash"></div><div class="np-community__grain"></div>
     <div class="np-community__body">
       <span class="np-community__tag">Move · Weekly, Tue + Sat</span>
       <h3 class="np-community__title">The 6am Run Club</h3>
       <p class="np-community__line">Same corner, same time, whoever wakes up.</p>
       <div class="np-community__foot">
         <span class="np-community__format">Small pack, all paces</span>
         <span>Drop in, no RSVP</span>
       </div>
     </div>
   </article>
   ────────────────────────────────────────────────────────────────────────── */
.np-community{
  position:relative;border-radius:var(--r);overflow:hidden;min-height:200px;
  background:var(--bg2);border:1px solid var(--border1);display:flex;
  transition:transform .22s var(--ease),border-color .22s var(--ease);
}
.np-community:hover,.np-community:focus-visible{transform:translateY(-4px);border-color:var(--border2)}
.np-community::before{content:'';position:absolute;top:0;left:14px;right:14px;height:1px;
  background:var(--glassHi);z-index:2;pointer-events:none}
.np-community__wash{position:absolute;inset:0;opacity:.32}
.np-community__grain{position:absolute;inset:0;background:url('/grain.png') repeat;opacity:.35;mix-blend-mode:overlay}
.np-community--move .np-community__wash{background:linear-gradient(100deg,var(--move-1) 0%,var(--bg2) 55%)}
.np-community--play .np-community__wash{background:linear-gradient(100deg,var(--play-1) 0%,var(--bg2) 55%)}
.np-community--travel .np-community__wash{background:var(--travel-wash);opacity:.4}
.np-community--orgs .np-community__wash{background:linear-gradient(100deg,var(--orgs-1) 0%,var(--bg2) 55%)}
.np-community__body{position:relative;z-index:1;padding:26px 28px;display:flex;flex-direction:column;
  gap:10px;justify-content:flex-end;width:100%}
.np-community__tag{font-family:'JetBrains Mono',monospace;font-size:10.5px;letter-spacing:.09em;
  text-transform:uppercase;color:var(--fg2)}
.np-community__title{font-family:'Space Grotesk',sans-serif;font-weight:600;font-size:clamp(21px,2.4vw,27px);
  letter-spacing:-.025em}
.np-community__line{font-size:14.5px;line-height:1.5;color:var(--fg2);max-width:40ch}
.np-community__foot{display:flex;align-items:center;gap:16px;margin-top:8px;padding-top:14px;
  border-top:1px solid var(--border1);font-family:'JetBrains Mono',monospace;font-size:11px;
  letter-spacing:.05em;text-transform:uppercase;color:var(--fg2)}
.np-community__format{color:var(--fg1)}
/* Two multi-word mono labels side by side need ~330px of clean run; a phone card's
   inner width is exactly that, so the pair wraps into two ragged two-line columns
   ("ONE TRAIL, ONE / SATURDAY" beside "BEGINNER / FRIENDLY"). Stack them instead. */
@media (max-width:560px){
  .np-community__foot{flex-direction:column;align-items:flex-start;gap:6px}
}

/* ── 8. Nav ────────────────────────────────────────────────────────────── */
nav{position:sticky;top:0;z-index:50;display:flex;align-items:center;justify-content:space-between;
  gap:16px;padding:14px 40px;background:rgba(26,26,32,.72);backdrop-filter:blur(20px);
  border-bottom:1px solid transparent;
  transition:border-color .22s var(--ease),transform .3s var(--ease)}
nav.stuck{border-bottom-color:var(--border1)}
/* Retracts on the way down so the pinned scroll sequences own the full frame,
   and comes straight back the moment you scroll up (site.js). transform only —
   animating height or top would relayout the whole page every frame. */
nav.nav--away{transform:translateY(-100%)}
@media (prefers-reduced-motion:reduce){nav.nav--away{transform:none}}
.word{font-family:'Space Grotesk',sans-serif;font-weight:600;font-size:21px;letter-spacing:-.03em;
  color:var(--fg1);text-decoration:none;display:inline-flex;align-items:center;gap:8px;min-height:44px}
.word img{width:24px;height:24px;object-fit:contain;flex:none}
.navcenter{display:flex;gap:2px;align-items:center;position:absolute;left:50%;transform:translateX(-50%)}
.navcenter a{font-family:'Manrope',sans-serif;font-weight:600;font-size:14.5px;color:var(--fg2);
  text-decoration:none;padding:8px 14px;border-radius:var(--r-sm);min-height:44px;display:flex;align-items:center;
  transition:color .12s var(--ease),background .12s var(--ease)}
.navcenter a:hover,.navcenter a[aria-current="page"]{color:var(--fg1);background:var(--glass)}
.navlinks{display:flex;gap:4px;align-items:center}
.navlinks .btn-glass{font-size:14px}
.navtoggle{display:none}

/* Mobile disclosure panel for the four verticals */
.navpanel{display:none}
.navpanel[data-open="true"]{
  display:flex;flex-direction:column;gap:2px;position:absolute;top:100%;left:0;right:0;
  padding:10px 22px 16px;background:rgba(26,26,32,.96);backdrop-filter:blur(20px);
  border-bottom:1px solid var(--border1);
}
.navpanel a{font-family:'Manrope',sans-serif;font-weight:600;font-size:16px;color:var(--fg1);
  text-decoration:none;padding:12px 4px;min-height:44px;display:flex;align-items:center;
  border-bottom:1px solid var(--border1)}
.navpanel a:last-child{border-bottom:none}

/* ── 9. Footer ─────────────────────────────────────────────────────────── */
footer{border-top:1px solid var(--border1);padding:36px 0 44px}
.frow{display:flex;align-items:center;justify-content:space-between;gap:20px;flex-wrap:wrap}
.flinks{display:flex;gap:22px;flex-wrap:wrap}
footer a{color:var(--fg2);text-decoration:none;font-size:14px;transition:color .12s var(--ease);
  display:inline-flex;align-items:center;min-height:44px;padding:4px 9px;margin:-4px -9px}
footer a:hover{color:var(--fg1)}
.fcopy{font-family:'JetBrains Mono',monospace;font-size:11px;letter-spacing:.07em;
  text-transform:uppercase;color:var(--fg2)}

/* ── 10. Motion ────────────────────────────────────────────────────────── */
.rv{opacity:0;transform:translateY(18px);
  transition:opacity .6s var(--ease),transform .6s var(--ease);transition-delay:var(--rv-delay,0ms)}
.rv.in{opacity:1;transform:none}

.np-poster,.np-community,.step,.rec{will-change:transform}

/* Rotation variance (TEARDOWN §9.6) — for scattered/collage arrangements, no two cards
   share an angle; a perfectly aligned grid of the same assets reads 3x cheaper. Wrap a
   set of posters/community cards in .np-scatter and give each a distinct .np-tilt-N —
   transform only, and the hover lift straightens the card back to 0deg as it lifts.
   Flattens to zero on mobile (§11).
   z-ordering discipline (rematch: a full titled card must never end up UNDER a
   neighbour's bleed): .np-poster already defaults to z-index:1 and raises to 5 on
   hover/focus so the card being looked at always comes to front; --art bleed/background
   cards sit at z-index:0 so an in-place, title-bearing card is never covered by one. For
   true absolute-position collage layouts (see the "collage" demo below), give each card
   an explicit inline z-index following its intended front-to-back order AND enough
   top/left spacing that titles don't land on top of each other — spacing is the
   builder's job (positions are per-instance by nature), the z-index floor is site.css's. */
.np-scatter{position:relative;isolation:isolate}
.np-tilt-1{transform:rotate(-3deg)}
.np-tilt-2{transform:rotate(2deg)}
.np-tilt-3{transform:rotate(-1.5deg)}
.np-tilt-4{transform:rotate(3.5deg)}
.np-tilt-5{transform:rotate(-2.5deg)}
.np-tilt-1:hover,.np-tilt-1:focus-visible,.np-tilt-2:hover,.np-tilt-2:focus-visible,
.np-tilt-3:hover,.np-tilt-3:focus-visible,.np-tilt-4:hover,.np-tilt-4:focus-visible,
.np-tilt-5:hover,.np-tilt-5:focus-visible{transform:translateY(-4px) rotate(0deg)}

@media (prefers-reduced-motion:reduce){
  html{scroll-behavior:auto}
  .rv{opacity:1;transform:none;transition:none}
  *,*::before,*::after{animation-duration:.01ms !important;animation-iteration-count:1 !important;
    transition-duration:.01ms !important;scroll-behavior:auto !important}
}

/* ── 11. Responsive ────────────────────────────────────────────────────── */
@media (max-width:1040px){
  .grid-3,.grid-4{grid-template-columns:repeat(2,1fr)}
}
@media (max-width:900px){
  section{padding:var(--section-space-sm) 0}
  .wrap{padding:0 22px}
  nav{padding:12px 22px}
  .navcenter{display:none}
  .navtoggle{display:inline-flex}
  .grid-2,.grid-3,.grid-4{grid-template-columns:1fr 1fr}
  .grid-posters{grid-template-columns:repeat(2,1fr)}
}
@media (max-width:560px){
  .navlinks .hide-sm{display:none}
  .word{font-size:19px}
  .grid-2,.grid-3,.grid-4{grid-template-columns:1fr}
  .grid-posters{grid-template-columns:repeat(2,1fr);gap:14px}
  /* Rotation variance is a desktop collage effect only — flatten on mobile. */
  .np-tilt-1,.np-tilt-2,.np-tilt-3,.np-tilt-4,.np-tilt-5{transform:none}
}

/* ── 12. Accessibility ─────────────────────────────────────────────────── */
.sr-only{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;
  clip:rect(0,0,0,0);white-space:nowrap;border:0}

a:focus-visible,button:focus-visible,.btn:focus-visible,.cta:focus-visible,
.np-poster:focus-visible,.np-community:focus-visible,input:focus-visible,[tabindex]:focus-visible{
  outline:2px solid var(--accent);outline-offset:2px;border-radius:var(--r-sm);
}
