/* =========================================
   Zhou's Studio — stylesheet
   Static bilingual pages generated by build.js.
   Chinese pages live at /, English pages at /en/.
   ========================================= */

:root {
    --bg: #ffffff;
    --ink: #111111;
    --muted: #8a8a8a;
    --rule: #e8e8e8;
    --hero-bg: #0d0d0d;
    --font-main: 'Helvetica Neue', Helvetica, Arial, 'PingFang SC', 'Hiragino Sans GB', 'Microsoft YaHei', sans-serif;
    --font-serif: Georgia, 'Times New Roman', 'Songti SC', 'Noto Serif SC', STSong, serif;
    --gutter: 20px;
    --header-h: 64px;
    --ease: cubic-bezier(0.22, 0.61, 0.36, 1);
}

* { margin: 0; padding: 0; box-sizing: border-box; }

html {
    -webkit-text-size-adjust: 100%;
    scroll-behavior: smooth;
    /* The home page is exactly one viewport tall, so a downward drag there is
       never a scroll — without this Chrome reads it as pull-to-refresh and
       reloads the site. */
    overscroll-behavior-y: contain;
}

body {
    background: var(--bg);
    color: var(--ink);
    font-family: var(--font-main);
    font-size: 15px;
    line-height: 1.6;
    /* Sticky footer. An empty category is only ~500px tall, and without this the
       footer sat at the middle of the screen with a screenful of white under it.
       The header and the lightbox are position:fixed, so they are out of flow
       and unaffected. */
    display: flex;
    flex-direction: column;
    min-height: 100vh;
    -webkit-font-smoothing: antialiased;
    -moz-osx-font-smoothing: grayscale;
}

img, video { max-width: 100%; }
picture { display: block; }
a { color: inherit; }

:focus-visible { outline: 2px solid currentColor; outline-offset: 3px; }

.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;
}

/* =========================================
   Header + navigation
   Transparent over the home hero, solid everywhere else.
   ========================================= */
.site-header {
    position: fixed;
    top: 0; left: 0;
    width: 100%;
    height: var(--header-h);
    display: flex;
    align-items: center;
    justify-content: space-between;
    padding: 0 40px;
    z-index: 100;
    background: rgba(255, 255, 255, 0.94);
    backdrop-filter: saturate(180%) blur(14px);
    -webkit-backdrop-filter: saturate(180%) blur(14px);
    border-bottom: 1px solid rgba(0, 0, 0, 0.05);
    transition: background 0.35s var(--ease), color 0.35s var(--ease), border-color 0.35s var(--ease),
        transform 0.45s var(--ease);
}

/* Auto-hide: scrolling down tucks the header away, scrolling up brings it back. */
.site-header.is-hidden { transform: translateY(-100%); }

/* The bar row (brand + nav). On the desktop bar this is transparent to layout,
   so the logo and the nav stay direct flex children of .site-header; on a phone
   it becomes the top row of a two-row header. */
.header-bar { display: contents; }

/* The phone-only line of short category names. Collapsed to nothing everywhere
   else — see the 1080px block at the bottom of this file. */
.category-strip { display: none; }

/* On the home hero the bar carries no chrome at all — the blurred backdrop
   behind it is already dark, so white type reads without a scrim or a rule. */
.home-page .site-header:not(.is-scrolled) {
    background: transparent;
    backdrop-filter: none;
    -webkit-backdrop-filter: none;
    border-bottom-color: transparent;
    color: #fff;
}

.logo {
    font-family: var(--font-serif);
    font-size: 17px;
    font-weight: 400;
    letter-spacing: 4px;
    text-decoration: none;
    white-space: nowrap;
    position: relative;
    z-index: 2;
}

/* --- nav typography: serif, wide tracking, light weight ------------------ */
.site-nav {
    display: flex;
    align-items: center;
    gap: 34px;
}

.site-nav a,
.nav-works-trigger {
    position: relative;
    display: inline-flex;
    align-items: center;
    gap: 6px;
    padding: 6px 0;
    border: 0;
    background: none;
    color: inherit;
    font-family: var(--font-serif);
    font-size: 13px;
    font-weight: 400;
    letter-spacing: 3px;
    text-decoration: none;
    white-space: nowrap;
    cursor: pointer;
    opacity: 0.72;
    transition: opacity 0.4s var(--ease);
}
html[lang='en'] .site-nav a,
html[lang='en'] .nav-works-trigger { letter-spacing: 1.8px; }

.site-nav a:hover,
.site-nav a.active,
.nav-works-trigger:hover,
.nav-works-trigger.active,
.nav-works.is-open .nav-works-trigger { opacity: 1; }

/* Hairline that wipes in from the left on hover / for the current section. */
.site-nav a::after,
.nav-works-trigger::after {
    content: '';
    position: absolute;
    left: 0;
    bottom: 0;
    width: 100%;
    height: 1px;
    background: currentColor;
    transform: scaleX(0);
    transform-origin: right center;
    transition: transform 0.45s cubic-bezier(0.19, 1, 0.22, 1);
}
.site-nav a:hover::after,
.site-nav a.active::after,
.nav-works-trigger:hover::after,
.nav-works-trigger.active::after,
.nav-works.is-open .nav-works-trigger::after {
    transform: scaleX(1);
    transform-origin: left center;
}

.nav-plus {
    font-size: 13px;
    line-height: 1;
    opacity: 0.5;
    transition: transform 0.35s var(--ease);
}
.nav-works.is-open .nav-plus { transform: rotate(45deg); }

/* --- 作品 fly-out: a magazine catalogue strip under the bar -------------- */
.nav-works { display: block; }

.works-menu {
    position: absolute;
    top: 100%;
    left: 0;
    width: 100%;
    display: flex;
    justify-content: center;
    align-items: flex-start;
    gap: 46px;
    padding: 24px 40px 26px;
    background: rgba(255, 255, 255, 0.97);
    backdrop-filter: blur(18px);
    -webkit-backdrop-filter: blur(18px);
    border-bottom: 1px solid rgba(0, 0, 0, 0.06);
    box-shadow: 0 32px 56px -44px rgba(0, 0, 0, 0.5);
    /* The panel is always light, even while the bar itself sits over the hero in
       white type — without this the links inherit white and vanish on white. */
    color: var(--ink);
    opacity: 0;
    visibility: hidden;
    transform: translateY(-10px);
    transition: opacity 0.36s var(--ease), transform 0.36s var(--ease), visibility 0.36s;
}
.nav-works.is-open .works-menu {
    opacity: 1;
    visibility: visible;
    transform: none;
}

.works-menu a {
    position: relative;
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 6px;
    padding: 0;
    letter-spacing: 0;
    opacity: 0.6;
    transition: opacity 0.3s var(--ease);
}
.works-menu a:hover,
.works-menu a.active { opacity: 1; }

.wm-label {
    font-family: var(--font-serif);
    font-size: 14px;
    font-weight: 400;
    letter-spacing: 3px;
    line-height: 1.2;
}
html[lang='en'] .wm-label { letter-spacing: 1.6px; }

/* The hairline is the base line for the whole bilingual block (name + micro
   label) rather than a divider wedged between the two. */
.works-menu a::after {
    content: '';
    position: absolute;
    left: 0;
    bottom: -6px;
    width: 100%;
    height: 1px;
    background: currentColor;
    transform: scaleX(0);
    transform-origin: right center;
    transition: transform 0.45s cubic-bezier(0.19, 1, 0.22, 1);
}
.works-menu a:hover::after,
.works-menu a.active::after {
    transform: scaleX(1);
    transform-origin: left center;
}

.wm-sub {
    font-family: var(--font-main);
    font-size: 8px;
    font-weight: 600;
    letter-spacing: 1.8px;
    line-height: 1;
    text-transform: uppercase;
    opacity: 0.42;
    white-space: nowrap;
}

/* Items fade down in sequence — `backwards` (not `both`) so the animation never
   pins opacity and the hover state still transitions normally. */
.nav-works.is-open .works-menu a { animation: wm-in 0.42s var(--ease) backwards; }
.nav-works.is-open .works-menu a:nth-child(1) { animation-delay: 0.03s; }
.nav-works.is-open .works-menu a:nth-child(2) { animation-delay: 0.06s; }
.nav-works.is-open .works-menu a:nth-child(3) { animation-delay: 0.09s; }
.nav-works.is-open .works-menu a:nth-child(4) { animation-delay: 0.12s; }
.nav-works.is-open .works-menu a:nth-child(5) { animation-delay: 0.15s; }
.nav-works.is-open .works-menu a:nth-child(6) { animation-delay: 0.18s; }
.nav-works.is-open .works-menu a:nth-child(7) { animation-delay: 0.21s; }

@keyframes wm-in {
    from { opacity: 0; transform: translateY(-7px); }
}

/* =========================================
   Home — full-bleed carousel only
   ========================================= */
.home-main { display: block; }

.hero {
    position: relative;
    height: 100svh;
    min-height: 480px;
    background: #0d0d0d;
    overflow: hidden;
}

.hero-stage { position: absolute; inset: 0; }

.hero-slide {
    position: absolute;
    inset: 0;
    display: none;
    opacity: 0;
    transition: opacity 1s var(--ease);
}
/* display:none on the inactive slides is what stops the browser downloading all
   20 hero images at once; hero.js reveals one just before it is needed. */
.hero-slide.is-visible { display: block; }
.hero-slide.is-active { opacity: 1; }

/* A blurred, darkened copy of the same photo fills the letterbox area, so a
   portrait frame sits whole in the middle of a landscape screen without dead
   black bars — and the white nav stays readable without a scrim. */
.hero-slide::before {
    content: '';
    position: absolute;
    inset: -8%;
    background-image: var(--hero-bg);
    background-size: cover;
    background-position: center;
    filter: blur(40px) brightness(0.42) saturate(0.92);
}
/* Only the slide actually on screen pays for the blur. */
.hero-slide.is-visible:not(.is-active)::before { display: none; }

/* <picture> is a block wrapper with no intrinsic height, so it must be stretched
   explicitly or the <img>'s height:100% has nothing to resolve against. */
.hero-slide > picture,
.hero-slide > video.hero-media {
    display: block;
    position: absolute;
    inset: 0;
    width: 100%;
    height: 100%;
}

.hero-media {
    display: block;
    width: 100%;
    height: 100%;
    object-fit: contain;
    object-position: center center;
}
.hero-slide video.hero-media { object-fit: cover; }

/* --- arrows: hairline chevrons, revealed on hover or keyboard focus ------ */
.hero-arrow {
    position: absolute;
    top: 50%;
    transform: translateY(-50%);
    width: 56px; height: 56px;
    display: flex;
    align-items: center;
    justify-content: center;
    border: 0;
    background: none;
    color: #fff;
    cursor: pointer;
    z-index: 4;
    opacity: 0;
    filter: drop-shadow(0 0 6px rgba(0, 0, 0, 0.5));
    transition: opacity 0.35s var(--ease);
}
.hero-arrow svg { width: 30px; height: 30px; }
.hero:hover .hero-arrow { opacity: 0.5; }
.hero-arrow:hover,
.hero-arrow:focus-visible { opacity: 1; }
.hero-prev { left: 14px; }
.hero-next { right: 14px; }

/* --- progress: 08 —— 20 -------------------------------------------------- */
.hero-progress {
    position: absolute;
    left: 40px;
    bottom: 34px;
    display: flex;
    align-items: center;
    gap: 12px;
    z-index: 3;
    color: #fff;
}
.hero-num {
    font-size: 10px;
    letter-spacing: 1.6px;
    font-variant-numeric: tabular-nums;
    opacity: 0.7;
    text-shadow: 0 0 10px rgba(0, 0, 0, 0.55);
}
.hero-track {
    position: relative;
    width: 86px;
    height: 1px;
    background: rgba(255, 255, 255, 0.22);
    box-shadow: 0 0 8px rgba(0, 0, 0, 0.45);
}
.hero-bar {
    position: absolute;
    inset: 0;
    background: #fff;
    transform: scaleX(0.05);
    transform-origin: left center;
    transition: transform 0.6s var(--ease);
}

/* =========================================
   Inner pages
   ========================================= */
.page-main {
    max-width: 1600px;
    margin: 0 auto;
    /* 22px below the bar, not 40: the header is already visually quiet, so a
       big gap under it just pushed the first row down the page. */
    padding: calc(var(--header-h) + 22px) var(--gutter) 80px;
    /* Absorbs the leftover height so the footer lands at the bottom of short
       pages instead of floating mid-screen. */
    flex: 1 0 auto;
    width: 100%;
}

.breadcrumb {
    font-size: 11px;
    letter-spacing: 1.4px;
    text-transform: uppercase;
    color: var(--muted);
    /* Also the gap under the category-index row on category / all-works pages. */
    margin-bottom: 22px;
}
.breadcrumb a { text-decoration: none; }
.breadcrumb a:hover { color: var(--ink); }
.breadcrumb span[aria-current] { color: var(--ink); }

/* The all-works page and every category page share this row as their masthead —
   there is no big title any more, so this is both the heading and the way in. */
.category-index {
    display: flex;
    flex-wrap: wrap;
    gap: 10px 26px;
}
/* Direct children only: English items nest the two label spans, and a
   descendant selector would compound the opacity to 0.25. */
.category-index > a,
.category-index > span {
    font-family: var(--font-serif);
    font-size: 13px;
    letter-spacing: 2.4px;
    color: var(--ink);
    opacity: 0.5;
    transition: opacity 0.3s var(--ease);
}
html[lang='en'] .category-index > a,
html[lang='en'] .category-index > span { letter-spacing: 1.4px; }
.category-index > a:hover { opacity: 1; }
/* Desktop English shows the full category name; the phone block swaps to the
   short one. Chinese has a single label and never uses these. */
.ci-short { display: none; }
/* The current one is a <span>, marked with the same hairline the top nav uses. */
.category-index > span[aria-current] {
    position: relative;
    opacity: 1;
}
.category-index > span[aria-current]::after {
    content: '';
    position: absolute;
    left: 0;
    right: 0;
    bottom: -6px;
    height: 1px;
    background: currentColor;
}

.page-header { margin-bottom: 44px; max-width: 760px; }
.page-title {
    font-family: var(--font-serif);
    font-weight: 400;
    font-size: clamp(30px, 4.4vw, 54px);
    line-height: 1.08;
    letter-spacing: 0.01em;
}
.page-intro {
    margin-top: 16px;
    font-size: 15px;
    color: #444;
    max-width: 62ch;
}
.page-count {
    margin-top: 14px;
    font-size: 11px;
    letter-spacing: 1.8px;
    text-transform: uppercase;
    color: var(--muted);
}
.project-credit {
    margin-top: 14px;
    font-size: 12px;
    letter-spacing: 1.6px;
    text-transform: uppercase;
    color: var(--muted);
}

/* ---------------------------------------------------------------------------
   Category tool bar — works count on the left, season filter on the right,
   closed by a hairline directly above the grid. It only exists where there is
   something to filter, so it never competes with the top nav.
   --------------------------------------------------------------------------- */
.category-bar {
    display: flex;
    align-items: baseline;
    justify-content: space-between;
    gap: 16px 30px;
    flex-wrap: wrap;
    padding-bottom: 14px;
    margin-bottom: 32px;
    border-bottom: 1px solid var(--rule);
}
.category-bar .page-count { margin: 0; }
[data-works-count] {
    display: inline-block;
    font-variant-numeric: tabular-nums;
}
[data-works-count].is-updated { animation: count-lift 0.5s var(--ease); }
@keyframes count-lift {
    from { opacity: 0.2; transform: translateY(-5px); }
}

.season-filter {
    display: flex;
    align-items: center;
    gap: 26px;
    flex-wrap: wrap;
}
.season-btn {
    position: relative;
    display: inline-flex;
    align-items: baseline;
    gap: 6px;
    padding: 2px 0;
    border: 0;
    background: none;
    font: inherit;
    color: inherit;
    cursor: pointer;
    opacity: 0.42;
    transition: opacity 0.3s var(--ease);
}
/* The "/" separators live in the gap, so they never shift the labels. */
.season-btn + .season-btn::before {
    content: '/';
    position: absolute;
    left: -15px;
    font-size: 11px;
    opacity: 0.4;
}
.season-btn:hover { opacity: 0.78; }
.season-btn.is-active { opacity: 1; }
.s-label {
    font-family: var(--font-serif);
    font-size: 13px;
    letter-spacing: 2.4px;
}
html[lang='en'] .s-label { letter-spacing: 1.4px; }
.s-sub {
    font-size: 8px;
    font-weight: 600;
    letter-spacing: 1.6px;
    text-transform: uppercase;
    opacity: 0.5;
}

.gallery-item.is-filtered-out { display: none; }

/* Masonry grid — every photo keeps its own aspect ratio (no cropping). */
.category-grid { columns: 4; column-gap: 16px; }
@media (max-width: 1200px) { .category-grid { columns: 3; } }
@media (max-width: 760px) { .category-grid { columns: 2; column-gap: 10px; } }

.gallery-item {
    position: relative;
    display: block;
    break-inside: avoid;
    margin-bottom: 16px;
    overflow: hidden;
    background: #f4f4f4;
    text-decoration: none;
    color: #fff;
}
@media (max-width: 760px) { .gallery-item { margin-bottom: 10px; } }

.gallery-item img,
.gallery-item video {
    display: block;
    width: 100%;
    height: auto;
    transition: transform 0.6s var(--ease);
}
.gallery-item:hover img,
.gallery-item:focus-visible img { transform: scale(1.035); }

.gallery-caption {
    position: absolute;
    left: 0; right: 0; bottom: 0;
    padding: 34px 14px 12px;
    font-size: 12px;
    font-weight: 600;
    letter-spacing: 0.6px;
    background: linear-gradient(to top, rgba(0, 0, 0, 0.62), rgba(0, 0, 0, 0));
    opacity: 0;
    transform: translateY(6px);
    transition: opacity 0.35s var(--ease), transform 0.35s var(--ease);
}
.gallery-item:hover .gallery-caption,
.gallery-item:focus-visible .gallery-caption { opacity: 1; transform: none; }

.empty-state {
    /* The 80px this used to carry was breathing room under the category intro;
       that paragraph is gone. No top padding at all now: the 34px margin under
       the category row is already the page's rhythm, so matching it keeps the
       message exactly as far from the row as the grid is on a filled page. */
    padding: 0 0 40px;
    max-width: 520px;
    color: var(--muted);
}
.empty-state p { margin-bottom: 22px; }

.cta-button {
    display: inline-block;
    padding: 12px 26px;
    border: 1px solid var(--ink);
    font-size: 11px;
    font-weight: 700;
    letter-spacing: 2px;
    text-transform: uppercase;
    text-decoration: none;
    transition: background 0.25s ease, color 0.25s ease;
}
.cta-button:hover { background: var(--ink); color: #fff; }

/* =========================================
   Project page
   ========================================= */
.work-page .page-header { max-width: 900px; }

.project-gallery {
    max-width: 900px;
    margin: 0 auto;
    display: flex;
    flex-direction: column;
    gap: 34px;
}
.project-figure { margin: 0; }
.project-img { display: block; width: 100%; height: auto; }

.project-cta {
    max-width: 900px;
    margin: 70px auto 0;
    padding-top: 34px;
    border-top: 1px solid var(--rule);
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 20px;
    flex-wrap: wrap;
}
.project-cta p { color: var(--muted); font-size: 14px; }

.project-nav {
    max-width: 900px;
    margin: 50px auto 0;
    display: grid;
    grid-template-columns: 1fr 1fr;
    gap: 20px;
}
.project-nav-link {
    display: flex;
    flex-direction: column;
    gap: 6px;
    padding: 20px 0;
    border-top: 1px solid var(--rule);
    text-decoration: none;
}
.project-nav-link.next { text-align: right; }
.project-nav-link .dir {
    font-size: 10px;
    letter-spacing: 2px;
    text-transform: uppercase;
    color: var(--muted);
}
.project-nav-link .ttl { font-size: 15px; }
.project-nav-link:hover .ttl { text-decoration: underline; }

/* =========================================
   About
   ========================================= */
.about-layout {
    display: grid;
    grid-template-columns: minmax(240px, 380px) minmax(0, 1fr);
    gap: 56px;
    align-items: start;
}
.about-portrait img { display: block; width: 100%; height: auto; }
.about-text { max-width: 640px; }
.about-role {
    margin-top: 12px;
    font-size: 11px;
    font-weight: 700;
    letter-spacing: 2.4px;
    text-transform: uppercase;
    color: var(--muted);
}
.about-location { margin-top: 6px; font-size: 13px; color: var(--muted); }
.about-bio { margin-top: 26px; font-size: 16px; line-height: 1.75; }

.about-subhead {
    margin: 44px 0 16px;
    font-size: 11px;
    font-weight: 700;
    letter-spacing: 2.2px;
    text-transform: uppercase;
    color: var(--muted);
}
.credit-list {
    list-style: none;
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(190px, 1fr));
    gap: 6px 22px;
    font-size: 14px;
}
.about-enquiry { font-size: 14px; }
.about-enquiry a { text-underline-offset: 3px; }

/* =========================================
   Contact
   ========================================= */
.contact-layout {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
    gap: 50px;
    max-width: 900px;
}
.about-contact { display: flex; flex-direction: column; gap: 2px; }
.about-contact-item {
    display: flex;
    align-items: baseline;
    justify-content: space-between;
    gap: 20px;
    padding: 16px 0;
    border-bottom: 1px solid var(--rule);
}
.about-contact-item .label {
    font-size: 10px;
    font-weight: 700;
    letter-spacing: 1.8px;
    text-transform: uppercase;
    color: var(--muted);
}
.about-contact-item .value { text-decoration: none; font-size: 15px; }
.about-contact-item a.value:hover { text-decoration: underline; }

.copy-button {
    display: inline-flex;
    align-items: center;
    gap: 10px;
    border: 0;
    background: transparent;
    font: inherit;
    font-size: 15px;
    color: inherit;
    cursor: pointer;
    padding: 0;
}
.copy-hint {
    font-size: 9px;
    font-weight: 700;
    letter-spacing: 1.4px;
    text-transform: uppercase;
    color: var(--muted);
}
.copy-button.copied .copy-hint { color: #1a7f37; }

/* =========================================
   Footer
   ========================================= */
.site-footer {
    border-top: 1px solid var(--rule);
    padding: 40px var(--gutter) 34px;
    text-align: center;
}
.footer-location { font-size: 12px; color: var(--muted); }
.footer-copy { margin-top: 6px; font-size: 11px; color: var(--muted); }

/* =========================================
   Lightbox
   ========================================= */
.lightbox-overlay {
    display: none;
    position: fixed;
    inset: 0;
    z-index: 200;
    background: #0a0a0a;
    overflow-y: auto;
    overscroll-behavior: contain;
}
.close-btn {
    position: fixed;
    top: 14px; right: 18px;
    z-index: 210;
    width: 44px; height: 44px;
    border: 0;
    background: transparent;
    color: #fff;
    font-size: 30px;
    line-height: 1;
    cursor: pointer;
    opacity: 0.75;
}
.close-btn:hover { opacity: 1; }

.lightbox-meta {
    position: fixed;
    top: 22px; left: 22px;
    z-index: 210;
    display: flex;
    align-items: baseline;
    gap: 14px;
    color: #fff;
    pointer-events: none;
}
.lightbox-title { font-size: 13px; letter-spacing: 1px; }
.lightbox-counter { font-size: 11px; opacity: 0.6; font-variant-numeric: tabular-nums; }
.lightbox-share {
    pointer-events: auto;
    border: 1px solid rgba(255, 255, 255, 0.5);
    border-radius: 999px;
    background: transparent;
    color: #fff;
    font: inherit;
    font-size: 10px;
    font-weight: 700;
    letter-spacing: 1.4px;
    text-transform: uppercase;
    padding: 4px 12px;
    cursor: pointer;
    white-space: nowrap;
    transition: background 0.25s ease, color 0.25s ease, border-color 0.25s ease;
}
.lightbox-share:hover { background: rgba(255, 255, 255, 0.16); }
.lightbox-share.is-copied { background: #fff; color: #111; border-color: #fff; }
.lightbox-share.is-failed { border-color: #ff9a9a; color: #ff9a9a; }

.lightbox-content { padding: 70px 0 120px; }
.lightbox-item-wrapper {
    max-width: min(1600px, 92vw);
    margin: 0 auto;
    display: flex;
    justify-content: center;
}
.lightbox-item-wrapper img,
.lightbox-item-wrapper video { max-width: 100%; height: auto; }
.lightbox-divider { height: 26px; }

/* Appears once the visitor reaches the end of the photo stream. */
.lightbox-return {
    position: fixed;
    left: 50%;
    bottom: 20px;
    transform: translate(-50%, 14px);
    z-index: 210;
    display: inline-flex;
    align-items: center;
    gap: 8px;
    padding: 11px 20px;
    border-radius: 999px;
    background: rgba(255, 255, 255, 0.94);
    color: #111;
    font-size: 11px;
    font-weight: 700;
    letter-spacing: 1.6px;
    text-transform: uppercase;
    text-decoration: none;
    white-space: nowrap;
    opacity: 0;
    visibility: hidden;
    transition: opacity 0.3s var(--ease), transform 0.3s var(--ease), visibility 0.3s;
}
.lightbox-return.is-visible {
    opacity: 1;
    visibility: visible;
    transform: translate(-50%, 0);
}
.lightbox-return svg { width: 15px; height: 15px; }
.lightbox-return:hover { background: #fff; }

/* =========================================
   Responsive
   ========================================= */
@media (max-width: 1320px) {
    .site-nav { gap: 26px; }
}

@media (max-width: 1080px) {
    :root { --header-h: 56px; }

    /* =====================================
       Phone header: one bar + one summoned strip
       The drawer is gone. The bar carries the brand and the three page links;
       the categories live in a single line underneath that is never permanent.
       On the home page it is the only chrome there is, and it only comes down
       when the visitor swipes. See js/nav.js.
       ===================================== */
    .site-header {
        flex-direction: column;
        align-items: stretch;
        justify-content: flex-start;
        height: auto;
        padding: 0;
    }
    .header-bar {
        display: flex;
        align-items: center;
        justify-content: space-between;
        gap: 14px;
        height: var(--header-h);
        padding: 0 20px;
    }

    .site-nav { gap: 18px; }
    .site-nav a,
    .nav-works-trigger { font-size: 12px; letter-spacing: 2px; }
    html[lang='en'] .site-nav a,
    html[lang='en'] .nav-works-trigger { font-size: 11px; letter-spacing: 1.2px; }

    /* 作品 is a desktop fly-out; on a phone the strip below carries the same
       links. #works-menu stays in the DOM — js/category.js reads its order. */
    .nav-works { display: none; }

    /* --- the category strip: collapsed to nothing until summoned -------- */
    .category-strip {
        display: grid;
        grid-template-rows: 0fr;
        transition: grid-template-rows 0.5s var(--ease);
    }
    .site-header.is-revealed .category-strip { grid-template-rows: 1fr; }

    .category-strip-inner {
        display: flex;
        align-items: center;
        justify-content: space-between;
        gap: 8px;
        min-height: 0;
        overflow: hidden;
        padding: 0 20px;
    }
    .category-strip-inner a {
        flex: 0 0 auto;
        padding: 13px 0 15px;
        font-family: var(--font-serif);
        font-size: clamp(11px, 3.1vw, 13px);
        letter-spacing: 2px;
        text-decoration: none;
        white-space: nowrap;
        /* Matches the desktop bar's resting weight — the strip is a secondary
           line, but on white pages 0.6 read as washed out. */
        opacity: 0.72;
        transition: opacity 0.3s var(--ease);
    }
    .category-strip-inner a:hover,
    .category-strip-inner a:focus-visible,
    .category-strip-inner a.active { opacity: 1; }
    /* English needs a smaller, tighter cut — seven words on one phone line. */
    html[lang='en'] .category-strip-inner a {
        font-family: var(--font-main);
        font-size: clamp(8px, 2.5vw, 10px);
        font-weight: 600;
        letter-spacing: 1px;
        text-transform: uppercase;
    }

    /* --- home: the photograph owns the whole screen ---------------------- */
    /* Nothing at rest — no bar, no logo, no counter. The menu is a floating
       card that fades in a little below the top edge; it never slides down from
       the very top, and it is never there on arrival. js/nav.js summons it on a
       vertical swipe only. */
    .home-page .site-header,
    .home-page .site-header:not(.is-scrolled) {
        padding: 12px 12px 0;
        background: none;
        backdrop-filter: none;
        -webkit-backdrop-filter: none;
        border-bottom: 0;
        color: #fff;
        opacity: 0;
        transform: translateY(-10px);
        pointer-events: none;
        transition: opacity 0.6s var(--ease), transform 0.6s var(--ease);
    }
    .home-page .site-header.is-revealed {
        opacity: 1;
        transform: none;
        pointer-events: auto;
    }
    /* Row 1 is not part of the home menu: here the categories are the point. */
    .home-page .header-bar { display: none; }

    /* The card itself. On the home page the strip is already at full height —
       it fades in as one piece with the header instead of unrolling. */
    .home-page .category-strip {
        grid-template-rows: 1fr;
        transition: none;
        overflow: hidden;
        border-radius: 4px;
        /* The photo behind this corner is often almost black, so the fill alone
           does not read as a surface — the hairline is what makes it float. */
        border: 1px solid rgba(255, 255, 255, 0.14);
        background: rgba(18, 18, 18, 0.52);
        backdrop-filter: saturate(160%) blur(16px);
        -webkit-backdrop-filter: saturate(160%) blur(16px);
        box-shadow: 0 20px 44px -30px rgba(0, 0, 0, 0.75);
    }
    .home-page .category-strip-inner { padding: 0 16px; }
    .home-page .category-strip-inner a { padding: 15px 0 17px; }

    /* Nothing scrolls here, so a vertical drag is read as "summon the menu"
       and claimed — otherwise the browser would treat it as pull-to-refresh.
       Horizontal panning and pinch-zoom stay native. */
    .home-page .hero { touch-action: pan-x pinch-zoom; }

    /* Hidden on phones for now — on a full-bleed photo the counter competed
       with the frame. The desktop composition still uses it. */
    /* Seven short names have to stay on one line here — the desktop gap would
       push 活动 onto a row of its own. Mirrors the phone category strip's sizes,
       which are already proven to fit at 320px in both languages. `wrap` stays
       on as the safety net: if a name ever grows, it wraps instead of forcing
       the whole page to scroll sideways. */
    .category-index {
        justify-content: space-between;
        /* 8px is a hair too wide for the English row at 320px: 233px of words +
           6×8px of gap = 281px inside a 280px column, so it wrapped by 1px. */
        gap: 6px;
    }
    .category-index > a,
    .category-index > span {
        font-size: clamp(11px, 3.1vw, 13px);
        letter-spacing: 2px;
        white-space: nowrap;
    }
    /* "Architecture" and "Still Life" cannot share a phone line with five other
       words, so the English row switches to the short labels here. */
    .ci-long { display: none; }
    .ci-short { display: inline; }
    html[lang='en'] .category-index > a,
    html[lang='en'] .category-index > span {
        font-family: var(--font-main);
        font-size: clamp(8px, 2.5vw, 10px);
        font-weight: 600;
        letter-spacing: 1px;
    }

    .hero-progress { display: none; }

    /* Drop the micro-labels so the season filter stays on one line. */
    .season-filter { gap: 20px; }
    .season-btn .s-sub { display: none; }
    .season-btn + .season-btn::before { left: -12px; }
}

@media (max-width: 960px) {
    .about-layout { grid-template-columns: 1fr; gap: 34px; }
    .about-portrait { max-width: 320px; }
}

/* The brand must sit on the same left edge as the content column. The content
   is capped at 1600px and carries its own --gutter padding, so its left edge is
   `max(0, (100% - 1600px) / 2) + gutter`. The header used a flat 40px, which put
   the brand 20px right of the copy on a 1440 screen and — once the cap kicked in
   — 20px left of it on a 1920 one. Deriving it keeps the two locked together at
   every width. (Below 1081px the mobile rule pads .header-bar by the gutter, so
   they already agree.) */
@media (min-width: 1081px) {
    .site-header {
        padding-left: calc(max(0px, (100% - 1600px) / 2) + var(--gutter));
        padding-right: calc(max(0px, (100% - 1600px) / 2) + var(--gutter));
    }
}

@media (max-width: 640px) {
    .logo { font-size: 15px; letter-spacing: 3px; }
    .page-main { padding-top: calc(var(--header-h) + 14px); padding-bottom: 56px; }
    .project-gallery { gap: 18px; }
    .project-nav { grid-template-columns: 1fr; }
    .project-nav-link.next { text-align: left; }
    .lightbox-meta { flex-wrap: wrap; max-width: calc(100vw - 90px); row-gap: 4px; }
}

/* The narrowest phones. The English brand name is the problem: "Nabai
   Photography" measures 167px against the Chinese logo's 72px, and with
   About / Contact / 中文 (137px) the bar needs 344px inside a 320px screen —
   so 中文 ran 18px off the right edge. Shrink the brand and tighten the nav
   gap instead of dropping a link. Measured: the row needs 253px here, leaving
   ~27px of slack at 320px. */
@media (max-width: 360px) {
    .logo { font-size: 13px; letter-spacing: 1.4px; }
    .site-nav { gap: 14px; }
}
/* Galaxy Fold-class widths (280px). The row still wanted 287px, so the brand
   steps down once more. */
@media (max-width: 320px) {
    .logo { font-size: 12px; letter-spacing: 1px; }
    .site-nav { gap: 10px; }
}
/* At 280px the seven English names needed 270px inside a 240px column, so
   EVENTS wrapped onto a line of its own. Dropping the letter-spacing buys back
   ~41px and keeps the row a single catalogue line, which is the whole point. */
@media (max-width: 300px) {
    .category-index { gap: 5px; }
    html[lang='en'] .category-index > a,
    html[lang='en'] .category-index > span { letter-spacing: 0; }
}

@media (prefers-reduced-motion: reduce) {
    html { scroll-behavior: auto; }
    .hero-slide,
    .gallery-item img,
    .gallery-caption,
    .site-nav,
    .site-header,
    .nav-works,
    .works-menu::before,
    .works-menu a { transition: none !important; }
}
