/* material.css — surface model.
   ---------------------------------------------------------------------------
   Everything in here exists to make a rectangle read as a physical object made
   of a real material, lit from a single source above and slightly in front.
   Flat fills are what make interfaces look computed rather than manufactured,
   so no surface in this product is a flat fill.

   A matte surface in the real world does four things at once, and each has a
   direct CSS analogue:

     1. It falls off in brightness away from the light        → base gradient
     2. Its top edge catches a specular line, its bottom edge
        falls into shadow                                     → inset box-shadows
     3. It diffuses light unevenly because the surface is
        microscopically rough                                 → grain overlay
     4. It bounces light back onto whatever is near it, and
        occludes light where it meets another surface          → bounce + contact shadow

   Miss any one of them and the eye reads "graphic" instead of "object". Item 3
   is the one interfaces almost always skip, and it is the one doing most of the
   work here: without grain, a gradient reads as a gradient, not as anodised
   aluminium or a moulded instrument bezel.

   The reference materials are deliberate: the milled-and-bead-blasted aluminium
   of Apple hardware, and the matte magnesium bezels and etched-legend keys of
   certified avionics. Both are matte, both are precise, neither is glossy.
   Nothing here uses a glassy highlight or a coloured glow. */

:root {
  /* ---- The light.
     One source, above and slightly forward. Every shadow and highlight in the
     product derives from this, which is why surfaces feel like they share a room. */
  --lit: 255 255 255;       /* specular colour   */
  --shade: 0 0 0;           /* occlusion colour  */

  /* ---- Microtexture.
     fractalNoise at a high base frequency reads as surface roughness rather
     than as visible dots. numOctaves=3 gives grain at more than one scale, which
     is what stops it looking like a regular screen pattern. Rendered greyscale
     via feColorMatrix so it tints nothing — it only perturbs luminance, exactly
     as real surface roughness does. stitchTiles keeps the 180px tile seamless. */
  --grain: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='180' height='180'%3E%3Cfilter id='g' x='0' y='0' width='100%25' height='100%25'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.82' numOctaves='3' stitchTiles='stitch' result='t'/%3E%3CfeColorMatrix type='saturate' values='0' in='t'/%3E%3C/filter%3E%3Crect width='180' height='180' filter='url(%23g)'/%3E%3C/svg%3E");

  /* Grain strength is per-theme: a dark matte surface shows roughness as light
     scatter, a pale one shows it as tooth in the paper. Different sign, different
     amount, same physical idea. */
  --grain-opacity: 0.038;
  --grain-blend: overlay;

  /* ---- Elevation.
     Two shadows per level, never one. The tight dark one is contact occlusion
     and is what actually sells the height; the wide soft one is ambient. A single
     blurry shadow is the classic tell of a drawn interface. */
  --el-1:
    0 1px 1px -0.5px rgb(var(--shade) / 0.28),
    0 3px 6px -2px rgb(var(--shade) / 0.24);
  --el-2:
    0 1px 1px -0.5px rgb(var(--shade) / 0.32),
    0 6px 14px -4px rgb(var(--shade) / 0.34),
    0 18px 36px -12px rgb(var(--shade) / 0.30);
  --el-3:
    0 1px 2px -0.5px rgb(var(--shade) / 0.36),
    0 12px 26px -6px rgb(var(--shade) / 0.40),
    0 40px 72px -20px rgb(var(--shade) / 0.44);

  /* ---- Edges.
     --edge-top is the specular line where the surface turns toward the light.
     --edge-bottom is the far side falling away. --bounce is light returning off
     whatever sits below, caught on the inside of the lower edge — subtle, and the
     detail that makes a panel feel like it is sitting in a lit space rather than
     pasted onto a background. */
  --edge-top: inset 0 1px 0 rgb(var(--lit) / 0.07);
  --edge-bottom: inset 0 -1px 0 rgb(var(--shade) / 0.55);
  --bounce: inset 0 -18px 26px -22px rgb(var(--lit) / 0.055);
}

:root[data-theme="light"] {
  /* Paper and painted metal scatter far more than dark anodising, so the grain
     goes up and switches to multiply — roughness on a pale surface reads as
     shadow in the tooth, not as scattered highlight. */
  --grain-opacity: 0.055;
  --grain-blend: multiply;
  --edge-top: inset 0 1px 0 rgb(var(--lit) / 0.9);
  --edge-bottom: inset 0 -1px 0 rgb(var(--shade) / 0.13);
  --bounce: inset 0 -18px 26px -22px rgb(var(--shade) / 0.035);
  --el-1: 0 1px 1px -0.5px rgb(var(--shade) / 0.07), 0 3px 6px -2px rgb(var(--shade) / 0.06);
  --el-2: 0 1px 1px -0.5px rgb(var(--shade) / 0.08), 0 6px 14px -4px rgb(var(--shade) / 0.09), 0 18px 36px -12px rgb(var(--shade) / 0.07);
  --el-3: 0 1px 2px -0.5px rgb(var(--shade) / 0.10), 0 12px 26px -6px rgb(var(--shade) / 0.12), 0 40px 72px -20px rgb(var(--shade) / 0.12);
}

/* ---------------------------------------------------------------------------
   The grain overlay.

   Applied through ::after rather than as an extra background layer so it sits
   above any gradient the element already has, and so it can be blended. Pointer
   events off, and inherit the host's radius so it never squares off a corner.

   Deliberately NOT applied to: the WebGL canvas (the renderer has its own
   grain and doubling it reads as noise), instrument glass, or anything with
   live-updating text, where a static overlay under moving digits shimmers. */
.mat,
.mat-raised,
.mat-inset,
.mat-metal { position: relative; }

.mat::after,
.mat-raised::after,
.mat-inset::after,
.mat-metal::after {
  content: "";
  position: absolute;
  inset: 0;
  border-radius: inherit;
  background-image: var(--grain);
  background-size: 180px 180px;
  opacity: var(--grain-opacity);
  mix-blend-mode: var(--grain-blend);
  pointer-events: none;
  z-index: 0;
}
/* Content has to clear the overlay. Anything positioned inside a material
   surface gets lifted above the grain. */
.mat > *,
.mat-raised > *,
.mat-inset > *,
.mat-metal > * { position: relative; z-index: 1; }

/* ---------------------------------------------------------------------------
   Surface tiers. */

/* Flush panel — set into the surrounding surface, catching light on its top
   edge only. The workhorse. */
.mat {
  background-image: linear-gradient(
    180deg,
    rgb(var(--lit) / 0.028) 0%,
    rgb(var(--lit) / 0.008) 42%,
    rgb(var(--shade) / 0.09) 100%
  );
  box-shadow: var(--edge-top), var(--edge-bottom), var(--bounce);
}

/* Raised — a discrete object sitting above the panel, so it gets contact and
   ambient shadow as well as its own edge lighting. */
.mat-raised {
  background-image: linear-gradient(
    180deg,
    rgb(var(--lit) / 0.045) 0%,
    rgb(var(--lit) / 0.012) 38%,
    rgb(var(--shade) / 0.12) 100%
  );
  box-shadow: var(--edge-top), var(--edge-bottom), var(--bounce), var(--el-2);
}

/* Inset — a recess. The gradient inverts, because a hollow is dark at the top
   where the near wall shades it and bright at the bottom where light pools. */
.mat-inset {
  background-image: linear-gradient(
    180deg,
    rgb(var(--shade) / 0.30) 0%,
    rgb(var(--shade) / 0.10) 34%,
    rgb(var(--lit) / 0.018) 100%
  );
  box-shadow:
    inset 0 2px 5px -1px rgb(var(--shade) / 0.55),
    inset 0 -1px 0 rgb(var(--lit) / 0.05);
}

/* Anodised metal — bead-blasted aluminium. Three things distinguish it from a
   painted panel: a cooler cast, a much tighter specular band near the top
   (metal has a narrow highlight where paint has a broad one), and a faint
   vertical brush direction from the milling. */
.mat-metal {
  background-image:
    linear-gradient(180deg,
      rgb(var(--lit) / 0.075) 0%,
      rgb(var(--lit) / 0.030) 6%,
      rgb(var(--lit) / 0.008) 30%,
      rgb(var(--shade) / 0.10) 82%,
      rgb(var(--shade) / 0.16) 100%),
    repeating-linear-gradient(90deg,
      rgb(var(--lit) / 0.011) 0 1px,
      transparent 1px 3px);
  box-shadow:
    inset 0 1px 0 rgb(var(--lit) / 0.11),
    inset 0 -1px 0 rgb(var(--shade) / 0.6),
    inset 1px 0 0 rgb(var(--lit) / 0.03),
    inset -1px 0 0 rgb(var(--shade) / 0.3),
    var(--el-1);
}

/* ---------------------------------------------------------------------------
   Specular sweep.

   A real matte surface still has a broad, weak highlight that moves with the
   viewer. Tracking the actual cursor gives a surface presence that a static
   gradient cannot, and because it is a wide low-opacity radial rather than a
   hard glint, it reads as diffuse scatter and not as glass.

   --mx/--my are written by the pointer handler in js/material.js; the fallback
   50%/0% keeps it looking correct before any pointer has moved and on touch. */
.mat-sheen::before {
  content: "";
  position: absolute;
  inset: 0;
  border-radius: inherit;
  background: radial-gradient(
    340px circle at var(--mx, 50%) var(--my, 0%),
    rgb(var(--lit) / 0.055),
    rgb(var(--lit) / 0.014) 42%,
    transparent 68%
  );
  opacity: 0;
  transition: opacity 420ms var(--ease);
  pointer-events: none;
  z-index: 0;
}
.mat-sheen:hover::before { opacity: 1; }

/* Ambient occlusion in the crease where a panel meets the surface behind it.
   Applied to section boundaries; it is what stops a long page reading as one
   continuous flat plane. */
.mat-seam {
  background-image: linear-gradient(
    180deg,
    rgb(var(--shade) / 0.22) 0%,
    transparent 60px
  );
}

@media (prefers-reduced-motion: reduce) {
  .mat-sheen::before { transition: none; }
}

/* ═══════════════════════════════════════════════════════════════════════════
   Applying the surface model to the product.

   Kept here rather than scattered through app.css so that every material
   decision in the interface is visible in one place, and so the tiers stay
   consistent: a mission card and a modal are the same kind of object at
   different elevations, and they should be lit identically.

   The ::after grain overlay is attached by selector, so the components rendered
   from JS templates pick it up without any change to the template. */

.card,
.hero-panel,
.area,
.gs-plan .plan-row,
.gs-facts > *,
.deadline,
.nav,
.btn,
.icon-btn,
.modal-card,
.brief-panel,
.tape,
.gates-panel,
.feed-panel { position: relative; }

.card::after,
.hero-panel::after,
.area::after,
.modal-card::after,
.deadline::after {
  content: "";
  position: absolute; inset: 0; border-radius: inherit;
  background-image: var(--grain);
  background-size: 180px 180px;
  opacity: var(--grain-opacity);
  mix-blend-mode: var(--grain-blend);
  pointer-events: none;
}
/* .card already used ::after for its own hover wash, so that one is re-homed to
   ::before below and the grain takes ::after. Order matters: the wash must sit
   under the grain, because a highlight on a rough surface is still rough. */

/* Mission cards — raised objects, and the primary thing the eye lands on. */
.card {
  background-image: linear-gradient(
    180deg, rgb(var(--lit) / 0.042) 0%, rgb(var(--lit) / 0.010) 40%,
    rgb(var(--shade) / 0.13) 100%);
  box-shadow: var(--edge-top), var(--edge-bottom), var(--bounce), var(--el-1);
  transition:
    border-color var(--dur) var(--ease),
    transform var(--dur) var(--ease),
    box-shadow var(--dur) var(--ease);
}
.card:hover, .card:focus-visible {
  box-shadow: var(--edge-top), var(--edge-bottom), var(--bounce), var(--el-3);
}

/* Instrument surfaces are recessed into the panel, the way real avionics are
   bezelled into a stack. */
.hero-panel,
.gates-panel,
.feed-panel,
.brief-panel {
  background-image: linear-gradient(
    180deg, rgb(var(--shade) / 0.26) 0%, rgb(var(--shade) / 0.06) 30%,
    rgb(var(--lit) / 0.016) 100%);
  box-shadow:
    inset 0 1px 3px rgb(var(--shade) / 0.5),
    inset 0 -1px 0 rgb(var(--lit) / 0.045),
    var(--el-2);
}
/* Individual readouts sit deeper still — glass set into the bezel. */
.hp-cell {
  box-shadow: inset 0 1px 2px rgb(var(--shade) / 0.55), inset 0 -1px 0 rgb(var(--lit) / 0.03);
}

/* Controls read as machined parts. */
.btn, .icon-btn, .nav-btn.on, .deadline {
  background-image:
    linear-gradient(180deg,
      rgb(var(--lit) / 0.07) 0%, rgb(var(--lit) / 0.025) 8%,
      rgb(var(--lit) / 0.006) 34%, rgb(var(--shade) / 0.11) 100%);
  box-shadow:
    inset 0 1px 0 rgb(var(--lit) / 0.10),
    inset 0 -1px 0 rgb(var(--shade) / 0.5),
    var(--el-1);
}
.btn:active, .icon-btn:active {
  box-shadow: inset 0 2px 5px rgb(var(--shade) / 0.5), inset 0 -1px 0 rgb(var(--lit) / 0.03);
  transform: translateY(0.5px);
}
/* The nav is a recess that the active tab rises out of. */
.nav {
  box-shadow: inset 0 1px 3px rgb(var(--shade) / 0.42), inset 0 -1px 0 rgb(var(--lit) / 0.035);
}

/* Ground School area tiles. */
.area {
  background-image: linear-gradient(
    180deg, rgb(var(--lit) / 0.032) 0%, rgb(var(--lit) / 0.008) 42%,
    rgb(var(--shade) / 0.10) 100%);
  box-shadow: var(--edge-top), var(--edge-bottom), var(--bounce);
}

/* Modals float highest, so they get the deepest ambient shadow in the product
   plus a visible top-edge specular to separate them from the scrim. */
.modal-card {
  background-image: linear-gradient(
    180deg, rgb(var(--lit) / 0.05) 0%, rgb(var(--lit) / 0.012) 30%,
    rgb(var(--shade) / 0.10) 100%);
  box-shadow:
    inset 0 1px 0 rgb(var(--lit) / 0.09),
    inset 0 -1px 0 rgb(var(--shade) / 0.5),
    var(--el-3);
}

/* The scrim gets a slight vignette so the modal sits in a pool of light rather
   than on a flat grey sheet. */
.modal {
  background:
    radial-gradient(120% 90% at 50% 32%, rgb(var(--shade) / 0.55), rgb(var(--shade) / 0.82) 70%);
}

/* Grain is suppressed over the live 3D view and over any element whose numbers
   update every frame: a static overlay beneath moving digits shimmers, and the
   renderer applies its own film grain in the scene pass. */
#scene::after, .hud::after, .tape::after { content: none !important; }
