/* Nicole Chou — Portfolio: shared design system stylesheet.
   Every page on this site (index.html, style-guide.html, and any future
   page) links this one file — see DESIGN-SYSTEM.md for the written
   reference and style-guide.html for a live, visual one. Never fork this
   file per-page: a token or component changed here changes everywhere,
   which is the whole point. */
:root{
  /* light values = the Figma file's Color variables */
  --paper:#fcfcfc;
  --paper-2:#f8f8f6;
  --ink:#111214;
  --ink-2:#3c3f43;
  --ink-3:#52545b;
  --rule:#CBCBC4;
  --rule-soft:#DEDED8;
  --edge:#8A8A83;          /* ruler ticks + control borders: >=3:1 non-text */
  --dot:rgba(22,23,26,.16);
  --dot-hot:rgba(192,62,15,.62);
  --accent:#C03E0F;
  --select:#2E5BEA;
  --on-select:#FFFFFF;
  --chrome:rgba(246,246,243,.82);   /* translucent glass for .bar + the ruler frame -- kept separate from --menu-bg, which the mobile menu panel needs fully opaque */
  --menu-bg:#fcfcfc;
  --card:#FFFFFF;
  /* fixed (not theme-swapped) so the marquee tag's white-on-blue contrast
     ratio -- ~5.5:1, passing WCAG AA for normal text -- holds in both
     themes; --select's dark value (#7391FF) is a light tint meant to sit
     ON a dark page, not to carry white text itself, so reusing it here
     would fail contrast in dark mode */
  --tag-blue:#2E5BEA;
  /* Figma's own component purple -- the Component Frame outline/label;
     fixed in both themes like the tool it references */
  --component:#9747FF;

  --gutter:24px;
  --step:8px;
  --maxw:1268px;   /* 1156px content column (Figma) + main's 56px side padding */

  /* one superfamily: the pixel face is a Geist sibling, so it pairs by
     construction, and the sans/mono are drawn for UI legibility */
  --mono:"Geist Mono",ui-monospace,SFMono-Regular,Menlo,monospace;
  --body:"Geist",system-ui,-apple-system,"Segoe UI",sans-serif;
  --display:"Geist",system-ui,-apple-system,sans-serif;
  --name:"Geist Pixel","Geist",system-ui,sans-serif;
}
@media (prefers-color-scheme:dark){
  :root:not([data-theme="light"]){
    --paper:#131417;
    --paper-2:#191A1E;
    --ink:#E9E9E4;
    --ink-2:#A4A6AE;
    --ink-3:#82858D;
    --rule:#34363C;
    --rule-soft:#26282D;
    --edge:#64676F;
    --dot:rgba(233,233,228,.10);
    --dot-hot:rgba(255,122,64,.55);
    --accent:#FF7A40;
    --select:#7391FF;
    --on-select:#0E1220;
    --chrome:rgba(25,26,30,.82);
    --menu-bg:#191A1E;
    --card:#1B1C20;
  }
}
:root[data-theme="dark"]{
  --paper:#131417;
  --paper-2:#191A1E;
  --ink:#E9E9E4;
  --ink-2:#A4A6AE;
  --ink-3:#82858D;
  --rule:#34363C;
  --rule-soft:#26282D;
  --edge:#64676F;
  --dot:rgba(233,233,228,.13);
  --dot-hot:rgba(255,122,64,.68);
  --accent:#FF7A40;
  --select:#7391FF;
  --on-select:#0E1220;
  --chrome:rgba(25,26,30,.82);
  --menu-bg:#191A1E;
  --card:#1B1C20;
}

*{box-sizing:border-box}
html{scroll-behavior:smooth;-webkit-text-size-adjust:100%;text-size-adjust:100%}   /* stop mobile Safari/Chrome from auto-boosting small text -- render exactly what's specified */
body{
  background:var(--paper);
  color:var(--ink);
  font-family:var(--body);
  font-size:16px;
  line-height:1.55;
  margin:0;
  padding:calc(46px + var(--gutter)) 0 0 var(--gutter);   /* bar height + the ruler frame now below it */
  overflow-x:hidden;
  -webkit-font-smoothing:antialiased;
}

/* ---------- canvas layers ---------- */
/* the ruler frame now sits below the full-width bar, so the rulers are
   offset by its 46px height. The dot canvas covers the whole viewport so the
   homepage intro's grid fills it edge to edge; the script anchors the grid to
   the ruler's origin and stops drawing under the chrome once it's shown. */
#dots{
  position:fixed; inset:0;
  width:100%; height:100%;
  z-index:0; pointer-events:none;
}
.chrome{position:fixed;z-index:60;pointer-events:none}
/* explicit CSS size: a canvas won't stretch to `right`/`bottom` on its own,
   so without this the strip stops short of the edge after any resize */
#ruler-x{top:46px;left:var(--gutter);right:0;height:var(--gutter);width:calc(100% - var(--gutter));border-bottom:1px solid var(--rule)}
#ruler-y{top:calc(46px + var(--gutter));left:0;bottom:0;width:var(--gutter);height:calc(100% - 46px - var(--gutter));border-right:1px solid var(--rule)}
.corner{
  top:46px;left:0;width:var(--gutter);height:var(--gutter);
  border-right:1px solid var(--rule);border-bottom:1px solid var(--rule);
  display:grid;place-items:center;
  font-family:var(--mono);font-size:8px;color:var(--ink-3);letter-spacing:.02em;
}

/* Figma-style custom cursor: only where a real mouse is in play (a CSS
   cursor:url() can't carry live-updating text, so this is JS-positioned
   instead, and only on devices that actually have a pointer worth
   replacing -- touchscreens never get cursor:none or the chip at all).
   Hidden unconditionally here first: the styles that turn it into a
   fixed, floating chip only exist inside the media query below, so
   without this a touch device would render it as a plain static block
   sitting in normal document flow. */
.cursor-chip{display:none}
@media (hover:hover) and (pointer:fine){
  /* !important: .burger/.motion-track set their own cursor:pointer, a
     class selector that otherwise beats the universal selector's
     specificity and would leave the native pointer showing on those two.
     Scoped to body:has(.cursor-chip) rather than a bare *: this stylesheet
     is shared by every page, but the chip + its tracking script only ship
     with index.html -- without this scoping, any other page that links
     styles.css (style-guide.html included) would hide the OS cursor with
     nothing drawn in its place. Pages that don't include the chip element
     keep their normal cursor automatically. */
  body:has(.cursor-chip) *{cursor:none!important}
  .cursor-chip{
    display:block;
    position:fixed;top:0;left:0;z-index:9999;pointer-events:none;
    opacity:0;transition:opacity .15s ease;
    will-change:transform;
  }
  .cursor-chip.is-visible{opacity:1}
  .cursor-chip svg{display:block;overflow:visible}
  .cursor-chip path{
    fill:var(--tag-blue);stroke:#FFFFFF;stroke-width:1.6;
    stroke-linejoin:round;stroke-linecap:round;
  }
  .cursor-coords{
    position:absolute;left:16px;top:17px;
    display:flex;flex-direction:column;gap:1px;
    font-family:var(--mono);font-size:10px;letter-spacing:.02em;
    color:var(--tag-blue);
    white-space:nowrap;font-variant-numeric:tabular-nums;
  }
}

/* ---------- nav ---------- */
.bar{
  position:fixed;top:0;left:0;right:0;height:46px;z-index:50;
  display:flex;align-items:center;justify-content:flex-start;
  gap:clamp(14px,2.4vw,26px);padding:0 20px;
  border-bottom:1px solid var(--rule-soft);
}
/* the toolbar and the ruler frame are the same pane of glass */
.bar,#ruler-x,#ruler-y,.corner{
  -webkit-backdrop-filter:blur(8px);
  backdrop-filter:blur(8px);
}
.bar,#ruler-x,#ruler-y,.corner{background:var(--chrome)}
/* while the mobile menu is open, the bar it hangs from matches the panel
   below it (solid) instead of staying at the translucent resting state */
#bar[data-open="true"]{background:var(--menu-bg)}
.logo{
  width:32px;height:32px;flex:none;display:block;
  transition:transform .22s cubic-bezier(.4,0,.2,1);
}
.logo img{display:block;width:100%;height:100%;object-fit:contain}
.logo:hover{transform:rotate(-9deg) scale(1.07)}
.logo:active{transform:rotate(-9deg) scale(.93);transition-duration:.08s}
.logo:focus-visible{outline:2px solid var(--accent);outline-offset:3px}
.menu,.menu-panel{display:contents}
/* centred on the bar itself, so the wide toggle on the right doesn't shove
   the links off-centre the way space-between would */
.nav{
  display:flex;gap:16px;   /* + each link's 4px side padding = Figma's 24px */
  position:absolute;left:50%;top:50%;transform:translate(-50%,-50%);
}
/* Lucide "menu" icon, no label; its three lines fold into an X while open */
.burger{
  display:none;
  margin-left:auto;margin-right:-14px;
  align-items:center;justify-content:center;
  padding:14px;   /* 44x44 hit target around the 16px icon */
  background:none;border:0;cursor:pointer;
  color:var(--ink-2);
}
.burger-icon{display:block;width:16px;height:16px;overflow:visible}
.burger-icon line{
  transform-box:fill-box;transform-origin:center;
  transition:transform .22s cubic-bezier(.4,0,.2,1),opacity .15s ease;
}
#bar[data-open="true"] .burger-icon line:nth-child(1){transform:translateY(7px) rotate(45deg)}
#bar[data-open="true"] .burger-icon line:nth-child(2){opacity:0}
#bar[data-open="true"] .burger-icon line:nth-child(3){transform:translateY(-7px) rotate(-45deg)}
.burger:focus-visible{outline:2px solid var(--accent);outline-offset:2px}
/* .motion is a plain (non-interactive) row now -- only .motion-track,
   the switch itself, responds to clicks/taps. The label is connected for
   accessible naming via aria-labelledby, not by wrapping it in a control,
   so clicking the text does nothing. */
.motion{
  margin-left:auto;
  display:flex;align-items:center;gap:9px;
  font-family:var(--mono);font-size:12px;letter-spacing:.08em;text-transform:uppercase;
  color:var(--ink-3);
}
/* the actual hit target: 44x44 (meets WCAG 2.5.5's AAA target size, well
   past 2.5.8's 24x24 AA minimum) even though the visible pill stays small
   -- negative margin pulls the layout footprint back to the pill's own
   26x14 so the bigger invisible area doesn't widen the row or the gap
   before the label */
.motion-track{
  appearance:none;-webkit-appearance:none;
  background:none;border:0;padding:0;cursor:pointer;
  flex:none;
  width:44px;height:44px;margin:-15px -9px;
  display:flex;align-items:center;justify-content:center;
}
.motion-track:focus-visible{outline:2px solid var(--accent);outline-offset:3px}
.motion-pill{
  display:block;
  width:26px;height:14px;position:relative;border-radius:7px;
  border:1px solid var(--edge);background:var(--paper);
  transition:border-color .18s ease;
}
.motion-pill::after{
  /* top:50% + translateY(-50%) rather than a fixed top offset: the box
     model here is symmetric either way (1.5px top/bottom against the
     padding-box), but that's a sub-pixel value, and plain top/left
     layout positioning can round to a whole device pixel asymmetrically
     depending on the browser -- a transform is resolved at the
     compositing stage with real sub-pixel precision, so it can't drift
     off-centre the same way. Keep both transforms combined below (the
     checked-state slide adds translateX to the same declaration) --
     a second, separate transform would replace this one, not add to it. */
  content:"";position:absolute;top:50%;left:1.5px;width:9px;height:9px;border-radius:50%;
  transform:translateY(-50%);
  background:var(--ink-3);transition:transform .2s cubic-bezier(.4,0,.2,1),background .18s ease;
}
.motion:has(.motion-track[aria-checked="true"]){color:var(--accent)}
.motion-track[aria-checked="true"] .motion-pill{border-color:var(--accent)}
.motion-track[aria-checked="true"] .motion-pill::after{transform:translateY(-50%) translateX(11px);background:var(--accent)}
.nav a{
  font-family:var(--mono);font-weight:500;font-size:12px;letter-spacing:.08em;text-transform:uppercase;
  color:var(--ink-2);text-decoration:none;padding:6px 4px;position:relative;
  transition:color .18s ease;
}
.nav a::after{
  content:"";position:absolute;left:0;right:0;bottom:1px;height:1px;
  background:var(--accent);transform:scaleX(0);transform-origin:left;
  transition:transform .22s cubic-bezier(.4,0,.2,1);
}
.nav a:hover,.nav a:focus-visible{color:var(--ink)}
.nav a:hover::after,.nav a:focus-visible::after{transform:scaleX(1)}

/* Text links everywhere get the nav's hover underline: a 1px line that
   grows from the left with the same timing. Drawn as a background rather
   than the nav's absolutely-positioned ::after so it follows a link that
   wraps onto a second line; it takes the link's own colour. Add any new
   inline-link context to this list rather than giving it its own hover. */
.about-prose a,.spec a,.back-link,.cs-facts dd a,.cs-cite a,.cs-footer a{
  text-decoration:none;
  background:linear-gradient(currentColor,currentColor) 0 100% / 0 1px no-repeat;
  transition:background-size .22s cubic-bezier(.4,0,.2,1);
}
:is(.about-prose a,.spec a,.back-link,.cs-facts dd a,.cs-cite a,.cs-footer a):is(:hover,:focus-visible){background-size:100% 1px}
a:focus-visible{outline:2px solid var(--accent);outline-offset:3px}

/* ---------- shell ---------- */
main{position:relative;z-index:10;max-width:var(--maxw);margin:0 auto;padding:0 clamp(20px,4vw,56px)}
section{scroll-margin-top:calc(var(--gutter) + 46px + 24px)}

/* ---------- selection chrome ---------- */
.sel{position:relative}
.sel-box{
  position:absolute;inset:-22px 0;   /* hugs the name horizontally so its outer edges land on the same margin as #work/#about */
  border:1px solid var(--select);
  opacity:0;transition:opacity .2s ease;pointer-events:none;
}
.sel-box i{
  position:absolute;width:6px;height:6px;background:var(--paper-2);
  border:1px solid var(--select);
}
.sel-box i:nth-child(1){left:-3.5px;top:-3.5px}
.sel-box i:nth-child(2){right:-3.5px;top:-3.5px}
.sel-box i:nth-child(3){left:-3.5px;bottom:-3.5px}
.sel-box i:nth-child(4){right:-3.5px;bottom:-3.5px}
.sel-dim{
  position:absolute;left:50%;bottom:-11px;transform:translate(-50%,100%);
  white-space:nowrap;
  background:var(--select);color:var(--on-select);
  font-family:var(--mono);font-size:9px;letter-spacing:.02em;
  padding:2px 6px;white-space:nowrap;font-variant-numeric:tabular-nums;
}

/* ---------- hero ---------- */
/* Figma "Hero slot": the name block is centred with equal space above and
   below, in a hero that fills the first screen. .hero is the query container
   the name's size is measured against. */
.hero{
  container-type:inline-size;
  /* always fills the first screen below the bar + ruler. svh is the safe
     floor (address bar expanded), dvh tracks the real visible area on iOS,
     and --vh100 (window.innerHeight, set by index.html's build()) is the
     most reliable once the script has run; the padding stays as the minimum */
  min-height:calc(100vh - 46px - var(--gutter));
  min-height:calc(100svh - 46px - var(--gutter));
  min-height:calc(100dvh - 46px - var(--gutter));
  min-height:calc(var(--vh100, 100dvh) - 46px - var(--gutter));
  display:flex;flex-direction:column;align-items:center;
  justify-content:center;
  justify-content:safe center;   /* short viewports: fall back to start rather than overflowing both edges */
  padding:clamp(60px,8.5vw,122px) 0;
  position:relative;
  /* the name's size, which everything else in the hero is placed from.
     Scales with the viewport at Figma's ratio -- 180px in the 1416px-wide
     hero slot at 1440 (the name spans ~83% of it) -- with no fixed cap, so
     it fills the screen at any width. The second term only kicks in on
     short, very wide windows, keeping the whole hero within one screen
     (1.686em = tag room + name line + gap; 152px = lede + breathing room). */
  --nfs:min(calc((100vw - var(--gutter)) * .1271), calc((var(--vh100, 100dvh) - 46px - var(--gutter) - 152px) / 1.686));
}
/* the name plus its tag; shrink-wraps the name so the tag and the lede can
   be placed against the name's own width, like Figma's frame. It may run a
   little wider than main's content column (as in Figma at 1440): the hero
   centres it, so it overflows evenly into the side margins. */
.hero-frame{display:flex;flex-direction:column;align-items:stretch;gap:calc(var(--nfs) * .106)}
.hero .sel{padding-top:calc(var(--nfs) * .28)}   /* room for the tag, which rides up over the name's top edge */
/* Display/Hero Name: 180px at 1440, fluid (see --nfs) */
h1.name{
  margin:0;
  font-family:var(--name);
  font-weight:400;               /* Geist Pixel is single-weight */
  text-transform:uppercase;
  font-size:var(--nfs);          /* the name measures ~6.54em */
  line-height:1.3;
  letter-spacing:0;
  white-space:nowrap;
  text-align:center;   /* on mobile the two stacked words rarely render the same
                           width -- centering keeps each line's gaps even */
  color:var(--ink);
}
/* a marquee tag, not a die-cut sticker: solid fill in the same blue as
   every other marquee's border, bold white text. Fixed --tag-blue rather
   than --select, since --select's dark value can't carry white text at AA
   contrast -- see the --tag-blue definition. Mono/Tag. */
.sticker{
  position:absolute;z-index:3;margin:0;
  /* centred where Figma puts it: 1.364em in from the name's right edge and
     .092em above its top (em = the name's size, so it tracks the name) */
  top:calc(var(--nfs) * .188);left:calc(100% - var(--nfs) * 1.364);
  transform:translate(-50%,-50%) rotate(5deg);
  font-family:var(--mono);
  font-size:22px;line-height:1;font-weight:700;
  letter-spacing:.1em;text-transform:uppercase;
  color:#FFFFFF;
  background:var(--tag-blue);
  border:1.5px solid var(--tag-blue);
  padding:9px 17px;
  white-space:nowrap;
}
/* corner handles, same idea as .sel-box/.tbox's, so this reads as a
   marquee too -- paper-coloured fill with a border in the same blue */
.sticker i{
  position:absolute;width:6px;height:6px;
  background:var(--paper);border:1.5px solid var(--tag-blue);
}
.sticker i:nth-child(1){left:-3.5px;top:-3.5px}
.sticker i:nth-child(2){right:-3.5px;top:-3.5px}
.sticker i:nth-child(3){left:-3.5px;bottom:-3.5px}
.sticker i:nth-child(4){right:-3.5px;bottom:-3.5px}
/* the lede's frame, tilted opposite the tag; starts 5.44% in from the
   name's left edge (64px of 1177 at 1440), where Figma places it */
.tlayer{
  position:relative;
  align-self:flex-start;
  width:max-content;      /* hug the line(s) so the box wraps the text, not the column */
  max-width:100%;
  margin-left:5.44%;
  padding:14px 24px;
  transform:rotate(-1.8deg);
  transform-origin:50% 50%;   /* tilt about its centre, as Figma rotates it */
}
.tbox{
  position:absolute;inset:-14px 0;   /* Figma's desktop marquee stands 14px proud of the text box, top and bottom */
  border:1.5px solid var(--select);
  pointer-events:none;
}
.tbox i{
  position:absolute;width:7px;height:7px;
  background:var(--paper);border:1.5px solid var(--select);
}
.tbox i:nth-child(1){left:-4.25px;top:-4.25px}
.tbox i:nth-child(2){right:-4.25px;top:-4.25px}
.tbox i:nth-child(3){left:-4.25px;bottom:-4.25px}
.tbox i:nth-child(4){right:-4.25px;bottom:-4.25px}
/* Display/Lede */
.lede{
  margin:0;
  font-family:var(--mono);font-weight:400;
  font-size:20px;line-height:1.35;
  color:var(--ink-2);
  text-wrap:pretty;
}

/* ---------- section head (Section Head, Type=Home) ---------- */
.head{
  display:flex;align-items:center;justify-content:space-between;gap:clamp(12px,2vw,22px);
  padding-bottom:16px;border-bottom:1px solid var(--rule);margin-bottom:40px;flex-wrap:wrap;
}
/* Display/Section Head */
h2{
  font-family:var(--name);
  font-weight:400;                 /* Geist Pixel is single-weight */
  text-transform:uppercase;
  font-size:28px;
  letter-spacing:0;line-height:1;margin:0;color:var(--ink);
}
.head-note{font-family:var(--mono);font-size:10px;letter-spacing:.08em;text-transform:uppercase;color:var(--ink-3)}

/* ---------- sections ---------- */
#work{padding:80px 0}
#playground{padding:0 0 80px}
#about{padding:0 0 40px}

/* ---------- card (Figma "Work Card") ---------- */
/* Shared by the homepage grid and each case study's "See more work". */
.grid{display:grid;grid-template-columns:1fr;gap:24px}
@media (min-width:700px){ .grid{grid-template-columns:1fr 1fr} }
.card{position:relative;display:flex}
.card-body{
  position:relative;flex:1;
  display:flex;flex-direction:column;gap:16px;
  padding:20px;
  background:var(--card);
  border:1px solid var(--rule-soft);
  transition:border-color .18s ease;
}
.thumb{aspect-ratio:1.52;overflow:hidden}
.thumb img{display:block;width:100%;height:100%;object-fit:cover}
.card-text{display:flex;flex-direction:column;gap:8px}
/* Body/Card Title */
h3{
  font-family:var(--display);
  font-variation-settings:"wght" 600;
  font-weight:600;font-size:18px;letter-spacing:-.015em;
  margin:0;line-height:1.28;color:var(--ink);
}
h3 a{color:inherit;text-decoration:none}
.card h3 a::after{content:"";position:absolute;inset:-10px;z-index:1}
/* Body/Card Copy */
.card-text p{margin:0;font-size:14px;line-height:1.5;color:var(--ink-2)}
/* Mono/Card Meta: date left, role right */
.card .meta{
  margin:auto 0 0;padding-top:16px;border-top:1px solid var(--rule-soft);
  display:flex;justify-content:space-between;align-items:center;gap:12px;
  font-family:var(--mono);font-weight:500;font-size:11px;line-height:1;
  letter-spacing:.05em;text-transform:uppercase;color:var(--ink-3);
}
/* hover only where a real pointer can hover — otherwise a tap on a touch
   screen leaves the card stuck in its hover state until you tap elsewhere */
@media (hover:hover){
  .card:hover .card-body{border-color:var(--rule)}
  .card:hover .sel-box{opacity:1}
}
/* :active covers the touch case: browsers drop it the moment a touch ends
   or turns into a scroll, so it can't get stuck the way :hover can */
.card:active .card-body{border-color:var(--rule)}
.card:active .sel-box{opacity:1}
/* keyboard focus gets it too; :focus-visible so a touch tap doesn't */
.card:has(:focus-visible) .card-body{border-color:var(--rule)}
.card:has(:focus-visible) .sel-box{opacity:1}
.card .sel-box{inset:-10px}
.card .sel-box i{background:var(--paper)}

/* ---------- about ---------- */
.about-body{display:flex;align-items:center;justify-content:center;gap:64px;padding-bottom:8px}
.about-prose{flex:0 1 580px;display:flex;flex-direction:column;gap:16px}
.about-prose p{margin:0;color:var(--ink)}
.about-prose a{color:var(--select)}
/* "Currently obsessed with:" + the pixel icons; the list wraps as one unit */
.obsessions{display:flex;flex-wrap:wrap;align-items:center;gap:8px}
.obs-list{display:inline-flex;flex-wrap:wrap;align-items:center;gap:4px}
.obs{display:inline-flex;align-items:center;gap:6px}
.obs img{width:20px;height:20px;flex:none}

/* Component Frame: an image framed like a selected Figma component --
   purple outline, white corner handles, component icon + name above */
.cframe{flex:none;width:366px;margin:0;display:flex;flex-direction:column;gap:6px;transform:rotate(-2.44deg)}
.cframe-label{
  display:flex;align-items:center;gap:4px;
  font-family:var(--mono);font-weight:500;font-size:12px;line-height:16px;letter-spacing:.06em;
  color:var(--component);
}
.cframe-label svg{width:14px;height:14px;flex:none}
.cframe-img{position:relative;aspect-ratio:366/337;outline:1px solid var(--component)}
.cframe-img img{display:block;width:100%;height:100%;object-fit:cover}
.cframe-img i{
  position:absolute;width:7px;height:7px;
  background:var(--paper);border:1px solid var(--component);
}
.cframe-img i:nth-of-type(1){left:-3.5px;top:-3.5px}
.cframe-img i:nth-of-type(2){right:-3.5px;top:-3.5px}
.cframe-img i:nth-of-type(3){left:-3.5px;bottom:-3.5px}
.cframe-img i:nth-of-type(4){right:-3.5px;bottom:-3.5px}

/* legacy: style-guide.html's colophon list */
.spec{border-top:1px solid var(--rule);padding-top:12px}
.spec dl{margin:0;display:grid;grid-template-columns:auto 1fr;gap:7px 18px;
  font-family:var(--mono);font-size:10px;letter-spacing:.04em;font-variant-numeric:tabular-nums}
.spec dt{color:var(--ink-3);text-transform:uppercase}
.spec dd{margin:0;color:var(--ink-2)}
.spec a{color:var(--accent)}

/* ---------- footer (Footer, Page=Home) ---------- */
footer{
  border-top:1px solid var(--rule);padding:20px 0 30px;
  display:flex;justify-content:space-between;gap:16px;flex-wrap:wrap;
  font-family:var(--mono);font-size:9.5px;letter-spacing:.06em;text-transform:uppercase;color:var(--ink-3);
}
.site-footer{
  display:grid;grid-template-columns:1fr;gap:16px;
  padding:20px 0 24px;
  font-weight:500;text-transform:none;
}
.site-footer > p{margin:0}
/* Mono/Label */
.foot-name{font-size:12px;line-height:16px;letter-spacing:.06em}
/* Mono/Head Note */
.foot-note,.foot-made{font-size:10px;line-height:10px;letter-spacing:.08em;text-transform:uppercase}
@media (min-width:700px){
  .site-footer{
    grid-template-columns:1fr 1fr;
    grid-template-areas:"name made" "clock updated";
    gap:8px 16px;align-items:center;
    padding-bottom:80px;
  }
  .foot-name{grid-area:name}
  .foot-made{grid-area:made;text-align:right;font-size:12px;line-height:16px;letter-spacing:.06em;text-transform:none}
  .foot-updated{grid-area:updated;text-align:right}
  .foot-clock{grid-area:clock}
}

@media (max-width:560px){
  /* Display/Hero Name Mobile: the words stack, 96px, tighter leading */
  h1.name{white-space:normal;font-size:min(96px,28.8cqw);line-height:.97}   /* longest word measures 3.42em */
  h1.name span{display:block}
  .hero{gap:34px}
  .hero-frame{gap:34px}
  .hero .sel{padding-top:88px}
  .sticker{top:0;right:0;left:auto;transform:rotate(5deg)}
  .lede{max-width:14em;text-wrap:balance;text-align:center}   /* wrap into a tight, even column so .tlayer's box hugs it */
  .tlayer{align-self:center;margin-left:0}
  .tbox{inset:0}   /* on mobile the marquee hugs the two-line lede */
}
@media (max-width:699px){
  #work{padding:56px 0}
  #playground,#about{padding:0 0 56px}
  .head{margin-bottom:24px}
  .about-body{flex-direction:column;align-items:stretch;gap:24px}
  .cframe{width:100%;transform:none}
  .about-prose{flex:none}
}
@media (max-width:560px){
  /* the gutter's only job was reserving room for the ruler frame; with the
     ruler hidden below, it collapses to 0 so body/#dots/.bar stop being
     offset on the left only — main's own symmetric padding then gives
     equal margins on both sides */
  :root{--gutter:0px}
  #ruler-x,#ruler-y,.corner{display:none}
}
/* the bar is viewport minus the gutter, so centred links (Home, Work,
   Playground, About) clear the reduce-motion toggle only above ~620px of
   viewport — go straight to the hamburger at that point instead of an
   in-between state where the nav de-centers into flow order while the
   toggle stays put. */
@media (max-width:640px){
  .burger{display:flex}
  /* stays in the DOM so it can animate; `visibility` (not display) keeps the
     links out of the tab order while closed. The blurred panel is one level
     in (.menu-panel), and — just as important — none of the reveal animation
     lives on *this* wrapper: an ancestor with opacity/transform/clip-path
     makes browsers composite the panel's backdrop-filter from an isolated,
     mostly-blank layer instead of the real page behind it, which is why the
     blur read as much weaker here than on .bar. max-height + overflow is the
     one reveal technique that doesn't trigger that. */
  .menu{
    display:block;
    position:absolute;top:100%;left:0;right:0;
    overflow:hidden;
    max-height:0;
    visibility:hidden;
    transition:max-height .3s cubic-bezier(.4,0,.2,1),
               visibility 0s linear .3s;
  }
  /* the slide + fade move here instead — same element as the blur, which is
     the combination that doesn't fight backdrop-filter */
  .menu-panel{
    display:flex;
    flex-direction:column;
    background:var(--menu-bg);           /* solid enough to read even where blur doesn't render */
    -webkit-backdrop-filter:blur(8px);
    backdrop-filter:blur(8px);
    border-bottom:1px solid var(--rule);
    padding:6px;
    transform:translateY(-10px);
    opacity:0;
    transition:transform .3s cubic-bezier(.4,0,.2,1),
               opacity .18s ease;
  }
  #bar[data-open="true"] .menu{
    max-height:400px;
    visibility:visible;
    transition:max-height .3s cubic-bezier(.4,0,.2,1),
               visibility 0s;
  }
  #bar[data-open="true"] .menu-panel{
    transform:translateY(0);
    opacity:1;
    transition:transform .3s cubic-bezier(.4,0,.2,1),
               opacity .16s ease;
  }
  /* .nav's base rule centers it with position:absolute for the desktop bar;
     that has to be undone here or it pulls the links out of .menu-panel's
     flex flow entirely, leaving just .motion to size the dropdown */
  .nav{position:static;top:auto;left:auto;transform:none;flex-direction:column;gap:0;width:100%}
  .nav a{font-size:12px;padding:11px 12px;width:100%}   /* same as desktop's .nav a -- stated explicitly rather than left to inheritance */
  .nav a::after{display:none}
  .nav a:hover,.nav a:focus-visible{background:var(--select);color:var(--on-select)}
  .motion{
    position:static;transform:none;
    width:100%;justify-content:space-between;
    font-size:12px;   /* same as desktop's .motion */
    padding:11px 12px;margin-top:5px;margin-left:0;
    border-top:1px solid var(--rule-soft);
  }
}
/* Homepage intro: index.html's <head> adds .intro before first paint, so the
   page opens as the bare dot grid while the blue wave crosses it. As the wave
   leaves, the script swaps .intro for .intro-in and the chrome, then the
   content, fade in. Only opacity/transform, and fill "backwards" so nothing
   (e.g. a transform on main) lingers once the animation ends. */
.intro .bar,.intro .chrome,.intro main{opacity:0;pointer-events:none}
@keyframes intro-fade{from{opacity:0}}
@keyframes intro-rise{from{opacity:0;transform:translateY(14px)}}
.intro-in .bar,.intro-in .chrome{animation:intro-fade .5s ease-out backwards}
.intro-in main{animation:intro-rise .7s cubic-bezier(.2,.7,.2,1) .12s backwards}

/* Page transitions: navigating between the homepage and the case studies
   (either way, back/forward included) fades the old page out and the new
   one in with a slight lift, like the intro's content. Cross-document View
   Transitions, so both pages opt in here; browsers without them just
   navigate normally. The nav is lifted out as its own layer only while a
   transition runs, so it holds still -- naming it permanently would make the
   bar a backdrop root and could change its glass treatment. */
@view-transition{navigation:auto}
:root:active-view-transition .bar{view-transition-name:site-nav}
@keyframes page-out{to{opacity:0}}
@keyframes page-in{from{opacity:0;transform:translateY(10px)}}
::view-transition-old(root){animation:page-out .18s ease-in both}
::view-transition-new(root){animation:page-in .38s cubic-bezier(.2,.7,.2,1) .1s both}
@media (prefers-reduced-motion:reduce){
  @view-transition{navigation:none}
}

@media (prefers-reduced-motion:reduce){
  html{scroll-behavior:auto}
  *{transition-duration:.01ms!important;transition-delay:0s!important}
}
:root[data-motion="reduced"]{scroll-behavior:auto}
:root[data-motion="reduced"] *:not(.motion-track){transition-duration:.01ms!important;transition-delay:0s!important}
:root[data-motion="full"]{scroll-behavior:smooth}
