@charset "UTF-8";

/* ==========================================================================
   Sierra Peterson — design tokens
   All colors live here as custom properties; never hard-code a hex in a
   component rule. The accent is Yves Klein Blue (#002FA7), the site's signature.
   ========================================================================== */

:root {
  /* Light theme (default) — a soft warm "paper" ground rather than pure white,
     with --surface kept a step lighter so raised elements still read. */
  --bg: #f7f6f3;
  --bg-soft: #ecebe6;
  --surface: #fffefb;
  --ink: #1c1c1e;
  --muted: #4d515a;             /* darkened (from #545861) to hold WCAG AA over the aurora field */
  --line: rgba(20, 21, 26, 0.12);
  --accent: #002fa7;            /* Yves Klein Blue */
  --accent-deep: #001f73;       /* deep ultramarine — the caustic filaments' core hue */
  --accent-soft: rgba(0, 47, 167, 0.12);
  --ink-display: #1c1c1e;       /* the name is set in ink black; pointer proximity steeps it Klein blue */
  --on-accent: #ffffff;
  --shadow: 0 24px 60px -28px rgba(0, 20, 80, 0.40);

  /* Prism tints — the fallback glow's share of the spectrum (the WebGL field
     generates its own). Kept translucent and pale: scattered light, not paint. */
  --prism-rose: rgba(255, 84, 112, 0.075);
  --prism-amber: rgba(255, 176, 32, 0.07);
  --prism-teal: rgba(0, 190, 160, 0.07);
  --prism-violet: rgba(122, 66, 255, 0.085);

  /* Dispersion fringes for the name's water engine (app.js): where the word's
     surface bends, these split off the ink's shoulders as text-shadows. */
  --fringe-warm: #ff4d6d;
  --fringe-cool: #3f7dff;

  --font-ui: system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
  --font-display: "General Sans", "Helvetica Neue", Helvetica, Arial, sans-serif;
  /* Caps tracking — shared by the name and handles so the compensation
     (text-indent swallows the trailing letter-space) stays in one place. */
  --track-display: 0.06em;
  --track-hairline: 0.16em;

  --ease: cubic-bezier(0.22, 1, 0.36, 1);
  --speed: 0.4s;

  /* Ambient glow position — nudged by the pointer (see app.js) */
  --spot-x: 42%;
  --spot-y: 40%;
}

html[data-theme="dark"] {
  --bg: #1c1c1e;
  --bg-soft: #2c2c2e;
  --surface: #2c2c2e;
  --ink: #f1f0ec;
  --muted: #a6aab4;            /* lightened from #9a9ea8 to hold WCAG AA over the animated bg */
  --line: rgba(241, 240, 236, 0.14);
  --accent: #7e96ff;            /* lightened Yves blue for contrast on dark */
  --accent-deep: #3d56c9;       /* richer ultramarine core for the dark caustics */
  --accent-soft: rgba(126, 150, 255, 0.15);
  --ink-display: #f6f8ff;       /* paper-white with a breath of blue on dark */
  --on-accent: #1c1c1e;
  --shadow: 0 24px 70px -30px rgba(0, 0, 0, 0.8);

  /* Dark gets slightly brighter prisms — jewel light in deep water */
  --prism-rose: rgba(255, 96, 128, 0.10);
  --prism-amber: rgba(255, 186, 64, 0.085);
  --prism-teal: rgba(48, 214, 180, 0.09);
  --prism-violet: rgba(148, 96, 255, 0.11);
  --fringe-warm: #ff7a94;
  --fringe-cool: #8fa6ff;
}

/* ==========================================================================
   Type — General Sans (ITF Free Font License), self-hosted variable font
   --------------------------------------------------------------------------
   One woff2 (~38 KB), preloaded from <head> so it is almost always ready
   before first paint. Axis: wght 200–700. Everything display-facing is set
   in ALL CAPS with open tracking — a modern, unadorned grotesque where the
   identity comes from color, spacing, and motion rather than ornament. The
   handles and modal title share the face for cohesion; small interface text
   (footer, kbd caps) stays on the system stack.
   ========================================================================== */

@font-face {
  font-family: "General Sans";
  font-style: normal;
  font-weight: 200 700;
  font-display: swap;
  src: url("fonts/general-sans-var.woff2") format("woff2");
}

/* ==========================================================================
   Base
   ========================================================================== */

*,
*::before,
*::after {
  box-sizing: border-box;
}

html {
  font-family: var(--font-ui);
  font-size: 100%;
  -webkit-text-size-adjust: 100%;
}

body {
  margin: 0;
  min-height: 100svh;
  background-color: var(--bg);
  color: var(--ink);
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;
  transition: background-color var(--speed) var(--ease), color var(--speed) var(--ease);
}

/* The "prism water" WebGL canvas sits behind the content (above the fallback
   glow). It's revealed by background.js once the first frame is painted, so it
   fades in rather than flashing. With prefers-reduced-motion the global rule
   below removes the transition, so it simply appears. */
.bg-canvas {
  position: fixed;
  inset: 0;
  width: 100%;
  height: 100%;
  z-index: -1;
  display: block;
  pointer-events: none;
  opacity: 0;
  transition: opacity 1.2s var(--ease);
}

/* background.js sets [hidden] when WebGL2 is unavailable; honor it over the
   display:block above so the fallback glow is the only thing painted. */
.bg-canvas[hidden] {
  display: none;
}

/* A soft, chic glow behind the composition. This is the fallback layer: it sits
   one step further back (z-index -2) and is covered by .bg-canvas whenever the
   WebGL background is active; it shows through when WebGL2 is unavailable. */
body::before {
  content: "";
  position: fixed;
  inset: 0;
  z-index: -2;
  background:
    radial-gradient(50% 48% at var(--spot-x) var(--spot-y), var(--accent-soft), transparent 70%),
    radial-gradient(34% 56% at 70% 14%, var(--accent-soft), transparent 72%),
    radial-gradient(30% 44% at 82% 40%, var(--prism-rose), transparent 70%),
    radial-gradient(26% 40% at 58% 74%, var(--prism-amber), transparent 70%),
    radial-gradient(32% 46% at 18% 78%, var(--prism-teal), transparent 72%),
    radial-gradient(36% 50% at 10% 22%, var(--prism-violet), transparent 72%),
    radial-gradient(40% 60% at 66% 88%, var(--accent-soft), transparent 74%),
    radial-gradient(45% 45% at 12% 50%, var(--bg-soft), transparent 62%);
  opacity: 0.9;
  pointer-events: none;
  transition: background-position var(--speed) var(--ease);
}

/* Chic detail: brand-tinted text selection */
::selection {
  background: var(--accent);
  color: var(--on-accent);
}

.page {
  display: flex;
  flex-direction: column;
  min-height: 100svh;
  padding: clamp(1rem, 3vw, 2rem);
}

/* ==========================================================================
   Controls (bottom-left cluster, sits in the footer)
   ========================================================================== */

.controls {
  display: flex;
  align-items: center;
  gap: 0.25rem;
  /* Pull the transparent buttons left so the icon glyphs optically align
     with the page edge / copyright text rather than the button padding. */
  margin-left: -0.5rem;
}

.icon-btn {
  display: inline-grid;
  place-items: center;
  width: 2.5rem;
  height: 2.5rem;
  padding: 0;
  color: var(--muted);
  background: transparent;
  border: 1px solid transparent;
  border-radius: 999px;
  cursor: pointer;
  transition: color var(--speed) var(--ease),
              background-color var(--speed) var(--ease),
              border-color var(--speed) var(--ease),
              transform 0.2s var(--ease);
}

.icon-btn:hover {
  color: var(--ink);
  background: var(--surface);
  border-color: var(--line);
}

.icon-btn:active {
  transform: scale(0.92);
}

.icon-btn:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
}

.icon {
  display: block;
}

/* Show the icon that lets you switch *to* the other theme */
.icon--moon { display: none; }
html[data-theme="dark"] .icon--sun { display: none; }
html[data-theme="dark"] .icon--moon { display: block; }

/* ==========================================================================
   Stage — the name + links
   ========================================================================== */

.stage {
  flex: 1;
  display: flex;
  align-items: center;
  justify-content: center;
  padding-inline: clamp(1rem, 4vw, 2rem);
}

.intro {
  display: flex;
  flex-direction: column;
  align-items: center;
  text-align: center;
  /* Name and handles read as one centered lockup — close, not floating apart */
  gap: clamp(0.9rem, 2.2vh, 1.4rem);
  max-width: 100%;
}

/* The name — modern unadorned caps in ink black (paper-white on dark);
   pointer proximity steeps it Klein blue. Medium weight (500 on the wght
   axis) with open tracking; character comes from color, air, and motion
   rather than the letterforms themselves. The text-indent mirrors the
   trailing letter-space so caps center optically. */
.name {
  margin: 0;
  font-family: var(--font-display);
  font-size: clamp(2.5rem, 7vw, 6.75rem);
  font-weight: 500;
  line-height: 1.06;
  text-transform: uppercase;
  letter-spacing: var(--track-display);
  text-indent: var(--track-display);
  text-wrap: balance;
  text-box: trim-both cap alphabetic; /* optical centering where supported */
  color: var(--ink-display);
  -webkit-tap-highlight-color: transparent;
  opacity: 0;
  transform: translateY(14px);
  animation: rise 1.1s var(--ease) 0.05s forwards;
  transition: color 0.9s var(--ease);
}

/* Small portrait screens get their own type scale: the name breaks into a
   two-line display block that anchors the composition without shouting. */
@media (max-width: 600px) {
  .name {
    font-size: clamp(2rem, 10.5vw, 3.3rem);
    line-height: 1.14;
  }
}

/* Per-letter structure, built by app.js. The h1 keeps an aria-label with the
   real name; the visual letters are aria-hidden spans. Words stay whole so
   the line can still wrap between them on phones. Once split, the h1's own
   entrance animation is disabled — the letters carry it instead, surfacing
   in a quiet cascade (--i is the letter index, set inline by app.js).
   After each letter's entrance finishes, app.js removes the animation so the
   pointer engine's inline transforms take over (a forwards fill would
   otherwise pin the animated value and block them). */
.name.is-split {
  animation: none;
  opacity: 1;
  transform: none;
}

.name__w {
  display: inline-block;
  white-space: nowrap;
}

/* The hidden pre-entrance state lives ONLY inside the keyframes (fill-mode
   "both" applies it through the stagger delay). The base state here must be
   the clean resting state: app.js removes the animation after it ends so its
   forwards fill can't pin the letters, and whatever the pointer engine does
   must land back on this rule when it clears its inline styles. */
.name__l {
  display: inline-block;
  /* Pivot near the baseline so the water engine's tilt reads as a glyph
     rocking on the surface, not spinning around its middle. */
  transform-origin: 50% 78%;
  animation: letter-rise 0.9s var(--ease) both;
  animation-delay: calc(0.08s + var(--i) * 0.042s);
}

/* Fallback only: if app.js never split the name (JS off / very old browser),
   the whole name steeps to Klein blue on hover / tap. When the per-letter
   engine is running it handles ink itself, letter by letter, and this rule
   must stay out of its way. The :hover is gated to hover-capable devices so
   iOS sticky-hover can't leave the name stuck blue. */
@media (hover: hover) {
  .name:not(.is-split):hover {
    color: var(--accent);
  }
}

.name:not(.is-split):active {
  color: var(--accent);
}

/* Small-caps handles, split by a hairline divider, tucked close under the name */
.links {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: clamp(0.85rem, 3vw, 1.5rem);
  opacity: 0;
  transform: translateY(16px);
  /* Follows the name's letter cascade rather than racing it */
  animation: rise 0.9s var(--ease) 0.5s forwards;
}

.link {
  display: inline-flex;
  align-items: center;
  gap: 0.45rem;
  padding: 0.4rem 0.5rem;
  font-family: var(--font-display);
  font-size: clamp(0.78rem, 1.3vw, 0.9rem);
  font-weight: 500;
  text-transform: uppercase;
  letter-spacing: var(--track-hairline);
  text-indent: var(--track-hairline);
  text-decoration: none;
  color: var(--muted);
  border-radius: 0.4rem;
  transition: color var(--speed) var(--ease), transform var(--speed) var(--ease);
}

/* Hairline between the two handles */
.links__divider {
  flex: none;
  width: 1px;
  height: 1.1em;
  background: var(--line);
}

.link:hover,
.link:focus-visible {
  color: var(--accent);
}

.link:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 3px;
}

/* The label keeps a subtle underline that grows on interaction */
.link__label {
  position: relative;
}

.link__label::after {
  content: "";
  position: absolute;
  left: 0;
  right: 100%;
  bottom: -0.2em;
  height: 1.5px;
  background: currentColor;
  transition: right var(--speed) var(--ease);
}

.link:hover .link__label::after,
.link:focus-visible .link__label::after {
  right: 0;
}

/* ==========================================================================
   Footer
   ========================================================================== */

.footer {
  display: flex;
  align-items: center;
  justify-content: space-between;
  flex-wrap: wrap;
  gap: 0.5rem;
  font-size: 0.8rem;
  font-variant-numeric: tabular-nums;
  letter-spacing: 0.01em;
  color: var(--muted);
  opacity: 0;
  animation: rise 0.9s var(--ease) 0.75s forwards;
}

/* ==========================================================================
   Keyboard caps
   ========================================================================== */

kbd {
  display: inline-grid;
  place-items: center;
  min-width: 1.65em;
  padding: 0.15em 0.45em;
  font-family: var(--font-ui);
  font-size: 0.82em;
  line-height: 1;
  color: var(--ink);
  background: var(--surface);
  border: 1px solid var(--line);
  border-bottom-width: 2px;
  border-radius: 0.4em;
}

.combo {
  display: inline-flex;
  align-items: center;
  gap: 0.3em;
  white-space: nowrap;
}

/* ==========================================================================
   Modal
   ========================================================================== */

.modal {
  width: min(420px, calc(100vw - 2rem));
  padding: 0;
  color: var(--ink);
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: 16px;
  box-shadow: var(--shadow);
}

.modal::backdrop {
  background: rgba(28, 28, 30, 0.45);   /* #1c1c1e at 45% — matches the design black */
  backdrop-filter: blur(3px);
}

/* Entrance animation for the dialog */
.modal[open] {
  animation: modal-in 0.32s var(--ease);
}

.modal[open]::backdrop {
  animation: fade-in 0.32s var(--ease);
}

.modal__head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 1.1rem 1.1rem 0.6rem 1.4rem;
}

.modal__title {
  margin: 0;
  font-family: var(--font-display);
  font-weight: 560;
  font-size: 1.15rem;
  letter-spacing: 0.01em;
}

.shortcut-list {
  list-style: none;
  margin: 0;
  padding: 0.3rem 1.4rem 1.4rem;
}

.shortcut-list li {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 1rem;
  padding: 0.7rem 0;
  border-top: 1px solid var(--line);
  font-size: 0.92rem;
}

.shortcut-list li:first-child {
  border-top: none;
}

.shortcut-list__label {
  color: var(--muted);
}

/* Destination rows are live links: muted at rest like the other labels, then
   the same Klein-blue shift + growing hairline underline as the main handles. */
.shortcut-list__link {
  position: relative;
  text-decoration: none;
  transition: color var(--speed) var(--ease);
}

.shortcut-list__link::after {
  content: "";
  position: absolute;
  left: 0;
  right: 100%;
  bottom: -0.15em;
  height: 1px;
  background: currentColor;
  transition: right var(--speed) var(--ease);
}

.shortcut-list__link:hover,
.shortcut-list__link:focus-visible {
  color: var(--accent);
}

.shortcut-list__link:hover::after,
.shortcut-list__link:focus-visible::after {
  right: 0;
}

.shortcut-list__link:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 3px;
  border-radius: 0.2em;
}

/* ==========================================================================
   Animations
   ========================================================================== */

@keyframes rise {
  from { opacity: 0; transform: translateY(16px); }
  to   { opacity: 1; transform: translateY(0); }
}

@keyframes letter-rise {
  from { opacity: 0; transform: translateY(0.4em); }
  to   { opacity: 1; transform: translateY(0); }
}

@keyframes modal-in {
  from { opacity: 0; transform: translateY(8px) scale(0.98); }
  to   { opacity: 1; transform: translateY(0) scale(1); }
}

@keyframes fade-in {
  from { opacity: 0; }
  to   { opacity: 1; }
}

/* Honor users who prefer less motion: no entrance transforms, near-instant transitions */
@media (prefers-reduced-motion: reduce) {
  *,
  *::before,
  *::after {
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.01ms !important;
  }
  .name,
  .name__l,
  .links,
  .footer {
    opacity: 1;
    transform: none;
  }
}
