/* Inter Tight (latin), self-hosted. SIL Open Font License 1.1: /assets/OFL.txt */
@font-face {
  font-family: "Inter Tight";
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: url("/assets/inter-tight-latin.83d548cd.woff2") format("woff2");
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
  font-family: "Inter Tight";
  font-style: normal;
  font-weight: 500;
  font-display: swap;
  src: url("/assets/inter-tight-latin.83d548cd.woff2") format("woff2");
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
  font-family: "Inter Tight";
  font-style: normal;
  font-weight: 600;
  font-display: swap;
  src: url("/assets/inter-tight-latin.83d548cd.woff2") format("woff2");
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}

/* Layout: one dark screen. A live WebGL light field fills it edge to edge, the wordmark sits on a solid at the centre,
   eight solid link nodes ride one slowly turning ring around it, evenly spaced (equal gaps edge to edge), each with a
   small drift of its own; a small physics world keeps them apart (they never overlap, and bounce if they ever touch).
   The email sits quietly at the foot.
   Single dark look by design: every colour is set here, the body paints its own ground.
   Fonts: one family for the whole page, Inter Tight (the wordmark's face): labels at 600, the line and captions at 400. */
:root {
  --void: #020204;            /* the ground: near black with a breath of blue */
  --ink: #FFFFFF;             /* wordmark, labels */
  --ink-2: rgba(232, 235, 255, .74);
  --ink-3: rgba(214, 219, 255, .5);
  --hair: rgba(255, 255, 255, .1);
  --scrim: rgba(2, 2, 4, .8);
  --holo: conic-gradient(from 0deg, #79F2FF, #8C95FF, #CF86FF, #FF86C8, #FFC48A, #95FFD2, #79F2FF);
  --holo-line: linear-gradient(90deg, #79F2FF, #8C95FF 30%, #CF86FF 55%, #FF86C8 80%, #FFC48A);
  --font-ui: "Inter Tight", system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;
  --font-mark: var(--font-ui);
  --wm-wt: 600;               /* wordmark weight and tracking */
  --wm-ls: -.015em;
  --ls-ui: -.005em;           /* label tracking: the script reads it too, so it measures labels the way they are set */
  --core: 268px;
  color-scheme: dark;
}

html { height: 100%; box-sizing: border-box; overflow: hidden; background: var(--void); }
*, *::before, *::after { box-sizing: inherit; }
body {
  height: 100%; margin: 0; overflow: hidden; overscroll-behavior: none;
  background-color: var(--void);
  /* the still ground: what shows before the light field starts, and instead of it when WebGL is missing */
  background-image:
    radial-gradient(55% 45% at 20% 24%, rgba(96, 70, 255, .11), rgba(96, 70, 255, 0) 70%),
    radial-gradient(48% 40% at 82% 78%, rgba(0, 196, 255, .09), rgba(0, 196, 255, 0) 70%),
    radial-gradient(36% 30% at 76% 14%, rgba(255, 70, 170, .07), rgba(255, 70, 170, 0) 70%);
  color: var(--ink);
  font-family: var(--font-ui); font-size: 15px; line-height: 1.45;
  -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale;
}
p { margin: 0; }

.hub { position: relative; width: 100%; height: 100%; overflow: hidden; isolation: isolate; }

/* Light field (WebGL) and connectors (2D) */
.field {
  position: absolute; left: 0; top: 0; width: 100%; height: 100%; z-index: 0; display: block;
  opacity: 0; transition: opacity 1.4s ease; pointer-events: none;
}
.gl-on .field { opacity: 1; }
.wires { position: absolute; left: 0; top: 0; width: 100%; height: 100%; z-index: 2; pointer-events: none; }
.stage { position: absolute; inset: 0; z-index: 3; }
/* contact sparks sit above the nodes (in the gap two nodes touch across, which their shadows would darken) and below
   the centre and the caption */
.sparks { position: absolute; left: 0; top: 0; width: 100%; height: 100%; z-index: 25; pointer-events: none; }

/* ---------- Solids: the centre and the eight links share one material, set by data-nodes on the root: obsidian
   (a black machined puck).
   The light field draws what lies behind and under each solid (refraction, frost, the shadow it casts); the page draws
   the crisp parts (faces, edges, highlights). .lt is a frame the script turns toward the light (the same light the field
   leans to), so highlights and lit edges always sit on the side that faces it. ---------- */
.nd { --dm: 0; --bev: clamp(4px, calc(var(--d, 110px) * .055), 9px); }
.lt, .lt-a, .lt-b { position: absolute; inset: 0; border-radius: inherit; pointer-events: none; }
.lt::before, .lt::after { content: ""; position: absolute; pointer-events: none; }
.lt { opacity: calc(1 - .55 * var(--dm)); transition: opacity .3s ease; }
.nd::after { content: ""; position: absolute; inset: 0; border-radius: inherit; pointer-events: none; opacity: 0; transition: opacity .3s ease; }
.nd.is-active::after { opacity: 1; }
/* the centre's light frame turns every frame (its light travels): its own layer, so the turn is composited, not repainted */
#core-lt { will-change: transform; }
.b-dot { display: none; }

/* Centre: the wordmark sits on the same material */
.core-pos { position: absolute; left: 0; top: 0; z-index: 30; width: var(--core); height: var(--core); pointer-events: none; }
/* the square wrapper takes no pointer (its corners sit over the nodes); the round lens does. The bottom padding lifts
   the word and its line to the lens's optical centre */
.core {
  --d: var(--core);
  position: relative; width: 100%; height: 100%; border-radius: 50%;
  display: grid; place-content: center; justify-items: center; text-align: center;
  padding-bottom: calc(var(--core) * .07);
  pointer-events: auto;
  will-change: transform, opacity;
}
/* Wordmark: the one word, plain white, sized so its ink spans about 60% of the lens (--wmk, a share of the lens per
   face, set by the script; the fallbacks here are Inter Tight's, the no-JS look). --wm-dx moves the ink onto the lens's centre line
   (tracking leaves a little space after the last letter, and each face's N and m sit differently in their boxes) */
.wordmark {
  position: relative; left: var(--wm-dx, -.0095em); margin: 0;
  font-family: var(--font-mark); font-weight: var(--wm-wt); font-kerning: normal;
  font-size: calc(var(--core) * var(--wmk, .1975)); line-height: 1; letter-spacing: var(--wm-ls); color: var(--ink);
}
.core-line {
  position: relative; margin-top: calc(var(--core) * .05); max-width: calc(var(--core) * .7);
  font-weight: 400; font-size: clamp(13px, calc(var(--core) * .056), 17.5px); line-height: 1.36; letter-spacing: 0;
  color: var(--ink-2); text-wrap: balance;
}
/* a lens wide enough for it (the script measures) sets the line as "I build AI for / local service businesses." */
.is-big-core .core-nb { display: block; white-space: nowrap; }
.is-ring .core-line { font-size: 12.5px; max-width: calc(var(--core) * .8); margin-top: calc(var(--core) * .06); }

/* The links: a real list */
.orbits { list-style: none; margin: 0; padding: 0; position: absolute; inset: 0; z-index: 20; }
.bubble {
  position: absolute; left: 0; top: 0;
  width: var(--d, 110px); height: var(--d, 110px); border-radius: 50%;
  display: grid; place-content: center; justify-items: center; text-align: center;
  color: var(--ink); text-decoration: none;
  transform-origin: 50% 50%; will-change: transform, opacity;
  -webkit-tap-highlight-color: transparent; touch-action: manipulation;
  transition: box-shadow .3s ease, background-color .3s ease;
}
/* focus: a mouse or a finger never leaves a ring (the script also lets go of a node it opened); from the keyboard a
   thin bright hairline follows the solid's outline a few px out, like the rims around it */
.bubble:focus, .bubble:focus:not(:focus-visible) { outline: none; }
.bubble:focus-visible { outline: 1.5px solid rgba(236, 240, 255, .92); outline-offset: 5px; }
.b-label {
  position: relative; display: block; max-width: 86%; min-width: min-content;   /* a single long word stays centred under its mark */
  font-weight: 600; font-size: var(--fs, 17px); line-height: 1.08; letter-spacing: var(--ls-ui);
  text-wrap: balance; text-shadow: 0 1px 10px rgba(0, 0, 0, .7);
  opacity: calc(1 - .42 * var(--dm));
}
/* phones: one circle size; the lead links keep a full-white label, the others a slightly quieter one */
[data-nodes="obsidian"] .is-ring .bubble:not(.is-lead) .b-label { color: rgba(236, 239, 255, .84); }
/* Marks: every node carries one (the Steady arcs, the official Instagram, LinkedIn and TikTok glyphs, a line icon for
   the rest), all in one box (--mk, set by the script: the arcs' size), each glyph sized inside it to the same optical
   size. Brand glyphs are solid white and never altered; line icons are a white stroke with round ends and joins, its
   weight set per icon to match the solid glyphs */
.b-mark { position: relative; display: block; flex: none; overflow: visible; width: var(--mk, calc(var(--fs, 17px) * 1.15)); height: var(--mk, calc(var(--fs, 17px) * 1.15)); margin-bottom: calc(var(--fs, 17px) * .32); color: var(--ink); opacity: calc(1 - .42 * var(--dm)); }
.b-mark path, .b-mark circle { fill: none; stroke: currentColor; stroke-width: var(--sw, 20); stroke-linecap: round; stroke-linejoin: round; }
.b-mark.is-solid path { fill: currentColor; stroke: none; }
/* circles: the mark and label sit as one block, lifted by a fifth of the mark's height and gap toward the optical centre
   (as the lens lifts its word), which keeps a wide label clear of the rim */
[data-nodes="obsidian"] .bubble.has-mark { padding-bottom: calc((var(--mk, 0px) + var(--fs, 17px) * .32) * .4); }

/* --- Obsidian: an opaque black puck with a machined edge. The face is solid (the field skips it); a chamfer catches
   the light on one side and falls dark on the other; an RGB-split rim light (the amoeba rims' split: red inside,
   blue outside) runs only along the edge that faces the light; a heavy shadow under it. A faint hairline runs all the
   way round and a little bounce light sits low on the face, so the whole silhouette reads on the unlit side too. --- */
[data-nodes="obsidian"] .nd {
  --bev: clamp(3.5px, calc(var(--d, 110px) * .045), 7px);
  background:
    radial-gradient(70% 28% at 50% 100%, rgba(150, 170, 255, .05), rgba(150, 170, 255, 0) 70%),
    radial-gradient(circle at 50% 50%, #0a0a0d 0%, #08080b 64%, #060608 100%);
  box-shadow:
    inset 0 0 0 1px rgba(255, 255, 255, .09),
    0 0 0 1px rgba(255, 255, 255, .04),
    0 calc(var(--d) * .14) calc(var(--d) * .28) calc(var(--d) * -.06) rgba(0, 0, 0, .92),
    0 calc(var(--d) * .035) calc(var(--d) * .07) rgba(0, 0, 0, .6);
}
[data-nodes="obsidian"] .lt::before {   /* face: soft gloss toward the light, a turned-metal streak on its axis */
  inset: 0; border-radius: 50%;
  background:
    radial-gradient(80% 50% at 50% 0%, rgba(255, 255, 255, .09), rgba(255, 255, 255, 0) 70%),
    conic-gradient(rgba(255, 255, 255, .05), rgba(255, 255, 255, 0) 24deg 156deg, rgba(255, 255, 255, .028) 180deg, rgba(255, 255, 255, 0) 204deg 336deg, rgba(255, 255, 255, .05));
  -webkit-mask: radial-gradient(farthest-side, #000 calc(100% - var(--bev) - 1px), transparent calc(100% - var(--bev)));
          mask: radial-gradient(farthest-side, #000 calc(100% - var(--bev) - 1px), transparent calc(100% - var(--bev)));
}
[data-nodes="obsidian"] .lt::after {     /* fine turning marks */
  inset: var(--bev); border-radius: 50%;
  background: repeating-radial-gradient(circle, rgba(255, 255, 255, .011) 0 1px, rgba(255, 255, 255, 0) 1px 2.5px);
}
[data-nodes="obsidian"] .lt-a {               /* chamfer */
  background: conic-gradient(rgba(255, 255, 255, .46), rgba(255, 255, 255, .15) 50deg, rgba(255, 255, 255, 0) 110deg 250deg, rgba(255, 255, 255, .15) 310deg, rgba(255, 255, 255, .46));
  -webkit-mask: radial-gradient(farthest-side, transparent calc(100% - var(--bev) - .6px), #000 calc(100% - var(--bev)), #000 calc(100% - 2.2px), transparent calc(100% - 1.6px));
          mask: radial-gradient(farthest-side, transparent calc(100% - var(--bev) - .6px), #000 calc(100% - var(--bev)), #000 calc(100% - 2.2px), transparent calc(100% - 1.6px));
}
[data-nodes="obsidian"] .lt-b,
[data-nodes="obsidian"] .nd::after {                   /* rim light, RGB split */
  background: radial-gradient(farthest-side, rgba(255, 64, 150, 0) calc(100% - 3.4px), rgba(255, 64, 150, .95) calc(100% - 2.5px), #fff calc(100% - 1.5px), rgba(64, 186, 255, .95) calc(100% - .55px), rgba(64, 186, 255, 0));
}
[data-nodes="obsidian"] .lt-b {
  -webkit-mask: conic-gradient(#000, rgba(0, 0, 0, .55) 42deg, transparent 96deg 264deg, rgba(0, 0, 0, .55) 318deg, #000);
          mask: conic-gradient(#000, rgba(0, 0, 0, .55) 42deg, transparent 96deg 264deg, rgba(0, 0, 0, .55) 318deg, #000);
}
[data-nodes="obsidian"] .nd.is-active::after { opacity: .8; }

/* the light field hides at once when it goes (a lost context paints a grey placeholder while it would fade) */
.no-gl .field { opacity: 0; transition: none; }

.sr {
  position: absolute !important; width: 1px; height: 1px; padding: 0; margin: -1px;
  overflow: hidden; clip: rect(0 0 0 0); clip-path: inset(50%); white-space: nowrap; border: 0;
}

/* Caption: the active node's line and an Open link, set straight onto the dark beside it, never over another node
   or the lens. Only its words and the Open link take the pointer, so a node near it stays easy to hit */
.cap {
  position: absolute; left: 0; top: 0; z-index: 120; width: var(--cw, 260px);
  display: grid; justify-items: center; gap: 6px; text-align: center;
  opacity: 0; pointer-events: none; visibility: hidden;
  transition: opacity .25s ease, visibility 0s linear .25s;
}
.cap.is-on { opacity: 1; visibility: visible; transition: opacity .3s ease .05s, visibility 0s; }
.cap.is-on .cap-hit, .cap.is-on .cap-open { pointer-events: auto; }
.cap::before {
  content: ""; position: absolute; inset: -24px -30px; z-index: -1; border-radius: 50%; pointer-events: none;
  background: radial-gradient(closest-side, var(--scrim), rgba(2, 2, 4, .55) 55%, rgba(2, 2, 4, 0));
}
.cap[data-al="l"] { justify-items: start; text-align: left; }
.cap[data-al="r"] { justify-items: end; text-align: right; }
.cap-desc { font-size: 15px; line-height: 1.4; color: var(--ink); text-wrap: balance; text-shadow: 0 1px 12px rgba(0, 0, 0, .85); font-variant-numeric: tabular-nums; }
/* the Open link is a 44px tap target; the negative margins keep the caption's layout as compact as the text */
.cap-open {
  position: relative; display: inline-flex; align-items: center; gap: 7px; min-height: 44px; margin: -7px 0; padding: 0 2px;
  font-size: 12px; font-weight: 600; letter-spacing: .1em; text-transform: uppercase;
  color: var(--ink); text-decoration: none;
}
.cap-open::after {
  content: ""; position: absolute; left: 2px; right: 2px; bottom: 12px; height: 1px;
  background: var(--holo-line); opacity: .9;
}
.cap-open:focus { outline: none; }
.cap-open:focus-visible { outline: 1.5px solid rgba(236, 240, 255, .92); outline-offset: 3px; border-radius: 4px; }
.arrow { width: 11px; height: 11px; flex: none; }
.arrow path { fill: none; stroke: currentColor; stroke-width: 1.6; stroke-linecap: round; stroke-linejoin: round; }
/* Docked: when no spot beside the node is clear of the other nodes and the lens (a crowded phone screen), the caption
   takes the email row's place at the foot, words left and Open right, and the email steps aside until it goes */
.cap.is-dock { width: var(--dw, 320px); grid-template-columns: minmax(0, 1fr) auto; align-items: center; justify-items: start; column-gap: 18px; text-align: left; }
.cap.is-dock .cap-open { justify-self: end; margin: 0; }
.cap.is-dock::before { inset: -14px -24px; }

/* Foot: the email, quiet and selectable */
.foot {
  position: absolute; left: 0; right: 0; bottom: 0; z-index: 50;
  display: flex; align-items: center; justify-content: center; gap: 6px;
  padding: 0 max(16px, env(safe-area-inset-right, 0px)) calc(14px + env(safe-area-inset-bottom, 0px)) max(16px, env(safe-area-inset-left, 0px));
  pointer-events: none;   /* the insets: clear of the home indicator and notch (the page draws under them: viewport-fit=cover) */
  font-size: 13px; font-weight: 500; line-height: 1.3; letter-spacing: .01em; color: var(--ink-3);
  text-shadow: 0 0 2px rgba(2, 2, 4, .8), 0 0 12px rgba(2, 2, 4, .9);
}
.foot > * { pointer-events: auto; }
.foot { transition: opacity .25s ease .05s, visibility 0s; }
.is-docked .foot { opacity: 0; visibility: hidden; transition: opacity .2s ease, visibility 0s linear .2s; }
.mail { color: var(--ink-2); -webkit-user-select: all; user-select: all; overflow-wrap: anywhere; }
.copy {
  position: relative; min-height: 30px; padding: 0 6px; border: 0; background: transparent; cursor: pointer;
  font: inherit; color: rgba(214, 219, 255, .64);
  text-decoration: underline; text-decoration-color: rgba(255, 255, 255, .22); text-underline-offset: 4px;
}
.copy::after { content: ""; position: absolute; inset: -7px -4px; }   /* a 44px tap target */
.copy:hover { color: var(--ink); text-decoration-color: rgba(255, 255, 255, .6); }
.copy:focus { outline: none; }
.copy:focus-visible { outline: 1.5px solid rgba(236, 240, 255, .92); outline-offset: 2px; border-radius: 4px; }

@media (max-width: 639px), (max-height: 519px) {
  .cap-desc { font-size: 14.5px; }
}

@media (prefers-reduced-motion: reduce) {
  .cap, .cap.is-on, .field, .foot, .is-docked .foot { transition: none; }
}

/* No JavaScript: the same markup reads as a plain, centred link list (the script adds the js class to the root element) */
html:not(.js) { overflow: auto; }
html:not(.js) body, html:not(.js) .hub { height: auto; overflow: visible; }
html:not(.js) .hub { min-height: 100vh; }
html:not(.js) .field, html:not(.js) .wires, html:not(.js) .sparks, html:not(.js) .cap, html:not(.js) .copy { display: none; }
html:not(.js) .stage { position: static; padding: 64px 16px 40px; }
html:not(.js) .core-pos { position: relative; margin: 0 auto 32px; --core: 232px; }
html:not(.js) .core { background-color: rgba(5, 5, 10, .7); }
html:not(.js) .orbits { position: static; display: grid; gap: 10px; max-width: 420px; margin: 0 auto; }
html:not(.js) .bubble {
  position: static; width: auto; height: auto; border-radius: 22px; padding: 14px 18px;
  place-content: start; justify-items: start; text-align: left;
  background: rgba(255, 255, 255, .035); box-shadow: inset 0 0 0 1px rgba(255, 255, 255, .12);
}
html:not(.js) .bubble::after, html:not(.js) .lt, html:not(.js) .b-dot { display: none; }
html:not(.js) .b-label { max-width: none; }
html:not(.js) .sr {
  position: static !important; width: auto; height: auto; margin: 4px 0 0; overflow: visible;
  clip: auto; clip-path: none; white-space: normal;
  font-size: 14px; line-height: 1.35; color: var(--ink-2); font-variant-numeric: tabular-nums;
}
html:not(.js) .sr .x { display: none; }
html:not(.js) .foot { position: static; padding-bottom: 40px; }
