/* The Kai design system (assets/kai/kai.css, vendored by kai-design-system/scripts/sync.mjs)
   is linked ahead of this file and owns the tokens, the fonts, the element base, the pills,
   the dialogs and the eyebrow. This file is the site's layout and its own pieces only. */

/* Adapter: the site's old token names, read from Kai. No new values here. */
:root{
  --paper:var(--kai-paper);--surface:var(--kai-surface);--wash:var(--kai-wash);
  --ink:var(--kai-ink);--secondary:var(--kai-secondary);--muted:var(--kai-muted);
  --line:var(--kai-line);--control:var(--kai-control);--focus:var(--kai-focus);
  --accent:var(--kai-accent);--accent-ink:var(--kai-accent-ink);--accent-wash:var(--kai-accent-wash);
  --display:var(--kai-display);--body:var(--kai-body);--ease:var(--kai-ease);
}

body{min-height:100svh;display:flex;flex-direction:column;transition:background-color .45s ease,color .45s ease}
a{text-decoration:none}
a,button{-webkit-tap-highlight-color:transparent}
/* Pages take the wider focus offset; dense controls keep Kai's own 2px. */
:focus-visible{outline:2px solid var(--focus);outline-offset:6px;border-radius:4px}
.hero .kai-eyebrow,.page-head .kai-eyebrow,.site-dialog .kai-eyebrow{margin:0 0 16px}

/* ---------- chrome: the Kai pills, fixed over the page ---------- */
/* viewport-fit=cover lets the page run under a phone's home indicator; the chrome pads by the safe areas. */
.masthead{position:fixed;top:0;left:0;right:0;z-index:2;display:flex;align-items:center;gap:12px;padding:calc(24px + env(safe-area-inset-top,0px)) calc(48px + env(safe-area-inset-right,0px)) 24px calc(48px + env(safe-area-inset-left,0px));pointer-events:none}
.masthead>*{pointer-events:auto}
.masthead .spacer{flex:1;pointer-events:none}
.theme .icon{display:inline-block;line-height:1;transition:transform .6s var(--ease)}
.kai-pill.wordmark{font-family:var(--display);font-weight:300;font-size:17px;letter-spacing:-.03em}

/* Theme switch: the colours cross-fade, and where the View Transitions API exists the new
   theme is revealed in a circle growing from the toggle (the script sets --reveal-x/y). */
html.theme-switching body,html.theme-switching .kai-pill{transition:none}
::view-transition-old(root),::view-transition-new(root){animation:none;mix-blend-mode:normal}
::view-transition-new(root){animation:reveal .65s var(--ease)}
@keyframes reveal{from{clip-path:circle(0 at var(--reveal-x,50%) var(--reveal-y,0))}to{clip-path:circle(160% at var(--reveal-x,50%) var(--reveal-y,0))}}

/* ---------- the landing: one screen, orb above the name ---------- */
/* The hero takes the orb's pointer events (the canvas sits under the page); manipulation
   keeps one-finger scroll and pinch but drops double-tap zoom, so quick taps register. */
.hero{flex:1;display:grid;grid-template-rows:minmax(0,1fr) auto;justify-items:center;align-content:center;gap:clamp(8px,2vh,28px);padding:calc(96px + env(safe-area-inset-top,0px)) 24px 32px;isolation:isolate;touch-action:manipulation;-webkit-tap-highlight-color:transparent}
.hero.over-orb{cursor:pointer}
.presence{position:relative;width:min(720px,88vw);height:100%;min-height:0;max-height:560px;align-self:center;justify-self:center;pointer-events:none;z-index:-1}
.presence canvas{position:absolute;inset:0;width:100%;height:100%;opacity:0;transition:opacity .7s}
/* The shader sizes the cloud against the canvas's shorter side and draws past it, so a box
   wider than it is tall cuts the cloud off flat along the top and bottom. The mask turns that
   cut into a dissolve at every aspect ratio — the edge treatment The Double Drop uses on its
   chart. The cloud's core sits well inside the opaque part, so nothing of substance is lost. */
.presence canvas{-webkit-mask-image:radial-gradient(closest-side,#000 56%,rgba(0,0,0,.6) 80%,transparent 99%);mask-image:radial-gradient(closest-side,#000 56%,rgba(0,0,0,.6) 80%,transparent 99%)}
.presence.ready canvas{opacity:1}
.presence.ready .fallback{opacity:0}
/* Shown until WebGL reports in, and left standing if it never does. The script sets the
   --orb-* colours to the hour's palette; the literals are the night field for no-JS. */
.fallback{position:absolute;inset:12% 15%;border-radius:47% 53% 42% 58%;background:radial-gradient(ellipse at 30% 35%,var(--orb-b,#8bc2d7) 0,transparent 55%),radial-gradient(ellipse at 75% 60%,var(--orb-c,#e4aaa5) 0,transparent 55%),radial-gradient(ellipse at 40% 80%,var(--orb-a,#8dace0) 0,transparent 60%);filter:blur(35px);opacity:.8;transition:opacity .7s,background .8s}
.introduction{text-align:center;position:relative}
h1{font-family:var(--display);font-weight:300;font-size:clamp(40px,6.4vw,84px);line-height:1.08;letter-spacing:-.055em;margin:0 0 14px;text-wrap:balance}
h1 .dot{color:var(--accent)}
.tag{font-size:15px;color:var(--muted);margin:0}
/* Who else is in the room, and the one hint the orb ever gives. Empty most of the time. */
.company{font-size:12px;color:var(--muted);letter-spacing:.02em;margin:10px 0 0;min-height:1.3em;opacity:.85;transition:opacity .5s}
.company:empty{opacity:0}
/* The kept orb: a square the visitor can share or save. */
/* The hold ring: fills round a still finger; full means letting go keeps the frame. Wide
   enough to show round a thumb; smaller under a mouse. It waits a beat so taps never flash it. */
.hold-ring{position:fixed;left:0;top:0;width:116px;height:116px;margin:-58px 0 0 -58px;pointer-events:none;opacity:0;transition:opacity .2s;z-index:5}
.hold-ring.mouse{width:64px;height:64px;margin:-32px 0 0 -32px}
.hold-ring svg{display:block;width:100%;height:100%;transform:rotate(-90deg);overflow:visible}
.hold-ring circle{fill:none;stroke:var(--ink);stroke-width:3}
.hold-ring .track{opacity:.16}
.hold-ring .fill{stroke-linecap:round;stroke-dasharray:100;stroke-dashoffset:100}
.hold-ring.charging{opacity:1;transition-delay:.35s}
.hold-ring.charging .fill{animation:hold-fill 1.15s linear .35s forwards}
.hold-ring.armed{opacity:1}
.hold-ring.armed .fill{stroke-dashoffset:0;stroke:var(--accent-ink)}
.hold-ring.armed.off{opacity:.3}
@keyframes hold-fill{to{stroke-dashoffset:0}}
.site-dialog.keep img{display:block;width:min(320px,calc(100vw - 120px));height:auto;aspect-ratio:1;margin:0 auto 8px;border-radius:14px;border:1px solid var(--line);background:var(--wash)}

/* The name settles in one glyph at a time, from a blur — the same motion as Kai's title,
   without the typewriter. The script adds the spans; with no JS the name is simply there.
   The glyphs rest fully visible with no animation of their own: the reveal only runs under
   html.reveal, which the script adds on a visible, motion-allowing load, and html.revealed
   stops it 1.6s later. Anything that never ticks animations — a capture, a background tab,
   an old engine — therefore shows the name instead of a blank h1. */
.ch{display:inline-block;white-space:pre;opacity:1}
html.reveal .ch{animation:ch-in .55s var(--ease) backwards}
html.revealed .ch{animation:none}
@keyframes ch-in{from{opacity:0;filter:blur(6px)}}

/* ---------- the contact line ---------- */
.contact-line{display:flex;align-items:center;justify-content:center;flex-wrap:wrap;gap:0 14px;padding:16px clamp(20px,4vw,48px) calc(20px + env(safe-area-inset-bottom,0px));border-top:1px solid var(--line);font-size:13px;color:var(--muted);transition:border-color .45s ease}
.contact-line a{transition:color .3s ease}
.contact-line a:hover{color:var(--ink)}
.contact-line a:not(:last-child)::after{content:"/";margin-left:14px;color:var(--line)}

/* ---------- the contact and keep dialogs ---------- */
/* A mailto: link does nothing on a machine with no mail app set, so the pill opens a Kai
   dialog instead. The site centres its dialogs; the title pads both sides so the close button
   does not pull it off centre. The matched pill pair is Kai's .kai-actions. */
.site-dialog{text-align:center}
.site-dialog p{margin:0}
.site-dialog .kai-eyebrow{margin:0}
.site-dialog .kai-dialog-title{padding-inline:28px}
.site-dialog p.address{color:var(--ink);font-size:18px;letter-spacing:-.2px;overflow-wrap:anywhere;user-select:all}
.site-dialog p.status{min-height:1.5em;font-size:13px;color:var(--muted)}

/* ---------- /projects ---------- */
/* This page scrolls, so its chrome floats over paper that fades out under the bar — otherwise
   rows slide behind the pills and read through them. */
body.list .masthead{background:linear-gradient(to bottom,var(--paper) 0%,var(--paper) 62%,color-mix(in srgb,var(--paper) 85%,transparent) 82%,transparent 100%)}
.page{width:100%;max-width:1080px;margin:0 auto;padding:calc(132px + env(safe-area-inset-top,0px)) clamp(20px,4vw,48px) 0;flex:1}
.page-head{margin:0 0 clamp(40px,7vh,72px)}
.page-head h1{font-size:clamp(44px,7vw,92px);margin:0 0 14px}
.group{margin-top:clamp(40px,6vh,64px)}
.group-head{border-top:1px solid var(--line);padding-top:16px;margin-bottom:4px}
.group-head h2{font-family:var(--display);font-weight:300;font-size:clamp(26px,3.4vw,38px);letter-spacing:-.045em;line-height:1.05;margin:0}
.rows{list-style:none;margin:0;padding:0}
.row{display:grid;grid-template-columns:minmax(10ch,15em) 1fr auto;align-items:baseline;gap:6px clamp(16px,3vw,36px);padding:18px 0;border-bottom:1px solid var(--line)}
.rows>li:first-child .row{border-top:1px solid var(--line)}
.row .name{font-size:16px;font-weight:500;letter-spacing:-.01em}
.row .what{font-size:14px;color:var(--muted)}
.row .state{font-size:11px;letter-spacing:.1em;text-transform:uppercase;color:var(--muted);white-space:nowrap}
/* The hover wash reaches into the gutter, but the row's own box stays at the content width,
   so its hairline still lines up with the section rule above it and the footer below. */
a.row{position:relative;isolation:isolate}
a.row::before{content:"";position:absolute;inset:0 -12px;z-index:-1;border-radius:8px;transition:background-color .3s ease}
a.row:hover::before{background:var(--wash)}
a.row:hover .name{color:var(--accent-ink)}
a.row .state{color:var(--accent-ink)}
/* The orb on this page takes the colour of the project being pointed at. It rides in the
   masthead, centred between the wordmark and Contact, at pill height and drawn larger than its
   box so the cloud reads at that size; there it only changes colour, since a nudged form is a
   smudge that small;
   where the screen is wide enough to leave a margin it moves out to the right edge instead,
   half off the page, clear of the rows. The lit row shows a dot in the same colour. */
.row-orb{position:relative;flex:none;width:44px;height:44px;margin:0 auto;pointer-events:none!important}
body.list .masthead .spacer{flex:0}
.row-orb canvas,.row-orb .fallback{position:absolute;inset:-24px;width:calc(100% + 48px);height:calc(100% + 48px)}
.row-orb canvas{opacity:0;transition:opacity .7s;-webkit-mask-image:radial-gradient(closest-side,#000 56%,rgba(0,0,0,.6) 80%,transparent 99%);mask-image:radial-gradient(closest-side,#000 56%,rgba(0,0,0,.6) 80%,transparent 99%)}
.row-orb .fallback{inset:0;width:100%;height:100%;border-radius:47% 53% 42% 58%;filter:blur(7px);opacity:.85}
.row-orb.ready canvas{opacity:1}
.row-orb.ready .fallback{opacity:0}
@media(min-width:1360px){
  .row-orb{position:fixed;top:50%;right:-190px;width:380px;height:380px;margin:0;transform:translateY(-50%)}
  body.list .masthead .spacer{flex:1}
  .row-orb canvas{inset:0;width:100%;height:100%}
  .row-orb .fallback{inset:12% 15%;width:auto;height:auto;filter:blur(30px)}
}
.row .name{position:relative}
.row .name::before{content:"";position:absolute;left:-14px;top:.55em;width:6px;height:6px;border-radius:50%;background:var(--tint,var(--accent));opacity:0;transform:scale(.4);transition:opacity .3s ease,transform .3s var(--ease)}
.row.lit .name::before{opacity:1;transform:none}
/* The footer sits outside <main>, so it takes the page's own content width to keep its
   hairline flush with the rows above it. */
body.list .contact-line{width:calc(100% - 2*clamp(20px,4vw,48px));max-width:calc(1080px - 2*clamp(20px,4vw,48px));margin:clamp(56px,9vh,96px) auto 0;padding-inline:0}

/* ---------- phones ---------- */
@media(max-width:600px){
  .masthead{padding:calc(16px + env(safe-area-inset-top,0px)) 16px 16px;gap:8px}
  .kai-pill.wordmark{padding:0 14px;font-size:16px} /* kept: it is the only route home from /projects */
  /* The landing is a single phone screen, never a scroll view: dvh tracks Safari's toolbar so
     the contact line always sits just above it. */
  body.home{height:100svh;height:100dvh;overflow:hidden}
  .hero{padding:calc(80px + env(safe-area-inset-top,0px)) 16px 20px;gap:4px}
  .presence{width:100%;max-height:none}
  h1{font-size:clamp(30px,8.6vw,44px);letter-spacing:-.05em;margin-bottom:10px}
  .tag{font-size:14px}
  .hero .kai-eyebrow,.page-head .kai-eyebrow{font-size:9px;margin-bottom:12px}
  /* Email and all four socials stay on one line: the type and the gaps scale with the viewport. */
  .contact-line{flex-wrap:nowrap;white-space:nowrap;gap:0 .55em;font-size:min(3vw,12px);padding:12px 16px calc(14px + env(safe-area-inset-bottom,0px))}
  .contact-line a:not(:last-child)::after{margin-left:.55em}
  .page{padding-top:calc(104px + env(safe-area-inset-top,0px))}
  .row{grid-template-columns:1fr auto;row-gap:2px;padding:15px 0}
  .row .name{grid-row:1;grid-column:1}
  .row .state{grid-row:1;grid-column:2}
  .row .what{grid-row:2;grid-column:1/-1}
}
