/* ============================================================
   Xiao Studio — BASE stylesheet (shared by every page)
   ------------------------------------------------------------
   Loaded LAST in each page's <head>, after that page's own
   inline <style>, so the tokens defined here win the cascade.

   Contents:
     1. Web fonts (Inter + XImpact display face)
     2. Theme tokens — light (default) + dark (auto & manual)
     3. Shared base elements (body, links, selection)
     4. Top navigation
     5. Theme toggle button (markup injected by site.js)
     6. Title display face

   Page-specific layout lives in its own file, e.g. home.css.
   ============================================================ */

@import url('https://rsms.me/inter/inter.css');
@font-face{font-family:'XImpact';src:url('../fonts/Impacted.ttf') format('truetype');font-display:swap;}
@font-face{font-family:'Graphik';src:url('../fonts/Graphik-Light.ttf') format('truetype');font-weight:300;font-style:normal;font-display:swap;}
@font-face{font-family:'Graphik';src:url('../fonts/Graphik-Regular.ttf') format('truetype');font-weight:400;font-style:normal;font-display:swap;}
@font-face{font-family:'Graphik';src:url('../fonts/Graphik-Medium.ttf') format('truetype');font-weight:500;font-style:normal;font-display:swap;}

/* ---- SYSTEM-LEVEL cross-page transition ------------------------------------
   Native path (Chrome 126+/Safari 18.2+/Edge): cross-document view transitions.
   On EVERY same-origin navigation — menu, in-page links, back/forward — the
   browser snapshots the live viewport (whatever the scroll position) and
   crossfades it into the new page with plus-lighter blending, so black↔white
   pages blend through a clean luminance ramp instead of cutting or flashing.
   Browsers without support get the colour-wash fallback in site.js (module 2b);
   both read the page surface from the same tokens, so the feel matches. */
@view-transition { navigation: auto; }
::view-transition-group(root){ animation-duration:.72s; }
::view-transition-old(root),::view-transition-new(root){
  animation-duration:.72s;                       /* explicit — not every engine inherits from the group */
  animation-timing-function:cubic-bezier(.2,.7,.2,1);
}
/* (BacktoXiao used to hold its own view-transition layer so it sat still while
   pages dissolved — but that layer floats ABOVE the loading veil and, when a
   page had moved the wordmark (xiaoyu's centred closing), it slid across the
   ritual. The veil is the transition now: the wordmark rides the root
   crossfade with everything else, covered and revealed cleanly.) */
@media (prefers-reduced-motion:reduce){
  ::view-transition-group(*),::view-transition-image-pair(*),
  ::view-transition-old(*),::view-transition-new(*){ animation:none !important; }
}
/* the capability self-test (site.js module 2b) must be IMPERCEPTIBLE: while it
   runs, all transition animations collapse to zero so it never holds the page
   under a snapshot — without this it inherits the .72s fade above, and a menu
   opened in that window seems frozen. */
html[data-vt-probe]::view-transition-group(*),
html[data-vt-probe]::view-transition-image-pair(*),
html[data-vt-probe]::view-transition-old(*),
html[data-vt-probe]::view-transition-new(*){ animation-duration:0s !important; animation-delay:0s !important; }

/* colour-wash veil — fallback cross-page transition (site.js module 2b) for
   browsers without cross-document view transitions (Firefox, older Safari).
   Leaving: the veil fades TO the destination page's resting surface colour;
   arriving: the new page starts under that same colour and the veil lifts. */
.pt-veil{position:fixed;inset:0;z-index:2000;pointer-events:none;opacity:0;
  transition:opacity .4s ease;}
.pt-veil.on{opacity:1;}
.pt-veil--instant{transition:none;}

/* menu departure ritual — the SAME loading veil (.load-veil below), painted
   with the DESTINATION page's surface + name (inline by site.js 2c), rises
   over the open menu and the wash; when the short ritual completes site.js
   releases the navigation, and the arrival veil picks up the identical word —
   one continuous ritual across the two documents. (Replaced .nav-progress.) */
.load-veil.nav-veil{z-index:2002;opacity:0;transition:none;
  animation:navVeilIn .32s ease forwards;}
@keyframes navVeilIn{to{opacity:1;}}

/* SYSTEM loading veil — pages that OPEN on heavy media declare
   <body data-load-veil="css-selector"> (site.js module 2d): a page-coloured
   cover holds the page's OWN NAME in large Graphik Light — each letter
   breathes from blur to focus (the site's blur-up language) over a slow
   hairline — until the watched image is ready; then the whole word snaps
   sharp and the veil dissolves. Complements the menu departure ritual above
   (that one rides the OLD page; this rides the NEW page's first paint). */
.load-veil{position:fixed;inset:0;z-index:1900;background:var(--page-bg,#fff);
  display:flex;flex-direction:column;align-items:center;justify-content:center;
  gap:clamp(18px,3.2vh,30px);padding:0 6vw;
  transition:opacity .6s ease .15s;}
.load-veil.off{opacity:0;pointer-events:none;}
.load-veil-title{font-family:'Graphik','Inter',-apple-system,system-ui,Arial,sans-serif;
  font-weight:300;font-size:clamp(30px,5.6vw,62px);line-height:1.16;
  letter-spacing:.01em;text-align:center;max-width:22ch;
  color:rgb(var(--page-ink-rgb));
  display:grid;place-items:center;}                /* word LAYERS stack for the cross-fade */
.load-veil-title .lv-w{grid-area:1/1;display:block;}
.load-veil-title b{font-weight:inherit;white-space:nowrap;}
/* the outgoing tongue exhales as ONE composited layer while the next arrives */
.load-veil-title .lv-w.lv-w-out{animation:veilOut .5s ease forwards;pointer-events:none;}
@keyframes veilOut{to{opacity:0;filter:blur(5px);}}
html[data-input="touch"] .load-veil-title .lv-w.lv-w-out{animation-name:veilOutSoft;}
@keyframes veilOutSoft{to{opacity:0;}}
.load-veil.off .lv-w-out{display:none;}           /* no ghosts under the lift freeze */
.load-veil-title i{font-style:normal;display:inline-block;opacity:.16;filter:blur(7px);
  animation:veilBreath 2.7s ease-in-out infinite;animation-delay:var(--li,0s);
  will-change:opacity,filter;}
@keyframes veilBreath{0%,100%{opacity:.16;filter:blur(7px);}42%{opacity:.94;filter:blur(0);}}
/* the ARRIVAL veil turns the name through the four tongues (site.js 2d adds
   .lv-cycle): each language's letters settle in with a quick capped stagger,
   exhale together (.lv-out), and the next arrives. */
.load-veil-title.lv-cycle i{animation:veilIn .6s cubic-bezier(.2,.7,.2,1) both;
  animation-delay:var(--li,0s);}
@keyframes veilIn{from{opacity:0;filter:blur(7px);transform:translateY(10px);}
  to{opacity:.94;filter:blur(0);transform:none;}}
/* lift: freeze whichever tongue is up, sharp (beats every branch above) */
.load-veil.off .load-veil-title i,
html[data-input="touch"] .load-veil.off .load-veil-title.lv-cycle i{
  animation:none;opacity:1;filter:none;
  transition:opacity .4s ease,filter .4s ease;}
.load-veil-line{width:clamp(130px,24vw,230px);height:1px;position:relative;overflow:hidden;
  background:rgba(var(--page-ink-rgb),.14);}
.load-veil-line::after{content:"";position:absolute;top:0;bottom:0;left:0;width:38%;
  background:rgba(var(--page-ink-rgb),.6);
  animation:veilLine 1.9s cubic-bezier(.45,0,.2,1) infinite;}
@keyframes veilLine{from{transform:translateX(-110%);}to{transform:translateX(290%);}}
.load-veil-word{font-family:'Graphik','Inter',-apple-system,system-ui,Arial,sans-serif;
  font-weight:400;font-size:11px;letter-spacing:.44em;text-indent:.44em;
  text-transform:uppercase;color:rgba(var(--page-ink-rgb),.42);}
/* touch devices: letters move by OPACITY only — animating filter per-glyph
   repaints every frame and janks mid-range phones (same lesson as the menu
   backdrop-filter freeze); the snap-sharp on lift needs nothing to undo */
html[data-input="touch"] .load-veil-title i{filter:none;animation-name:veilBreathSoft;}
@keyframes veilBreathSoft{0%,100%{opacity:.18;}42%{opacity:.95;}}
html[data-input="touch"] .load-veil-title.lv-cycle i{filter:none;animation-name:veilInSoft;}
@keyframes veilInSoft{from{opacity:0;transform:translateY(10px);}to{opacity:.94;transform:none;}}
/* phones: the clamp floor reads small at 390px — give the name real presence */
html[data-device="phone"] .load-veil-title{font-size:clamp(34px,9vw,44px);max-width:92vw;}
@media (prefers-reduced-motion:reduce){
  .load-veil-title i,html[data-input="touch"] .load-veil-title i{animation:none;opacity:.9;filter:none;}
  .load-veil-line::after{animation:none;width:100%;background:rgba(var(--page-ink-rgb),.35);}
}

/* ---- SYSTEM-LEVEL page transition (shared → every page, incl. future ones, inherits this) ----
   Each page fades up over its own background colour. Page CSS may set
   --page-bg / --page-ink; pages that do not set them fall back to the system
   theme tokens below. */
@keyframes pageFadeIn{from{opacity:0}to{opacity:1}}
@keyframes bxBlurIn{from{filter:blur(15px)}to{filter:none}}

/* ---- SYSTEM-LEVEL blur-up load reveal (every page, incl. future ones) ------
   The page paints BLURRED and resolves to sharp as a one-shot on first paint.
   The blur lives on the ROOT <html>, never on <body>: per the Filter Effects
   spec the document root is the one element whose `filter` does NOT become a
   containing block for position:fixed descendants — so the fixed BacktoXIAO
   trigger and the overlay menu it opens stay pinned to the VIEWPORT. (Put the
   same filter on <body> and every fixed child re-anchors to <body>; on a hash /
   already-scrolled load like untitled-series-i.html#process they land off-screen
   and the menu "disappears" and won't open.) Pure CSS, so it runs on its OWN —
   independent of site.js, the menu, or any per-page markup — and always ENDS at
   filter:none (animation `both`), so the page can never be stranded blurred even
   if no script ever runs, and nothing lingers to re-create the containing block.
   site.js (module 0b) only PRELOADS the key imagery and, when present, HOLDS the
   clear (play-state paused via html.bx-hold) until that imagery has decoded — a
   true reveal-when-ready. Reduced motion gets neither blur nor fade. */
html{background:var(--page-bg,var(--bg));transition:background-color .45s ease;overflow-x:hidden;overflow-x:clip;}
/* blur-up runs ONLY on true pointer devices. Touch tiers skip the page-wide
   filter animation entirely — it fights the mobile swipe galleries and can
   freeze iOS — matching the site's standing "no animated blur on touch" rule.
   The query is evaluated at parse time, so phones never even start the blur. */
@media (hover:hover) and (pointer:fine){
  html{animation:bxBlurIn 1.25s ease both;}
  html.bx-hold{animation-play-state:paused;}                 /* hold the blur until site.js releases it */
}
body{margin:0;animation:pageFadeIn .55s ease both;}
@media (prefers-reduced-motion:reduce){ html,body{animation:none;filter:none;} }

/* ---- 2 · theme tokens --------------------------------------------- */
:root{
  /* light (default) */
  --bg:#ffffff;
  --ink:#16130d;
  --dim:rgba(22,19,13,.62);
  --faint:rgba(22,19,13,.40);
  --line:rgba(22,19,13,.14);
  --scrim:linear-gradient(180deg,rgba(255,255,255,.97),rgba(255,255,255,.60) 55%,transparent);
  --sel-bg:#16130d; --sel-fg:#ffffff;

  /* font stacks — everything is Inter except the XIAO/title display face */
  --gro:'Inter',-apple-system,system-ui,Arial,sans-serif;
  --serif:'Inter',-apple-system,system-ui,Arial,sans-serif;
  --imp:'XImpact','Impact','Haettenschweiler','Arial Narrow Bold',sans-serif;

  font-feature-settings:'liga' 1,'calt' 1;
  color-scheme:light dark;
}

/* ---- SYSTEM-LEVEL device tiers + stage tokens -----------------------------
   One vocabulary for every device: phone ≤760 · pad = touch 761–1366 (iPads;
   laptops are pointer:fine so they stay desktop) · desktop otherwise.
   site.js mirrors the SAME conditions onto <html data-device data-input> as a
   stable selector/JS API; the tokens below are pure media queries, so picture
   stages size correctly even before any script runs. Pages size their main
   picture blocks from these instead of inventing breakpoints. */
:root{
  --edge: clamp(24px,3vw,48px);      /* page safe margin */
  --tap: 30px;                       /* minimum touch/hit target */
  --stage-w: min(74vw,1280px);       /* primary picture-stage width */
  --stage-h: 78svh;                  /* primary picture-stage height */
  --ui-gap: clamp(20px,2.4vw,40px);  /* rhythm between stage blocks */
}
@media (min-width:761px) and (max-width:1366px) and (pointer:coarse){
  :root{ --edge:28px; --tap:44px; --stage-w:88vw; --stage-h:72svh; --ui-gap:24px; }
}
@media (max-width:760px){
  :root{ --edge:16px; --tap:44px; --stage-w:92vw; --stage-h:64svh; --ui-gap:18px; }
}

/* ---- SYSTEM-LEVEL auto theme: maps every page's --page-bg / --page-ink / --page-panel-bg
   to the OS light/dark from ONE place. site.css loads after each page's CSS, so this wins
   over their fixed values. Fixed-surface pages are reasserted below from this shared
   file, so white/dark artwork pages do not become unreadable in the opposite mode. ---- */
:root{ --page-bg:#ffffff; --page-ink:#16130d; --page-panel-bg:#ebe9e2; --page-ink-rgb:22,19,13; --page-bg-rgb:255,255,255; }
@media (prefers-color-scheme:dark){
  :root:not([data-theme="light"]){ --page-bg:#000; --page-ink:#ededed; --page-panel-bg:#0c0c0c; --page-ink-rgb:237,237,237; --page-bg-rgb:0,0,0; }
}
@supports (font-variation-settings:normal){
  :root{ --gro:'InterVariable',-apple-system,system-ui,Arial,sans-serif;
         --serif:'InterVariable',-apple-system,system-ui,Arial,sans-serif; }
}

/* dark tokens — applied automatically on dark systems, unless the
   visitor has explicitly forced light via the toggle … */
@media (prefers-color-scheme:dark){
  :root:not([data-theme="light"]){
    --bg:#000000;
    --ink:#ededed;
    --dim:rgba(237,237,237,.62);
    --faint:rgba(237,237,237,.40);
    --line:rgba(237,237,237,.16);
    --scrim:linear-gradient(180deg,rgba(0,0,0,.92),rgba(0,0,0,.55) 55%,transparent);
    --sel-bg:#ededed; --sel-fg:#000;
  }
}
/* … or whenever the visitor manually picks dark */
:root[data-theme="dark"]{
  --bg:#000000;
  --ink:#ededed;
  --dim:rgba(237,237,237,.62);
  --faint:rgba(237,237,237,.40);
  --line:rgba(237,237,237,.16);
  --scrim:linear-gradient(180deg,rgba(0,0,0,.92),rgba(0,0,0,.55) 55%,transparent);
  --sel-bg:#ededed; --sel-fg:#000;
}

/* Shared page-surface guard.
   Some artwork/legal pages are intentionally fixed dark, and some index/studio
   pages are intentionally fixed light. Because site.css is loaded last, the
   exceptions also live here; this keeps day mode from turning a black page into
   a white page while leaving system-themed pages such as Gaze free to adapt.
   html:root:has(...) — the :root is load-bearing: it lifts specificity to (0,2,2)
   so these fixed surfaces beat the (0,2,0) dark-scheme token block above. */
html:root:has(body.series-page:not(.gaze-page):not(.xiaoyu-page)),
html:root:has(body.vn-page),
html:root:has(body.ax-page),
html:root:has(body.arc01-page),
html:root:has(body.legal-page){
  --page-bg:#000;
  --page-ink:#fff;
  --page-panel-bg:#0b0b0b;
  --page-ink-rgb:255,255,255;
  --page-bg-rgb:0,0,0;
  --bg:#000;
  --ink:#fff;
  --dim:rgba(255,255,255,.62);
  --faint:rgba(255,255,255,.40);
  --line:rgba(255,255,255,.16);
  --scrim:linear-gradient(180deg,rgba(0,0,0,.92),rgba(0,0,0,.55) 55%,transparent);
  --sel-bg:#fff;
  --sel-fg:#000;
  color-scheme:dark;
}
html:root:has(body.pt-page){
  --page-bg:#0a0a0a;
  --page-ink:#f2f2f2;
  --page-panel-bg:#0b0b0b;
  --page-ink-rgb:242,242,242;
  --page-bg-rgb:10,10,10;
  --bg:#0a0a0a;
  --ink:#f2f2f2;
  --dim:rgba(242,242,242,.62);
  --faint:rgba(242,242,242,.40);
  --line:rgba(242,242,242,.16);
  --scrim:linear-gradient(180deg,rgba(10,10,10,.92),rgba(10,10,10,.55) 55%,transparent);
  --sel-bg:#f2f2f2;
  --sel-fg:#0a0a0a;
  color-scheme:dark;
}
html:root:has(body.canvas-page){
  --page-bg:#000;
  --page-ink:#f4f1ec;
  --page-panel-bg:#0b0b0b;
  --page-ink-rgb:244,241,236;
  --page-bg-rgb:0,0,0;
  --bg:#000;
  --ink:#f4f1ec;
  --dim:rgba(244,241,236,.62);
  --faint:rgba(244,241,236,.40);
  --line:rgba(244,241,236,.16);
  --scrim:linear-gradient(180deg,rgba(0,0,0,.92),rgba(0,0,0,.55) 55%,transparent);
  --sel-bg:#f4f1ec;
  --sel-fg:#000;
  color-scheme:dark;
}
html:root:has(body.portfolio),
html:root:has(body.os-page),
html:root:has(body.xiaoyu-page){
  --page-bg:#f4f3ee;
  --page-ink:#16130d;
  --page-panel-bg:#ebe9e2;
  --page-ink-rgb:22,19,13;
  --page-bg-rgb:244,243,238;
  --bg:#f4f3ee;
  --ink:#16130d;
  --dim:rgba(22,19,13,.62);
  --faint:rgba(22,19,13,.40);
  --line:rgba(22,19,13,.14);
  --scrim:linear-gradient(180deg,rgba(255,255,255,.97),rgba(255,255,255,.60) 55%,transparent);
  --sel-bg:#16130d;
  --sel-fg:#fff;
  color-scheme:light;
}
html:root:has(body.xiaoyu-page){
  --page-bg:#fff;
  --page-bg-rgb:255,255,255;
  --bg:#fff;
}
/* The White Room — intentionally a fixed WHITE cube in both light & dark; the
   ignition intro plays black→burst→white on top, then the room rests white. */
html:root:has(body.wr-page){
  --page-bg:#ffffff;
  --page-ink:#16130d;
  --page-panel-bg:#f3f1ea;
  --page-ink-rgb:22,19,13;
  --page-bg-rgb:255,255,255;
  --bg:#ffffff;
  --ink:#16130d;
  --dim:rgba(22,19,13,.62);
  --faint:rgba(22,19,13,.40);
  --line:rgba(22,19,13,.14);
  --scrim:linear-gradient(180deg,rgba(255,255,255,.97),rgba(255,255,255,.60) 55%,transparent);
  --sel-bg:#16130d;
  --sel-fg:#fff;
  color-scheme:light;
}

/* ---- 3 · shared base ---------------------------------------------- */
html,body{background:var(--page-bg,var(--bg));}
body{color:var(--page-ink,var(--ink));font-family:var(--gro);overflow-x:hidden;overflow-x:clip;transition:background-color .45s ease,color .45s ease;}
a{color:inherit;}
/* SYSTEM: keep a real semantic heading available to search engines and screen
   readers on artwork-first pages without changing their visual composition. */
.sr-only{
  position:absolute!important;
  width:1px!important;
  height:1px!important;
  padding:0!important;
  margin:-1px!important;
  overflow:hidden!important;
  clip:rect(0,0,0,0)!important;
  clip-path:inset(50%)!important;
  white-space:nowrap!important;
  border:0!important;
}
/* SYSTEM: the WHOLE SITE opts out of iOS double-tap zoom. Repeated taps on
   artworks (XiaoYu's framed/digital toggle, Gaze's plates) were stepping the
   page scale up tap by tap, and once the page is zoomed every position:fixed
   layer (the Gaze spotlight veil, menus) covers only PART of the visual
   viewport — "the black mask stops half way". touch-action:manipulation on
   the root kills double-tap zoom everywhere while KEEPING two-finger pinch
   (accessibility); elements that declare their own pan-x/pan-y still work,
   since ancestors only ever intersect the allowed gestures. */
html,body{touch-action:manipulation;}
a,button,[role="button"],.menu-trigger{touch-action:manipulation;}
::selection{background:var(--sel-bg);color:var(--sel-fg);}

/* ---- 4 · top nav --------------------------------------------------- */
.top{background:var(--scrim) !important;-webkit-backdrop-filter:saturate(140%) blur(6px);backdrop-filter:saturate(140%) blur(6px);}
.top a,.nav a{color:var(--dim) !important;text-shadow:none !important;}
.top a:hover,.nav a:hover,.nav a.on,.top a.on{color:var(--ink) !important;}
.lang{display:none !important;}

/* BacktoXiao return/menu entry — matches the supplied type settings. */
.backto,
.backto-link{
  font-family:var(--gro) !important;
  font-weight:700 !important;
  font-size:28px !important;
  line-height:20px !important;
  letter-spacing:-3.3px !important;
  text-transform:none !important;
  white-space:nowrap;
}
.top a.backto-link,
.nav a.backto-link,
.footer-nav a.backto-link,
.backto{
  color:var(--ink) !important;
}
.backto:hover,
.top a.backto-link:hover,
.nav a.backto-link:hover,
.footer-nav a.backto-link:hover{
  opacity:.65;
}

/* ---- 5 · theme toggle (injected by site.js into the nav) ----------- */
.theme-toggle{margin-left:clamp(10px,1.6vw,22px);width:30px;height:30px;flex:none;padding:0;
  display:inline-flex;align-items:center;justify-content:center;line-height:0;cursor:pointer;
  border:1px solid var(--line);border-radius:999px;background:transparent;color:var(--dim);
  transition:color .25s ease,border-color .25s ease;}
.theme-toggle:hover{color:var(--ink);border-color:var(--ink);}
.theme-toggle svg{width:15px;height:15px;display:block;fill:none;stroke:currentColor;
  stroke-width:1.6;stroke-linecap:round;stroke-linejoin:round;}
/* moon shows in light mode, sun shows in dark mode */
.theme-toggle .ic-sun{display:none;}
:root[data-theme="dark"] .theme-toggle .ic-sun{display:block;}
:root[data-theme="dark"] .theme-toggle .ic-moon{display:none;}
@media (prefers-color-scheme:dark){
  :root:not([data-theme="light"]) .theme-toggle .ic-sun{display:block;}
  :root:not([data-theme="light"]) .theme-toggle .ic-moon{display:none;}
}

/* ---- 6 · titles keep the Impact display face ----------------------- */
.wordmark,.head h1,.hero h1,.head h1 em,.hero h1 em{
  font-family:var(--imp) !important;font-style:normal !important;letter-spacing:0 !important;}

/* ---- 7 · shared overlay menu (opened by XIAO on home, BacktoXiao on
   open-studio; built + wired by site.js) ----------------------------- */
.menu{position:fixed;inset:0;z-index:90;display:flex;align-items:center;justify-content:center;
  background:rgba(8,8,8,.66);backdrop-filter:blur(7px) saturate(120%);-webkit-backdrop-filter:blur(7px) saturate(120%);
  opacity:0;pointer-events:none;transition:opacity .42s cubic-bezier(.2,.7,.2,1);}
/* PERF: the blur itself never animates (each frame would re-blur the whole
   viewport — the iOS/iPad "frozen menu"). Desktop fades the frosted layer's
   opacity only; TOUCH tiers skip live backdrop blur entirely and rest on a
   deeper scrim — same hierarchy, fluid open on every phone/pad GPU. */
html[data-input="touch"] .menu{backdrop-filter:none;-webkit-backdrop-filter:none;background:rgba(8,8,8,.9);}
.menu.open{opacity:1;pointer-events:auto;}
.menu-list{display:flex;flex-direction:column;align-items:center;gap:clamp(16px,2.6vh,30px);}
/* original mixed-format menu, enlarged: every menu word stays pure white; the two Xiao wordmarks stay bold (.backto-link) */
.menu-list a{color:#fff !important;-webkit-text-fill-color:#fff;text-decoration:none;font-family:var(--gro);font-weight:400;letter-spacing:-3.3px;line-height:1.05;
  font-size:clamp(32px,5.8vw,60px);opacity:1;transition:opacity .2s ease;}
.menu-list a.backto-link{color:#fff !important;-webkit-text-fill-color:#fff;opacity:1;font-size:clamp(24px,3.6vw,38px) !important;}
.menu-list a:hover{opacity:1;}

/* Site search lives under AboutXIAO, but keeps enough separation to read as a
   utility rather than another navigation item. Its measure follows the
   viewport while staying deliberately shorter than the menu wordmarks. */
.menu-search{width:min(64vw,430px);margin-block-start:clamp(14px,3vh,30px);margin-block-start:clamp(14px,3svh,30px);
  color:#fff;font-family:'Graphik',var(--gro);letter-spacing:0;}
.menu-search-form{position:relative;display:grid;grid-template-columns:minmax(0,1fr) auto;align-items:center;
  border-bottom:1px solid rgba(255,255,255,.55);}
.menu-search-label{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);white-space:nowrap;border:0;}
.menu-search-form input{width:100%;min-width:0;height:42px;padding:0 16px 0 0;border:0;border-radius:0;outline:0;
  background:transparent;color:#fff;font:400 16px/1.2 'Graphik',var(--gro);letter-spacing:0;-webkit-appearance:none;appearance:none;}
.menu-search-form input::placeholder{color:rgba(255,255,255,.42);opacity:1;}
.menu-search-form input::-webkit-search-cancel-button{-webkit-appearance:none;appearance:none;display:none;}
.menu-search-form button{height:38px;padding:0;border:0;border-radius:0;background:transparent;color:rgba(255,255,255,.68);
  font:500 10px/1 'Graphik',var(--gro);letter-spacing:0;text-transform:uppercase;cursor:pointer;transition:color .2s ease;}
.menu-search-form button:hover{color:#fff;}
.menu-search-form:focus-within{border-bottom-color:#fff;box-shadow:0 1px 0 #fff;}
.menu-search-form input:focus-visible{outline:0;}
.menu-search-form button:focus-visible{outline:1px solid #fff;outline-offset:4px;}
.menu-search-status{min-height:14px;margin:7px 0 0;color:rgba(255,255,255,.46);font-size:10px;line-height:1.4;letter-spacing:0;text-align:left;}
.menu-search-results{width:100%;max-height:min(20vh,190px);overflow-y:auto;overscroll-behavior:contain;list-style:none;
  margin:9px 0 0;padding:0;border-top:1px solid rgba(255,255,255,.2);scrollbar-width:thin;scrollbar-color:rgba(255,255,255,.35) transparent;}
.menu-search-results:empty{display:none;}
.menu-search-result{margin:0;border-bottom:1px solid rgba(255,255,255,.2);text-align:left;}
.menu-list .menu-search-result>a{display:grid;grid-template-columns:minmax(0,1fr) auto;gap:18px;align-items:start;padding:11px 0 12px;
  color:#fff !important;-webkit-text-fill-color:#fff;font:400 13px/1.3 'Graphik',var(--gro) !important;letter-spacing:0 !important;text-decoration:none;}
.menu-search-result-title{display:block;margin:0 0 4px;font-size:13px;line-height:1.25;font-weight:500;letter-spacing:0;}
.menu-search-result-snippet{display:-webkit-box;overflow:hidden;margin:0;color:rgba(255,255,255,.52);font-size:10.5px;line-height:1.42;
  letter-spacing:0;-webkit-box-orient:vertical;-webkit-line-clamp:2;}
.menu-search-result-path{padding-top:2px;color:rgba(255,255,255,.4);font-size:9px;line-height:1.3;letter-spacing:0;white-space:nowrap;}
.menu-list .menu-search-result>a:hover .menu-search-result-title{opacity:.62;}
/* staggered entrance — each item rises in while staying pure white */
.menu.open .menu-list > *{animation:menuItemIn .55s cubic-bezier(.2,.7,.2,1) backwards;}
.menu.open .menu-list > :nth-child(1){animation-delay:.05s}
.menu.open .menu-list > :nth-child(2){animation-delay:.11s}
.menu.open .menu-list > :nth-child(3){animation-delay:.17s}
.menu.open .menu-list > :nth-child(4){animation-delay:.23s}
.menu.open .menu-list > :nth-child(5){animation-delay:.29s}
.menu.open .menu-list > :nth-child(6){animation-delay:.35s}
.menu.open .menu-list > :nth-child(7){animation-delay:.41s}
/* each item is INVISIBLE through its stagger delay (backwards fill), then rises
   in — without the opacity, every item sat visibly 20px off-position waiting for
   its turn, which read as the menu being stuck rather than choreographed. */
@keyframes menuItemIn{from{opacity:0;transform:translateY(20px);}to{opacity:1;transform:none;}}
@keyframes menuItemOut{to{opacity:0;transform:translateY(-18px);filter:blur(4px);}}
.menu.leaving{opacity:0;pointer-events:none;}
.menu.leaving .menu-list > *{animation:menuItemOut .38s cubic-bezier(.4,0,.2,1) both;}
@media (prefers-reduced-motion:reduce){.menu.open .menu-list > *{animation:none;}}
.menu .theme-toggle{margin:18px 0 0;width:38px;height:38px;border-color:rgba(255,255,255,.45);color:#fff;}
.menu .theme-toggle:hover{border-color:#fff;color:#fff;}
.menu .theme-toggle svg{width:18px;height:18px;}

/* ---- language row (site.js 2e) — a quiet line under the menu words.
   The current tongue holds full white; the others wait at half light. */
.menu-langs{position:absolute;left:50%;transform:translateX(-50%);
  bottom:calc(env(safe-area-inset-bottom,0px) + clamp(26px,5vh,48px));
  display:flex;align-items:center;gap:8px;}
.menu-langs button{background:none;border:0;padding:4px 1px;cursor:pointer;
  font-family:'Graphik',var(--gro);font-weight:400;font-size:10px;letter-spacing:.18em;
  color:rgba(255,255,255,.45);transition:color .25s ease;}
.menu-langs button:hover{color:rgba(255,255,255,.8);}
.menu-langs button.on{color:#fff;}
.menu-langs button.on::after{content:"";display:block;height:1px;background:#fff;margin-top:3px;opacity:.7;}
.menu-langs .ml-sep{color:rgba(255,255,255,.25);font-size:10px;}
/* the cookie banner shares the bottom edge — step aside while the menu is open
   so the language row stays reachable (banner returns when the menu closes) */
body:has(.menu.open) .xc-banner{opacity:0;pointer-events:none;transition:opacity .3s ease;}
@media (max-width:760px){
  .menu-search{width:min(76vw,320px);}
  .menu-search-form input{height:40px;font-size:16px;}
  .menu-search-results{max-height:min(20vh,170px);}
  .menu-list .menu-search-result>a{grid-template-columns:1fr;gap:5px;padding:10px 0 11px;}
  .menu-search-result-path{display:none;}
}
@media (max-height:720px){
  .menu-list{gap:10px;}
  .menu-search{margin-block-start:clamp(10px,3vh,18px);margin-block-start:clamp(10px,3svh,18px);}
  .menu-search-results{max-height:118px;}
  .menu-search-result-snippet{display:none;}
}
/* Landscape phones and shallow split-screen windows cannot contain the full
   navigation plus search in a centred fixed stack. Put every control in one
   scrollable flow so the first link, search results, and language choices all
   remain reachable instead of clipping or overlapping each other. */
@media (max-height:500px){
  .menu{flex-direction:column;align-items:center;justify-content:flex-start;overflow-y:auto;
    overscroll-behavior-y:contain;-webkit-overflow-scrolling:touch;box-sizing:border-box;
    padding-block:calc(env(safe-area-inset-top,0px) + 16px) calc(env(safe-area-inset-bottom,0px) + 18px);
    padding-inline:calc(env(safe-area-inset-left,0px) + 12px) calc(env(safe-area-inset-right,0px) + 12px);}
  .menu-list{flex:none;}
  .menu-langs{position:static;left:auto;bottom:auto;transform:none;flex:none;margin-top:18px;}
}

/* Chinese: XiaoYu already speaks its native 中文 — hide its English half
   instead of doubling the tongue (the beats dialog, the walk fragments,
   the statement, and the closing note). */
html[data-lang="zh"] .xiaoyu-page .xy-beat-en,
html[data-lang="zh"] .xiaoyu-page .xyw-text .en,
html[data-lang="zh"] .xiaoyu-page .xy-st-en,
html[data-lang="zh"] .xiaoyu-page .xy-work-note-en,
html[data-lang="zh"] .xiaoyu-page .xy-mb-lead-en{display:none;}

/* ---- 8 · BacktoXiao system-wide auto-hide (set by site.js when the button would
   sit over text or an image). open-studio keeps its own controller, so this is
   scoped away from it. ---- */
body:not(.os-page) .backto{transition:opacity .35s ease;}
.backto.backto--auto-hide{opacity:0 !important;pointer-events:none !important;}

/* unified footer container — system-wide base. Uses :where() (0 specificity) so any
   page that defines its own .site-foot still wins, while pages without one (e.g. portrait)
   inherit a centred, padded footer instead of a flush, left-aligned strip. */
:where(.site-foot){position:relative;z-index:1;text-align:center;padding:clamp(40px,8vh,90px) 20px;}

/* unified copyright — Georgia, one smaller size everywhere, with policy links beside.
   SYSTEM-LEVEL: this is the ONLY place the footer type is defined; each page just adds its
   own ink colour (light vs dark bg). Responsive on every screen — the line shrinks a hair on
   the narrowest phones, policy names never split mid-phrase, "© XIAOSTUDIO 2026" stays intact,
   and separators (·) never strand at a line start (see the &nbsp; glue in the footer markup). */
.copyright{font-family:Georgia,'Times New Roman',serif !important;font-style:italic;
  font-size:clamp(10.5px,3vw,11px) !important;letter-spacing:.02em;line-height:1.7;
  margin-inline:auto;max-width:60rem;overflow-wrap:break-word;}
.copyright a{color:inherit;text-decoration:none;border-bottom:1px solid currentColor;padding-bottom:1px;white-space:nowrap;}
.copyright a:hover{opacity:.6;}
/* keep each "·" glued to its neighbours so a wrap never leaves it dangling at a line start */
.copyright .csep{opacity:.45;margin:0 .55em;border:0;white-space:nowrap;}

/* ============================================================
   9 · Horizontal gallery — SYSTEM-LEVEL scroll-driven serpentine.
   Native vertical scroll drives a sticky-pinned track's translateX;
   a panel may wrap a .hgal-scroll for tall scroll-through content.
   Behaviour self-inits in site.js (module 6). Only STRUCTURE lives
   here — each page themes its own panel content. Mobile / reduced-
   motion fall back to a plain vertical stack.
       <section class="hgal" data-hgal [data-hgal-ease="smooth"]>
         <div class="hgal-pin"><div class="hgal-track">
           <div class="hgal-panel">… or … <div class="hgal-scroll">…</div>
         </div></div>
       </section>
   ============================================================ */
.hgal{position:relative;z-index:1;flex:none;}            /* tall; height set by site.js */
.hgal-pin{position:sticky;top:0;height:100vh;height:100svh;overflow:hidden;}
.hgal-track{display:flex;height:100%;will-change:transform;}
.hgal-panel{flex:0 0 100vw;width:100vw;height:100vh;height:100svh;overflow:hidden;}
.hgal-scroll{will-change:transform;}
@media (prefers-reduced-motion:reduce),(max-width:760px){
  .hgal{height:auto !important;}
  .hgal-pin{position:static;height:auto;overflow:visible;}
  .hgal-track{flex-direction:column;transform:none !important;}
  .hgal-panel{flex:none;width:100%;height:auto;overflow:visible;}
  .hgal-scroll{transform:none !important;}
}
/* opt-in cinematic mobile — set data-hgal-mobile="solo" on the section: one work
   per screen, centred & scroll-snapped; site.js fades each panel toward black as
   it leaves the viewport centre (the previous work darkens as the next arrives).
   ONLY for pages whose content is (almost) entirely hgal panels (archives.html):
   the snap is PAGE-WIDE (html) and mandatory, so on a mixed page the viewport
   gets trapped on the few snap points and the rest becomes unreachable. */
@media (max-width:760px){
  html:has([data-hgal-mobile="solo"]){scroll-snap-type:y mandatory;}
  [data-hgal-mobile="solo"] .hgal-panel{height:100svh;min-height:100svh;
    scroll-snap-align:center;scroll-snap-stop:always;will-change:opacity;}
}

/* ============================================================
   9b · Horizontal filmstrip — SYSTEM-LEVEL continuous strip.
   Native vertical scroll maps 1:1 onto one uninterrupted
   horizontal band (a book unrolled). Behaviour self-inits in
   site.js (module 6b). Only STRUCTURE lives here — each page
   themes its own strip content.
       <section data-hstrip>
         <div class="hstrip-pin"><div class="hstrip-track">…</div></div>
       </section>
   Mobile + reduced-motion: the pin becomes a native swipe
   scroller filling the full viewport height — no scroll hijack.
   ============================================================ */
.hstrip{position:relative;z-index:1;}                      /* tall; height set by site.js */
.hstrip-pin{position:sticky;top:0;height:100vh;height:100svh;overflow:hidden;}
.hstrip-track{display:flex;align-items:stretch;height:100%;width:max-content;will-change:transform;}
.hstrip-bar{position:fixed;left:0;right:0;bottom:0;height:2px;z-index:40;background:rgba(var(--page-ink-rgb),.12);}
.hstrip-bar > i{display:block;height:100%;width:0;background:var(--page-ink);transition:width .12s linear;}
@media (prefers-reduced-motion:reduce),(max-width:760px){
  .hstrip{height:auto !important;}
  .hstrip-pin{position:static;height:100vh;height:100svh;overflow-x:auto;overflow-y:hidden;-webkit-overflow-scrolling:touch;}
  .hstrip-track{transform:none !important;}
  .hstrip-bar{display:none;}
}

/* ===== Privacy & analytics controls (system-level) ========================
   Purpose choices deliberately share one visual hierarchy: no choice is
   nudged through a filled “accept” treatment. All UI is injected by site.js. */
.xa-settings-trigger{position:fixed;right:12px;right:max(12px,env(safe-area-inset-right));bottom:12px;bottom:max(12px,env(safe-area-inset-bottom));
  z-index:1100;display:inline-flex;align-items:center;justify-content:center;min-height:36px;
  appearance:none;border:1px solid rgba(var(--page-ink-rgb),.32);border-radius:999px;
  padding:0 12px;background:rgba(var(--page-bg-rgb),.92);color:rgba(var(--page-ink-rgb),.8);
  -webkit-backdrop-filter:blur(12px);backdrop-filter:blur(12px);
  font:500 9px/1.2 'Graphik','Inter',-apple-system,system-ui,Arial,sans-serif;
  letter-spacing:.08em;text-transform:uppercase;cursor:pointer;
  transition:color .2s ease,border-color .2s ease,opacity .2s ease,transform .2s ease;}
.xa-settings-trigger:hover,.xa-settings-trigger:focus-visible{color:var(--page-ink);border-color:rgba(var(--page-ink-rgb),.55);}
.xa-notice-open .xa-settings-trigger,.xa-settings-open .xa-settings-trigger{opacity:0;pointer-events:none;transform:translateY(6px);}

.xc-banner{position:fixed;left:50%;bottom:20px;bottom:max(20px,env(safe-area-inset-bottom));transform:translate(-50%,16px);z-index:1200;
  display:grid;grid-template-columns:minmax(240px,1fr) auto;align-items:center;gap:clamp(18px,3vw,34px);
  width:min(940px,calc(100vw - 32px));padding:18px clamp(18px,3vw,28px);border-radius:2px;
  background:var(--page-bg);color:var(--page-ink);border:1px solid rgba(var(--page-ink-rgb),.16);
  box-shadow:inset 0 1px 0 rgba(var(--page-bg-rgb),.6),0 18px 40px -14px rgba(0,0,0,.34),0 40px 90px -34px rgba(0,0,0,.5);
  opacity:0;pointer-events:none;transition:opacity .32s ease,transform .32s cubic-bezier(.2,.7,.2,1);
  font-family:'Graphik','Inter',-apple-system,system-ui,Arial,sans-serif;}
.xc-banner.in{opacity:1;transform:translate(-50%,0);pointer-events:auto;}
.xc-copy{min-width:0;}
.xc-title{display:block;margin:0 0 6px;font-size:12px;font-weight:600;letter-spacing:.02em;}
.xc-text{margin:0;font-size:12px;line-height:1.55;color:rgba(var(--page-ink-rgb),.76);max-width:58ch;}
.xc-text a,.xa-policy-link a{color:var(--page-ink);text-decoration:underline;text-underline-offset:2px;}
.xc-text a:hover,.xa-policy-link a:hover{opacity:.62;}
.xc-actions,.xa-settings-actions{display:flex;align-items:center;justify-content:flex-end;gap:8px;flex-wrap:wrap;}
.xc-btn,.xc-link-btn{appearance:none;font-family:inherit;cursor:pointer;color:var(--page-ink);background:transparent;}
.xc-btn{font-size:9.5px;font-weight:500;text-transform:uppercase;letter-spacing:.1em;padding:10px 13px;
  border-radius:0;border:1px solid rgba(var(--page-ink-rgb),.34);white-space:nowrap;
  transition:transform .18s ease,background .18s ease,border-color .18s ease;}
.xc-btn:hover,.xc-btn:focus-visible{border-color:var(--page-ink);background:rgba(var(--page-ink-rgb),.05);transform:translateY(-1px);}
.xc-btn:active{transform:translateY(0);}
.xc-link-btn{padding:7px 3px;border:0;font-size:10px;text-decoration:underline;text-underline-offset:3px;opacity:.7;}
.xc-link-btn:hover,.xc-link-btn:focus-visible{opacity:1;}
.xc-btn:focus-visible,.xc-link-btn:focus-visible,.xa-settings-trigger:focus-visible,.xa-close:focus-visible{
  outline:2px solid var(--page-ink);outline-offset:3px;}

.xa-settings-backdrop{position:fixed;inset:0;z-index:1250;display:grid;place-items:center;padding:18px;
  padding:max(18px,env(safe-area-inset-top)) max(18px,env(safe-area-inset-right))
    max(18px,env(safe-area-inset-bottom)) max(18px,env(safe-area-inset-left));
  background:rgba(0,0,0,.42);opacity:0;pointer-events:none;transition:opacity .24s ease;
  font-family:'Graphik','Inter',-apple-system,system-ui,Arial,sans-serif;}
.xa-settings-backdrop.in{opacity:1;pointer-events:auto;}
.xa-settings-open{overflow:hidden;}
.xa-settings-dialog{position:relative;width:min(580px,100%);max-height:calc(100vh - 36px);max-height:min(760px,calc(100svh - 36px));overflow:auto;
  -webkit-overflow-scrolling:touch;overscroll-behavior:contain;padding:clamp(24px,5vw,44px);background:var(--page-bg);color:var(--page-ink);
  border:1px solid rgba(var(--page-ink-rgb),.18);box-shadow:0 32px 100px rgba(0,0,0,.4);
  transform:translateY(10px);transition:transform .28s cubic-bezier(.2,.7,.2,1);}
.xa-settings-backdrop.in .xa-settings-dialog{transform:translateY(0);}
.xa-close{position:absolute;top:14px;right:14px;appearance:none;border:0;background:transparent;color:var(--page-ink);
  padding:5px;font:500 9px/1 'Graphik','Inter',sans-serif;text-transform:uppercase;letter-spacing:.1em;cursor:pointer;}
.xa-kicker{margin:0 72px 8px 0;color:rgba(var(--page-ink-rgb),.58);font-size:10px;letter-spacing:.06em;text-transform:uppercase;}
.xa-settings-dialog h2{margin:0 0 28px;font-size:clamp(24px,5vw,38px);font-weight:400;line-height:1.05;}
.xa-purpose{padding:18px 0;border-top:1px solid rgba(var(--page-ink-rgb),.14);}
.xa-purpose h3{margin:0 0 7px;font-size:13px;font-weight:600;}
.xa-purpose p{margin:0;max-width:58ch;color:rgba(var(--page-ink-rgb),.72);font-size:12px;line-height:1.6;}
.xa-settings-actions{justify-content:flex-start;margin-top:24px;}
.xa-policy-link{margin:18px 0 0;font-size:11px;}

@media (max-width:760px){
  .xc-banner{left:16px;right:16px;bottom:16px;bottom:max(16px,env(safe-area-inset-bottom));width:auto;
    grid-template-columns:1fr;gap:14px;padding:18px;transform:translateY(16px);}
  .xc-banner.in{transform:translateY(0);}
  .xc-actions,.xa-settings-actions{display:grid;grid-template-columns:1fr;width:100%;}
  .xc-btn{width:100%;white-space:normal;text-align:center;min-height:42px;}
  .xc-link-btn{justify-self:center;}
  .xa-settings-trigger{min-height:44px;padding-inline:14px;font-size:9px;}
}
@media (prefers-reduced-motion:reduce){
  .xc-banner,.xc-btn,.xa-settings-trigger,.xa-settings-backdrop,.xa-settings-dialog{transition:none;}
}

/* ---- 9 · hand-drawn paging arrows (SYSTEM) --------------------------
   The artist's arrows from assets/img/whiteroom/po/early-concept/, one
   component for every prev/next pair on the site: the Portable Objects
   book and the transfer picker on The White Room: Syncing. Day/night is
   automatic — black pair on light surfaces, white pair on dark ones; the
   White Room pages (body.wr-page) are a fixed-white cube, so they always
   keep the black pair. Pages may override locally (see po.css .po-page). */
.po-bk-btn{appearance:none;background-color:transparent;background-repeat:no-repeat;
  background-position:center;background-size:contain;border:0;padding:0;cursor:pointer;
  width:clamp(50px,5.4vw,68px);height:clamp(32px,3.4vw,42px);
  opacity:.8;transition:opacity .25s ease,transform .25s ease;}
.po-bk-btn:hover{opacity:1;}
.po-bk-prev:hover{transform:translateX(-3px);}
.po-bk-next:hover{transform:translateX(3px);}
.po-bk-btn:disabled{opacity:.22;cursor:default;transform:none;}
.po-bk-prev{background-image:url(../img/whiteroom/po/early-concept/arrow-left-black.png);}
.po-bk-next{background-image:url(../img/whiteroom/po/early-concept/arrow-right-black.png);}
@media (prefers-color-scheme:dark){
  :root:not([data-theme="light"]) body:not(.wr-page) .po-bk-prev{background-image:url(../img/whiteroom/po/early-concept/arrow-left-white.png);}
  :root:not([data-theme="light"]) body:not(.wr-page) .po-bk-next{background-image:url(../img/whiteroom/po/early-concept/arrow-right-white.png);}
}
:root[data-theme="dark"] body:not(.wr-page) .po-bk-prev{background-image:url(../img/whiteroom/po/early-concept/arrow-left-white.png);}
:root[data-theme="dark"] body:not(.wr-page) .po-bk-next{background-image:url(../img/whiteroom/po/early-concept/arrow-right-white.png);}
@media (prefers-reduced-motion:reduce){.po-bk-btn{transition:opacity .2s ease;}}

/* ===== media protection — pairs with site.js module 8 ===================== */
img, video, canvas {
  -webkit-touch-callout: none;   /* iOS long-press "Save Image" */
  -webkit-user-drag: none;       /* drag photo to desktop */
  user-select: none;
}

/* ===== mobile menu — opens instantly. Animating backdrop-filter freezes iOS
   Safari over image-heavy pages, so coarse pointers get a plain dark veil. == */
@media (max-width:760px), (pointer:coarse){
  .menu{background:rgba(8,8,8,.93);backdrop-filter:none;-webkit-backdrop-filter:none;
    transition:opacity .42s cubic-bezier(.2,.7,.2,1);}
  .menu.leaving{backdrop-filter:none;-webkit-backdrop-filter:none;}
}
