/* ── Masonry portfolio — work.html's own stylesheet ──
   Promoted from an experimental work-masonry-test.html/.css/.js trio to the
   real work.html (the old flat-grid version this replaced is gone). Reuses
   the shared design tokens from css/styles.css (--text-color, --border-
   color, --ease-out, --padding-sides, .container, .type-*, .work-filters/
   .filter-* etc.) instead of redefining them — only what's genuinely new to
   this layout (the Masonry grid/cards and the project detail modal) lives
   here. */

/* ── Type overrides for this page ──
   Both roles keep everything else about .type-lead/.type-body-lg (size,
   line-height, color — css/styles.css) — only weight/family change, and
   only here, via this page's own stylesheet (index.html's .type-lead/
   .type-body-lg are untouched). .type-lead stays on the site's --font-main
   (Helvetica Neue) — its own font-weight:500 just wasn't bold, so this only
   adds that. .type-body-lg switches family to Inter at semibold (600),
   loaded in this page's own <head> (google fonts, weight 600 only) — its
   own explicit weight here, independent of the shared role's 400 in
   css/styles.css (only one Inter weight is loaded, so this must be set
   explicitly or the browser renders whatever weight IS loaded regardless of
   what's requested). */
.type-lead {
    font-weight: 700;
}
.type-body-lg {
    font-family: 'Inter', sans-serif;
    font-weight: 600;
}

/* ── Back button ──
   Was crowding the new "Our portfolio" lead right under it at the same
   .scroll-downcta size as a full CTA — shrunk ~30% (padding/font/icon/gap)
   and pinned to a compact, logo-matched width instead of stretching to the
   container (or to 100% under .process-portfolio-btn's own ≤768px rule,
   which this overrides via the inline width js/work.js sets —
   inline style always wins over an external stylesheet's media query).
   Width itself is measured off the header's own .logo-img at runtime, not
   a guessed px value, since that logo's own width is fluid (clamp-based)
   across viewports. Mirrored below Press & media (.wm-back-btn--bottom) —
   same class, same sizing/width, so both read as the same control. */
.wm-back-btn {
    /* .process-portfolio-btn (css/styles.css, loaded before this file but
       same specificity, so it still wins by source order without this)
       sets display:inline-block on this same element, silently overriding
       both .scroll-downcta's flex and .portfolio-back-btn's inline-flex —
       gap had no effect at all under inline-block, so the icon and "Volver"
       sat apart by whatever incidental inline whitespace the markup
       happened to have, not a real centered gap. Restoring inline-flex here
       (this file loads after styles.css, so it wins) makes the icon+text
       lay out as the centered flex row the other two rules already
       intended. */
    display: inline-flex;
    align-items: center;
    padding: 0.77rem 0.875rem;
    font-size: 0.6rem;
    gap: 0.35rem;
    margin-bottom: 1.1rem;
}
.wm-back-btn svg {
    width: 13px;
    height: 13px;
}
.wm-back-btn--bottom {
    margin-top: 2rem;
    margin-bottom: 0;
}

/* ── Portfolio intro: proper lead→body-lg→filters rhythm ──
   .type-lead/.type-body-lg's own line-height (1.25/1.5) was already correct
   per their role — what was missing were the gaps BETWEEN them: both had
   margin:0 (their canonical role only sets line-height/font/weight, never
   spacing — every other consumer supplies its own, e.g. #process
   .type-lead{margin-bottom:1.5rem} in css/styles.css), so "Our portfolio"
   sat flush against the line below it, and that line sat flush against the
   filter bar right under it. Same 1.5rem lead→body gap already used
   elsewhere (About/Process/Network); a bigger 2rem below the body line
   since the filters are a structurally different block, not more body
   text. */
#wm-portfolio .type-lead {
    margin-bottom: 0.75rem;
}
#wm-portfolio .type-body-lg {
    margin-bottom: 2rem;
}

/* ── Contact section: center the logo between its two border lines ──
   .contact-section's own padding is --section-padding (72-128px) on top vs
   --section-end-space (3rem/54px) on bottom (css/styles.css) — a deliberate
   asymmetry for sections that open with a heading, but here the logo is
   the section's only content, so that same gap makes it sit visibly closer
   to the footer's top border than to this section's own top border.
   Matching top to the same --section-end-space bottom already uses centers
   it between the two exactly. #contact + .contact-section (id+class) beats
   the shared rule's plain class on specificity, no !important needed. */
#contact.contact-section {
    padding-top: var(--section-end-space);
}

/* ── Contact section: no active-section accent border here ──
   #contact.section-active (css/styles.css) sets border-color: --section-
   accent (blue for #contact, set by main.js's sitewide scroll-spy) once it
   scrolls into view — a shorthand, so it recolors BOTH the permanent 1px
   gray top divider (.contact-section's own border-top, every section has
   one) AND the 2px left accent line, not just the left one. Meaningful on
   index.html, where it matches the corresponding nav link lighting up, but
   this page has no such nav link for a bare logo-only #contact, so blue
   just reads as a stray border either place. Left goes fully transparent
   (matches its own resting state); top is restored to the same neutral
   --border-color it always has outside of .section-active, instead of
   also going transparent (which would erase that permanent divider
   whenever this section happens to be active). Same specificity, later
   stylesheet — wins without !important. */
#contact.section-active {
    border-left-color: transparent;
    border-top-color: var(--border-color);
}

/* ── Press & media modal: same signature top stripe as every project cover ──
   #all-press-modal reuses the shared .coverage-modal/.press-modal chrome
   (css/styles.css) as-is — this only adds the one accent this specific
   instance was missing, scoped to its own id so index.html's copy of that
   same modal (which doesn't load this stylesheet at all) stays exactly as
   it is. */
#all-press-modal .coverage-modal {
    position: relative;
    border-radius: clamp(16px, 2vw, 20px);
    overflow: hidden;
    padding-top: calc(2.5rem + 4px);
}
#all-press-modal .coverage-modal::before {
    content: '';
    position: absolute;
    top: 0;
    left: 0;
    right: 0;
    height: 4px;
    background: linear-gradient(to right, var(--color-blue), var(--color-green), var(--color-yellow), var(--color-red));
}
#all-press-modal .coverage-modal-links a {
    border-radius: 10px;
}

/* ── Filter colors ──
   The 4 filter controls (Category/Capability/Year/Most recent) each get one
   of the site's own 4 signature accent colors — the same 4 hex values
   already used for .text-highlight-*, .work-pager-num.active's blue and
   .filter-dd-value's blue (css/styles.css) — instead of the plain neutral
   border every .filter-dd-trigger/.filter-chip gets by default. Scoped by
   id (dropdowns) and by #wm-sort-chip (an id js/work.js sets
   on the sort chip it builds) — no new classes, same "id + existing
   selector" mechanism used throughout this codebase. */
#wm-dropdown-category .filter-dd-trigger { border-color: var(--color-blue); }
#wm-dropdown-category .filter-dd-trigger:hover,
#wm-dropdown-category .filter-dd-trigger.has-value {
    border-color: var(--color-blue);
    background: color-mix(in srgb, var(--color-blue) 14%, transparent);
    color: var(--text-color);
}
#wm-dropdown-category .filter-dd-option.active { background: color-mix(in srgb, var(--color-blue) 22%, transparent); }

#wm-dropdown-capability .filter-dd-trigger { border-color: var(--color-green); }
#wm-dropdown-capability .filter-dd-trigger:hover,
#wm-dropdown-capability .filter-dd-trigger.has-value {
    border-color: var(--color-green);
    background: color-mix(in srgb, var(--color-green) 14%, transparent);
    color: var(--text-color);
}
#wm-dropdown-capability .filter-dd-value { color: var(--color-green); }
#wm-dropdown-capability .filter-dd-option.active { background: color-mix(in srgb, var(--color-green) 22%, transparent); }

#wm-dropdown-year .filter-dd-trigger { border-color: var(--color-yellow); }
#wm-dropdown-year .filter-dd-trigger:hover,
#wm-dropdown-year .filter-dd-trigger.has-value {
    border-color: var(--color-yellow);
    background: color-mix(in srgb, var(--color-yellow) 14%, transparent);
    color: var(--text-color);
}
#wm-dropdown-year .filter-dd-value { color: var(--color-yellow); }
#wm-dropdown-year .filter-dd-option.active { background: color-mix(in srgb, var(--color-yellow) 22%, transparent); }

#wm-sort-chip { border-color: var(--color-red); }
#wm-sort-chip:hover {
    border-color: var(--color-red);
    background: color-mix(in srgb, var(--color-red) 14%, transparent);
    color: var(--text-color);
}
#wm-sort-chip.active {
    background: var(--color-red);
    border-color: var(--color-red);
    color: #fff;
}

/* ── Grid ──
   Position:relative wrapper; js/work.js absolutely positions
   each .wm-card inside it (column-packed layout) and sets the wrapper's own
   height to match the tallest column — a plain block/grid layout can't do
   the "pack into whichever column is currently shortest" placement this
   needs, and a CSS multi-column layout can't animate a card moving from one
   column to another when a filter changes. Height transitions so the empty
   space left by a shrinking filter closes smoothly instead of snapping. */
.wm-grid {
    position: relative;
    width: 100%;
    transition: height 0.35s var(--ease-out);
}

/* ── Card ──
   Positioned via inline transform (translate3d, set in JS) rather than
   top/left — transform is what the browser can animate on its own compositor
   thread, so re-packing after a filter/sort/resize change is a smooth glide,
   not a jump. Width is also set inline per card (its column's own width).
   opacity/transform here double as the entrance-reveal + hover feedback +
   filtered-out hide, all through the same couple of properties. */
.wm-card {
    position: absolute;
    top: 0;
    left: 0;
    /* Belt-and-braces before JS sets each card's real per-column width: an
       absolutely positioned box with only left/top (no right) shrink-to-fits
       within its containing block anyway, but capping it here too means a
       card can never briefly render wider than the grid even in the instant
       before the first layout() call runs. */
    max-width: 100%;
    display: block;
    background: var(--surface-color-2);
    /* Same rounding as #faq-feature (clamp(16px, 2vw, 20px)) — border color
       stays the plain neutral --border-color though, not that card's
       --faq-glow-tinted one; this card has no glow/color of its own. */
    border-radius: clamp(16px, 2vw, 20px);
    overflow: hidden;
    border: 1px solid var(--border-color);
    opacity: 0;
    transform: translate3d(0, 24px, 0);
    transition:
        transform 0.5s var(--ease-out),
        opacity 0.5s var(--ease-out),
        border-color 0.2s ease,
        box-shadow 0.2s ease;
    will-change: transform;
}
/* Entrance + steady state: JS adds this once a card's initial position has
   been computed and it has scrolled into view, and again after every
   layout() call while it stays a match — see setCardPosition()'s use of
   translate3d in the transform it writes directly to style.transform.
   is-hidden (filtered out) fades it down and out without display:none, so
   the transform transition above still governs how it leaves. */
.wm-card.is-placed {
    opacity: 1;
}
.wm-card.is-hidden {
    opacity: 0;
    pointer-events: none;
}

/* ── The card's own click/keyboard target (Phase: capabilities "+") ──
   A real <button>, stretched over the whole card via position:absolute —
   the standard "clickable card" pattern. Needed once the card gained a
   SECOND, independent interactive control (the "+N" capabilities chip
   below): the old article[role=button][tabindex=0] would have made that
   a button nested inside an element already exposed as a button to
   assistive tech, which the spec explicitly rules out. Now .wm-card
   itself carries no interactive role at all — .wm-card-open is the real,
   sole "open this project" control, and .wm-card-capability-more (see
   below) is a genuinely separate, independently-focusable sibling, not a
   button trapped inside a button. Transparent background: it sits above
   the cover/text in the stacking order (position:absolute always paints
   over position:static siblings, regardless of DOM order) but being
   fully transparent, everything under it stays visible exactly as
   painted — it's a hit-target, not a visible layer. z-index 1 keeps it
   below .wm-card-capability-more's own z-index 2, so the "+" stays
   independently clickable/focusable above it where the two visually
   overlap. */
.wm-card-open {
    position: absolute;
    inset: 0;
    z-index: 1;
    width: 100%;
    height: 100%;
    background: transparent;
    border: 0;
    padding: 0;
    margin: 0;
    cursor: pointer;
    -webkit-tap-highlight-color: transparent;
}
.wm-card-open:focus-visible {
    outline: 2px solid var(--text-color);
    outline-offset: -2px;
}
/* :focus-within, not :focus-visible — the element that's actually
   focused is .wm-card-open or .wm-card-capability-more, both children,
   never .wm-card itself now. :hover still applies straight to .wm-card
   from a pointer over any descendant (standard CSS bubbling), so it
   doesn't need the same treatment. */
.wm-card:hover,
.wm-card:focus-within {
    border-color: var(--border-strong);
    box-shadow: 0 12px 28px rgba(0, 0, 0, 0.35);
}
.wm-card:hover .wm-card-img,
.wm-card:focus-within .wm-card-img {
    transform: scale(1.015);
}

/* Fixed 16:9 for every cover, so the media block is identical across cards
   regardless of each source photo's own natural ratio — a real equal-height
   grid needs this, not just the text reservations below. Confirmed safe
   (checked every one of the 23 covers' natural dimensions first): 22 of 23
   are already ~1.74–1.78, essentially 16:9 already, so object-fit: cover
   trims a sliver or nothing; the one outlier (share-image.png, 1.95, a
   centered logo on black with wide empty margin top and bottom) has ample
   clearance before a 16:9 crop would ever reach the logo itself. */
.wm-card-media {
    position: relative;
    overflow: hidden;
    aspect-ratio: 16 / 9;
    background: var(--surface-color-2);
    line-height: 0;
}
.wm-card-img {
    display: block;
    width: 100%;
    height: 100%;
    object-fit: cover;
    transition: transform 0.4s var(--ease-out);
}

/* Clearly-visible "this one plays" marker, bottom-right of the cover
   (Phase: video indicator round 3 — Patchwork yellow with a black ring,
   per direct request) — one self-contained SVG (circle + black ring +
   triangle, all drawn as vector shapes in a single 64×64 viewBox), not a
   CSS background-color + padding box: an earlier padding-based attempt
   silently clamped to a near-invisible ~20×18px against the sitewide
   `* { box-sizing: border-box }` reset once padding was added on top of a
   small declared size — a shape-based SVG has no such box-model trap,
   its own width/height just scales the whole graphic. Yellow circle +
   black ring (separates the icon from light-colored covers, where a
   white ring would blend in) + a dark triangle (not white —
   white-on-yellow measures under 2:1 contrast, close to unreadable; the
   dark triangle clears 8:1+ against this same yellow). Still a
   scrim-free, small secondary marker
   — not a full-image overlay or a giant centered button — the cover
   itself keeps reading as the cover. aria-hidden in the markup either
   way (decorative): the card's own accessible name carries "this plays a
   video" for assistive tech (see ensureVideoAccessibleName() in
   js/work.js), never this icon alone — Vimeo and YouTube
   cards get the identical treatment, no per-source styling. z-index is
   explicit since it sits above the cover image in the same stacking
   context. */
.wm-play-icon {
    position: absolute;
    right: clamp(8px, 2.4vw, 14px);
    bottom: clamp(8px, 2.4vw, 14px);
    z-index: 3;
    pointer-events: none;
}
.wm-play-icon svg {
    display: block;
    /* ~56–64px on desktop-sized columns, tapering down but never below
       38px even at the narrowest single-column mobile width — clearly
       bigger than the ~32px (and, before that, ~20px) it used to render
       at, per the spec's own floor. Two successive 10% cuts since, for the
       neutral-gray minimal restyle (38/60→34/54→31/49, 7vw→6.3vw→5.7vw). */
    width: clamp(31px, 5.7vw, 49px);
    height: clamp(31px, 5.7vw, 49px);
    filter: drop-shadow(0 1px 5px rgba(0, 0, 0, 0.4));
    transition: transform 0.2s ease;
}
.wm-card:hover .wm-play-icon svg,
.wm-card:focus-within .wm-play-icon svg {
    transform: scale(1.06);
}

/* Equal-height cards, without a mid-card gap (Phase: card restructure
   round 3) — the round 2 approach (a separate measured min-height per
   field: brand/title/categories/capabilities/location·year) equalized
   the TOTAL right enough, but did it by padding out whichever field a
   given project happened to be shorter on, wherever that field sat in
   the stack — visibly, that meant a big empty gap between a short
   1-line title and the category sitting right under it, since title was
   the field being padded. Replaced with ONE aggregate reservation on
   .wm-card-info itself (--wm-info-h, measured in
   measureSharedHeights()): every field below keeps a normal, constant
   margin and renders at its own natural height, so short content packs
   tightly from the top exactly like it visually should. Flexbox's own
   default (justify-content: flex-start, nothing else set) then puts
   whatever slack is needed to reach that reserved total AFTER the last
   child — the description preview — instead of splitting it awkwardly
   between fields partway down the card. */
.wm-card-info {
    display: flex;
    flex-direction: column;
    min-height: var(--wm-info-h, auto);
    padding: 1.1rem 1.25rem 1.4rem;
}
.wm-card-brand {
    font-size: 0.72rem;
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: 0.09em;
    color: var(--text-muted);
    /* Brand → title kept tight and compact on purpose — they read as one
       "who/what" unit, categories below are a distinct second unit. */
    margin-bottom: 0.3rem;
}
.wm-card-title {
    font-size: 1.15rem;
    font-weight: 700;
    line-height: 1.2;
    margin-bottom: 0.35rem;
    color: var(--text-color);
}
.wm-card-cats {
    font-size: 0.78rem;
    color: var(--text-muted);
    font-family: monospace;
    margin-bottom: 0.55rem;
}
/* Capabilities — up to 3 compact chips + a "+N" chip when there are more
   (Phase: capabilities limit). data-capabilities is still the only
   source for both this and the capability filter (js/work.js) — every
   real capability is rendered here (so the popup can
   clone the complete, untruncated set — see .wm-modal-capabilities),
   this just visually hides the 4th chip onward WITHIN THE CARD; nothing
   is removed from the DOM or duplicated as a second list. */
.wm-card-capabilities {
    display: flex;
    flex-wrap: wrap;
    align-content: flex-start;
    gap: 0.35rem;
    margin-bottom: 0.55rem;
}
.wm-card-capability-chip {
    font-size: 0.68rem;
    line-height: 1.4;
    padding: 0.15rem 0.5rem;
    border-radius: 999px;
    border: 1px solid var(--border-color);
    color: var(--text-muted);
    white-space: nowrap;
}
/* Chips past the 3rd — kept in the DOM (see the comment above) so the
   popup can show the full set from the same elements, just not painted
   inside the card itself. */
.wm-card-capabilities .wm-card-capability-chip--overflow {
    display: none;
}
/* The "+N" itself is a real, independently focusable <button> — not a
   4th plain chip — since it needs to open the same popup on its own,
   without becoming a button nested inside another interactive element
   (see .wm-card-open in the Card section above for why that's avoided
   entirely: .wm-card itself carries no button/role now). Same visual
   footprint as a chip, styled as its own small button. */
.wm-card-capability-more {
    position: relative;
    z-index: 2;
    font: inherit;
    font-size: 0.68rem;
    font-weight: 700;
    line-height: 1.4;
    padding: 0.15rem 0.55rem;
    border-radius: 999px;
    border: 1px solid var(--border-strong);
    background: var(--surface-color-2);
    color: var(--text-color);
    cursor: pointer;
    transition: border-color 0.2s ease, background 0.2s ease;
}
.wm-card-capability-more:hover,
.wm-card-capability-more:focus-visible {
    border-color: var(--color-yellow);
    background: color-mix(in srgb, var(--color-yellow) 16%, transparent);
}
.wm-card-capability-more:focus-visible {
    outline: 2px solid var(--text-color);
    outline-offset: 1px;
}
.wm-card-locyear {
    font-size: 0.78rem;
    color: var(--text-muted);
    font-family: monospace;
    margin-bottom: 0.55rem;
}
.wm-card-desc {
    font-size: 0.92rem;
    line-height: 1.45;
    /* var(--text-muted) instead of var(--text-color) + opacity — opacity on
       this element would composite its .text-highlight-* children too,
       dulling the Patchwork brand blue/green/yellow/red down toward the
       card's dark background instead of showing the true, full-saturation
       color used everywhere else on the site (About, Process, etc. never
       wrap their highlighted text in an opacity<1 ancestor). A muted color
       on the plain text only achieves the same dimmed-preview look without
       touching the highlight spans' own explicit colors at all. */
    color: var(--text-muted);
    /* Preview only — first line, ellipsis (Phase: 1-line preview), full
       text stays in the DOM (and in the popup, via #wm-modal-desc's own
       unclamped copy) so this reacts to language switches for free and
       never duplicates content. A fixed height, not min-height, and
       flex-shrink:0 — caught live in the 3-line version this replaced: a
       -webkit-line-clamp box's contribution to a flex column's height can
       track its own unclamped (pre-clip) content length in some engines
       even though it always PAINTS at the same clamped height, so two
       cards with equally short/long visible previews still ended up with
       different total card heights. A hard height leaves no such
       ambiguity for flex to resolve. */
    height: calc(0.92rem * 1.45 * 1);
    flex-shrink: 0;
    display: -webkit-box;
    -webkit-line-clamp: 1;
    -webkit-box-orient: vertical;
    overflow: hidden;
    text-overflow: ellipsis;
}

.wm-empty {
    display: none;
    padding: 3rem 0;
    text-align: center;
    color: var(--text-muted);
    font-size: 0.95rem;
}
.wm-empty.show {
    display: block;
}

@media (prefers-reduced-motion: reduce) {
    .wm-card {
        transition: opacity 0.2s ease;
    }
    .wm-card-img {
        transition: none;
    }
    .wm-grid {
        transition: none;
    }
}

/* ── Project modal ──
   Same overlay chrome as the site's other modals (.coverage-modal-overlay /
   .contact-modal-overlay in css/styles.css: fixed scrim + blur, opacity/
   transform transition) — reproduced here under its own wm- classes instead
   of reusing those directly, since this modal's internal layout (media +
   prev/next arrows either side) is its own new shape. */
.wm-modal-overlay {
    position: fixed;
    inset: 0;
    background: rgba(0, 0, 0, 0.88);
    backdrop-filter: blur(6px);
    z-index: 500;
    display: flex;
    align-items: center;
    justify-content: center;
    padding: 2rem;
    opacity: 0;
    pointer-events: none;
    transition: opacity 0.25s ease;
}
.wm-modal-overlay.open {
    opacity: 1;
    pointer-events: all;
}
.wm-modal {
    position: relative;
    background: var(--surface-color);
    border: 1px solid var(--border-color);
    width: min(920px, 100%);
    max-height: min(860px, 92vh);
    display: flex;
    flex-direction: column;
    transform: translateY(16px) scale(0.98);
    transition: transform 0.25s ease;
    outline: none;
}
.wm-modal-overlay.open .wm-modal {
    transform: translateY(0) scale(1);
}
.wm-modal-scroll {
    overflow-y: auto;
}
.wm-modal-media {
    position: relative;
    width: 100%;
    aspect-ratio: 16 / 9;
    background: #000;
    flex-shrink: 0;
}
.wm-modal-media img,
.wm-modal-media iframe {
    position: absolute;
    inset: 0;
    width: 100%;
    height: 100%;
    border: 0;
    object-fit: contain;
    background: #000;
}
.wm-modal-body {
    padding: 1.75rem 2rem 2.25rem;
}
.wm-modal-brand {
    display: block;
    font-size: 0.78rem;
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: 0.09em;
    color: var(--text-muted);
    margin-bottom: 0.5rem;
}
.wm-modal-title {
    font-size: clamp(1.4rem, 2.4vw, 1.9rem);
    font-weight: 700;
    line-height: 1.15;
    margin-bottom: 0.5rem;
    color: var(--text-color);
}
.wm-modal-cats {
    font-size: 0.85rem;
    color: var(--text-muted);
    font-family: monospace;
    margin-bottom: 0.75rem;
}
.wm-modal-capabilities {
    display: flex;
    flex-wrap: wrap;
    gap: 0.4rem;
    margin-bottom: 0.9rem;
}
.wm-modal-capabilities .wm-card-capability-chip {
    font-size: 0.78rem;
}
.wm-modal-locyear {
    font-size: 0.85rem;
    color: var(--text-muted);
    font-family: monospace;
    margin-bottom: 1rem;
}
.wm-modal-desc {
    font-size: 1rem;
    line-height: 1.6;
    /* var(--text-muted) instead of var(--text-color) + opacity — same fix
       as .wm-card-desc above, same reason: opacity here would dull the
       .text-highlight-* spans' true brand colors along with the plain text. */
    color: var(--text-muted);
    max-width: 68ch;
}
.wm-modal-youtube {
    margin-top: 1.25rem;
    display: inline-flex;
    align-items: center;
    gap: 0.6rem;
    padding: 0.7rem 1.1rem;
    border: 1px solid var(--border-color);
    color: var(--text-color);
    font-size: 0.82rem;
    font-weight: 600;
    text-transform: uppercase;
    letter-spacing: 0.04em;
    transition: border-color 0.2s ease, background 0.2s ease;
}
.wm-modal-youtube:hover {
    border-color: var(--text-color);
    background: var(--hover-tint);
}
.wm-modal-youtube[hidden] {
    display: none;
}

/* "About the project" / "Press & Social" — see js/work.js's renderActions().
   .project-btn/.coverage-modal-label/.coverage-modal-links are shared,
   unscoped classes from css/styles.css (same ones the aggregate "Press &
   media" modal below uses) — this only adds the vertical rhythm specific to
   sitting inside .wm-modal-body, under the YouTube link (margin-top matches
   it) and above the coverage link list once one exists. Renders empty (no
   margin needed) when a project has neither an about link nor coverage
   links — nothing here forces a gap when #wm-modal-actions has no children. */
.wm-modal-actions {
    margin-top: 1.25rem;
    display: flex;
    flex-direction: column;
    gap: 0.9rem;
}
.wm-modal-actions:empty {
    margin-top: 0;
}

.wm-modal-close {
    position: absolute;
    top: 0.75rem;
    right: 0.9rem;
    z-index: 2;
    min-width: 44px;
    min-height: 44px;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    background: rgba(0, 0, 0, 0.4);
    border: none;
    border-radius: 50%;
    color: #fff;
    font-size: 1.4rem;
    line-height: 1;
    cursor: pointer;
    transition: background 0.2s ease;
}
.wm-modal-close:hover {
    background: rgba(0, 0, 0, 0.65);
}

.wm-modal-nav {
    position: absolute;
    top: 50%;
    transform: translateY(-50%);
    z-index: 2;
    min-width: 48px;
    min-height: 48px;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    background: rgba(0, 0, 0, 0.4);
    border: none;
    border-radius: 50%;
    color: #fff;
    font-size: 1.8rem;
    line-height: 1;
    cursor: pointer;
    transition: background 0.2s ease, opacity 0.2s ease;
}
.wm-modal-nav:hover {
    background: rgba(0, 0, 0, 0.65);
}
.wm-modal-nav:disabled {
    opacity: 0;
    pointer-events: none;
}
.wm-modal-prev {
    left: -1.5rem;
}
.wm-modal-next {
    right: -1.5rem;
}

@media (max-width: 720px) {
    .wm-modal-overlay {
        padding: 0;
    }
    .wm-modal {
        width: 100%;
        height: 100%;
        max-height: none;
        border: none;
    }
    .wm-modal-media {
        aspect-ratio: 16 / 10;
    }
    .wm-modal-body {
        padding: 1.4rem 1.25rem 2rem;
    }
    .wm-modal-close {
        top: 0.6rem;
        right: 0.6rem;
    }
    .wm-modal-prev {
        left: 0.5rem;
    }
    .wm-modal-next {
        right: 0.5rem;
    }
    .wm-modal-nav {
        top: auto;
        bottom: 0.9rem;
        transform: none;
        background: rgba(0, 0, 0, 0.55);
    }
    .wm-modal-prev {
        bottom: 0.9rem;
    }
}

@media (prefers-reduced-motion: reduce) {
    .wm-modal-overlay,
    .wm-modal {
        transition: none;
    }
}
