/* ============================================================
   THE MACHINE — the product's one character (Basecamp 10397856146).

   ONE implementation, three hosts. This file is linked by the app
   (public/index.html), by the marketing site (public/landing/*), and is
   imported by the motion library (motion/src/app-css.ts), whose
   motion/src/components/Machine.tsx renders this same markup. So the
   videos and the product draw the same machine from the same rules and
   cannot drift. It lives under /landing/ because that is the one folder
   both the app (express.static on public/) and memachine.me (Pages,
   scripts/stage-landing.mjs) serve.

   Built only from parts the product already has:
     body  = the dark machine panel (--panel)
     eyes  = the panel lamps: hollow ring -> amber -> green, the lamp
             grammar (styles.css .lamp): green = running, amber = waiting
             on you, hollow = not finished
     mouth = the round red button (.film-btn: a cream ring around the
             record-red core, mono caps verb). It carries the step's verb
             when it is a button and changes shape when the machine talks,
             smiles or says "oh".
   Approved look: motion/reference/style-frames-round2.html and the beta
   explainer v1 (motion/src/ad/BetaExplainer.tsx).

   MARKUP (public/landing/machine.js builds it; Machine.tsx renders it):
     <div class="machine" data-mood="happy" style="--m-size:120px">
       <span class="machine-eye"><span class="machine-lamp"></span></span>
       <span class="machine-eye r"><span class="machine-lamp"></span></span>
       <span class="machine-mouth"><span class="machine-core">
         <span class="machine-verb">Film</span></span></span>
     </div>

   TWO WAYS TO DRIVE IT
   1. Moods, for pages: data-mood = idle | thinking | talking | happy |
      oh | button. Each mood is a look (eyes + mouth) plus its own small
      loop (blink; thinking glances side to side; talking moves its
      mouth). data-eyes / data-mouth override a mood's eyes or mouth.
      machine.js setMood() melts one mood into the next.
   2. Numbers, for frames: data-eyes (off | amber | ok), data-mouth
      (button | talk | smile | oh), the -from versions of both, and the
      registered numbers below set inline. Nothing loops without a mood,
      so a Remotion frame is exactly what its props say.

   Every size is a fraction of --m-size (the body's width), so it scales
   without a transform.
   ============================================================ */

/* The numbers that move. Registered so CSS animations and transitions
   interpolate them (an unregistered custom property only flips). */
@property --m-melt   { syntax: '<number>'; inherits: true; initial-value: 1; } /* 0..1 mouth: from -> to */
@property --m-eyes-p { syntax: '<number>'; inherits: true; initial-value: 1; } /* 0..1 eyes: from -> to */
@property --m-talk   { syntax: '<number>'; inherits: true; initial-value: 0; } /* 0..1 how open the talking mouth is */
@property --m-blink  { syntax: '<number>'; inherits: true; initial-value: 0; } /* 0 open .. 1 shut */
@property --m-look-x { syntax: '<number>'; inherits: true; initial-value: 0; } /* -1..1 where it looks */
@property --m-look-y { syntax: '<number>'; inherits: true; initial-value: 0; }
@property --m-press  { syntax: '<number>'; inherits: true; initial-value: 0; } /* 0..1 the button pressed */
@property --m-ring   { syntax: '<number>'; inherits: true; initial-value: 0; } /* 0..1 the ring's busy breath */

.machine {
  /* The lamps and the record red, fixed for a dark ground in both themes:
     the same values .panel re-declares (styles.css), so the eyes and the
     mouth are the panel's own lamps and button on either host.
     tests/machine.test.mjs keeps them equal to the panel's. */
  --ok: #79b587;
  --amber: #c99a4d;
  --accent: #c1301b;
  --accent-deep: #8f2011;
  --accent-on: #fffdf7;

  --m-size: 120px;
  --m-melt: 1; --m-eyes-p: 1; --m-talk: 0; --m-blink: 0;
  --m-look-x: 0; --m-look-y: 0; --m-press: 0; --m-ring: 0;

  /* The resting look: hollow eyes, the button. */
  --m-to-lit: 0; --m-to-green: 0; --m-to-squint: 0;
  --m-to-w: .78; --m-to-h: .78; --m-to-rt: .5; --m-to-rb: .5; --m-to-dy: 0;
  --m-to-text: 1; --m-to-rim: 1; --m-to-throat: 0;
  /* With nothing to change from, "from" is "to". */
  --m-from-lit: var(--m-to-lit); --m-from-green: var(--m-to-green); --m-from-squint: var(--m-to-squint);
  --m-from-w: var(--m-to-w); --m-from-h: var(--m-to-h); --m-from-rt: var(--m-to-rt); --m-from-rb: var(--m-to-rb);
  --m-from-dy: var(--m-to-dy); --m-from-text: var(--m-to-text); --m-from-rim: var(--m-to-rim); --m-from-throat: var(--m-to-throat);

  /* What is drawn: from blended into to. */
  --m-lit: calc(var(--m-from-lit) + (var(--m-to-lit) - var(--m-from-lit)) * var(--m-eyes-p));
  --m-green: calc(var(--m-from-green) + (var(--m-to-green) - var(--m-from-green)) * var(--m-eyes-p));
  --m-squint: calc(var(--m-from-squint) + (var(--m-to-squint) - var(--m-from-squint)) * var(--m-melt));
  --m-w: calc(var(--m-from-w) + (var(--m-to-w) - var(--m-from-w)) * var(--m-melt));
  --m-h: calc(var(--m-from-h) + (var(--m-to-h) - var(--m-from-h)) * var(--m-melt));
  --m-rt: calc(var(--m-from-rt) + (var(--m-to-rt) - var(--m-from-rt)) * var(--m-melt));
  --m-rb: calc(var(--m-from-rb) + (var(--m-to-rb) - var(--m-from-rb)) * var(--m-melt));
  --m-dy: calc(var(--m-from-dy) + (var(--m-to-dy) - var(--m-from-dy)) * var(--m-melt));
  --m-text: calc(var(--m-from-text) + (var(--m-to-text) - var(--m-from-text)) * var(--m-melt));
  --m-rim: calc(var(--m-from-rim) + (var(--m-to-rim) - var(--m-from-rim)) * var(--m-melt));
  --m-throat: calc(var(--m-from-throat) + (var(--m-to-throat) - var(--m-from-throat)) * var(--m-melt));

  position: relative;
  display: block;
  flex: none;
  box-sizing: border-box;
  width: var(--m-size);
  height: calc(var(--m-size) * 1.15);
  background: var(--panel);
  border: 1px solid var(--panel);
  border-radius: 3px;
  /* A hard printed shadow plus a soft lift. Fixed, not --ink-press: that
     token turns white in the dark theme and the body stays dark in both. */
  box-shadow:
    calc(var(--m-size) * .03) calc(var(--m-size) * .035) 0 #0b0906,
    0 calc(var(--m-size) * .15) calc(var(--m-size) * .2) calc(var(--m-size) * -.1) rgba(23, 20, 15, .55);
}

/* On the app's dark desk the body is nearly the ground's own colour, which
   leaves two floating eyes; the house hairline gives it its edge back. (The
   site does the same for its dark theme in landing.css.) */
:root[data-theme="dark"] .machine { border-color: var(--rule); }

/* ---- EYES: where each mood puts them, and what they change from ---- */
.machine[data-eyes="amber"],
.machine[data-mood="thinking"]:not([data-eyes]),
.machine[data-mood="talking"]:not([data-eyes]) { --m-to-lit: 1; --m-to-green: 0; }
.machine[data-eyes="ok"],
.machine[data-mood="happy"]:not([data-eyes]),
.machine[data-mood="button"]:not([data-eyes]) { --m-to-lit: 1; --m-to-green: 1; }

.machine[data-eyes-from="off"],
.machine[data-mood-from]:not([data-eyes-from]) { --m-from-lit: 0; --m-from-green: 0; }
.machine[data-eyes-from="amber"],
.machine[data-mood-from="thinking"]:not([data-eyes-from]),
.machine[data-mood-from="talking"]:not([data-eyes-from]) { --m-from-lit: 1; --m-from-green: 0; }
.machine[data-eyes-from="ok"],
.machine[data-mood-from="happy"]:not([data-eyes-from]),
.machine[data-mood-from="button"]:not([data-eyes-from]) { --m-from-lit: 1; --m-from-green: 1; }

/* The happy squint rides the mouth's melt (it is part of the smile). */
.machine[data-mood="happy"] { --m-to-squint: .85; }
.machine[data-mood-from] { --m-from-squint: 0; }
.machine[data-mood-from="happy"] { --m-from-squint: .85; }

/* ---- MOUTH: each shape is a handful of numbers (fractions of the mouth
   box): core width and height, top and bottom corner radius as a
   fraction of the height, drop, how much verb shows, how much ring
   shows, how open the throat is. Blending two is how the button melts
   into a smile. ---- */
.machine[data-mouth="smile"],
.machine[data-mood="happy"]:not([data-mouth]) {
  --m-to-w: .8; --m-to-h: .4; --m-to-rt: .02; --m-to-rb: 1; --m-to-dy: .12;
  --m-to-text: 0; --m-to-rim: 0; --m-to-throat: .35;
}
.machine[data-mouth="oh"],
.machine[data-mood="oh"]:not([data-mouth]) {
  --m-to-w: .44; --m-to-h: .54; --m-to-rt: .5; --m-to-rb: .5; --m-to-dy: .02;
  --m-to-text: 0; --m-to-rim: .55; --m-to-throat: 1;
}
.machine[data-mouth="talk"],
.machine[data-mood="talking"]:not([data-mouth]) {
  --m-to-w: calc(.62 + .08 * var(--m-talk)); --m-to-h: calc(.1 + .5 * var(--m-talk));
  --m-to-rt: .5; --m-to-rb: .5; --m-to-dy: calc(.06 - .04 * var(--m-talk));
  --m-to-text: 0; --m-to-rim: .5; --m-to-throat: calc(.4 + .6 * var(--m-talk));
}

.machine[data-mouth-from="button"],
.machine[data-mood-from]:not([data-mouth-from]) {
  --m-from-w: .78; --m-from-h: .78; --m-from-rt: .5; --m-from-rb: .5; --m-from-dy: 0;
  --m-from-text: 1; --m-from-rim: 1; --m-from-throat: 0;
}
.machine[data-mouth-from="smile"],
.machine[data-mood-from="happy"]:not([data-mouth-from]) {
  --m-from-w: .8; --m-from-h: .4; --m-from-rt: .02; --m-from-rb: 1; --m-from-dy: .12;
  --m-from-text: 0; --m-from-rim: 0; --m-from-throat: .35;
}
.machine[data-mouth-from="oh"],
.machine[data-mood-from="oh"]:not([data-mouth-from]) {
  --m-from-w: .44; --m-from-h: .54; --m-from-rt: .5; --m-from-rb: .5; --m-from-dy: .02;
  --m-from-text: 0; --m-from-rim: .55; --m-from-throat: 1;
}
.machine[data-mouth-from="talk"],
.machine[data-mood-from="talking"]:not([data-mouth-from]) {
  --m-from-w: calc(.62 + .08 * var(--m-talk)); --m-from-h: calc(.1 + .5 * var(--m-talk));
  --m-from-rt: .5; --m-from-rb: .5; --m-from-dy: calc(.06 - .04 * var(--m-talk));
  --m-from-text: 0; --m-from-rim: .5; --m-from-throat: calc(.4 + .6 * var(--m-talk));
}

/* ---- the eyes: two panel lamps ---- */
.machine-eye {
  position: absolute;
  left: calc(var(--m-size) * (.24 + .03 * var(--m-look-x)));
  top: calc(var(--m-size) * (.276 + .024 * var(--m-look-y)));
  width: calc(var(--m-size) * .15);
  height: calc(var(--m-size) * .15);
  transform: scaleY(calc(1 - .92 * var(--m-blink)));
}
.machine-eye.r { left: calc(var(--m-size) * (.61 + .03 * var(--m-look-x))); }
/* the green lamp's glow, outside the lamp's clip so it can spill */
.machine-eye::before {
  content: ""; position: absolute; inset: 0; border-radius: 50%;
  box-shadow: 0 0 calc(var(--m-size) * .08) color-mix(in srgb, var(--ok) 55%, transparent);
  opacity: calc(var(--m-green) * (1 - .4 * var(--m-squint)));
}
.machine-lamp {
  position: absolute; inset: 0; border-radius: 50%; overflow: hidden;
  /* the hollow ring (.lamp.off), fading as the lamp lights */
  box-shadow: inset 0 0 0 calc(var(--m-size) * .016) color-mix(in srgb, var(--panel-ink) calc(35% * (1 - var(--m-lit))), transparent);
}
/* the lit lamp: amber, turning green */
.machine-lamp::before {
  content: ""; position: absolute; inset: 0; border-radius: 50%;
  background: color-mix(in srgb, var(--ok) calc(min(1, var(--m-green) / max(var(--m-lit), .001)) * 100%), var(--amber));
  opacity: var(--m-lit);
}
/* the happy lower lid: a disc of panel rising from below ("^ ^") */
.machine-lamp::after {
  content: ""; position: absolute;
  left: -20%; width: 140%; height: 140%;
  top: calc(102% - 50% * var(--m-squint));
  border-radius: 50%;
  background: var(--panel);
}

/* ---- the mouth: the .film-btn ring and its red core ---- */
.machine-mouth {
  position: absolute;
  left: calc(var(--m-size) * .29);
  top: calc(var(--m-size) * .575);
  width: calc(var(--m-size) * .42);
  height: calc(var(--m-size) * .42);
  border-radius: 50%;
  box-shadow: inset 0 0 0 calc(var(--m-size) * .012)
    color-mix(in srgb, var(--panel-ink) calc((25% + 40% * var(--m-ring)) * var(--m-rim)), transparent);
}
.machine-core {
  --m-cw: calc(var(--m-size) * .42 * var(--m-w));
  --m-ch: calc(var(--m-size) * .42 * var(--m-h));
  position: absolute;
  left: calc((var(--m-size) * .42 - var(--m-cw)) / 2);
  top: calc((var(--m-size) * .42 - var(--m-ch)) / 2 + var(--m-size) * .42 * var(--m-dy));
  width: var(--m-cw);
  height: var(--m-ch);
  border-radius:
    calc(var(--m-cw) / 2) calc(var(--m-cw) / 2) calc(var(--m-cw) / 2) calc(var(--m-cw) / 2) /
    calc(var(--m-ch) * var(--m-rt)) calc(var(--m-ch) * var(--m-rt)) calc(var(--m-ch) * var(--m-rb)) calc(var(--m-ch) * var(--m-rb));
  overflow: hidden;
  display: grid; place-items: center;
  background: var(--accent);
  box-shadow: 0 0 calc(var(--m-size) * .12) color-mix(in srgb, var(--accent) calc(85% - 25% * var(--m-press)), transparent);
  /* the app's press is scale(.97); three times deeper so it reads small */
  transform: scale(calc(1 - .09 * var(--m-press)));
}
/* the throat: the deep red inside an open mouth */
.machine-core::before {
  content: ""; position: absolute;
  left: 18%; right: 18%; top: 34%; bottom: -30%;
  border-radius: 50%;
  background: var(--accent-deep);
  opacity: calc(var(--m-throat) * .9 + var(--m-press) * .5);
}
.machine-verb {
  position: relative;
  font-family: var(--font-mono, var(--mono));
  font-weight: 600;
  font-size: calc(var(--m-size) * .058);
  line-height: 1;
  letter-spacing: .2em;
  padding-left: .2em;
  text-transform: uppercase;
  color: var(--accent-on);
  opacity: var(--m-text);
  white-space: nowrap;
}

/* ---- LOOPS: only with a mood, so a driven frame never moves by itself ---- */
.machine[data-mood] { animation: m-blink 4.2s infinite; }
.machine[data-mood="thinking"] { animation: m-blink 4.2s infinite, m-look 3s ease-in-out infinite; }
.machine[data-mood="talking"]:not([data-lipsync]) { animation: m-blink 4.2s infinite, m-talk .9s ease-in-out infinite; }
/* happy hops once when it gets there, then just beams */
.machine[data-mood="happy"] { animation: m-blink 4.2s infinite, m-hop .9s cubic-bezier(.16, 1, .3, 1); }
@keyframes m-blink { 0%, 44%, 48%, 100% { --m-blink: 0; } 46% { --m-blink: 1; } }
@keyframes m-look { 0%, 100% { --m-look-x: -1; } 50% { --m-look-x: 1; } }
@keyframes m-talk {
  0%, 100% { --m-talk: .15; } 20% { --m-talk: .85; } 45% { --m-talk: .3; }
  65% { --m-talk: 1; } 85% { --m-talk: .4; }
}
@keyframes m-hop {
  0%, 100% { translate: 0 0; }
  30% { translate: 0 calc(var(--m-size) * -.07); }
  60% { translate: 0 0; }
}

@media (prefers-reduced-motion: reduce) {
  /* Still, never blank: no loops, and a talking mouth holds half open. */
  .machine[data-mood] { animation: none !important; }
  .machine[data-mood="talking"]:not([data-lipsync]) { --m-talk: .5; }
}
