/* ==========================================================================
   components.css — panels, buttons, tiles, flat icon badges, cursor, backdrop
   Mobile-first. Breakpoint overrides live in responsive.css.
   ========================================================================== */

/* --------------------------------------------------------------------------
   Layout shell
   -------------------------------------------------------------------------- */

.shell {
  width: 100%;
  max-width: var(--width-content);
  margin-inline: auto;
  padding-inline: var(--gutter);
}

.sprite {
  position: absolute;
  width: 0;
  height: 0;
  overflow: hidden;
}

.section {
  position: relative;
  z-index: var(--z-content);
  padding-block: var(--spacing-2xl);
}

.section__head {
  display: flex;
  align-items: center;
  gap: var(--spacing-sm);
  margin-bottom: var(--spacing-lg);
}

/* Keeps the title block from collapsing against the trailing hairline */
.section__heading {
  flex: 0 1 auto;
  min-width: 0;
}

.section__title {
  margin: 0;
  color: var(--color-text);
}

.section__sub {
  margin-top: var(--spacing-3xs);
  color: var(--color-text-muted);
  font-size: var(--text-sm);
}

/* Hairline rule under each section heading */
.section__head::after {
  content: "";
  flex: 1;
  height: 1px;
  background-image: var(--gradient-hairline);
}

/* --------------------------------------------------------------------------
   Backdrop: WebGL shader (or CSS fallback field) + grain + vignette

   The WebGL canvas is the bottom layer. When it is running, .has-shader hides
   the CSS glow/sweep below and the shader is the whole light field; otherwise
   those two drifting gradient layers are the fallback. Grain sits on top of
   either — large smooth gradients band on 8-bit displays, and a few percent of
   fractal noise breaks the banding for ~1 KB. The vignette frames it all.
   -------------------------------------------------------------------------- */

.backdrop {
  position: fixed;
  inset: 0;
  z-index: var(--z-backdrop);
  overflow: hidden;
  pointer-events: none;
}

/* WebGL flowing gradient (scripts/backdrop.js). Bottom-most layer, opaque, so
   the dots/grain/vignette above still read over it. Fades in once the first
   frame is drawn to avoid a flash of the raw canvas. */
.backdrop__shader {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  display: block;
  opacity: 0;
  transition: opacity 900ms var(--ease);
}

.has-shader .backdrop__shader {
  opacity: 1;
}

/* When the shader is live it is the light field, so the CSS glow + sweep are
   removed — leaving them would add light the shader's contrast guarantee does
   not account for. They stay put as the fallback when .has-shader is absent. */
.has-shader .backdrop__glow,
.has-shader .backdrop__sweep {
  display: none;
}

/* Primary light: main bloom up-left, aqua highlight top-right, an indigo
   deepening on the right for hue variation, and a teal lift along the base. */
.backdrop__glow {
  position: absolute;
  inset: -12%;
  background-image:
    radial-gradient(58% 46% at 24% 12%, rgba(38, 158, 205, 0.3), transparent 66%),
    radial-gradient(46% 40% at 84% 4%, rgba(64, 210, 245, 0.16), transparent 68%),
    radial-gradient(52% 44% at 94% 60%, rgba(46, 96, 190, 0.17), transparent 70%),
    radial-gradient(88% 56% at 40% 104%, rgba(14, 130, 160, 0.28), transparent 72%),
    radial-gradient(40% 34% at 6% 76%, rgba(20, 120, 150, 0.14), transparent 72%);
  will-change: transform;
  animation: glow-drift 38s var(--ease-in-out) infinite alternate;
}

/* Secondary light on a much slower clock. Because the two layers never line
   up, the field keeps shifting without any single element looking like it is
   moving — that is what sells depth. */
.backdrop__sweep {
  position: absolute;
  inset: -16%;
  background-image:
    radial-gradient(50% 38% at 66% 26%, rgba(120, 220, 255, 0.1), transparent 70%),
    radial-gradient(44% 36% at 16% 54%, rgba(80, 190, 235, 0.08), transparent 72%);
  will-change: transform;
  animation: sweep-drift 61s var(--ease-in-out) infinite alternate;
}

/* Monochrome fractal noise, tiled. Self-contained data URI — no request. */
.backdrop__grain {
  position: absolute;
  inset: 0;
  opacity: 0.038;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='200' height='200'%3E%3Cfilter id='g'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.85' numOctaves='3' stitchTiles='stitch'/%3E%3CfeColorMatrix type='saturate' values='0'/%3E%3C/filter%3E%3Crect width='200' height='200' filter='url(%23g)'/%3E%3C/svg%3E");
  background-size: 200px 200px;
}

/* Corner falloff over everything, so the grain fades with the light */
.backdrop__vignette {
  position: absolute;
  inset: 0;
  background-image: radial-gradient(
    112% 92% at 50% 40%,
    transparent 32%,
    rgba(2, 7, 11, 0.58) 100%
  );
}

@keyframes glow-drift {
  from {
    transform: translate3d(-1.6%, -1.2%, 0) scale(1.02);
  }
  to {
    transform: translate3d(1.6%, 1.4%, 0) scale(1.08);
  }
}

@keyframes sweep-drift {
  from {
    transform: translate3d(2.4%, 1.8%, 0) scale(1.06);
  }
  to {
    transform: translate3d(-2.2%, -1.4%, 0) scale(1);
  }
}

/* --------------------------------------------------------------------------
   Panel — the base surface
   One flat translucent tint, a hairline border, a light backdrop blur. That
   is the whole material. No sheen pseudo-element, no bevel stack, no
   gradient: the previous version simulated a pane of glass, this one is
   simply a surface.
   -------------------------------------------------------------------------- */

.panel {
  position: relative;
  border: 1px solid var(--panel-border);
  border-radius: var(--radius-lg);
  background-color: var(--panel-bg);
  box-shadow: var(--shadow-panel);
  -webkit-backdrop-filter: blur(var(--panel-blur));
  backdrop-filter: blur(var(--panel-blur));
}

.panel--raised {
  border-color: var(--panel-border-strong);
  background-color: var(--panel-bg-raised);
  box-shadow: var(--shadow-lg);
}

/* --------------------------------------------------------------------------
   Icon badge — flat
   The pictograms now paint with currentColor, so a single CSS colour drives
   the whole icon. That is only possible because they carry no built-in
   gradients or gloss any more.
   -------------------------------------------------------------------------- */

.badge {
  display: inline-grid;
  flex: 0 0 auto;
  place-items: center;
  width: 3rem;
  height: 3rem;
  color: var(--color-aqua-300);
}

.badge--lg {
  width: 3.5rem;
  height: 3.5rem;
}

.badge--sm {
  width: 2.25rem;
  height: 2.25rem;
}

.badge__icon {
  width: 100%;
  height: 100%;
  transition: color var(--duration-base) var(--ease);
}

/* Loading arc on the Coming Soon badge. fill-box puts the origin at the
   circle's own centre, so it spins true instead of orbiting. */
.spinner__arc {
  transform-box: fill-box;
  transform-origin: center;
  animation: spinner-rotate 1.15s linear infinite;
}

@keyframes spinner-rotate {
  to {
    transform: rotate(360deg);
  }
}

/* --------------------------------------------------------------------------
   Buttons — flat aqua pills
   -------------------------------------------------------------------------- */

.btn {
  position: relative;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--spacing-2xs);
  padding: 0.7rem 1.4rem;
  border: 1px solid transparent;
  border-radius: var(--radius-pill);
  font-family: var(--font-heading);
  font-size: var(--text-base);
  font-weight: var(--weight-semibold);
  letter-spacing: var(--tracking-wide);
  line-height: 1;
  text-decoration: none;
  white-space: nowrap;
  transition: transform var(--duration-fast) var(--ease-out),
    box-shadow var(--duration-base) var(--ease),
    filter var(--duration-base) var(--ease),
    background-color var(--duration-base) var(--ease),
    border-color var(--duration-base) var(--ease),
    color var(--duration-base) var(--ease);
}

.btn__icon {
  position: relative;
  width: 1em;
  height: 1em;
  flex: 0 0 auto;
}

.btn--sm {
  padding: 0.55rem 1.1rem;
  font-size: var(--text-sm);
}

.btn--primary {
  border-color: transparent;
  background-color: var(--color-aqua-400);
  color: var(--color-text-invert);
  box-shadow: var(--shadow-sm);
}

.btn--primary:hover,
.btn--primary:focus-visible {
  transform: translateY(-2px);
  /* Hover deepens the fill rather than lightening it — aqua-300 washed out
     toward white and the button stopped reading as a distinct element. */
  background-color: var(--color-aqua-500);
  box-shadow: var(--shadow-md), 0 0 26px rgba(23, 162, 201, 0.6);
}

.btn--primary:active {
  transform: translateY(0);
  background-color: var(--color-aqua-600);
}

.btn--ghost {
  border-color: var(--panel-border-strong);
  background-color: var(--panel-bg);
  color: var(--color-aqua-100);
  box-shadow: var(--shadow-sm);
  -webkit-backdrop-filter: blur(14px);
  backdrop-filter: blur(14px);
}

.btn--ghost:hover,
.btn--ghost:focus-visible {
  transform: translateY(-2px);
  border-color: var(--color-aqua-300);
  background-color: var(--panel-bg-raised);
  color: #ffffff;
  box-shadow: var(--shadow-md), var(--bloom-aqua-soft);
}

.btn--quiet {
  border-color: rgba(178, 236, 252, 0.28);
  color: var(--color-aqua-200);
}

.btn--quiet:hover,
.btn--quiet:focus-visible {
  border-color: var(--color-aqua-400);
  background-color: rgba(110, 231, 255, 0.1);
  color: var(--color-aqua-100);
}

/* --------------------------------------------------------------------------
   Header + navigation
   -------------------------------------------------------------------------- */

.site-header {
  position: sticky;
  top: 0;
  z-index: var(--z-header);
  transition: background-color var(--duration-base) var(--ease),
    box-shadow var(--duration-base) var(--ease),
    backdrop-filter var(--duration-base) var(--ease);
}

/* Frosts in once the page scrolls (class toggled by main.js) */
.site-header.is-stuck {
  background-color: rgba(4, 12, 18, 0.72);
  box-shadow: 0 1px 0 rgba(178, 236, 252, 0.14), var(--shadow-md);
  -webkit-backdrop-filter: blur(18px) saturate(150%);
  backdrop-filter: blur(18px) saturate(150%);
}

.site-header__inner {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--spacing-sm);
  width: 100%;
  max-width: var(--width-content);
  min-height: var(--header-height);
  margin-inline: auto;
  padding-inline: var(--gutter);
}

.brand {
  position: relative;
  display: inline-flex;
  align-items: center;
  gap: var(--spacing-2xs);
  /* Room for the traced rectangle; the negative inline start keeps the
     logomark optically aligned with the shell edge despite the padding. */
  margin-left: -0.55rem;
  padding: 0.4rem 0.95rem 0.4rem 0.55rem;
  border-radius: 14px;
  color: var(--color-text);
  text-decoration: none;
}

/* Glowing aqua line tracing the brand's perimeter ------------------------- */

/* width/height are load-bearing, not belt-and-braces: an <svg> with no
   width/height attributes has an intrinsic size of 300x150, and for an
   absolutely positioned *replaced* element `inset: 0` with `width: auto`
   resolves to that intrinsic size rather than to the inset box. Without these
   the frame renders 150px tall no matter how big the brand is. */
.brand__trace {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  overflow: visible;
  pointer-events: none;
}

.brand__trace rect {
  fill: none;
  stroke-linecap: round;
}

/* The travelling segment, and the only thing drawn — there is no rail behind
   it. 16% of the perimeter is lit. pathLength is 1000, so these figures are
   percentages of the perimeter rather than pixels, and the segment keeps its
   proportion whatever the brand measures. */
.brand__trace-comet {
  stroke: var(--color-aqua-300);
  stroke-width: 1.6;
  stroke-dasharray: 3 997;
  filter: drop-shadow(0 0 4px rgba(110, 231, 255, 0.9))
    drop-shadow(0 0 10px rgba(53, 201, 238, 0.55));
  animation: brand-trace 3s linear infinite;
}

/* The dash length is animated alongside the offset, so the streak stretches
   out of a dot, runs the perimeter, then has its tail catch its head and
   collapse back to a dot.

   Both ends must advance monotonically or the streak visibly reverses. With
   pathLength 1000 the head sits at (offset + dash) and the tail at (offset):
     head  173 -> 340 -> 870 -> 1173
     tail  170 -> 173 -> 710 -> 1170

   170 is where the F of "Fonseca" falls along the perimeter, measured from
   the Segoe UI metrics the font stack resolves to: the brand box is 215.7 x
   48.8 with a 14px corner, giving a 505px perimeter, and the F sits 99.8px in
   — (99.8 - 14) / 505 * 1000 = 170. Plain numbers on purpose: driving these
   from a custom property put a calc() in the keyframes and broke the
   animation outright.

   0% and 100% are the same point (1170 = 170 mod 1000), so the loop has no
   seam. */
@keyframes brand-trace {
  0% {
    stroke-dasharray: 3 997;
    stroke-dashoffset: -170;
  }
  10% {
    stroke-dasharray: 167 833;
    stroke-dashoffset: -173;
  }
  34% {
    stroke-dasharray: 160 840;
    stroke-dashoffset: -710;
    animation-timing-function: cubic-bezier(0.33, 0, 0.15, 1);
  }
  58% {
    stroke-dasharray: 3 997;
    stroke-dashoffset: -1170;
  }
  100% {
    stroke-dasharray: 3 997;
    stroke-dashoffset: -1170;
  }
}


.brand:hover .brand__trace-comet,
.brand:focus-visible .brand__trace-comet {
  animation-duration: 1.7s;
}

.brand__mark {
  display: grid;
  place-items: center;
  width: 2.25rem;
  height: 2.25rem;
}

.brand__logo {
  width: auto;
  height: 2.25rem;
  filter: drop-shadow(0 0 10px rgba(110, 231, 255, 0.45));
  transition: filter var(--duration-base) var(--ease), transform var(--duration-base) var(--ease-out);
}

.brand:hover .brand__logo,
.brand:focus-visible .brand__logo {
  transform: translateY(-1px);
  filter: drop-shadow(0 0 18px rgba(110, 231, 255, 0.8));
}

.brand__word {
  display: flex;
  flex-direction: column;
  line-height: 1.15;
}

.brand__name {
  font-family: var(--font-heading);
  font-size: var(--text-lg);
  font-weight: var(--weight-semibold);
  letter-spacing: var(--tracking-tight);
}

.brand__role {
  color: var(--color-text-muted);
  font-size: var(--text-xs);
  letter-spacing: var(--tracking-caps);
  text-transform: uppercase;
}

/* Menu button — hidden from 768px up (see responsive.css) */
.nav-toggle {
  display: grid;
  place-items: center;
  width: 2.75rem;
  height: 2.75rem;
  border: 1px solid var(--panel-border);
  border-radius: var(--radius-md);
  background-color: var(--panel-bg);
    -webkit-backdrop-filter: blur(14px);
  backdrop-filter: blur(14px);
  transition: border-color var(--duration-fast) var(--ease);
}

.nav-toggle:hover {
  border-color: var(--color-aqua-400);
}

.nav-toggle__bars {
  display: grid;
  gap: 4px;
  width: 1.125rem;
}

.nav-toggle__bars i {
  display: block;
  height: 2px;
  border-radius: 2px;
  background-color: var(--color-aqua-200);
  box-shadow: 0 0 6px rgba(110, 231, 255, 0.6);
  transition: transform var(--duration-base) var(--ease-in-out),
    opacity var(--duration-fast) var(--ease);
}

.nav-toggle[aria-expanded="true"] .nav-toggle__bars i:nth-child(1) {
  transform: translateY(6px) rotate(45deg);
}

.nav-toggle[aria-expanded="true"] .nav-toggle__bars i:nth-child(2) {
  opacity: 0;
}

.nav-toggle[aria-expanded="true"] .nav-toggle__bars i:nth-child(3) {
  transform: translateY(-6px) rotate(-45deg);
}

/* Mobile drawer */
.site-nav {
  position: absolute;
  top: calc(var(--header-height) - var(--spacing-2xs));
  right: var(--gutter);
  left: var(--gutter);
  border: 1px solid var(--panel-border-strong);
  border-radius: var(--radius-lg);
  background-color: rgba(5, 16, 24, 0.88);
  box-shadow: var(--shadow-lg);
  -webkit-backdrop-filter: blur(var(--panel-blur)) saturate(150%);
  backdrop-filter: blur(var(--panel-blur)) saturate(150%);
  opacity: 0;
  transform: translateY(-8px) scale(0.98);
  transform-origin: top right;
  visibility: hidden;
  transition: opacity var(--duration-base) var(--ease-out),
    transform var(--duration-base) var(--ease-out),
    visibility 0s linear var(--duration-base);
}

.site-nav.is-open {
  opacity: 1;
  transform: translateY(0) scale(1);
  visibility: visible;
  transition-delay: 0s;
}

.site-nav__list {
  display: grid;
  gap: var(--spacing-3xs);
  margin: 0;
  padding: var(--spacing-2xs);
}

.site-nav__link {
  display: block;
  padding: var(--spacing-2xs) var(--spacing-sm);
  border-radius: var(--radius-md);
  color: var(--color-text);
  font-family: var(--font-heading);
  font-weight: var(--weight-medium);
  text-decoration: none;
  transition: background-color var(--duration-fast) var(--ease),
    color var(--duration-fast) var(--ease);
}

.site-nav__link:hover,
.site-nav__link:focus-visible {
  background-color: rgba(110, 231, 255, 0.14);
  color: var(--color-aqua-200);
}

.site-nav__link.is-current {
  color: var(--color-aqua-300);
}

.site-nav__link--cta {
  border: 1px solid transparent;
  background-color: var(--color-aqua-400);
  color: var(--color-text-invert);
  text-align: center;
}

.site-nav__link--cta:hover,
.site-nav__link--cta:focus-visible {
  background-color: var(--color-aqua-300);
  color: var(--color-text-invert);
}

.site-nav__link--cta.is-current {
  color: var(--color-text-invert);
}

/* --------------------------------------------------------------------------
   Hero
   -------------------------------------------------------------------------- */

.hero {
  position: relative;
  z-index: var(--z-content);
  padding-block: var(--spacing-2xl) var(--spacing-xl);
}

.eyebrow {
  display: inline-flex;
  align-items: center;
  gap: var(--spacing-2xs);
  margin-bottom: var(--spacing-md);
  padding: 0.35rem 0.9rem;
  border: 1px solid var(--panel-border);
  border-radius: var(--radius-pill);
  background-color: var(--panel-bg);
    color: var(--color-text-muted);
  font-size: var(--text-xs);
  letter-spacing: var(--tracking-caps);
  text-transform: uppercase;
  -webkit-backdrop-filter: blur(14px);
  backdrop-filter: blur(14px);
}

.eyebrow__dot {
  width: 7px;
  height: 7px;
  border-radius: 50%;
  background-color: var(--color-lime-400);
  box-shadow: var(--bloom-lime);
}

.hero__title {
  max-width: 18ch;
  margin-bottom: var(--spacing-md);
}

.hero__accent {
  color: var(--color-aqua-300);
}

.hero__lede {
  max-width: var(--width-prose);
  color: var(--color-text-muted);
  font-size: var(--text-lg);
}

.hero__actions {
  display: flex;
  flex-wrap: wrap;
  gap: var(--spacing-2xs);
  margin-top: var(--spacing-lg);
}

.hero__stats {
  display: grid;
  gap: var(--spacing-sm);
  margin-top: var(--spacing-xl);
  padding: 0;
}

.hero__stat {
  padding-left: var(--spacing-sm);
  border-left: 1px solid rgba(178, 236, 252, 0.22);
}

.hero__stat dt {
  color: var(--color-text-muted);
  font-size: var(--text-xs);
  letter-spacing: var(--tracking-caps);
  text-transform: uppercase;
}

.hero__stat dd {
  margin: 0;
  font-family: var(--font-heading);
  font-size: var(--text-base);
  font-weight: var(--weight-medium);
}

/* --------------------------------------------------------------------------
   Filters
   -------------------------------------------------------------------------- */

.filters {
  display: flex;
  flex-wrap: wrap;
  gap: var(--spacing-3xs);
  margin-bottom: var(--spacing-md);
}

.chip {
  padding: 0.4rem 0.95rem;
  border: 1px solid var(--panel-border);
  border-radius: var(--radius-pill);
  background-color: var(--panel-bg);
    color: var(--color-text-muted);
  font-size: var(--text-sm);
  font-weight: var(--weight-medium);
  -webkit-backdrop-filter: blur(12px);
  backdrop-filter: blur(12px);
  transition: color var(--duration-fast) var(--ease),
    border-color var(--duration-fast) var(--ease),
    box-shadow var(--duration-base) var(--ease);
}

.chip:hover {
  border-color: var(--color-aqua-400);
  color: var(--color-aqua-100);
}

.chip.is-active {
  border-color: var(--color-aqua-300);
  color: var(--color-aqua-100);
  box-shadow: var(--bloom-aqua-soft);
}

/* --------------------------------------------------------------------------
   Project grid + tiles
   -------------------------------------------------------------------------- */

.grid {
  display: grid;
  gap: var(--spacing-md);
}

.tile {
  display: flex;
  flex-direction: column;
  padding: var(--spacing-md);
  transition: transform var(--duration-base) var(--ease-out),
    box-shadow var(--duration-base) var(--ease),
    border-color var(--duration-base) var(--ease),
    background-image var(--duration-base) var(--ease);
}

.tile:hover,
.tile:focus-within {
  transform: translateY(-4px);
  border-color: var(--panel-border-strong);
  background-color: var(--panel-bg-raised);
  box-shadow: var(--shadow-lg), var(--bloom-aqua-soft);
}

.tile:hover .badge__icon {
  color: var(--color-aqua-200);
}

/* Hidden by the filter controls */
.tile.is-filtered-out {
  display: none;
}

.tile__top {
  position: relative;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--spacing-2xs);
  margin-bottom: var(--spacing-sm);
}

.tile__title {
  position: relative;
  margin-bottom: var(--spacing-3xs);
}

.tile__meta {
  position: relative;
  color: var(--color-text-muted);
  font-size: var(--text-sm);
  letter-spacing: var(--tracking-wide);
}

.tile__body {
  position: relative;
  margin-top: var(--spacing-xs);
  color: var(--color-text-muted);
}

.tile__actions {
  position: relative;
  display: flex;
  flex-wrap: wrap;
  gap: var(--spacing-2xs);
  margin-top: var(--spacing-md);
}

/* Push the actions to the bottom so tiles in a row line up */
.tile .tile__actions {
  margin-top: auto;
  padding-top: var(--spacing-md);
}

/* --------------------------------------------------------------------------
   Status pills + tags
   -------------------------------------------------------------------------- */

.status {
  display: inline-flex;
  align-items: center;
  gap: var(--spacing-3xs);
  padding: 0.28rem 0.7rem;
  border: 1px solid rgba(178, 236, 252, 0.24);
  border-radius: var(--radius-pill);
  background-color: rgba(4, 20, 28, 0.62);
  font-size: var(--text-xs);
  font-weight: var(--weight-semibold);
  letter-spacing: var(--tracking-caps);
  text-transform: uppercase;
  white-space: nowrap;
}

.status__dot {
  width: 7px;
  height: 7px;
  border-radius: 50%;
}

.status--live {
  border-color: rgba(195, 245, 106, 0.4);
  color: var(--color-lime-300);
}

.status--live .status__dot {
  background-color: var(--color-lime-400);
  box-shadow: var(--bloom-lime);
}

.status--wip {
  border-color: rgba(110, 231, 255, 0.42);
  color: var(--color-aqua-200);
}

.status--wip .status__dot {
  background-color: var(--color-aqua-400);
  box-shadow: var(--bloom-aqua);
  animation: pulse 2.4s var(--ease-in-out) infinite;
}

@keyframes pulse {
  0%,
  100% {
    opacity: 1;
    transform: scale(1);
  }
  50% {
    opacity: 0.55;
    transform: scale(0.82);
  }
}

.tags {
  position: relative;
  display: flex;
  flex-wrap: wrap;
  gap: var(--spacing-3xs);
  margin-top: var(--spacing-sm);
  padding: 0;
}

.tag {
  padding: 0.2rem 0.62rem;
  border: 1px solid rgba(178, 236, 252, 0.2);
  border-radius: var(--radius-sm);
  background-color: rgba(9, 34, 47, 0.55);
  color: var(--color-text-muted);
  font-size: var(--text-xs);
  letter-spacing: var(--tracking-wide);
}

/* --------------------------------------------------------------------------
   Coming soon feature panel
   -------------------------------------------------------------------------- */

.section--next {
  padding-block: var(--spacing-2xl);
}

.feature {
  padding: var(--spacing-md);
}

.feature__head {
  position: relative;
  display: flex;
  align-items: center;
  gap: var(--spacing-sm);
  margin-bottom: var(--spacing-md);
}

.feature__title {
  margin-top: var(--spacing-3xs);
  font-size: var(--text-2xl);
}

.feature__lede {
  position: relative;
  max-width: 62ch;
  color: var(--color-text);
  font-size: var(--text-lg);
}

.steps {
  position: relative;
  display: grid;
  gap: var(--spacing-md);
  margin-top: var(--spacing-lg);
  padding: 0;
}

.step {
  display: flex;
  align-items: flex-start;
  gap: var(--spacing-sm);
}

.step__title {
  margin-bottom: var(--spacing-3xs);
  color: var(--color-aqua-200);
}

.step__body {
  color: var(--color-text-muted);
  font-size: var(--text-sm);
}

.feature__note {
  position: relative;
  margin-top: var(--spacing-lg);
  padding-top: var(--spacing-md);
  border-top: 1px solid rgba(178, 236, 252, 0.18);
  color: var(--color-text-muted);
  font-style: italic;
}

/* --------------------------------------------------------------------------
   About
   -------------------------------------------------------------------------- */

.about {
  display: grid;
  gap: var(--spacing-md);
  padding: var(--spacing-md);
  overflow: hidden;
}

.about__text {
  position: relative;
  max-width: var(--width-prose);
  color: var(--color-text-muted);
}

.about__mark {
  position: relative;
  display: grid;
  place-items: center;
}

.about__logo {
  width: auto;
  height: 11rem;
  opacity: 0.9;
  filter: drop-shadow(0 10px 30px rgba(2, 12, 20, 0.6)) drop-shadow(0 0 34px rgba(110, 231, 255, 0.35));
}

/* --------------------------------------------------------------------------
   Contact
   -------------------------------------------------------------------------- */

.contact {
  display: grid;
  gap: var(--spacing-2xs);
  padding: 0;
}

.contact__link {
  display: flex;
  align-items: center;
  gap: var(--spacing-sm);
  padding: var(--spacing-sm) var(--spacing-md);
  border-radius: var(--radius-md);
  color: var(--color-text);
  text-decoration: none;
  transition: transform var(--duration-base) var(--ease-out),
    border-color var(--duration-base) var(--ease),
    box-shadow var(--duration-base) var(--ease),
    background-image var(--duration-base) var(--ease);
}

.contact__link:hover,
.contact__link:focus-visible {
  transform: translateX(4px);
  border-color: var(--panel-border-strong);
  background-color: var(--panel-bg-raised);
  box-shadow: var(--shadow-md), var(--bloom-aqua-soft);
}

.contact__label {
  position: relative;
  min-width: 6rem;
  color: var(--color-text-muted);
  font-size: var(--text-xs);
  letter-spacing: var(--tracking-caps);
  text-transform: uppercase;
}

.contact__value {
  position: relative;
  flex: 1;
  font-family: var(--font-heading);
  font-size: var(--text-lg);
  font-weight: var(--weight-medium);
  overflow-wrap: anywhere;
}

.contact__arrow {
  position: relative;
  width: 0.9rem;
  height: 0.9rem;
  flex: 0 0 auto;
  color: var(--color-aqua-400);
  transition: transform var(--duration-base) var(--ease-out);
}

.contact__link:hover .contact__arrow {
  transform: translate(2px, -2px);
}

/* --------------------------------------------------------------------------
   Footer
   -------------------------------------------------------------------------- */

.site-footer {
  position: relative;
  z-index: var(--z-content);
  margin-top: var(--spacing-2xl);
  padding-block: var(--spacing-lg);
  border-top: 1px solid rgba(178, 236, 252, 0.14);
}

/* Closing quote — the page signs off in Dante's own words, under his mark. */
.quote {
  margin: 0;
  padding-block: var(--spacing-md) 0;
  text-align: center;
}

.quote__text {
  max-width: 26ch;
  margin-inline: auto;
  color: var(--color-text);
  font-family: var(--font-heading);
  font-size: var(--text-2xl);
  font-style: italic;
  font-weight: var(--weight-medium);
  line-height: var(--leading-snug);
  letter-spacing: var(--tracking-tight);
  text-shadow: 0 0 40px rgba(110, 231, 255, 0.3);
}

.quote__cite {
  margin-top: var(--spacing-sm);
  color: var(--color-text-muted);
  font-size: var(--text-xs);
  letter-spacing: var(--tracking-caps);
  text-transform: uppercase;
}

.quote__cite cite {
  font-style: normal;
}

/* Hairline separating the quote from the colophon row */
.quote::after {
  content: "";
  display: block;
  width: min(11rem, 45%);
  height: 1px;
  margin: var(--spacing-lg) auto 0;
  background-image: var(--gradient-hairline);
}

.site-footer__inner {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: var(--spacing-2xs);
  padding-top: var(--spacing-lg);
}

.site-footer__mark {
  display: inline-flex;
  align-items: center;
  gap: var(--spacing-2xs);
  font-family: var(--font-heading);
  font-weight: var(--weight-medium);
}

.site-footer__logo {
  width: auto;
  height: 1.75rem;
  opacity: 0.85;
}

.site-footer__note {
  color: var(--color-text-muted);
  font-size: var(--text-sm);
}

/* --------------------------------------------------------------------------
   Project detail modal
   -------------------------------------------------------------------------- */

.modal {
  width: min(42rem, calc(100vw - 2 * var(--gutter)));
  max-height: min(84vh, 48rem);
  margin: auto;
  padding: 0;
  border: 0;
  background: transparent;
  overflow: visible;
}

.modal::backdrop {
  background-color: rgba(2, 8, 12, 0.72);
  -webkit-backdrop-filter: blur(8px);
  backdrop-filter: blur(8px);
}

.modal[open] .modal__panel {
  animation: modal-in var(--duration-slow) var(--ease-out);
}

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

.modal__panel {
  max-height: min(84vh, 48rem);
  padding: var(--spacing-lg) var(--spacing-md) var(--spacing-md);
  overflow-y: auto;
  overscroll-behavior: contain;
}

.modal__close {
  position: absolute;
  top: var(--spacing-2xs);
  right: var(--spacing-2xs);
  z-index: 1;
  display: grid;
  place-items: center;
  width: 2.25rem;
  height: 2.25rem;
  border: 1px solid var(--panel-border);
  border-radius: 50%;
  background-color: rgba(4, 18, 26, 0.6);
  color: var(--color-aqua-200);
  transition: border-color var(--duration-fast) var(--ease),
    color var(--duration-fast) var(--ease),
    transform var(--duration-fast) var(--ease-out);
}

.modal__close:hover {
  transform: rotate(90deg);
  border-color: var(--color-aqua-400);
  color: var(--color-aqua-100);
}

.modal__close svg {
  width: 0.85rem;
  height: 0.85rem;
}

.modal__title {
  position: relative;
  margin-bottom: var(--spacing-2xs);
  font-size: var(--text-2xl);
}

.modal__body {
  position: relative;
  color: var(--color-text-muted);
}

.modal__body h4 {
  margin-top: var(--spacing-md);
  margin-bottom: var(--spacing-3xs);
  color: var(--color-aqua-200);
}

.modal__cta {
  position: relative;
  margin-top: var(--spacing-md);
}

.list {
  display: grid;
  gap: var(--spacing-3xs);
  margin: 0;
  padding: 0;
}

.list li {
  position: relative;
  padding-left: var(--spacing-sm);
}

.list li::before {
  content: "";
  position: absolute;
  top: 0.62em;
  left: 0;
  width: 6px;
  height: 6px;
  border-radius: 50%;
  background-color: var(--color-aqua-400);
  box-shadow: 0 0 8px rgba(110, 231, 255, 0.7);
}

/* --------------------------------------------------------------------------
   Scroll reveal
   Only armed once JS is running, so content is never trapped invisible.
   -------------------------------------------------------------------------- */

.js [data-reveal] {
  opacity: 0;
  transform: translateY(18px);
  /* --reveal-delay is set by animations.js to stagger a batch that crosses
     the viewport together. */
  transition: opacity var(--duration-slow) var(--ease-out) var(--reveal-delay, 0ms),
    transform var(--duration-slow) var(--ease-out) var(--reveal-delay, 0ms);
}

.js [data-reveal].is-revealed {
  opacity: 1;
  transform: none;
}

/* The hero drifts in from the right instead of up, on a longer, softer curve.
   Its items are all in view at load, so animations.js reveals them as one
   batch and the --reveal-delay stagger plays them in document order. */
.js .hero [data-reveal] {
  transform: translateX(var(--float-distance));
  transition: opacity var(--duration-float) var(--ease-float) var(--reveal-delay, 0ms),
    transform var(--duration-float) var(--ease-float) var(--reveal-delay, 0ms);
}

.js .hero [data-reveal].is-revealed {
  opacity: 1;
  transform: none;
}

@media (prefers-reduced-motion: reduce) {
  .js [data-reveal] {
    opacity: 1;
    transform: none;
  }

  /* The backdrop stays — a still image is not a motion problem — but the
     glow drift, the brand trace, and the loading spin are stopped dead. The
     trace holds a lit segment rather than vanishing, so the rectangle still
     reads as a deliberate frame. */
  .backdrop__glow,
  .backdrop__sweep,
  .brand__trace-comet,
  .spinner__arc {
    animation: none;
  }

  .brand__trace-comet {
    stroke-dasharray: none;
    stroke: rgba(110, 231, 255, 0.5);
  }

  /* The custom cursor and the magnetic pull are motion by definition, so
     both stand down entirely and the native cursor comes back. */
  .cursor {
    display: none;
  }

  [data-magnetic] {
    translate: none;
  }
}

/* --------------------------------------------------------------------------
   Custom cursor

   Two independent parts: a dot that tracks the pointer almost exactly, and a
   ring on a much lazier follow so it lags behind and then settles concentric
   behind the dot. Both are driven by --cursor-x/--cursor-y written by main.js;
   every declaration that consumes them lives here.

   Only shown when the page has activated it (html.has-cursor), which main.js
   does only for a fine pointer with motion allowed — so touch users and
   reduced-motion users keep their native cursor untouched.
   -------------------------------------------------------------------------- */

.cursor {
  position: fixed;
  top: 0;
  left: 0;
  z-index: var(--z-cursor);
  pointer-events: none;
}

.has-cursor,
.has-cursor a,
.has-cursor button,
.has-cursor [data-open-project] {
  cursor: none;
}

.cursor__dot,
.cursor__ring {
  position: fixed;
  top: 0;
  left: 0;
  border-radius: 50%;
  opacity: 0;
  /* -50% centres the shape on the point; the translate is applied by JS via
     the two custom properties. */
  translate: calc(var(--cursor-x, 50vw) - 50%) calc(var(--cursor-y, 50vh) - 50%);
  transition: opacity var(--duration-base) var(--ease),
    width var(--duration-base) var(--ease-out),
    height var(--duration-base) var(--ease-out),
    background-color var(--duration-base) var(--ease);
}

.has-cursor .cursor__dot,
.has-cursor .cursor__ring {
  opacity: 1;
}

.cursor__dot {
  width: var(--cursor-dot);
  height: var(--cursor-dot);
  background-color: var(--color-aqua-400);
}

.cursor__ring {
  width: var(--cursor-ring);
  height: var(--cursor-ring);
  border: 1px solid rgba(174, 214, 232, 0.55);
}

/* Over anything clickable the ring opens up and the dot swells slightly */
.cursor.is-active .cursor__ring {
  width: var(--cursor-ring-hover);
  height: var(--cursor-ring-hover);
  border-color: var(--color-aqua-400);
}

.cursor.is-active .cursor__dot {
  background-color: var(--color-aqua-200);
}

/* Over a solid aqua fill the light cursor vanished into the button. Flip it
   to a deep aqua so it stays readable against its own accent colour. */
.cursor.is-on-solid .cursor__dot {
  background-color: var(--color-abyss-700);
}

.cursor.is-on-solid .cursor__ring {
  border-color: rgba(7, 22, 31, 0.75);
}

/* Pressed */
.cursor.is-down .cursor__ring {
  width: calc(var(--cursor-ring) * 0.8);
  height: calc(var(--cursor-ring) * 0.8);
}

/* --------------------------------------------------------------------------
   Magnetic button

   main.js writes --magnet-x/--magnet-y while the pointer is in range. Using
   the `translate` property rather than `transform` keeps this independent of
   the hover lift, which is a `transform` — the two compose instead of
   overwriting each other.
   -------------------------------------------------------------------------- */

[data-magnetic] {
  translate: var(--magnet-x, 0px) var(--magnet-y, 0px);
}

/* The button springs on the way in and on the way out, but tracks the
   pointer with no easing in between so it still feels attached rather than
   laggy. main.js adds .is-engaging for one bounce on entry then drops it. */
[data-magnetic].is-engaging {
  transition: translate 340ms cubic-bezier(0.34, 1.7, 0.6, 1);
}

[data-magnetic].is-releasing {
  transition: translate 620ms cubic-bezier(0.34, 1.56, 0.64, 1);
}
