/* =====================================================================
   宁昊 · Ninghao — "The Sky Is Thinking / 昊 · 天在思考"
   Near-black sky. One amber point of light (日). One self-revising line.
   Everything else is empty sky. Restraint is the brief.
   ===================================================================== */

/* self-hosted fonts — no external CDN (Google Fonts is blocked / slow in mainland CN) */
@font-face {
  font-family: "Newsreader";
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: url("/assets/fonts/newsreader-400.woff2") format("woff2");
}
@font-face {
  font-family: "IBM Plex Mono";
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: url("/assets/fonts/ibm-plex-mono-400.woff2") format("woff2");
}

:root {
  /* palette — dark (default). Four roles + an rgb mirror of the accent for glows. */
  --sky:   #0A0B0D;   /* background, never pure black */
  --ink:   #E8E4DB;   /* committed / settled text — warm paper white */
  --ghost: #5B6068;   /* in-progress text, the caret's text, quiet labels */
  --sun:   #F2C14E;   /* the single accent: the 日 dot + the caret */
  --sun-rgb: 242, 193, 78;
  --atmos: radial-gradient(120% 90% at 50% 30%, #11131A 0%, #0A0B0D 46%, #07080A 100%);
  --grain-blend: screen;
  --grain-opacity: 0.026;

  --display: "Newsreader", Georgia, "Times New Roman", serif;
  --mono:    "IBM Plex Mono", ui-monospace, "SFMono-Regular", Menlo, monospace;

  --ease: cubic-bezier(0.22, 0.61, 0.36, 1);
}

/* light palette — warm daylight. Used when the system prefers light AND the visitor
   hasn't chosen, or when they explicitly pick light via the toggle. */
@media (prefers-color-scheme: light) {
  :root:not([data-theme]) {
    --sky:   #ECE7DB;
    --ink:   #1B1C1A;
    --ghost: #87897F;
    --sun:   #D9870F;
    --sun-rgb: 217, 135, 15;
    --atmos: radial-gradient(120% 90% at 50% 30%, #F8F4EA 0%, #ECE7DB 52%, #DCD5C3 100%);
    --grain-blend: multiply;
    --grain-opacity: 0.05;
  }
}
:root[data-theme="light"] {
  --sky:   #ECE7DB;
  --ink:   #1B1C1A;
  --ghost: #87897F;
  --sun:   #D9870F;
  --sun-rgb: 217, 135, 15;
  --atmos: radial-gradient(120% 90% at 50% 30%, #F8F4EA 0%, #ECE7DB 52%, #DCD5C3 100%);
  --grain-blend: multiply;
  --grain-opacity: 0.05;
}

*, *::before, *::after { box-sizing: border-box; }

html, body {
  margin: 0;
  height: 100%;
  background: var(--sky);
  color: var(--ink);
  overflow: hidden;   /* the breathing halo may reach the edges; never scroll */
  transition: background-color 0.6s var(--ease), color 0.6s var(--ease);
}

body {
  font-family: var(--display);
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;
  overflow: hidden;
  /* the page is never blank, never scrolls; it is a single held breath */
}

.sr-only {
  position: absolute !important;
  width: 1px; height: 1px;
  margin: -1px; padding: 0; border: 0;
  clip: rect(0 0 0 0); clip-path: inset(50%);
  overflow: hidden; white-space: nowrap;
}

/* ---- atmosphere: vignette + faint grain so the dark has depth ---------- */
.sky {
  position: fixed;
  inset: 0;
  z-index: 0;
  pointer-events: none;
  background: var(--atmos);
  transition: background 0.6s var(--ease);
}
.sky::after {
  /* faint procedural grain; keeps the field alive without "design" */
  content: "";
  position: absolute;
  inset: -50%;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='160' height='160'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.9' numOctaves='2' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23n)'/%3E%3C/svg%3E");
  opacity: var(--grain-opacity);
  mix-blend-mode: var(--grain-blend);
}

/* ---- the field: three horizontal zones, no boxes, no dividers --------- */
.field {
  position: relative;
  z-index: 1;
  height: 100%;
  min-height: 100svh;
  width: 100%;
}

/* zone 1 (~22% VH): the sun — the only persistent bright object.
   .sun is a zero-size anchor; halo + core are centered on it. */
.sun {
  position: absolute;
  top: 22%;
  left: 50%;
  transform: translate(-50%, -50%);
  width: min(380px, 88vmin);   /* generous click target, but never wider than the viewport */
  height: min(380px, 88vmin);
  display: grid;
  place-items: center;
  padding: 0;
  border: 0;
  background: transparent;
  opacity: 0;
  -webkit-tap-highlight-color: transparent;
}
.js .sun { cursor: pointer; }
.sun.is-awake { pointer-events: none; cursor: default; }   /* big hit area only matters until woken */
.sun:focus-visible { outline: none; }
.sun__halo,
.sun__core {
  position: absolute;
  left: 50%;
  top: 50%;
  border-radius: 50%;
  transform: translate(-50%, -50%);
  pointer-events: none;
}
.sun__core {
  width: 13px;
  height: 13px;
  background: var(--sun);
  box-shadow: 0 0 16px 3px rgba(var(--sun-rgb), 0.65);
  transition: width 0.8s var(--ease), height 0.8s var(--ease);
}
.sun__halo {
  width: 200px;
  height: 200px;
  opacity: 0.7;
  transition: width 0.8s var(--ease), height 0.8s var(--ease);
  background: radial-gradient(circle,
    rgba(var(--sun-rgb), 0.50) 0%,
    rgba(var(--sun-rgb), 0.16) 32%,
    rgba(var(--sun-rgb), 0.04) 56%,
    transparent 72%);
}
/* hover / focus: a gentle swell to signal the light can be touched */
.js .sun:hover .sun__halo,
.js .sun:focus-visible .sun__halo { opacity: 0.98; }
.js .sun:hover .sun__core,
.js .sun:focus-visible .sun__core { box-shadow: 0 0 20px 5px rgba(var(--sun-rgb), 0.85); }

/* audio-reactive: the 6s breathing KEEPS running; the sound just adds an extra
   bloom + brightness on top (so it never looks frozen when the drone is quiet). */
.sun.is-reactive {
  transform: translate(-50%, -50%) scale(calc(1 + var(--e, 0) * 0.5));
  filter: brightness(calc(1 + var(--e, 0) * 0.6));
}

/* the guide: a soft thread of light from the line up to the sun. It breathes on the
   SAME 6s rhythm as the sun (delay 2.1s) so the two pulse together, leading the eye up. */
.beam {
  position: absolute;
  left: 50%;
  top: 25%;
  height: 15%;
  width: 2px;
  transform: translateX(-50%);
  transform-origin: top center;
  border-radius: 2px;
  background: linear-gradient(to bottom,
    rgba(var(--sun-rgb), 0.6) 0%,
    rgba(var(--sun-rgb), 0.22) 42%,
    rgba(var(--sun-rgb), 0) 100%);
  filter: blur(1px);
  pointer-events: none;
  opacity: 0;
  transition: width 0.8s var(--ease), height 0.8s var(--ease);
}
.js .beam { animation: beam-breathe 6s ease-in-out 2.1s infinite; }
@keyframes beam-breathe {
  0%, 100% { opacity: 0.12; transform: translateX(-50%) scaleY(0.65); }
  50%      { opacity: 0.7;  transform: translateX(-50%) scaleY(1.04); }
}

/* after the light is clicked (.live), the whole breath grows. We enlarge the BASE
   size (not the keyframe), so the breathing keeps its phase and swells smoothly. */
.live .sun__halo { width: 300px; height: 300px; }
.live .sun__core { width: 17px; height: 17px; }
.live .beam { height: 18%; width: 3px; }

/* zone 2 (~42% VH): the self-revising inference line */
.infer {
  position: absolute;
  top: 42%;
  left: 50%;
  transform: translate(-50%, -50%);
  width: min(760px, 90vw);
  margin: 0;
  text-align: center;
  font-family: var(--mono);
  font-weight: 400;
  font-size: clamp(1.08rem, 0.82rem + 1.9vw, 1.68rem);
  line-height: 1.55;
  letter-spacing: 0.005em;
  opacity: 0;
  transition: opacity 0.7s var(--ease);
}
.infer.is-out { opacity: 0; }

.infer__text {
  color: var(--ghost);
  transition: color 0.4s var(--ease);
}
.infer__text.is-committed { color: var(--ink); }

/* the amber block caret — the second, smaller point of light */
.caret {
  display: inline-block;
  width: 0.52em;
  height: 1.05em;
  margin-left: 0.06em;
  vertical-align: -0.16em;
  background: var(--sun);
  box-shadow: 0 0 9px rgba(var(--sun-rgb), 0.5);
  animation: blink 1.06s steps(1, end) infinite;
}
@keyframes blink {
  0%, 50%   { opacity: 1; }
  50.01%, 100% { opacity: 0; }
}

/* zone 3 (bottom ~12% VH): the wordmark stack */
.mark {
  position: absolute;
  left: 0;
  right: 0;
  bottom: clamp(64px, 13vh, 116px);
  text-align: center;
  opacity: 1;
}
.mark__name {
  font-family: var(--display);
  font-weight: 400;
  font-size: clamp(1.25rem, 1rem + 1.2vw, 1.58rem);
  letter-spacing: 0.13em;
  text-indent: 0.13em;      /* compensate trailing tracking, keep optically centered */
  color: var(--ink);
}
.mark__soon {
  margin-top: 1.15em;
  font-family: var(--mono);
  font-weight: 400;
  font-size: 0.66rem;
  letter-spacing: 0.34em;
  text-indent: 0.34em;
  color: var(--ghost);
  opacity: 0.78;
}
/* legal / ICP filing footer — quiet, pinned to the very bottom */
.legal {
  position: absolute;
  left: 0;
  right: 0;
  bottom: clamp(14px, 2.6vh, 24px);
  margin: 0;
  padding: 0 16px;
  text-align: center;
  font-family: "IBM Plex Mono", ui-monospace, "PingFang SC", "Microsoft YaHei", monospace;
  font-size: 0.72rem;
  letter-spacing: 0.03em;
  line-height: 1.6;
  color: var(--ghost);
  opacity: 1;
}
.legal p { margin: 0; }
.legal p + p { margin-top: 0.55em; }
.legal__filings {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  align-items: center;
  gap: 0.3em 1.5em;
}
.legal a {
  color: inherit;
  text-decoration: none;
  white-space: nowrap;
  display: inline-flex;
  align-items: center;
  transition: color 0.3s var(--ease);
}
.legal a:hover { color: var(--ink); }
.legal .gongan img {
  height: 15px;
  width: auto;
  margin-right: 5px;
}

/* ---- theme toggle: top-right, follows system until the visitor picks ---- */
.theme {
  position: fixed;
  z-index: 4;
  top: clamp(16px, 3vw, 30px);
  right: clamp(16px, 3vw, 30px);
  width: 38px;
  height: 38px;
  display: grid;
  place-items: center;
  padding: 0;
  border: 0;
  background: transparent;
  color: var(--ghost);
  cursor: pointer;
  opacity: 0.5;
  transition: opacity 0.4s var(--ease), color 0.4s var(--ease);
  -webkit-tap-highlight-color: transparent;
}
.theme:hover, .theme:focus-visible { opacity: 1; color: var(--ink); outline: none; }
.no-js .theme { display: none; }   /* needs JS to actually switch */
.theme__icon { width: 20px; height: 20px; display: block; }
.theme__sun, .theme__moon { display: none; }
.theme.is-light .theme__sun { display: block; }
.theme.is-dark  .theme__moon { display: block; }

/* ---- ambient-sound toggle: a visible speaker, bottom-right ------------ */
.sound {
  position: fixed;
  z-index: 3;
  right: clamp(18px, 3.2vw, 34px);
  bottom: clamp(18px, 3.2vw, 34px);
  width: 42px;
  height: 42px;
  display: grid;
  place-items: center;
  padding: 0;
  border: 0;
  border-radius: 50%;
  background: transparent;
  color: var(--ghost);
  cursor: pointer;
  -webkit-tap-highlight-color: transparent;
}
.sound[hidden] { display: none; }
.sound__icon {
  width: 23px;
  height: 23px;
  display: block;
  color: inherit;
  transition: color 0.45s var(--ease), filter 0.45s var(--ease);
}
.sound__w { opacity: 0.3; transition: opacity 0.45s var(--ease); }
.sound:hover, .sound:focus-visible { color: var(--ink); outline: none; }
.sound:hover .sound__w, .sound:focus-visible .sound__w { opacity: 0.65; }

/* muted: a slow attention breath so the control can actually be found */
.js .sound[aria-pressed="false"] { animation: sound-hint 2.6s ease-in-out infinite; }
@keyframes sound-hint { 0%, 100% { opacity: 0.5; } 50% { opacity: 0.92; } }

/* on: amber, waves lit, steady */
.sound[aria-pressed="true"] { animation: none; opacity: 1; color: var(--sun); }
.sound[aria-pressed="true"] .sound__icon { filter: drop-shadow(0 0 6px rgba(var(--sun-rgb), 0.55)); }
.sound[aria-pressed="true"] .sound__w { opacity: 1; }
.sound[aria-pressed="true"] .sound__w2 { animation: wave-flick 2.4s ease-in-out infinite; }
@keyframes wave-flick { 0%, 100% { opacity: 0.5; } 50% { opacity: 1; } }

/* =====================================================================
   Animated state (JS present, motion allowed): entrance choreography
   1.2s black -> sun fades in & breathes -> line types -> wordmark last
   ===================================================================== */
.js .sun        { animation: sun-in 0.9s var(--ease) 1.2s forwards; }
.js .sun__halo  { animation: halo-breathe 6s ease-in-out 2.1s infinite; }
.js .sun__core  { animation: core-breathe 6s ease-in-out 2.1s infinite; }
@keyframes sun-in { from { opacity: 0; } to { opacity: 1; } }
@keyframes halo-breathe {
  0%, 100% { transform: translate(-50%, -50%) scale(0.38); opacity: 0.16; }
  50%      { transform: translate(-50%, -50%) scale(2.15); opacity: 1; }
}
@keyframes core-breathe {
  0%, 100% { transform: translate(-50%, -50%) scale(0.78); opacity: 0.55; box-shadow: 0 0 10px 2px rgba(var(--sun-rgb), 0.45); }
  50%      { transform: translate(-50%, -50%) scale(1.45); opacity: 1;    box-shadow: 0 0 38px 8px rgba(var(--sun-rgb), 1); }
}

.js .infer { opacity: 1; }           /* JS drives the line; container stays visible */
/* wordmark + legal footer are present from the first paint (no late fade-in) */

/* =====================================================================
   Fallbacks — the page is never blank, never silent-ugly
   ===================================================================== */

/* No JS: static, fully legible. Sun + first line (ink) + wordmark, no caret. */
.no-js .sun        { opacity: 1; }
.no-js .infer      { opacity: 1; }
.no-js .infer__text{ color: var(--ink); }
.no-js .caret      { display: none; }
.no-js .mark       { opacity: 1; }
.no-js .legal      { opacity: 1; }

/* Reduced motion (also triggered by iOS Low Power Mode): stay alive but CALM —
   a gentle opacity-led breath instead of the big bloom, and drop the light beam.
   The text still streams (a small, non-vestibular change), so phones aren't dead. */
@media (prefers-reduced-motion: reduce) {
  .js .sun__halo { animation: halo-soft 7s ease-in-out 1.4s infinite; }
  .js .sun__core { animation: core-soft 7s ease-in-out 1.4s infinite; }
  .beam { display: none !important; }
  .sound[aria-pressed="true"] .sound__core { animation: none !important; }
}
@keyframes halo-soft {
  0%, 100% { transform: translate(-50%, -50%) scale(0.85); opacity: 0.4; }
  50%      { transform: translate(-50%, -50%) scale(1.25); opacity: 0.9; }
}
@keyframes core-soft {
  0%, 100% { opacity: 0.68; }
  50%      { opacity: 1; }
}

/* very short / landscape phones: tighten the zones so nothing collides */
/* short / landscape phones: tighten zones and give the two-line footer room */
@media (max-height: 560px) {
  .sun   { top: 17%; }
  .infer { top: 43%; }
  .mark  { bottom: 78px; }
  .mark__name { font-size: 1.18rem; }
  .legal { bottom: 8px; font-size: 0.58rem; }
}
/* very narrow phones: keep the footer comfortable */
@media (max-width: 380px) {
  .legal { font-size: 0.64rem; padding: 0 12px; }
}
