/* moc3 -> cmo3 — the hermit crab.
   State lives on #stage[data-state]: idle | dragging | file | converting | done | error.
   The crab is generated as pixel <rect>s in layered <g> groups (see app.js) so
   parts (pupils, mouth, claws) animate independently. */

:root {
  --bg: #0f1117;
  --bg-card: #171a23;
  --border: #2a2f40;
  --text: #e6e8ee;
  --text-dim: #8a90a2;
  --accent: #6c8cff;
  --ok: #46d39a;
  --err: #ff6b6b;
  --ease: cubic-bezier(0.4, 0, 0.2, 1);
  --bounce: cubic-bezier(0.34, 1.56, 0.64, 1);
}

* { box-sizing: border-box; }

body {
  margin: 0;
  min-height: 100vh;
  display: grid;
  place-items: center;
  font-family: "Segoe UI", system-ui, -apple-system, sans-serif;
  color: var(--text);
  background: radial-gradient(1200px 600px at 50% -10%, #1b2030 0%, var(--bg) 60%);
}

.app {
  width: min(460px, 92vw);
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 18px;
  padding: 28px 0;
  text-align: center;
}

.app__head { text-align: center; }
.app__title { margin: 0; font-size: 30px; font-weight: 650; letter-spacing: -0.5px; }
.app__title .arrow { color: var(--accent); margin: 0 4px; }
.app__sub { margin: 6px 0 0; color: var(--text-dim); font-size: 14px; max-width: 36ch; }

/* --- stage (crab + speech + spit) --------------------------------------- */
.stage {
  position: relative;
  width: 100%;
  min-height: 300px;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: flex-end;
  padding-bottom: 64px;            /* room for the spat-out token */
}

/* speech bubble ----------------------------------------------------------- */
.speech {
  position: relative;
  background: var(--bg-card);
  border: 1px solid var(--border);
  border-radius: 12px;
  padding: 9px 14px;
  font-size: 14px;
  margin-bottom: 14px;
  min-height: 1em;
  transition: color 0.2s var(--ease);
  animation: speech-pop 0.25s var(--bounce);
}
.speech::after {                   /* little tail */
  content: "";
  position: absolute;
  left: 50%;
  bottom: -7px;
  width: 12px; height: 12px;
  background: var(--bg-card);
  border-right: 1px solid var(--border);
  border-bottom: 1px solid var(--border);
  transform: translateX(-50%) rotate(45deg);
}
@keyframes speech-pop { from { transform: translateY(4px) scale(0.96); opacity: 0; } }
.stage[data-state="done"] .speech { color: var(--ok); }
.stage[data-state="error"] .speech { color: var(--err); }

/* instruction sub-line inside the idle bubble */
.speech__sub {
  display: block;
  margin-top: 4px;
  font-size: 11.5px;
  font-weight: 400;
  color: var(--text-dim);
}
.speech__sub b { color: var(--text); font-weight: 600; }
.speech { max-width: 30ch; }

/* --- the crab (button = drop target + file picker) ----------------------- */
.crab {
  appearance: none;
  border: none;
  background: none;
  padding: 0;
  cursor: pointer;
  position: relative;
  width: 240px;
  display: block;
  transform-origin: 50% 90%;
  animation: bob 2.6s var(--ease) infinite;
}
.crab:focus-visible { outline: 2px solid var(--accent); outline-offset: 8px; border-radius: 8px; }

.crab__svg {
  width: 100%;
  height: auto;
  image-rendering: pixelated;       /* harmless on vector; crispEdges does the work */
  overflow: visible;
  filter: drop-shadow(0 10px 16px rgba(0, 0, 0, 0.45));
}

/* parts that scale need a predictable origin */
.lyr--pupils { transform-box: fill-box; transform-origin: center; }
/* open mouth is hidden until the crab is chewing */
.lyr--mouthOpen { display: none; }
.lyr--clawL { transform-box: fill-box; transform-origin: 100% 0%; }
.lyr--clawR { transform-box: fill-box; transform-origin: 0% 0%; }

/* idle: gentle bob + blink + slow claw flex (the "feed me" wave) */
.lyr--pupils { animation: blink 4s var(--ease) infinite; }
.lyr--clawL { animation: wave-l 2.6s var(--ease) infinite; }
.lyr--clawR { animation: wave-r 2.6s var(--ease) infinite; }

@keyframes bob { 0%, 100% { transform: translateY(0); } 50% { transform: translateY(-7px); } }
@keyframes blink {
  0%, 92%, 100% { transform: scaleY(1); }
  96% { transform: scaleY(0.1); }
}
@keyframes wave-l { 0%, 100% { transform: rotate(0deg); } 50% { transform: rotate(-5deg); } }
@keyframes wave-r { 0%, 100% { transform: rotate(0deg); } 50% { transform: rotate(5deg); } }
@keyframes wave-l-eager { 0%, 100% { transform: rotate(-2deg); } 50% { transform: rotate(-13deg); } }
@keyframes wave-r-eager { 0%, 100% { transform: rotate(2deg); } 50% { transform: rotate(13deg); } }

/* hover/dragging: crab perks up, claws open wide, mouth gapes */
.stage[data-state="dragging"] .crab,
.crab:hover {
  animation: bob 0.9s var(--ease) infinite;
  transform: scale(1.06);
}
.stage[data-state="dragging"] .lyr--clawL { animation: wave-l-eager 0.5s var(--ease) infinite; }
.stage[data-state="dragging"] .lyr--clawR { animation: wave-r-eager 0.5s var(--ease) infinite; }

/* converting: the crab shakes/chews while it digests the model */
.stage[data-state="converting"] .crab {
  animation: munch 0.18s steps(2) infinite;
  cursor: progress;
}
/* chewing: reveal the open mouth, and snap the closed mouth on/off it */
.stage[data-state="converting"] .lyr--mouthOpen { display: block; }
.stage[data-state="converting"] .lyr--mouthClosed { animation: chew 0.4s steps(1, end) infinite; }
.stage[data-state="converting"] .lyr--pupils { animation: none; }
@keyframes munch {
  0%   { transform: translate(-2px, 1px) rotate(-2deg); }
  50%  { transform: translate(2px, -1px) rotate(2deg); }
  100% { transform: translate(-1px, 0) rotate(-1deg); }
}
/* closed mouth at rest -> slid onto the open mouth -> back (a 2-frame chomp).
   --cdx/--cdy (viewBox units) are set per-art in app.js from the painted regions. */
@keyframes chew {
  0%   { transform: translate(0, 0); }
  50%  { transform: translate(calc(var(--cdx, 0) * 1px), calc(var(--cdy, 0) * 1px)); }
  100% { transform: translate(0, 0); }
}

/* done: a satisfied little hop. error: a queasy wobble. */
.stage[data-state="done"] .crab { animation: hop 0.5s var(--bounce); }
.stage[data-state="error"] .crab { animation: queasy 0.6s var(--ease); }
@keyframes hop {
  0% { transform: translateY(0); } 40% { transform: translateY(-18px) scale(1.04); }
  100% { transform: translateY(0); }
}
@keyframes queasy {
  0%, 100% { transform: rotate(0); } 20% { transform: rotate(-5deg); }
  60% { transform: rotate(4deg); } 80% { transform: rotate(-2deg); }
}

/* --- the spat-out token -------------------------------------------------- */
.spit {
  position: absolute;
  bottom: 6px;
  left: 50%;
  transform: translateX(-50%);
  height: 56px;
  display: flex;
  align-items: center;
  justify-content: center;
  pointer-events: none;            /* re-enabled on the token itself */
}
.token {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  padding: 7px 12px 7px 9px;
  border-radius: 10px;
  border: 1px solid var(--border);
  background: var(--bg-card);
  font-size: 13px;
  font-weight: 600;
  text-decoration: none;
  color: var(--text);
  pointer-events: auto;
}
.token--file {
  border-color: var(--ok);
  box-shadow: 0 8px 22px -8px rgba(70, 211, 154, 0.5);
  animation: spit-file 0.6s var(--bounce);
}
.token--file:hover { background: #1d2430; }
.token--junk {
  border-color: #5a4a3a;
  color: var(--text-dim);
  animation: spit-junk 0.7s var(--ease);
}

/* file icon: a tiny pixel floppy disk */
.token--file .token__icon {
  width: 16px; height: 18px; flex: none;
  background:
    linear-gradient(#cfe8dd, #cfe8dd) 3px 2px / 10px 5px no-repeat,   /* label */
    linear-gradient(#2a2630, #2a2630) 5px 9px / 6px 6px no-repeat,    /* shutter */
    #46d39a;
  border: 1px solid #2a2630;
  clip-path: polygon(0 0, 78% 0, 100% 22%, 100% 100%, 0 100%);        /* notched corner */
}
/* junk icon: a crumpled-paper / garbage blob */
.token--junk .token__icon {
  width: 16px; height: 16px; flex: none;
  background:
    radial-gradient(circle at 5px 6px, #8a7a66 2px, transparent 3px),
    radial-gradient(circle at 11px 10px, #6b5b4a 2px, transparent 3px),
    #7a6a56;
  border: 1px solid #3a2f24;
  border-radius: 45% 55% 50% 60%;
  transform: rotate(-8deg);
}
@keyframes spit-file {
  0%   { opacity: 0; transform: translateY(-96px) scale(0.4) rotate(-30deg); }
  55%  { opacity: 1; transform: translateY(10px) scale(1.06) rotate(8deg); }
  100% { transform: translateY(0) scale(1) rotate(0); }
}
@keyframes spit-junk {
  0%   { opacity: 0; transform: translateY(-80px) scale(0.5) rotate(40deg); }
  50%  { opacity: 1; transform: translateY(6px) rotate(-12deg); }
  75%  { transform: translateY(0) rotate(6deg); }
  100% { transform: translateY(0) rotate(-8deg); }
}

/* show the right token, hide the stage's token otherwise */
.spit { opacity: 0; transition: opacity 0.2s var(--ease); }
.stage[data-state="done"] .spit,
.stage[data-state="error"] .spit { opacity: 1; }

/* --- status + note ------------------------------------------------------- */
.status {
  position: absolute;
  bottom: -34px;
  left: 0; right: 0;
  font-size: 13px;
  line-height: 1.5;
  color: var(--text-dim);
  min-height: 1em;
}
.stage[data-state="done"] .status { color: var(--text); }
.stage[data-state="error"] .status { color: var(--err); }
.status a { color: var(--accent); font-weight: 600; }

/* respect reduced-motion: keep the state changes, drop the looping wiggle */
@media (prefers-reduced-motion: reduce) {
  .crab, .lyr--pupils, .lyr--clawL, .lyr--clawR,
  .stage[data-state="converting"] .crab,
  .stage[data-state="converting"] .lyr--mouthClosed { animation: none !important; }
  .token--file, .token--junk { animation: none; }
}
