/**
 * Acugotchi — the phone stylesheet.
 *
 * Written for a 360x640 phone held in one hand, and only then for a desktop
 * window. Three rules drive nearly every declaration below:
 *
 *   1. The page never scrolls. `#app` is a fixed column of exactly the
 *      *visual* viewport's height (`--app-h`, set by app.ts from
 *      `visualViewport`), and the transcript is the one element with
 *      `overflow`. A page that scrolls is a page where the keyboard can push
 *      the pet off screen.
 *   2. The pet is the only thing that moves. There is no transition on any
 *      layout property, and the only animations on the page are the fade a
 *      reaction chip makes on its way out and the 260ms a vitals bar takes to
 *      slide to a value it already holds. Both are feedback about something
 *      that just happened, never idle decoration competing with the creature.
 *   3. No `:hover`, anywhere. On a touch screen a hover rule is a state the
 *      finger gets stuck in. Feedback is `:active` and `:focus-visible`.
 *
 * Colours are a dark, low-chroma set so the pet's own palette is the brightest
 * thing on the screen.
 */

/* ------------------------------------------------------------- foundation -- */

:root {
  color-scheme: dark;

  --bg: #0b0a10;
  --panel: #14121d;
  --panel-2: #1b1828;
  --line: #2a2438;
  --ink: #e8e3f4;
  --dim: #8b83a4;
  --faint: #5d5674;
  --accent: #c3aaff;
  --warm: #ffcb8a;
  --bad: #ff9090;
  --good: #8fe0a8;
  --rose: #ffa8d2;

  --safe-t: env(safe-area-inset-top, 0px);
  --safe-b: env(safe-area-inset-bottom, 0px);
  --safe-l: env(safe-area-inset-left, 0px);
  --safe-r: env(safe-area-inset-right, 0px);

  /* Overwritten every visualViewport resize. 100dvh is the pre-JS fallback and
     100vh is deliberately never used: it is the viewport with the browser
     chrome pretended away, which is not a box anything fits inside. */
  --app-h: 100dvh;
  --app-top: 0px;

  --tap: 44px;
  --gap: 8px;
  --radius: 10px;
}

* { box-sizing: border-box; }

[hidden] { display: none !important; }

html, body {
  height: 100%;
  margin: 0;
  padding: 0;
  background: var(--bg);
  color: var(--ink);
  /* No rubber-banding: the document itself has nothing to scroll. */
  overflow: hidden;
  overscroll-behavior: none;
}

body {
  font: 13px/1.45 ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, "Liberation Mono", monospace;
  -webkit-text-size-adjust: 100%;
  text-size-adjust: 100%;
  -webkit-tap-highlight-color: transparent;
}

::selection { background: var(--accent); color: #12101a; }

/* ----------------------------------------------------------------- shell -- */

.app {
  position: fixed;
  top: 0;
  left: 0;
  right: 0;
  height: var(--app-h);
  /* iOS scrolls the *layout* viewport out from under a focused input; this
     puts the column back where the visual viewport actually is. */
  transform: translateY(var(--app-top));
  display: flex;
  flex-direction: column;
  overflow: hidden;
  /* Desktop is the afterthought: it gets the phone column, centred. */
  max-width: 460px;
  margin: 0 auto;
}

.screen {
  flex: 1 1 auto;
  min-height: 0;
  display: flex;
  flex-direction: column;
  padding-left: max(12px, var(--safe-l));
  padding-right: max(12px, var(--safe-r));
  padding-top: var(--safe-t);
}

.screen--centre { justify-content: center; }

/* ------------------------------------------------------------- the stage -- */

.slot {
  flex: 1 1 42%;
  min-height: 92px;
  display: flex;
  align-items: center;
  justify-content: center;
}

/* Keyboard up: the pet gives up height but never leaves. */
.app[data-kb="1"] .slot { flex-basis: 24%; min-height: 64px; }

.stage {
  position: relative;
  width: 100%;
  height: 100%;
  display: flex;
  align-items: center;
  justify-content: center;
  /* The one gesture surface. `none` so a drag turns the pet's head instead of
     fighting the transcript for the scroll. */
  touch-action: none;
  -webkit-user-select: none;
  user-select: none;
  cursor: default;
}

.pet {
  display: block;
  width: 100%;
  height: 100%;
  /* Crisp, not smooth. The renderer blits an integer scale of a 240px stage. */
  image-rendering: pixelated;
  image-rendering: crisp-edges;
}

.stage[data-dormant="1"] .pet { opacity: 0.5; filter: grayscale(0.7); }
.stage[data-dormant="2"] .pet { opacity: 0.32; filter: grayscale(1); }

.emote {
  position: absolute;
  top: 8%;
  left: 50%;
  transform: translateX(-50%);
  padding: 3px 9px;
  border: 1px solid var(--line);
  border-radius: 999px;
  background: var(--panel-2);
  color: var(--accent);
  font-size: 12px;
  letter-spacing: 0.08em;
  pointer-events: none;
  opacity: 0;
}

/* The single exception to "only the pet moves": a reaction chip fading out. */
.emote[data-on="1"] { opacity: 1; animation: emote-out 900ms ease-out forwards; }

@keyframes emote-out {
  0%   { opacity: 1; }
  60%  { opacity: 1; }
  100% { opacity: 0; }
}

/* ---------------------------------------------------------------- header -- */

.bar {
  flex: 0 0 auto;
  display: flex;
  align-items: center;
  gap: var(--gap);
  padding: 6px 0 2px;
  min-height: 34px;
}

.ident {
  flex: 1 1 auto;
  min-width: 0;
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 1px;
  padding: 2px 0;
  border: 0;
  background: none;
  color: inherit;
  font: inherit;
  text-align: left;
}

.ident__name {
  font-size: 15px;
  letter-spacing: 0.04em;
  color: var(--ink);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  max-width: 100%;
}

.ident__meta {
  font-size: 10px;
  letter-spacing: 0.06em;
  color: var(--dim);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  max-width: 100%;
}

.ident__meta[data-late="1"] { color: var(--warm); }

.sep { padding: 0 4px; color: var(--faint); }

/* A dot, not a spinner. Status should not compete with the pet for motion. */
.link {
  flex: 0 0 auto;
  width: 7px;
  height: 7px;
  border-radius: 50%;
  background: var(--faint);
}

.link[data-link="live"] { background: var(--good); }
.link[data-link="poll"] { background: var(--warm); }
.link[data-link="down"] { background: var(--bad); }

.chip {
  flex: 0 0 auto;
  min-height: 30px;
  padding: 0 10px;
  border: 1px solid var(--line);
  border-radius: 999px;
  background: var(--panel);
  color: var(--dim);
  font: inherit;
  font-size: 10px;
  letter-spacing: 0.1em;
  text-transform: uppercase;
}

.chip:active { background: var(--panel-2); color: var(--ink); }

/* ---------------------------------------------------------------- vitals -- */

/*
 * A strip, not a dashboard: four bars in one row directly under the pet, so
 * they read as the creature's own condition rather than as a panel about it.
 * Roughly 30px tall, `flex: 0 0 auto`, and nothing here is positioned or sized
 * against the viewport, so `--app-h` keeps working exactly as before.
 */

.stats {
  flex: 0 0 auto;
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  gap: 6px;
  padding: 2px 0 5px;
}

/* The keyboard takes the actions with it; the bars go too. Nothing on this row
   can be acted on while typing, and the pet needs the pixels more. */
.app[data-kb="1"] .stats { display: none; }

.stat {
  display: flex;
  flex-direction: column;
  gap: 3px;
  min-width: 0;
  /* One colour per bar, inherited so `[data-low]` can still override it. */
  --bar: var(--accent);
}

#stat-happy { --bar: var(--rose); }
#stat-tummy { --bar: var(--good); }
#stat-energy { --bar: var(--warm); }

.stat__top {
  display: flex;
  align-items: center;
  gap: 4px;
  min-width: 0;
  font-size: 9px;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--faint);
  white-space: nowrap;
  overflow: hidden;
}

.stat__glyph { font-size: 10px; line-height: 1; }

.stat__bar {
  position: relative;
  display: block;
  height: 11px;
  border: 1px solid var(--line);
  border-radius: 2px;
  background: var(--panel);
  overflow: hidden;
}

.stat__fill {
  position: absolute;
  top: 0;
  bottom: 0;
  left: 0;
  width: 0;
  background: var(--bar);
  /* The value is already right when this starts — app.ts writes the width in
     the same turn as the patch — so this animates pixels, never the number. */
  transition: width 260ms cubic-bezier(0.2, 0.9, 0.2, 1), background-color 200ms linear;
}

/* A quarter left. The bar stops being information and starts being a request —
   and it outlines itself, so an empty bar reads as urgent rather than absent. */
.stat[data-low="1"] .stat__fill { background: var(--bad); }
.stat[data-low="1"] .stat__bar { border-color: #5a2f3c; }

/* Chunky, not smooth: the gutters are painted *over* the fill, which turns one
   plain div into eight pixel cells for the price of one empty element. */
.stat__cells {
  position: absolute;
  inset: 0;
  background-image: repeating-linear-gradient(
    90deg,
    transparent 0 calc(12.5% - 2px),
    var(--panel) calc(12.5% - 2px) 12.5%
  );
}

/* Whichever bar an action just moved says so for 700ms, so feeding is visibly
   the thing that filled the tummy and shaking is visibly what cost the mood. */
.stat[data-bump] .stat__top { color: var(--ink); }
.stat[data-bump] .stat__bar { border-color: var(--bar); }
.stat[data-bump] .stat__fill { animation: stat-flash 700ms ease-out; }

.stat[data-bump="down"] .stat__top { color: var(--bad); }
.stat[data-bump="down"] .stat__bar { border-color: var(--bad); }

@keyframes stat-flash {
  0%, 30% { filter: brightness(1.8); }
  100%    { filter: none; }
}

/* ------------------------------------------------------------ transcript -- */

.log {
  flex: 1 1 58%;
  min-height: 0;
  overflow-y: auto;
  overscroll-behavior: contain;
  -webkit-overflow-scrolling: touch;
  display: flex;
  flex-direction: column;
  gap: 5px;
  padding: 6px 0 8px;
}

.msg {
  max-width: 82%;
  padding: 7px 10px;
  border-radius: var(--radius);
  border: 1px solid var(--line);
  word-break: break-word;
  white-space: pre-wrap;
}

.msg--pet {
  align-self: flex-start;
  background: var(--panel);
  border-bottom-left-radius: 3px;
}

.msg--you {
  align-self: flex-end;
  background: var(--panel-2);
  color: var(--dim);
  border-bottom-right-radius: 3px;
}

.msg--system {
  align-self: center;
  max-width: 100%;
  border: 0;
  background: none;
  color: var(--faint);
  font-size: 10px;
  letter-spacing: 0.06em;
  text-align: center;
  padding: 2px 0;
}

/* -------------------------------------------------------------- composer -- */

.composer {
  flex: 0 0 auto;
  display: flex;
  gap: var(--gap);
  padding: 4px 0 6px;
}

.field {
  min-height: var(--tap);
  padding: 0 12px;
  border: 1px solid var(--line);
  border-radius: var(--radius);
  background: var(--panel);
  color: var(--ink);
  font: inherit;
  /* 16px exactly: anything smaller and iOS zooms the page on focus, which
     breaks every viewport calculation on this page. */
  font-size: 16px;
}

.field::placeholder { color: var(--faint); }
.field:focus { outline: 2px solid var(--accent); outline-offset: -2px; }

.field--chat { flex: 1 1 auto; min-width: 0; }

/* ------------------------------------------------------------- the thumb -- */

.actions {
  flex: 0 0 auto;
  display: grid;
  grid-template-columns: repeat(6, 1fr);
  gap: 5px;
  padding-bottom: max(10px, var(--safe-b));
}

/* The keyboard covers this row entirely, so it stands down rather than
   pretending to be reachable. */
.app[data-kb="1"] .actions { display: none; }

.act {
  min-height: 48px;
  padding: 0 1px;
  border: 1px solid var(--line);
  border-radius: var(--radius);
  background: var(--panel);
  color: var(--dim);
  font: inherit;
  font-size: 9px;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 3px;
}

.act__glyph { font-size: 15px; line-height: 1; color: var(--ink); }
.act:active { background: var(--panel-2); border-color: var(--accent); }
.act[disabled] { opacity: 0.45; }

/* --------------------------------------------------------------- buttons -- */

.btn {
  min-height: var(--tap);
  padding: 0 16px;
  border: 1px solid var(--line);
  border-radius: var(--radius);
  background: var(--panel);
  color: var(--ink);
  font: inherit;
  font-size: 13px;
  letter-spacing: 0.06em;
  text-align: center;
  text-decoration: none;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  touch-action: manipulation;
}

.btn:active { background: var(--panel-2); }
.btn[disabled] { opacity: 0.5; }

.btn--primary {
  background: var(--accent);
  border-color: var(--accent);
  color: #120f1c;
}

.btn--primary:active { background: #a892e6; }

.btn--send { flex: 0 0 auto; min-width: 58px; padding: 0 12px; font-size: 11px; text-transform: uppercase; }

:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }

/* ----------------------------------------------------------------- plate -- */

.plate {
  flex: 0 0 auto;
  display: flex;
  flex-direction: column;
  gap: 10px;
  padding: 10px 0 max(14px, var(--safe-b));
}

.plate--centre { flex: 1 1 auto; justify-content: center; }

.sigil { margin: 0; font-size: 22px; color: var(--faint); text-align: center; letter-spacing: 0.3em; }

.title {
  margin: 0;
  font-size: 17px;
  font-weight: 400;
  letter-spacing: 0.06em;
  color: var(--ink);
}

.whisper { margin: 0; font-size: 11.5px; line-height: 1.6; color: var(--dim); }
.whisper--bar { padding: 0 0 4px; }

.fineprint { margin: 0; font-size: 10px; line-height: 1.6; color: var(--faint); }

.label {
  font-size: 9px;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--faint);
}

.note { margin: 0; font-size: 11px; }
.note--bad { color: var(--bad); }

.quote {
  margin: 0;
  padding: 8px 10px;
  border-left: 2px solid var(--line);
  color: var(--dim);
  font-size: 12px;
  white-space: pre-wrap;
  word-break: break-word;
}

.form { display: flex; flex-direction: column; gap: 8px; }
.choices { display: flex; flex-direction: column; gap: 8px; }

.soulbox {
  display: flex;
  flex-direction: column;
  gap: 8px;
  padding: 10px;
  border: 1px solid var(--line);
  border-radius: var(--radius);
  background: var(--panel);
}

.cid {
  display: block;
  font-size: 11px;
  color: var(--accent);
  word-break: break-all;
  user-select: all;
  -webkit-user-select: all;
}

/* ----------------------------------------------------------------- sheet -- */

.sheet {
  position: fixed;
  left: 0;
  right: 0;
  bottom: 0;
  z-index: 10;
  max-width: 460px;
  margin: 0 auto;
  padding: 14px max(12px, var(--safe-l)) max(14px, var(--safe-b)) max(12px, var(--safe-r));
  border-top: 1px solid var(--line);
  border-radius: 14px 14px 0 0;
  background: var(--panel);
  display: flex;
  flex-direction: column;
  gap: 10px;
}

/* -------------------------------------------------------------- courtesy -- */

@media (prefers-reduced-motion: reduce) {
  .emote[data-on="1"] { animation: none; }
  /* The bars still change — instantly, and still recoloured on the way — they
     just stop sliding and stop flashing. */
  .stat__fill { transition: none; }
  .stat[data-bump] .stat__fill { animation: none; }
}

/* Landscape on a phone: the stage keeps a fixed slice and the transcript
   takes the rest, rather than both fighting over 320 vertical pixels. */
@media (orientation: landscape) and (max-height: 520px) {
  .slot { flex-basis: 30%; min-height: 72px; }
  .actions { grid-template-columns: repeat(6, 1fr); }
  .act { min-height: var(--tap); }
}
