:root{--bg:#0d0d0d;--fg:#ededed;--dim:#cfcfcf;--muted:#8a8a8a;--faint:#5f5f5f;--line:#242424;--panel:#131313;--accent:#5b7dfb;--ok:#4ad07f;--danger:#d47a7a;--overlay:rgba(8, 8, 8, .95);--track:#2b2b2b;--track-on:#ededed;--knob:#8a8a8a;--knob-on:#111;--mono:ui-monospace,SFMono-Regular,Menlo,Consolas,monospace;color-scheme:dark}:root[data-theme=light]{--bg:#fff;--fg:#14161a;--dim:#33373d;--muted:#6b7078;--faint:#858c95;--line:#e3e5ea;--panel:#f6f7f9;--accent:#2f5bd6;--ok:#17925a;--danger:#c2504f;--overlay:rgba(252, 252, 253, .96);--track:#d7dae0;--track-on:#14161a;--knob:#fff;--knob-on:#fff;color-scheme:light}*{box-sizing:border-box}html{background:var(--bg)}body{margin:0;min-width:320px;background:var(--bg);color:var(--fg);font:16px/1.65 -apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,Helvetica,Arial,sans-serif;-webkit-font-smoothing:antialiased;transition:background .2s,color .2s}img{display:block;max-width:100%}button,input,select,textarea{font:inherit;color:inherit}a{color:var(--accent);text-decoration:none}a:hover{text-decoration:underline}::selection{background:var(--accent);color:#fff}.en,.si{display:none}body.en-version .en,body.si-version .si{display:block}body.en-version span.en,body.si-version span.si{display:inline}.wrap{width:min(600px,calc(100% - 40px));margin:0 auto;padding:44px 0 64px}.wrap.wide{width:min(860px,calc(100% - 40px))}.nav{display:flex;align-items:center;justify-content:space-between;gap:16px;margin-bottom:42px}.nav nav{display:flex;gap:20px}.nav nav a{color:var(--muted);font-size:15px;text-transform:lowercase;transition:color .18s}.nav nav a.active,.nav nav a:hover{color:var(--fg);text-decoration:none}.tools{display:flex;align-items:center;gap:14px}.lang{display:flex;align-items:center;gap:9px;font-size:13px}.lang button{padding:0;border:0;background:0 0;color:var(--muted);font-size:13px;text-transform:lowercase;cursor:pointer;transition:color .18s}.lang button.is-active,.lang button:hover{color:var(--fg)}.pill{position:relative;width:44px;height:23px;padding:0;border:0;border-radius:99px;background:var(--track);cursor:pointer;transition:background .2s}.pill::after{content:'';position:absolute;top:3px;left:3px;width:17px;height:17px;border-radius:50%;background:var(--knob);transition:transform .2s,background .2s}.pill.is-on{background:var(--track-on)}.pill.is-on::after{background:var(--knob-on);transform:translateX(21px)}h1{margin:0 0 24px;font-size:25px;font-weight:700;line-height:1.3;letter-spacing:-.01em}h2{margin:0 0 14px;font-size:19px;font-weight:700}p{margin:0 0 18px;color:var(--dim)}.hl{color:var(--accent)}.kicker{margin:0 0 10px;color:var(--muted);font:500 12px/1.5 var(--mono);text-transform:lowercase}section{margin-top:46px}.sep{padding-top:34px;border-top:1px solid var(--line)}.head{display:flex;align-items:baseline;justify-content:space-between;gap:14px;margin-bottom:12px}.head .kicker{margin:0}.live,.presence{display:inline-flex;align-items:center;gap:7px;color:var(--muted);font:500 12px/1.5 var(--mono)}.presence{margin-bottom:22px}.live::before,.presence::before{content:'';width:6px;height:6px;border-radius:50%;background:#4a4a4a;transition:background .2s}.live.online::before,.presence.is-online::before{background:var(--ok);box-shadow:0 0 8px rgba(74,208,127,.6)}.presence strong{color:var(--dim);font-weight:600}.links{display:flex;flex-wrap:wrap;gap:20px;margin-top:26px}.links a{display:inline-flex;align-items:center;gap:6px;color:var(--fg);font-size:15px}.links a::before{content:'\2197';color:var(--accent);font-size:13px}.links a:hover{color:var(--accent);text-decoration:none}.stats{display:grid}.stat{display:flex;align-items:baseline;justify-content:space-between;gap:14px;padding:9px 0;border-bottom:1px solid var(--line)}.stat:first-child{border-top:1px solid var(--line)}.stat small{color:var(--muted);font:500 12px var(--mono);text-transform:lowercase}.stat strong{font-size:14px;font-weight:500}.form{display:grid;gap:14px}.form label{display:grid;gap:6px;color:var(--muted);font:500 12px/1.4 var(--mono);text-transform:lowercase}.form input,.form select,.form textarea{width:100%;padding:9px 11px;border:1px solid var(--line);border-radius:6px;background:var(--panel);color:var(--fg);font-family:inherit;font-size:14px;outline:0;transition:border-color .18s}.form textarea{min-height:92px;resize:vertical}.form input:focus,.form select:focus,.form textarea:focus{border-color:var(--accent)}.btn{justify-self:start;padding:8px 14px;border:1px solid var(--line);border-radius:6px;background:0 0;color:var(--fg);font-size:13px;text-transform:lowercase;cursor:pointer;transition:color .18s,border-color .18s}.btn:hover{border-color:var(--accent);color:var(--accent)}.status{min-height:16px;margin:0;color:var(--muted);font:500 12px var(--mono)}.grid{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));align-items:start;gap:12px}.card{margin:0;border:1px solid var(--line);border-radius:6px;background:var(--panel);overflow:hidden;cursor:pointer}.card:focus-visible,.card:hover{border-color:var(--faint);outline:0}.card img{width:100%;height:auto;transition:opacity .2s}.card:hover img{opacity:.85}.card figcaption{display:flex;gap:8px;padding:8px 10px;color:var(--muted);font-size:13px;line-height:1.4}.card figcaption small{color:var(--faint);font:500 11px/1.6 var(--mono)}.card figcaption p{margin:0;color:var(--muted);overflow-wrap:anywhere}.missing{display:none;padding:22px 10px;text-align:center;color:var(--muted);font:500 12px var(--mono)}.card.is-missing img{display:none}.card.is-missing .missing{display:block}.empty{padding:40px 0;color:var(--muted);font:500 13px var(--mono);text-align:center}.grid .empty{grid-column:1/-1}.lb{position:fixed;inset:0;z-index:50;display:grid;place-items:center;padding:26px;background:var(--overlay);opacity:0;visibility:hidden;transition:opacity .2s,visibility .2s}.lb.open{opacity:1;visibility:visible}.lb-inner{display:grid;justify-items:center;max-width:100%}.lb img{max-width:min(1000px,100%);max-height:78vh;width:auto;border-radius:6px}.lb-bottom{display:flex;justify-content:space-between;gap:16px;width:100%;margin-top:10px;color:var(--muted);font:500 12px var(--mono)}.lb-close{position:absolute;top:16px;right:18px;width:32px;height:32px;border:1px solid var(--line);border-radius:6px;background:0 0;color:var(--muted);font-size:18px;line-height:1;cursor:pointer}.lb-close:hover{border-color:var(--accent);color:var(--fg)}.row{display:block;padding:16px 0;border-bottom:1px solid var(--line);color:inherit}.row:first-child{border-top:1px solid var(--line)}.row:hover{text-decoration:none}.row h2{margin:0 0 4px;font-size:16px;font-weight:600;transition:color .18s}.row:hover h2{color:var(--accent)}.row p{margin:0 0 9px;color:var(--muted);font-size:14px}.row .thumb{width:54px;height:54px;margin-bottom:10px;border-radius:6px;object-fit:cover}.tags{display:flex;flex-wrap:wrap;gap:6px}.tags span{padding:2px 8px;border:1px solid var(--line);border-radius:99px;color:var(--muted);font:500 11px/1.6 var(--mono)}.certs{display:grid}.cert{display:flex;align-items:flex-start;gap:15px;padding:16px 0;border-bottom:1px solid var(--line);color:inherit}.cert:first-child{border-top:1px solid var(--line)}.cert:hover{text-decoration:none}.cert:focus-visible{outline:1px solid var(--accent);outline-offset:3px}.cert-badge{flex:0 0 auto;width:64px;height:64px;border-radius:5px;object-fit:contain;transition:transform .18s}.cert:hover .cert-badge{transform:scale(1.06)}.cert-body{min-width:0}.cert-body h2{margin:0 0 4px;font-size:16px;font-weight:600;transition:color .18s}.cert:hover .cert-body h2{color:var(--accent)}.cert-body small{display:block;margin-bottom:9px;color:var(--faint);font:500 11px/1.6 var(--mono)}.cert-verify{display:inline-flex;align-items:center;gap:5px;margin-top:11px;color:var(--muted);font:500 11px/1.6 var(--mono);text-transform:lowercase;transition:color .18s}.cert-verify::after{content:'\2197';color:var(--accent)}.cert:hover .cert-verify{color:var(--fg)}.panel{margin-top:22px;padding:18px;border:1px solid var(--line);border-radius:8px;background:var(--panel)}.panel h2{margin:0 0 14px;font-size:15px}.mini{margin-bottom:12px;padding:5px 10px;border:1px solid var(--line);border-radius:5px;background:0 0;color:var(--muted);font:500 11px var(--mono);text-transform:lowercase;cursor:pointer}.mini:hover{color:var(--fg)}.list{display:grid;color:var(--muted);font-size:13px}.list-row{display:flex;align-items:flex-start;justify-content:space-between;gap:14px;padding:10px 0;border-top:1px solid var(--line)}.list-row small{display:block;margin-top:4px;color:var(--faint);font:500 11px var(--mono)}.list-row strong{color:var(--dim);font-weight:600}.list-row p{margin:8px 0 0;color:var(--muted);font-size:13px}.list-row button{flex:0 0 auto;padding:4px 9px;border:1px solid var(--line);border-radius:5px;background:0 0;color:var(--muted);font:500 11px var(--mono);text-transform:lowercase;cursor:pointer}.list-row button:hover{border-color:var(--danger);color:var(--danger)}.foot{display:flex;flex-wrap:wrap;justify-content:space-between;gap:12px;margin-top:54px;padding-top:18px;border-top:1px solid var(--line);color:var(--muted);font:500 12px var(--mono);text-transform:lowercase}.foot a{color:var(--muted)}.foot a:hover{color:var(--fg);text-decoration:none}[data-router-view]{animation:.32s both fade}.router-leaving [data-router-view]{opacity:.25;transition:opacity .15s}@keyframes fade{from{opacity:0;transform:translateY(6px)}to{opacity:1;transform:none}}@media (max-width:560px){.wrap{padding:32px 0 52px}.grid{grid-template-columns:1fr}.cert{gap:12px}.cert-badge{width:54px;height:54px}.nav{flex-wrap:wrap;gap:12px}.nav nav{gap:16px}h1{font-size:22px}}@media (prefers-reduced-motion:reduce){*,::after,::before{animation:none!important;transition:none!important}}

/* ============================================================
   1. MOTION SCALE
   One easing curve and one duration scale for the whole site,
   so every effect below reads as part of the same system.
   ============================================================ */

:root{
    --ease:cubic-bezier(.22,.61,.36,1);      /* the curve, used everywhere */
    --ease-soft:cubic-bezier(.16,1,.3,1);    /* gentle overshoot, entrances only */
    --t1:140ms;                              /* micro: press, colour swap */
    --t2:220ms;                              /* standard: hover, borders */
    --t3:340ms;                              /* deliberate: modal, theme, page */
    --t4:520ms;                              /* entrance: scroll reveal */
    --t-route:170ms;                         /* must match the wait in js/router.js */
}

/* ============================================================
   2. TIME-OF-DAY PALETTES
   [data-daypart] is set on <html> by the inline head script (so
   there is no flash) and kept current by js/motion.js.

       morning  05:00-10:59   cooler, fresher, a touch airy
       day      11:00-16:59   the original palette, crispest
       evening  17:00-20:59   warmer, accent drifts to violet
       night    21:00-04:59   warmest and dimmest, low glare

   Each daypart only shifts the palette's temperature -- same
   structure, same roles, same near-monochrome + one accent
   identity. Both light and dark schemes get all four, so the
   existing theme toggle keeps full authority over light vs dark.

   Every combination was checked: body text >= 15:1, paragraphs
   >= 10.7:1, 12px muted text >= 4.65:1, accents >= 5.8:1.
   ============================================================ */

/* Registering the palette as <color> lets the values interpolate,
   so a theme change crossfades instead of cutting. Browsers
   without @property ignore this and fall back to .theme-anim. */
@property --bg       { syntax:"<color>"; inherits:true; initial-value:#0d0d0d }
@property --fg       { syntax:"<color>"; inherits:true; initial-value:#ededed }
@property --dim      { syntax:"<color>"; inherits:true; initial-value:#cfcfcf }
@property --muted    { syntax:"<color>"; inherits:true; initial-value:#8a8a8a }
@property --faint    { syntax:"<color>"; inherits:true; initial-value:#5f5f5f }
@property --line     { syntax:"<color>"; inherits:true; initial-value:#242424 }
@property --panel    { syntax:"<color>"; inherits:true; initial-value:#131313 }
@property --accent   { syntax:"<color>"; inherits:true; initial-value:#5b7dfb }
@property --ok       { syntax:"<color>"; inherits:true; initial-value:#4ad07f }
@property --danger   { syntax:"<color>"; inherits:true; initial-value:#d47a7a }
@property --overlay  { syntax:"<color>"; inherits:true; initial-value:rgba(8,8,8,.95) }
@property --track    { syntax:"<color>"; inherits:true; initial-value:#2b2b2b }
@property --track-on { syntax:"<color>"; inherits:true; initial-value:#ededed }
@property --knob     { syntax:"<color>"; inherits:true; initial-value:#8a8a8a }
@property --knob-on  { syntax:"<color>"; inherits:true; initial-value:#111111 }

:root{
    transition-property:--bg,--fg,--dim,--muted,--faint,--line,--panel,--accent,--ok,--danger,--overlay,--track,--track-on,--knob,--knob-on;
    transition-duration:var(--t3);
    transition-timing-function:var(--ease);
}

/* Blanket crossfade for browsers with no @property support.
   js/motion.js adds .theme-anim for the length of a swap. */
.theme-anim,
.theme-anim *,
.theme-anim ::before,
.theme-anim ::after{
    transition:background-color var(--t3) var(--ease),border-color var(--t3) var(--ease),color var(--t3) var(--ease),box-shadow var(--t3) var(--ease)!important;
}

/* --- dark scheme ------------------------------------------- */

:root[data-theme=dark][data-daypart=morning]{
    --bg:#0b0d10;--fg:#eaeff4;--dim:#cbd3db;--muted:#8b959e;--faint:#646d76;
    --line:#212731;--panel:#11151a;--accent:#5d9cf0;--ok:#45cf95;--danger:#d47a86;
    --overlay:rgba(7,9,12,.95);--track:#29303a;--track-on:#eaeff4;--knob:#8b959e;--knob-on:#0f1216;
}
:root[data-theme=dark][data-daypart=day]{
    --bg:#0d0d0d;--fg:#ededed;--dim:#cfcfcf;--muted:#8a8a8a;--faint:#5f5f5f;
    --line:#242424;--panel:#131313;--accent:#5b7dfb;--ok:#4ad07f;--danger:#d47a7a;
    --overlay:rgba(8,8,8,.95);--track:#2b2b2b;--track-on:#ededed;--knob:#8a8a8a;--knob-on:#111;
}
:root[data-theme=dark][data-daypart=evening]{
    --bg:#100e0c;--fg:#f1ebe4;--dim:#d6cdc3;--muted:#9a8f83;--faint:#6f665c;
    --line:#2a2320;--panel:#171412;--accent:#9b82f5;--ok:#62cf78;--danger:#dd8570;
    --overlay:rgba(12,10,8,.95);--track:#332b27;--track-on:#f1ebe4;--knob:#9a8f83;--knob-on:#141110;
}
:root[data-theme=dark][data-daypart=night]{
    --bg:#0a0908;--fg:#e9e0d6;--dim:#cbc0b4;--muted:#998e80;--faint:#71675a;
    --line:#26211d;--panel:#12100e;--accent:#9a9ee4;--ok:#6bc98a;--danger:#d68f7a;
    --overlay:rgba(8,7,6,.95);--track:#2e2823;--track-on:#e9e0d6;--knob:#998e80;--knob-on:#100e0c;
}

/* --- light scheme ------------------------------------------ */

:root[data-theme=light][data-daypart=morning]{
    --bg:#fbfdff;--fg:#101720;--dim:#2e353f;--muted:#646d79;--faint:#7d8794;
    --line:#dde5ee;--panel:#f1f5fa;--accent:#1f5fc4;--ok:#0d8560;--danger:#bc4a5a;
    --overlay:rgba(250,252,255,.96);--track:#d2dae4;--track-on:#101720;--knob:#fff;--knob-on:#fff;
}
:root[data-theme=light][data-daypart=day]{
    --bg:#fff;--fg:#14161a;--dim:#33373d;--muted:#6b7078;--faint:#858c95;
    --line:#e3e5ea;--panel:#f6f7f9;--accent:#2f5bd6;--ok:#17925a;--danger:#c2504f;
    --overlay:rgba(252,252,253,.96);--track:#d7dae0;--track-on:#14161a;--knob:#fff;--knob-on:#fff;
}
:root[data-theme=light][data-daypart=evening]{
    --bg:#fffcf8;--fg:#1a1511;--dim:#3c342c;--muted:#75695c;--faint:#8d8174;
    --line:#ece3d8;--panel:#faf5ee;--accent:#6440c6;--ok:#2b8347;--danger:#bd5540;
    --overlay:rgba(255,252,248,.96);--track:#e0d6c9;--track-on:#1a1511;--knob:#fff;--knob-on:#fff;
}
:root[data-theme=light][data-daypart=night]{
    --bg:#faf6f0;--fg:#1d1712;--dim:#3f382f;--muted:#6e6355;--faint:#857b6d;
    --line:#e5dcd0;--panel:#f3ede4;--accent:#71479f;--ok:#357f4c;--danger:#ab4f39;
    --overlay:rgba(250,246,240,.96);--track:#ddd3c5;--track-on:#1d1712;--knob:#fff;--knob-on:#fff;
}

/* ============================================================
   3. INTERACTION POLISH
   Appended after the base sheet so these win on equal
   specificity. Only transform/opacity/colour are animated.
   ============================================================ */

html{scroll-behavior:smooth}

body{transition:background-color var(--t3) var(--ease),color var(--t3) var(--ease)}

/* ---------- links & focus ---------- */

a{transition:color var(--t1) var(--ease),opacity var(--t1) var(--ease)}

a:focus-visible,
button:focus-visible,
.card:focus-visible,
.row:focus-visible,
[tabindex]:focus-visible{
    outline:2px solid var(--accent);
    outline-offset:3px;
    border-radius:4px;
}

/* ---------- nav ---------- */

.nav nav a{position:relative;transition:color var(--t2) var(--ease)}
.nav nav a::after{
    content:'';
    position:absolute;
    left:0;
    right:0;
    bottom:-4px;
    height:1px;
    background:currentColor;
    transform:scaleX(0);
    transform-origin:left center;
    transition:transform var(--t3) var(--ease);
}
.nav nav a:hover::after,
.nav nav a:focus-visible::after,
.nav nav a.active::after{transform:scaleX(1)}

.lang button{transition:color var(--t1) var(--ease),transform var(--t1) var(--ease)}
.lang button:active{transform:scale(.92)}

.pill{transition:background var(--t2) var(--ease),transform var(--t1) var(--ease),box-shadow var(--t2) var(--ease)}
.pill:hover{box-shadow:0 0 0 3px rgba(91,125,251,.14)}
.pill:active{transform:scale(.94)}
.pill::after{transition:transform var(--t3) var(--ease),background var(--t2) var(--ease)}

/* ---------- sound toggle ---------- */

.mute{
    display:inline-flex;
    align-items:center;
    justify-content:center;
    width:26px;
    height:26px;
    padding:0;
    border:1px solid var(--line);
    border-radius:6px;
    background:0 0;
    color:var(--muted);
    cursor:pointer;
    transition:color var(--t1) var(--ease),border-color var(--t1) var(--ease),transform var(--t1) var(--ease);
}
.mute:hover{color:var(--fg);border-color:var(--faint)}
.mute:active{transform:scale(.92)}
.mute.is-off{color:var(--faint)}
.mute svg{display:block;width:14px;height:14px}
.mute .wave,
.mute .slash{transform-origin:center;transition:opacity var(--t2) var(--ease),transform var(--t2) var(--ease)}
.mute .slash{opacity:0;transform:scale(.55)}
.mute.is-off .wave{opacity:0;transform:scale(.55)}
.mute.is-off .slash{opacity:1;transform:none}

/* ---------- inline link list ---------- */

.links a{transition:color var(--t1) var(--ease),transform var(--t2) var(--ease)}
.links a::before{
    display:inline-block;
    transition:transform var(--t2) var(--ease),color var(--t1) var(--ease);
}
.links a:hover{transform:translateY(-1px)}
.links a:hover::before{transform:translate(2px,-2px)}
.links a:active{transform:translateY(0)}

/* ---------- buttons / form ---------- */

.btn{transition:color var(--t1) var(--ease),border-color var(--t1) var(--ease),background-color var(--t1) var(--ease),transform var(--t1) var(--ease)}
.btn:active{transform:translateY(1px) scale(.99)}

.mini,
.list-row button{transition:color var(--t1) var(--ease),border-color var(--t1) var(--ease),transform var(--t1) var(--ease)}
.mini:active,
.list-row button:active{transform:translateY(1px)}

.form input,
.form select,
.form textarea{transition:border-color var(--t1) var(--ease),background-color var(--t1) var(--ease),box-shadow var(--t1) var(--ease)}
.form input:focus,
.form select:focus,
.form textarea:focus{box-shadow:0 0 0 3px rgba(91,125,251,.12)}

.status{transition:color var(--t2) var(--ease)}

/* ---------- live / presence dot ---------- */

.live.online::before,
.presence.is-online::before{animation:dotPulse 2.4s ease-in-out infinite}

@keyframes dotPulse{
    0%,100%{box-shadow:0 0 0 0 rgba(74,208,127,.55),0 0 8px rgba(74,208,127,.6)}
    50%{box-shadow:0 0 0 4px rgba(74,208,127,0),0 0 10px rgba(74,208,127,.75)}
}

/* ---------- stat rows ---------- */

.stat{transition:border-color var(--t2) var(--ease)}
.stat strong{transition:color var(--t2) var(--ease)}

/* ---------- project / repo rows ---------- */

.row{
    position:relative;
    transition:background-color var(--t2) var(--ease),box-shadow var(--t2) var(--ease);
}
.row:hover,
.row:focus-visible{
    background:var(--panel);
    /* Fakes 10px of horizontal bleed without touching layout. */
    box-shadow:inset 3px 0 0 var(--accent),10px 0 0 var(--panel),-10px 0 0 var(--panel);
}
.row .thumb{transition:transform var(--t2) var(--ease)}
.row:hover .thumb{transform:scale(1.07)}
.row h2{transition:color var(--t1) var(--ease),transform var(--t2) var(--ease)}
.row:hover h2{transform:translateX(3px)}

.tags span{transition:border-color var(--t1) var(--ease),color var(--t1) var(--ease)}
.row:hover .tags span{border-color:var(--faint)}

/* ---------- certificates ---------- */

.cert{transition:background-color var(--t2) var(--ease),box-shadow var(--t2) var(--ease)}
.cert:hover{
    background:var(--panel);
    box-shadow:inset 3px 0 0 var(--accent),10px 0 0 var(--panel),-10px 0 0 var(--panel);
}
.cert-badge{transition:transform var(--t3) var(--ease)}
.cert:hover .cert-badge{transform:scale(1.08) rotate(-2deg)}
.cert-verify::after{display:inline-block;transition:transform var(--t2) var(--ease)}
.cert:hover .cert-verify::after{transform:translate(2px,-2px)}

/* ---------- footer ---------- */

.foot a{transition:color var(--t1) var(--ease),transform var(--t2) var(--ease)}
.foot a:hover{transform:translateY(-1px)}

/* ============================================================
   4. GALLERY
   Source photos in this archive run from 0.55 to 1.78 aspect
   ratio. The thumbnail box is a fixed square and the photo is
   `contain`ed inside it, so nothing is ever cropped or
   stretched, grid rows line up, and there is no layout shift
   while lazy images decode.
   ============================================================ */

.grid{gap:14px}

.card{
    position:relative;
    display:flex;
    flex-direction:column;
    border-radius:8px;
    transition:border-color var(--t2) var(--ease),transform var(--t3) var(--ease),box-shadow var(--t3) var(--ease);
}
.card:hover,
.card:focus-visible{
    transform:translateY(-3px);
    box-shadow:0 10px 26px rgba(0,0,0,.28);
}
:root[data-theme=light] .card:hover,
:root[data-theme=light] .card:focus-visible{box-shadow:0 10px 26px rgba(20,22,26,.12)}
.card:active{transform:translateY(-1px)}

.card img{
    width:100%;
    height:auto;
    aspect-ratio:1/1;
    object-fit:contain;
    object-position:center;
    background:var(--bg);
    transition:opacity var(--t2) var(--ease),transform var(--t3) var(--ease);
}
.card:hover img{opacity:1;transform:scale(1.035)}

/* --- captions: stronger contrast, clearer separation --- */

.card figcaption{
    align-items:center;
    gap:10px;
    padding:11px 12px 12px;
    border-top:1px solid var(--line);
    background:var(--panel);
    color:var(--dim);
    font-size:13px;
    transition:border-color var(--t2) var(--ease),background-color var(--t2) var(--ease);
}
.card figcaption small{
    flex:0 0 auto;
    padding:2px 8px;
    border:1px solid var(--line);
    border-radius:99px;
    background:var(--bg);
    color:var(--muted);
    font:600 11px/1.6 var(--mono);
    letter-spacing:.04em;
    transition:color var(--t1) var(--ease),border-color var(--t1) var(--ease),background-color var(--t1) var(--ease);
}
.card figcaption p{
    color:var(--fg);
    font-weight:500;
    line-height:1.45;
}
.card:hover figcaption,
.card:focus-visible figcaption{border-top-color:var(--faint)}
.card:hover figcaption small,
.card:focus-visible figcaption small{color:var(--accent);border-color:var(--accent)}

.missing{transition:opacity var(--t2) var(--ease)}

/* ---------- lightbox ---------- */

.lb{
    transition:opacity var(--t3) var(--ease),visibility var(--t3) var(--ease),backdrop-filter var(--t3) var(--ease);
    -webkit-backdrop-filter:blur(0);
    backdrop-filter:blur(0);
}
.lb.open{
    -webkit-backdrop-filter:blur(3px);
    backdrop-filter:blur(3px);
}
.lb-inner{
    opacity:0;
    transform:scale(.94) translateY(10px);
    transition:opacity var(--t2) var(--ease),transform var(--t3) var(--ease-soft);
}
.lb.open .lb-inner{opacity:1;transform:none}
.lb img{transition:opacity var(--t2) var(--ease)}
.lb-bottom{
    opacity:0;
    transform:translateY(6px);
    transition:opacity var(--t2) var(--ease) 120ms,transform var(--t3) var(--ease) 120ms;
}
.lb.open .lb-bottom{opacity:1;transform:none}
.lb-close{transition:color var(--t1) var(--ease),border-color var(--t1) var(--ease),transform var(--t3) var(--ease)}
.lb-close:hover{transform:rotate(90deg)}
.lb-close:active{transform:rotate(90deg) scale(.9)}

/* ---------- page transitions ---------- */

[data-router-view]{animation:var(--t3) var(--ease) both fade}
.router-leaving [data-router-view]{
    opacity:0;
    transform:translateY(-6px);
    transition:opacity var(--t-route) var(--ease),transform var(--t-route) var(--ease);
}
@keyframes fade{from{opacity:0;transform:translateY(8px)}to{opacity:1;transform:none}}

/* ============================================================
   5. SCROLL REVEAL
   [data-reveal] is set by js/motion.js, which also puts
   .js-motion on <html>. Without JS nothing is ever hidden.
   ============================================================ */

.js-motion [data-reveal]{
    opacity:0;
    transform:translateY(16px);
    transition:opacity var(--t4) var(--ease),transform var(--t4) var(--ease);
}
.js-motion [data-reveal=fade]{transform:none}
.js-motion [data-reveal].is-revealed{opacity:1;transform:none}

/* ============================================================
   6. SECRET
   ============================================================ */

#secretEgg{
    position:fixed;
    inset:0;
    z-index:80;
    display:grid;
    place-items:center;
    padding:26px;
    background:var(--overlay);
    opacity:0;
    visibility:hidden;
    cursor:pointer;
    -webkit-backdrop-filter:blur(0);
    backdrop-filter:blur(0);
    transition:opacity var(--t4) var(--ease),visibility var(--t4) var(--ease),backdrop-filter var(--t4) var(--ease);
}
#secretEgg.is-on{
    opacity:1;
    visibility:visible;
    -webkit-backdrop-filter:blur(4px);
    backdrop-filter:blur(4px);
}
#secretEgg img{
    max-width:min(560px,100%);
    max-height:78vh;
    width:auto;
    border:1px solid var(--line);
    border-radius:10px;
    box-shadow:0 24px 60px rgba(0,0,0,.45);
    opacity:0;
    transform:scale(.8) rotate(-2.5deg);
    transition:opacity var(--t4) var(--ease) 60ms,transform 700ms var(--ease-soft) 60ms;
}
#secretEgg.is-on img{opacity:1;transform:none}

/* ============================================================
   7. REDUCED MOTION
   Re-stated last so nothing above can leave content invisible.
   Palettes still apply -- they just swap instantly.
   ============================================================ */

@media (prefers-reduced-motion:reduce){
    html{scroll-behavior:auto}
    *,::before,::after{animation:none!important;transition:none!important}
    .js-motion [data-reveal]{opacity:1!important;transform:none!important}
    .lb-inner,.lb-bottom{opacity:1;transform:none}
    #secretEgg.is-on img{opacity:1;transform:none}
}

/* Three photos per row, cropped edge to edge. */
[data-page=gallery] .wrap.wide{width:min(1240px,calc(100% - 64px))}
[data-page=gallery] main>h1{font-size:clamp(38px,6vw,64px);letter-spacing:-.045em;line-height:1.05;margin-bottom:18px}
[data-page=gallery] main>p{max-width:54ch}
[data-page=gallery] .stats{display:flex;flex-wrap:wrap;gap:16px 36px;margin:30px 0 18px}
[data-page=gallery] .stat{display:block;padding:0;border:0;background:none}
[data-page=gallery] .stat:first-child{border:0}
[data-page=gallery] .stat small{display:block;margin-bottom:4px;font-size:10px;letter-spacing:.08em}
[data-page=gallery] .stat strong{font-size:13px;color:var(--dim)}
#galleryGrid{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));grid-auto-rows:auto;grid-auto-flow:row;align-items:stretch;gap:0;margin-top:32px}
#galleryGrid .card{position:relative;display:block;grid-column:auto;margin:0;padding:0;min-width:0;aspect-ratio:1;border:0;border-radius:0;background:none;box-shadow:none;overflow:hidden;cursor:zoom-in;isolation:auto;transform:none;translate:none}
#galleryGrid .card img{position:absolute;inset:0;display:block;width:100%;height:100%;max-height:none;object-fit:cover;object-position:center;aspect-ratio:auto;border-radius:0;background:none;opacity:1;transition:transform .3s var(--ease),filter .3s var(--ease)}
#galleryGrid .card:hover img{transform:scale(1.04);filter:brightness(1.06);opacity:1}
#galleryGrid .card:focus-visible{outline:2px solid var(--accent);outline-offset:-3px}
#galleryGrid .card::after{display:none}
#galleryGrid .card figcaption{display:none}
#galleryGrid .card.is-missing{display:none}
#galleryGrid .empty{grid-column:1/-1}
.photo-dialog{position:fixed;inset:0;width:100%;height:100%;max-width:none;max-height:none;margin:0;padding:32px;border:0;background:var(--bg);color:var(--fg);overflow:auto}
.photo-dialog::backdrop{background:rgba(0,0,0,.88)}
.photo-dialog[open]{display:grid;place-items:center;animation:photo-open .2s var(--ease)}
.photo-dialog .photo-content{width:min(1100px,100%);display:grid;justify-items:center;gap:20px}
.photo-dialog .photo-expanded{display:block;width:auto;height:auto;max-width:100%;max-height:calc(100dvh - 180px);object-fit:contain;border-radius:2px}
.photo-dialog .photo-caption{margin:0;max-width:64ch;text-align:center;font-size:16px;color:var(--fg);line-height:1.6}
.photo-dialog .photo-controls{display:flex;align-items:center;justify-content:center;gap:22px;color:var(--muted);font:12px var(--mono)}
.photo-dialog button{border:1px solid var(--line);background:transparent;color:var(--fg);border-radius:50%;width:42px;height:42px;cursor:pointer;font-size:20px}
.photo-dialog button:hover{border-color:var(--accent);color:var(--accent)}
.photo-dialog button:focus-visible{outline:2px solid var(--accent);outline-offset:4px}
.photo-dialog .photo-close{position:absolute;top:18px;right:20px;z-index:1}
@keyframes photo-open{from{opacity:0;transform:scale(.985)}to{opacity:1;transform:none}}
@media(max-width:800px){
 [data-page=gallery] .wrap.wide{width:calc(100% - 40px)}
 .photo-dialog{padding:64px 20px 24px}
}
@media(max-width:480px){
 [data-page=gallery] .stats{gap:16px 22px}
 [data-page=gallery] .stat:last-child{flex-basis:100%}
}
@media(prefers-reduced-motion:reduce){#galleryGrid .card img,.photo-dialog[open]{transition:none;animation:none;transform:none!important}}
/* Deep ambient light; static gradients keep scrolling inexpensive. */
:root{--t1:180ms;--t2:320ms;--t3:480ms;--t4:680ms;--t-route:240ms;--glow:color-mix(in srgb,var(--accent) 22%,transparent)}
body{isolation:isolate}
body::before{content:none}
:root[data-theme=light]{--glow:color-mix(in srgb,var(--accent) 10%,transparent)}
.hl{text-shadow:0 0 6px color-mix(in srgb,var(--accent) 85%,transparent),0 0 18px color-mix(in srgb,var(--accent) 80%,transparent),0 0 40px color-mix(in srgb,var(--accent) 55%,transparent),0 0 80px color-mix(in srgb,var(--accent) 35%,transparent)}
.nav nav a.active::after{background:var(--accent);box-shadow:0 0 8px var(--accent),0 0 20px color-mix(in srgb,var(--accent) 65%,transparent),0 0 40px var(--glow)}
.links a:hover,.nav nav a:hover{text-shadow:0 0 22px var(--glow)}
.form input:focus,.form textarea:focus{box-shadow:0 0 0 3px color-mix(in srgb,var(--accent) 10%,transparent),0 8px 32px var(--glow)}
.btn:hover{background:color-mix(in srgb,var(--accent) 8%,transparent);box-shadow:0 5px 28px var(--glow)}
[data-router-view]{animation:fade 680ms var(--ease-soft) both}
.router-leaving [data-router-view]{animation:none;opacity:0;transform:translateY(-8px);transition:opacity var(--t-route) var(--ease),transform var(--t-route) var(--ease)}
@keyframes fade{from{opacity:0;transform:translateY(12px)}to{opacity:1;transform:none}}
@media(prefers-reduced-motion:reduce){html{scroll-behavior:auto}*,::before,::after{animation:none!important;transition:none!important}}

/* A fresh accent on each full load, retained across smooth navigation. */
:root[data-accent=forest][data-theme]{--accent:#49956b}
:root[data-accent=red][data-theme]{--accent:#c75a65}
:root[data-accent=blue][data-theme]{--accent:#5b7dfb}
:root[data-accent=forest][data-theme=light]{--accent:#236541}
:root[data-accent=red][data-theme=light]{--accent:#a62d3c}
:root[data-accent=blue][data-theme=light]{--accent:#2f5bd6}

:root[data-accent=orange][data-theme]{--accent:#cb793e}
:root[data-accent=orange][data-theme=light]{--accent:#9b4b18}

.message-heading{color:var(--accent);text-shadow:0 0 6px color-mix(in srgb,var(--accent) 85%,transparent),0 0 18px color-mix(in srgb,var(--accent) 80%,transparent),0 0 40px color-mix(in srgb,var(--accent) 55%,transparent),0 0 80px color-mix(in srgb,var(--accent) 35%,transparent)}

body[data-page=gallery] main>h1,body[data-page=projects] main>h1{color:var(--accent);text-shadow:0 0 6px color-mix(in srgb,var(--accent) 85%,transparent),0 0 18px color-mix(in srgb,var(--accent) 80%,transparent),0 0 40px color-mix(in srgb,var(--accent) 55%,transparent),0 0 80px color-mix(in srgb,var(--accent) 35%,transparent)}

/* Flat accent colors, preserving the gallery's neutral photographic drop shadows. */
*,::before,::after{text-shadow:none!important}
:root{--glow:transparent!important}
.hl,.message-heading{font-weight:700}
.nav nav a.active,.links a{color:var(--accent)}
.nav nav a::after,.live::before,.presence::before,.pill:hover,.btn:hover,.form input:focus,.form textarea:focus{box-shadow:none!important}
body[data-page=projects] #githubRepos .project-entry::before,body[data-page=projects] #githubRepos .project-entry::after{content:none!important;display:none!important}
body[data-page=projects] #githubRepos .project-entry{border-left:0!important;box-shadow:none!important}
body[data-page=about] main>h1{min-height:1.3em}
.greeting-readable{position:absolute!important;width:1px!important;height:1px!important;padding:0!important;margin:-1px!important;overflow:hidden!important;clip-path:inset(50%)!important;white-space:nowrap!important;border:0!important;user-select:none}
.greeting-cursor{display:inline-block;width:0;height:.9em;border-left:2px solid var(--accent);margin-left:5px;vertical-align:-.06em;animation:greeting-blink 1s steps(1,end) infinite}
@keyframes greeting-blink{0%,49%{opacity:1}50%,100%{opacity:0}}
@media(prefers-reduced-motion:reduce){.greeting-cursor{animation:none!important;opacity:1}}

main>.kicker.moving-kicker{text-transform:none}
