/* ==========================================================================
   JPDEV enhancement layer  (added 2026-09-05)
   Loaded after styles.css. Everything here is additive polish; delete this
   file's <link> and js/enhance.js's <script> in index.html to revert.
   Colour, type and spacing all come from the tokens in styles.css.
   ========================================================================== */

/* ----- Global texture and states ----------------------------------------- */

/* Film grain over the whole page: a near-invisible texture that keeps the
   flat blacks from banding and gives the glow something to sit in. Sized at
   2x and translated in steps so the animation is a pure compositor move. */
.grain {
  position: fixed;
  inset: -50%;
  width: 200%;
  height: 200%;
  z-index: 2000;
  pointer-events: none;
  opacity: 0.055;
  background-image: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='240' height='240'><filter id='n'><feTurbulence type='fractalNoise' baseFrequency='0.85' numOctaves='3' stitchTiles='stitch'/><feColorMatrix type='saturate' values='0'/></filter><rect width='100%25' height='100%25' filter='url(%23n)'/></svg>");
  background-size: 240px 240px;
  will-change: transform;
  animation: grain-shift 1.4s steps(7) infinite;
}

@keyframes grain-shift {
  0%   { transform: translate(0, 0); }
  15%  { transform: translate(-2.4%, 1.6%); }
  30%  { transform: translate(1.8%, -2.2%); }
  45%  { transform: translate(-1.2%, -1.4%); }
  60%  { transform: translate(2.6%, 1.1%); }
  75%  { transform: translate(-2.0%, 2.4%); }
  90%  { transform: translate(1.4%, -0.8%); }
  100% { transform: translate(0, 0); }
}

@media (max-width: 768px) {
  .grain { animation: none; }
}

/* Selection and focus in the terminal blue, so keyboard users get the same
   accent the hover states use */
::selection {
  background: rgba(151, 196, 255, 0.32);
  color: var(--color-stellar-white);
}

:focus-visible {
  outline: 1px solid var(--color-terminal-blue);
  outline-offset: 4px;
}

/* ----- Background field ---------------------------------------------------- */

/* On the widths that draw the globe, the warm bloom radiates from its centre:
   a tight hot core under the sphere plus the original broad horizon wash,
   both anchored to --globe-x / --globe-y, which js/enhance.js keeps in step
   with wherever the globe is placed. */
@media (min-width: 1100px) {
  .bg-glow {
    background:
      radial-gradient(circle 18vmax at var(--globe-x, 79%) var(--globe-y, 58%),
        rgba(255, 99, 8, 0.30), rgba(255, 99, 8, 0.12) 42%, transparent 74%),
      radial-gradient(circle 78vmax at var(--globe-x, 79%) var(--globe-y, 58%),
        rgba(255, 99, 8, 0.16), rgba(189, 201, 230, 0.10) 30%, rgba(151, 196, 255, 0.06) 58%, transparent 84%);
  }
}

/* The globe layer: a second fixed <pre> over the field, finer-grained, that
   js/enhance.js positions, sizes and fills. Font metrics here must match
   GCW / GCH in that file (0.6em advance + 0.5px spacing, 1.15 line-height). */
.bg-globe {
  position: fixed;
  left: 0;
  top: 0;
  z-index: -1;
  margin: 0;
  overflow: hidden;
  font-family: var(--font-geistmono);
  font-size: 9px;
  line-height: 1.15;
  letter-spacing: 0.5px;
  color: rgba(151, 196, 255, 0.3);
  opacity: 0;
  transition: opacity 90ms linear;
  pointer-events: none;
  user-select: none;
  -webkit-user-select: none;
}

/* Continents and the sonar ping */
.bg-globe b {
  font-weight: 400;
  color: rgba(151, 196, 255, 0.8);
  text-shadow: 0 0 6px rgba(151, 196, 255, 0.45);
}

/* The home marker and its label */
.bg-globe i {
  font-style: normal;
  color: rgba(151, 196, 255, 0.98);
  text-shadow: 0 0 10px rgba(151, 196, 255, 0.55);
}

/* The takeover is instant; the globe should not linger over it */
body.modal-open .bg-globe {
  opacity: 0 !important;
}

/* The field's brighter tone, available to any field effect */
.bg-field b {
  font-weight: 400;
  color: rgba(151, 196, 255, 0.6);
  text-shadow: 0 0 6px rgba(151, 196, 255, 0.35);
}

/* Text the field stamps into itself (the home marker and its label) */
.bg-field i {
  font-style: normal;
  color: rgba(151, 196, 255, 0.95);
  text-shadow: 0 0 10px rgba(151, 196, 255, 0.5);
}

/* While the takeover is open the field goes white; the globe's cells follow
   it down as they fade rather than lingering blue */
body.modal-open .bg-field b,
body.modal-open .bg-field i {
  color: rgba(255, 255, 255, 0.35);
  text-shadow: none;
}

/* The field's edge mask was centred on the hero copy; the globe sits in the
   right-hand bloom, so on the widths that draw it the mask opens a little
   wider on that side. Narrower screens keep the original mask. */
@media (min-width: 1100px) {
  .bg-field {
    -webkit-mask-image: radial-gradient(ellipse 95% 85% at 58% 48%, black 40%, rgba(0, 0, 0, 0.4) 100%);
    mask-image: radial-gradient(ellipse 95% 85% at 58% 48%, black 40%, rgba(0, 0, 0, 0.4) 100%);
  }
}

/* ----- Nav ------------------------------------------------------------------ */

/* Scroll spy: the link for the section under the viewport's midline */
.nav-link {
  transition: color 250ms ease;
}

.nav-link.is-current {
  color: var(--color-terminal-blue);
}

/* The stamp now carries a live clock; tabular digits keep it from jittering */
.nav-meta {
  font-variant-numeric: tabular-nums;
}

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

/* Ghost pill stays outlined and unfilled; it just picks up a little of the
   field's light while hovered */
.hero-cta {
  transition: box-shadow 300ms ease, border-color 300ms ease;
}

.hero-cta:hover {
  box-shadow: 0 0 0 1px rgba(255, 255, 255, 0.12), 0 0 32px rgba(151, 196, 255, 0.18);
}

/* ----- Track record --------------------------------------------------------- */

/* The headline figures glow like the terminal they're quoting */
.stat-value {
  font-variant-numeric: tabular-nums;
  text-shadow: 0 0 28px rgba(151, 196, 255, 0.28);
}

/* Monthly bars grow out of the baseline the first time the chart is seen
   (and again each time the ledger takeover prints it). js/enhance.js arms
   the grid, sets --bar-delay per month, then flips it to grown. */
.chart-grid.chart-armed .bar {
  height: 0 !important;
}

.chart-grid.chart-armed .bar.neg {
  margin-bottom: var(--chart-base, 0px) !important;
}

.chart-grid.chart-armed .bar-val {
  opacity: 0;
}

.chart-grid.chart-grown .bar {
  transition:
    height 900ms cubic-bezier(0.16, 1, 0.3, 1) var(--bar-delay, 0ms),
    margin-bottom 900ms cubic-bezier(0.16, 1, 0.3, 1) var(--bar-delay, 0ms);
}

.chart-grid.chart-grown .bar-val {
  opacity: 1;
  transition: opacity 400ms ease calc(var(--bar-delay, 0ms) + 520ms);
}

/* Hovering one month dims the others so its bars can be read on their own */
.chart-grid:hover .chart-month {
  transition: opacity 220ms ease;
}

.chart-grid:hover .chart-month:not(:hover) {
  opacity: 0.42;
}

/* ----- Cards ---------------------------------------------------------------- */

/* Cursor spotlight: a soft pool of light inside the card and a brighter
   run along the hairline border, both centred on the pointer via --mx/--my */
.project-card {
  --mx: 50%;
  --my: 50%;
}

.card-spot,
.card-spot-ring {
  position: absolute;
  pointer-events: none;
  opacity: 0;
  transition: opacity 320ms ease;
}

.card-spot {
  inset: 0;
  background: radial-gradient(
    440px circle at var(--mx) var(--my),
    rgba(151, 196, 255, 0.09),
    rgba(151, 196, 255, 0.025) 40%,
    transparent 64%
  );
}

.card-spot-ring {
  inset: -1px;
  border: 1px solid transparent;
  background: radial-gradient(
    280px circle at var(--mx) var(--my),
    rgba(151, 196, 255, 0.85),
    rgba(151, 196, 255, 0.12) 52%,
    transparent 74%
  ) border-box;
  -webkit-mask: linear-gradient(#000 0 0) padding-box, linear-gradient(#000 0 0);
  -webkit-mask-composite: xor;
  mask: linear-gradient(#000 0 0) padding-box, linear-gradient(#000 0 0);
  mask-composite: exclude;
}

.project-card:hover .card-spot,
.project-card:hover .card-spot-ring {
  opacity: 1;
}

/* Every project card now says how to open it, like the track card does.
   It sits at the far end of the tag row and wraps under it on narrow cards. */
.card-open {
  margin-left: auto;
  align-self: center;
  white-space: nowrap;
  font-family: var(--font-geistmono);
  font-size: var(--text-mono-badge);
  line-height: var(--leading-mono-badge);
  letter-spacing: var(--tracking-mono-badge);
  text-transform: uppercase;
  color: var(--color-smoke);
  pointer-events: none;
  transition: color 200ms ease;
}

.project-card:hover .card-open {
  color: var(--color-terminal-blue);
}

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

.copy-email {
  margin-left: var(--spacing-16);
  vertical-align: middle;
  font-family: var(--font-geistmono);
  font-size: var(--text-mono-badge);
  letter-spacing: var(--tracking-mono-badge);
  text-transform: uppercase;
  color: var(--color-smoke);
  transition: color 200ms ease;
}

.copy-email:hover,
.copy-email.done {
  color: var(--color-terminal-blue);
}

@media (max-width: 480px) {
  .copy-email {
    display: block;
    margin: var(--spacing-12) 0 0;
  }
}

/* ----- Reduced motion --------------------------------------------------------
   styles.css already kills every animation and transition under this query;
   these only make sure nothing is left hidden or collapsed. */
@media (prefers-reduced-motion: reduce) {
  .chart-grid.chart-armed .bar { height: auto !important; }
  .chart-grid.chart-armed .bar-val { opacity: 1; }
  .grain { animation: none; }
}
