/* ------------------------------------------------------------------
   Palette: dark chocolate cake and cream frosting on cool slate, with
   copper as the one warm accent. Every colour is a token; the three
   :root blocks below cover light, system-dark and toggled-dark, so a
   colour changed in one place only applies in one theme.

   The log panel keeps a dark console ground in both themes, so its
   own accents (--log-pid, --log-logger) are defined once.
   ------------------------------------------------------------------ */

:root {
  --ground:    #E3E7EA;
  --surface:   #F7F8F9;
  --chrome:    #DDE2E6;
  --line:      #C8D0D5;
  --ink:       #1C2227;
  --muted:     #5B656C;
  --gutter:    #9AA5AC;

  --accent:    #B4551A;   /* copper */
  --accent-in: #FFF6EE;
  --flame:     #E8912B;

  --cake:      #4A2F22;   /* dark chocolate tiers */
  --cake-edge: #33201A;
  --frosting:  #EFE3D2;   /* cream, dripping over the sides */
  --wax:       #F2E8DA;   /* candle wax */
  --wax-edge:  #CBBBA6;

  --term-bg:   #191E22;
  --term-ink:  #DCE3E8;
  --term-dim:  #7B868E;
  --ok:        #2E9E6B;
  --warn:      #C07C1B;

  --log-pid:    #C084C8;  /* Spring paints the pid magenta … */
  --log-logger: #4FBEC4;  /* … and the logger name cyan */

  --syn-key:   #2F5FA8;
  --syn-str:   #2A6B4F;
  --syn-num:   #8A5A1E;
  --syn-fn:    #6B3FA0;
  --syn-com:   #7C868D;
  --syn-pun:   #4A545B;

  --shadow:    0 1px 2px rgba(28,34,39,.07), 0 14px 38px -16px rgba(28,34,39,.35);

  --step-0: 1rem;
  --step-1: 1.16rem;
  --step-2: 1.5rem;
  --step-3: clamp(2rem, 5.5vw, 3.1rem);
}

@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) {
    --ground:    #14181B;
    --surface:   #1D2226;
    --chrome:    #262C31;
    --line:      #333B41;
    --ink:       #E7ECEF;
    --muted:     #94A0A8;
    --gutter:    #6A757C;

    --accent:    #DE813A;
    --accent-in: #1F1108;
    --flame:     #F5AC4E;

    --cake:      #563526;
    --cake-edge: #3A2318;
    --frosting:  #EADCC9;
    --wax:       #EFE4D4;
    --wax-edge:  #A8977F;

    --term-bg:   #0E1114;
    --term-ink:  #E4EAEE;
    --term-dim:  #78848C;
    --ok:        #4FD495;
    --warn:      #E8AE4E;

    --syn-key:   #7FB0F0;
    --syn-str:   #6FD09A;
    --syn-num:   #E0A85A;
    --syn-fn:    #B995F0;
    --syn-com:   #78838B;
    --syn-pun:   #A9B4BB;

    --shadow:    0 1px 2px rgba(0,0,0,.5), 0 20px 44px -18px rgba(0,0,0,.75);
  }
}

:root[data-theme="dark"] {
  --ground:    #14181B;
  --surface:   #1D2226;
  --chrome:    #262C31;
  --line:      #333B41;
  --ink:       #E7ECEF;
  --muted:     #94A0A8;
  --gutter:    #6A757C;

  --accent:    #DE813A;
  --accent-in: #1F1108;
  --flame:     #F5AC4E;

  --cake:      #563526;
  --cake-edge: #3A2318;
  --frosting:  #EADCC9;
  --wax:       #EFE4D4;
  --wax-edge:  #A8977F;

  --term-bg:   #0E1114;
  --term-ink:  #E4EAEE;
  --term-dim:  #78848C;
  --ok:        #4FD495;
  --warn:      #E8AE4E;

  --syn-key:   #7FB0F0;
  --syn-str:   #6FD09A;
  --syn-num:   #E0A85A;
  --syn-fn:    #B995F0;
  --syn-com:   #78838B;
  --syn-pun:   #A9B4BB;

  --shadow:    0 1px 2px rgba(0,0,0,.5), 0 20px 44px -18px rgba(0,0,0,.75);
}

* { box-sizing: border-box; }

body {
  margin: 0;
  min-height: 100vh;
  background: var(--ground);
  color: var(--ink);
  font-family: "Familjen Grotesk", "Segoe UI", system-ui, sans-serif;
  font-size: var(--step-0);
  line-height: 1.55;
  -webkit-font-smoothing: antialiased;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: clamp(1rem, 3vw, 2.5rem);
}

/* ---- the card ---- */
.card {
  width: 100%;
  max-width: 78rem;
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: 4px;
  box-shadow: var(--shadow);
  overflow: hidden;
}
.rail {
  height: 7px;
  background-image: repeating-linear-gradient(115deg,
    var(--accent) 0 9px, var(--wax) 9px 18px);
  background-size: 40px 100%;
}
.rail.running { animation: march 700ms linear infinite; }
@keyframes march { to { background-position: 40px 0; } }

.bar {
  display: flex;
  align-items: center;
  gap: 1rem;
  padding: .6rem .85rem;
  background: var(--chrome);
  border-bottom: 1px solid var(--line);
  font-family: "JetBrains Mono", ui-monospace, monospace;
  font-size: .8rem;
  color: var(--muted);
}
.bar .file { color: var(--ink); font-weight: 500; }
.bar .spacer { flex: 1; }

button {
  font: inherit;
  cursor: pointer;
  border-radius: 3px;
  transition: transform 120ms ease, background 160ms ease, color 160ms ease;
}
button:focus-visible, [tabindex]:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
}
.run {
  font-family: "JetBrains Mono", ui-monospace, monospace;
  font-size: .78rem;
  font-weight: 700;
  letter-spacing: .06em;
  text-transform: uppercase;
  padding: .45rem .95rem;
  border: 1px solid var(--accent);
  background: var(--accent);
  color: var(--accent-in);
}
.run:hover:not(:disabled) { transform: translateY(-1px); }
.run:disabled { cursor: progress; opacity: .55; }

.panes {
  display: grid;
  grid-template-columns: minmax(0, 1.12fr) minmax(0, 1fr);
}
@media (max-width: 64rem) {
  .panes { grid-template-columns: minmax(0, 1fr); }
}

/* ---- source pane ---- */
.source {
  margin: 0;
  padding: 1.25rem 1rem 1.5rem;
  border-right: 1px solid var(--line);
  overflow-x: auto;
  font-family: "JetBrains Mono", ui-monospace, monospace;
  font-size: .82rem;
  line-height: 1.85;
  tab-size: 2;
}
@media (max-width: 64rem) {
  .source { border-right: 0; border-bottom: 1px solid var(--line); }
}
.source code { counter-reset: ln; display: block; }
.ln { display: block; white-space: pre; }
.ln::before {
  content: counter(ln);
  counter-increment: ln;
  display: inline-block;
  width: 2ch;
  margin-right: 1.4rem;
  text-align: right;
  color: var(--gutter);
  font-variant-numeric: tabular-nums;
  user-select: none;
}
.k { color: var(--syn-key); }
.t { color: var(--syn-fn); }
.s { color: var(--syn-str); }
.n { color: var(--syn-num); }
.f { color: var(--syn-fn); }
.c { color: var(--syn-com); font-style: italic; }
.p { color: var(--syn-pun); }

/* ---- stage pane ---- */
.stage {
  display: flex;
  flex-direction: column;
  justify-content: center;
  gap: 1rem;
  padding: 1.25rem 1.25rem 1.4rem;
  min-width: 0;
}
/* Two of these: the birthday line above the cake and the sign-off below it.
   The min-height reserves the sign-off's space from the start, so it does not
   shove the cake down when it arrives — and keeping it to one line's worth
   pulls the cake up toward the top of the pane. */
.greeting {
  margin: 0;
  min-height: 1.2em;
  /* flex drops whitespace-only text nodes between items, so the space
     between words has to come from gap, not from markup */
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0 .3em;
  font-family: "Fraunces", Georgia, serif;
  font-variation-settings: "SOFT" 100, "WONK" 1, "opsz" 100;
  font-weight: 700;
  font-size: var(--step-3);
  line-height: 1.06;
  letter-spacing: -.02em;
}
/* sits a little further off the cake than the flex gap alone would give it */
.greeting.sign-off { margin-top: .8rem; }
.greeting .idle {
  font-family: "JetBrains Mono", ui-monospace, monospace;
  font-size: .8rem;
  font-weight: 400;
  letter-spacing: 0;
  color: var(--muted);
}
.greeting .shout { color: var(--accent); }
.greeting .word {
  display: inline-block;
  opacity: 0;
  transform: translateY(.35em) rotate(-2deg);
  animation: rise 520ms cubic-bezier(.2,.9,.2,1) forwards;
}
@keyframes rise { to { opacity: 1; transform: none; } }

/* ---- cake ---- */
.cake {
  display: block;
  width: 100%;
  height: auto;
  max-width: 25rem;
  margin: 0 auto;
  touch-action: none;
}
/* aiming matters, so say so with the cursor */
.armed .cake { cursor: crosshair; }

/* Each flame's transform-origin is set in user units by card.js, at the
   wick. Do NOT use keywords here: transform-box defaults to view-box for
   SVG, so `center bottom` would pivot around the bottom of the whole
   drawing and throw the flames down onto the cake. */
.flame { transition: opacity 200ms ease, transform 140ms ease; }
.flame.flicker { animation: flick 220ms steps(2, end) infinite alternate; }
@keyframes flick {
  from { transform: scale(1) translateY(0); }
  to   { transform: scale(1.06) translateY(-.5px); }
}
/* the lean itself is written per flame by card.js, scaled by how close the
   breath is — a flame out of reach keeps flickering and stays upright */

/* ---- the puff of air ---- */
.cake .hit { pointer-events: all; }

.breath { pointer-events: none; opacity: 0; transition: opacity 160ms ease; }
.breath.on { opacity: 1; }
.breath circle { fill: none; stroke: var(--muted); }
.breath .reach {
  stroke-width: 1;
  stroke-dasharray: 3 5;
  opacity: .35;
}
/* The wisps are the moving air the breath is made of. They carry no CSS
   animation on purpose: drawBreath() in card.js writes their transform and
   opacity per frame, using the SVG transform attribute rather than the CSS
   property, which keeps them clear of the transform-box behaviour that
   already broke the flames once. */
.breath .wisp {
  fill: none;
  stroke: var(--muted);
  stroke-width: 1.6;
  stroke-linecap: round;
}

.hint {
  margin: 0;
  font-family: "JetBrains Mono", ui-monospace, monospace;
  font-size: .74rem;
  color: var(--muted);
  min-height: 1.4em;
}

/* ---- the log, across the full width ---- */
.console {
  border-top: 1px solid var(--line);
  padding: .85rem 1.1rem 1.1rem;
  display: flex;
  flex-direction: column;
  gap: .5rem;
}
.log-head {
  display: flex;
  align-items: center;
  gap: .55rem;
  font-family: "JetBrains Mono", ui-monospace, monospace;
  font-size: .68rem;
  letter-spacing: .16em;
  text-transform: uppercase;
  color: var(--muted);
}
.log-head .pip {
  width: 6px;
  height: 6px;
  border-radius: 50%;
  background: var(--gutter);
  transition: background 200ms ease;
}
.log-head.live .pip { background: var(--ok); }
.log-head .spacer { flex: 1; }

.log {
  background: var(--term-bg);
  color: var(--term-ink);
  border-radius: 3px;
  padding: .8rem .9rem;
  font-family: "JetBrains Mono", ui-monospace, monospace;
  font-size: .74rem;
  line-height: 1.75;
  height: 11.5rem;
  overflow: auto;
}
/* one line per record, padded into columns like a real appender —
   long lines scroll sideways rather than wrapping */
.log .row { white-space: pre; }
.log .ts     { color: var(--term-dim); font-variant-numeric: tabular-nums; }
.log .lvl    { font-weight: 700; }
.log .lvl.info  { color: var(--ok); }
.log .lvl.debug { color: var(--term-ink); }
.log .lvl.warn  { color: var(--warn); }
.log .pid    { color: var(--log-pid); }
.log .sep    { color: var(--term-dim); }
.log .thr    { color: var(--term-dim); }
.log .logger { color: var(--log-logger); }
.log .msg.hi { color: var(--accent); font-weight: 700; }
.log .idle   { color: var(--term-dim); }
.log .tail   { color: var(--accent); animation: blink 1s steps(1) infinite; }
@keyframes blink { 50% { opacity: 0; } }

#confetti {
  position: fixed;
  inset: 0;
  width: 100%;
  height: 100%;
  pointer-events: none;
  z-index: 50;
}

@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: .001ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: .001ms !important;
  }
}
