/* ===========================================================================
   thesaltedhash.tv
   ===========================================================================
   Every colour is a custom property. The values below are the house palette
   (eye-of-the-hash, client/src/sceneTokens.js) and are what a visitor without
   JavaScript sees; with it, js/palette.js walks them through the other house
   presets a few times a second.

   The terminal box, the arched title and the spark are the scene cards' look
   (client/public/shared/scene-card.css), copied and resized for a page that
   has to work from a phone to a 1440p monitor.
   =========================================================================== */

@font-face {
  /* Rubik Mono One, cut down to capitals, digits and a little punctuation
     (8 KB). SIL OFL 1.1, licence in assets/OFL-rubik-mono-one.txt. */
  font-family: "Salted Title";
  src: url("assets/rubik-mono-one-caps.woff2") format("woff2");
  font-display: swap;
}

:root {
  --bg: #01060D; --panel: #031228; --sunk: #052B59; --edge: #1E66B8;
  --accent: #63B3ED; --accent-deep: #1E66B8; --accent2: #4FD2FF; --accent3: #E5F77D;
  --alert: #FF5A6E; --ink: #EBF8FF; --ink-dim: #8CC8FF; --ink-faint: #4A7BA8;
  --warp: 0;
  --mono: ui-monospace, "SFMono-Regular", "Cascadia Mono", Menlo, Consolas, "Liberation Mono", "DejaVu Sans Mono", monospace;
  --title: "Salted Title", var(--mono);
  color-scheme: dark;
  /* The terminal is sized in rem, and rem follows the screen. */
  font-size: clamp(14px, 0.36vw + 10px, 19.5px);
}

* { box-sizing: border-box; }
html, body { margin: 0; }
body {
  min-height: 100vh; min-height: 100svh;
  overflow-x: hidden;
  background: radial-gradient(ellipse at 50% 40%, var(--sunk), var(--bg) 70%) fixed, var(--bg);
  color: var(--ink);
  font-family: var(--mono);
  -webkit-tap-highlight-color: transparent;
  -webkit-user-select: none; user-select: none;
  -webkit-touch-callout: none;
}

.sr-only, .js .title-plain {
  position: absolute; width: 1px; height: 1px; overflow: hidden;
  clip-path: inset(50%); white-space: nowrap;
}

/* ---- layer 1: deep space ---------------------------------------------- */
#space { position: fixed; inset: 0; width: 100%; height: 100%; display: block; z-index: 0; }
/* Layer 3, the season's weather: over the stars, under everything else. */
#weather { position: fixed; inset: 0; width: 100%; height: 100%; display: block; z-index: 1; pointer-events: none; }
.scanlines {
  position: fixed; inset: 0; z-index: 1; pointer-events: none;
  background: repeating-linear-gradient(to bottom, transparent 0 2px, rgba(0, 0, 0, 0.22) 3px 4px);
}
.vignette {
  position: fixed; inset: 0; z-index: 1; pointer-events: none;
  box-shadow: inset 0 0 min(18vw, 180px) rgba(0, 3, 10, 0.92);
}

/* ---- the stage -------------------------------------------------------- */
.stage {
  position: relative; z-index: 2;
  min-height: 100vh; min-height: 100svh;
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(0, min(52rem, 52vw)) minmax(0, 1fr);
  grid-template-rows: auto auto;
  align-content: center;
  column-gap: clamp(16px, 3vw, 64px);
  row-gap: clamp(8px, 2.2vh, 32px);
  padding: 24px 16px 120px;
}

/* ---- layer 5: the title ------------------------------------------------ */
.title {
  grid-column: 1 / -1; grid-row: 1;
  margin: 0; font-weight: normal;
}
.no-js .title-plain {
  display: block; text-align: center;
  font: clamp(26px, 7vw, 96px) var(--title); text-transform: uppercase;
  color: var(--accent); text-shadow: 0 0 8px var(--accent), 0 0 18px var(--accent);
}
.no-js .arch { display: none; }
.arch {
  position: relative; display: block; width: 100%;
  height: 120px; /* replaced by title.js once it has measured */
  --title-px: 64px;
}
.arch-char {
  position: absolute; left: 50%; top: 0;
  font: var(--title-px) / 1.1 var(--title);
  color: var(--accent);
  text-shadow:
    calc(var(--warp) * -6px) 0 rgba(255, 40, 110, calc(var(--warp) * 0.85)),
    calc(var(--warp) * 6px) 0 rgba(40, 210, 255, calc(var(--warp) * 0.85)),
    0 0 0.08em var(--accent), 0 0 0.17em var(--accent);
  transform-origin: bottom center;
  transition: color 0.4s ease, opacity 0.4s ease, text-shadow 0.4s ease;
  will-change: transform;
  white-space: pre;
}
.spark {
  position: absolute; left: 50%; top: 0; z-index: 2;
  width: 16px; height: 16px; border-radius: 50%;
  background: var(--ink);
  box-shadow: 0 0 12px var(--ink), 0 0 25px var(--accent3), 0 0 40px var(--accent);
  opacity: 0; pointer-events: none;
  transition: opacity 0.3s ease;
}

/* ---- layer 4: Hashy ---------------------------------------------------- */
.hashy {
  grid-column: 1; grid-row: 2;
  justify-self: end; align-self: center;
  width: clamp(150px, 16vw, 380px);
  cursor: pointer;
  -webkit-user-drag: none;
}
.hashy-float { animation: hashy-float 7s ease-in-out infinite; }
.hashy-tilt { position: relative; }
/* A soft glow behind him, drawn once, rather than a drop-shadow filter that
   would be recomputed for every video frame. */
.hashy-tilt::before {
  content: ''; position: absolute; inset: 8% -10% 0; z-index: -1; pointer-events: none;
  background: radial-gradient(closest-side, color-mix(in srgb, var(--accent) 32%, transparent), transparent);
}
.hashy-img, .hashy-live { display: block; width: 100%; height: auto; }
.hashy-live { position: absolute; inset: 0; height: 100%; }
.hashy.live .hashy-img { visibility: hidden; }
.hashy.asleep .hashy-img, .hashy.asleep .hashy-live { filter: brightness(0.85); }
/* The video only feeds the canvas; it is never seen. Kept in the page and
   not display:none, because iOS will not decode a video it considers gone. */
.hashy-video { position: fixed; left: 0; top: 0; width: 2px; height: 2px; opacity: 0; pointer-events: none; }
@keyframes hashy-float { 0%, 100% { transform: translateY(0); } 50% { transform: translateY(-3%); } }

/* The expressions: VTube Studio item sequences as strips, one frame per step. */
.fx {
  position: absolute; pointer-events: none; opacity: 0;
  background-repeat: no-repeat; background-position: 0 0;
}
.fx-sparkle { left: 4%; top: -6%; width: 92%; aspect-ratio: 217 / 202; background-image: url("assets/sparkle.webp"); background-size: 3000% 100%; }
.fx-blush   { left: 17%; top: 7%; width: 66%; aspect-ratio: 157 / 113; background-image: url("assets/blush.webp"); background-size: 2400% 100%; }
.fx-zzz     { left: 68%; top: -22%; width: 14%; aspect-ratio: 46 / 117; background-image: url("assets/zzz.webp"); background-size: 2400% 100%; }
.fx-sparkle.play { animation: fx-frames 1.8s steps(30, jump-none) 1 forwards, fx-life 1.8s linear 1 forwards; }
/* The blush and the zzz strips are one 8-frame loop drawn three times over. */
.fx-blush.play   { animation: fx-frames 1.5s steps(24, jump-none) infinite, fx-life 3.6s linear 1 forwards; }
.fx-zzz.play     { opacity: 1; animation: fx-frames 2.6s steps(24, jump-none) infinite; }
@keyframes fx-frames { from { background-position-x: 0%; } to { background-position-x: 100%; } }
@keyframes fx-life { 0%, 85% { opacity: 1; } 100% { opacity: 0; } }

/* ---- layer 5: the terminal -------------------------------------------- */
.term-wrap {
  grid-column: 2; grid-row: 2;
  align-self: center;
  padding: 2px; border-radius: 14px;
  background: linear-gradient(135deg, var(--accent), var(--accent2), var(--accent3));
  box-shadow:
    0 0 calc(35px + 80px * var(--warp)) color-mix(in srgb, var(--accent) 28%, transparent);
}
.term {
  position: relative; overflow: hidden;
  padding: clamp(18px, 2.4rem, 52px) clamp(14px, 2.6rem, 56px) clamp(16px, 2rem, 44px);
  border-radius: 12px;
  background: color-mix(in srgb, var(--panel) 93%, transparent);
  user-select: text; -webkit-user-select: text;
}
.term::after {
  content: ''; position: absolute; inset: 0; pointer-events: none;
  background: repeating-linear-gradient(to bottom, transparent 0 2px, rgba(0, 0, 0, 0.18) 3px 4px);
}
.term p { margin: 0; }
.term-head {
  font-size: 0.8rem; letter-spacing: 0.3em; text-align: center;
  color: var(--accent3);
  text-shadow: 0 0 10px color-mix(in srgb, var(--accent3) 60%, transparent);
}
.boot {
  margin-top: 1.1rem !important;
  min-height: 2.9em; font-size: 0.95rem; line-height: 1.45; letter-spacing: 0.04em;
  text-align: center;
  color: var(--accent);
  text-shadow: 0 0 10px color-mix(in srgb, var(--accent) 60%, transparent);
  transition: opacity 0.3s ease;
}
.boot.fade { opacity: 0; }
/* An announcement from the Hashboard: the lime of the terminal's own header. */
.boot.notice { color: var(--accent3); text-shadow: 0 0 10px color-mix(in srgb, var(--accent3) 55%, transparent); }
.cursor {
  display: inline-block; width: 0.6em; height: 1.1em; margin-left: 0.4em; vertical-align: -0.2em;
  background: var(--accent3);
  animation: blink 1s steps(1) infinite;
}
@keyframes blink { 50% { opacity: 0; } }
.divider {
  height: 2px; margin: 1.1rem 0 1rem;
  background: linear-gradient(90deg, transparent, var(--accent), var(--accent2), var(--accent3), transparent);
  box-shadow: 0 0 10px color-mix(in srgb, var(--accent) 50%, transparent);
}
.links { list-style: none; margin: 0; padding: 0; }
.links li + li { margin-top: 0.3rem; }
.link {
  display: flex; flex-wrap: wrap; align-items: baseline; column-gap: 1ch;
  min-height: 48px; padding: 0.6rem 0.8rem; border-radius: 8px;
  font-size: 1.1rem; line-height: 1.35;
  color: var(--ink); text-decoration: none;
  transition: background-color 0.2s ease, opacity 0.2s ease;
}
.link .k { color: var(--accent3); font-weight: 700; min-width: calc((var(--label-ch, 9) + 0.5) * 1ch); white-space: pre; }
.link .v { color: var(--accent); overflow-wrap: anywhere; }
.link:hover, .link:focus-visible { background: color-mix(in srgb, var(--accent) 16%, transparent); }
.link:hover .v, .link:focus-visible .v { color: var(--ink); text-decoration: underline; text-underline-offset: 0.2em; }
.link:focus-visible { outline: 3px solid var(--accent3); outline-offset: 2px; opacity: 1 !important; }
.term.typing .link:not(.on) { opacity: 0; }
.status {
  margin-top: 1rem !important; padding: 0 0.8rem; min-height: 1.4em;
  font-size: 0.8rem; letter-spacing: 0.16em;
  color: var(--ink-dim);
}

/* ---- the live line (js/live.js) ------------------------------------------ */
.status[data-state="live"] {
  color: var(--alert);
  text-shadow: 0 0 10px color-mix(in srgb, var(--alert) 55%, transparent);
}
.status[data-state="offline"] { color: var(--ink-dim); }
.link.live .k::after {
  content: " \25CF LIVE"; /* a dot and the word */
  color: var(--alert); font-size: 0.8em; letter-spacing: 0.08em;
  animation: live-pulse 1.4s ease-in-out infinite;
}
@keyframes live-pulse { 50% { opacity: 0.35; } }

/* ---- the clips (js/live.js) ------------------------------------------------ */
.clips {
  grid-column: 2; grid-row: 3;
  display: grid; gap: 0.6rem;
  margin: 0; min-width: 0;
}
.clips[hidden] { display: none; }
.clips-head {
  margin: 0; font-size: 0.75rem; letter-spacing: 0.3em; color: var(--accent3);
  text-shadow: 0 0 10px color-mix(in srgb, var(--accent3) 50%, transparent);
}
.clip-list {
  list-style: none; margin: 0; padding: 0;
  display: grid; gap: 0.6rem; grid-template-columns: repeat(4, minmax(0, 1fr));
}
.clip {
  display: grid; gap: 0.35rem; height: 100%;
  padding: 0.45rem; border-radius: 10px;
  border: 1px solid color-mix(in srgb, var(--edge) 70%, transparent);
  background: color-mix(in srgb, var(--panel) 88%, transparent);
  color: var(--ink); text-decoration: none;
  transition: border-color 0.2s ease, transform 0.2s ease;
}
.clip:hover, .clip:focus-visible { border-color: var(--accent); transform: translateY(-2px); }
.clip:focus-visible { outline: 3px solid var(--accent3); outline-offset: 2px; }
.clip img {
  display: block; width: 100%; height: auto; aspect-ratio: 16 / 9; object-fit: cover;
  border-radius: 6px; background: var(--sunk);
}
.clip-text { display: grid; gap: 0.15rem; min-width: 0; }
.clip-title {
  font-size: 0.82rem; line-height: 1.3;
  display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden;
  overflow-wrap: anywhere;
}
.clip-meta { font-size: 0.72rem; color: var(--ink-dim); letter-spacing: 0.04em; }

/* Laptops and up: the clips stand in the empty column right of the terminal,
   a compact list with the thumbnail beside the title. */
@media (min-width: 1200px) and (min-aspect-ratio: 1/1) {
  .clips { grid-column: 3; grid-row: 2; align-self: center; justify-self: start; width: min(100%, 21rem); }
  .clip-list { grid-template-columns: 1fr; }
  .clip { grid-template-columns: clamp(5.5rem, 6.5vw, 7.5rem) minmax(0, 1fr); align-items: center; gap: 0.7rem; }
}

/* ---- the hashbrowns ----------------------------------------------------- */
.spuds {
  position: fixed; left: 0; right: 0; bottom: 0; z-index: 3;
  /* Visible, so hash_b0t's finishers can go up (a UFO, a firework). A fixed
     element's overflow never scrolls the page. */
  height: 100px; overflow: visible; pointer-events: none;
}
.spud {
  position: absolute; left: 0; bottom: 8px;
  width: 64px; height: 90px;
  display: flex; justify-content: center; align-items: flex-end;
  pointer-events: auto; cursor: pointer;
  will-change: transform; transform-origin: 50% 100%;
  transition: opacity 0.5s ease;
}
.spud .sprite { margin-top: 0; }
/* ⚠️ THE STAGE DRAWS WITH content-box, AND `* { box-sizing: border-box }`
   ABOVE SHRANK EVERY POTATO BY ITS 3px OUTLINE (50x38 against the stream's
   56x44) and every hat and hash_b0t's rig with it. Everything inside a lane
   slot is the stage's own CSS, so it gets the stage's box model. (The
   wardrobe page has the same rule in wardrobe.css.) */
.spud * { box-sizing: content-box; }
.spud.walking .sprite { animation: waddle var(--waddle, 0.5s) ease-in-out infinite alternate; }
.spud.hop .sprite { animation: hop 0.55s cubic-bezier(0.3, 1.5, 0.5, 1) 1; }
.spud.fresh .sprite { animation: pop-in 0.45s cubic-bezier(0.3, 1.6, 0.5, 1) 1; }
.spud.leaving { opacity: 0; }
@keyframes waddle { from { transform: rotate(-5deg); } to { transform: rotate(5deg) translateY(-3px); } }
@keyframes hop {
  0% { transform: translateY(0) scale(1.12, 0.84); }
  40% { transform: translateY(-30px) scale(0.92, 1.1); }
  100% { transform: none; }
}
@keyframes pop-in { from { transform: translateY(-40px) scale(0.3); opacity: 0; } to { transform: none; opacity: 1; } }
/* The faces' moods (happy, wide, O, grin, flat) moved to potato.css, which the
   stream shares, on 2026-09-25. */
.spud .sprite.bouncer-rig { margin-top: 0; }
.spud.hashbot { cursor: default; pointer-events: none; }
/* Big screens scale the potatoes in potatoes.js (spudScale), not with zoom. */

/* ---- the warp ----------------------------------------------------------- */

/* ---- narrow screens: one column ----------------------------------------- */
@media (max-width: 900px), (max-aspect-ratio: 1/1) {
  .stage {
    grid-template-columns: minmax(0, 1fr);
    grid-template-rows: auto auto auto;
    align-content: start;
    padding-top: max(16px, env(safe-area-inset-top));
  }
  /* Under the title on a phone, so he is pointing up at it there too. */
  .title { grid-row: 1; }
  .hashy { grid-column: 1; grid-row: 2; justify-self: center; width: clamp(96px, 26vw, 180px); }
  .term-wrap { grid-column: 1; grid-row: 3; width: min(100%, 40rem); justify-self: center; }
}
@media (max-width: 900px), (max-aspect-ratio: 1/1) {
  .clips { grid-column: 1; grid-row: 4; width: min(100%, 40rem); justify-self: center; }
  .clip-list { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}
@media (max-width: 520px) {
  /* A phone: every URL on one line of its own, under its label. */
  .term { padding-left: 12px; padding-right: 12px; }
  .link { flex-direction: column; font-size: 1rem; padding: 0.5rem 0.55rem; }
  .status { padding: 0 0.55rem; }
  .link .v { overflow-wrap: normal; white-space: nowrap; }
}
@media (max-width: 900px) and (min-height: 700px), (max-aspect-ratio: 1/1) and (min-height: 700px) {
  .stage { align-content: center; }
}

/* ---- less motion --------------------------------------------------------- */
@media (prefers-reduced-motion: reduce) {
  .hashy-float, .cursor, .spud .sprite { animation: none !important; }
  .fx { display: none; }
  .arch-char, .boot, .link, .clip { transition: none; }
  .link.live .k::after { animation: none; }
}
