/* ==========================================================================
   Components
   ========================================================================== */

/* ---- the charge meter / nav -------------------------------------------- *
   Translucent, with the page scrolling underneath it. It doubles as the
   scroll indicator: down charges the cell, up lets it dissipate.
 * ------------------------------------------------------------------------ */

.site-nav {
  position: fixed;
  z-index: var(--z-nav);
  inset-block-start: calc(env(safe-area-inset-top, 0px) + var(--s-3));
  inset-inline-end: calc(env(safe-area-inset-right, 0px) + var(--s-3));
  display: flex;
  flex-direction: column;
  align-items: flex-end;
  gap: var(--s-2);
}

/* Reading downhill, the pill tucks most of the way out of the way; any
   upward scroll brings it straight back. */
.site-nav.is-tucked {
  translate: 0 calc(-100% - var(--s-4));
  opacity: 0;
}

.site-nav {
  transition: translate 420ms cubic-bezier(0.2, 0.8, 0.2, 1),
    opacity 260ms ease;
}

.nav-pill {
  display: flex;
  align-items: center;
  gap: var(--s-3);
  padding: 6px 12px 6px 6px;
  border-radius: 999px;
  background: var(--glass);
  backdrop-filter: blur(var(--glass-blur)) saturate(180%);
  -webkit-backdrop-filter: blur(var(--glass-blur)) saturate(180%);
  border: 1px solid var(--glass-edge);
  box-shadow: var(--shadow-md);
  color: var(--fg);
  transform: translateZ(0);
}

/* A full cell earns a little light. */
.site-nav[style*='--glow: 1'] .cell,
.site-nav[style*='--glow:1'] .cell {
  opacity: 1;
  border-color: var(--charge);
  color: var(--charge);
}

.nav-avatar {
  width: 30px;
  height: 30px;
  border-radius: 50%;
  object-fit: cover;
  flex: none;
}

.nav-name {
  font-size: var(--fs-small);
  font-weight: 600;
  letter-spacing: -0.01em;
  line-height: 1;
  white-space: nowrap;
}

.nav-name-first {
  opacity: 0.6;
}

/* The cell itself. */
.cell {
  position: relative;
  display: block;
  width: 34px;
  height: 15px;
  border: 1.5px solid currentColor;
  border-radius: 4px;
  opacity: 0.75;
  flex: none;
}

.cell-fill {
  position: absolute;
  inset: 2px;
  transform-origin: left center;
  transform: scaleX(0);
  border-radius: 1.5px;
  background: var(--charge);
  box-shadow: 0 0 8px color-mix(in oklab, var(--charge) 60%, transparent);
}

.cell-cap {
  position: absolute;
  inset-inline-start: 100%;
  inset-block-start: 50%;
  translate: 1.5px -50%;
  width: 3px;
  height: 6px;
  border-radius: 0 2px 2px 0;
  background: currentColor;
}

.nav-readout {
  font-size: 0.68rem;
  color: var(--fg-muted);
  min-width: 2.4em;
  text-align: right;
}

.nav-menu {
  min-width: 12.5rem;
  padding: var(--s-2);
  border-radius: var(--r-md);
  background: var(--glass);
  backdrop-filter: blur(var(--glass-blur)) saturate(180%);
  -webkit-backdrop-filter: blur(var(--glass-blur)) saturate(180%);
  border: 1px solid var(--glass-edge);
  box-shadow: var(--shadow-lg);
  /* Anchored to the pill it came out of, so it grows from its source. */
  transform-origin: top right;
}

.nav-menu[hidden] {
  display: none;
}

.nav-menu a {
  display: flex;
  gap: var(--s-3);
  align-items: baseline;
  padding: var(--s-2) var(--s-3);
  border-radius: var(--r-sm);
  text-decoration: none;
  font-size: var(--fs-small);
}

.nav-menu a .mono {
  color: var(--fg-faint);
  font-size: 0.7rem;
}

.nav-menu a[aria-current='true'] {
  color: var(--accent-fg);
}

.nav-menu a[aria-current='true'] .mono {
  color: var(--accent);
}

@media (hover: hover) and (pointer: fine) {
  .nav-menu a:hover {
    background: color-mix(in oklab, var(--fg) 8%, transparent);
  }
}

.nav-menu a:active {
  background: color-mix(in oklab, var(--fg) 12%, transparent);
}

@media (max-width: 34rem) {
  .nav-name,
  .nav-readout {
    display: none;
  }
}


/* ---- issue headings ----------------------------------------------------- */

.issue-head {
  margin-block-end: var(--s-7);
  max-width: 44rem;
}

.issue-head .label {
  margin-block-end: var(--s-4);
}

.issue-no {
  color: var(--accent);
  font-weight: 700;
}

.issue-head .lead {
  margin-block-start: var(--s-5);
}

.dot-sep {
  margin-inline: 0.6em;
  opacity: 0.4;
}


/* ---- links -------------------------------------------------------------- *
   The rule grows from the side the pointer arrived on, so the underline
   agrees with the direction you came from.
 * ------------------------------------------------------------------------ */

.ulink {
  position: relative;
  text-decoration: none;
  color: inherit;
  padding-block-end: 1px;
}

.ulink::after {
  content: '';
  position: absolute;
  inset-inline: 0;
  inset-block-end: 0;
  height: 1px;
  background: currentColor;
  opacity: 0.45;
  transform: scaleX(var(--u, 1));
  transform-origin: var(--u-origin, left);
  transition: transform 320ms cubic-bezier(0.2, 0.8, 0.2, 1), opacity 200ms ease;
}

@media (hover: hover) and (pointer: fine) {
  .ulink {
    --u: 0;
  }
  .ulink:hover::after,
  .ulink:focus-visible::after {
    --u: 1;
    opacity: 1;
  }
  /* Keep a faint rule so the link is still visibly a link at rest. */
  .ulink::before {
    content: '';
    position: absolute;
    inset-inline: 0;
    inset-block-end: 0;
    height: 1px;
    background: currentColor;
    opacity: 0.22;
  }
}

/* Standalone links in a list get a real target. Links inside a sentence
   deliberately do not -- padding them out would break the line. */
.links a {
  display: inline-block;
  padding-block: 9px;
}


/* ---- buttons ------------------------------------------------------------ */

.ghost-btn {
  display: inline-flex;
  align-items: center;
  gap: var(--s-2);
  padding: var(--s-2) var(--s-4);
  border: 1px solid var(--rule-strong);
  border-radius: 999px;
  font-size: var(--fs-small);
  font-weight: 550;
  color: var(--fg);
  /* Feedback is on the press, not the release. */
  transition: transform 110ms ease-out, background-color 160ms ease,
    border-color 160ms ease;
}

@media (hover: hover) and (pointer: fine) {
  .ghost-btn:hover {
    background: color-mix(in oklab, var(--fg) 7%, transparent);
    border-color: var(--fg-muted);
  }
}

.ghost-btn:active {
  transform: scale(0.96);
  background: color-mix(in oklab, var(--fg) 12%, transparent);
}

.ghost-btn[aria-pressed='true'] {
  background: var(--accent);
  border-color: var(--accent);
  color: var(--bg);
}


/* ---- chips -------------------------------------------------------------- *
   Magnetic: they lean toward the pointer on a spring. JS writes --mx/--my.
 * ------------------------------------------------------------------------ */

.chips {
  display: flex;
  flex-wrap: wrap;
  gap: var(--s-2);
  margin-block-start: var(--s-3);
}

.chip {
  --mx: 0px;
  --my: 0px;
  padding: var(--s-2) var(--s-4);
  border: 1px solid var(--rule-strong);
  border-radius: 999px;
  font-size: var(--fs-small);
  background: color-mix(in oklab, var(--bg-raised) 60%, transparent);
  translate: var(--mx) var(--my);
  will-change: translate;
}


/* ---- photo tiles -------------------------------------------------------- *
   Each tile is a halftone of the actual photograph. Hovering measures it and
   the dots resolve into the image. On touch it resolves on entering view.

   It also happens to solve a real problem: these three photographs are only
   ~300px, so they are never rendered above that, and the dot screen carries
   the frame at any size.
 * ------------------------------------------------------------------------ */

.tiles {
  display: flex;
  flex-wrap: wrap;
  gap: var(--s-5);
  margin-block-start: var(--s-8);
}

.tile figure {
  margin: 0;
  width: 10.5rem;
}

.tile-frame {
  position: relative;
  display: block;
  aspect-ratio: 308 / 298;
  border-radius: var(--r-md);
  overflow: hidden;
  background: var(--bg-sunk);
  box-shadow: var(--shadow-sm);
  /* Rotations are set per tile in JS so the row sits like scattered prints. */
  rotate: var(--tilt, 0deg);
  transition: box-shadow 300ms ease;
}

.tile-frame img,
.tile-screen {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
}

.tile-frame img {
  object-fit: cover;
  opacity: var(--resolved, 0);
}

.tile-screen {
  color: var(--dot);
  opacity: calc(1 - var(--resolved, 0));
}

.tile figcaption {
  margin-block-start: var(--s-3);
  color: var(--fg-muted);
}

@media (hover: hover) and (pointer: fine) {
  .tile:hover .tile-frame {
    box-shadow: var(--shadow-md);
  }
}

.tiles-hint {
  margin-block-start: var(--s-5);
}

@media (hover: none) {
  .tiles-hint {
    display: none;
  }
}


/* ---- reveal ------------------------------------------------------------- *
   Headings decohere in: a short scatter that settles. Purely decorative, so
   it is the first thing to go under reduced motion.
 * ------------------------------------------------------------------------ */

.reveal {
  opacity: 0;
}

.reveal.is-in {
  opacity: 1;
  transition: opacity 700ms cubic-bezier(0.2, 0.7, 0.2, 1);
}

@media (prefers-reduced-motion: reduce) {
  .reveal {
    opacity: 1;
  }
}


/* ---- the detector reticle ----------------------------------------------- *
   Desktop only, and off entirely under reduced motion.
 * ------------------------------------------------------------------------ */

.reticle {
  position: fixed;
  inset-block-start: 0;
  inset-inline-start: 0;
  z-index: var(--z-reticle);
  width: 26px;
  height: 26px;
  margin: -13px 0 0 -13px;
  border: 1px solid var(--fg);
  border-radius: 50%;
  pointer-events: none;
  opacity: 0;
  will-change: transform;
  transition: opacity 240ms ease;
}

.reticle.is-live {
  opacity: 0.45;
}

.reticle.is-hot {
  opacity: 0.9;
}

@media (hover: none), (pointer: coarse), (prefers-reduced-motion: reduce) {
  .reticle {
    display: none;
  }
}


/* ---- halftone canvases -------------------------------------------------- */

.halftone-canvas {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  color: var(--dot);
  pointer-events: none;
}

.section-field {
  opacity: 0.16;
}


/* ---- variant cover ------------------------------------------------------ *
   Every run gets one. Rotates the whole page's hue and pushes the ink toward
   newsprint -- same layout, alternate printing.
 * ------------------------------------------------------------------------ */

:root[data-variant='on'] {
  --gold: #b0483c;
  --accent: #b0483c;
  --accent-fg: #c65a4c;
  --charge: #ff5f4d;
  --charge-soft: #ffa08f;
  --bg: #efe6d6;
  --bg-raised: #f7f0e3;
  --bg-sunk: #e2d6c0;
  --teal: #4a72a8;
}

:root[data-variant='on'] .lights-out {
  --bg: #140b12;
  --bg-raised: #1e1019;
  --bg-sunk: #0a0509;
  --accent: var(--charge);
}

:root[data-variant='on'] body {
  --dot: color-mix(in oklab, #2a1420 76%, transparent);
}

:root[data-variant='on'] .hero-photo img {
  filter: saturate(0.35) sepia(0.4) hue-rotate(-16deg) contrast(1.08);
}
