/* ---------------------------------------------------------------- tokens -- */

:root {
  --bg: #0E0F11;
  --text: #D6DCE3;
  --dim: #6E7681;
  --line: #24282E;
  --accent: #E24A55;

  /* Not themed: the portrait art and the party are tuned for a dark ground. */
  --party-ink: #F7F6F3;
  --party-scrim: #0E0F11;
  --party-accent: #E24A55;

  /* Wordle scoring. White ink on all three in both themes. */
  --tile-hit: #2F8F53;
  --tile-near: #A8801F;
  --tile-miss: #3A3F47;
  --tile-ink: #FFFFFF;

  --gutter: 26px;
  --mono: 'JetBrains Mono', ui-monospace, SFMono-Regular, Menlo, monospace;
}

:root[data-theme='light'] {
  --bg: #F7F6F3;
  --text: #17181A;
  --dim: #6B6F76;
  --line: #E0DDD5;
  --accent: #9B1B30;

  --tile-hit: #27794A;
  --tile-near: #8A6412;
  --tile-miss: #6E6A5E;
}

* { box-sizing: border-box; }

html, body {
  margin: 0;
  height: 100%;
}

body {
  overflow: hidden;
  background: var(--bg);
  color: var(--text);
  font-family: var(--mono);
  font-size: 15px;
}

body.is-dancing,
body.is-destroyed { overflow: hidden; }

a {
  color: var(--accent);
  text-decoration: underline;
  text-underline-offset: 3px;
  cursor: pointer;
}

a:hover { text-decoration-thickness: 2px; }

/* The browser default washes out dim and accent text alike, and the transcript
   is meant to be copied. Reverse video is both what a terminal does and the
   highest-contrast option available (~14:1 in either theme). */
::selection {
  background: var(--text);
  color: var(--bg);
}

/* The portrait keeps its own colours in both themes, and reverse video would
   turn the face into a negative — so it highlights on a lifted dark ground
   instead, staying light-on-dark the way the art is tuned for. */
.line--art::selection {
  background: #3A3F47;
  color: #FFFFFF;
}

/* --------------------------------------------------------------- layout -- */

.console {
  /* Pinned to the viewport, not merely at least as tall as it: the transcript
     between the title bar and the hint bar has to be the thing that scrolls,
     otherwise the whole document scrolls and the prompt walks off the bottom. */
  height: 100vh;
  height: 100dvh;
  display: flex;
  flex-direction: column;
  cursor: text;
}

.titlebar {
  flex: none;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 16px;
  padding: 15px var(--gutter);
  border-bottom: 1px solid var(--line);
  font-size: 12px;
  letter-spacing: 0.1em;
  color: var(--dim);
}

.titlebar__id {
  display: flex;
  align-items: center;
  gap: 10px;
  min-width: 0;
}

.titlebar__label {
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.titlebar__dot {
  flex: none;
  width: 7px;
  height: 7px;
  border-radius: 50%;
  background: var(--accent);
}

.titlebar__theme {
  flex: none;
  font-family: inherit;
  font-size: 11px;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  padding: 6px 12px;
  border: 1px solid var(--line);
  border-radius: 2px;
  background: transparent;
  color: var(--dim);
  cursor: pointer;
}

.titlebar__theme:hover {
  color: #E24A55;
  border-color: #E24A55;
}

.transcript {
  flex: 1;
  overflow-y: auto;
  overflow-x: auto;
  padding: var(--gutter) var(--gutter) 10px;
  line-height: 1.75;
}

.transcript__body { max-width: 960px; }

/* ---------------------------------------------------------------- lines -- */

.line {
  white-space: pre;
  font-size: 15px;
  line-height: 1.75;
}

/* Blank lines carry the vertical rhythm. Not &nbsp; — that breaks selection. */
.line--blank { min-height: 15px; }

.line--dim,
.line--in { color: var(--dim); }

.line--accent { color: var(--accent); }

/* The portrait is tuned for light-on-dark and inverts illegibly on a light
   background, so it keeps its own colours in both themes. */
.line--art {
  font-size: 8px;
  line-height: 1.15;
  color: #D6DCE3;
  background: #0E0F11;
  padding: 16px 14px;
  width: fit-content;
}

/* The toys are printed output, not framed art: unlike the portrait they are
   plain characters that read correctly either way up, so they take the page's
   own colours in both themes. Only the metrics change — tighter leading, so a
   donut looks round rather than stretched. */
.line--toy {
  white-space: pre;
  font-size: 12px;
  line-height: 1.1;
  padding: 10px 0;
  width: fit-content;
}

/* ---------------------------------------------------------------- wordle -- */

.line--tiles {
  display: flex;
  gap: 5px;
  margin: 3px 0;
}

.tile {
  width: 1.9em;
  padding: 5px 0;
  border-radius: 2px;
  font-weight: 700;
  text-align: center;
  color: var(--tile-ink);
}

.tile--hit { background: var(--tile-hit); }
.tile--near { background: var(--tile-near); }
.tile--miss { background: var(--tile-miss); }

/* --------------------------------------------------------------- prompt -- */

.prompt {
  position: relative;
  display: flex;
  align-items: center;
  gap: 10px;
  padding-top: 2px;
}

.prompt__label {
  flex: none;
  white-space: pre;
  color: var(--dim);
}

.prompt__field {
  display: flex;
  align-items: center;
  min-width: 0;
}

.prompt__typed {
  white-space: pre;
  color: var(--text);
}

.prompt__caret {
  flex: none;
  display: inline-block;
  white-space: pre;
  background: var(--accent);
  color: var(--bg);
}

/* Only blinks when it holds a bare space — a blinking block would flash the
   suggested character in and out. */
.prompt__caret.is-blinking { animation: blink 1.1s step-end infinite; }

.prompt__ghost {
  white-space: pre;
  color: var(--dim);
}

.prompt__input {
  position: absolute;
  left: 0;
  top: 0;
  width: 100%;
  height: 100%;
  padding: 0;
  border: none;
  outline: none;
  background: transparent;
  opacity: 0;
  font-family: var(--mono);
  font-size: 15px;
  color: transparent;
}

/* ----------------------------------------------------------------- hints -- */

.hints {
  flex: none;
  display: flex;
  flex-wrap: wrap;
  gap: 20px;
  padding: 11px var(--gutter) 15px;
  border-top: 1px solid var(--line);
  font-size: 11px;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--dim);
}

.hints__accent { color: var(--accent); }

/* Transparent to the hint bar's flex layout, so chips keep its gap and scroll. */
.hints__chips { display: contents; }

/* Command chips are the touch fallback for the hidden input. */
.chip {
  display: none;
  font-family: var(--mono);
  font-size: 11px;
  letter-spacing: 0.06em;
  padding: 7px 10px;
  border: 1px solid var(--line);
  border-radius: 2px;
  background: transparent;
  color: var(--accent);
  cursor: pointer;
  white-space: nowrap;
}

/* A game's own controls are worth showing on a desktop too — the arrows are the
   clearest possible statement of what the keyboard now does. */
.chip--mode {
  display: block;
  min-width: 48px;
  font-size: 14px;
  text-align: center;
}

@media (max-width: 700px) {
  :root { --gutter: 16px; }

  .hints {
    flex-wrap: nowrap;
    gap: 8px;
    overflow-x: auto;
    scrollbar-width: none;
  }

  .hints::-webkit-scrollbar { display: none; }
  .hints__desktop { display: none; }
  .chip { display: block; }

  /* The Ruby version is a nicety; the hostname is the part worth the width. */
  .titlebar__irb { display: none; }
}

/* --------------------------------------------------- no-script fallback -- */

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

/* ---------------------------------------------------------------- party -- */

.party {
  position: fixed;
  inset: 0;
  z-index: 99;
  overflow: hidden;
  cursor: pointer;
}

.party__scrim {
  position: absolute;
  inset: 0;
  background: var(--party-scrim);
  opacity: 0.9;
}

.party__lights {
  position: absolute;
  inset: 0;
}

/* One strobing wash per light, each with its own colour, position and tempo —
   a single red glow reads as an error state, not a rave. */
.party__light {
  position: absolute;
  inset: 0;
  mix-blend-mode: screen;
  opacity: 0;
  background: radial-gradient(circle at var(--x) var(--y), var(--color), transparent 45%);
  animation: rave var(--dur) steps(2, end) var(--delay) infinite;
}

.party__floor {
  position: absolute;
  inset: 0;
}

.party__dancer {
  position: absolute;
  pointer-events: none;
  animation: popIn 0.42s cubic-bezier(.2, 1.5, .4, 1) both;
}

.party__sway {
  animation-timing-function: ease-in-out;
  animation-iteration-count: infinite;
  transform-origin: 50% 90%;
}

.party__ascii {
  margin: 0;
  font-family: var(--mono);
  font-weight: 700;
  line-height: 1.05;
  white-space: pre;
  color: var(--party-ink);
  text-shadow: 0 0 14px var(--glow), 0 0 30px var(--glow);
}

.party__image {
  width: 100%;
  background-size: contain;
  background-position: center bottom;
  background-repeat: no-repeat;
  filter: drop-shadow(0 8px 18px rgba(0, 0, 0, 0.45));
}

.party__caption {
  position: absolute;
  left: 50%;
  bottom: 26px;
  transform: translateX(-50%);
  font-size: 11px;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--party-ink);
  background: rgba(14, 15, 17, 0.82);
  padding: 9px 16px;
  border: 1px solid var(--party-accent);
  text-align: center;
}

/* ------------------------------------------------------------- confetti -- */

/* Above the page, below every overlay, and deaf to the mouse — it celebrates
   without ever getting between the reader and the prompt. */
.confetti {
  position: fixed;
  inset: 0;
  z-index: 98;
  pointer-events: none;
}

/* --------------------------------------------------------------- matrix -- */

.matrix {
  position: fixed;
  inset: 0;
  z-index: 99;
  overflow: hidden;
  background: #000000;
  cursor: pointer;
}

.matrix canvas { display: block; }

/* ------------------------------------------------------- andy.destroy! -- */

/* The one overlay on this site with no way out. Escape is swallowed on purpose;
   only a reload brings the session back. */
.crt {
  position: fixed;
  inset: 0;
  z-index: 999;
  display: flex;
  align-items: center;
  justify-content: center;
  background: #000000;
  cursor: default;
}

.crt__screen { transform-origin: 50% 50%; }

.crt__face {
  margin: 0;
  font-family: var(--mono);
  font-weight: 700;
  font-size: 15px;
  line-height: 1.05;
  white-space: pre;
  color: #E24A55;
  text-shadow: 0 0 26px rgba(226, 74, 85, 0.5);
  animation: fury 0.72s ease-in-out infinite;
}

.crt__screen.is-collapsing { animation: crtSquash 0.36s cubic-bezier(.6, 0, .9, .4) forwards; }

/* The bright line a cathode-ray tube leaves behind on power-off. */
.crt__beam {
  position: absolute;
  left: 50%;
  top: 50%;
  width: 68vw;
  height: 3px;
  transform: translate(-50%, -50%);
  background: #FFFFFF;
  box-shadow: 0 0 26px 7px rgba(255, 255, 255, 0.7);
  opacity: 0;
}

.crt__beam.is-firing { animation: crtBeam 0.46s ease-in forwards; }

.crt__hint {
  position: absolute;
  left: 50%;
  bottom: 34px;
  transform: translateX(-50%);
  font-size: 11px;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: #7C848E;
  opacity: 0;
  transition: opacity 1.4s ease;
}

.crt__hint.is-visible { opacity: 0.7; }

/* ------------------------------------------------------------ keyframes -- */

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

@keyframes popIn {
  from { transform: scale(0.2) rotate(-12deg); opacity: 0; }
  70% { transform: scale(1.12) rotate(4deg); opacity: 1; }
  to { transform: scale(1) rotate(0deg); opacity: 1; }
}

@keyframes sway {
  0%, 100% { transform: translateY(0) rotate(-4deg); }
  50% { transform: translateY(-14px) rotate(4deg); }
}

@keyframes sway-alt {
  0%, 100% { transform: translateY(-4px) rotate(6deg) scale(0.96); }
  50% { transform: translateY(-18px) rotate(-6deg) scale(1.04); }
}

@keyframes rave {
  0%, 100% { opacity: 0; }
  50% { opacity: var(--peak, 0.3); }
}

@keyframes fury {
  0%, 100% { transform: translateX(0); }
  25% { transform: translateX(-2px); }
  75% { transform: translateX(2px); }
}

@keyframes crtSquash {
  0% { transform: scale(1, 1); filter: brightness(1); }
  60% { transform: scale(1.02, 0.03); filter: brightness(4); }
  100% { transform: scale(1.05, 0.004); filter: brightness(9); }
}

@keyframes crtBeam {
  0% { opacity: 1; width: 68vw; }
  55% { opacity: 1; width: 14px; }
  100% { opacity: 0; width: 3px; }
}

@media (prefers-reduced-motion: reduce) {
  .prompt__caret.is-blinking,
  .party__dancer,
  .party__sway,
  .party__light,
  .crt__face {
    animation: none !important;
  }

  .party__light { opacity: calc(var(--peak, 0.3) * 0.6); }
}
