/**
 * adeiv-theme.css
 * ADEIV Studio WordPress Theme — Main Stylesheet
 *
 * Design System: Experimental Minimalism + Swiss Brutalism
 * Palette: Strict monochrome + Chroma Key accents (#00FF00 / #0000FF)
 * Typography: Montserrat / Hanken Grotesk + JetBrains Mono
 * No Tailwind — pure vanilla CSS with custom properties.
 */

/* ═══════════════════════════════════════════════════════════
   0. CSS CUSTOM PROPERTIES (Design Tokens)
═══════════════════════════════════════════════════════════ */
:root {
    /* Colours */
    --color-primary:            #000000;
    --color-on-primary:         #ffffff;
    --color-surface:            #f9f9f9;
    --color-surface-container:  #eeeeee;
    --color-on-surface:         #1a1c1c;
    --color-secondary:          #5f5e5e;
    --color-outline-variant:    rgba(210, 210, 210, 0.5);
    --color-border:             #d2d2d2;
    --color-text:               #333333;
    --color-text-muted:         #8f8f8f;
    --color-chroma-green:       #00FF00;
    --color-chroma-blue:        #0000FF;

    /* RGB triplets — needed so the translucent overlays below can share
       the same base color as the solid ones instead of duplicating a hex
       value as separate rgba() numbers scattered through the file. */
    --color-black-rgb:              0, 0, 0;
    --color-white-rgb:              255, 255, 255;
    --color-grid-line-rgb:          180, 180, 180;

    /* Semantic overlays — built from the RGB triplets above.
       Change the base color once (e.g. --color-surface-container) and
       update its matching -rgb line to keep these in sync. */
    --color-overlay-strong:       rgba(var(--color-black-rgb), 0.80);   /* image caption gradients — dark end */
    --color-overlay-soft:         rgba(var(--color-black-rgb), 0.35);   /* image caption gradients — fade end */
    --color-overlay-caption:      rgba(var(--color-black-rgb), 0.45);   /* gallery/split-block image caption gradient — lighter than the archive card overlay above */
    --color-card-glass:           rgba(var(--color-white-rgb), 0.40);   /* sandbox card resting background */
    --color-card-glass-hover:     rgba(var(--color-white-rgb), 0.90);   /* sandbox card hover background */
    --color-grid-line:            rgba(var(--color-grid-line-rgb), 0.18);

    /* Typography */
    --font-sans:   'Montserrat', 'Hanken Grotesk', 'Noto Sans TC', 'Noto Sans CJK TC', sans-serif;
    /* 'Noto Sans TC' inserted before the generic 'monospace' fallback:
       JetBrains Mono/Courier New have no CJK glyphs at all, so any
       Chinese text painted through a --font-mono element (tag labels,
       the Experience year, "no results" copy, pagination) fell straight
       through to the browser's own default system font once the zh-TW
       translation was live — the design system wasn't actually skipping
       the Chinese version, it just had no CJK-capable font left to fall
       back to in this one variable (--font-sans already had Noto Sans TC
       and was fine). Same family --font-sans already loads via
       inc/enqueue.php, so this adds no new font request. */
    --font-mono:   'JetBrains Mono', 'Noto Sans TC', 'Courier New', monospace;

    /* Spacing */
    --space-xs:  4px;
    --space-sm:  8px;
    --space-md:  16px;
    --space-lg:  24px;
    --space-xl:  40px;
    --space-2xl: 64px;
    --space-3xl: 96px;

    /* Layout */
    --header-height:   72px;
    --container-width: 90%;
    --max-width:       1800px;

    /* Transitions */
    --transition-fast:   0.2s ease;
    --transition-medium: 0.4s ease;
    --transition-slow:   0.7s cubic-bezier(0.16, 1, 0.3, 1);

    /* Z-Index Scale — the site's full stacking order, low to high, in one
       place. Before this, every layer picked its own arbitrary number
       (500, 900, 1000, 9999...) with no record of why, so a future new
       overlay had nothing to check its number against — it could easily
       land between two existing layers and silently break the stacking
       order (show under something it should cover, or cover something
       it shouldn't). These tokens are the SAME numeric values every
       layer already used — nothing on the site visually changes — but
       now named and ordered so a future addition picks the token that
       matches its intended place instead of inventing a number.
       To add a new layer: decide where it belongs in this list, use the
       token already there if it's the same kind of thing (e.g. another
       full-screen modal joins --z-modal), or add a new token in the
       right position (with room to spare — the gaps here are wide on
       purpose) if it's genuinely a new kind of layer. */
    --z-background:     0;     /* decorative background behind all real content — .grid-bg */
    --z-base:            1;     /* the normal page content stacking context — .site-wrapper */
    --z-local-raised:   10;     /* raised one step within an ALREADY-scoped local context (a card, a hero section) — never competes with the global layers below it; reused freely by unrelated components — .canvas-container, .project-card-overlay */
    --z-local-top:      20;     /* the top layer within that SAME kind of local context, above --z-local-raised — .hero-section (above .canvas-container) */
    --z-footer:         50;     /* .site-footer */
    --z-header:        500;     /* .site-header (sticky) */
    --z-nav-overlay:   900;     /* full-screen mobile menu — .mobile-nav-overlay */
    --z-modal:        1000;     /* a full-screen modal/dialog above the nav overlay — .lightbox-overlay; any future modal (a share dialog, a confirmation) belongs here too */
    --z-cursor:       9999;     /* the custom cursor — always above ordinary content and modals — .crosshair-h, .crosshair-v */
    --z-cursor-top:  10000;     /* the cursor's own dot, above the cursor's crosshair lines — .crosshair-dot */
}

/* ═══════════════════════════════════════════════════════════
   1. RESET & BASE
═══════════════════════════════════════════════════════════ */
*, *::before, *::after {
    box-sizing: border-box;
    margin: 0;
    padding: 0;
}

html {
    font-size: 16px;
    scroll-behavior: smooth;
    -webkit-font-smoothing: antialiased;
    -moz-osx-font-smoothing: grayscale;
}

body {
    font-family: var(--font-sans);
    background-color: var(--color-surface);
    color: var(--color-text);
    cursor: none;       /* hide default cursor — crosshair replaces it */
    user-select: none;
    overflow-x: hidden;
    min-height: 100vh;
}

/* Allow text selection in article content areas */
.project-body,
.project-article p,
.about-section .section-content-col,
.post-card-excerpt {
    user-select: text;
}

a {
    color: inherit;
    text-decoration: none;
}

img, video {
    display: block;
    max-width: 100%;
    height: auto;
}

button {
    background: transparent;
    border: none;
    font-family: inherit;
    cursor: none;
}

ul, ol { list-style: none; }

/* ═══════════════════════════════════════════════════════════
   2. CROSSHAIR CURSOR
═══════════════════════════════════════════════════════════ */
.crosshair-h,
.crosshair-v {
    position: fixed;
    pointer-events: none;
    z-index: var(--z-cursor);
    background-color: var(--color-secondary);
    opacity: 0.5;
    transition: opacity var(--transition-fast);
}

.crosshair-h {
    height: 1px;
    width: 100vw;
    left: 0;
    top: -1px; /* positioned by JS */
}

.crosshair-v {
    width: 1px;
    height: 100vh;
    top: 0;
    left: -1px; /* positioned by JS */
}

.crosshair-dot {
    position: fixed;
    width: 8px;
    height: 8px;
    background-color: var(--color-chroma-green);
    pointer-events: none;
    z-index: var(--z-cursor-top);
    transform: translate(-50%, -50%);
    transition: transform var(--transition-fast), background-color var(--transition-fast);
}

.crosshair-dot--hover {
    transform: translate(-50%, -50%) scale(2.5);
    background-color: var(--color-chroma-green);
}

/* Touch/no-hover devices never fire mousemove, so the crosshair would
   otherwise sit frozen dead-center on every page forever. Hide it and
   restore the normal cursor there instead. */
@media (hover: none), (pointer: coarse) {
    .crosshair-h,
    .crosshair-v,
    .crosshair-dot {
        display: none !important;
    }

    body,
    button {
        cursor: auto;
    }
}

/* ═══════════════════════════════════════════════════════════
   3. KINETIC GRID BACKGROUND
═══════════════════════════════════════════════════════════ */
.grid-bg {
    position: fixed;
    inset: -25%;           /* oversized so parallax edges are hidden */
    width: 150%;
    height: 150%;
    background-image:
        linear-gradient(var(--color-grid-line) 1px, transparent 1px),
        linear-gradient(90deg, var(--color-grid-line) 1px, transparent 1px);
    background-size: 40px 40px;
    pointer-events: none;
    z-index: var(--z-background);
    transform-origin: center;
    transform-style: preserve-3d;
    will-change: transform;
    animation: gridBreathe 12s ease-in-out infinite;
}

@keyframes gridBreathe {
    0%, 100% { opacity: 0.45; }
    50%       { opacity: 0.65; }
}

/* ═══════════════════════════════════════════════════════════
   4. SITE WRAPPER & LAYOUT
═══════════════════════════════════════════════════════════ */
.site-wrapper {
    position: relative;
    z-index: var(--z-base);
    display: flex;
    flex-direction: column;
    min-height: 100vh;
}

.site-main {
    flex: 1;
    position: relative;
    overflow-x: hidden;
}

/* ═══════════════════════════════════════════════════════════
   5. SITE HEADER
═══════════════════════════════════════════════════════════ */
.site-header {
    position: sticky;
    top: 0;
    z-index: var(--z-header);
    background-color: var(--color-surface);
    border-bottom: 1px solid var(--color-border);
    padding: 12px 0;
}

.header-inner {
    display: flex;
    align-items: center;
    justify-content: space-between;
    width: var(--container-width);
    max-width: var(--max-width);
    margin: 0 auto;
    gap: var(--space-xl);
}

/* Logo */
.header-logo {
    flex-shrink: 0;
    display: flex;
    align-items: center;
}

.header-logo a,
.header-logo img,
.custom-logo-link {
    display: flex;
    align-items: center;
    justify-content: center;
    width: 56px;
    height: 56px;
}

.custom-logo {
    width: 56px;
    height: 56px;
    object-fit: contain;
}

.logo-fallback {
    font-weight: 800;
    font-size: 13px;
    letter-spacing: 0.15em;
    text-transform: uppercase;
    color: var(--color-text);
}

/* Right-hand header group: nav + language switcher + hamburger, held
   together so .header-inner's logo-vs-group space-between stays a
   two-item split (see the comment in header.php next to .header-right
   for why a third loose sibling broke this). */
.header-right {
    display: flex;
    align-items: center;
    gap: 48px;
}

/* Desktop navigation */
.primary-nav .nav-menu {
    display: flex;
    align-items: center;
    gap: 48px;
}

.primary-nav .nav-menu li a {
    font-size: 17px;
    font-weight: 700;
    letter-spacing: 0.1em;
    text-transform: uppercase;
    color: var(--color-text);
    transition: opacity var(--transition-fast);
}

.primary-nav .nav-menu li a:hover,
.primary-nav .nav-menu li.current-menu-item > a {
    opacity: 0.5;
}

/* Hamburger */
.nav-toggle {
    display: none;
    align-items: center;
    justify-content: center;
    color: var(--color-text);
    min-width: 44px;
    min-height: 44px;
    transition: opacity var(--transition-fast);
}

.nav-toggle:hover { opacity: 0.5; }

/* Language switcher */
.lang-switcher {
    display: flex;
    align-items: center;
    gap: 12px;
}

.lang-link {
    font-size: 12px;
    font-family: var(--font-mono);
    letter-spacing: 0.1em;
    font-weight: 700;
    text-transform: uppercase;
    color: var(--color-text-muted);
    transition: color var(--transition-fast);
}

.lang-link.lang-active,
.lang-link:hover { color: var(--color-text); }

/* ═══════════════════════════════════════════════════════════
   6. SITE FOOTER
═══════════════════════════════════════════════════════════ */
.site-footer {
    position: relative;
    z-index: var(--z-footer);
    background-color: var(--color-surface-container);
    border-top: 1px solid var(--color-border);
    padding: var(--space-lg) 0;
}

.footer-inner {
    display: flex;
    align-items: center;
    justify-content: space-between;
    width: var(--container-width);
    max-width: var(--max-width);
    margin: 0 auto;
}

.footer-copy {
    font-size: 13px;
    font-weight: 400;
    letter-spacing: 0.1em;
    color: var(--color-text);
}

.footer-links {
    display: flex;
    align-items: center;
    gap: var(--space-md);
}

.footer-icon-link {
    display: flex;
    align-items: center;
    justify-content: center;
    color: var(--color-secondary);
    padding: 10px;
    margin: -10px;
    transition: opacity var(--transition-fast);
}

.footer-icon-link:hover { opacity: 0.5; }

.footer-icon-link .material-symbols-outlined {
    font-size: 18px;
}

/* ═══════════════════════════════════════════════════════════
   7. MOBILE NAVIGATION OVERLAY
═══════════════════════════════════════════════════════════ */
.mobile-nav-overlay {
    position: fixed;
    inset: 0;
    background-color: var(--color-surface);
    z-index: var(--z-nav-overlay);
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    opacity: 0;
    pointer-events: none;
    transform: translateY(-100%);
    transition: opacity 0.4s ease, transform 0.4s cubic-bezier(0.16, 1, 0.3, 1);
}

.mobile-nav-overlay.is-open {
    opacity: 1;
    pointer-events: all;
    transform: translateY(0);
}

.mobile-nav-close {
    position: absolute;
    top: var(--space-lg);
    right: var(--space-xl);
    color: var(--color-text);
    display: flex;
    align-items: center;
    justify-content: center;
    min-width: 44px;
    min-height: 44px;
    transition: opacity var(--transition-fast);
}

.mobile-nav-close:hover { opacity: 0.5; }

.mobile-nav-list {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: var(--space-xl);
}

.mobile-nav-list li a {
    font-size: 32px;
    font-weight: 800;
    letter-spacing: 0.1em;
    text-transform: uppercase;
    color: var(--color-text);
    transition: opacity var(--transition-fast);
}

.mobile-nav-list li a:hover { opacity: 0.5; }

/* ═══════════════════════════════════════════════════════════
   8. PAGE CONTAINER (shared layout wrapper)
═══════════════════════════════════════════════════════════ */
.page-container {
    width: var(--container-width);
    max-width: var(--max-width);
    margin: 0 auto;
    padding: var(--space-2xl) 0 var(--space-3xl);
}

/* ═══════════════════════════════════════════════════════════
   9. PAGE HEADING ROW
═══════════════════════════════════════════════════════════ */
.page-heading-row {
    margin-bottom: var(--space-md);
}

.page-title {
    font-size: 46px;
    font-weight: 700;
    letter-spacing: 3px;
    text-transform: uppercase;
    color: var(--color-text);
    line-height: 1;
}

/* ═══════════════════════════════════════════════════════════
   10. FRONT PAGE — Three.js Globe
═══════════════════════════════════════════════════════════ */
.page-home-main {
    position: relative;
    height: calc(100vh - var(--header-height));
    overflow: hidden;
}

.canvas-container {
    position: absolute;
    inset: 0;
    z-index: var(--z-local-raised);
}

/* Mobile-only static replacement for .canvas-container — see
   front-page.php / inc/enqueue.php. Same position/sizing/layer as the
   canvas it replaces, just a plain cover-fit <img> instead of a WebGL
   scene. */
.hero-static-fallback {
    position: absolute;
    inset: 0;
    z-index: var(--z-local-raised);
    width: 100%;
    height: 100%;
    object-fit: cover;
    filter: grayscale(100%);
}

/* !important is required here: assets/js/globe.js calls Three.js's
   renderer.setSize(), which sets inline style="width:...px; height:...px"
   directly on this <canvas> on every resize. An inline style always beats
   a non-!important CSS rule regardless of specificity, so without
   !important this rule would only apply for one frame before JS
   overwrites it with a fixed pixel size that doesn't track the
   container's own responsive width/height. */
.canvas-container canvas {
    display: block;
    width: 100% !important;
    height: 100% !important;
}

.hero-section {
    position: absolute;
    inset: 0;
    display: flex;
    align-items: center;
    justify-content: center;
    z-index: var(--z-local-top);
    pointer-events: none;
}

.hero-overlay {
    display: flex;
    flex-direction: column;
    align-items: center;
    text-align: center;
    padding: var(--space-md);
    pointer-events: none;
}

.hero-title {
    font-size: 46px;
    font-weight: 700;
    letter-spacing: 0.1em;
    text-transform: uppercase;
    color: var(--color-text);
    pointer-events: none;
    user-select: none;
}

/* Entrance animation */
@keyframes fadeInExpand {
    from {
        opacity: 0;
        letter-spacing: -0.05em;
        transform: scale(0.98);
    }
    to {
        opacity: 1;
        letter-spacing: 0.1em;
        transform: scale(1);
    }
}

.animate-entrance {
    animation: fadeInExpand 2.5s cubic-bezier(0.19, 1, 0.22, 1) forwards;
}

/* ═══════════════════════════════════════════════════════════
   11. ABOUT PAGE
═══════════════════════════════════════════════════════════ */
.about-section,
.experience-section {
    display: flex;
    flex-direction: row;
    gap: var(--space-xl);
    margin-bottom: var(--space-3xl);
}

.experience-section {
    border-top: 1px solid var(--color-border);
    padding-top: var(--space-2xl);
}

.section-label-col {
    width: 35%;
    flex-shrink: 0;
}

.section-content-col {
    flex: 1;
}

.section-heading {
    font-size: 46px;
    font-weight: 700;
    letter-spacing: 0.1em;
    text-transform: uppercase;
    color: var(--color-text);
    line-height: 1.1;
}

/* Bio text — scoped to ".about-section .section-content-col", NOT the
   bare ".section-content-col" the rest of this file used to use here.
   ".section-content-col" is a plain layout column (flex: 1, no content
   opinion of its own) reused by BOTH the About section (Bio Text) and
   the Experience section below it (Year/Title) — a bare
   ".section-content-col p" rule doesn't know which one it's in, so it
   silently applied Bio Text's font-size/line-height/margin to every
   Experience Year/Title too (they're <p> tags as well), overriding
   Experience's own, differently-sized styling by pure specificity, no
   matter what was written there (see CHANGELOG 1.77.3). Scoping this to
   ".about-section" — the same pattern the ".about-section
   .section-content-col" rule just above already uses — makes this rule
   only ever match Bio Text, however many other sections end up reusing
   ".section-content-col" in the future. */
.about-section .section-content-col p {
    font-size: 15px;
    line-height: 1.7;
    color: var(--color-text);
    margin-bottom: var(--space-lg);
}

.about-section .section-content-col p:last-child { margin-bottom: 0; }

/* Experience list */
.experience-list {
    display: flex;
    flex-direction: column;
    gap: 0;
}

/* 24px gap from one item's content down to the NEXT item's Year (i.e.
   between entries) — not the Year→Title gap inside one entry, which is
   the separate, smaller gap set on .experience-year below. */
.experience-item {
    margin-bottom: var(--space-lg);
}

.experience-item--last {
    margin-bottom: 0;
}

/* Every rule below is qualified with the ".experience-list" ancestor —
   NOT for extra "safety", but because without it these lose to the Bio
   text rule above: ".experience-year"/".experience-title p" are plain
   <p> tags living inside the same ".section-content-col" that rule
   targets, ".section-content-col p" (one class + one type = specificity
   0,1,1) has EQUAL OR HIGHER specificity than ".experience-year" alone
   (one class = 0,1,0) or ".experience-title p" alone (tied at 0,1,1,
   resolved only by source order, which is fragile) — so the Bio text's
   font-size/line-height/color/margin-bottom was silently winning on the
   Year line and the title's first line, no matter what was written
   below with those bare selectors. Adding ".experience-list" (an extra
   class) makes every selector here strictly higher specificity
   (0,2,x > 0,1,1), so they win outright regardless of source order —
   this was the actual cause of "改了但畫面沒變" here. */
.experience-list .experience-year {
    font-family: var(--font-mono);
    font-size: 13px;
    letter-spacing: 0.05em;
    color: var(--color-text-muted);
    line-height: 1.4;
    /* Gap down to the Title below it, within the SAME entry — a
       different, smaller gap than the 24px between entries above. */
    margin-bottom: 8px;
}

/* Base paragraph typography (font-family/color inherited from body —
   same font the rest of the site's running text uses), no longer a
   heading style. This now holds WYSIWYG output (field_exp_title —
   see inc/acf-fields/about.php) rather than plain text, so Bold and
   font size are set per word/phrase from inside that field itself
   (inline <strong>/style="font-size:…") instead of one fixed style
   applied to the whole title. */
.experience-list .experience-title {
    color: var(--color-text);
    font-size: 15px;
    line-height: 1.4;
}

/* TinyMCE wraps each line the WYSIWYG editor's Enter key creates in its
   own <p> — without this, the browser's default <p> margin (~1em top
   and bottom) would reintroduce large gaps between the lines of a
   multi-line title that Shift+Enter/<br> was meant to avoid. */
.experience-list .experience-title p {
    margin: 0;
}
.experience-list .experience-title p + p {
    margin-top: var(--space-xs);
}

/* ═══════════════════════════════════════════════════════════
   12. FILTER BAR (Projects & Sandbox)
═══════════════════════════════════════════════════════════ */
.filter-bar {
    display: flex;
    align-items: center;
    gap: var(--space-xl);
    /* Was margin-bottom: var(--space-3xl) (96px) + padding-bottom:
       var(--space-md) (16px) = 112px total gap down to the card grid —
       far more than the 16px between the page title and this filter bar
       just above it (.page-heading-row's own margin-bottom), which read
       as too much dead space. padding-bottom served no visual purpose
       here (no border/background on this element to make the padding
       area visible — it was functionally just more margin), so it's
       dropped rather than kept at a smaller value. */
    margin-bottom: var(--space-lg);
}

.filter-btn {
    font-size: 13px;
    font-weight: 700;
    letter-spacing: 0.1em;
    text-transform: uppercase;
    color: var(--color-text-muted);
    transition: color var(--transition-fast);
    padding: 12px 0;
    margin: -12px 0;
}

.filter-btn:hover,
.filter-btn--active {
    color: var(--color-text);
}

/* ═══════════════════════════════════════════════════════════
   13. PROJECT GRID & CARD
═══════════════════════════════════════════════════════════ */
.project-grid {
    display: grid;
    grid-template-columns: repeat(3, 1fr);
    gap: var(--space-lg) var(--space-lg);
    margin-bottom: var(--space-2xl);
}

.project-card {
    display: block;
}

/* "Hidden behind a load-more toggle" — shared by the Projects archive's
   "SEE MORE" and the Sandbox archive's "EXPLORE MORE" (see .post-card
   below): one rule instead of a duplicate per card type, so a future
   third listing that adopts the same load-more pattern only needs to add
   its own card class to this selector, not write a new rule. */
.project-card--hidden,
.post-card--hidden {
    display: none;
}

.project-card-link {
    display: block;
    text-decoration: none;
    color: inherit;
}

.project-card-img-wrap {
    position: relative;
    aspect-ratio: 16 / 10;
    overflow: hidden;
    background-color: var(--color-surface-container);
}

.project-card-img {
    width: 100%;
    height: 100%;
    object-fit: cover;
    filter: grayscale(100%);
    transform: scale(1);
    transition:
        filter  var(--transition-slow),
        transform var(--transition-slow);
}

.project-card-link:hover .project-card-img {
    filter: grayscale(0%);
    transform: scale(1.08);
}

.project-card-img--placeholder {
    background-color: var(--color-surface-container);
}

/* Title overlay at bottom of card image */
.project-card-overlay {
    position: absolute;
    /* left/right (not the Tailwind-only "inset-x", which isn't real CSS and
       is silently dropped by the browser as an invalid property) — without
       them, an absolutely-positioned box with no width set shrinks-to-fit
       its content, so this ends up exactly as wide as the title text
       instead of spanning the card, and the gradient background follows
       that same narrow box. left/right: 0 pins both edges to the
       position:relative .project-card-img-wrap, so the box (and its
       gradient) is always the full card width regardless of title length. */
    left: 0;
    right: 0;
    bottom: 0;
    padding: var(--space-md); /* was hardcoded 16px — same value as the token, now stays in sync if it's ever retuned */
    background: linear-gradient(to top, var(--color-overlay-strong), var(--color-overlay-soft) 50%, transparent);
    display: flex;
    align-items: flex-end;
    z-index: var(--z-local-raised);
    pointer-events: none;
}

.project-card-title {
    color: var(--color-on-primary);
    font-size: 18px;
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: 0;
    line-height: 1.3;
}

/* Related projects grid (3 col on project detail) */
.related-projects-grid {
    display: grid;
    grid-template-columns: repeat(3, 1fr);
    gap: var(--space-lg);
}

/* ═══════════════════════════════════════════════════════════
   14. PROJECT DETAIL
═══════════════════════════════════════════════════════════ */
.page-project-detail-main .page-container {
    padding-top: var(--space-xl);
}

/* Breadcrumb */
.breadcrumb {
    display: flex;
    align-items: center;
    gap: var(--space-sm);
    margin-bottom: var(--space-lg);
    font-size: 13px;
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: 0.1em;
}

.breadcrumb-link {
    color: var(--color-text-muted);
    transition: color var(--transition-fast);
}

.breadcrumb-link:hover { color: var(--color-text); }

.breadcrumb-sep { color: var(--color-text-muted); }

.breadcrumb-current { color: var(--color-text); }

/* Hero image (21:12) */
.project-hero-wrap {
    aspect-ratio: 16 / 9;
    overflow: hidden;
    margin-bottom: var(--space-xl);
}

.project-hero-img {
    width: 100%;
    height: 100%;
    object-fit: cover;
}

/* Meta block */
.project-meta-block {
    margin-bottom: var(--space-xl);
}

.project-title {
    font-size: 40px;
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: -0.01em;
    line-height: 1.15;
    color: var(--color-text);
    margin-bottom: 32px;
}

/* Content Block title (h2) — visually smaller than the page's main
   title (h1, .project-title above), matching its lower heading level */
.project-block-title {
    font-size: 28px;
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: -0.01em;
    line-height: 1.15;
    color: var(--color-text);
}

.project-meta-line {
    font-size: 13px;
    font-weight: 500;
    text-transform: uppercase;
    letter-spacing: 0.05em;
    color: var(--color-text-muted);
    margin-bottom: var(--space-lg);
}

/* Visually hidden but still accessible to screen readers/SEO (used for
   the page's real <h1>, which is otherwise fully block-driven visually) */
.visually-hidden {
    position: absolute;
    width: 1px;
    height: 1px;
    padding: 0;
    margin: -1px;
    overflow: hidden;
    clip: rect(0, 0, 0, 0);
    white-space: nowrap;
    border: 0;
}

/* Two-column intro: title + native content (left) / Project Info list
   (right). On desktop, narrower than the hero image by design (90% of it,
   centered) — see .project-intro-grid/.project-blocks width rule below;
   tablet/mobile (≤1024px) reset both back to the hero's full width. */
.project-intro-grid {
    display: grid;
    grid-template-columns: 1fr 280px;
    gap: var(--space-2xl);
    align-items: start;
    margin-bottom: var(--space-2xl);
}

/* Desktop only: content (the intro row above and Content Blocks below)
   is 90% of the hero image's width, centered — the hero itself
   (.project-hero-wrap) is untouched and stays full width, so it reads as
   deliberately wider than the body content beneath it. Reset to 100% at
   the existing tablet breakpoint (≤1024px, see RESPONSIVE — TABLET
   below) so tablet and mobile keep their current full-width layout
   unchanged, as requested. */
.project-intro-grid,
.project-blocks {
    width: 90%;
    margin-left: auto;
    margin-right: auto;
}

.project-info-list {
    display: flex;
    flex-direction: column;
    gap: var(--space-lg);
}

.project-info-label {
    font-size: 13px;
    color: var(--color-text-muted);
    margin-bottom: var(--space-xs);
}

.project-info-value {
    font-size: 14px;
    font-weight: 700;
    text-transform: uppercase;
    line-height: 1.4;
    color: var(--color-text);
}

.project-credit-line {
    margin: 0;
}

.project-credit-line + .project-credit-line {
    margin-top: 2px;
}

.project-credit-role {
    color: var(--color-text);
}

/* Body content */
.project-body {
    max-width: 800px;
    font-size: 15px;
    line-height: 1.75;
    color: var(--color-text);
    margin-bottom: var(--space-2xl);
}

.project-body > * + * { margin-top: var(--space-lg); }

/* Content added via Content Blocks matches the hero image's full width
   — no narrow reading column like the native editor content in the
   two-column intro above uses. */
.project-blocks .project-body {
    max-width: none;
}

/* Content Blocks — a vertical stack. Uses --space-lg (24px) between
   every top-level option, same as the Group's own internal gap below —
   one consistent 24px rhythm site-wide, whether options are stacked
   top-to-bottom in Content Blocks or laid out inside a Group. */
.project-blocks {
    margin-bottom: var(--space-2xl);
}

.project-blocks > * + * {
    margin-top: var(--space-lg);
}

/* Group block — its own nested items sit in a row, sharing the width
   evenly (flex: 1 1 0 gives every item an equal share regardless of its
   own content width). Uses --space-lg — the same site-wide grid gutter
   as .project-grid / .related-projects-grid / Gallery's own grid below,
   and the same 24px used between top-level Content Block options
   above — so every grid-like layout on the site shares one consistent
   gutter. */
.project-block-group {
    display: flex;
    flex-wrap: wrap;
    gap: var(--space-lg);
}

.project-block-group-item {
    flex: 1 1 0;
    min-width: 0; /* allow items (e.g. a wide image) to shrink below their content size instead of overflowing the row */
}

/* Section — its own Items stack top to bottom (unlike Group, which lays
   them out side by side), using the same 24px (--space-lg) rhythm as
   everywhere else in Content Blocks. A Group nested inside a Section is
   just another block-level child here, so it's already full width with
   no extra CSS needed (a Section isn't a flex container, so there's no
   flex-shrink-to-content behaviour to correct for). */
.project-section > * + * {
    margin-top: var(--space-lg);
}

/* Every atomic type's own render function carries a base margin-bottom
   (.project-body, .project-gallery-grid, .project-video-wrap, etc.) for
   when it's used elsewhere (e.g. the native Content editor). Inside
   Content Blocks that margin is redundant with — and stacks on top
   of — the 24px rhythm above, so it's zeroed here for EVERY direct
   child, not a per-class allowlist: a per-class list silently misses
   any type added later (this was a real bug — .project-video-wrap got
   left out of the Group-item version once, inflating the gap below any
   Group containing a Video). Covers Video's optional second element
   (.project-video-links, for extra videos) too, since both become
   direct children here. !important is deliberate and permanent here,
   not a quick patch: this rule exists specifically to override each
   type's own base margin-bottom (meant for contexts outside Content
   Blocks), and relying on selector-specificity tricks to win that fight
   has already caused one real, hard-to-spot spacing bug — !important
   makes the override unconditional regardless of what else is added to
   this file later. */
.project-blocks > *,
.project-block-group-item > *,
.project-section > * {
    margin-bottom: 0 !important;
}

/* Video block: extra videos beyond the first embedded player, as a
   plain named text-link list */
.project-video-links {
    list-style: none;
    margin: var(--space-sm) 0 0;
    padding: 0;
}

.project-video-links li + li {
    margin-top: var(--space-xs);
}

.project-video-links a {
    font-size: 14px;
    color: var(--color-text-muted);
    text-decoration: underline;
    text-underline-offset: 2px;
}

.project-video-links a:hover {
    color: var(--color-text);
}

/* Spacer block — pure blank vertical space, no visible content.
   Small/Medium deliberately share the --space-lg/--space-2xl rhythm used
   everywhere else (retuning that scale retunes these too, same as any
   other gap/margin on the site). Large (120px) is its own one-off value,
   not a multiple of the spacing scale — left as a literal rather than
   inventing a --space-4xl token that nothing else would ever use. */
.project-spacer--small  { height: var(--space-lg); }
.project-spacer--medium { height: var(--space-2xl); }
.project-spacer--large  { height: 120px; }

/* Gallery grid (2-column) — --space-lg is the same site-wide grid
   gutter as .project-grid / .related-projects-grid / a Group's row */
.project-gallery-grid {
    display: flex;
    flex-wrap: wrap;
    gap: var(--space-lg);
    margin-bottom: var(--space-2xl);
    /* --gallery-cols and justify-content are set inline per-block, from
       the Gallery block's Columns/Alignment fields (see the 'gallery_block'
       case in template-parts/project-style-content.php) */
}

.project-gallery-item {
    position: relative;
    aspect-ratio: 4 / 3; /* landscape default; overridden per-photo below */
    overflow: hidden;
    flex: 0 0 calc((100% - (var(--gallery-cols, 2) - 1) * var(--space-lg)) / var(--gallery-cols, 2));
    max-width: calc((100% - (var(--gallery-cols, 2) - 1) * var(--space-lg)) / var(--gallery-cols, 2));
}

/* Cell shape follows the photo's own orientation (set via PHP in
   adeiv_render_gallery_block(), inc/helpers.php, from the image's actual
   width/height) — crops far less than forcing every photo into one
   fixed ratio, while every cell still fully fills (object-fit: cover
   below, no letterboxing/gaps). */
.project-gallery-item--landscape { aspect-ratio: 4 / 3; }
.project-gallery-item--portrait  { aspect-ratio: 3 / 4; }

.project-gallery-item img {
    width: 100%;
    height: 100%;
    object-fit: cover;
}

/* Content Blocks "Image" — object-fit: cover crop box. 4:3 is the base/
   default ratio (also the fallback for any Image block saved before the
   Aspect Ratio field existed, so old content renders unchanged) —
   matches Gallery's own default landscape crop, so the two look
   consistent wherever they appear together (stacked in a Section, or
   side by side in a Group). The Aspect Ratio field (see
   inc/acf-fields/projects.php's adeiv_image_block_sub_fields()) lets an
   editor pick one of the modifier classes below instead, per-image —
   see adeiv_render_image_block() in inc/helpers.php for how the field
   value maps to a class. Was a 1:1 square by default at one point:
   inside a Group, that made the Image column noticeably taller than a
   short Description/Title next to it, reading as extra empty space
   below the shorter item even though it was the image's own content
   height, not a margin bug — 4:3 is a less extreme default ratio that
   narrows that visual gap without removing the crop entirely; 1:1 is
   still available as an explicit per-image choice for when a square
   crop is actually wanted. */
.project-block-image {
    position: relative;
    aspect-ratio: 4 / 3;
    overflow: hidden;
}

.project-block-image--16-9 { aspect-ratio: 16 / 9; }
.project-block-image--1-1  { aspect-ratio: 1 / 1; }
.project-block-image--3-4  { aspect-ratio: 3 / 4; }

.project-block-image img {
    width: 100%;
    height: 100%;
    object-fit: cover;
    display: block;
}

/* "Original" — no crop at all: full width, height follows the photo's
   own natural proportions. `aspect-ratio: auto` stops the figure
   reserving a fixed-ratio box; `overflow: visible` is harmless once
   nothing is cropped, but keeps this rule self-contained instead of
   relying on the base rule's `overflow: hidden` having nothing to hide.
   height/object-fit are overridden explicitly on BOTH the .lightbox-
   trigger anchor and the <img> itself, rather than left to fall back
   from percentage-height-against-an-auto-height-parent — that same
   percentage-resolution chain was the exact root cause of a real,
   previously-fixed bug in this file (see the .lightbox-trigger comment
   above), so this rule states the auto height directly instead of
   depending on the same fallback behavior working out coincidentally.

   object-fit: contain (not "initial"/"fill", the CSS default) replaces
   what this rule used to say ("initial"). The theory was: the native
   width/height HTML attributes (added for CLS prevention) can be wrong
   for phone photos with EXIF rotation — WordPress's stored attachment
   metadata can report the PRE-rotation pixel size while the browser
   auto-rotates the actual image for display — and "initial"/fill would
   then stretch the image to match a wrongly-shaped reserved box.
   Tested against a real headless-browser render with deliberately
   swapped width/height attributes (simulating that exact mismatch):
   current Chromium re-derives the box's aspect ratio from the loaded
   image's real (post-rotation) size once it finishes decoding, so the
   final layout came out correct either way — no permanent stretch was
   reproducible from this specific cause in this engine. "contain" is
   kept anyway, because it is strictly safer than "fill" with no
   downside here (this element is never meant to letterbox against a
   background, so contain vs. fill/initial only matters when the box
   and image disagree): it cannot stretch/distort during the brief
   window before the image finishes loading, or in any engine that
   doesn't re-derive the ratio the way this Chromium build does. If the
   reported blur turns out not to be about this, the more likely
   causes are the source photo's resolution being too low for the width
   it renders at when uncropped (upscale blur — "Original" can end up
   wider on screen than a cropped ratio would in the same layout, so it
   needs a higher-resolution original to look equally sharp), or a
   WordPress `big_image_size_threshold` downscale on a very large
   original; neither could be verified without a live WordPress/ACF
   install and a real problem photo. */
.project-block-image--original {
    aspect-ratio: auto;
    overflow: visible;
}

.project-block-image--original .lightbox-trigger {
    height: auto;
}

.project-block-image--original img {
    height: auto;
    object-fit: contain;
}

.project-gallery-caption {
    position: absolute;
    bottom: 0;
    left: 0;
    right: 0;
    padding: 12px var(--space-md); /* 16px side padding matches the token; 12px has no matching token, left as a literal */
    background: linear-gradient(to top, var(--color-overlay-caption), transparent);
    color: var(--color-on-primary);
    font-size: 13px;
    text-transform: uppercase;
    letter-spacing: 0;
    line-height: 1.3;
    font-weight: 400;
}

/* Feature video (optional, ACF oEmbed) — responsive 16:9 embed */
.project-video-wrap {
    position: relative;
    aspect-ratio: 16 / 9;
    overflow: hidden;
    border: 1px solid var(--color-border);
    background-color: var(--color-surface-container);
    margin-bottom: var(--space-2xl);
}

.project-video-wrap iframe {
    position: absolute;
    inset: 0;
    width: 100%;
    height: 100%;
    border: 0;
}

/* Related projects heading */
.related-projects-section {
    padding-top: var(--space-2xl);
    border-top: 1px solid var(--color-border);
}

.related-projects-header {
    display: flex;
    align-items: center;
    justify-content: space-between;
    margin-bottom: var(--space-xl);
}

.related-projects-title {
    font-size: 32px;
    font-weight: 700;
    text-transform: uppercase;
    color: var(--color-text);
}

/* ═══════════════════════════════════════════════════════════
   15. POST / SANDBOX GRID & CARD
═══════════════════════════════════════════════════════════ */
.post-grid {
    display: grid;
    grid-template-columns: repeat(2, 1fr);
    gap: 32px 48px;
    margin-bottom: var(--space-xl);
}

.post-card {
    position: relative;
    background-color: var(--color-card-glass);
    border: 1px solid var(--color-border);
    transition:
        background-color var(--transition-medium),
        border-color var(--transition-medium);
}

.post-card:hover {
    background-color: var(--color-card-glass-hover);
    border-color: var(--color-text);
}

.post-card-link {
    display: flex;
    flex-direction: column;
    gap: 12px;
    padding: var(--space-xl);
    text-decoration: none;
    color: inherit;
}

.post-card-tag-row {
    display: flex;
    align-items: center;
    gap: var(--space-md);
    font-family: var(--font-mono);
    font-size: 13px;
    letter-spacing: 0.05em;
    text-transform: lowercase;
    color: var(--color-text-muted);
}

.post-card-tag {
    padding: 2px var(--space-sm); /* 2px has no matching token, left as a literal — same convention as .project-gallery-caption's padding elsewhere in this file */
    border: 1px solid var(--color-border);
    font-weight: 400;
}

.post-card-title {
    font-size: 20px;
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: 0;
    line-height: 1.2;
    color: var(--color-text);
}

.post-card-excerpt {
    font-size: 15px;
    line-height: 1.65;
    color: var(--color-text);
}

/* ═══════════════════════════════════════════════════════════
   16. LOAD MORE / EXPLORE MORE BUTTON
═══════════════════════════════════════════════════════════ */
.load-more-wrap {
    display: flex;
    justify-content: center;
    align-items: center;
    padding: var(--space-xl) 0 var(--space-md);
}

.btn-load-more {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: var(--space-xs);
    color: var(--color-text-muted);
    font-size: 13px;
    font-weight: 400;
    text-transform: uppercase;
    letter-spacing: 0;
    transition: color var(--transition-fast);
    padding: var(--space-sm);
}

.btn-load-more:hover { color: var(--color-secondary); }

.btn-load-more .material-symbols-outlined {
    font-size: 18px;
    transition: transform var(--transition-fast);
}

.btn-load-more:hover .material-symbols-outlined {
    transform: translateY(3px);
}

/* ═══════════════════════════════════════════════════════════
   17. 404 PAGE
═══════════════════════════════════════════════════════════ */
.page-404-main .page-container {
    display: flex;
    align-items: center;
    justify-content: center;
    min-height: calc(100vh - var(--header-height) - 80px);
}

.error-404-block {
    text-align: center;
    max-width: 600px;
}

.error-code-label {
    font-family: var(--font-mono);
    font-size: 120px;
    font-weight: 700;
    color: var(--color-surface-container);
    line-height: 1;
    letter-spacing: -0.05em;
    margin-bottom: var(--space-md);
}

.error-title {
    font-size: 40px;
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: 0.05em;
    color: var(--color-text);
    margin-bottom: var(--space-md);
}

.error-body {
    font-size: 16px;
    color: var(--color-text-muted);
    margin-bottom: var(--space-xl);
}

/* ═══════════════════════════════════════════════════════════
   18. SHARED BUTTON STYLES
═══════════════════════════════════════════════════════════ */
.btn-outline {
    display: inline-block;
    padding: 12px 32px;
    border: 1px solid var(--color-text);
    font-size: 13px;
    font-weight: 700;
    letter-spacing: 0.1em;
    text-transform: uppercase;
    color: var(--color-text);
    transition: background-color var(--transition-fast), color var(--transition-fast);
}

.btn-outline:hover {
    background-color: var(--color-text);
    color: var(--color-on-primary);
}

/* ═══════════════════════════════════════════════════════════
   19. WORDPRESS CONTENT EDITOR STYLES
   (for the_content() output in project-body)
═══════════════════════════════════════════════════════════ */
.project-body h2 {
    font-size: 24px;
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: 0.05em;
    margin-top: var(--space-2xl);
    margin-bottom: var(--space-md);
    color: var(--color-text);
}

.project-body h3 {
    font-size: 18px;
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: 0.05em;
    margin-top: var(--space-xl);
    margin-bottom: var(--space-sm);
    color: var(--color-text);
}

.project-body ul,
.project-body ol {
    list-style: disc;
    padding-left: var(--space-xl);
    margin-bottom: var(--space-md);
}

.project-body li { margin-bottom: 6px; }

.project-body a {
    border-bottom: 1px solid var(--color-text);
    transition: opacity var(--transition-fast);
}

.project-body a:hover { opacity: 0.5; }

/* ═══════════════════════════════════════════════════════════
   20. NO RESULTS
═══════════════════════════════════════════════════════════ */
.no-results {
    font-family: var(--font-mono);
    font-size: 13px;
    letter-spacing: 0.05em;
    color: var(--color-text-muted);
    padding: var(--space-2xl) 0;
}

/* ═══════════════════════════════════════════════════════════
   21. PAGINATION
═══════════════════════════════════════════════════════════ */
.nav-links {
    display: flex;
    gap: var(--space-md);
    justify-content: center;
    padding: var(--space-xl) 0;
}

.nav-links .page-numbers {
    font-size: 13px;
    font-family: var(--font-mono);
    font-weight: 700;
    letter-spacing: 0.05em;
    color: var(--color-text-muted);
    transition: color var(--transition-fast);
}

.nav-links .page-numbers.current,
.nav-links .page-numbers:hover {
    color: var(--color-text);
}

/* ═══════════════════════════════════════════════════════════
   21b. LIGHTBOX (Project images)
═══════════════════════════════════════════════════════════ */
/* width/height: 100% is deliberately on the BASE .lightbox-trigger rule,
   not scoped to .project-gallery-item like it used to be — that scoping
   was itself a real bug: Content Blocks' "Image" block
   (.project-block-image) ALSO wraps its <img> in a .lightbox-trigger
   anchor, but never got these dimensions since the rule only matched
   inside .project-gallery-item. Without them, the anchor's height fell
   back to auto (shrink-to-its-content), which breaks the
   percentage-height chain down to the <img> itself (an <img> with
   height:100% needs its own parent to have a definite height to resolve
   against — an "auto"-height anchor doesn't provide one) — so the image
   rendered at its own natural aspect ratio instead of being cropped to
   fill the box by object-fit: cover. Visually, whenever a photo's own
   aspect ratio didn't match the box (almost always, for a real photo),
   this left literal blank space at the bottom of the image box, inside
   the crop frame, below the visible photo — not a margin, so it never
   showed up as a margin-bottom to check. A blanket base rule (matching
   the "no per-type allowlist" lesson already applied to the
   margin-bottom reset above) means any future use of .lightbox-trigger
   gets correctly-sized images by default, instead of needing its own
   scoped exception added by hand. */
.lightbox-trigger {
    display: block;
    cursor: pointer;
    width: 100%;
    height: 100%;
}

.lightbox-overlay {
    position: fixed;
    inset: 0;
    z-index: var(--z-modal);
    display: flex;
    align-items: center;
    justify-content: center;
    padding: var(--space-xl);
    background-color: rgba(var(--color-black-rgb), 0.80);
    opacity: 0;
    visibility: hidden;
    transition: opacity var(--transition-medium);
}

.lightbox-overlay.is-open {
    opacity: 1;
    visibility: visible;
}

.lightbox-img {
    max-width: 100%;
    max-height: 100%;
    width: auto;
    height: auto;
    object-fit: contain;
    border: none;
}

.lightbox-close {
    position: absolute;
    top: var(--space-lg);
    right: var(--space-lg);
    width: 44px;
    height: 44px;
    display: flex;
    align-items: center;
    justify-content: center;
    background: none;
    border: 1px solid rgba(var(--color-white-rgb), 0.4);
    color: var(--color-on-primary);
    cursor: pointer;
    transition: border-color var(--transition-fast), color var(--transition-fast);
}

.lightbox-close:hover {
    border-color: var(--color-chroma-green);
    color: var(--color-chroma-green);
}

/* ═══════════════════════════════════════════════════════════
   22. RESPONSIVE — TABLET (≤ 1024px)
═══════════════════════════════════════════════════════════ */
@media (max-width: 1024px) {
    .project-grid,
    .related-projects-grid {
        grid-template-columns: repeat(2, 1fr);
    }

    /* Undo the desktop-only 90%-of-hero width from the base
       .project-intro-grid/.project-blocks rule above — tablet and mobile
       keep the same full-width content they had before this change. */
    .project-intro-grid,
    .project-blocks {
        width: 100%;
        margin-left: 0;
        margin-right: 0;
    }
}

/* ═══════════════════════════════════════════════════════════
   23. RESPONSIVE — MOBILE (≤ 768px)
═══════════════════════════════════════════════════════════ */
@media (max-width: 768px) {
    /* Header */
    .site-header {
        padding: 12px 0;
    }

    .primary-nav { display: none; }
    .lang-switcher { display: none; }

    .nav-toggle {
        display: flex;
    }

    /* Page content (not header/footer, which keep their existing
       percentage-based width) gets a fixed 24px side margin on mobile —
       deliberately a fixed value, not a percentage, but still written as
       the --space-lg token since it happens to equal 24px, so it stays in
       sync if that token is ever retuned. */
    .page-container {
        width: 100%;
        max-width: 100%;
        padding-left: var(--space-lg);
        padding-right: var(--space-lg);
    }

    /* Breadcrumb wraps onto multiple lines instead of staying squeezed
       onto one and overflowing/shrinking on a narrow screen */
    .breadcrumb {
        flex-wrap: wrap;
        row-gap: var(--space-xs);
    }

    /* Page titles */
    .page-title,
    .section-heading,
    .hero-title {
        font-size: 32px;
        letter-spacing: 2px;
    }

    /* 404 page — was missing a mobile override entirely (every other
       heading in this file gets one at both breakpoints below); at
       120px/40px on a ~375px-wide phone the "404" code label alone
       is close to the full viewport width. */
    .error-code-label { font-size: 72px; }
    .error-title       { font-size: 28px; }

    /* About two-col → single col */
    .about-section,
    .experience-section {
        flex-direction: column;
    }

    .section-label-col { width: 100%; }

    /* Project grid → 1 col */
    .project-grid,
    .related-projects-grid {
        grid-template-columns: 1fr;
    }

    /* Post grid → 1 col */
    .post-grid {
        grid-template-columns: 1fr;
        gap: var(--space-lg);
    }

    /* Project gallery → 1 col (overrides the per-block Columns setting,
       which is otherwise an inline style and would need !important here
       specifically because of that) */
    .project-gallery-grid {
        --gallery-cols: 1 !important;
    }

    /* Project detail */
    .project-title { font-size: 28px; }
    .project-block-title { font-size: 22px; }

    .project-intro-grid {
        grid-template-columns: 1fr;
        gap: var(--space-xl);
    }

    /* A Group's items sit side by side on desktop, sharing the row
       evenly — on a narrow phone screen, splitting into 2+ columns gets
       cramped fast, so stack them into a single column instead. */
    .project-block-group {
        flex-direction: column;
    }

    .project-block-group-item {
        flex-basis: auto;
        width: 100%;
    }

    .project-spacer--small  { height: var(--space-md); }
    .project-spacer--medium { height: var(--space-xl); }
    .project-spacer--large  { height: var(--space-2xl); }

    .lightbox-overlay {
        padding: var(--space-md);
    }

    /* Footer */
    .footer-inner {
        flex-direction: column;
        gap: var(--space-md);
        text-align: center;
    }
}

/* ═══════════════════════════════════════════════════════════
   24. RESPONSIVE — SMALL MOBILE (≤ 576px)
═══════════════════════════════════════════════════════════ */
@media (max-width: 576px) {
    :root {
        --container-width: 95%;
    }

    .page-container {
        padding-top: var(--space-xl);
    }

    .page-title,
    .section-heading {
        font-size: 28px;
    }

    .hero-title { font-size: 28px; }

    .error-code-label { font-size: 56px; }
    .error-title       { font-size: 24px; }

    .filter-bar { gap: var(--space-lg); }

    .post-card-link { padding: var(--space-lg); }
}

/* ═══════════════════════════════════════════════════════════
   25. LANGUAGE-SPECIFIC TYPOGRAPHY (zh-TW)
   ---------------------------------------------------------
   Chinese glyphs are square and have no ascenders/descenders — the bits
   of a Latin letter (b, l, g, p…) that stick above/below the main body
   of the text and create natural-looking breathing room between lines.
   That means the SAME numeric line-height ratio (e.g. 1.7×) reads
   visibly tighter for Chinese than it does for English; this isn't
   something editors can fix from wp-admin (line-height was never a
   WYSIWYG toolbar control — About's Bio Text, Content Blocks'
   Description, and Sandbox excerpts are all plain HTML content, the
   spacing is 100% this stylesheet's doing), so it has to be fixed here.
   html[lang] already changes per page on its own — Polylang swaps it
   for the page's language (header.php's language_attributes() call
   doesn't need to change) — so this only affects the Chinese pages;
   English keeps its existing, already-tuned values untouched.
   [lang^="zh"] (starts-with), not [lang="zh-TW"]: the exact string
   depends on the WordPress locale set for the Chinese language under
   Polylang's own Languages list (zh-TW vs zh-Hant vs zh-Hant-TW all
   exist as real possibilities depending on what was picked there) —
   matching the "zh" prefix works regardless of which one it is,
   without needing to know or hard-code the exact value.
   Covers every long-form body-copy class in the theme, not just About:
   .about-section .section-content-col p — About page Bio Text
   .project-body                        — Project detail body copy AND
                                           Content Blocks' Description
                                           block (adeiv_render_description_block()
                                           in inc/helpers.php reuses this
                                           same class)
   .post-card-excerpt                   — Sandbox card excerpt
   ========================================================= */
html[lang^="zh"] .about-section .section-content-col p,
html[lang^="zh"] .project-body,
html[lang^="zh"] .post-card-excerpt {
    line-height: 2;
}
