/* v22 polish layer — refinement + motion. translate / opacity / clip-path / colour only. */
:root{--e3:cubic-bezier(.16,1,.3,1)}
/* A. typography */
body{font-feature-settings:"kern","liga";text-rendering:optimizeLegibility}
.ix,.row .k,.facts b,.tn,.sk em,#tlo,.no,.ct a{font-variant-numeric:lining-nums tabular-nums}
.sh h2{letter-spacing:-.025em}.sh h2 small{letter-spacing:.32em;font-size:var(--fs-2);color:var(--ink3);position:relative;top:-.15em}
.row h3{letter-spacing:.01em}.row h3 .cn{letter-spacing:.06em}
.lead .cn{letter-spacing:.06em;line-height:var(--lh-4)}
.hero .meta{letter-spacing:.22em}
.hero .role{letter-spacing:.02em}
.pillars h4{letter-spacing:.04em}
.facts b{letter-spacing:.02em}
/* B. keyword highlight sits on the text, not on the padded hit box */
.fx .kw{background-origin:content-box;background-clip:content-box}
/* C. contact: phone/email never break from their copy button */
.cl{flex-wrap:nowrap}
@media(max-width:600px){.ct a[href^=mailto]{font-size:clamp(.8rem,3.6vw,1rem);letter-spacing:-.01em}.ct{font-size:var(--fs-3)}}
/* D. hairlines: everything fades at the ends */
.sh:after{-webkit-mask-image:linear-gradient(90deg,transparent,#000 10%,#000 90%,transparent);mask-image:linear-gradient(90deg,transparent,#000 10%,#000 90%,transparent)}
.row+.row:before{content:"";position:absolute;left:0;right:0;top:0;height:1px;background:var(--fd)}
/* E. smoother section colour morph */
body{transition:background-color 1.2s var(--e3),color 1.2s var(--e3)}
.progress{height:1px;box-shadow:0 0 .5rem color-mix(in srgb,var(--acc) 50%,transparent)}
/* M1 hero scroll cue */
.cue{position:absolute;left:50%;bottom:var(--s6);translate:-50% 0;display:flex;flex-direction:column;align-items:center;gap:var(--s2);font-size:var(--fs-1);letter-spacing:.3em;text-transform:uppercase;color:var(--ink3);z-index:2;pointer-events:none;transition:opacity var(--d2) var(--e1)}
.cue i{width:1px;height:3rem;background:color-mix(in srgb,var(--ink3) 25%,transparent);position:relative;overflow:hidden}
.cue i:after{content:"";position:absolute;left:0;top:0;width:1px;height:40%;background:var(--acc);animation:cue 2.4s var(--e2) infinite}
@keyframes cue{0%{translate:0 -100%}100%{translate:0 260%}}
.scrolled .cue{opacity:0}
@media(max-width:760px){.cue{display:none}}
/* M2 nav: rolling label on hover (duplicate slides up, no scramble) + gliding active marker */
header nav a{position:relative;overflow:hidden}
header nav a .r1,header nav a .r2{display:block;transition:translate var(--d2) var(--e3)}
header nav a .r2{position:absolute;left:0;right:0;top:50%;translate:0 100%;margin-top:-.5em;text-align:center;color:var(--acc)}
header nav a .rw{position:relative;display:block;overflow:hidden;line-height:1}
@media(hover:hover){header nav a:hover .r1{translate:0 -110%}header nav a:hover .r2{translate:0 -50%}}
header nav .mk2{position:absolute;bottom:.55rem;left:0;height:1px;width:0;background:var(--acc);transition:translate .6s var(--e3),width .6s var(--e3),opacity var(--d2);opacity:0;pointer-events:none}
@media(min-width:1001px){header nav{position:relative}}
@media(max-width:1000px){header nav .mk2{display:none}}
/* M3 hero name crossfade gains a soft focus pull */
.swap>span{transition:opacity var(--d3) var(--e3),clip-path var(--d3) var(--e3),filter var(--d3) var(--e3)}
.swap .en2{filter:blur(6px)}.swap:hover .en2,.swap:focus-visible .en2,.swap.on .en2{filter:none}
.swap:hover .zh1,.swap:focus-visible .zh1,.swap.on .zh1{filter:blur(6px)}
/* M4 hero underline under the name draws from the centre */
.htxt .role{background-size:0 1px;background-position:center top;transition:background-size 1.4s var(--e3) .5s}
.v22on .htxt .role{background-size:100% 1px}
/* M5 facts: hairline dividers grow, numeral rises */
.facts:before,.facts:after{scale:0 1;transition:scale 1.4s var(--e3)}
.facts.v-in:before,.facts.v-in:after{scale:1 1}
.facts div{transition:background-color var(--d2) var(--e1)}
@media(hover:hover){.facts div:hover{background-color:color-mix(in srgb,var(--acc) 5%,transparent)}.facts div:hover b{color:var(--acc)}}
.facts b{transition:color var(--d2) var(--e1)}
/* M6 pillars: accent rule extends + numeral glides on hover */
.pillars>div:before{transition:width .7s var(--e3)}
.pillars h4 em{display:inline-block;transition:translate var(--d2) var(--e3)}
@media(hover:hover){.pillars>div:hover:before{width:100%}.pillars>div:hover h4 em{translate:-.25rem 0}}
/* M7 course chips: staggered rise + fill sweep on hover */
.courses span{position:relative;overflow:hidden;isolation:isolate;transition:color var(--d2) var(--e1),opacity .7s var(--e3),translate .7s var(--e3);transition-delay:0s,0s,calc(var(--ci,0)*70ms),calc(var(--ci,0)*70ms)}
.courses span:before{content:"";position:absolute;inset:0;z-index:-1;background:var(--acc);clip-path:inset(0 100% 0 0);transition:clip-path var(--d2) var(--e3)}
@media(hover:hover){.courses span:hover{color:var(--bg)}.courses span:hover:before{clip-path:inset(0 0 0 0)}}
.v22 .courses:not(.v-in) span{opacity:0;translate:0 .5rem}
/* M8 row labels slide in */
.row .k{transition:opacity .9s var(--e3),translate .9s var(--e3)}
.v22 .row:not(.v-in) .k{opacity:0;translate:-.75rem 0}
/* M9 job rows: accent spine draws on hover */
.job:after{content:"";position:absolute;left:-1.25rem;top:var(--s6);bottom:var(--s6);width:1px;background:var(--acc);scale:1 0;transform-origin:top;transition:scale .7s var(--e3)}
@media(hover:hover){.job:hover:after,.job.on:after{scale:1 1}}
@media(max-width:760px){.job:after{display:none}}
.job .fig img{transition:filter .8s var(--e3)}
.job:not(.on) .fig img{filter:saturate(.75) brightness(.98)}
/* M10 skill cards: pointer spotlight inside the card */
.sk>div{position:relative;overflow:hidden;isolation:isolate}
.sk>div:after{content:"";position:absolute;inset:0;z-index:-1;background:radial-gradient(14rem circle at var(--sx,50%) var(--sy,-30%),color-mix(in srgb,var(--acc) 12%,transparent),transparent 70%);opacity:0;transition:opacity var(--d2) var(--e1)}
@media(hover:hover){.sk>div:hover:after{opacity:1}}
.sk em{display:inline-block;transition:translate var(--d2) var(--e3)}
@media(hover:hover){.sk>div:hover em{translate:.25rem 0}}
/* M11 honours badge: one soft sheen sweep on reveal */
.hon .res{position:relative;overflow:hidden}
.hon .res:after{content:"";position:absolute;inset:0;background:linear-gradient(100deg,transparent 30%,color-mix(in srgb,#fff 55%,transparent) 50%,transparent 70%);translate:-120% 0}
.hon.in .res:after{transition:translate 1.6s var(--e3) .5s;translate:120% 0}
/* M12 talks: number slides, hairline fills with accent on hover */
.talks li .tn{transition:translate var(--d2) var(--e3)}
.talks li:before{content:"";position:absolute;left:0;bottom:0;height:1px;width:100%;background:var(--acc);scale:0 1;transform-origin:left;transition:scale .7s var(--e3);z-index:1}
@media(hover:hover){.talks li:hover .tn{translate:.375rem 0}.talks li:hover:before{scale:1 1}.talks li:hover{color:var(--ink)}}
/* M13 big faint section numeral behind headings, drifting slower than the page (parallax via translate) */

.sec .sh{isolation:isolate}
@media(max-width:760px){}
/* M14 contact: links underline draws from left on hover */
.ct a{background-size:100% 1px;position:relative}
.ct a:after{content:"";position:absolute;left:0;right:0;bottom:20%;height:1px;background:var(--acc);scale:0 1;transform-origin:left;transition:scale .6s var(--e3)}
@media(hover:hover){.ct a:hover:after{scale:1 1}}
/* M15 back to top arrow lifts */
.foot a .ar{display:inline-block;margin-left:.35em;transition:translate var(--d2) var(--e3)}
@media(hover:hover){.foot a:hover .ar{translate:0 -.25rem}}
/* M16 theme change: circular wipe from the toggle (View Transitions) */
::view-transition-old(root),::view-transition-new(root){animation:none;mix-blend-mode:normal}
/* M18 headings: letters rise inside a mask */
.sh h2 .ch2{display:inline-block;overflow:hidden;vertical-align:bottom;padding-bottom:.08em}
.sh h2 .ch2>span{display:inline-block;translate:0 105%;transition:translate 1s var(--e3);transition-delay:calc(var(--k)*45ms)}
.sh.v-in h2 .ch2>span,.rm .sh h2 .ch2>span{translate:none}
/* M19 photo: soft vignette breathes in after load (opacity only) */
.ph{position:relative}
.ph:after{content:"";position:absolute;inset:0;pointer-events:none;background:radial-gradient(120% 90% at 50% 40%,transparent 60%,color-mix(in srgb,var(--bg) 55%,transparent));opacity:0;transition:opacity 2s var(--e3) .4s}
.v22on .ph:after{opacity:1}
/* M20 filter pills: sliding active capsule */
.flt{position:relative;isolation:isolate}.flt .fk{position:absolute;z-index:-1;border-radius:99px;background:color-mix(in srgb,var(--acc) 12%,transparent);transition:translate .6s var(--e3),width .6s var(--e3),height .6s var(--e3);pointer-events:none}
.rm *{transition-duration:.01s!important;animation:none!important}

/* ===== v23 ===== */
/* name swap: soft hairline focus ring, same accent as the site, no heavy box */
.swap:focus-visible{outline:2px solid var(--acc);outline-offset:.5rem;border-radius:999px}
/* shortcut switch in the panel */
.hint .kbs{display:flex;align-items:center;gap:var(--s2);margin-top:var(--s2);padding:var(--s2) 0 0;border:0;border-top:1px solid color-mix(in srgb,var(--ink3) 40%,transparent);background:none;color:inherit;font:500 var(--fs-1)/var(--lh-2) var(--sans);cursor:pointer;text-align:left}
.hint .kbs i{position:relative;width:1.9rem;height:1rem;border-radius:1rem;background:color-mix(in srgb,var(--ink3) 45%,transparent);transition:background var(--d2) var(--e3)}
.hint .kbs i:after{content:"";position:absolute;top:.15rem;left:.15rem;width:.7rem;height:.7rem;border-radius:50%;background:var(--bg);transition:translate var(--d2) var(--e3)}
.hint .kbs[aria-checked=true] i{background:var(--acc)}.hint .kbs[aria-checked=true] i:after{translate:.9rem 0}
/* P2 type: the interpunct uses the sans face (serif fallback drew it as a hairline bar) */
.hero .role em{font-family:var(--sans);font-style:normal;font-weight:500;padding:0 .12em;background:none!important}
/* P2 hierarchy on phones: name clearly above the subtitle */
@media(max-width:760px){.nmh{font-size:var(--fs-8)}.hero .role{font-size:var(--fs-4);margin-top:var(--s5)}}
/* P2 1440 balance: name block sits on the optical centre, not the bottom third */
@media(min-width:1101px){.hero{align-items:center}.htxt{padding-bottom:var(--s8);margin-top:-4vh}}
/* containment: isolate card/row layout from the rest of the page */
.sk>div,.job,.talks li,.hon{contain:layout style}
/* v23: the off-state roll copy is fully invisible (not just clipped) so it never reads as overlapping text */
/* v23: watermark numerals off on phones (they sat under copy and photos at 390) */

/* v24 nav roll: exact 1.6em mask, both labels centred in it, English rolls up to the Chinese section title */
header nav a .rw{position:relative;display:block;overflow:hidden;height:1.5em;line-height:1.5em}
header nav a .r1,header nav a .r2{display:block;height:1.5em;line-height:1.5em;margin:0;white-space:nowrap;text-align:center;transition:translate var(--d2) var(--e3),opacity var(--d2) var(--e3)}
header nav a .r2{position:absolute;inset:calc(50% + .6em) 0 auto 0;translate:none;font-family:var(--cn);text-transform:none;letter-spacing:.24em;text-indent:.24em;font-size:var(--fs-2);height:1.2em;line-height:1.2em;color:var(--acc);opacity:0}
@media(hover:hover){header nav a:hover .r1{translate:0 -100%;opacity:0}header nav a:hover .r2{translate:0 -100%;opacity:1}}
header nav a:focus-visible .r1{translate:0 -100%;opacity:0}header nav a:focus-visible .r2{translate:0 -100%;opacity:1}
@media(prefers-reduced-motion:reduce){header nav a .r1,header nav a .r2{transition:opacity var(--d1,.2s) linear}}
/* v24 cursor over nav: both rings shrink to a thin hollow dot so the label stays readable */
.fc.cnav #cr{width:.375rem!important;height:.375rem!important;background:transparent!important}

/* v25 hierarchy: on wide screens the subtitle steps further back from the name (64px name vs 24px subtitle token --fs-5, was 35px) */
@media (min-width:1101px){.hero .role{font-size:var(--fs-5);letter-spacing:.06em}}
.hint.off>span{opacity:.45}
.talks .tn{color:color-mix(in srgb,var(--acc) 86%,var(--ink))}
.hint.show{transition:opacity .45s cubic-bezier(.2,.8,.2,1),translate .45s cubic-bezier(.2,.8,.2,1),visibility 0s}

/* v25 Profile: structured label/value rows (was one paragraph) */
.pf{display:grid;gap:0;margin:0 0 var(--s7,3rem);max-width:52rem;border-top:1px solid var(--line)}
.pf>div{display:grid;grid-template-columns:9rem 1fr;gap:var(--s5);align-items:baseline;padding:var(--s4) 0;border-bottom:1px solid var(--line)}
.pf dt{font:500 var(--fs-3)/var(--lh-3) var(--cn);color:var(--ink);letter-spacing:.08em}
.pf dt span{display:block;font:500 var(--fs-1)/var(--lh-3) var(--sans);letter-spacing:.14em;text-transform:uppercase;color:var(--ink2)}
.pf dd{margin:0;font:400 var(--fs-4)/var(--lh-4) var(--cn);color:var(--ink)}
.pf dd i{font-style:normal;color:var(--acc);margin:0 var(--s2)}
@media(max-width:760px){.pf>div{grid-template-columns:1fr;gap:var(--s1)}.pf dt{display:flex;gap:var(--s3);align-items:baseline}.pf dt span{display:inline}.pf dd{font-size:var(--fs-3)}}
.nw{white-space:nowrap;display:inline-block}
@media(max-width:760px){.pf dd i{display:none}.pf dd>.nw{display:table}}
.htxt{transition:opacity var(--d2) var(--e3)}
@media(max-width:760px){.ring{right:2px;bottom:var(--s4)}}
@media(max-width:760px){.sec .sh .ix{margin-right:calc(var(--hit) + var(--s2))}}
