:root{--ink:#090a0b;--graphite:#111315;--paper:#f6f5f1;--warm:#f2f1ed;--deep:#141618;--muted-d:rgba(242,241,237,.6);--muted-l:rgba(20,22,24,.66);--line-d:rgba(255,255,255,.14);--line-l:rgba(20,22,24,.14);--brand:#D70B1C;--brand-soft:#FF3D4E;--brand-deep:#9E0824;--gutter:clamp(28px,4vw,72px);--space:clamp(200px,20vw,360px);--wide:1760px;--ease:cubic-bezier(.16,1,.3,1);--header-h:80px;--h2:clamp(40px,4.8vw,76px);--sco-measure:900px;--cap-flow-scroll:200vh}
@media(max-width:1023px){:root{--header-h:68px}}

/* Design Gate A refinement: evidence-led desktop capability visuals and selected-work balance */
body .selected-work-section .section-head{margin-bottom:clamp(44px,4.5vw,68px)}body .selected-work-section .lead-work .media{height:min(55vw,640px)}

/* Design Gate A final punch: state-correct nodes and local desktop balance */
*{box-sizing:border-box}html{scroll-behavior:smooth;background:var(--ink)}html[lang=ko]{word-break:keep-all;overflow-wrap:break-word}body{margin:0;background:var(--ink);color:var(--warm);font-family:Pretendard,Inter,Arial,"Noto Sans KR",sans-serif;-webkit-font-smoothing:antialiased}body.menu-open{overflow:hidden}a{color:inherit;text-decoration:none}button{font:inherit;color:inherit}img{max-width:100%}:focus-visible{outline:2px solid var(--brand-soft);outline-offset:5px}.wrap{width:min(100%,var(--wide));margin:auto;padding-inline:var(--gutter)}.section{padding-block:var(--space)}.dark{background:var(--ink);color:var(--warm)}.graphite{background:var(--graphite);color:var(--warm)}.light{background:var(--paper);color:var(--deep)}.grid{display:grid;grid-template-columns:repeat(12,minmax(0,1fr));gap:24px}.meta,.eyebrow{font-size:11px;line-height:1.3;letter-spacing:.12em;text-transform:uppercase}.muted-dark{color:var(--muted-d)}.muted-light{color:var(--muted-l)}.display{margin:0;font-size:clamp(52px,7vw,112px);line-height:.94;letter-spacing:-.05em;font-weight:560}.section-title{margin:0;font-size:var(--h2);line-height:1.03;letter-spacing:-.04em;font-weight:560}.body-large{font-size:clamp(19px,1.7vw,25px);line-height:1.6}.copy{font-size:16px;line-height:1.72}.rule{border-top:1px solid currentColor;opacity:.18}.link{display:inline-flex;gap:10px;align-items:center;margin-top:30px;padding-bottom:7px;border-bottom:1px solid currentColor;font-size:11px;font-weight:700;letter-spacing:.1em}.link span{transition:transform .22s}.link:hover span{transform:translateX(4px)}.status{font-size:11px;font-weight:700;letter-spacing:.09em;text-transform:uppercase}.status-progress{color:var(--brand-soft)}.reveal{opacity:0;transform:translateY(36px);transition:opacity .7s var(--ease),transform .7s var(--ease);transition-delay:calc(var(--reveal-i,0) * 55ms)}.reveal.visible{opacity:1;transform:none}
[data-parallax]{will-change:transform}

/* shared header */
.site-header{position:fixed;inset:0 0 auto;z-index:1000;height:80px;background:transparent;border-bottom:1px solid rgba(255,255,255,.06);transition:background .3s var(--ease),border-color .3s var(--ease)}.site-header.scrolled,.site-header.solid{background:rgba(8,8,10,.72);backdrop-filter:blur(16px);-webkit-backdrop-filter:blur(16px);border-color:rgba(255,255,255,.08)}.nav{height:100%;display:flex;align-items:center;justify-content:space-between}.brand{font-size:13px;font-weight:760;letter-spacing:.11em}.nav-side,.nav-links,.lang{display:flex;align-items:center}.nav-side{gap:36px}.nav-links{gap:30px}.nav-links a{position:relative;padding:12px 2px;font-size:13px;font-weight:650;letter-spacing:.03em;color:rgba(255,255,255,.8);transition:color .25s var(--ease)}.nav-links a:before{content:"";position:absolute;inset:-10px -6px}.nav-links a:after{content:"";position:absolute;left:0;right:0;bottom:7px;height:1px;background:currentColor;transform:scaleX(0);transform-origin:right;transition:transform .34s var(--ease)}.nav-links a:hover{color:#fff}.nav-links a:hover:after{transform:scaleX(1);transform-origin:left}.nav-links a.active{color:#fff}.nav-links a.active:after{transform:scaleX(1);background:var(--brand-soft)}.lang{gap:2px;padding:3px;border:1px solid rgba(255,255,255,.16);border-radius:999px}.lang button{border:0;background:transparent;border-radius:999px;padding:8px 15px;cursor:pointer;color:rgba(255,255,255,.48);font-size:11px;font-weight:650;letter-spacing:.04em;transition:background .3s var(--ease),color .3s var(--ease)}.lang button.active{background:rgba(255,255,255,.92);color:#08080a}.nav-panel{display:none}

/* hero */
.home-hero{position:relative;min-height:100svh;overflow:hidden;isolation:isolate}.home-hero .progressive-image,.home-hero .hero-video{position:absolute;top:-8%;bottom:-8%;left:0;right:0;height:auto}
.home-hero .hero-video{width:100%;height:116%;object-fit:cover;object-position:center;pointer-events:none;z-index:1}.home-hero .progressive-image__preview,.home-hero .progressive-image__full{object-fit:cover;object-position:center}.hero-shade{position:absolute;inset:0;background:linear-gradient(90deg,rgba(9,10,11,.84),rgba(9,10,11,.48) 40%,rgba(9,10,11,.08) 70%,rgba(9,10,11,.18))}.home-hero-inner{position:relative;z-index:2;min-height:100svh;display:flex;align-items:flex-end;padding-bottom:clamp(72px,9vh,112px)}.hero-copy{max-width:min(48vw,820px)}.hero-copy .eyebrow{color:rgba(242,241,237,.68);margin-bottom:24px}.hero-title{margin:0;font-weight:560;letter-spacing:-.04em;line-height:1.02;font-size:clamp(52px,6.1vw,92px)}html[lang=en] .hero-title{font-size:clamp(58px,6.8vw,108px);line-height:.95}.hero-support{max-width:640px;color:rgba(242,241,237,.78);margin:28px 0 0;font-size:clamp(18px,1.5vw,23px);line-height:1.55}
/* The entrance runs as an animation, not a transition, because the fourth child of this
   block is the hero CTA and it needs `transition` for its own hover. As a transition the
   stagger's transition-delay:.36s stayed on the element for the life of the page and
   silently applied to the hover too: the label's colour change arrived 360ms late, long
   after the fill had covered it, and the CTA read as blank for that whole window. An
   animation states the entrance without touching a property the hover depends on. */
.hero-copy>*{opacity:0;transform:translateY(28px)}
.loaded .hero-copy>*{animation:hero-copy-in .8s var(--ease) forwards}
.loaded .hero-copy>*:nth-child(2){animation-delay:.12s}
.loaded .hero-copy>*:nth-child(3){animation-delay:.24s}
.loaded .hero-copy>*:nth-child(4){animation-delay:.36s}
@keyframes hero-copy-in{to{opacity:1;transform:none}}
@media(prefers-reduced-motion:reduce){.hero-copy>*{opacity:1;transform:none}.loaded .hero-copy>*{animation:none}}

.page-hero{min-height:72svh;padding-top:160px;display:flex;align-items:flex-end;padding-bottom:clamp(72px,10vw,140px);position:relative;overflow:hidden}.page-hero .wrap{position:relative;z-index:1}.page-hero .eyebrow{color:var(--muted-d);margin-bottom:28px}.page-hero .display{max-width:1200px}.page-hero .body-large{max-width:720px;color:var(--muted-d);margin:36px 0 0}

/* home evolution + capabilities */
.evo-label{grid-column:1/3;color:var(--muted-l)}.evo-main{grid-column:4/12}.evo-main .body-large{max-width:680px;margin:55px 0 0 auto;color:var(--muted-l)}

/* 01 / EVOLUTION — full-bleed type marquee, no band or rules: just the sequence
   drifting across the paper surface. Speed and direction are driven from scroll
   in site.js (drifts slowly at rest, accelerates while scrolling, and runs
   backwards when the user scrolls up). */
.dna-band{position:relative;overflow:hidden;margin-top:clamp(88px,10vw,150px)}
.dna-track{display:flex;width:max-content;transform:translate3d(var(--dna-x,0px),0,0)}
.dna-half{display:flex;flex:none}
/* The floor is 21px, not a proportional shrink: below ~20px the band stops reading as
   a moving statement and starts reading as a stray rule. Mobile keeps roughly half the
   desktop reduction so the type survives; scroll speed and star rotation are unchanged. */
.dna-seq{display:inline-flex;align-items:center;white-space:nowrap;
  font-size:clamp(21px,1.7vw,29px);font-weight:600;letter-spacing:.04em;color:var(--deep)}
.dna-arrow{font-style:normal;margin-inline:clamp(14px,1.6vw,30px);color:rgba(20,22,24,.62)}
/* Was the character U+2733. On iOS that glyph has emoji presentation, so the band's
   star arrived as a green emoji on phones while desktop drew the line asterisk from
   the text font. Drawn as SVG it is the same mark everywhere and inherits the ink. */
.dna-star{display:inline-flex;align-items:center;color:var(--deep);
  margin-inline:clamp(60px,9vw,170px);transform:rotate(var(--dna-rot,0deg))}
.dna-star svg{width:1em;height:1em;display:block}

/* work cards and media */
.section-head{display:flex;justify-content:space-between;align-items:flex-end;padding-bottom:22px;border-bottom:1px solid currentColor;margin-bottom:clamp(52px,6vw,90px)}.section-head small{opacity:.55}.lead-work{display:grid;grid-template-columns:7fr 4fr;gap:clamp(28px,5vw,84px);align-items:end}.media{overflow:hidden;background:#17191b}.media img{display:block;width:100%;height:100%;object-fit:cover;transition:transform .7s var(--ease)}.media:hover img{transform:scale(1.015)}.lead-work .media{height:min(62vw,720px)}.work-info h3{font-size:clamp(40px,4.8vw,76px);line-height:1.02;letter-spacing:-.04em;margin:20px 0}.work-info p{color:var(--muted-l);max-width:530px}.secondary-work{display:grid;grid-template-columns:4fr 6fr;gap:clamp(28px,5vw,84px);align-items:center;margin-top:clamp(100px,13vw,190px)}.secondary-work .media{height:min(45vw,560px)}.editorial-cases{display:grid;grid-template-columns:1fr 1fr;margin-top:clamp(100px,12vw,170px);border-top:1px solid var(--line-l)}.editorial-case{padding:44px 7vw 60px 0;border-bottom:1px solid var(--line-l)}.editorial-case:nth-child(even){padding-left:4vw;border-left:1px solid var(--line-l)}.editorial-case h3{font-size:clamp(30px,3vw,48px);margin:18px 0}.editorial-case .thumb{height:230px;margin-bottom:30px}.editorial-case .thumb img{object-fit:contain;background:#e8e7e2}.proof-list{display:grid;grid-template-columns:5fr 7fr;border-top:1px solid var(--line-l)}.proof-feature{padding:50px 5vw 50px 0}.proof-feature img{width:100%;object-fit:contain;filter:saturate(.35)}.proof-rows{border-left:1px solid var(--line-l)}.proof-row{display:grid;grid-template-columns:120px minmax(0,1fr);gap:24px;padding:30px 0 30px 4vw;border-bottom:1px solid var(--line-l)}.proof-row h3{margin:0;font-size:20px}.proof-row p{margin:8px 0 0;color:var(--muted-l)}
.current{position:relative;overflow:visible}.current:before{content:"";position:absolute;inset:0;background:repeating-linear-gradient(90deg,transparent 0 calc(8.33% - 1px),rgba(255,255,255,.04) calc(8.33% - 1px) 8.33%);opacity:.6}.current .wrap{position:relative}.current-grid{display:grid;grid-template-columns:4fr 7fr;gap:8.33vw;align-items:end}.current h2{font-size:var(--h2);line-height:1.02;letter-spacing:-.045em;margin:18px 0 0}.current-copy{color:var(--muted-d)}
.bridge{display:grid;grid-template-columns:8fr 4fr;align-items:end;gap:40px}.bridge .section-title{max-width:980px}.bridge-links{display:flex;flex-direction:column;align-items:flex-start}

/* long-form pages */
.statement{grid-column:3/12}.statement p{font-size:clamp(38px,4.7vw,72px);line-height:1.12;letter-spacing:-.04em;margin:0}.phases{margin-top:80px;border-top:1px solid var(--line-l)}.phase{display:grid;grid-template-columns:minmax(0,2fr) minmax(0,4.4fr) minmax(0,4.6fr);gap:24px;padding:28px 0;border-bottom:1px solid var(--line-l)}.phase strong{font-size:clamp(25px,2.8vw,42px);font-weight:560}
/* At 768-1023 the phase label column is ~191px and "ENVIRONMENTS" measures 194px at
   the 25px floor, so the longest single word overflowed by 3px. Wrapping is word-level
   by request, so the word cannot be broken -- the floor steps down instead. */
@media(min-width:768px) and (max-width:1023px){.phase strong{font-size:23px}}.phase p{margin:0;color:var(--muted-l);line-height:1.6}.timeline{border-top:1px solid var(--line-d)}.timeline-row{display:grid;grid-template-columns:minmax(0,2fr) minmax(0,4fr) minmax(0,6fr);gap:24px;padding:28px 0;border-bottom:1px solid var(--line-d)}.timeline-row strong{font-size:23px}.timeline-row p{margin:0;color:var(--muted-d);line-height:1.6}.fact-line{display:grid;grid-template-columns:repeat(3,1fr);border-top:1px solid var(--line-l)}.fact{padding:36px 3vw 36px 0;border-bottom:1px solid var(--line-l)}.fact+ .fact{padding-left:3vw;border-left:1px solid var(--line-l)}.fact h3{margin:0 0 12px;font-size:24px}.fact p{margin:0;color:var(--muted-l)}
.cap-detail{display:grid;grid-template-columns:4fr 7fr;gap:8.33vw;padding-block:var(--space);border-top:1px solid var(--line-d)}.cap-detail h2{font-size:var(--h2);line-height:1;letter-spacing:-.045em;margin:18px 0}.cap-detail .body-large{color:var(--muted-d)}.tag-list{display:flex;flex-wrap:wrap;gap:10px;margin-top:clamp(30px,3vw,44px)}.tag{display:inline-flex;align-items:center;padding:10px 16px;border:1px solid var(--line-d);border-radius:999px;font-size:11px;line-height:1;letter-spacing:.08em;color:var(--muted-d);transition:border-color .3s var(--ease),color .3s var(--ease),background .3s var(--ease)}
.tag:hover{border-color:currentColor;color:inherit}
.work-lead{height:min(60vw,760px)}.work-duo{display:grid;grid-template-columns:1.15fr .85fr;gap:6px;height:100%}.work-duo img{width:100%;height:100%;object-fit:cover}.work-duo .progressive-image+.progressive-image img{filter:brightness(.82)}.project-row{display:grid;grid-template-columns:minmax(0,2fr) minmax(0,4fr) minmax(0,5fr);gap:32px;padding:38px 0;border-top:1px solid var(--line-l)}.project-row h3{font-size:clamp(27px,3vw,46px);margin:0}.project-row p{margin:0;color:var(--muted-l);line-height:1.65}.archive-cols{display:grid;grid-template-columns:1fr 1fr;gap:8vw}.archive-col h3{font-size:28px;margin:0 0 30px}.archive-list{list-style:none;margin:0;padding:0;border-top:1px solid var(--line-d)}.archive-list li{display:flex;justify-content:space-between;gap:20px;padding:18px 0;border-bottom:1px solid var(--line-d);color:var(--muted-d)}.archive-list strong{color:var(--warm);font-weight:560}.engagement{display:grid;grid-template-columns:140px minmax(0,1fr) 220px;gap:24px;padding:22px 0;border-top:1px solid var(--line-l)}
.contact-list{border-top:1px solid var(--line-l)}.contact-row{display:grid;grid-template-columns:minmax(0,5fr) minmax(0,6fr) auto;column-gap:clamp(24px,3vw,56px);padding:32px 0;border-bottom:1px solid var(--line-l);align-items:baseline}.contact-row h3{margin:0;font-size:clamp(23px,2.1vw,34px);line-height:1.16;letter-spacing:-.025em}.contact-row p{margin:0;color:var(--muted-l);max-width:46ch}.placeholder{border:1px solid var(--line-l);padding:34px;color:var(--muted-l);margin-top:50px}.footer{padding:46px 0;background:var(--ink);color:var(--muted-d);border-top:1px solid var(--line-d)}.footer-inner{display:flex;justify-content:space-between;gap:24px}.footer a{color:var(--warm)}

@media(max-width:1023px){.site-header{height:68px}.nav-links{display:none}.nav-panel.open{opacity:1;visibility:visible;transform:none}.home-hero{min-height:100svh}.home-hero-inner{min-height:100svh}.hero-copy{max-width:62vw}.lead-work,.secondary-work,.current-grid,.cap-detail{grid-template-columns:1fr}.secondary-work .media{order:-1}.project-info{padding-top:20px}.statement{grid-column:2/12}.page-hero{min-height:65svh}.archive-cols{gap:4vw}.contact-row{grid-template-columns:minmax(0,5fr) minmax(0,6fr)}.contact-row .meta{display:none}}
@media(max-width:767px){:root{--gutter:20px;--space:184px}.grid{display:block}.hero-copy{max-width:100%}.home-hero .hero-video{object-position:center}.hero-shade{background:linear-gradient(180deg,rgba(9,10,11,.08),rgba(9,10,11,.28) 42%,rgba(9,10,11,.94) 86%,rgba(9,10,11,.99))}.hero-title{font-size:clamp(43px,12vw,58px)}html[lang=en] .hero-title{font-size:clamp(46px,12vw,61px)}.hero-support{font-size:17px}.page-hero{min-height:72svh;padding-top:120px}.display{font-size:clamp(48px,14vw,74px)}.evo-label{margin-bottom:58px}.evo-main .body-large{margin-top:38px}.section-head{align-items:flex-start;gap:20px}.lead-work,.secondary-work{display:block}.lead-work .media,.secondary-work .media{height:72vw;margin-bottom:34px}.editorial-cases{display:block}.editorial-case,.editorial-case:nth-child(even){padding:38px 0;border-left:0}.editorial-case .thumb{height:190px}.proof-list{display:block}.proof-feature{padding-right:0}.proof-rows{border-left:0}.proof-row{grid-template-columns:90px minmax(0,1fr);padding-left:0}.current-grid{display:block}.current-copy{margin-top:50px}.bridge{display:block}.bridge-links{margin-top:50px}.statement{margin:0}.statement p{font-size:37px}.phase,.timeline-row{grid-template-columns:1fr}.phase{gap:12px}.timeline-row{gap:12px}.fact-line{display:block}.fact,.fact+.fact{padding:28px 0;border-left:0}.cap-detail{display:block}.work-lead{height:auto}.work-duo{display:block}.work-duo img{height:72vw}.project-row{grid-template-columns:1fr;gap:12px}.archive-cols{display:block}.archive-col+ .archive-col{margin-top:70px}.engagement{grid-template-columns:1fr;gap:8px}.contact-row{display:block}.contact-row p{margin-top:10px}.footer-inner{display:block}}
@media(prefers-reduced-motion:reduce){*,*:before,*:after{animation-duration:.01ms!important;animation-iteration-count:1!important;transition-duration:.01ms!important;scroll-behavior:auto!important}.reveal{opacity:1;transform:none}[data-parallax]{transform:none!important}[data-parallax-bg]{background-position:center center!important}}

/* HOME Final Punch Pass: scoped state, lead-work title visibility. */
@media(min-width:1024px){body .selected-work-section .lead-work .work-info{transform:translateY(-44px)}}

/* WORK Refinement Pass 01: page-scoped hierarchy and evidence-weight adjustments. */
.work-page .secondary-work{grid-template-columns:5fr 5fr}
.work-page .secondary-work .media{height:min(30vw,380px);background:#e8e7e2}
.work-page .secondary-work .media img{object-fit:contain}
.work-support .section-head{margin-bottom:clamp(34px,3.4vw,54px)}
.work-support .section-title{font-size:var(--h2)}
.work-transition{padding-block:84px}
.work-systems{padding-block:100px}
.work-systems .archive-list li{padding-block:14px}
.work-systems .archive-col .media{height:240px!important;margin-top:26px!important}
.work-engagements{padding-block:84px}
.work-engagements .engagement{padding-block:14px}
.work-technology-proof{padding-block:80px}
.work-technology-proof .fact{padding-top:26px;padding-bottom:26px}
@media(max-width:1023px){.work-page .secondary-work .media{height:min(46vw,380px)}.work-transition{padding-block:76px}.work-systems{padding-block:88px}.work-engagements{padding-block:76px}.work-technology-proof{padding-block:72px}}
@media(max-width:767px){.work-page .secondary-work .media{height:54vw}.work-support .section-head{margin-bottom:36px}.work-transition,.work-systems,.work-engagements,.work-technology-proof{padding-block:64px}.work-systems .archive-col .media{height:56vw!important}.work-engagements .engagement{padding-block:12px}.work-technology-proof .fact{padding-block:22px}}

/* WORK Final Visual Pass: approved Yancheng evidence hierarchy only. */
.work-page .work-duo{grid-template-columns:65fr 35fr}
@media(max-width:767px){.work-page .work-duo{display:block}}

/* WORK SCO Evidence Integration: field primary, public-context secondary. */
.work-current .current-grid{align-items:start}
.work-current .current-copy{display:grid;grid-template-columns:1fr 1fr;gap:28px;color:var(--muted-d)}
.sco-evidence{position:relative;margin:0;overflow:hidden;background:#0b0d0f;border:1px solid var(--line-d)}
.sco-evidence img{display:block;width:100%;height:100%;object-fit:cover}
.sco-evidence figcaption{position:absolute;left:14px;bottom:12px;padding:7px 9px;background:rgba(9,10,11,.78);color:rgba(242,241,237,.8);font-size:9px;letter-spacing:.12em;text-transform:uppercase;backdrop-filter:blur(8px)}
.sco-evidence-secondary img{object-position:center}
/* Figure box size is driven by aspect-ratio down in the ONGOING PROJECT block, not by
   fixed heights here: both sources are 16:9, and a shared px/vw row height was cropping
   them to portrait at tablet. */
@media(max-width:767px){.sco-lead{font-size:18px}.sco-evidence figcaption{left:10px;bottom:9px}}

/* DESIGN GATE C REFINEMENT PASS 01 — page-scoped density and evidence hierarchy. */
.company-history{padding-block:clamp(76px,7vw,112px)}
.company-history .section-head{margin-bottom:clamp(38px,4vw,58px)}
.company-history .timeline-row{padding-block:20px}
.company-credibility{padding-block:clamp(76px,7vw,112px)}
.company-credibility .section-head{margin-bottom:clamp(38px,4vw,58px)}
.company-credibility .proof-row{padding-block:22px}
.company-facts{padding-block:clamp(72px,7.1vw,104px)}
.company-facts .section-head{margin-bottom:36px}
.company-facts .fact{padding-block:26px}
.contact-endpoint{padding-block:clamp(76px,7vw,112px)}
.contact-reserved{display:grid;grid-template-columns:minmax(0,2fr) minmax(0,8fr) auto;align-items:center;gap:24px;padding:30px 0;border-top:1px solid var(--line-l);border-bottom:1px solid var(--line-l);color:var(--muted-l)}
.contact-reserved strong{font-size:clamp(28px,3vw,46px);font-weight:560;color:var(--deep)}
.contact-profile{padding-block:clamp(64px,6vw,92px)}
.contact-profile .placeholder{padding:24px}
@media(max-width:1023px){.company-history,.company-credibility{padding-block:88px}.company-facts{padding-block:72px}}
@media(max-width:767px){.contact-reserved{grid-template-columns:1fr auto;gap:12px}.contact-reserved .meta{grid-column:1/-1}.company-history,.company-credibility{padding-block:72px}.company-history .timeline-row{padding-block:18px}.company-facts{padding-block:64px}}

/* LIMITED VISUAL AUTHORITY CORRECTION — hierarchy, scale, and deliberate rhythm only. */
@media(min-width:1024px){
  /* HOME: each chapter carries one dominant statement or evidence field. */
  .home-page main>.section{padding-block:clamp(148px,12vw,190px)}
  .home-page main>.section:nth-of-type(2) .evo-main{grid-column:4/12}
  .home-page main>.section:nth-of-type(2) .body-large{max-width:760px;margin-top:58px}
  .home-page .selected-work-section .section-head{margin-bottom:clamp(88px,8vw,124px)}
  .home-page .selected-work-section .lead-work{grid-template-columns:minmax(0,8fr) minmax(300px,3fr);gap:clamp(54px,6vw,104px)}
  .home-page .selected-work-section .lead-work .media{height:min(58vw,790px)}
  .home-page .selected-work-section .lead-work .work-info{transform:translateY(-70px)}
  .home-page .selected-work-section .lead-work .work-info h3{font-size:clamp(54px,5.4vw,84px)}
  .home-page .selected-work-section .secondary-work{grid-template-columns:3.5fr 7fr;margin-top:clamp(150px,15vw,230px)}
  .home-page .selected-work-section .secondary-work .media{height:min(36vw,500px)}
  .home-page .current-grid{grid-template-columns:7fr 4fr;align-items:center;gap:8.33vw}
  .home-page .current-copy{font-size:clamp(19px,1.5vw,23px);line-height:1.7}

  /* WORK: current development and delivered evidence read as full chapters. */
  .work-page .work-current{padding-block:clamp(150px,12vw,190px)}
  .work-page .work-current .current-grid{grid-template-columns:4fr 8fr;gap:clamp(54px,6vw,96px)}
  /* NB: the SCO title is NOT sized here — it sits in a 218-313px rail, where this
     display scale rags to four lines. See .sco-head h2 in the ONGOING PROJECT block. */
  .work-page main>.section.light:nth-of-type(3){padding-block:clamp(160px,13vw,210px)}
  .work-page main>.section.light:nth-of-type(3) .section-head{margin-bottom:clamp(90px,8vw,130px)}
  .work-page .lead-work{grid-template-columns:minmax(0,8fr) minmax(310px,3fr);gap:clamp(56px,6vw,104px)}
  .work-page .lead-work .work-info h3{font-size:clamp(56px,5.4vw,84px)}
  .work-page .work-lead{height:min(58vw,800px)}
  .work-page .secondary-work{margin-top:clamp(170px,16vw,250px)}
  .work-page .editorial-cases{margin-top:clamp(150px,14vw,220px)}
  .work-page .editorial-case{padding-top:62px;padding-bottom:82px}

  /* CAPABILITIES: domains lead; keywords recede. */
  .capabilities-page .cap-detail{grid-template-columns:4fr 8fr;gap:8.33vw;padding-block:clamp(140px,11vw,180px)}
  .capabilities-page .cap-detail .body-large{font-size:clamp(21px,1.7vw,27px);max-width:720px}

  /* COMPANY: retain reduced content density, increase chapter separation. */
  .company-page .company-history,.company-page .company-credibility{padding-block:clamp(118px,9vw,154px)}
  .company-page .company-facts{padding-block:clamp(104px,8vw,138px)}
  .company-page .timeline-row{grid-template-columns:minmax(0,2fr) minmax(0,3fr) minmax(0,7fr);padding-block:26px}
  .company-page .company-credibility .proof-list{grid-template-columns:6fr 6fr}
}

@media(min-width:768px) and (max-width:1023px){
  .home-page main>.section,.work-page main>.section,.capabilities-page main>.section{padding-block:112px}
  .home-page .selected-work-section .lead-work .media{height:min(70vw,700px)}
  .home-page .selected-work-section .work-info{padding-top:44px}
  .work-page main>.section.light:nth-of-type(3){padding-block:124px}
  .capabilities-page .cap-detail{padding-block:116px}
}

@media(max-width:767px){
  .home-page main>.section{padding-block:112px}
  
  .home-page main>.section:nth-of-type(2) .body-large{margin-top:44px}
  .home-page .selected-work-section{padding-block:124px}
  .home-page .selected-work-section .section-head{margin-bottom:62px}
  .home-page .selected-work-section .lead-work .media{height:84vw}
  .home-page .selected-work-section .lead-work .work-info h3{font-size:46px}
  .home-page .selected-work-section .secondary-work{margin-top:130px}
  .home-page .current-copy{margin-top:64px}

  .work-page .work-current{padding-block:118px}
  .work-page .work-current .current-copy{margin-top:58px;gap:38px}
  .work-page main>.section.light:nth-of-type(3){padding-block:124px}
  .work-page main>.section.light:nth-of-type(3) .section-head{margin-bottom:64px}
  .work-page .work-lead .work-duo .progressive-image:first-child{height:86vw}
  .work-page .work-lead .work-duo .progressive-image+.progressive-image{height:58vw;margin-top:6px}
  .work-page .lead-work .work-info{padding-top:10px}
  .work-page .lead-work .work-info h3{font-size:48px}
  .work-page .secondary-work{margin-top:132px}
  .work-page .editorial-cases{margin-top:120px}

  .capabilities-page .cap-detail{padding-block:118px}
  .capabilities-page .cap-detail .body-large{font-size:20px;line-height:1.65}

  .company-page .company-history,.company-page .company-credibility{padding-block:86px}
  .company-page .company-facts{padding-block:78px}
}

/* CONTACT pending-endpoint state — honest status, no false link affordance. */
.contact-reserved.is-pending{grid-template-columns:minmax(0,2fr) minmax(0,8fr) auto;cursor:default}
.contact-reserved.is-pending strong{color:var(--muted-l)}
.contact-reserved .contact-pending-tag{grid-column:auto;justify-self:end;align-self:center;width:max-content;padding:6px 10px;border-radius:999px;background:rgba(20,22,24,.06);color:rgba(20,22,24,.72);white-space:nowrap}
.contact-pending-note{max-width:640px;margin:26px 0 0;color:var(--muted-l);font-size:16px;line-height:1.72}
.contact-pending-links{display:flex;flex-wrap:wrap;gap:0 40px}
@media(max-width:767px){.contact-reserved.is-pending{grid-template-columns:1fr auto}.contact-reserved .contact-pending-tag{grid-column:2;justify-self:end}.contact-pending-links{flex-direction:column;gap:0}}

/* Touch target minimum (WCAG 2.2 AA 2.5.8) — invisible hit area, no layout shift.
   Controls whose hit areas would sit side by side (the KR/EN pill, MENU) grow their
   real box instead, so the expanders never overlap each other. */
@media(max-width:1023px){
  .link,.brand,.footer a,.nav-menu a{position:relative}
  .link::after,.brand::after,.footer a::after,.nav-menu a::after{content:"";position:absolute;left:0;right:0;top:50%;transform:translateY(-50%);height:44px}
  .site-header .menu-trigger{min-width:44px;min-height:44px;padding:8px 4px;text-align:right}
  .lang button{min-height:44px;padding:13px 15px}
  .lang{padding:2px}
  .nav-lang button{min-height:44px}
}

/* ============================================================
   VISUAL AUTHORITY PASS 02
   01 HOME SCO flagship band · 02 CTA system · 03 CORE density
   04 type mid-tier · 05 background rhythm · 07 hero + interaction
   ============================================================ */

:root{
  --fs-sub:clamp(27px,2.6vw,38px);
  --cta:13px;
  --cta-lg:15px;
}

/* ---------- 02. CTA SYSTEM — links become real targets ---------- */
.link{
  gap:14px;
  margin-top:36px;
  padding:15px 24px;
  border:1px solid currentColor;
  border-bottom-width:1px;
  font-size:var(--cta);
  letter-spacing:.07em;
  transition:background .34s var(--ease),color .34s var(--ease),border-color .34s var(--ease);
}
.link span{font-size:1.15em;line-height:1}
.link-lg{padding:19px 32px;font-size:var(--cta-lg);margin-top:40px}
.light .link:hover{color:var(--paper);border-color:var(--deep)}
.dark .link:hover,.graphite .link:hover,.home-hero .link:hover,.page-hero .link:hover{
  color:var(--ink);border-color:var(--warm)}
.link:focus-visible{outline:2px solid var(--brand-soft);outline-offset:4px}
.bridge-links{gap:16px}
.footer a{padding:10px 0;font-weight:650}

/* ---------- 01. HOME · SCO FLAGSHIP BAND — full-bleed background hero ---------- */
.home-page .home-sco{padding-block:0!important}
.sco-hero{position:relative;overflow:hidden;background:#0b0d0f;min-height:min(113vh,1200px);display:grid}
.sco-hero .progressive-image{position:absolute;left:0;right:0;top:-80px;bottom:-80px;height:auto}
.sco-hero .progressive-image__preview,.sco-hero .progressive-image__full{object-fit:cover;object-position:center 56%}
.sco-hero:before{content:"";position:absolute;inset:0;pointer-events:none;background:rgba(9,10,11,.62)}
.sco-hero:after{content:"";position:absolute;inset:0;pointer-events:none;
  background:linear-gradient(180deg,rgba(9,10,11,.5) 0%,rgba(9,10,11,.28) 36%,rgba(72,6,12,0) 58%,rgba(110,8,18,.52) 82%,rgba(72,4,10,.9) 100%)}

.sco-hero-grid{
  position:relative;z-index:2;align-self:end;width:100%;
  display:grid;grid-template-columns:repeat(12,minmax(0,1fr));grid-template-rows:auto auto auto auto;
  gap:14px clamp(24px,3vw,40px);
  padding-block:clamp(48px,6vw,88px)}
.sco-hero-eyebrow{grid-column:1/7;grid-row:1}
.sco-hero-title{grid-column:1/9;grid-row:2;margin:14px 0 0;font-weight:700;letter-spacing:-.045em;line-height:1;font-size:clamp(50px,6.4vw,100px);color:var(--warm)}
.sco-hero-desc{grid-column:1/7;grid-row:3;margin:clamp(24px,3vw,40px) 0 0;color:var(--warm)}
.sco-hero-status-block{grid-column:1/7;grid-row:4;margin-top:20px}
.sco-status-line{display:flex;align-items:center;gap:9px;font-size:11px;font-weight:700;letter-spacing:.12em;text-transform:uppercase;color:var(--brand-soft)}
.sco-status-dot{width:6px;height:6px;border-radius:50%;background:var(--brand-soft);box-shadow:0 0 0 4px rgba(215,11,28,.18)}
.sco-hero-status-block .sco-disclaimer{margin:8px 0 0;color:var(--muted-d)}
.sco-hero-cta{grid-column:9/13;grid-row:3/5;align-self:end;justify-self:end;margin-top:0}

@media(max-width:1023px){
  .sco-hero-grid{grid-template-columns:1fr;grid-template-rows:none}
  .sco-hero-eyebrow,.sco-hero-title,.sco-hero-desc,.sco-hero-status-block,.sco-hero-cta{grid-column:1/-1;grid-row:auto;justify-self:start;text-align:left}
  .sco-hero-cta{margin-top:clamp(28px,3vw,40px)}
}
@media(max-width:767px){
  .sco-hero{min-height:98vh}
  .sco-hero-grid{gap:10px;padding-block:56px}
  .sco-hero-title{font-size:44px}
  .link{padding:15px 20px}
  .link-lg{padding:17px 24px;font-size:14px}
}

/* ---------- 02b. CTA scale by context ---------- */
.home-hero .link,.page-hero .link,.bridge .link,.current-copy .link,.sco-hero-cta{
  padding:19px 32px;font-size:var(--cta-lg)}
.section-head .link{margin-top:0}
.bridge-links .link{margin-top:0}
.bridge-links{gap:14px}

/* ---------- CLOSING BRIDGE — graphite landing, converging lines toward the CTAs ---------- */
.bridge-close{position:relative;overflow:visible;padding-block:calc(var(--space) * 1.15)}
.bridge-close:before{content:"";position:absolute;inset:0;pointer-events:none;
  background:conic-gradient(from 208deg at 84% 92%,
    transparent 0 6deg,rgba(255,255,255,.05) 6deg 7deg,transparent 7deg 15deg,
    rgba(255,255,255,.04) 15deg 16deg,transparent 16deg 26deg,
    rgba(255,255,255,.035) 26deg 27deg,transparent 27deg 40deg,
    rgba(255,255,255,.03) 40deg 41deg,transparent 41deg 360deg);
  -webkit-mask-image:radial-gradient(120% 100% at 84% 92%,#000 0%,transparent 72%);
          mask-image:radial-gradient(120% 100% at 84% 92%,#000 0%,transparent 72%)}
.bridge-close .wrap{position:relative;z-index:1}
.bridge-eyebrow{margin-bottom:28px}
.bridge-support{max-width:46ch;margin:26px 0 0}
/* Title lands first, CTAs follow — cause before action.
   !important because site.js assigns --reveal-i inline, and .bridge-links is a
   separate parent so its counter would otherwise restart at 0 alongside the title. */
.bridge-close .section-title{--reveal-i:0!important}
.bridge-close .bridge-support{--reveal-i:2!important}
.bridge-close .bridge-links .link{--reveal-i:3!important}
.bridge-close .bridge-links .link+.link{--reveal-i:4!important}

/* ---------- 03b. Stop muting the strongest assets ---------- */
.proof-feature img,.company-credibility .proof-feature img{filter:none}

/* ---------- 04. Type scale — restore the missing middle ---------- */
.proof-row h3{font-size:clamp(21px,1.9vw,27px)}
.proof-row p{font-size:16px}
.fact h3{font-size:var(--fs-sub)}
.archive-col h3{font-size:var(--fs-sub)}
.timeline-row strong{font-size:clamp(22px,2vw,28px)}
.contact-reserved strong{font-size:var(--fs-sub)}
.meta,.eyebrow{font-size:12px}
.status{font-size:12px}
.sco-evidence figcaption{font-size:11px}

.archive-list li{font-size:15px}

/* Company DNA is a top-level brand asset, not a footnote */
@media(min-width:1024px){
}
@media(max-width:767px){
}

/* ---------- 05. BACKGROUND RHYTHM ---------- */
/* graphite must read as a genuine third surface, not a shade of ink */
:root{--graphite:#171a1d}

/* Blocks authored for the paper surface, re-hosted on a dark band */
.on-dark .proof-list,.on-dark .proof-rows,.on-dark .proof-row,
.on-dark .fact-line,.on-dark .fact,.on-dark .fact+.fact,
.on-dark .phases,.on-dark .phase,.on-dark .project-row,
.on-dark .engagement,.on-dark .contact-list,.on-dark .contact-row,
.on-dark .contact-reserved,.on-dark .placeholder,
.on-dark .editorial-cases,.on-dark .editorial-case,
.on-dark .editorial-case:nth-child(even),
.on-dark .section-head,.on-dark .tag-list,.on-dark .tag,
.on-dark .timeline,.on-dark .timeline-row,.on-dark .archive-list li{
  border-color:var(--line-d)}
.on-dark .proof-row p,.on-dark .fact p,.on-dark .phase p,
.on-dark .project-row p,.on-dark .work-info p,.on-dark .contact-row p,
.on-dark .contact-reserved,.on-dark .placeholder,.on-dark .evo-label,
.on-dark .evo-main .body-large,.on-dark .archive-list li,.on-dark .tag{
  color:var(--muted-d)}
.on-dark .contact-reserved strong,.on-dark .archive-list strong{color:var(--warm)}
.on-dark .editorial-case .thumb img,.on-dark .secondary-work .media{background:#16191b}

/* Capabilities CORE 02 chapter now sits on paper */
.capabilities-page .cap-band{padding-block:0}

/* Products & systems inventory — moved from Work. Same archive list as before,
   scoped here so Work no longer hosts a capability catalogue. */
.cap-systems .archive-list li{padding-block:14px}
.cap-systems .archive-visual{position:relative;height:240px;margin-top:26px;overflow:hidden;background:#0d0f11}
@media(max-width:767px){
  .cap-systems .archive-cols{display:block}
  .cap-systems .archive-col+.archive-col{margin-top:48px}
  .cap-systems .archive-visual{height:56vw}
}
.on-light .cap-detail{border-top-color:var(--line-l)}
/* The .on-dark class on this section is inherited from before subpages went to a single
   cream surface -- it now sits on #F9F5EF, where near-white text measured 1.03:1 and the
   badge was simply invisible. Ink, not paper. */
.on-dark .contact-reserved .contact-pending-tag{
  color:rgba(0,0,0,.72);background:rgba(0,0,0,.05);border-color:rgba(0,0,0,.16)}
.on-dark .contact-pending-note{color:var(--muted-l)}

/* ---------- 07. HERO IDENTITY PER PAGE + INTERACTION ---------- */
/* Heroes stay T-E brand visuals per ASSET GOVERNANCE — no project evidence. */
.page-hero .wrap,.home-hero-inner{position:relative;z-index:2}

/* The hero field is now one layer: the live particle wave, one curve per menu, selected by
   <body data-field> in js/hero-field.js. Three things used to stack here -- a per-page
   geometric pattern, a static SVG particle field over it, and the canvas over that -- and
   behind a single headline that was two too many. With the motion carrying the identity the
   other two only muddied it, so the SVG fields, their generator, the decorative ring and the
   four CSS patterns (strata lines, perception grid, field marks, conic lines) are gone.
   They are in the history at c56e68e if any of it is wanted back.

   Brightness lives in one place now: `intensity` in js/hero-field.js, set by the contrast
   measurement in motion/particle-wave/README.md. This element no longer dims it a second
   time, so there is no pair of dials to keep in agreement. */
.page-hero{isolation:isolate}
.hero-field{
  position:absolute;inset:-10% -6%;z-index:0;pointer-events:none;mix-blend-mode:screen;
  /* The copy gate is an accessibility fix, not a taste one, and it is what buys the field its
     brightness. The headline runs to about 63% of the width, so a gate that opened at 52%
     left the crest crossing live text: raising intensity to 0.52 against the old gate put a
     crest pixel of rgb(249,190,200) on a headline glyph at 1.40:1. Holding it closed to
     roughly where the words end instead means the right of the frame can run much brighter
     than the whole field ever could. Quiet where the words are, alive where they are not.
     It gates in page space, so it stays correct at any width — the column is anchored to
     the gutter while nothing about the composition is. */
  -webkit-mask-image:linear-gradient(90deg,transparent 0 14%,rgba(0,0,0,.1) 30%,rgba(0,0,0,.4) 48%,rgba(0,0,0,.82) 63%,#000 76%);
          mask-image:linear-gradient(90deg,transparent 0 14%,rgba(0,0,0,.1) 30%,rgba(0,0,0,.4) 48%,rgba(0,0,0,.82) 63%,#000 76%)}
/* This keeps the field out from under the fixed header, whose nav label and language pills
   sit directly over the hero and washed out once the field got bright enough to be worth
   having. A mask on the child multiplies with the parent's, so the two compose without
   needing mask-composite — which is the only way to say it in one layer and is patchy in
   older Safari.
   It is a per-page value because the pages do not carry the same copy, and because the curves
   meet it differently. Capabilities and contact clear their own headlines and need nothing but
   the header fade. Company's runs to 62% of the width with its curve crossing the second line,
   and work's crest train oscillates straight through its first — 2.63:1 and 2.61:1 measured
   with the header fade alone — so those two also dip across their copy band. Applying the dip
   to all four instead flattened contact, whose whole identity is a curve descending into the
   open space on the right. */
.hero-field-canvas{
  position:absolute;inset:0;display:block;width:100%;height:100%;
  opacity:0;transition:opacity 1.2s var(--ease);
  -webkit-mask-image:var(--hero-shield);mask-image:var(--hero-shield)}
.subpage{--hero-shield:linear-gradient(180deg,transparent 0 9%,rgba(0,0,0,.28) 16%,#000 24%)}
.company-page,.work-page{--hero-shield:linear-gradient(180deg,transparent 0 9%,rgba(0,0,0,.28) 16%,#000 23% 27%,rgba(0,0,0,.24) 35% 68%,#000 86%)}
.hero-field.is-live .hero-field-canvas{opacity:1}

/* Narrow viewports crop the hero to roughly a portrait box, and the copy runs full width
   there rather than sitting in a left column, so the horizontal gate protects nothing. The
   bottom fade replaces it. */
@media(max-width:767px){
  /* full-width copy here, so the parent's bottom fade is the whole shield */
  .subpage,.company-page,.work-page{--hero-shield:none}
  .hero-field{inset:-6% -4%;
    -webkit-mask-image:linear-gradient(180deg,#000 0 13%,rgba(0,0,0,.14) 30%,rgba(0,0,0,.03) 54%,transparent 72%);
            mask-image:linear-gradient(180deg,#000 0 13%,rgba(0,0,0,.14) 30%,rgba(0,0,0,.03) 54%,transparent 72%)}
}
@media(prefers-reduced-motion:reduce){
  .hero-field-canvas{transition:none}
}

/* Scroll cue. It should still read as secondary to the hero CTA, but it was being
   dimmed twice -- a muted colour at .6 alpha inside an element also set to opacity .6,
   compounding to .36 and 3.01:1, which is a hint nobody can see. It is white now, and
   the single opacity below is the only thing holding it back. */
.scroll-cue{
  position:absolute;z-index:3;left:50%;bottom:48px;transform:translateX(-50%);
  display:flex;flex-direction:column;align-items:center;gap:12px;
  color:#fff;font-size:11px;letter-spacing:.22em;opacity:.86}
/* The track was rgba(255,255,255,.14) -- 1.42:1, effectively invisible, so the moving
   tick appeared to travel through nothing. */
.scroll-cue-rail{position:relative;display:block;width:1px;height:52px;
  background:rgba(255,255,255,.34);overflow:hidden}
.scroll-cue-rail i{
  position:absolute;left:0;top:0;width:1px;height:16px;background:#fff;
  animation:cue 2.8s var(--ease) infinite}
@keyframes cue{0%{transform:translateY(-16px)}60%,100%{transform:translateY(50px)}}
.is-scrolled .scroll-cue{opacity:0;transition:opacity .4s}

/* Media reacts enough to feel alive */
.media img{transition:transform .85s var(--ease)}
.media:hover img{transform:scale(1.045)}
.editorial-case{transition:background .35s var(--ease)}
.editorial-case:hover{background:rgba(20,22,24,.035)}
.on-dark .editorial-case:hover{background:rgba(255,255,255,.035)}
.proof-row,.project-row,.contact-row,.timeline-row,.engagement,.archive-list li{
  transition:background .3s var(--ease)}
.proof-row:hover,.project-row:hover,.contact-row:hover,.timeline-row:hover,
.engagement:hover,.archive-list li:hover{background:rgba(20,22,24,.03)}
.dark .proof-row:hover,.dark .timeline-row:hover,.dark .archive-list li:hover,
.graphite .proof-row:hover,.graphite .timeline-row:hover,.graphite .archive-list li:hover,
.on-dark .proof-row:hover,.on-dark .project-row:hover,.on-dark .contact-row:hover,
.on-dark .timeline-row:hover,.on-dark .engagement:hover,.on-dark .archive-list li:hover{
  background:rgba(255,255,255,.045)}

@media(max-width:767px){
  .scroll-cue{bottom:26px;gap:8px}
  .scroll-cue-rail{height:36px}
  @keyframes cue{0%{transform:translateY(-14px)}60%,100%{transform:translateY(34px)}}
}
@media(max-height:700px){
  .scroll-cue-label{display:none}
}
@media(max-height:600px){
  .scroll-cue{display:none}
}
@media(prefers-reduced-motion:reduce){
  .scroll-cue-rail i{animation:none;height:100%;opacity:.5}
  .media:hover img{transform:none}
}

/* WCAG 2.2 SC 2.5.8 — remaining sub-24px targets in the header */
.brand{display:inline-flex;align-items:center;min-height:26px}
@media(max-width:1023px){.brand{min-height:44px}}
/* Header logo. The source art is full-colour (red marks plus a gradient), so it is
   flattened to solid white rather than shipping a second recoloured file — the header
   sits on the dark hero and on the blurred dark bar, and reads white on both.
   Height is tied to the header so it fills it without crowding the 1px bottom rule. */
.brand-mark{display:block;width:auto;height:clamp(26px,calc(var(--header-h) * .42),34px);
  filter:brightness(0) invert(1)}
/* Desktop only. Unscoped this tied with the <=1023px 44px rule and won on source
   order once the pill styles were generalised beyond .site-header, dropping the
   toggle back to 39px on touch. */
@media(min-width:1024px){.lang button{min-height:32px}}

/* ---------- SELECTED WORK — visual card grid (six-col track, cards span) ----------
   Six columns rather than three, so the grid can express a row of two as well as a row
   of three: a card spanning 2 gives the usual three-up, spanning 3 gives a two-up, and
   the lead still spans the lot. Three columns cannot say "two per row" at all — it would
   need a card 1.5 columns wide — and nesting a second grid inside would break the 2px
   seam alignment that makes these cards read as one block. */
.selected-work-section .card-grid{
  display:grid;grid-template-columns:repeat(6,minmax(0,1fr));gap:2px}
.selected-work-section .visual-card{grid-column:span 2}
.visual-card{
  position:relative;display:block;overflow:hidden;
  background:var(--ink);color:var(--warm);
  height:clamp(300px,26vw,382px)}
.selected-work-section .visual-card-lead{grid-column:1/-1}
.visual-card-lead{height:clamp(380px,42vw,620px)}
/* Half-width: two per row. Taller than the three-up card because at 1.5x the width the
   standard height reads as a letterbox rather than a card. */
.selected-work-section .visual-card-half{grid-column:span 3}
.visual-card-half{height:clamp(340px,32vw,470px)}
.visual-card-half .card-link h3{font-size:clamp(26px,2.6vw,38px)}
.visual-card img{
  display:block;width:100%;height:100%;object-fit:cover;
  transform:scale(1.02);transition:transform 1s var(--ease)}
.visual-card:hover img{transform:scale(1.06)}
/* Live project clips sit on top of the still. The still is the first paint and the
   reduced-motion fallback; the video is an enhancement that has to earn its download. */
.live-loop{position:relative;display:block;width:100%;height:100%;overflow:hidden}
.visual-card>.live-loop,.cap-rec-shot>.live-loop,.archive-visual>.live-loop{position:absolute;inset:0}
.live-loop-video{position:absolute;inset:0;display:block;width:100%;height:100%;object-fit:cover;
  opacity:0;transition:opacity .7s var(--ease);pointer-events:none;background:#0d0f11}
.live-loop.is-playing .live-loop-video{opacity:1}
.visual-card>.live-loop{transform:scale(1.02);transition:transform 1s var(--ease)}
.visual-card:hover>.live-loop{transform:scale(1.06)}
.visual-card>.live-loop img{transform:none}
.visual-card:hover>.live-loop img{transform:none}
.cap-rec-shot>.live-loop .progressive-image__full{transition:none}
.cap-rec:hover .cap-rec-shot>.live-loop{transform:scale(1.04);transition:transform .85s var(--ease)}
@media(max-width:767px){
  .visual-card>.live-loop,.visual-card:hover>.live-loop{transform:none}
}
/* Our stills run from dark venue photos to white product UI, and each card's
   copy block is a different height — so the scrim rides on .card-copy itself
   rather than a fixed percentage of the card. */
.visual-card:after{
  content:"";position:absolute;inset:0;pointer-events:none;
  background:linear-gradient(180deg,rgba(9,10,11,.10),rgba(9,10,11,.24))}
.card-copy{
  position:absolute;z-index:1;inset:auto 0 0;
  padding:clamp(64px,7vw,104px) clamp(22px,2.4vw,40px) clamp(22px,2.4vw,40px);
  /* The scrim carries a brand undertone rather than being neutral black, so the
     resting card already reads as ours. It is deliberately confined to the copy
     area: a veil over the whole image turns complementary colours muddy -- our
     brighter stills lost their blue sky to a red cast -- while down here it only
     ever sits on top of the darkening the text already needed. Kept light enough
     that the hover wash is still a jump rather than a slightly redder version of
     the same thing. */
  background:linear-gradient(180deg,
    rgba(46,4,9,0) 0%,
    rgba(46,4,9,.68) 34%,
    rgba(34,3,7,.95) 64%,
    rgba(24,2,5,.99) 100%)}
.card-eyebrow{
  display:block;color:var(--muted-d);
  font-size:11px;font-weight:700;letter-spacing:.14em;text-transform:uppercase}
.card-series{
  position:absolute;z-index:3;top:0;left:0;
  display:inline-block;margin:0;border-radius:0;
  padding:8px 11px;
  background:var(--brand);color:#fff;
  font-size:10px;font-weight:700;letter-spacing:.14em;text-transform:uppercase;
  line-height:1.2;pointer-events:none}
.card-en-name{
  display:block;margin:8px 0 0;
  font-size:11px;font-weight:700;letter-spacing:.12em;text-transform:uppercase;
  color:rgba(242,241,237,.62);line-height:1.45;max-width:46ch}
html[lang=en] .card-en-name{display:none}
.card-link{display:flex;justify-content:space-between;align-items:flex-end;gap:24px;margin-top:14px}
.card-link h3{
  margin:0;font-weight:700;line-height:1.06;letter-spacing:-.035em;
  font-size:clamp(23px,2.1vw,31px)}
.visual-card-lead .card-link h3{font-size:clamp(34px,3.6vw,54px)}
.card-link p{
  margin:12px 0 0;font-size:14px;line-height:1.7;
  color:rgba(242,241,237,.74);max-width:54ch}
.visual-card-lead .card-link p{font-size:15px;max-width:60ch}
.card-arrow{
  flex:none;font-size:22px;line-height:1;color:var(--muted-d);
  transition:transform .34s var(--ease)}
.visual-card:hover .card-arrow{transform:translate(4px,-4px)}
.visual-card:focus-visible{outline:2px solid var(--brand-soft);outline-offset:3px}

/* Keep the 3-up rhythm on tablet; only collapse to a single column on phones. */
@media(max-width:1023px){
  .visual-card{height:min(46vw,330px)}
  .visual-card-lead{height:min(58vw,470px)}
  .card-copy{padding:clamp(52px,7vw,72px) 20px 20px}
  .card-link{gap:14px;margin-top:10px}
  .card-link h3{font-size:clamp(18px,2.3vw,23px)}
  .visual-card-lead .card-link h3{font-size:clamp(28px,4vw,42px)}
  .card-link p{font-size:13px;line-height:1.6}
  .visual-card-lead .card-link p{font-size:14px}
  /* 11px is the site-wide floor for tracked-out uppercase meta text — the wide
     letter-spacing thins the strokes, so it reads smaller than its nominal size. */
  .card-eyebrow{font-size:11px;letter-spacing:.12em}
  .card-arrow{font-size:18px}
}
@media(max-width:767px){
  .selected-work-section .card-grid{grid-template-columns:1fr;gap:12px}
  .selected-work-section .visual-card,
  .selected-work-section .visual-card-lead,
  .selected-work-section .visual-card-half{grid-column:1/-1}
  .visual-card-half{height:74vw;min-height:300px}
  .visual-card{height:68vw;min-height:290px}
  .visual-card-lead{height:82vw;min-height:340px}
  .card-link{gap:16px}
  .card-link p{font-size:13.5px}
}
@media(prefers-reduced-motion:reduce){
  .visual-card img{transform:none}
  .visual-card:hover img{transform:none}
  .visual-card:hover .card-arrow{transform:none}
}
@media(max-width:767px){
  /* title and the section CTA each get their own line once the CTA is a button */
  .section-head{flex-direction:column;align-items:flex-start;gap:22px}
  .section-head .link{margin-top:0}
}
/* Mobile panel language buttons become visible when the menu opens */
.nav-lang button{min-width:44px;min-height:44px;text-align:left}

/* ---------- 03 / SELECTED WORK — pinned horizontal card rail ----------
   Same pin mechanic as CORE CAPABILITIES, but the payload travels sideways:
   four equal cards (half the wrap width each) slide left until the fourth
   sits where the first started, then the section releases. */
.work-rail{padding-block:0}
.rail-track{position:relative;height:calc(100vh - var(--header-h) + var(--rail-scroll,220vh))}
.rail-sticky{position:sticky;top:var(--header-h);height:calc(100vh - var(--header-h));display:flex;align-items:center;overflow:hidden}
.rail-inner{width:min(100%,var(--wide));display:flex;flex-direction:column;justify-content:center;height:100%;padding-block:clamp(28px,4vh,56px)}
.work-rail .section-head{flex:none;align-items:flex-start}
.work-rail .section-head .link{margin-top:18px}
.work-rail .section-head-support{margin:14px 0 0;max-width:46ch}
/* Starts flush with the section title (no full-bleed breakout, which drifted
   left of the title once the wrap hit its max width) and simply overflows to
   the right — .rail-sticky already clips at the viewport edges, so cards run
   off both sides while scrolling. */
.rail-viewport{flex:1;min-height:0;display:flex;align-items:center;margin-top:clamp(28px,3.5vh,52px);
  overflow:visible}
.rail-strip{display:flex;gap:clamp(16px,1.6vw,28px);will-change:transform;
  transform:translate3d(var(--rail-x,0px),0,0)}
.work-rail .visual-card{flex:none;width:calc((min(100vw,var(--wide)) - var(--gutter)*2)/2);height:min(41vh,413px)}

@media(max-width:1023px){
  .work-rail{padding-block:var(--space)}
  .rail-track{height:auto}
  .rail-sticky{position:static;height:auto;overflow:visible;display:block}
  .rail-inner{height:auto;display:block;padding-block:0}
  .rail-viewport{display:block;width:auto;margin-left:0;padding-inline:0;overflow:visible;margin-top:44px}
  .rail-strip{display:grid;grid-template-columns:1fr;gap:14px;transform:none!important}
  .work-rail .visual-card{width:auto;height:68vw;min-height:290px}
}
@media(prefers-reduced-motion:reduce){
  .work-rail{padding-block:var(--space)}
  .rail-track{height:auto}
  .rail-sticky{position:static;height:auto;overflow:visible;display:block}
  .rail-inner{height:auto;display:block;padding-block:0}
  .rail-viewport{display:block;width:auto;margin-left:0;padding-inline:0;overflow:visible;margin-top:44px}
  .rail-strip{display:grid;grid-template-columns:1fr;gap:14px;transform:none!important}
  .work-rail .visual-card{width:auto;height:68vw;min-height:290px}
}

/* ---------- SUBPAGES — one surface, one text colour ----------
   Heroes keep their dark treatment; every other section drops the alternating
   dark/graphite/light rhythm. Rather than overriding each rule, the palette
   tokens are redefined inside those sections so existing rules resolve to
   light-surface values on their own. */
.subpage main>section:not(.page-hero){
  --ink:#F9F5EF;--graphite:#F9F5EF;--paper:#F9F5EF;--deep:#000;--warm:#000;
  --muted-d:rgba(0,0,0,.62);--muted-l:rgba(0,0,0,.62);
  --line-d:rgba(0,0,0,.14);--line-l:rgba(0,0,0,.14);
  --brand-soft:var(--brand);
  background:#F9F5EF;color:#000}
/* Media wells lose their dark fill so contained artwork sits on the same paper. */
.subpage main>section:not(.page-hero) .media,
.subpage main>section:not(.page-hero) .editorial-case .thumb img{background:#EFE9E0}
/* Photo cards keep their own dark palette — their copy sits on a dark scrim. */
.subpage .visual-card,.subpage .sco-evidence{
  --ink:#090a0b;--warm:#f2f1ed;--muted-d:rgba(242,241,237,.6);
  --line-d:rgba(255,255,255,.14);--brand-soft:#FF3D4E;color:#f2f1ed}
.subpage .visual-card{background:#090a0b}
.subpage .sco-evidence{background:#0b0d0f}

/* ---------- CLOSING BLOCK — sits behind the page and is uncovered on scroll ----------
   The last section plus the footer are fixed at the bottom of the viewport, one
   layer below <main>. As main scrolls up past the viewport bottom the block is
   revealed from the bottom edge upward, like a panel opening behind the page.
   .reveal-spacer adds the scroll distance that reveal needs (height set in JS). */
main{position:relative;z-index:1;background:var(--ink)}
.subpage main{background:#F9F5EF}
.reveal-block{position:fixed;left:0;right:0;bottom:0;z-index:0;
  display:flex;flex-direction:column;justify-content:flex-end;min-height:100svh}
.reveal-spacer{height:100svh}

/* Closing sections: generous air, everything centred, CTA last. */
.closing-block{flex:1;display:flex;align-items:center;
  padding-block:clamp(120px,14vh,220px)!important}
.closing-block>.wrap{width:min(100%,var(--wide))}
.closing-block .bridge{display:flex;flex-direction:column;align-items:center;text-align:center;gap:0}
.closing-block .bridge-copy{display:flex;flex-direction:column;align-items:center;max-width:820px}
.closing-block .section-title{max-width:20ch}
.closing-block .bridge-support{max-width:52ch;margin-top:28px}
.closing-block .bridge-links{align-items:center;margin-top:clamp(48px,6vh,88px)}
/* company / contact close on content sections — centre them the same way. */
.closing-block .current-grid{display:flex;flex-direction:column;align-items:center;text-align:center;gap:0}
.closing-block .current-grid>*{max-width:820px}
.closing-block .current-copy{display:flex;flex-direction:column;align-items:center;margin-top:28px}
.closing-block .placeholder{display:flex;flex-direction:column;align-items:center;text-align:center;max-width:720px;margin-inline:auto}
.closing-block .link{margin-top:clamp(40px,5vh,72px)}

@media(max-width:767px){
  .closing-block{padding-block:clamp(96px,12vh,160px)!important}
}
/* No fixed layering when motion is reduced — the block simply ends the page. */
@media(prefers-reduced-motion:reduce){
  .reveal-block{position:static;min-height:0}
  .reveal-spacer{display:none}
}

/* The closing block lives outside <main>, so it carries its own surface. */
.reveal-block{background:var(--graphite);color:var(--warm)}

/* ---------- SUBPAGE GRID — 12 columns: 3 title / 1 breathing / 8 content ----------
   Applied over the existing markup rather than rewriting each section: the section
   head becomes the left column and everything after it becomes the content column.
   Tablet folds to 8 columns (2 + 6); mobile stacks. */
@media(min-width:1024px){
  .subpage main>section:not(.page-hero)>.wrap{
    display:grid;grid-template-columns:repeat(12,minmax(0,1fr));
    column-gap:24px;row-gap:clamp(28px,3vw,48px);align-items:start}
  .subpage main>section:not(.page-hero)>.wrap>*{grid-column:5/13}
  .subpage main>section:not(.page-hero)>.wrap>.section-head,
  .subpage main>section:not(.page-hero)>.wrap.grid>.evo-label,
  .subpage main>section:not(.page-hero)>.wrap.current-grid>:first-child{grid-column:1/4}
  /* cap-detail carries its own title/media split — remap it to the same rhythm */
  .subpage main>section:not(.page-hero)>.wrap>.cap-detail{grid-column:1/-1;
    display:grid;grid-template-columns:repeat(12,minmax(0,1fr));column-gap:24px;padding-block:0;border-top:0}
  .subpage main>section:not(.page-hero)>.wrap>.cap-detail>:first-child{grid-column:1/4}
  .subpage main>section:not(.page-hero)>.wrap>.cap-detail>*+*{grid-column:5/13}
}
@media(min-width:768px) and (max-width:1023px){
  .subpage main>section:not(.page-hero)>.wrap{
    display:grid;grid-template-columns:repeat(8,minmax(0,1fr));
    column-gap:20px;row-gap:clamp(24px,3vw,40px);align-items:start}
  .subpage main>section:not(.page-hero)>.wrap>*{grid-column:3/9}
  .subpage main>section:not(.page-hero)>.wrap>.section-head,
  .subpage main>section:not(.page-hero)>.wrap.grid>.evo-label,
  .subpage main>section:not(.page-hero)>.wrap.current-grid>:first-child{grid-column:1/3}
  .subpage main>section:not(.page-hero)>.wrap>.cap-detail{grid-column:1/-1;
    display:grid;grid-template-columns:repeat(8,minmax(0,1fr));column-gap:20px;padding-block:0;border-top:0}
  .subpage main>section:not(.page-hero)>.wrap>.cap-detail>:first-child{grid-column:1/3}
  .subpage main>section:not(.page-hero)>.wrap>.cap-detail>*+*{grid-column:3/9}
}
/* The head is a column now, not a full-width rule above the content. */
@media(min-width:768px){
  .subpage main>section:not(.page-hero) .section-head{
    display:block;border-bottom:0;padding-bottom:0;margin-bottom:0}
  .subpage main>section:not(.page-hero) .section-head .section-title{margin-top:14px}
  .subpage main>section:not(.page-hero) .section-head .link{margin-top:26px}
  .subpage main>section:not(.page-hero)>.wrap.current-grid{gap:0 24px}
}
@media(max-width:767px){
  .subpage main>section:not(.page-hero)>.wrap{display:block}
  .subpage main>section:not(.page-hero)>.wrap>*+*{margin-top:36px}
}

/* current-grid carries a more specific legacy 2-column rule — re-map it. */
@media(min-width:1024px){
  .subpage main>section:not(.page-hero)>.wrap.current-grid{
    grid-template-columns:repeat(12,minmax(0,1fr))!important;gap:0 24px!important}
}
@media(min-width:768px) and (max-width:1023px){
  .subpage main>section:not(.page-hero)>.wrap.current-grid{
    grid-template-columns:repeat(8,minmax(0,1fr))!important;gap:0 20px!important}
}

/* The closing block arrives a shade darker and settles to graphite
   as it finishes opening. Driven from scroll in site.js. */
.subpage .reveal-block{position:fixed}
.subpage .reveal-block:after{content:"";position:absolute;inset:0;pointer-events:none;
  background:#0b0d0f;opacity:var(--reveal-dim,0)}
@media(prefers-reduced-motion:reduce){.subpage .reveal-block:after{display:none}}

/* ---------- SUBPAGE SECTION RHYTHM ----------
   These pages had accumulated a per-section padding scale (72–190px) that is far
   tighter than the site rhythm. One value for all of them, so scrolling through a
   subpage has the same generous cadence as the home page. */
.subpage main>section:not(.page-hero){padding-block:var(--space)!important}
.subpage main>section:not(.page-hero)>.wrap>.cap-detail{padding-block:0;border-top:0}

/* Row grids use minmax(0,fr) so a long label can't widen its own column and
   knock that row out of alignment with its siblings — labels wrap instead. */
.phase strong,.timeline-row strong,.project-row h3,.engagement strong{
  overflow-wrap:normal;word-break:normal;hyphens:none}

/* ---------- CROSS-CAPABILITY — integration stated at full type scale ----------
   Each capability is one large line; the fifth is the one that binds them, so it
   inverts into a filled block and carries the project it is currently running in. */
.integration-stack{list-style:none;margin:clamp(44px,5vw,76px) 0 0;padding:0;
  border-top:1px solid var(--line-d);--int-name:clamp(30px,5vw,72px)}
.integration-step{display:flex;align-items:flex-start;gap:clamp(16px,2.2vw,36px);
  padding:clamp(18px,2.2vw,30px) 0;border-bottom:1px solid var(--line-d)}
/* Rows align on the box top; the small labels get an optical nudge so their
   caps line up with the cap of the large name rather than floating above it. */
.integration-index{flex:none;font-size:12px;letter-spacing:.14em;
  font-variant-numeric:tabular-nums;color:var(--muted-d);
  margin-top:calc(var(--int-name) * .22)}
.integration-name{font-size:var(--int-name);font-weight:640;letter-spacing:-.035em;
  line-height:.98;overflow-wrap:normal;
  /* shrink and wrap the text rather than dropping to a new flex line */
  flex:0 1 auto;min-width:0}
.integration-ko{margin-left:auto;flex:none;font-size:12px;letter-spacing:.06em;color:var(--muted-d);
  margin-top:calc(var(--int-name) * .22)}

.integration-final{margin-top:clamp(20px,2.5vw,34px);border-bottom:0;
  background:#0b0d0f;color:#f2f1ed;
  padding:clamp(26px,3vw,42px) clamp(22px,2.6vw,40px)}
.integration-final .integration-index{color:#f2f1ed}
.integration-final .integration-name{color:#f2f1ed}
.integration-note{margin-left:auto;display:flex;flex-direction:column;gap:6px;text-align:right;
  margin-top:calc(var(--int-name) * .22)}
.integration-note strong{font-size:13px;font-weight:650;letter-spacing:.04em;color:#f2f1ed}
.integration-note em{font-style:normal;font-size:11px;font-weight:700;letter-spacing:.12em;
  text-transform:uppercase;color:var(--muted-d)}

/* Wrapping was removed at desktop so the long names stay on their row. At 768-1023 the
   row is only ~525px, which squeezes "INTEGRATION" into 158px against the 238px it needs
   at this size -- an 80px overflow. Allow the row to wrap here too; the description drops
   to the next line and the name keeps its scale. */
@media(min-width:768px) and (max-width:1023px){
  .integration-step{flex-wrap:wrap;gap:8px 18px}
}
@media(max-width:767px){
  .integration-step{flex-wrap:wrap;gap:10px 14px}
  .integration-ko{margin-left:0;width:100%}
  .integration-note{margin-left:0;width:100%;text-align:left;margin-top:14px}
}
/* The Korean gloss is redundant next to the English name — drop it in EN. */
html[lang=en] .integration-ko{display:none}
/* Stacked on phones: labels return to the flow, no nudge needed. */
@media(max-width:767px){.integration-ko{margin-top:0}}

/* ---------- SUBPAGE HERO KEY COPY — two forced lines ----------
   Each line is its own span (not a <br>) because setLang rewrites textContent,
   which would strip a break element on every language toggle. Sized so the
   longest line still holds two lines down to small phones. */
.hero-lines span{display:block}
.subpage .page-hero .hero-lines{
  max-width:none;
  /* 7.2vw is the largest the hero can go while every page still holds two lines in
     both languages — measured at 360px the binding case is the English capabilities
     line, which wraps above 27.5px. Anything larger needs shorter copy or a third line. */
  font-size:clamp(23px,7.2vw,76px);
  line-height:1.05;
  letter-spacing:-.035em;
  text-wrap:balance}
.subpage .page-hero .body-large{margin-top:clamp(26px,3vw,44px)}




/* ---------- PROJECT DETAIL DRAWER ----------
   Desktop: enters from the right. Mobile: rises as a bottom sheet.
   Detail copy is a placeholder — only already-published card copy is echoed. */
.drawer{position:fixed;inset:0;z-index:1200;visibility:hidden;pointer-events:none}
.drawer.is-open{visibility:visible;pointer-events:auto}
.drawer-scrim{position:absolute;inset:0;background:rgba(9,10,11,.62);
  opacity:0;transition:opacity .42s var(--ease)}
.drawer.is-open .drawer-scrim{opacity:1}

.drawer-panel{position:absolute;top:0;right:0;height:100%;width:min(560px,92vw);
  display:flex;flex-direction:column;overflow:hidden;
  background:var(--ink);color:var(--warm);border-left:1px solid var(--line-d);
  transform:translateX(100%);transition:transform .5s var(--ease);
  will-change:transform}
.drawer.is-open .drawer-panel{transform:none}
.drawer-panel:focus{outline:none}

.drawer-close{position:absolute;z-index:2;top:18px;right:18px;
  width:44px;height:44px;display:flex;align-items:center;justify-content:center;
  border:1px solid var(--line-d);border-radius:999px;background:rgba(9,10,11,.55);
  backdrop-filter:blur(8px);color:var(--warm);font-size:15px;cursor:pointer;
  transition:background .3s var(--ease),border-color .3s var(--ease)}
.drawer-close:hover{background:var(--warm);color:var(--ink);border-color:var(--warm)}

.drawer-scroll{overflow-y:auto;overscroll-behavior:contain;height:100%}
.drawer-media{position:relative;height:clamp(220px,32vh,320px);overflow:hidden;background:#0d0f11}
.drawer-media .progressive-image__preview,.drawer-media .progressive-image__full{object-fit:cover}
.drawer-body{padding:clamp(26px,3vw,40px) clamp(22px,2.6vw,36px) clamp(40px,5vh,64px)}
.drawer-status{color:var(--brand-soft);letter-spacing:.14em}
.drawer-series{
  display:inline-block;margin:10px 0 0;border-radius:0;
  padding:7px 10px;
  background:var(--brand);color:#fff;
  font-size:10px;font-weight:700;letter-spacing:.14em;text-transform:uppercase;
  line-height:1.2}
.drawer-en-name{margin:8px 0 0;color:var(--muted-d);letter-spacing:.12em;line-height:1.45}
html[lang=en] .drawer-en-name{display:none}
.drawer-title{margin:14px 0 0;font-size:clamp(26px,2.4vw,36px);font-weight:600;
  letter-spacing:-.03em;line-height:1.2}
.drawer-summary{margin:18px 0 0;color:var(--warm);font-size:16px;line-height:1.7}
.drawer-note{margin:32px 0 0;color:var(--muted-d);letter-spacing:.13em}
.drawer-desc{margin:10px 0 0;color:var(--muted-d);font-size:15px;line-height:1.75}
.drawer-tags{display:flex;flex-wrap:wrap;gap:8px;margin-top:26px}
.drawer-tags span{padding:8px 13px;border:1px solid var(--line-d);border-radius:999px;
  font-size:10px;letter-spacing:.09em;text-transform:uppercase;color:var(--muted-d)}

@media(max-width:767px){
  .drawer-panel{top:auto;bottom:0;left:0;right:0;width:auto;
    height:min(88svh,760px);border-left:0;border-top:1px solid var(--line-d);
    border-radius:20px 20px 0 0;transform:translateY(100%)}
  .drawer-panel:before{content:"";position:absolute;z-index:2;top:10px;left:50%;
    transform:translateX(-50%);width:44px;height:4px;border-radius:999px;
    background:rgba(242,241,237,.28)}
  .drawer-close{top:22px;right:14px}
  .drawer-media{height:clamp(180px,26vh,240px)}
}
@media(prefers-reduced-motion:reduce){
  .drawer-scrim,.drawer-panel{transition:none}
}
body.drawer-open{overflow:hidden}

/* Trailing panel after the last rail card — reads once the rail has run out. */
.rail-outro{flex:none;display:flex;align-items:center;
  width:calc((min(100vw,var(--wide)) - var(--gutter)*2)/2);
  height:min(41vh,413px);padding-right:var(--gutter);
  font-size:clamp(22px,2.4vw,38px);font-weight:600;letter-spacing:-.025em;
  line-height:1.35;color:rgba(20,22,24,.16)}
@media(max-width:1023px){
  /* Stacked, the horizontal "trailing" idea cannot survive — so re-role it into a
     self-standing closing line under the last card rather than shrinking it into a
     caption. Bigger and less faint, because at this size .16 alpha reads as a defect. */
  .rail-outro{width:auto;height:auto;padding:clamp(30px,7vw,46px) 0 0;
    font-size:clamp(27px,7vw,36px);line-height:1.3;color:rgba(20,22,24,.34)}
}

/* ---------- MOBILE SAFETY STATES ----------
   Placed last on purpose: both rules must win over .subpage .reveal-block{position:fixed}. */

/* Height guard (see js/site.js). The reveal block is fixed and bottom-anchored, so
   content taller than the viewport escapes off the TOP and can never be scrolled to.
   When JS measures that overflow it drops the block into normal flow — the same
   fallback prefers-reduced-motion already uses. */
body.reveal-static .reveal-block{position:static;min-height:0}
body.reveal-static .reveal-spacer{display:none}
body.reveal-static .reveal-block:after{display:none}

/* iOS-safe scroll lock. Safari on iOS ignores body{overflow:hidden}, so the page keeps
   rubber-banding behind an open menu or drawer. JS pins the body at its scroll offset
   via inline top and restores it on release. */
body.is-scroll-locked{position:fixed;left:0;right:0;width:100%;overflow:hidden}

/* Collapsed-grid double rule. At >=768px the section head is a grid COLUMN beside its
   content, so the head's bottom rule and the content block's top rule read as one
   aligned baseline. Below 768px the grid stacks and the two land on top of each other
   with only a gap between them — two rules with nothing in between. The head rule
   already closes the header, so the block below it drops its own opening rule.
   Covers .phases, .timeline, .proof-list, .fact-line, .integration-stack,
   .contact-list, .contact-reserved and the first .engagement row. */
/* Written as `.wrap > .section-head + *` rather than `.section-head + *` on purpose.
   At one class it ties with any later single-class rule that sets a border-top, and
   source order decides -- which is exactly how .engagement-list silently reintroduced
   the doubled rule after this fix was already in. Two classes wins regardless of order. */
@media(max-width:767px){
  .wrap>.section-head+*{border-top:0}
}
/* The home page has no 12-column grid, so its section head is a full-width band above
   its content at EVERY width -- the doubled rule there was never only a mobile problem.
   Scoping the fix to <=767px hid it on phones and left it on desktop. */
.home-page .wrap>.section-head+*{border-top:0}


/* ---------- 01 / ONGOING PROJECT — SCO Noah's Ark ----------
   Six levels of hierarchy on the subpage 12-column grid:
   identity (cols 1-4) | lead → body → evidence → boundary → keywords (cols 5-13).
   The evidence pair sits directly under the paragraph that names the two documents.
   The boundary note is demoted by space, size and a vertical footnote hairline — never
   by a horizontal rule, which would stack against the framed evidence boxes above it —
   because it qualifies the claim rather than continuing it, so it must not read as
   another body paragraph. */
/* Title scale is set by the RAIL, not the viewport. The rail runs ~218px (1024) to
   ~313px (1440); the page-level .work-page .work-current h2 display scale (60-88px)
   rags "SCO 노아의 방주 프로젝트" to four lines of 120/149/100/199px in it. This scale is
   tuned so the Korean title breaks once — "SCO 노아의" / "방주 프로젝트" — at every width
   from 768 up. Needs .work-page to outrank that page-level rule. */
.sco-head h2{margin:16px 0 0;line-height:1.07;letter-spacing:-.035em;text-wrap:balance}
/* Optical top alignment. The rail and the narrative start on the same grid line, but the
   eyebrow (12px/1.3) and the lead (clamp(19-25px)/1.6) then set their first baselines
   16px apart at 1440 — the most exposed alignment point in the section, since the two
   labels sit side by side at the top. Drop the rail so the BASELINES meet, not the boxes.
   Measured deltas: 9.5px @1024, 13px @1280, 16px @1440 — linear in vw, hence the calc.
   Excluded below 768, where the two blocks stack and there is nothing to align to. */
@media(min-width:768px){
  .sco-head{margin-top:clamp(9.5px,calc(1.5625vw - 6.5px),16.6px)}
}
/* The rail title is a section title like every other, so it takes --h2 rather than a
   scale of its own. It wraps to three lines in the narrow rail; that is accepted.
   Only the hard floor is held back: the 768px rail is 162px wide and a four-syllable
   word at 40px very nearly fills it, so below 900px the title steps down enough to
   keep clear of the edge. */
@media(min-width:768px) and (max-width:899px){
  .work-page .work-current .sco-head h2,
  .sco-head h2{font-size:34px}
}
.sco-status-line{display:flex;align-items:center;gap:9px;margin:22px 0 0;
  font-size:11px;font-weight:700;letter-spacing:.13em;color:var(--brand-soft)}
/* On cream, --brand-soft resolves to --brand (#D70B1C), which measures 4.89:1 against
   #F9F5EF and passes AA on its own -- the darker one-off the blue needed here is gone. */
.subpage .sco-status-line{color:var(--brand)}
.sco-status-dot{flex:none;width:7px;height:7px;border-radius:50%;background:var(--brand);
  box-shadow:0 0 0 4px rgba(215,11,28,.16)}
.sco-classification{display:flex;flex-direction:column;gap:4px;margin:14px 0 0}

.sco-lead{margin:0;color:var(--warm)}
.sco-body{margin-top:clamp(26px,2.6vw,38px);display:flex;flex-direction:column;gap:18px}
.sco-body .copy{margin:0;color:var(--muted-d);max-width:var(--sco-measure)}

/* Evidence pair. Equal columns, and the box is sized by aspect-ratio rather than a
   shared row height: both sources are 16:9, and the old 7fr/5fr split with one row
   height cropped them to 0.92 (1440), 0.84 (1024) and 0.85/0.85 (768) — i.e. the
   secondary was rendered PORTRAIT from a landscape photograph, discarding half the
   frame. 16/11 trims 16:9 to ~91% of its width: a deliberate, equal, mild crop.
   Equal weight also matches the copy, which names the two records as a pair.
   Gap tracks the 24px subpage grid gutter so the pair sits on the page grid. */
.sco-evidence-duo{display:grid;grid-template-columns:minmax(0,1fr) minmax(0,1fr);
  gap:clamp(12px,1.6vw,24px);margin-top:clamp(36px,3.6vw,56px)}
.sco-evidence-duo .sco-evidence{height:auto;aspect-ratio:16/11}

/* Ascending spacing tiers. Previously the four joints in the narrative measured
   26/34/30/34 (mobile) and 38/48/43/49 (1440) — effectively equal, so five blocks read
   as a flat list and the boundary note read as another body paragraph. The cadence now
   widens as the register changes: prose -> evidence -> qualifier -> closing metadata. */
.sco-boundary{margin-top:clamp(44px,4.4vw,66px)}
/* Demoted, not dimmed. The old opacity:.9 never applied — .sco-disclaimer is also a
   .reveal, and .reveal.visible{opacity:1} outranks it (0-2-0 over 0-1-0), so this note
   rendered at exactly the body colour. Rather than fight that (and spend contrast), the
   demotion is carried by size, leading and a footnote hairline. The hairline is derived
   from currentColor, never a literal rgba, so it survives the cream-surface remap; it is
   vertical, so it adds no horizontal rule beneath the framed evidence boxes. */
.sco-disclaimer{margin:0;max-width:var(--sco-measure);font-size:14px;line-height:1.78;
  color:var(--muted-d);padding-left:clamp(16px,1.6vw,22px);
  border-left:1px solid;border-left-color:color-mix(in srgb,currentColor 24%,transparent)}

.sco-keywords{margin-top:clamp(56px,5.6vw,88px)}
.sco-keywords .tag-list{margin-top:16px;list-style:none;padding:0}

@media(max-width:767px){
  /* Stack the pair; the 16/11 box carries over so both figures keep the same crop and
     the same shape, instead of the old 62vw/52vw pair of mismatched heights. */
  /* Stacked, the two framed boxes sat 14px apart and read as two rules with nothing
     between them. Give them room so each reads as its own plate. */
  .sco-evidence-duo{grid-template-columns:1fr;gap:34px;margin-top:36px}
  .sco-head h2{margin-top:14px}
  .sco-body{gap:16px}
  .sco-boundary{margin-top:40px}
  .sco-keywords{margin-top:52px}
  /* Stacked, the identity block must separate from the narrative by more than the
     22-26px used INSIDE it, or the two groups merge. The generic subpage stack rule
     only gives 36px and outranks the old .work-current .sco-narrative margin. */
  .subpage main>section#ongoing-project>.wrap>.sco-narrative{margin-top:56px}
}

/* NOT sticky. The rail sits beside a much taller narrative and pinning it would be the
   obvious move, but an earlier overflow:hidden on .current (for the column overlay)
   made the section a sticky scroll container, so position:sticky degraded to a plain
   downward offset by `top` (measured: the head dropped 288px -> 404px). Overflow is
   visible again now that the overlay no longer needs clipping, and the rail stays in
   flow so the old sticky bug cannot return. */

/* ---------- PROJECT CARD / DRAWER DETAIL ---------- */
/* Cityfield has no project image in the archive, so the card carries a labelled
   placeholder rather than an unrelated stand-in photograph. */
.card-media-empty{position:absolute;inset:0;display:flex;align-items:center;justify-content:center;
  background:#101315;
  background-image:linear-gradient(rgba(255,255,255,.045) 1px,transparent 1px),
                   linear-gradient(90deg,rgba(255,255,255,.045) 1px,transparent 1px);
  background-size:34px 34px}
.card-media-empty span{font-size:11px;letter-spacing:.16em;text-transform:uppercase;
  color:rgba(242,241,237,.34)}

.drawer-class{margin:10px 0 0;color:var(--muted-d);letter-spacing:.13em}
.drawer-desc{margin-top:22px;display:flex;flex-direction:column;gap:15px}
.drawer-desc p{margin:0;color:var(--muted-d);font-size:15px;line-height:1.75}
.drawer-extra:not(:empty){position:relative;height:clamp(180px,26vh,240px);margin-top:26px;
  overflow:hidden;background:#0d0f11;border:1px solid var(--line-d)}
.drawer-extra .progressive-image__preview,.drawer-extra .progressive-image__full{object-fit:cover}

/* Spatial-record drawers (Hongdae, Hyundai): taller hero, stacked extras. */
.drawer.is-spatial .drawer-media{height:min(48vh,440px)}
.drawer-shot,.drawer-still{position:relative;margin:0;height:100%;overflow:hidden;background:#0d0f11}
.drawer-shot video{display:block;width:100%;height:100%;object-fit:cover;background:#0d0f11}
.drawer-still .progressive-image{position:absolute;inset:0}
.drawer-play{position:absolute;z-index:2;left:50%;top:50%;transform:translate(-50%,-50%);
  display:grid;place-items:center;width:56px;height:56px;padding:0;border-radius:999px;
  border:1px solid rgba(255,255,255,.4);background:rgba(9,10,11,.58);color:#fff;cursor:pointer;
  backdrop-filter:blur(8px);transition:background .3s var(--ease),border-color .3s var(--ease),opacity .3s var(--ease)}
.drawer-play svg{width:22px;height:22px;display:block}
.drawer-play:not(.is-playing) svg{margin-left:2px}
.drawer-play:hover,.drawer-play:focus-visible{background:var(--brand);border-color:var(--brand)}
.drawer-play.is-auto{opacity:0}
.drawer-shot:hover .drawer-play.is-auto,.drawer-shot:focus-within .drawer-play.is-auto{opacity:1}
.drawer-progress{position:absolute;z-index:3;left:0;right:0;bottom:0;height:14px;cursor:pointer}
.drawer-progress:before{content:"";position:absolute;left:0;right:0;bottom:0;height:2px;
  background:rgba(255,255,255,.22)}
.drawer-progress-fill{position:absolute;left:0;bottom:0;height:2px;width:100%;
  transform:scaleX(0);transform-origin:left center;background:#fff;pointer-events:none}
.drawer-shot figcaption,.drawer-still figcaption{position:absolute;z-index:2;left:12px;bottom:10px;
  padding:7px 9px;max-width:calc(100% - 24px);
  background:rgba(9,10,11,.78);color:rgba(242,241,237,.8);
  font-size:9px;letter-spacing:.12em;text-transform:uppercase;backdrop-filter:blur(8px)}
.drawer-shot figcaption{bottom:16px}
.drawer-extra.drawer-extra-stack{height:auto;overflow:visible;border:0;background:transparent;
  display:flex;flex-direction:column;gap:18px}
.drawer-extra.drawer-extra-stack .drawer-still,
.drawer-extra.drawer-extra-stack .drawer-shot{
  height:clamp(180px,26vh,240px);border:1px solid var(--line-d)}
@media(max-width:767px){
  .drawer.is-spatial .drawer-media{height:min(36vh,280px)}
  .drawer-play.is-auto{opacity:1}
}
@media(prefers-reduced-motion:reduce){
  .drawer-play.is-auto{opacity:1}
}
/* Scope limits read as a qualification, not as another body paragraph. */
.drawer-boundary:not(:empty){margin-top:26px}
.drawer-limit{margin:0 0 12px;font-size:13px;line-height:1.7;color:rgba(242,241,237,.44)}
.drawer-limit:last-child{margin-bottom:0}
.drawer-tags span{white-space:nowrap}

/* Deep link from HOME 05 lands on the section, not under the fixed header. Applies to
   the load-time jump and to in-page anchor scrolling alike. */
main>section[id]{scroll-margin-top:calc(var(--header-h) + clamp(16px,2vh,32px))}
/* The anchor lands on the content row, with a little air above it. The id itself keeps
   its own margin as the no-JS fallback. */
main>section[id]>.wrap{scroll-margin-top:calc(var(--header-h) + clamp(28px,4vh,56px))}

/* ---------- CTA HOVER — colour wipes in like a progress fill ----------
   The fill is a pseudo-element scaled on X from the left edge, not a background
   transition, so it reads as filling rather than as switching. Deliberately NOT
   overflow:hidden — .link::after is the invisible 44px touch-target expander below
   1023px, and clipping the element would cut that hit area back down. inset:0 keeps
   the fill inside the border box on its own. The label flips colour slightly late so
   it changes as the fill passes under it rather than before it arrives. */
/* z-index:0 makes the button its own stacking context so the sweep below can sit at
   -1: behind the label but still in front of the button's own background. Raising the
   children instead only ever lifted the arrow -- it is a <span>, while the label is a
   bare text node with no box to give a z-index to, so it stayed under the fill and
   vanished for the length of every hover and every tap. */
.link{position:relative;z-index:0;
  transition:color .34s var(--ease) .08s,border-color .34s var(--ease)}
.link>*{position:relative;z-index:1}
.link::before{content:"";position:absolute;z-index:-1;inset:0;
  transform:scaleX(0);transform-origin:left center;
  transition:transform .52s var(--ease);
  background:var(--deep)}
.dark .link::before,.graphite .link::before,
.home-hero .link::before,.page-hero .link::before{background:var(--warm)}
.link:hover::before,.link:focus-visible::before{transform:scaleX(1)}

/* Same idea on the drawer's close button; it is a circle, so this one does clip. */
.drawer-close{position:absolute;overflow:hidden}
.drawer-close>*{position:relative;z-index:1}
.drawer-close::before{content:"";position:absolute;z-index:0;inset:0;background:var(--warm);
  transform:scaleX(0);transform-origin:left center;transition:transform .42s var(--ease)}
.drawer-close:hover{background:rgba(9,10,11,.55);border-color:var(--warm)}
.drawer-close:hover::before{transform:scaleX(1)}
.drawer-close:hover{color:var(--ink)}

/* ---------- LANGUAGE TOGGLE — the pill slides between KR and EN ----------
   A real element rather than a pseudo, so its travel can be measured and so it can
   size itself to whichever button is active instead of assuming the two are equal.
   JS positions it; the gap is removed so the halves tile. */
/* Any .lang, not just the header one: the menu panel now carries the same control,
   and without position:relative here its pill anchored to the fixed panel instead --
   rendering as a full-height white band down the middle of the menu. */
.lang{position:relative;gap:0}
.lang-pill{position:absolute;z-index:0;left:0;top:3px;bottom:3px;
  border-radius:999px;background:rgba(255,255,255,.92);
  transition:transform .42s var(--ease),width .42s var(--ease)}
.lang button{position:relative;z-index:1}
.lang button.active{background:transparent;color:#08080a}
@media(max-width:1023px){.lang-pill{top:2px;bottom:2px}}

/* HOME 05 flagship banner is the one title deliberately above --h2: it is the single
   full-bleed band on the page and carries the current-scale statement. */
.home-page .sco-hero-title{font-size:clamp(66px,6.7vw,104px)}
@media(max-width:767px){.home-page .sco-hero-title{font-size:52px}}

/* ---------- 05 / PARTNERSHIPS — a register, four fields ----------
   The old row was `140px 1fr 220px` carrying a single short name, leaving ~430px of
   dead space per row and making the two ends read as unrelated. The gap is now the
   scope field, transcribed from EVIDENCE_LEDGER_v2.1 — the answer to "what", which
   was the only one of when/who/what/how-far the row did not answer.
   The year is a cohort header rather than a repeated cell: it read "2022" six times
   in the column the eye lands on first. */
.engagement-list{border-top:1px solid var(--line-l)}
.engagement-year{padding:clamp(30px,3vw,44px) 0 10px;
  font-size:11px;font-weight:700;letter-spacing:.14em;color:var(--muted-l)}
.engagement-year:first-child{padding-top:clamp(18px,1.8vw,26px)}
.engagement{grid-template-columns:46px minmax(0,5fr) minmax(0,6fr) auto;
  gap:clamp(16px,1.8vw,28px);align-items:baseline;padding:clamp(15px,1.6vw,21px) 0}
.engagement-month{font-size:13px;color:var(--muted-l);font-variant-numeric:tabular-nums}
.engagement strong{font-size:clamp(17px,1.35vw,21px);font-weight:600;letter-spacing:-.015em;line-height:1.3}
.engagement-scope{color:var(--muted-l);font-size:15px;line-height:1.45}
.engagement .status{justify-self:end;text-align:right;color:var(--muted-l);white-space:nowrap}

@media(max-width:1023px){
  /* Scope wraps under the name rather than squeezing four columns into a tablet. */
  .engagement{grid-template-columns:46px minmax(0,1fr) auto;gap:4px 18px}
  .engagement-scope{grid-column:2;grid-row:2}
  .engagement .status{grid-row:1}
}
@media(max-width:767px){
  .engagement{grid-template-columns:40px minmax(0,1fr);gap:5px 12px;padding:16px 0}
  .engagement strong{font-size:17px}
  .engagement-scope{grid-column:2;grid-row:2;font-size:14px}
  .engagement .status{grid-column:2;grid-row:3;justify-self:start;text-align:left;white-space:normal}
  .engagement-year{padding:26px 0 8px}
}

/* ---------- CAPABILITIES — the left column had 164-307px of nothing ----------
   Four elements (eyebrow, title, one sentence, chips) sat against a 480-690px image.
   The practice list is what fills it: how the work is approached, which is the bridge
   between "what field is this" and "have they actually done it". */
.cap-practice{margin-top:clamp(30px,3vw,44px)}
.cap-practice ul{list-style:none;margin:14px 0 0;padding:0;display:flex;flex-direction:column;gap:16px}
.cap-practice strong{display:block;font-size:15px;font-weight:650;letter-spacing:-.01em}
.cap-practice span{display:block;margin-top:4px;font-size:14px;line-height:1.6;color:var(--muted-d)}
.on-light .cap-practice span{color:var(--muted-l)}

/* Evidence was a run-on sentence naming projects with nowhere to go. Same shape in all
   three bands now, and the named projects that exist as cards are links. */
.cap-evidence{margin-top:clamp(20px,2vw,28px)}
.cap-evidence ul{list-style:none;margin:12px 0 0;padding:0;
  display:flex;flex-wrap:wrap;gap:8px 10px}
.cap-evidence li{display:flex;align-items:center}
.cap-evidence li+li:before{content:"·";margin-right:10px;opacity:.45}
.cap-evidence a,.cap-evidence span{font-size:14px;color:var(--muted-d)}
.on-light .cap-evidence a,.on-light .cap-evidence span{color:var(--muted-l)}
.cap-evidence a{border-bottom:1px solid currentColor;padding-bottom:1px;
  transition:color .28s var(--ease),border-color .28s var(--ease)}
.cap-evidence a:hover{color:var(--warm)}
.on-light .cap-evidence a:hover{color:var(--ink)}


@media(min-width:768px) and (max-width:1023px){
  /* The left column is only 2 of 8 here; with the practice list it would be squeezed,
     so the two halves stack instead of competing for a narrow rail. */
  .subpage main>section:not(.page-hero)>.wrap>.cap-detail{display:block}
  .cap-detail>div+div{margin-top:clamp(34px,4vw,52px)}
}
/* These links are new, so they were outside the existing hit-area rule. They sit inline
   in a wrapping list where an invisible ::after expander would overlap the row above, so
   the box itself grows instead. */
@media(max-width:1023px){
  .cap-evidence ul{gap:2px 10px}
  .cap-evidence a,.cap-evidence span{display:inline-flex;align-items:center;min-height:44px}
}

/* ---------- SECTION ROUTES ----------
   company.html and contact.html had zero links in <main>: they named capabilities and
   projects and gave the reader nowhere to go, the same dead end capabilities.html had.
   One shared block so every section that names something can point at it. */
/* No rule of its own. Every list this follows already ends on a bottom border, so a
   top rule here stacks against it 40px away -- the doubled line this pass exists to
   remove. The label plus the space carries the separation. */
.section-relate{margin-top:clamp(34px,3.4vw,52px)}
.section-relate ul{list-style:none;margin:12px 0 0;padding:0;display:flex;flex-wrap:wrap;gap:8px 10px}
.section-relate li{display:flex;align-items:center}
.section-relate li+li:before{content:"·";margin-right:10px;opacity:.45}
.section-relate a{font-size:14px;color:var(--muted-l);
  border-bottom:1px solid currentColor;padding-bottom:1px;
  transition:color .28s var(--ease)}
.on-dark .section-relate a,.dark .section-relate a,.graphite .section-relate a{color:var(--muted-d)}
.section-relate a:hover{color:var(--ink)}
.on-dark .section-relate a:hover,.dark .section-relate a:hover,.graphite .section-relate a:hover{color:var(--warm)}

/* 05 / COMPANY FACTS held 149px of content under a 172px heading, and one of its three
   facts was "SYSTEMS + ENVIRONMENTS", which states nothing. Reuses the proof-row shape
   already carrying the validation list directly above it. */
.company-summary{border-top:1px solid var(--line-d)}
.company-summary .proof-row{grid-template-columns:minmax(140px,2fr) minmax(0,8fr);padding:22px 0 22px 0}
.company-summary .proof-row p{margin:0;color:var(--muted-d);font-size:16px;line-height:1.6}

@media(max-width:1023px){
  /* Same reason as the capability evidence links: new inline links in a wrapping list,
     where an invisible expander would overlap the row above. */
  .section-relate ul{gap:2px 10px}
  .section-relate a{display:inline-flex;align-items:center;min-height:44px}
}
@media(max-width:767px){
  .company-summary .proof-row{grid-template-columns:1fr;gap:6px}
  .company-summary .proof-row p{font-size:15px}
}

/* ---------- HERO SCROLL CUE — must not collide with the CTA ----------
   The cue is centred while the hero CTA is left-aligned, so on a phone the button's
   right edge ran into the cue's column: measured 20-205 against 187-243 horizontally
   and touching exactly at y=774. Reserve the cue's own height in the hero's bottom
   padding so the button always clears it, rather than nudging one of them sideways. */
@media(max-width:767px){
  .home-hero-inner{padding-bottom:clamp(118px,15vh,158px)}
}
/* Below the height where the cue itself is hidden, that reserve is dead space. */
@media(max-width:767px) and (max-height:600px){
  .home-hero-inner{padding-bottom:clamp(64px,9vh,88px)}
}

/* ---------- FOOTER — one block on every page ----------
   Was a per-page line ("REBORN SOFT · CONTACT", "· CAPABILITIES" …) with no copyright.
   Two tiers now: identity and navigation up top, the legal line and the CTA underneath,
   separated by a hairline. Identical markup on all five pages. */
.footer{padding:clamp(48px,5vw,72px) 0 clamp(34px,3.4vw,44px)}
.footer-inner{display:block}
.footer-top{display:flex;justify-content:space-between;align-items:flex-start;gap:28px 40px;flex-wrap:wrap}

.footer-brand{display:flex;align-items:center;gap:14px;min-width:0}
.footer-mark{flex:none;display:block;width:auto;height:26px;
  /* Greyscale, flattened to white on the dark closing surface. */
  filter:grayscale(1) brightness(0) invert(1);opacity:.6}
.footer-descriptor{margin:0;font-size:12px;letter-spacing:.06em;line-height:1.4;opacity:.8}

.footer-nav{display:flex;flex-wrap:wrap;gap:10px 28px}
.footer-nav a{position:relative;font-size:13px;font-weight:600;letter-spacing:.02em;
  transition:opacity .28s var(--ease)}
.footer-nav a:hover{opacity:.62}

/* The hairline sits between two rows of content, so it never stacks against another. */
.footer-base{display:flex;justify-content:space-between;align-items:center;gap:14px 28px;flex-wrap:wrap;
  margin-top:clamp(34px,3.4vw,48px);padding-top:clamp(20px,2vw,26px);
  border-top:1px solid var(--line-d)}
.footer-copy{margin:0;font-size:12px;letter-spacing:.01em;line-height:1.5;opacity:.85}
.footer-totop{border:0;background:none;padding:0;cursor:pointer;
  font-size:12px;font-weight:650;letter-spacing:.08em;white-space:nowrap;
  transition:opacity .28s var(--ease)}
.footer-totop:hover{opacity:.62}

@media(max-width:767px){
  .footer-top{flex-direction:column;gap:26px}
  .footer-nav{gap:12px 24px}
  .footer-base{flex-direction:column;align-items:flex-start;gap:16px}
  .footer-copy{font-size:11px}
  .footer-mark{height:24px}
}
/* Footer links are new interactive targets, so they take the touch-size treatment the
   rest of the site uses rather than inheriting text-height hit areas. */
@media(max-width:1023px){
  .footer-nav a{display:inline-flex;align-items:center;min-height:44px}
}
@media(max-width:1023px){.footer-totop{display:inline-flex;align-items:center;min-height:44px}}

/* ---------- MOBILE MENU ----------
   Was a flat list of four words with no state: the trigger still said MENU while open,
   nothing marked the current page, focus stayed on <body>, and the rows were 41px.
   Rebuilt as a numbered index in the same technical language the rest of the site uses. */
.nav-panel{position:fixed;inset:0;z-index:999;display:flex;flex-direction:column;
  justify-content:space-between;
  /* No gap: space-between already separates the two blocks, and a fixed gap on top of it
     added 32px to the content height -- enough to overflow a 1280x720 laptop. */
  padding:calc(var(--header-h) + clamp(28px,5vh,56px)) var(--gutter) clamp(32px,5vh,48px);
  background:var(--ink);
  opacity:0;visibility:hidden;transform:translateY(-10px);
  /* visibility is switched at 0s, not interpolated: on open it must be visible in the
     same frame or focus() lands on a hidden element and is dropped, which left keyboard
     users on <body>. On close it waits out the fade before hiding. */
  transition:opacity .38s var(--ease),transform .38s var(--ease),visibility 0s .38s}
.nav-panel.open{opacity:1;visibility:visible;transform:none;
  transition:opacity .38s var(--ease),transform .38s var(--ease),visibility 0s}

.nav-menu{display:flex;flex-direction:column;border-top:1px solid var(--line-d)}
.nav-item{position:relative;display:grid;
  grid-template-columns:34px minmax(0,1fr) auto;align-items:baseline;gap:0 14px;
  padding:clamp(15px,2.2vh,22px) 0;border-bottom:1px solid var(--line-d);
  /* Each row arrives a beat after the one above; the stagger is set inline per item. */
  opacity:0;transform:translateY(10px);
  transition:opacity .42s var(--ease),transform .42s var(--ease);
  transition-delay:calc(var(--i,0) * 55ms + 90ms)}
.nav-panel.open .nav-item{opacity:1;transform:none}
.nav-num{font-size:11px;letter-spacing:.13em;color:var(--muted-d);font-variant-numeric:tabular-nums}
.nav-name{font-size:clamp(28px,7.4vw,38px);font-weight:600;letter-spacing:-.03em;line-height:1.15}
.nav-en{font-size:10px;letter-spacing:.14em;color:var(--muted-d);white-space:nowrap}
/* Which page you are already on — the desktop nav says so, the mobile menu did not. */
.nav-item.active .nav-num,.nav-item.active .nav-en{color:var(--brand-soft)}
/* Marked the way the desktop nav marks it -- an underline on the label -- rather than
   a bar pinned to the screen edge, which read as a stray fragment a full gutter away
   from the row it belonged to. */
.nav-item.active .nav-name{position:relative;color:#fff}
.nav-item.active .nav-name:after{content:"";position:absolute;left:0;right:0;bottom:-6px;
  height:2px;background:var(--brand-soft)}

.nav-foot{display:flex;align-items:center;justify-content:space-between;gap:18px;flex-wrap:wrap;
  opacity:0;transition:opacity .4s var(--ease) .3s}
.nav-panel.open .nav-foot{opacity:1}
.nav-descriptor{margin:0;font-size:11px;letter-spacing:.06em;color:var(--muted-d);max-width:22ch}

/* Trigger states: one label per state, swapped by the open class rather than in JS. */
body.menu-open .site-header{background:var(--ink);border-color:var(--line-d)}

@media(prefers-reduced-motion:reduce){
  .nav-item,.nav-foot{opacity:1;transform:none;transition:none}
}

/* ---------- MENU TRIGGER — hamburger that folds into an X ----------
   Two stages, so the bars visibly gather before they turn: the outer span carries the
   translate, the inner <i> carries the rotate, and each gets the other's duration as a
   delay. Opening runs gather → rotate; closing runs rotate → spread, so the motion
   reverses rather than replaying. One transform per element keeps it compositor-only. */
.menu-trigger{display:block;border:0;background:none;padding:0;cursor:pointer;color:#fff;
  width:44px;height:44px;display:grid;place-items:center}
.mt-bars{position:relative;display:block;width:24px;height:16px}
.mt-bar{position:absolute;left:0;width:24px;height:2px;display:block;
  transition:transform .2s var(--ease) .2s}      /* close: spread happens last */
.mt-bar i{display:block;width:100%;height:100%;background:currentColor;border-radius:1px;
  transition:transform .2s var(--ease),opacity .16s linear}
.mt-bar:nth-child(1){top:0}
.mt-bar:nth-child(2){top:7px}
.mt-bar:nth-child(3){top:14px}

body.menu-open .mt-bar{transition-delay:0s}      /* open: gather happens first */
body.menu-open .mt-bar i{transition-delay:.2s}
body.menu-open .mt-bar:nth-child(1){transform:translateY(7px)}
body.menu-open .mt-bar:nth-child(3){transform:translateY(-7px)}
body.menu-open .mt-bar:nth-child(1) i{transform:rotate(45deg)}
body.menu-open .mt-bar:nth-child(3) i{transform:rotate(-45deg)}
body.menu-open .mt-bar:nth-child(2) i{opacity:0}
.menu-trigger:focus-visible{outline:2px solid var(--brand-soft);outline-offset:4px}

/* Nothing competes with the menu while it is open: the mark and the header language
   toggle step aside, and the panel carries its own toggle at the foot. */
.brand,.site-header .lang{transition:opacity .28s var(--ease),visibility .28s}
body.menu-open .brand,body.menu-open .site-header .lang{opacity:0;visibility:hidden;pointer-events:none}

/* ---------- THE MENU ITSELF — now the navigation at every width ---------- */
.nav-panel{padding-top:calc(var(--header-h) + clamp(24px,6vh,80px));
  /* Last-resort safety: if a viewport is short enough that even the height-aware
     scale cannot fit four rows, the menu scrolls rather than losing the foot. */
  overflow-y:auto;overscroll-behavior:contain}
.nav-menu{border-top:0}
.nav-item{grid-template-columns:auto minmax(0,1fr) auto;gap:0 clamp(14px,2vw,32px);
  padding:clamp(15px,2.4vh,26px) 0;align-items:center}
/* Sized against BOTH axes. On width alone the four rows filled a 1440x900 viewport to
   within 2px, so any shorter laptop would have pushed the foot off-screen. */
.nav-name{font-size:clamp(34px,min(9vw,12.5vh),124px);font-weight:640;letter-spacing:-.04em;line-height:1.02}
.nav-num{font-size:clamp(11px,1vw,13px)}
.nav-en{font-size:clamp(10px,.85vw,12px)}

/* Hover / press: the name lifts and slides, its rule wipes in from the left in the same
   direction as every other fill on the site, and the row's companions brighten. */
@media(hover:hover){
  .nav-item .nav-name{transition:transform .42s var(--ease),color .3s var(--ease)}
  .nav-item .nav-num,.nav-item .nav-en{transition:color .3s var(--ease),transform .42s var(--ease)}
  .nav-item:hover .nav-name{transform:translateX(clamp(8px,1.2vw,22px));color:#fff}
  .nav-item:hover .nav-num{color:var(--brand-soft)}
  .nav-item:hover .nav-en{color:var(--warm);transform:translateX(calc(clamp(8px,1.2vw,22px) * -1))}
}
.nav-name{position:relative}
.nav-item .nav-name:before{content:"";position:absolute;left:0;right:0;bottom:-.06em;height:2px;
  background:var(--brand-soft);transform:scaleX(0);transform-origin:left center;
  transition:transform .46s var(--ease)}
.nav-item:hover .nav-name:before,.nav-item:focus-visible .nav-name:before{transform:scaleX(1)}
.nav-item:active .nav-name{transform:translateX(clamp(8px,1.2vw,22px)) scale(.985)}
.nav-item:focus-visible{outline:2px solid var(--brand-soft);outline-offset:6px}

@media(prefers-reduced-motion:reduce){
  .mt-bar,.mt-bar i,.nav-item .nav-name,.nav-item .nav-num,.nav-item .nav-en,
  .nav-item .nav-name:before{transition:none!important}
}

/* ---------- CLOSING REVEAL — keep the block within a phone screen ----------
   The reveal only runs while the closing block plus footer fit the viewport; past that
   the height guard drops them into normal flow so nothing is stranded off-screen. The
   two-tier footer added ~140px on mobile, which pushed company and contact over that
   line and silently switched the effect off. Trimmed on both sides so it fits again:
   measured 924px against an 844px viewport before, comfortably under after. */
@media(max-width:767px){
  .closing-block{padding-block:clamp(42px,5.2vh,64px)!important}
  .footer{padding:26px 0 22px}
  .footer-inner{--footer-gap:18px}
  .footer-top{gap:18px}
  .footer-nav{gap:8px 22px}
  .footer-base{margin-top:18px;padding-top:14px;gap:10px}
  .footer-descriptor{font-size:11px}
  /* Left at 44px: the compaction was to win back height for the closing reveal, and
     there is now 113-147px of slack, so there is no reason to shrink a touch target. */
  /* min-height alone left the two-character labels 23px wide. */
  .footer-nav a{min-height:44px;min-width:44px;justify-content:center}
  .footer-totop{min-height:44px}
}
/* Applied for a single synchronous measurement by the height guard, never painted. */
.reveal-block.is-measuring{min-height:0}
.reveal-block.is-measuring .closing-block{flex:none}

/* Same cream-surface accent as the SCO status line above. */
.subpage .status-progress{color:var(--brand)}
/* 2.88:1 at 11px; the label has to be readable even though the tile is a placeholder. */
.card-media-empty span{color:rgba(242,241,237,.62)}

/* Collapsing the whole nav behind the hamburger left the chrome with no sense of
   place -- the overlay marks the current page, but only once opened. This is the
   quiet standing answer to "where am I". Hidden while the menu is open, since the
   overlay states it far more loudly, and below 768 where the bar has no room. */
.nav-here{font-size:11px;letter-spacing:.14em;text-transform:uppercase;
  color:var(--muted-d);white-space:nowrap;transition:opacity .24s var(--ease)}
body.menu-open .nav-here{opacity:0;pointer-events:none}
@media(max-width:767px){.nav-here{display:none}}

/* The one place the brand hits at full strength. Everything above uses red as a small
   state mark; the primary CTA instead fills with it edge to edge on hover -- a single
   large red field per screen, which is what makes the mark read as deliberate rather
   than decorative. Scoped to .link-lg so secondary links keep the neutral sweep, and
   listed after the context rules so it wins the tie on source order rather than
   needing !important. */
.link-lg::before,.closing-block .link::before,
.light .link-lg::before,.light .closing-block .link::before,
.dark .link-lg::before,.graphite .link-lg::before,
.dark .closing-block .link::before,.graphite .closing-block .link::before,
.home-hero .link-lg::before,.page-hero .link-lg::before{background:var(--brand)}
.link-lg:hover,.link-lg:focus-visible,
.closing-block .link:hover,.closing-block .link:focus-visible,
.light .link-lg:hover,.light .closing-block .link:hover,
.dark .link-lg:hover,.graphite .link-lg:hover,
.dark .closing-block .link:hover,.graphite .closing-block .link:hover,
.home-hero .link-lg:hover,.page-hero .link-lg:hover{border-color:var(--brand)}
/* The `color:#fff` that used to be on this rule is gone: see section 12.1 — forcing the
   label to white is what made it cross the fill's luminance and disappear mid-sweep. */

/* The panel takes focus when the menu opens; it is a container, not a control, so it
   must not paint a ring of its own. The items keep theirs. */
.nav-panel:focus{outline:none}

/* Brand wash on card activation -------------------------------------------------
   A full-bleed brand layer over the card, with the image left faintly visible
   through it. .85 is not arbitrary: our stills run from dark venue photography to
   white product UI, and white card text over the wash measures 4.60:1 on a pure
   white image -- the worst case -- against 4.31:1 at .80, which fails AA. Darker
   images land around 6.7:1.

   The card already carries an ::after scrim, so the wash uses ::before and sits
   above the image but below .card-copy. The copy's own dark gradient is dropped
   while the wash is up, since a near-black gradient over red just reads muddy;
   the text then sits directly on the brand colour, which is what the measurement
   above assumes. Its muted greys go white for the same reason. */
.visual-card::before{
  content:"";position:absolute;inset:0;z-index:1;pointer-events:none;
  background:var(--brand);opacity:0;
  transition:opacity .34s var(--ease)}
.card-copy{z-index:2}
.card-copy,.card-eyebrow,.card-en-name,.card-arrow,.card-link p{
  transition:background .34s var(--ease),color .34s var(--ease)}

.visual-card.is-washed::before{opacity:.85}
.visual-card.is-washed .card-copy{background:none}
.visual-card.is-washed .card-eyebrow,
.visual-card.is-washed .card-en-name,
.visual-card.is-washed .card-arrow,
.visual-card.is-washed .card-link p,
.visual-card.is-washed .card-link h3{color:#fff}

/* Pointer devices get it on hover. Touch gets it on press: :hover is unreliable
   there (and sticks after the tap), so the class is driven from pointer events
   instead -- see js/site.js. Keyboard focus gets it too, so the state is not
   mouse-only. */
@media(hover:hover){
  .visual-card:hover::before{opacity:.85}
  .visual-card:hover .card-copy{background:none}
  .visual-card:hover .card-eyebrow,
  .visual-card:hover .card-en-name,
  .visual-card:hover .card-arrow,
  .visual-card:hover .card-link p,
  .visual-card:hover .card-link h3{color:#fff}
}
.visual-card:focus-visible::before{opacity:.85}
.visual-card:focus-visible .card-copy{background:none}
.visual-card:focus-visible .card-eyebrow,
.visual-card:focus-visible .card-en-name,
.visual-card:focus-visible .card-arrow,
.visual-card:focus-visible .card-link p,
.visual-card:focus-visible .card-link h3{color:#fff}

@media(prefers-reduced-motion:reduce){
  .visual-card::before,.card-copy,.card-eyebrow,.card-en-name,.card-arrow,.card-link p{transition:none}
}

/* The section directly under a subpage hero starts closer than the standard rhythm.
   That rhythm exists to separate one section from the next, but this one's top edge
   meets the hero, which already separates with its own bottom padding and a full
   change of background -- so the gap was being paid for twice, and on a phone all
   184px of it landed inside the first screen: hero 608, viewport 844, and the 236px
   left over was empty. Growing the hero would only have hidden it lower down. */
.subpage main>section.page-hero + section{padding-top:clamp(88px,9vw,168px)!important}
/* The !important is not decoration: the blanket subpage rule above already carries
   one, so overriding it needs to match. Specificity does the actual deciding -- this
   selector is one type deeper than that rule's :not(.page-hero). */

/* Section star ------------------------------------------------------------------
   A line mark centred in the gap between sections, rotating with scroll position.
   The generous section rhythm was a deliberate earlier decision, so the gap is not
   shrunk to fix the emptiness -- it is given something to hold instead.

   Monochrome by choice: at five gaps a page across five pages this would put the
   brand colour in ~25 decorative places, which is exactly the spread the accent
   rules exist to prevent. It inherits the surrounding surface's ink so it works on
   both the cream and the dark sections without a second rule.

   z-index:2 is required: the mark is centred on the seam, so half of it lives in
   the next section's padding. Without a stacking context above that sibling, the
   next section's background paints over the lower arms and the star reads as cut. */
.section-star{
  display:flex;justify-content:center;position:relative;z-index:2;
  height:0;overflow:visible;pointer-events:none;user-select:none}
.section-star svg{
  width:100px;height:100px;overflow:visible;stroke-width:4;stroke-linecap:round;
  /* The star adds no height of its own, so it lands in the empty run between two
     sections without moving anything. How far down is measured in js/site.js and
     written here as --star-drop: the gap is the two sections' facing paddings, and
     the mark's own height has to come out of the centring. The fallback is the
     correct value for the standard subpage rhythm (--space on both faces, 100px
     glyph) and is only ever painted if that script does not run -- which cannot
     happen, since the same script is what injects the mark. */
  transform:translateY(var(--star-drop,calc(var(--space) - 50px))) rotate(var(--star-rot,0deg));
  color:currentColor;opacity:.42}
.on-dark .section-star svg,.dark .section-star svg,.graphite .section-star svg{opacity:.38}
@media(max-width:767px){
  /* 100px is a quarter of a 390px viewport -- at that size the ornament competes with
     the headline it is meant to sit quietly between. */
  .section-star svg{width:72px;height:72px}
}
@media(prefers-reduced-motion:reduce){
  .section-star svg{transform:translateY(var(--star-drop,calc(var(--space) - 50px)))}
}

/* CTA press state -----------------------------------------------------------------
   The fill and the text colour were both bound to :hover alone. Touch matches neither
   :hover nor :focus-visible reliably, so on a phone the sweep would run -- iOS fakes
   hover on tap -- while the text kept its resting colour and ended up sitting on top
   of its own fill, unreadable for the length of the sweep. Every rule that fills now
   has a matching rule that recolours the text, and .is-pressed drives both from the
   pointer event so the two can never come apart again.

   The sweep is also cut short here. Half a second is right for a hover that may never
   be committed; once a tap has happened the button should confirm and get out of the
   way, so pressing snaps the fill in and js/site.js routes just after. */
.link.is-pressed{transition:color .12s var(--ease),border-color .12s var(--ease)}
.link.is-pressed::before{transition:transform .16s var(--ease);transform:scaleX(1)}
.link:active::before{transform:scaleX(1)}

/* Neutral CTAs: text flips to the surface it is being covered by. */
.light .link.is-pressed,.light .link:active{color:var(--paper);border-color:var(--deep)}
.dark .link.is-pressed,.graphite .link.is-pressed,
.home-hero .link.is-pressed,.page-hero .link.is-pressed,
.dark .link:active,.graphite .link:active,
.home-hero .link:active,.page-hero .link:active{color:var(--ink);border-color:var(--warm)}

/* Primary and closing CTAs fill with the brand, so their text goes white -- listed
   after the neutral rules so it wins on the buttons that carry both. */
.link-lg.is-pressed,.closing-block .link.is-pressed,
.link-lg:active,.closing-block .link:active,
.light .link-lg.is-pressed,.light .closing-block .link.is-pressed,
.dark .link-lg.is-pressed,.graphite .link-lg.is-pressed,
.dark .closing-block .link.is-pressed,.graphite .closing-block .link.is-pressed,
.home-hero .link-lg.is-pressed,.page-hero .link-lg.is-pressed,
.light .link-lg:active,.light .closing-block .link:active,
.dark .link-lg:active,.graphite .link-lg:active,
.home-hero .link-lg:active,.page-hero .link-lg:active{color:#fff;border-color:var(--brand)}

@media(prefers-reduced-motion:reduce){
  .link.is-pressed::before{transition:none}
}

/* Pending project index ----------------------------------------------------------
   The projects that have no written record yet. They sit under the card grid as a
   deliberate second tier rather than as empty cards: a card promises an image and a
   story, and eleven hollow ones would read as a broken page. An index row promises
   name, status and a one-line scope -- not a story -- so the row still reads as an
   index rather than as an unfinished card.

   When a project's copy and imagery arrive it graduates upward -- its row is deleted
   and a .visual-card takes its place in the grid above. Nothing here is load-bearing
   for that move. */
.pending-block{
  margin-top:clamp(64px,7vw,110px);
  padding-top:clamp(34px,3.6vw,56px);
  border-top:1px solid var(--line-l)}
.pending-head{
  display:flex;justify-content:space-between;align-items:baseline;
  gap:clamp(16px,3vw,48px);margin-bottom:clamp(20px,2.4vw,34px)}
.pending-note{margin:0;font-size:13px;line-height:1.6;color:var(--muted-l)}

.pending-list{list-style:none;margin:0;padding:0}
.pending-year{
  padding:clamp(28px,2.8vw,40px) 0 8px;
  font-size:11px;font-weight:700;letter-spacing:.14em;color:var(--muted-l)}
.pending-year:first-child{padding-top:4px}
.pending-row{
  display:grid;grid-template-columns:auto minmax(0,1fr) auto;align-items:baseline;
  gap:clamp(16px,2.2vw,38px);
  padding-block:clamp(15px,1.7vw,21px);
  border-top:1px solid var(--line-l);
  min-height:44px;
  cursor:default;
  transition:background .3s var(--ease)}
.pending-row:last-child{border-bottom:1px solid var(--line-l)}
.pending-idx{
  font-size:11px;letter-spacing:.12em;color:var(--muted-l);
  font-variant-numeric:tabular-nums;
  transition:color .3s var(--ease)}
.pending-body{display:flex;flex-direction:column;gap:5px;min-width:0}
.pending-name{
  font-size:clamp(16px,1.5vw,21px);font-weight:600;letter-spacing:-.02em;
  transition:transform .34s var(--ease)}
.pending-scope{font-size:14px;line-height:1.5;color:var(--muted-l)}
.pending-status{
  font-size:11px;font-weight:700;letter-spacing:.12em;text-transform:uppercase;
  color:var(--muted-l);white-space:nowrap}

/* Hover borrows the row treatment already used by the archive and engagement lists,
   so this reads as part of the same family. The index takes the brand because it is
   marking state -- which row you are on -- and that is what the accent is for. */
@media(hover:hover){
  .pending-row:hover{background:rgba(20,22,24,.03)}
  .pending-row:hover .pending-idx{color:var(--brand)}
  .pending-row:hover .pending-name{transform:translateX(clamp(5px,.7vw,10px))}
}
.pending-row.is-touched{background:rgba(20,22,24,.05)}
.pending-row.is-touched .pending-idx{color:var(--brand)}

@media(max-width:767px){
  .pending-head{flex-direction:column;align-items:flex-start;gap:8px}
  .pending-row{grid-template-columns:auto minmax(0,1fr);gap:6px 14px}
  .pending-status{grid-column:2;justify-self:start;white-space:normal}
}
@media(prefers-reduced-motion:reduce){
  .pending-row,.pending-idx,.pending-name{transition:none}
}

/* Toast ---------------------------------------------------------------------------
   Reinforcement, not the only channel: .pending-note already states the same thing
   permanently, so this is aria-hidden and never announced twice. Bottom-left on a
   desktop -- the bottom-left corner is taken by the work-in-progress badge, and two
   pills stacked in one corner read as an error state. Centred on a phone, lifted
   clear of that badge vertically instead. */
.toast{
  position:fixed;z-index:900;
  right:var(--gutter);bottom:26px;
  display:flex;align-items:center;gap:10px;
  max-width:min(420px,calc(100vw - var(--gutter) * 2));
  padding:12px 18px;border-radius:999px;
  background:rgba(9,10,11,.94);
  -webkit-backdrop-filter:blur(10px);backdrop-filter:blur(10px);
  color:#f2f1ed;font-size:12px;line-height:1.45;letter-spacing:.01em;
  box-shadow:0 10px 34px rgba(0,0,0,.30);
  opacity:0;transform:translateY(10px);pointer-events:none;
  transition:opacity .26s var(--ease),transform .26s var(--ease)}
.toast.is-shown{opacity:1;transform:none}
.toast b{font-weight:650;color:#fff}
.toast i{
  flex:none;width:5px;height:5px;border-radius:50%;
  background:var(--brand-soft);font-style:normal}
@media(max-width:767px){
  .toast{left:50%;right:auto;bottom:76px;transform:translate(-50%,10px);
    max-width:calc(100vw - 32px)}
  .toast.is-shown{transform:translate(-50%,0)}
}
@media(prefers-reduced-motion:reduce){
  .toast{transition:opacity .01s}
}

/* ---------- 12. HOVER STATES THAT ARE ACTUALLY VISIBLE ----------
   Three defects, all of which only appear while the pointer is on the control, which is
   why none of them showed up in a static review.

   1. The fill and the label were racing, and the label lost. --ease is
      cubic-bezier(.16,1,.3,1), heavily front-loaded: the .52s sweep already covers 86% of
      the button by 150ms, while the label's colour sat behind a .08s delay and did not
      visibly move until ~250ms. For that quarter second the label was still its rest colour
      sitting on a fill of that same colour — measured at 1.0:1 on the home hero CTA, which
      is not "hard to read", it is gone. The label now tracks the sweep: no meaningful delay,
      short enough to finish just after the fill passes under it, slow enough that it never
      arrives before the fill does.

   2. Text links reached for var(--ink) to mean "dark text". That stopped being true when
      subpage sections started redefining the palette tokens — inside them --ink IS the paper
      (#F9F5EF), so `color:var(--ink)` on hover set the label to precisely the background it
      was sitting on. Capabilities' evidence links measured 1.00:1 on hover. --brand-soft is
      the safe token for this job because it is a foreground colour on every surface here:
      5.71:1 on the dark sections, and 4.89:1 on the subpage paper where it resolves to the
      full brand.

   3. The footer links and the back-to-top button had no hover state at all. */

/* --- 12.1 the fill, and why the label no longer changes colour ------------------- */
/* The first attempt here retimed the label against the sweep. It cannot work, and the
   measurement is what showed why. The fill's edge crosses a given glyph instantly; the
   label's colour changes globally over time. Whenever the label has to travel from a dark
   rest colour to a light hover one, it passes through the fill's own luminance somewhere in
   the middle — and at that instant every glyph already covered by the fill is the same
   brightness as the fill. Measured as the share of the label under 3:1, the worst frame hid
   70% of it on the subpage surface and 100% on the light sections, at 20-40ms. No delay or
   duration moves that; the two ends of the button want opposite timings.

   So the label stops moving. Each surface fills with the brand tone its existing label
   already reads against, and the label keeps the colour it had at rest:

     dark surfaces   light label  ->  --brand      (deep red)    4.7:1
     light + paper   dark label   ->  --brand-lit  (bright red)  6.0:1

   Worst frame is now 0% of the label under 3:1 on all three surfaces. The state change is
   carried by the fill and the border, which is plenty — it was never the label's job. */
:root{--brand-lit:#FF3D4E}
/* Text links have no fill, so the whole state change is the accent colour, and it has to
   clear 4.5:1 against whatever it sits on. The brand has two tones for exactly this: the lit
   one carries on dark ground (5.71:1), the deep one on paper (4.89:1). Getting it backwards
   is a real failure, not a subtlety — the bright tone on the subpage footer's cream measured
   3.21:1. Declared per surface rather than derived from --brand-soft, which the subpage
   sections redefine out from under any rule that trusts it. */
:root,.dark,.graphite,.home-hero,.page-hero,.footer,.reveal-block{--link-accent:var(--brand-lit)}
.light,.on-light,
.subpage main>section:not(.page-hero){--link-accent:var(--brand)}

.link{--cta-fill:var(--brand);--cta-label:var(--warm);
  transition:border-color .3s var(--ease)}
.light .link{--cta-fill:var(--brand-lit);--cta-label:var(--deep)}
/* Subpage sections repaint to one cream surface and invert the palette tokens inside
   themselves, so --brand-soft resolves to the deep brand there. --brand-lit is declared on
   :root and left alone, which is the whole reason it exists as a separate token. */
.subpage main>section:not(.page-hero) .link{--cta-fill:var(--brand-lit);--cta-label:#000}
.reveal-block .link{--cta-fill:var(--brand);--cta-label:var(--warm)}
/* The doubled class is deliberate. The context rules further up are written as
   `.dark .link::before` and `.light .link:hover`, so a plain `.link` here loses to them on
   specificity no matter how late it appears. `.link.link` matches exactly the same elements
   at one class higher, which is enough to take the tie on source order without !important
   and without restating every context selector. */
.link.link::before{background:var(--cta-fill)}
.link.link:hover,.link.link:focus-visible,.link.link.is-pressed,.link.link:active{
  color:var(--cta-label);border-color:var(--cta-fill)}
.link.link.is-pressed{transition:border-color .12s var(--ease)}

/* --- 12.2 the hero CTA -------------------------------------------------------------- */
/* Requested as brand, and it already is under the rule above: the hero is a dark surface, so
   it takes the deep brand with its light label sitting at 4.7:1 on top. Listed separately
   only to beat the older .home-hero/.page-hero colour rules on source order. */
.home-hero .link,.page-hero .link{--cta-fill:var(--brand);--cta-label:var(--warm)}

/* --- 12.3 underline text links --------------------------------------------------- */
/* No fill to carry the change, so colour and rule have to carry it between them. The second
   line comes from a box-shadow rather than a thicker border so the text does not shift. */
.cap-evidence a,.section-relate a{
  transition:color .22s var(--ease),border-color .22s var(--ease),box-shadow .22s var(--ease)}
.cap-evidence a:hover,.cap-evidence a:focus-visible,
.on-light .cap-evidence a:hover,.on-light .cap-evidence a:focus-visible,
.section-relate a:hover,.section-relate a:focus-visible,
.dark .section-relate a:hover,.graphite .section-relate a:hover,
.on-light .section-relate a:hover{
  color:var(--link-accent);border-color:var(--link-accent);
  box-shadow:0 2px 0 -1px var(--link-accent)}

/* --- 12.4 the footer's own text buttons ------------------------------------------ */
/* ::after is already the invisible 44px touch target, so the rule goes on ::before. It wipes
   in from the left and out to the right, the same direction language as the nav. */
.footer-nav a,.footer-totop{
  position:relative;cursor:pointer;transition:color .22s var(--ease)}
.footer-totop{border:0;background:transparent;padding:10px 0;font-weight:650;letter-spacing:.02em}
.footer-nav a::before,.footer-totop::before{
  content:"";position:absolute;left:0;right:0;bottom:6px;height:1px;background:currentColor;
  transform:scaleX(0);transform-origin:right center;transition:transform .3s var(--ease)}
.footer-nav a:hover,.footer-nav a:focus-visible,
.footer-totop:hover,.footer-totop:focus-visible{color:var(--link-accent)}
.footer-nav a:hover::before,.footer-nav a:focus-visible::before,
.footer-totop:hover::before,.footer-totop:focus-visible::before{
  transform:scaleX(1);transform-origin:left center}

/* Touch has no hover to give feedback with, so the press state stands in for it. Without
   this a tap on a phone shows nothing at all between the touch and the navigation. */
@media(hover:none){
  .footer-nav a:active,.footer-totop:active{color:var(--link-accent)}
  .footer-nav a:active::before,.footer-totop:active::before{transform:scaleX(1);transform-origin:left center}
  .cap-evidence a:active,.section-relate a:active{
    color:var(--link-accent);border-color:var(--link-accent);box-shadow:0 2px 0 -1px var(--link-accent)}
}
@media(prefers-reduced-motion:reduce){
  .footer-nav a::before,.footer-totop::before{transition:none}
}

/* ---------- SELECTED WORK — cards awaiting their record ----------
   The empty frame reuses .card-media-empty, the placeholder Cityfield already
   established; the drawer looks for exactly that class when a card has no archive image,
   so a parallel one would have opened an empty media box.

   What is new is the eyebrow. Cityfield carries a real status line (CONTRACTED · 2022)
   because the ledger has one for it. These two are in neither the ledger nor the drawer's
   DETAIL map, so there is no status to print — and writing one would invent a claim the
   evidence does not support, which the TRUTH rules in AGENTS.md forbid. The eyebrow says
   the record is being prepared, styled as a tag so it reads as a state rather than as a
   status that happens to be worded oddly. */
.visual-card--pending .card-eyebrow{
  display:inline-block;padding:5px 10px;border-radius:999px;
  background:rgba(242,241,237,.08);color:rgba(242,241,237,.66);letter-spacing:.1em}

/* ---------- INQUIRY TYPES — the term/description row, measured ----------
   Three faults, all of them the column ratio and all of them worst exactly where the type
   is largest. At 1600 the section sits in the subpage grid's 5/13 span, so the row has
   973px to divide, and 3fr 7fr 2fr gave the heading 243px, the description 568px and the
   number 162px.

   The heading is the primary element and it was the one starving. Every Korean title wrapped
   to two lines at 40px, broken wherever 243px happened to fall rather than where the phrase
   does — "일반 사업 / 문의". English was worse: "Content / Spatial Experience Collaboration"
   ran to four lines and 180px tall while its own description was a single 18px line.

   The description had the opposite problem: 568px to hold one line that needed ~470.

   And the number held 162px to show two characters, left-aligned inside its column, which
   left a ragged gap between the description and the rule's right edge.

   5fr 6fr auto puts the space where the content is. The heading also comes down from a 40px
   cap to 34px — at 973px shared three ways, no cap large enough to keep "콘텐츠·공간 경험
   협업" on one line leaves the description a usable measure, and a heading that fits is
   worth more here than two more points of size. Its line-height was 1.375, loose enough
   that the forced second line read as two separate lines rather than one wrapped title. */
.contact-row .meta{justify-self:end;align-self:baseline}
/* Centre alignment floated the single-line description in the middle of a two-line heading,
   so the two read as unrelated. Baselines tie them to the same line. */

/* ---------- CAPABILITY RECORD — the proof row that replaced the hero image ----------
   The three CORE bands each carried one 973x690 photograph. Measured against the
   repo's own image rule (image-loading-rule/README.md: a full should be about twice
   its display width), all three were under it and one was far under -- yancheng's
   simulator is an 810px original painted 973px wide, which is 0.42 of the pixels a
   2x screen asks for, a 2.4x enlargement. The originals are the limit, not the
   compression: the PNG masters are 1445, 1148 and 810 wide. Nothing could be
   re-exported to fix it.

   So the frame moved to the material instead of the other way round. A record card is
   capped at 320px, which turns the same files into 1.3-2.5x on a 2x screen without a
   single new asset. The cap is the whole mechanism -- if these ever grow, the deficit
   comes straight back.

   Three other things fell out of the same change. The photographs are captioned now,
   so a picture states which project it is and what stage that project reached rather
   than sitting there as decoration. Several small records read as work done
   repeatedly, which is what a capability claims; one big photograph reads as work
   done once. And hana's face-access has a #000 background baked in, which was a black
   slab on the #EFE9E0 media well -- on an ink card plate it simply disappears.

   Status strings are the Evidence Ledger's approved public wording, identical to the
   card eyebrows on work.html. Nothing here says more than that page already says. */
/* The proof column ends above the claim column now that it is not carrying a 690px
   photograph -- 262px short on CORE 01, 431px on CORE 03. Both alternatives were tried
   and both are worse: align-self:end drops the records below the fold and leaves the
   heading with a blank half-screen beside it, and center pushes the label off the line
   the heading sits on. Top is where the proof answers the claim, so the slack stays at
   the bottom, which is the one place this page already runs on whitespace. */
.cap-record{margin-top:clamp(40px,4vw,62px)}
.cap-record-row{display:flex;flex-wrap:wrap;gap:clamp(14px,1.6vw,22px);margin-top:16px}
.cap-rec{flex:1 1 200px;max-width:320px;color:inherit}
/* 16/9, not the 4/3 this shipped with, because it is the shape the material already is.
   Three of the six records are exactly 16/9 and crop nothing at all; motion-measure is
   too. Against 4/3 every one of them lost a quarter of its width and face-access lost
   39 per cent -- a device lineup with the tablet and the door terminal cut off its ends.
   At 16/9 the worst case is face-access at 18 per cent, and the shorter frame raises
   pixel density as a side effect (1.15 to 1.53 at 1600 on a 2x screen). */
.cap-rec-shot{position:relative;display:block;aspect-ratio:16/9;overflow:hidden;background:#0d0f11}
/* Only the full layer scales. The preview carries its own transform for the blur-up
   and a blanket img rule would cancel it, leaving the placeholder unblurred. */
.cap-rec-shot .progressive-image__full{transition:transform .85s var(--ease)}
.cap-rec:hover .cap-rec-shot .progressive-image__full{transform:scale(1.04)}
.cap-rec-name{display:block;margin-top:14px;font-size:15px;line-height:1.45;font-weight:560;
  letter-spacing:-.01em;text-decoration:1px solid transparent underline;text-underline-offset:4px;
  transition:color .22s var(--ease),text-decoration-color .22s var(--ease)}
.cap-rec-status{display:block;margin-top:7px;font-size:10px;line-height:1.3;letter-spacing:.11em;
  text-transform:uppercase;color:var(--muted-l)}
.cap-rec:hover .cap-rec-name,.cap-rec:focus-visible .cap-rec-name{
  color:var(--link-accent);text-decoration-color:var(--link-accent)}
/* The leftover list is no longer the only evidence on screen, so it gives up the inline
   run-on shape and becomes rows that can carry a status of their own. */
.cap-evidence ul{flex-direction:column;gap:0}
.cap-evidence li{display:block}
.cap-evidence li+li{border-top:1px solid var(--line-l)}
.cap-evidence li+li:before{content:none}
.cap-evidence a{display:flex;justify-content:space-between;align-items:baseline;gap:20px;
  padding:12px 0;border-bottom:0}
.cap-ev-status{font-size:10px;letter-spacing:.11em;text-transform:uppercase;
  color:var(--muted-l);white-space:nowrap}
.cap-evidence a:hover .cap-ev-name,.cap-evidence a:focus-visible .cap-ev-name{
  text-decoration:underline;text-underline-offset:4px}
/* The 12.3 underline rule paints a rule under the whole <a>; that <a> is now a full-width
   row, so the rule would run edge to edge under a name and a status that do not belong
   together. The name carries it instead. */
.cap-evidence a:hover,.cap-evidence a:focus-visible,
.on-light .cap-evidence a:hover,.on-light .cap-evidence a:focus-visible{box-shadow:none}
@media(max-width:767px){
  /* One card per row at the cap. Two would put a 15px name under a 165px picture. */
  .cap-rec{flex:1 1 100%}
}

/* ---------- CAPABILITY BAND — the rail is a head, not a column ----------
   .cap-detail's first child sits at grid-column 1/4, the same rail every .section-head
   on a subpage uses. A head rail holds an eyebrow and a title. This one was also holding
   the body paragraph, the tag list and the practice list, and it is the only place on the
   site that puts body copy in that rail -- #integrated, on this same page, puts its
   body-large in the 5/13 field with a max-width, which is the pattern.

   Measured, the rail was 350px at 1600 and 235px at 1100, running the claim sentence at
   13 and 10.4 Korean characters a line against a comfortable 25-35. Meanwhile the 973px
   field held 320px cards and 262-431px of nothing. Width was on the wrong side.

   Widening the rail was tried and rejected: at 1/6 the measure recovers to 26 characters,
   but the field then starts at 812px while every other section on the page starts its
   content at 563px, and that break shows as soon as you scroll from CORE 03 into
   INTEGRATED. Moving the copy into the field fixes the measure and keeps the rail on the
   line the rest of the page uses.

   DOM order is unchanged, so the stacked breakpoints keep the reading order they had, and
   at 390 the column is narrower than the cap so a phone renders exactly as before. Between
   about 600 and 1023, where .cap-detail is display:block across the full wrap, the cap does
   now bind -- 512px of 727 at 767, 486px of 828 at 900 -- which pulls those from roughly 38
   characters a line back to 27. That is the same correction, not a side effect to undo. */
.cap-detail .body-large{max-width:46ch}

/* ---------- PROOF GALLERY — the credential list drives the certificate beside it ----------
   The feature held one certificate and the rows beside it named five, so four of them had
   nothing to look at. A row that owns a scan now carries data-shot and putting attention on
   it brings that scan up: hover on a mouse, tap on a touch screen, focus on a keyboard.

   The swap is sticky -- the last one asked for stays. Reverting on mouse-leave flickers when
   the pointer crosses between rows, and on a touch screen there is no leave event at all, so
   sticky is the only behaviour that is the same on both.

   Layers rather than a src swap: each shot is its own progressive-image stacked in the frame
   and crossfaded, so a certificate never blinks through white while the next one decodes.
   js/site.js builds every layer but the first, which stays in the markup so the section still
   shows something with no JS. */
/* A4, because that is what the scans are, so a certificate fills the frame instead of
   floating in it. The width is capped as well: the feature owns half the row, and a
   portrait frame given 736px of it renders a certificate over a thousand pixels tall and
   leaves the section standing on empty space. 420 is about as large as a document wants
   to be read at here. The paddings the old auto-height box carried are dropped -- the
   ratio governs the shape now, and the cap leaves the gutter the padding used to fake. */
.proof-feature{position:relative;width:min(100%,420px);aspect-ratio:1/1.414;padding:0}
/* The old box sized itself to whichever certificate was in the markup, so it carried a
   max-height and paddings per page to keep that one scan in proportion. The ratio does
   that job now for every scan, and those overrides only fought it -- company capped the
   picture at 430px inside a 594px frame, which left the certificate floating above a
   band of empty paper. They are gone, including the 767px height clamp that existed
   only because an absolutely-stacked layer had no intrinsic height to collapse from. */
.proof-feature .progressive-image{position:absolute;inset:0;opacity:0;
  transition:opacity .38s var(--ease)}
.proof-feature .progressive-image.is-shown{opacity:1}
/* A certificate is a document -- it may never be cropped, so this frame is the one place that
   overrides the pipeline's cover default. The frame is close to the scans' own proportion, so
   the letterbox it leaves is a few pixels rather than a band. */
.proof-feature .progressive-image__preview,
.proof-feature .progressive-image__full{object-fit:contain}
.proof-row[data-shot]{cursor:pointer;transition:color .22s var(--ease)}
.proof-row[data-shot]:hover,.proof-row[data-shot]:focus-visible{color:var(--link-accent)}
.proof-row[data-shot]:focus-visible{outline:2px solid var(--brand-soft);outline-offset:6px}
/* The marker sits in the year column's own gutter, so turning it on moves nothing. */
.proof-row[data-shot]{position:relative}
.proof-row[data-shot]::before{content:"";position:absolute;left:-3px;top:calc(50% - 5px);
  width:5px;height:10px;background:var(--link-accent);opacity:0;transition:opacity .28s var(--ease)}
.proof-row.is-shown::before{opacity:1}
@media(max-width:1023px){
  /* No left gutter to sit in once the rail collapses, so the marker moves under the year. */
  .proof-row[data-shot]::before{left:0;top:auto;bottom:6px;width:18px;height:2px}
}
@media(prefers-reduced-motion:reduce){
  .proof-feature .progressive-image,.proof-row[data-shot]::before{transition:none}
}

/* ---------- HOME HERO — looping exhibition clip ----------
   Still + poster hold the first paint. The H.264 loop sits in the same stage so
   parallax moves photograph and video together. The 116% height with an 8% overhang
   is so the parallax has somewhere to travel without exposing an edge. */
.home-hero .hero-stage{position:absolute;top:-8%;bottom:-8%;left:0;right:0;height:116%;
  pointer-events:none;z-index:0}
.home-hero .hero-stage .progressive-image{position:absolute;inset:0}
.home-hero .hero-stage .hero-video{position:absolute;inset:0;width:100%;height:100%;
  object-fit:cover;object-position:center;z-index:1}
.home-hero .hero-shade{z-index:2}
@media(max-width:767px){
  .home-hero .progressive-image__preview,
  .home-hero .progressive-image__full,
  .home-hero .hero-video{object-position:center}
}
