/* Shared world layer: tokens, the animated mark, and the signal CTA.
   Loaded by every page so the replacement world is one system rather than a
   per-page reimplementation. Page files own only what is specific to them. */

:root {
  --void: #050406;
  --ink: #0B0A0D;
  --paper: #F4F2EF;
  /* both grays clear 4.5:1 on the void and on the panel ink */
  --gray: #A7A29A;   /* 8.06:1 */
  --dim: #7D7972;    /* 4.72:1 */
  --line: rgba(244, 242, 239, 0.08);
  --line-hi: rgba(244, 242, 239, 0.16);
  --signal: #FF4A1C;
  --signal-soft: #FFB59E;
  --ease: cubic-bezier(0.22, 1, 0.36, 1);
  --col: min(760px, calc(100% - 40px));
}

/* shared primitives: every surface measures itself against the same column */
.col { width: var(--col); margin-inline: auto; }
.sr-only { position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px; overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; border: 0; }

.grain {
  position: fixed; inset: 0; z-index: 60; pointer-events: none; opacity: .1; mix-blend-mode: overlay;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='160' height='160'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='.9' numOctaves='3' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23n)'/%3E%3C/svg%3E");
}

/* ---- the mark ---------------------------------------------------------
   Morph plus expand-ring: the three bars slide apart along their own 45deg
   axis, the two accent squares trade weight, and a ring blooms behind. The
   geometry is never altered, only translated, so the logo stays itself. */
.mark {
  position: relative; display: inline-flex; align-items: center; justify-content: center;
  width: 44px; height: 44px; margin-left: -12px; text-decoration: none;
}
.mark svg { width: 21px; height: 21px; overflow: visible; }
.mark-bar, .mark-dot {
  transition: transform .55s var(--ease);
}
.mark-dot rect { transition: fill-opacity .45s var(--ease); }
.mark:hover .mark-bar--1, .mark:focus-visible .mark-bar--1 { transform: translate(3.4px, -3.4px); }
.mark:hover .mark-bar--2, .mark:focus-visible .mark-bar--2 { transform: translate(-3px, 3px); }
.mark:hover .mark-bar--3, .mark:focus-visible .mark-bar--3 { transform: translate(3px, -3px); }
.mark:hover .mark-dot--a, .mark:focus-visible .mark-dot--a { transform: translate(-5px, 5px); }
.mark:hover .mark-dot--b, .mark:focus-visible .mark-dot--b { transform: translate(5px, -5px); }
/* the accents trade places in weight, so the mark reads as alive rather than
   merely nudged */
.mark:hover .mark-dot--a rect, .mark:focus-visible .mark-dot--a rect { fill-opacity: .4; }
.mark:hover .mark-dot--b rect, .mark:focus-visible .mark-dot--b rect { fill-opacity: 1; }
.mark-ring {
  position: absolute; inset: 3px; border-radius: 50%;
  border: 1px solid rgba(255, 74, 28, .45);
  opacity: 0; transform: scale(.55);
  transition: opacity .45s var(--ease), transform .55s var(--ease);
  pointer-events: none;
}
.mark:hover .mark-ring, .mark:focus-visible .mark-ring { opacity: 1; transform: scale(1); }
.mark:active .mark-ring { transform: scale(1.18); opacity: .25; }

/* ---- the signal CTA ---------------------------------------------------
   The site's long-standing shiny button, carried over by request and remapped
   from the retired cyan to the signal red-orange. A rotating conic sits in the
   border box, a shimmer sweeps the face, and the label glows on hover. */
@property --gradient-angle { syntax: '<angle>'; initial-value: 0deg; inherits: false; }
@property --gradient-angle-offset { syntax: '<angle>'; initial-value: 0deg; inherits: false; }
@property --gradient-percent { syntax: '<percentage>'; initial-value: 5%; inherits: false; }
@property --gradient-shine { syntax: '<color>'; initial-value: #F4F2EF; inherits: false; }

.shiny-wrap { position: relative; display: inline-block; }
.shiny {
  --shiny-bg: #0B0A0D;
  --shiny-bg-subtle: rgba(244, 242, 239, .08);
  --shiny-fg: var(--paper);
  --shiny-highlight: var(--signal);
  --shiny-highlight-subtle: var(--signal-soft);
  --animation: gradient-angle linear infinite;
  --duration: 3s;
  --shadow-size: 2px;
  --transition: 800ms cubic-bezier(.25, 1, .5, 1);
  isolation: isolate; position: relative;
  display: inline-flex; align-items: center; justify-content: center;
  overflow: hidden; cursor: pointer; outline-offset: 4px;
  min-width: 232px; min-height: 54px; padding: 15px 28px;
  border: 1px solid transparent; border-radius: 360px;
  color: var(--shiny-fg); text-decoration: none;
  font: 500 14.5px/1.2 Archivo, sans-serif; letter-spacing: -.005em;
  background:
    linear-gradient(var(--shiny-bg), var(--shiny-bg)) padding-box,
    conic-gradient(
      from calc(var(--gradient-angle) - var(--gradient-angle-offset)),
      transparent,
      var(--shiny-highlight) var(--gradient-percent),
      var(--gradient-shine) calc(var(--gradient-percent) * 2),
      var(--shiny-highlight) calc(var(--gradient-percent) * 3),
      transparent calc(var(--gradient-percent) * 4)
    ) border-box;
  box-shadow: inset 0 0 0 1px var(--shiny-bg-subtle);
  transition: var(--transition);
  transition-property: --gradient-angle-offset, --gradient-percent, --gradient-shine;
}
.shiny::before, .shiny::after, .shiny-content::before {
  content: ''; pointer-events: none; position: absolute;
  inset-inline-start: 50%; inset-block-start: 50%;
  translate: -50% -50%; z-index: -1;
}
.shiny:active { translate: 0 1px; }
.shiny::before {
  --size: calc(100% - var(--shadow-size) * 3);
  --position: 2px;
  --space: calc(var(--position) * 2);
  width: var(--size); height: var(--size);
  background: radial-gradient(circle at var(--position) var(--position), white calc(var(--position) / 4), transparent 0) padding-box;
  background-size: var(--space) var(--space);
  background-repeat: space;
  mask-image: conic-gradient(from calc(var(--gradient-angle) + 45deg), black, transparent 10% 90%, black);
  border-radius: inherit;
  opacity: .4;
}
.shiny::after {
  --animation: shimmer linear infinite;
  width: 100%; aspect-ratio: 1;
  background: linear-gradient(-50deg, transparent, var(--shiny-highlight), transparent);
  mask-image: radial-gradient(circle at bottom, transparent 40%, black);
  opacity: .6;
}
.shiny-content { position: relative; z-index: 1; display: inline-flex; align-items: center; gap: 10px; }
.shiny-content::before {
  width: calc(100% + 2.25rem); height: calc(100% + 1rem);
  border-radius: 999px;
  background: radial-gradient(ellipse at center, rgba(255, 74, 28, .34) 0%, rgba(255, 74, 28, .14) 46%, transparent 74%);
  filter: blur(5px); opacity: 0;
  transition: opacity var(--transition);
  animation: calc(var(--duration) * 1.5) shiny-breathe linear infinite;
}
.shiny, .shiny::before, .shiny::after {
  animation: var(--animation) var(--duration), var(--animation) calc(var(--duration) / .4) reverse paused;
  animation-composition: add;
}
.shiny:is(:hover, :focus-visible) {
  --gradient-percent: 20%;
  --gradient-angle-offset: 95deg;
  --gradient-shine: var(--shiny-highlight-subtle);
}
.shiny:is(:hover, :focus-visible),
.shiny:is(:hover, :focus-visible)::before,
.shiny:is(:hover, :focus-visible)::after { animation-play-state: running; }
.shiny:is(:hover, :focus-visible) .shiny-content::before { opacity: 1; }
.shiny .pip { width: 6px; height: 6px; border-radius: 50%; background: var(--signal); box-shadow: 0 0 9px rgba(255, 74, 28, .9); }
.shiny .arr { transition: transform .35s cubic-bezier(.76, 0, .24, 1); }
.shiny:hover .arr { transform: translateX(5px); }

@keyframes gradient-angle { to { --gradient-angle: 360deg; } }
@keyframes shimmer { to { rotate: 360deg; } }
@keyframes shiny-breathe { from, to { scale: 1; } 50% { scale: 1.2; } }

:where(a, button, input, textarea, summary):focus-visible {
  outline: 2px solid var(--signal); outline-offset: 3px; border-radius: 3px;
}

@media (prefers-reduced-motion: reduce) {
  .shiny, .shiny::before, .shiny::after, .shiny-content::before { animation: none !important; }
  .mark-bar, .mark-dot, .mark-dot rect, .mark-ring { transition-duration: 120ms !important; }
}

@media (max-width: 620px) {
  :root { --col: calc(100% - 32px); }
  .shiny { min-width: 0; width: 100%; }
}

/* ---- the cursor --------------------------------------------------------
   A signal dot pinned to the pointer plus a bone ring that trails it, and
   over a destination the ring carries the verb for that destination.
   Only ever active on a fine pointer with motion allowed; `has-cursor` is set
   by the script, so a device that never runs it keeps its native cursor. */
.cursor-ring,
.cursor-caret,
.cursor-dot {
  position: fixed; top: 0; left: 0; z-index: 90;
  pointer-events: none; opacity: 0;
  will-change: transform;
}
/* The ring is two elements on purpose. CSS applies the standalone `scale`
   property after `transform`, so scaling the element that also carries the
   translate scales the position with it and the ring drifts toward the top
   left. The outer element only ever positions; the inner one only ever
   scales. Both stay on the compositor, so no state touches layout. */
.cursor-ring { width: 52px; height: 52px; }
.cursor-ring-shape {
  display: block; width: 100%; height: 100%; border-radius: 999px;
  border: 1px solid rgba(244, 242, 239, .8);
  transform: scale(0.577);
  transition: transform .28s var(--ease), border-color .28s var(--ease), background-color .28s var(--ease);
}
.cursor-caret {
  width: 2px; height: 26px; border-radius: 1px;
  background: rgba(244, 242, 239, .85);
  transition: opacity .2s linear;
}
.cursor-dot {
  width: 6px; height: 6px; border-radius: 999px;
  background: var(--signal);
  transition: opacity .2s linear;
}
.cursor-on .cursor-ring { opacity: 1; }
.cursor-on .cursor-dot { opacity: 1; }
.cursor-on .cursor-caret.is-on { opacity: 1; }

/* over something actionable the ring opens and takes the signal */
.cursor-ring.is-live .cursor-ring-shape {
  transform: scale(1);
  border-color: rgba(255, 74, 28, .95);
  background-color: rgba(255, 74, 28, .08);
}
/* over an input the ring gets out of the caret's way */
.cursor-ring.is-text { opacity: 0 !important; }
.cursor-ring.is-down .cursor-ring-shape { transform: scale(0.42); border-color: rgba(255, 74, 28, 1); }

/* The native cursor is only removed once the custom one is actually running,
   and `.has-cursor` is set by the script, so a device that never runs it keeps
   its own cursor. `!important` is deliberate and scoped: page sheets set their
   own `cursor: pointer` on filters, pills and rows, and those beat a plain
   inherited value, which left the OS arrow showing on some pages and not
   others. This is the single place that decides. */
.has-cursor,
.has-cursor * { cursor: none !important; }
/* typing and text selection keep their real affordances */
.has-cursor :is(input:not([type="button"]):not([type="submit"]), textarea, [contenteditable="true"]) {
  cursor: text !important;
}

@media (hover: none), (pointer: coarse) {
  .cursor-ring, .cursor-caret, .cursor-dot { display: none; }
}
@media (prefers-reduced-motion: reduce) {
  .cursor-ring, .cursor-caret, .cursor-dot { display: none; }
  .has-cursor, .has-cursor * { cursor: auto !important; }
  .has-cursor :is(input, textarea, [contenteditable="true"]) { cursor: text !important; }
}

/* the skip link is the first thing a keyboard reaches; it must be visible then */
.skip-link {
  position: fixed; top: max(10px, env(safe-area-inset-top)); left: max(10px, env(safe-area-inset-left)); z-index: 200;
  max-width: calc(100vw - 20px); transform: translate3d(0, calc(-100% - 24px), 0);
  min-height: 44px; display: inline-flex; align-items: center;
  padding: 12px 18px; border-radius: 999px;
  background: var(--ink); color: var(--paper);
  border: 1px solid var(--line-hi); text-decoration: none;
  font: 500 13px/1 Archivo, sans-serif;
  transition: transform .25s var(--ease);
}
.skip-link:focus-visible { transform: none; outline: 2px solid var(--signal); outline-offset: 3px; }
main:focus { outline: none; }

@media (max-width: 620px) {
  .skip-link { min-height: 44px; padding-inline: 14px; font-size: 12.5px; }
}

/* ---- the ordinary button ----------------------------------------------
   One loud button per site. `.shiny` belongs to the home hero and nowhere
   else; every other action is this: a hairline pill that fills a little on
   hover and moves its arrow. It shares the CTA's radius, height and label
   size, so the family is obvious and the hierarchy is unmistakable. */
.btn {
  min-height: 46px; display: inline-flex; align-items: center; gap: 10px;
  padding: 0 20px; border: 1px solid var(--line-hi); border-radius: 999px;
  background: transparent; color: var(--paper); cursor: pointer; text-decoration: none;
  font: 500 13.5px/1 Archivo, sans-serif; letter-spacing: -.002em;
  transition: border-color .25s var(--ease), background .25s var(--ease), color .25s var(--ease);
}
.btn:hover { border-color: rgba(255, 74, 28, .5); background: rgba(255, 74, 28, .06); }
.btn .icon, .btn .arr { color: var(--signal); transition: transform .3s var(--ease); }
.btn:hover .icon, .btn:hover .arr { transform: translateX(4px); }
/* the send action is the one place a form is committed, so it carries a fill */
.btn--send { border-color: rgba(255, 74, 28, .42); background: rgba(255, 74, 28, .1); }
.btn--send:hover { background: rgba(255, 74, 28, .18); border-color: rgba(255, 74, 28, .7); }

/* ---- the cursor's word -------------------------------------------------
   Over a destination the ring names the action: View for a case study or the
   resume, Read for a log or a thought. The word is derived from the href, so
   a piece written next week gets the right verb without being tagged. The
   ring grows to hold it rather than the word floating loose beside it. */
.cursor-label {
  position: absolute; inset: 0;
  display: flex; align-items: center; justify-content: center;
  color: var(--paper); font: 500 11px/1 "JetBrains Mono", monospace;
  letter-spacing: .1em; text-transform: uppercase;
  opacity: 0; transition: opacity .2s var(--ease);
  pointer-events: none; white-space: nowrap;
}
.cursor-ring.has-label .cursor-label { opacity: 1; }
/* When the ring carries a word, the word IS the cursor. The precision dot is
   pinned to the exact pointer, which lands in the middle of the label and eats
   a letter, so it stands down for as long as a word is showing. */
.cursor-ring.has-label ~ .cursor-dot { opacity: 0 !important; }
.cursor-ring.has-label .cursor-ring-shape {
  transform: scale(1.42);
  border-color: rgba(255, 74, 28, .95);
  background-color: rgba(255, 74, 28, .14);
}
/* a press still registers while the ring is carrying a word */
.cursor-ring.has-label.is-down .cursor-ring-shape { transform: scale(1.22); }

/* ---- document baseline, ported from the retired editorial.css ----------
   editorial.css carried 214 selectors, of which 22 still matched anything.
   These are the global ones. They live here now so a single sheet owns the
   document's baseline instead of a retired sheet setting it and a newer one
   quietly overriding half of it.

   Deliberately NOT ported from it: `font: 300 16px/1.6 "IBM Plex Sans"`.
   That face was never in the font request either, so it had always been
   falling back; the size and leading it did carry move to pages.css, where
   they belong to non-home surfaces only. */
* { box-sizing: border-box; }
html { scroll-behavior: smooth; overflow-x: clip; }
body { min-height: 100vh; margin: 0; overflow-x: clip; isolation: isolate; }
h1, h2 { margin: 0; font-family: Archivo, sans-serif; letter-spacing: -.03em; }
@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }
}

/* ---- hover preview -----------------------------------------------------
   A small video panel that follows the pointer over a row that declares one.
   Fixed, so it is never clipped by a band's overflow, and pointer-events off
   so it can never sit between the pointer and the link it is describing.

   No shadow: this world has no shadow system. The panel separates from the
   page with a hairline and with the light in the footage itself, which is the
   same logic the tiles and the opening panel use. */
.hover-preview {
  position: fixed; top: 0; left: 0; z-index: 70;
  width: 296px;
  /* The default matches the first preview; each row can declare its own via
     `data-preview-ratio`, because the two recordings are 1.52 and 1.62 and
     forcing one shape on both would crop whichever lost. */
  aspect-ratio: var(--preview-ratio, 1.5254);
  border-radius: 8px; overflow: hidden;
  background: var(--ink);
  border: 1px solid var(--line-hi);
  pointer-events: none;
  opacity: 0;
  /* scale and opacity only; the position is driven by transform in the script,
     so the two are composed into one transform there and the scale rides here */
  transition: opacity .16s var(--ease);
  will-change: transform;
}
.hover-preview video {
  display: block; width: 100%; height: 100%;
  object-fit: cover;
  /* the footage is a bright product screen against a near-black site; a small
     lift keeps it from glaring at 296px */
  opacity: .92;
}
.hover-preview.is-on {
  opacity: 1;
  transition: opacity .26s var(--ease);
}

@media (prefers-reduced-motion: reduce) {
  /* the panel still appears, it simply does not play; the poster carries it */
  .hover-preview { transition: opacity .01ms; }
}
