/* 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; }
/* detail sub-line is dim for the idle hint, but readable for results/errors */
.stage[data-state="done"] .speech__sub { color: var(--text); }
.stage[data-state="error"] .speech__sub { color: var(--err); }

/* --- 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); }
}

/* queued: waiting for a free worker — calm patient bob (NOT the munch shake),
   mouth stays shut, with an occasional impatient claw tap. */
.stage[data-state="queued"] .crab { animation: bob 1.8s var(--ease) infinite; cursor: progress; }
.stage[data-state="queued"] .lyr--clawL { animation: tap-l 0.9s var(--ease) infinite; }
.stage[data-state="queued"] .lyr--clawR { animation: tap-r 0.9s var(--ease) infinite; }
@keyframes tap-l { 0%, 72%, 100% { transform: rotate(0deg); } 84% { transform: rotate(-4deg); } }
@keyframes tap-r { 0%, 76%, 100% { transform: rotate(0deg); } 88% { transform: rotate(4deg); } }

/* 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 ------------------------------------------------------- */
/* --- footer (creator link) ---------------------------------------------- */
.app__foot {
  margin-top: 10px;
}
.app__x {
  display: inline-flex;
  align-items: center;
  gap: 7px;
  padding: 6px 12px;
  border-radius: 999px;
  border: 1px solid var(--border);
  color: var(--text-dim);
  font-size: 13px;
  font-weight: 600;
  text-decoration: none;
  transition: color 0.15s var(--ease), border-color 0.15s var(--ease),
    background 0.15s var(--ease);
}
.app__x:hover {
  color: var(--text);
  border-color: var(--accent);
  background: var(--bg-card);
}
.app__x svg { display: block; flex: none; }

/* --- mode switch (Convert | Eye Toggles) -------------------------------- */
.modes {
  display: inline-flex;
  gap: 4px;
  padding: 4px;
  border: 1px solid var(--border);
  border-radius: 999px;
  background: var(--bg-card);
}
.mode {
  appearance: none;
  border: none;
  background: none;
  cursor: pointer;
  color: var(--text-dim);
  font: inherit;
  font-size: 14px;
  font-weight: 600;
  padding: 7px 16px;
  border-radius: 999px;
  display: inline-flex;
  align-items: baseline;
  gap: 5px;
  transition: color 0.15s var(--ease), background 0.2s var(--ease);
}
.mode__sub { font-size: 11px; font-weight: 500; opacity: 0.7; }
.mode[aria-selected="true"] {
  color: #fff;
  background: linear-gradient(180deg, var(--accent), #5878e6);
  box-shadow: 0 4px 14px -6px var(--accent);
}
.mode:not([aria-selected="true"]):hover { color: var(--text); }

/* --- eye-toggle options panel ------------------------------------------- */
.eye-controls[hidden] { display: none; }   /* author display:flex would override the attr */
.eye-controls {
  width: 100%;
  display: flex;
  flex-direction: column;
  gap: 8px;
  padding: 14px;
  border: 1px solid var(--border);
  border-radius: 14px;
  background: var(--bg-card);
  animation: speech-pop 0.25s var(--bounce);
}
.eye-controls__label {
  margin: 0 0 2px;
  font-size: 12px;
  color: var(--text-dim);
  text-align: left;
}
.style-row {
  display: flex;
  align-items: center;
  gap: 8px;
  flex-wrap: nowrap;       /* keep the toggle + controls on ONE line; controls shrink to fit */
}
.style-toggle {
  appearance: none;
  cursor: pointer;
  font: inherit;
  font-size: 13px;
  font-weight: 600;
  color: var(--text-dim);
  background: transparent;
  border: 1px solid var(--border);
  border-radius: 10px;
  padding: 7px 10px;
  min-width: 70px;
  flex: none;              /* fixed width; the controls beside it absorb the slack */
  text-align: left;
  display: inline-flex;
  align-items: center;
  gap: 6px;
  transition: all 0.15s var(--ease);
}
.style-toggle__glyph { font-size: 15px; line-height: 1; }
.style-toggle[aria-pressed="true"] {
  color: #fff;
  border-color: var(--accent);
  background: rgba(108, 140, 255, 0.16);
}
.style-toggle:hover { border-color: var(--accent); }

/* the color/opacity/spin options; dim + disable when the style is off */
.style-opts {
  display: inline-flex;
  align-items: center;
  gap: 9px;
  flex: 1 1 auto;         /* take the remaining row width and shrink when it's tight */
  min-width: 0;
  transition: opacity 0.15s var(--ease);
}
.style-row[data-on="false"] .style-opts { opacity: 0.3; pointer-events: none; }

.style-preview {
  width: 56px; height: 64px;
  flex: none;
  border-radius: 8px;
  border: 1px solid var(--border);
  background: #0b0d13;            /* dark behind the eye, like the model canvas */
  image-rendering: auto;
}

.style-color {
  appearance: none;
  -webkit-appearance: none;
  width: 26px; height: 26px;
  border: 1px solid var(--border);
  border-radius: 8px;
  background: none;
  cursor: pointer;
  padding: 0;
  flex: none;
}
.style-color::-webkit-color-swatch-wrapper { padding: 2px; }
.style-color::-webkit-color-swatch { border: none; border-radius: 6px; }
.style-color::-moz-color-swatch { border: none; border-radius: 6px; }

.slider {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  font-size: 11.5px;
  color: var(--text-dim);
  flex: 1 1 auto;          /* grow/shrink to fill the remaining row width */
  min-width: 0;
}
/* swirl has two controls (opacity + spin) — stack them so the row stays inline */
.slider-stack {
  display: inline-flex;
  flex-direction: column;
  gap: 6px;
  flex: 1 1 auto;          /* the flexible part of the swirl row */
  min-width: 0;
}
.slider-stack .slider { flex: none; width: 100%; }   /* fill the stack width, don't grow vertically */
.slider input[type="range"] {
  -webkit-appearance: none;
  appearance: none;
  flex: 1 1 auto; min-width: 44px; width: auto;   /* fill the slider, shrink to a floor */
  height: 4px;
  border-radius: 999px;
  background: var(--border);
  cursor: pointer;
}
.slider input[type="range"]::-webkit-slider-thumb {
  -webkit-appearance: none;
  width: 14px; height: 14px;
  border-radius: 50%;
  background: var(--accent);
  cursor: pointer;
}
.slider input[type="range"]::-moz-range-thumb {
  width: 14px; height: 14px; border: none;
  border-radius: 50%;
  background: var(--accent);
  cursor: pointer;
}
.slider__val { min-width: 28px; flex: none; color: var(--text); font-variant-numeric: tabular-nums; }

/* 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,
  .stage[data-state="queued"] .crab { animation: none !important; }
  .token--file, .token--junk, .eye-controls { animation: none; }
}

/* below-the-fold about copy (crawlable SEO text + genuine description) */
.about {
  max-width: 600px;
  margin: 2.75rem auto 3.25rem;
  padding: 0 1.25rem;
  text-align: center;
  color: var(--text-dim);
  font-size: 0.85rem;
  line-height: 1.65;
}
.about h2 { margin: 0 0 0.6rem; font-size: 1rem; font-weight: 600; color: var(--text); }
.about p { margin: 0 0 0.7rem; }
.about strong { color: var(--text); font-weight: 600; }
.about code {
  padding: 0.05em 0.35em;
  border-radius: 4px;
  background: var(--bg-card);
  border: 1px solid var(--border);
  font-size: 0.85em;
}
.about a { color: var(--accent); text-decoration: none; }
.about a:hover { text-decoration: underline; }
.about__foot { margin-top: 1rem; opacity: 0.75; font-size: 0.8rem; }
