/* One world: a night sky. Dark only, by the owner's decision. One accent: --star (pale starlight). */
:root{
  --bg:#05060b;
  --ink:#eef1f8;
  --mute:#a4acc1;
  --faint:#8a92a9;
  --star:#bcd0ff;
  --hair:rgba(255,255,255,.08);
  --hair-hi:rgba(188,208,255,.34);
  --ease:cubic-bezier(.32,.72,0,1);
  --sans:"Geist",ui-sans-serif,system-ui,sans-serif;
  --mono:"Geist Mono",ui-monospace,monospace;
  /* Sprite palette from mascots.json. */
  --sG:#d5dbef;--sg:#9aa5c8;--sQ:#e2e6f3;--sM:#b4bcd9;--sm:#3b4261;--sW:#eef0f6;
  color-scheme:dark;
}
*{box-sizing:border-box}
html{background:var(--bg)}
body{margin:0;min-height:100dvh;background:radial-gradient(120% 70% at 50% 0%,#0b1020,var(--bg) 70%) fixed;color:var(--ink);
  font:400 15px/1.55 var(--sans);-webkit-font-smoothing:antialiased;overflow-x:hidden}

#sky{position:fixed;inset:0;width:100%;height:100%;opacity:0;pointer-events:none;transition:opacity 1.8s var(--ease)}
#sky.on{opacity:1}

#stage{position:relative;min-height:100dvh;display:flex;flex-direction:column;align-items:center;justify-content:center;
  padding:clamp(40px,6vh,72px) 24px clamp(88px,11vh,120px)}

/* Connectors: hairline curves, parent to each card. Law of Uniform Connectedness. */
#links{position:absolute;inset:0;width:100%;height:100%;overflow:visible;pointer-events:none}
#links path{fill:none}
#links .base{stroke:url(#lg);stroke-width:1;stroke-opacity:.75;stroke-dasharray:1 1;transition:stroke-opacity .5s var(--ease)}
#links .pulse{stroke:var(--pc,#e4ecff);stroke-dasharray:var(--l) 3;stroke-dashoffset:var(--l);opacity:0;transition:opacity .5s var(--ease)}
#links .tail{--l:.18;stroke-width:3;--o:.2}
#links .body{--l:.08;stroke-width:1.5;--o:.55}
#links .head{--l:.022;stroke-width:2;--o:1;stroke:color-mix(in srgb,var(--pc,#e4ecff) 45%,#fff)}
#links .on .base{stroke-opacity:1}
#links .on .tail{--o:.34}
#links .end,#links .origin{fill:#dfe7ff}
#links .end{opacity:.55;transition:opacity .5s var(--ease)}
#links .on .end{opacity:1}

/* The parent: the north star and the wordmark. The star is the one focal point (Von Restorff). */
#parent{position:relative;display:flex;flex-direction:column;align-items:center;text-align:center}
.north{position:relative;display:grid;place-items:center;width:72px;height:72px}
.north svg{position:relative;width:30px;height:30px;fill:#f7f9ff;filter:drop-shadow(0 0 4px rgba(214,226,255,.95)) drop-shadow(0 0 14px rgba(170,196,255,.6))}
.glow{position:absolute;left:50%;top:50%;width:420px;height:420px;margin:-210px;border-radius:50%;pointer-events:none;
  background:radial-gradient(circle,rgba(205,220,255,.22) 0,rgba(160,188,255,.08) 22%,rgba(140,170,255,.025) 42%,transparent 66%)}
.spikes::before,.spikes::after{content:"";position:absolute;left:50%;top:50%}
.spikes::before{width:260px;height:1px;margin:-.5px 0 0 -130px;background:linear-gradient(90deg,transparent,rgba(222,232,255,.5),transparent)}
.spikes::after{width:1px;height:150px;margin:-75px 0 0 -.5px;background:linear-gradient(180deg,transparent,rgba(222,232,255,.42),transparent)}
/* Lockup: Nor left of the wordmark. One Nor pixel = 0.04em, rounded to whole px so each pixel stays crisp.
   The body (13 px rows) is 0.52em, about the x-height (0.53em). The bottom tip sits on the baseline. */
.wordmark .nor{--u:round(nearest,.04em,1px);flex:none;width:calc(var(--u) * 16);height:calc(var(--u) * 16);margin-right:.2em;fill:currentColor;overflow:visible}
.wordmark .nor .tip{fill:#7aa2f7}
.wordmark .nor .lids{visibility:hidden}
.wordmark .nor.blink .lids{visibility:visible}
.wordmark{display:flex;align-items:baseline;margin:18px 0 0;padding-bottom:.06em;font:500 clamp(54px,min(9vw,13.5vh),128px)/.95 var(--sans);letter-spacing:-.058em;color:var(--ink)}

/* Cards: double bezel. Outer shell 28 px radius, 6 px padding; inner core 22 px radius (concentric).
   Figure-ground: the core is a lighter cool slate than the sky; the shell has a top-lit hairline,
   a dark outer ring and a dark halo, so trails stop short of the edge. Each card has one accent (--acc). */
#projects{display:flex;flex-wrap:wrap;justify-content:center;gap:48px 80px;width:100%;max-width:1000px;margin:clamp(72px,12vh,124px) 0 0;padding:0;list-style:none}
#projects li{position:relative}
a.node,.maker{--acc:var(--star);--spring:cubic-bezier(.32,.72,0,1);
  border:1px solid transparent;
  background:linear-gradient(#0c0f19,#0c0f19) padding-box,
    linear-gradient(180deg,rgba(226,234,255,.42),rgba(226,234,255,.12) 28%,rgba(226,234,255,.07) 70%,rgba(226,234,255,.12)) border-box;
  box-shadow:0 0 0 1px rgba(2,3,7,.75),0 0 46px 14px rgba(4,5,10,.62),0 44px 80px -40px rgba(0,0,0,.95)}
a.node{position:relative;display:block;width:360px;padding:6px;border-radius:28px;
  color:var(--ink);text-decoration:none;touch-action:none;user-select:none;-webkit-user-drag:none;-webkit-tap-highlight-color:transparent;
  will-change:translate;transition:translate .38s var(--spring)}
/* The accent edge: a short line of the card's color on the top hairline. */
a.node::before,.maker::before{content:"";position:absolute;top:-1px;left:18%;right:18%;height:1.5px;border-radius:1px;pointer-events:none;
  background:linear-gradient(90deg,transparent,var(--acc) 30%,var(--acc) 70%,transparent);box-shadow:0 0 12px color-mix(in srgb,var(--acc) 55%,transparent);opacity:.85;transform:scaleX(.86);
  transition:transform .38s var(--spring),opacity .38s var(--spring)}
/* The hover glow fades by opacity only. */
a.node::after,.maker::after{content:"";position:absolute;inset:-1px;border-radius:inherit;pointer-events:none;opacity:0;transition:opacity .38s var(--spring);
  box-shadow:0 0 0 1px color-mix(in srgb,var(--acc) 34%,transparent),0 36px 90px -34px color-mix(in srgb,var(--acc) 46%,transparent),0 0 36px -6px color-mix(in srgb,var(--acc) 22%,transparent)}
.core{position:relative;display:block;overflow:hidden;border-radius:22px;background:linear-gradient(180deg,#1b2030,#141826);
  box-shadow:inset 0 1px 0 rgba(255,255,255,.1),inset 0 0 0 1px rgba(255,255,255,.035);transition:transform .38s var(--spring)}
a.node .core{padding:22px 22px 20px}
.core::before{content:"";position:absolute;inset:0;border-radius:inherit;opacity:0;pointer-events:none;transition:opacity .38s var(--spring);
  background:radial-gradient(280px circle at var(--mx,50%) var(--my,0),color-mix(in srgb,var(--acc) 10%,transparent),transparent 65%)}
.sheen{position:absolute;inset:-10% -60%;pointer-events:none;transform:translateX(-120%);
  background:linear-gradient(100deg,transparent 42%,rgba(235,242,255,.06) 50%,transparent 58%)}
.top{display:flex;align-items:flex-start;justify-content:space-between}
.tile{position:relative;display:grid;place-items:center;width:60px;height:60px;border-radius:16px;
  background:radial-gradient(circle at 50% 72%,color-mix(in srgb,var(--acc) 26%,transparent),transparent 70%),rgba(255,255,255,.035);
  box-shadow:inset 0 0 0 1px color-mix(in srgb,var(--acc) 20%,rgba(255,255,255,.06)),inset 0 1px 0 rgba(255,255,255,.08)}
.tile::after{content:"";position:absolute;inset:-14px;border-radius:24px;pointer-events:none;opacity:0;transition:opacity .38s var(--spring);
  background:radial-gradient(circle,color-mix(in srgb,var(--acc) 26%,transparent),transparent 66%)}
.sprite{position:relative;display:block}
.go{display:grid;place-items:center;flex:none;width:34px;height:34px;border-radius:50%;background:rgba(255,255,255,.06);color:var(--mute);
  transition:transform .38s var(--spring),background-color .38s var(--spring),color .38s var(--spring)}
.title{display:block;margin-top:26px;font:600 24px/1.1 var(--sans);letter-spacing:-.03em}
.line{display:block;margin-top:10px;color:var(--mute);font-size:15px;line-height:1.5;text-wrap:pretty}
.tags{display:flex;gap:18px;margin-top:20px;padding-top:16px;border-top:1px solid rgba(255,255,255,.07);color:var(--faint);
  font:500 11px/1 var(--mono);letter-spacing:.08em;text-transform:uppercase}
.sprite .f1,.flip .sprite .f0{display:none}
.flip .sprite .f1{display:inline}
.cG{fill:var(--sG)}.cg{fill:var(--sg)}.cQ{fill:var(--sQ)}.cM{fill:var(--sM)}.cm{fill:var(--sm)}.cW{fill:var(--sW)}

a.node.hot{translate:0 -6px}
a.node.hot::before,.maker:hover::before,.maker:focus-visible::before{opacity:1;transform:scaleX(1)}
a.node.hot::after,.maker:hover::after,.maker:focus-visible::after{opacity:1}
a.node.hot .core::before,a.node.hot .tile::after{opacity:1}
a.node.hot .sheen{transform:translateX(120%);transition:transform .38s var(--spring)}
a.node.hot .go,.maker:hover .go,.maker:focus-visible .go{transform:translate(2px,-2px);background:color-mix(in srgb,var(--acc) 20%,transparent);color:var(--acc)}
a.node:active .core{transform:scale(.988)}
a.node.drag{cursor:grabbing}
a.node:focus-visible,.maker:focus-visible{outline:2px solid var(--star);outline-offset:4px}

/* Made-by card: the same card language, quieter, with the page accent. */
.maker{position:fixed;right:calc(24px + env(safe-area-inset-right));bottom:calc(24px + env(safe-area-inset-bottom));z-index:2;display:block;width:280px;padding:5px;border-radius:22px;
  color:var(--ink);text-decoration:none;transition:translate .38s var(--spring)}
.maker .core{display:flex;align-items:center;gap:12px;padding:9px 10px 9px 9px;border-radius:17px}
.maker img{display:block;flex:none;width:40px;height:40px;border-radius:12px;filter:grayscale(1);transition:filter .38s var(--spring)}
.who{display:flex;flex:1;flex-direction:column;gap:1px;min-width:0}
.nm{font:500 14px/1.3 var(--sans)}
.dom{color:var(--mute);font:400 12px/1.3 var(--mono)}
.maker .go{width:30px;height:30px}
.maker:hover,.maker:focus-visible{translate:0 -2px}
.maker:hover img,.maker:focus-visible img{filter:none}

/* Entrance: the star, the wordmark, the line, then the connectors, then the cards. */
@media (prefers-reduced-motion:no-preference){
  .north{animation:rise 1.4s var(--ease) both}
  .glow{animation:breathe 7s cubic-bezier(.45,0,.55,1) 1.4s infinite alternate}
  .wordmark{animation:rise 1.3s var(--ease) .15s both}
  #links .base{animation:draw 1.4s var(--ease) .55s backwards}
  #links .origin,#links .end{animation:fade 1s var(--ease) .55s backwards}
  #links .pulse{opacity:var(--o);animation:5.6s cubic-bezier(.6,0,.3,1) var(--d) infinite}
  #links .tail{animation-name:pt}
  #links .body{animation-name:pb}
  #links .head{animation-name:ph}
  #projects li{animation:rise 1.2s var(--ease) calc(.85s + var(--i) * .14s) both}
  .maker{animation:rise 1.2s var(--ease) 1.3s both}
}
@keyframes rise{from{opacity:0;transform:translateY(20px);filter:blur(10px)}}
@keyframes fade{from{opacity:0}}
@keyframes draw{from{stroke-dashoffset:1}}
/* Each layer moves by 1.2 path lengths, so the three leading edges stay together. Offset = --l, then --l - 1.2. */
@keyframes pt{0%{stroke-dashoffset:.18}55%,100%{stroke-dashoffset:-1.02}}
@keyframes pb{0%{stroke-dashoffset:.08}55%,100%{stroke-dashoffset:-1.12}}
@keyframes ph{0%{stroke-dashoffset:.022}55%,100%{stroke-dashoffset:-1.178}}
@keyframes breathe{from{transform:scale(.9);opacity:.75}to{transform:scale(1.08);opacity:1}}

/* Phone: one column, 16 px gutters, the made-by card in flow. Drag is off. */
@media (max-width:640px){
  #stage{min-height:0;justify-content:flex-start;padding:48px 16px 24px}
  .north{width:60px;height:60px}
  .glow{width:300px;height:300px;margin:-150px}
  .spikes::before{width:180px;margin-left:-90px}
  #projects{flex-direction:column;flex-wrap:nowrap;align-items:stretch;gap:24px;margin-top:64px}
  a.node{width:100%;touch-action:auto}
  .maker{position:relative;right:auto;bottom:auto;width:auto;margin:32px 16px calc(24px + env(safe-area-inset-bottom))}
}
@media (prefers-reduced-motion:reduce){
  a.node.hot,.maker:hover,.maker:focus-visible{translate:none}
  .sheen{display:none}
}
