/* MILKMAN ZERO — shared core stylesheet.
   Every HTML page links exactly two stylesheets: this file + its own page CSS.
   Keep anything used on 2+ pages here; page-specific rules belong in the
   per-page file. */

@charset "UTF-8";

/* --- fonts -------------------------------------------------------------- */

@font-face {
  font-family: 'MorePerfectDOSVGA';
  src: url('../assets/fonts/MorePerfectDOSVGA.ttf') format('truetype');
  font-display: block;
}

/* Panel UI font (performer_window + dev_window). Audience pages never
   reference "JetBrains Mono", so this never downloads there. */
@font-face {
  font-family: 'JetBrains Mono';
  src: url('../assets/fonts/JetBrainsMono-Regular.ttf') format('truetype');
  font-weight: 400;
  font-display: block;
}

@font-face {
  font-family: 'JetBrains Mono';
  src: url('../assets/fonts/JetBrainsMono-Bold.ttf') format('truetype');
  font-weight: 700;
  font-display: block;
}

/* --- custom properties -------------------------------------------------- */

:root {
  /* Original MILKMAN ZERO palette: white paper, black ink. */
  --bg: #ffffff;
  --fg: #000000;
  --accent: #000000;

  --font-body: 'MorePerfectDOSVGA', 'Courier New', monospace;
  --font-size-body: 2.2rem;

  --transition-fast: 0.15s;
  --transition-slow: 0.6s;

  /* Phase 2 animation vars — read by home.css keyframes */
  --stab-shake-amount: 25px;
  --stab-flash-color: #8B0000;

  /* HEMORRHAGE (screamSpray) palette — arterial bleed gradient. Spatter
     glyphs render near-black against the red flood (clots, never red-on-red)
     and dry to maroon as the pool soaks away. */
  --blood-bright: #B00000;     /* first decay spurt */
  --blood-arterial: #a00000;   /* attack slam + pulse peak */
  --blood-clot: #1a0000;       /* attack-band spatter (near-black) */
  --blood-dry: #4a0000;        /* drain/pool sludge, final spurt */
  --bone: #f4ece0;             /* inverted --fg during the red flood */
  /* --stab-flash-color (#8B0000) doubles as the pulse trough / dark red. */

  /* changeColor fade length. JS sets this per-animation to match the Python
     timer_wait_then_fire duration; every --bg/--fg consumer transitions over
     it so the body, text, prompt and input recolor in lockstep. */
  --color-transition-duration: 3s;

  --cursor-position: 0;
  --prefix-width: 2ch;
  --char-width: 1ch;

  /* = 1 CSS px at the 1920×1080 design baseline. Layout is now pinned to
     1080p (see html/body rules below) so this is effectively a constant
     1px. The 2× nearest-neighbor upscale on body doubles every CSS px
     into a 2-device-px block on the 4K bitmap. */
  --scale-px: calc(1rem / 16);
}

/* --- viewport-aware scaling -------------------------------------------- */
/* One central formula. Every page uses plain rem. Zero per-page
   html font-size rules, zero per-page scaling media queries.
   1rem = 16px at 1920×1080. min() picks the more-constrained axis so
   non-16:9 viewports letterbox instead of overflowing.
     1920×1080 base:  1rem = 16px
     3840×2160 (2×):  1rem = 32px
     1920×1200 16:10: 1rem = 16px  (width-bound; extra height = slack)
     2560×1440:       1rem ≈ 21.33px
   The 100vw/120, 100vh/67.5 formula resolves to exact integer px at every
   16:9 resolution (32px at 4K, 16px at 1080p) instead of the old 0.833vw
   truncation, which landed at 31.9872px — fractionally off the device-
   pixel grid every rem value depends on.

   Audience HTML now renders NATIVE 4K — webrenderTOP at 3840×2160 with
   1rem = 32px so every rem-based rule doubles to occupy the same relative
   area as the previous 1080p+scale-2 pipeline, but every device pixel is
   painted directly by CEF. Block-doubling shim removed; AA + pixel-perfect
   sub-pixel font hinting are now in play. The performer panel still uses
   its own scale-2 trick inside `performer_window.css`. */
html {
  font-size: min(100vw / 120, 100vh / 67.5);
}

/* --- resets ------------------------------------------------------------- */

* {
  border: none;
  outline: none;
  box-shadow: none;
  box-sizing: border-box;
}

html, body {
  margin: 0;
  padding: 0;
  height: 100vh;
  width: 100vw;
  overflow: hidden;
}

body {
  background-color: var(--bg);
  color: var(--fg);
  font-family: var(--font-body);
  font-size: var(--font-size-body);
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
}

/* --- audience render hooks --------------------------------------------- */
/* Opt-in via `<body class="audience-render">` on every audience HTML.
   The class used to drive a 1080p+scale-2 block-doubling shim; that's
   been removed in favor of native-4K painting. The class is kept as a
   semantic hook in case audience-only styling is needed later. */
body.audience-render {
  /* native 4K — no transform, no image-rendering override. CEF paints
     directly at 3840×2160. rem values from core.css scale via the
     html font-size formula so layout stays proportional.
     Font AA disabled — glyphs render 1-bit (hard on/off) so the DOS
     bitmap font stays crisp pixel-perfect with no grey edge fringe.
     -webkit-text-stroke + text-shadow are belt-and-suspenders for
     Chromium builds that keep subpixel AA on variable-width fonts
     even when -webkit-font-smoothing:none is set. */
  -webkit-font-smoothing: none;
  font-smooth: never;
  text-rendering: geometricPrecision;
  -webkit-text-stroke: 0;
  text-shadow: 0 0 0 currentColor;
}

/* --- shared @keyframes -------------------------------------------------- */
/* Used by home.html effect classes, reusable by future subpages. */

@keyframes stabShake {
  0%   { transform: translate(0, 0); }
  10%  { transform: translate(calc(var(--stab-shake-amount) *  1),    calc(var(--stab-shake-amount) * -0.4)); }
  20%  { transform: translate(calc(var(--stab-shake-amount) * -0.8),  calc(var(--stab-shake-amount) *  0.6)); }
  30%  { transform: translate(calc(var(--stab-shake-amount) *  0.6),  calc(var(--stab-shake-amount) *  0.8)); }
  40%  { transform: translate(calc(var(--stab-shake-amount) * -0.9),  calc(var(--stab-shake-amount) * -0.5)); }
  50%  { transform: translate(calc(var(--stab-shake-amount) *  0.7),  calc(var(--stab-shake-amount) * -0.7)); }
  60%  { transform: translate(calc(var(--stab-shake-amount) * -0.5),  calc(var(--stab-shake-amount) *  0.4)); }
  70%  { transform: translate(calc(var(--stab-shake-amount) *  0.4),  calc(var(--stab-shake-amount) *  0.3)); }
  80%  { transform: translate(calc(var(--stab-shake-amount) * -0.3),  calc(var(--stab-shake-amount) * -0.2)); }
  90%  { transform: translate(calc(var(--stab-shake-amount) *  0.2),  calc(var(--stab-shake-amount) *  0.1)); }
  100% { transform: translate(0, 0); }
}

@keyframes stabFlash {
  0%, 100% { background-color: inherit; }
  15%      { background-color: var(--stab-flash-color); }
  30%      { background-color: inherit; }
}

/* ===========================================================================
   HEMORRHAGE (screamSpray) — one arterial event over duration D.
   Band offsets are fixed: ATTACK 0–12%, DECAY 12–75%, DRAIN 75–92%,
   DRY 92–100%. home.js sizes every animation-duration to the D it receives
   from TD (TD owns the clock; these keyframes own the pixels). The
   arterial-red override color is injected inline as --hem-arterial; the rest
   come from the palette tokens above.
   =========================================================================== */

/* Body background: hard-cut slam to arterial red, eased heartbeat decay
   (peaks → arterial, troughs → dark red, each throb shallower), then fade
   back to paper-white through the drain/dry bands. The browser sets
   transition:none on body before applying this so the slam reads as a hard
   cut, not a 3s changeColor fade. */
@keyframes hemorrhageBg {
  0%    { background-color: #ffffff; }
  /* ATTACK — instantaneous slam (no ease into it). */
  0.01% { background-color: var(--hem-arterial, var(--blood-arterial)); }
  12%   { background-color: var(--hem-arterial, var(--blood-arterial)); }
  /* DECAY — dying heartbeat. Throbs get shallower as they march toward
     drain; troughs sink to the dark-red trough color (#8B0000). */
  24%   { background-color: var(--stab-flash-color); }
  33%   { background-color: var(--hem-arterial, var(--blood-arterial)); }
  45%   { background-color: var(--stab-flash-color); }
  54%   { background-color: #960000; }
  63%   { background-color: var(--stab-flash-color); }
  75%   { background-color: var(--blood-dry); }
  /* DRAIN / DRY — soak back to exact paper white from the top down. */
  92%   { background-color: #d9b8b8; }
  100%  { background-color: #ffffff; }
}

/* Text color: invert to bone while the red floods, settle to exact black as
   it drains so the body copy stays legible the whole way through. */
@keyframes hemorrhageFg {
  0%    { color: #000000; }
  0.01% { color: var(--bone); }
  75%   { color: var(--bone); }
  92%   { color: #3a1010; }
  100%  { color: #000000; }
}

/* One-frame whole-screen impact: a hard shake plus a horizontal
   scanline-tear / chroma-shift garnish, all spent inside the ATTACK band.
   --hem-shake is set inline per-fire from the intensity profile. */
@keyframes hemorrhageSlam {
  0%   { transform: translate(0, 0) skewX(0deg);
         filter: none; }
  2%   { transform: translate(calc(var(--hem-shake) *  1.0), calc(var(--hem-shake) * -0.5)) skewX(-1.5deg);
         filter: drop-shadow(6px 0 0 rgba(255,0,0,0.5)) drop-shadow(-6px 0 0 rgba(0,255,255,0.4)); }
  5%   { transform: translate(calc(var(--hem-shake) * -0.9), calc(var(--hem-shake) *  0.6)) skewX(1.5deg);
         filter: drop-shadow(-5px 0 0 rgba(255,0,0,0.5)); }
  8%   { transform: translate(calc(var(--hem-shake) *  0.6), calc(var(--hem-shake) *  0.3)) skewX(0deg);
         filter: none; }
  11%  { transform: translate(calc(var(--hem-shake) * -0.3), calc(var(--hem-shake) * -0.2)) skewX(0deg); }
  12%, 100% { transform: translate(0, 0) skewX(0deg); filter: none; }
}

/* Glyph spatter overlay — absolutely positioned, full-viewport, above text,
   never receives pointer events and never participates in body layout. */
#hemorrhage-overlay {
  position: absolute;
  inset: 0;
  pointer-events: none;
  z-index: 9000;
  overflow: hidden;
  font-family: var(--font-body);
  contain: layout style paint;
}

/* A single blood drop. It is positioned at its LANDING spot inline (left/top
   in px); the flight is expressed as a transform OFFSET that starts at the
   word's origin (--hem-from-x/y, the vector from landing point back to the
   word) and resolves to 0 on impact. Mass drives font-size inline (specks
   small, heavy drops big). --hem-drip is the inline downward glide distance;
   --hem-squash is the impact-settle scale. The glyph's own animation-duration
   is sized per-drop so its life (flight + stick + drip + dry) fits the shared
   D timeline; pool-bound heavy drops use the full D so their long glide lands
   in the DRAIN band. */
.hem-glyph {
  position: absolute;
  display: block;
  width: var(--hem-cell, 1ch);
  height: var(--hem-cell, 1ch);
  line-height: var(--hem-cell, 1ch);
  text-align: center;
  white-space: pre;
  color: var(--blood-clot);
  opacity: 0;
  transform-origin: center top;
  will-change: transform, opacity;
  animation-name: hemDropLife;
  animation-timing-function: linear;
  animation-fill-mode: both;
}

/* One drop's life on its own (per-glyph-duration) timeline:
     0–8%    FLIGHT  — travel from the word origin (--hem-from-x/y) to landing
                       (offset 0). A ballistic arc: lateral closes linearly
                       while the vertical overshoots low then settles, reading
                       as a gravity bend. The drop is hidden until it leaves
                       the muzzle (1%) so it doesn't smear from frame 0.
     8–14%   IMPACT  — quick squash (scale up via --hem-squash, ~1.3) then
                       settle to 1, so the drop reads as hitting glass.
     14–26%  STICK   — held in place, color ages bright → arterial.
     26–88%  DRIP    — glassy downward glide of --hem-drip px. Slow start,
                       gentle mid accel, decelerating stall (the cubic %s).
                       Specks have a small --hem-drip and a short total
                       duration so they stall + dry early; heavy pool-bound
                       drops have a large --hem-drip and the full D, so the
                       same decel curve still covers the whole screen and the
                       glide arrives during the global DRAIN band.
     88–100% DRY     — fade out through --blood-dry (specks dry where they
                       stalled; pool drops fade into the thickening sludge). */
@keyframes hemDropLife {
  0% {
    opacity: 0;
    transform: translate(var(--hem-from-x, 0), var(--hem-from-y, 0)) scale(0.9);
    color: var(--blood-clot);
  }
  /* muzzle — drop becomes visible just after leaving the word. */
  1% {
    opacity: 1;
    transform: translate(calc(var(--hem-from-x, 0) * 0.92),
                         calc(var(--hem-from-y, 0) * 0.86)) scale(0.95);
    color: var(--blood-clot);
  }
  /* arc apex — lateral mostly closed while the vertical leads it down, so the
     path curves (ballistic) rather than tracking a straight line to landing. */
  5% {
    opacity: 1;
    transform: translate(calc(var(--hem-from-x, 0) * 0.30),
                         calc(var(--hem-from-y, 0) * 0.12)) scale(1);
    color: var(--blood-clot);
  }
  /* IMPACT — touchdown + squash against the glass. */
  8% {
    opacity: 1;
    transform: translate(0, 0) scaleX(var(--hem-squash, 1.3)) scaleY(0.7);
    color: var(--blood-clot);
  }
  14% {
    opacity: 1;
    transform: translate(0, 0) scale(1);
    color: var(--blood-clot);
  }
  /* STICK — clinging, color ages. */
  26% {
    opacity: 1;
    transform: translate(0, 0) scale(1);
    color: var(--blood-bright);
  }
  /* DRIP — glassy glide: slow start, accel, decelerating stall. */
  45% {
    opacity: 1;
    transform: translate(0, calc(var(--hem-drip, 0) * 0.18)) scale(1);
    color: var(--blood-arterial);
  }
  66% {
    opacity: 1;
    transform: translate(0, calc(var(--hem-drip, 0) * 0.62)) scale(1);
    color: var(--stab-flash-color);
  }
  88% {
    opacity: 0.9;
    transform: translate(0, var(--hem-drip, 0)) scale(1);
    color: var(--blood-dry);
  }
  /* DRY — soaked away. */
  100% {
    opacity: 0;
    transform: translate(0, var(--hem-drip, 0)) scale(1);
    color: var(--blood-dry);
  }
}

/* DECAY spurt drops reuse hemDropLife but launch later (animation-delay set
   inline) and are sparser/darker — the inline --hem-tint shifts their base
   color. Class kept distinct so home.js can target them for teardown. */
.hem-glyph.hem-spurt {
  color: var(--hem-tint, var(--blood-bright));
}

/* A thin fading drip trail behind a heavy pool-bound drop. It is a child
   pseudo-stripe stretched up from the drop, faded along its length, riding
   the same per-drop timeline so it grows with the glide and fades in DRY. */
.hem-glyph.hem-trail::before {
  content: "";
  position: absolute;
  left: 50%;
  /* anchored at the drop's top and grown UPWARD, so the stripe lengthens
     behind the descending drop (toward the stick point it came from). */
  bottom: 50%;
  width: 0.18ch;
  height: var(--hem-drip, 0);
  transform: translateX(-50%) scaleY(0);
  transform-origin: bottom center;
  background: linear-gradient(to top,
    var(--blood-dry), var(--blood-dry) 40%, transparent);
  opacity: 0;
  pointer-events: none;
  animation-name: hemTrailLife;
  animation-duration: inherit;
  animation-delay: inherit;
  animation-timing-function: linear;
  animation-fill-mode: both;
}

/* Trail only exists once the drop starts gliding; it fades with the DRY band.
   scaleY rides the same drip progress so the stripe lengthens behind the
   drop rather than popping in at full height. */
@keyframes hemTrailLife {
  0%, 26%  { opacity: 0;    transform: translateX(-50%) scaleY(0); }
  45%      { opacity: 0.28; transform: translateX(-50%) scaleY(0.18); }
  66%      { opacity: 0.34; transform: translateX(-50%) scaleY(0.62); }
  88%      { opacity: 0.3;  transform: translateX(-50%) scaleY(1); }
  100%     { opacity: 0;    transform: translateX(-50%) scaleY(1); }
}

/* The bottom-edge pool: a dark-maroon sludge line that thickens through
   DRAIN then soaks away in DRY. Height/opacity ride the shared timeline. */
#hemorrhage-pool {
  position: absolute;
  left: 0;
  right: 0;
  bottom: 0;
  height: 0;
  background: var(--blood-dry);
  pointer-events: none;
  z-index: 8999;
  animation-name: hemPool;
  animation-timing-function: ease-in-out;
  animation-fill-mode: both;
}

@keyframes hemPool {
  0%, 60% { height: 0; opacity: 0.95; }
  75%     { height: 0.6rem; opacity: 0.95; }   /* glyphs begin pooling */
  92%     { height: 2.2rem; opacity: 0.9; }    /* thickest sludge */
  100%    { height: 0; opacity: 0; }           /* soaked away */
}

@keyframes rewriteBlink {
  0%, 50%   { opacity: 1; }
  51%, 100% { opacity: 0; }
}

@keyframes inputBlink {
  0%, 49%   { opacity: 1; }
  50%, 100% { opacity: 0; }
}
