/* overlay.css — the controller itself: CSS variables, part fills, pressed states, and
   the transparent overlay (OBS) mode. Shared by the studio preview and the OBS output. */

.controller {
  /* Defaults; main.js overrides these per-config. Kept here so the overlay renders
     correctly even before JS sets anything. Face defaults shown are DualSense's. */
  --body: #f4f5f7;
  --body-edge: #d4d8de;
  --btn: #eceef1;
  --btn-edge: #c4c9d1;
  --accent: #2b8fff;
  --stick: #1b1d22;
  --touchpad: #191b1f;
  --light: #1c9eff;
  --label: #8b9099;
  --sym-top: #9aa0aa;
  --sym-right: #9aa0aa;
  --sym-bottom: #9aa0aa;
  --sym-left: #9aa0aa;
  --shell-tint: #ffffff; /* photo recolor (multiply); #ffffff = no change */
  --btn-tint: #ffffff;
  --light-tint: transparent; /* light-bar recolor (color blend); transparent = keep original blue */
  --scale: 1;

  line-height: 0;
  transform: scale(var(--scale));
  transform-origin: center center;
  isolation: isolate; /* contain the photo recolor's mix-blend-mode to the controller's own layers */
}

/* photo recolor: a color rect multiplied over a masked zone (shell / buttons), so the baked photo's
   highlights + shadows show through the new color. Sits just above the photo, below the hotspots. */
.controller .tint { mix-blend-mode: multiply; pointer-events: none; }
/* light bar is a saturated blue -> recolor by hue with the "color" blend (keeps its glow/brightness) */
.controller .tint-light { mix-blend-mode: color; pointer-events: none; }
/* custom shell wrap: a user image multiplied over the shell zone (same idea as .tint, but an image) */
.controller .wrap-img { mix-blend-mode: multiply; pointer-events: none; }

.controller svg {
  display: block;
  width: 560px;
  height: auto;
  overflow: visible;
}

/* --- part fills --- */
.controller .body { fill: var(--body); stroke: var(--body-edge); stroke-width: 6; }
.controller .faceplate { fill: var(--touchpad); }
.controller .btn-base { fill: var(--btn); stroke: var(--btn-edge); stroke-width: 2; }
.controller .touchpad { fill: var(--touchpad); stroke: var(--btn-edge); stroke-width: 2; }
/* a touchpad surface that matches the shell/buttons (e.g. white DualSense pad) */
.controller .pad-surface { fill: var(--btn); stroke: var(--btn-edge); stroke-width: 2; }
/* dark trigger tips (declared after .btn-base so they stay dark until pressed) */
.controller .trigger-tip { fill: var(--stick); stroke: var(--btn-edge); stroke-width: 2; }
.controller .dpad-hub { fill: var(--btn); }
.controller .dpad-glyph { fill: #8b9099; }
/* small holes/grilles sit on the dark center accent → faint light marks read best */
.controller .speaker, .controller .mic, .controller .deco { fill: #ffffff; opacity: 0.2; }
.controller .lightbar { fill: var(--light); opacity: 0.9; }
.controller .lightstrip { fill: none; stroke: var(--light); stroke-width: 3; opacity: 0.95; stroke-linecap: round; }
.controller .stick-well { fill: var(--touchpad); stroke: var(--btn-edge); stroke-width: 3; }
/* the stick cap is declared after .btn-base so it keeps its own fill until pressed.
   .stick-top is the GROUP that translates; the cap + textured ring move together. */
.controller .stick-cap { fill: var(--stick); stroke: var(--btn-edge); stroke-width: 2; }
.controller .stick-ring { fill: none; stroke: var(--btn-edge); stroke-width: 2; opacity: 0.45; }

/* --- data-region coloring (for spec-compliant external SVG art) ---
   Declared AFTER the class fills so that where both apply (e.g. a stick cap tagged
   class="btn-base" data-region="stick"), the region wins for the base color. */
.controller [data-region="body"]   { fill: var(--body); }
.controller [data-region="button"] { fill: var(--btn); }
.controller [data-region="center"] { fill: var(--touchpad); }
.controller [data-region="stick"]  { fill: var(--stick); }
.controller [data-region="light"]  { stroke: var(--light); }

/* --- face-button symbols ---
   Position class sets `color`; the glyph style uses currentColor so the same four
   colors work whether the glyph is a stroked PS shape or a filled Xbox letter. */
.controller .sym-top { color: var(--sym-top); }
.controller .sym-right { color: var(--sym-right); }
.controller .sym-bottom { color: var(--sym-bottom); }
.controller .sym-left { color: var(--sym-left); }
.controller .sym-stroke {
  fill: none;
  stroke: currentColor;
  stroke-width: 4;
  stroke-linecap: round;
  stroke-linejoin: round;
}
.controller .sym-letter {
  stroke: none;
  fill: currentColor;
  font: 700 30px ui-sans-serif, system-ui, "Segoe UI", Roboto, sans-serif;
  text-anchor: middle;
  dominant-baseline: central;
}

/* --- transitions + pressed state --- */
.controller [data-control] { transition: filter 0.06s linear; }
.controller [data-control] .btn-base { transition: fill 0.05s linear, stroke 0.05s linear; }
.controller .stick-top { transition: transform 0.05s linear; }

/* press feedback: glow + slight brighten on the whole control. Works for class-based
   art, data-region SVGs, and photo skins — it never depends on a part's fill. */
.controller [data-control].pressed { filter: brightness(1.15) drop-shadow(0 0 6px var(--accent)) drop-shadow(0 0 16px var(--accent)); }
/* Halo only: a more vibrant, wider accent bloom — applied to EVERY control (sticks, touchpad, face
   buttons, d-pad, paddles), not just the triggers/bumpers, so the halo look is consistent across the pad. */
.controller:not(.glow-full) [data-control].pressed {
  filter: brightness(1.18) drop-shadow(0 0 5px var(--accent)) drop-shadow(0 0 13px var(--accent)) drop-shadow(0 0 26px var(--accent));
}

/* photo-controller hotspots: invisible until pressed. Full glow = a solid-ish accent fill over the
   button (inside glows). Halo only = glow AROUND the button like the triggers: an accent OUTLINE with
   no interior fill, so the bloom radiates from the edge and the button face stays clear. */
.controller .hotspot { fill: var(--accent); opacity: 0; transition: opacity 0.05s linear; }
.controller.glow-full [data-control].pressed .hotspot { opacity: 0.55; }
.controller:not(.glow-full) [data-control].pressed .hotspot {
  fill: none;
  stroke: var(--accent);
  stroke-width: 8;
  opacity: 0.9;
}

/* glow style switch (photo triggers/bumpers): "full" tints the button face with the accent
   (.glow-fill), "halo" is just the brightness + accent halo (the .pressed group filter). Toggled
   by .glow-full on the .controller. The feFlood floods trigger images with the live accent color. */
.controller feFlood { flood-color: var(--accent); }
.controller .glow-fill { fill: var(--accent); opacity: 0; transition: opacity 0.05s linear; pointer-events: none; }
.controller:not(.glow-full) .glow-fill { display: none; }
.controller.glow-full [data-control].pressed .glow-fill { opacity: 0.55; }

/* RGB cycle: a registered hue animates 0->360 and drives --accent through the rainbow. Every glow
   (hotspot fills, .pressed halos, trigger floods) reads --accent, so the whole pad cycles together.
   render.js adds .rgb and stops setting --accent inline so this rule wins. Runs in studio + OBS. */
@property --os-hue { syntax: "<number>"; inherits: false; initial-value: 0; }
@keyframes os-rgb-cycle { to { --os-hue: 360; } }
.controller.rgb { animation: os-rgb-cycle var(--rgb-dur, 3s) linear infinite; --accent: hsl(var(--os-hue), 100%, 60%); }

/* calibration mode: reveal every hotspot, make it draggable, and suppress the press glow */
.controller.calibrating .hotspot { opacity: 0.5; stroke: #ffffff; stroke-width: 3; cursor: move; }
.controller.calibrating [data-control] { filter: none !important; touch-action: none; }
/* draggable resize handle on the lower-right of each calibratable dot (drag it to resize) */
.controller.calibrating .cal-handle { fill: #ffffff; stroke: var(--accent); stroke-width: 3; opacity: 0.95; cursor: nwse-resize; }
.controller.calibrating .cal-handle:hover { fill: var(--accent); }

.controller [data-control].clickable { cursor: pointer; }

/* --- overlay (OBS) mode: just the controller, transparent background --- */
body.overlay-mode { background: transparent; }
body.overlay-mode .os-header,
body.overlay-mode .os-panel-wrap,
body.overlay-mode .os-hint { display: none; }
body.overlay-mode .os-main { display: block; height: 100vh; }
body.overlay-mode .os-stage-wrap {
  height: 100vh;
  background: transparent;
  padding: 0;
}
body.overlay-mode .os-stage {
  height: 100vh;
  display: flex;
  align-items: center;
  justify-content: center;
}

/* output stack: the controller + the optional creator-code line, centered together as one group.
   Shared by the studio preview and the OBS output; overlay-mode adds the full-height centering above. */
.os-stage {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 22px;
}

/* creator code line — an optional "CODE: XXXX" beneath the pad. Label + value are separate spans so
   each carries its own display font + color (set inline by render.js). */
.os-code {
  display: flex;
  align-items: center; /* center the value to the label (so a taller code font stays balanced) */
  gap: 0.18em;
  font-size: 34px;
  line-height: 1; /* equal line boxes → align-items:center reads as true optical centering */
  white-space: nowrap;
  text-shadow: 0 2px 7px rgba(0, 0, 0, 0.55), 0 0 2px rgba(0, 0, 0, 0.45);
}
.os-code[hidden] { display: none; } /* beat .os-code{display:flex} — the UA [hidden] rule would lose */
.os-code-label { color: #ffffff; }
.os-code-value { color: #ff2e2e; }
/* "Move on canvas" mode (studio only): a dashed grab handle around the code so it reads as draggable */
.os-code-move { cursor: move; touch-action: none; outline: 2px dashed rgba(106, 166, 255, 0.9); outline-offset: 7px; border-radius: 4px; }

/* rear grip buttons block (def.backButtons, e.g. the Steam Controller's L4/L5/R4/R5) — an optional,
   user-draggable cluster beneath the pad. Part of the output group, so it ships to OBS like the code.
   Each paddle is positioned/sized inline by render.js (--ang = gradient angle, --rot = splay tilt). */
.os-back { position: relative; flex: none; }
.os-back[hidden] { display: none; } /* beat any display set above (mirrors .os-code[hidden]) */
.os-back-btn {
  position: absolute;
  border-radius: 50% / 46%;
  background: linear-gradient(var(--ang, 150deg), #3a3a3c 0%, #2a2a2c 45%, #202022 100%);
  box-shadow: 0 10px 26px rgba(0, 0, 0, 0.32), inset 0 3px 6px rgba(255, 255, 255, 0.10), inset 0 -8px 14px rgba(0, 0, 0, 0.45);
  transform: rotate(var(--rot, 0deg));
  display: flex; align-items: center; justify-content: center;
  transition: box-shadow 0.08s ease, filter 0.08s ease;
}
.os-back-btn span {
  font: 500 13px/1 "Helvetica Neue", Arial, sans-serif;
  letter-spacing: 0.05em; color: rgba(255, 255, 255, 0.22); user-select: none;
}
.os-back-btn.clickable { cursor: pointer; }
/* lit (pressed / click-preview / assigned-button live): accent ring + bloom, label brightens */
.os-back-btn.pressed {
  filter: brightness(1.12);
  box-shadow: 0 0 0 2px var(--accent, #2b8fff), 0 0 18px 3px var(--accent, #2b8fff),
              inset 0 3px 6px rgba(255, 255, 255, 0.12), inset 0 -8px 14px rgba(0, 0, 0, 0.45);
}
.os-back-btn.pressed span { color: #fff; }
/* move mode (studio only): dashed grab handle around the whole block so it reads as draggable */
.os-back-move { cursor: move; touch-action: none; outline: 2px dashed rgba(106, 166, 255, 0.9); outline-offset: 9px; border-radius: 8px; }

/* touchpad eyes — optional reactive overlay on the touchpad. Colors via CSS vars set by render.js;
   motion is driven by js/touchpad-eyes.js (transform attributes). Non-interactive (never blocks clicks). */
#tp-eyes { pointer-events: none; }
#tp-eyes .sclera rect { fill: var(--eye-sclera, #f4f1ea); }
#tp-eyes .pupil .p     { fill: var(--pupil, #16121f); }
#tp-eyes .pupil .glint { fill: var(--glow, #ffffff); }
#tp-eyes .lid          { fill: var(--lid, var(--pupil, #16121f)); }
#tp-eyes .brow         { fill: var(--brow, var(--pupil, #16121f)); }
/* pixel skin: alternate eye art swapped in by the active-state class (set by the engine). Hidden by
   default; the dead 'X' and the happy '^_^' arc replace the pupils/sclera while their state is held. */
#tp-eyes .dead-art, #tp-eyes .happy-art { display: none; }
#tp-eyes .xeye rect, #tp-eyes .harc rect { fill: var(--pupil, #16121f); }
#tp-eyes.state-dead .dead-art  { display: block; }
#tp-eyes.state-dead .pupil     { display: none; }   /* X replaces the pupils, sclera stays */
#tp-eyes.state-win  .happy-art { display: block; }
#tp-eyes.state-win  .sclera rect,
#tp-eyes.state-win  .pupil     { display: none; }    /* arcs replace the whole eye */

/* robot skins (visor / lens) — their own palette; the engine mirrors the state-* class onto #tp-eyes,
   so the expression colors below light up on the dead/angry reactions. */
#tp-eyes[data-skin="visor"], #tp-eyes[data-skin="lens"] {
  --robo-glow:  #23e6ff;  /* neon core */
  --robo-hot:   #eafdff;  /* white-hot center highlight */
  --robo-dark:  #07070d;  /* glass cavity */
  --robo-line:  #3d3d4d;  /* housing rim */
  --robo-inner: #1c4b55;  /* glass rim (glow-tinted) */
}
#tp-eyes .robo-bar     { fill: var(--robo-glow); }
#tp-eyes .robo-hotspot { fill: var(--robo-hot); }
#tp-eyes .robo-glass   { fill: var(--robo-dark); stroke: var(--robo-inner); }
#tp-eyes .robo-rim     { stroke: var(--robo-line); }
#tp-eyes .robo-screw   { fill: #2e2e3a; stroke: #454556; }
#tp-eyes .robo-plate   { fill: #343442; }
#tp-eyes .robo-ring-a  { stroke: #1b8fa3; }
#tp-eyes .robo-ring-b  { stroke: var(--robo-glow); }
#tp-eyes[data-skin="visor"].state-angry .robo-bar,     #tp-eyes[data-skin="lens"].state-angry .robo-bar     { fill: #ff2e4d; }
#tp-eyes[data-skin="visor"].state-angry .robo-hotspot, #tp-eyes[data-skin="lens"].state-angry .robo-hotspot { fill: #ffe3e8; }
#tp-eyes[data-skin="visor"].state-angry #visor-bloom { fill: url(#tpe-v-core-red); }
#tp-eyes[data-skin="lens"].state-angry  .lens-bloom  { fill: url(#tpe-l-core-red); }
#tp-eyes[data-skin="lens"].state-angry  .robo-ring-b { stroke: #ff2e4d; }
#tp-eyes[data-skin="visor"].state-angry .robo-glass,  #tp-eyes[data-skin="lens"].state-angry .robo-glass  { stroke: #5c1c2a; }
#tp-eyes[data-skin="visor"].state-dead .robo-bar,     #tp-eyes[data-skin="lens"].state-dead .robo-bar     { fill: #566070; }
#tp-eyes[data-skin="visor"].state-dead .robo-hotspot, #tp-eyes[data-skin="lens"].state-dead .robo-hotspot { fill: #97a1ad; }
#tp-eyes[data-skin="visor"].state-dead #visor-bloom { fill: url(#tpe-v-core-dead); }
#tp-eyes[data-skin="lens"].state-dead  .lens-bloom  { fill: url(#tpe-l-core-dead); }
#tp-eyes[data-skin="lens"].state-dead  .robo-ring-a { stroke: #39424d; }
#tp-eyes[data-skin="lens"].state-dead  .robo-ring-b { stroke: #566070; }
#tp-eyes[data-skin="visor"].state-dead .robo-glass,  #tp-eyes[data-skin="lens"].state-dead .robo-glass  { stroke: #2a3038; }
/* eyes move/resize calibration (studio only): a dashed frame + a corner resize handle */
.controller.eyes-calibrating #tp-eyes { pointer-events: auto; cursor: move; touch-action: none; }
.eye-cal-frame { fill: none; stroke: #6aa6ff; stroke-width: 2; vector-effect: non-scaling-stroke; stroke-dasharray: 7 6; pointer-events: none; }
.eye-cal-handle { fill: #ffffff; stroke: #6aa6ff; stroke-width: 2; vector-effect: non-scaling-stroke; cursor: nwse-resize; pointer-events: auto; }

/* =====================================================================================
   KEYBOARD device (config.device === "keyboard"). Everything below is scoped to .rig /
   .kbd / .mouse / .key — disjoint from the controller's .controller / [data-control]
   rules above, so the two engines coexist without touching each other. The look vars +
   state classes live on .rig (the wrapper) so BOTH the keyboard and the optional mouse
   inherit them; .kbd / .mouse are just the two device containers inside it.
   (The @property --os-hue, @keyframes os-rgb-cycle, body.overlay-mode, and .os-stage
   rules are shared from the controller section above — not redefined here.)
   ===================================================================================== */

.rig {
  --case: #15171c;
  --keycap: #23262e;
  --keycap-edge: #101216;
  --legend: #c7ccd6;
  --dim-level: 0.25;

  display: flex;
  align-items: flex-end;
  gap: 30px;
  line-height: 0;
  transform: scale(var(--scale));
  transform-origin: center center;
  isolation: isolate;
}

.kbd svg { display: block; width: 920px; max-width: 100%; height: auto; overflow: visible; }
.mouse svg { display: block; width: 150px; height: auto; overflow: visible; }

/* device backgrounds (inherit --case from .rig) */
.kbd .case { fill: var(--case); }
.mouse .mouse-body { fill: var(--case); stroke: var(--keycap-edge); stroke-width: 2; }
.mouse .mouse-seam { fill: none; stroke: var(--keycap-edge); stroke-width: 2; opacity: 0.7; }
.mouse .wheel-well { fill: var(--keycap-edge); }
.kbd .wrap-img { pointer-events: none; }

/* keys + mouse buttons (shared .key/.kc/.lg) */
.rig .key .kc {
  fill: var(--ka, var(--keycap)); /* --ka = per-key color override (set inline on painted keys/buttons) */
  stroke: var(--keycap-edge);
  stroke-width: 1.5;
  transition: fill 0.05s linear, opacity 0.12s linear;
}
.rig .key .lg {
  fill: var(--legend);
  text-anchor: middle;
  dominant-baseline: central;
  font-family: ui-sans-serif, system-ui, "Segoe UI", Roboto, sans-serif;
  font-weight: 600;
  pointer-events: none;
  user-select: none;
}
.rig .key { transition: filter 0.06s linear, opacity 0.14s linear; }

/* custom whole-board art (keyboard only): the image is the board; keycaps go clear so it shows through,
   legends + glow stay on top. A faint outline keeps the key grid readable over busy art. */
.kbd.wrapped .key .kc { fill: transparent; stroke: rgba(255, 255, 255, 0.12); }

/* press feedback. Full glow = fill with the accent + halo; Halo only = a wider bloom, face unchanged.
   A painted key/button (--ka) glows in its own color. */
.rig .key.pressed { filter: brightness(1.14) drop-shadow(0 0 6px var(--ka, var(--accent))) drop-shadow(0 0 16px var(--ka, var(--accent))); }
.rig.glow-full .key.pressed .kc { fill: var(--ka, var(--accent)); stroke: var(--ka, var(--accent)); }
.rig:not(.glow-full) .key.pressed {
  filter: brightness(1.12) drop-shadow(0 0 5px var(--ka, var(--accent))) drop-shadow(0 0 13px var(--ka, var(--accent))) drop-shadow(0 0 26px var(--ka, var(--accent)));
}

/* dim modes (driven off the slider via --dim-level) */
.rig.dim-spotlight .key.inactive { opacity: var(--dim-level); }
.rig.dim-reaction .key { opacity: var(--dim-level); }
.rig.dim-reaction .key.pressed { opacity: 1; }

/* "select your keys" mode: show every key full-strength + outline the chosen ones */
.rig.selecting .key { opacity: 1 !important; cursor: pointer; }
.rig.selecting .key .kc { stroke: #3a414f; }
.rig.selecting .key.selected .kc { stroke: var(--accent); stroke-width: 3.5; }

/* "paint key colors" mode: show every key full-strength; click a key to apply the current color */
.rig.painting .key { opacity: 1 !important; cursor: crosshair; }

.rig .key.clickable { cursor: pointer; }

/* RGB cycle (reuses the shared --os-hue / os-rgb-cycle defined in the controller section) */
.rig.rgb { animation: os-rgb-cycle var(--rgb-dur, 3s) linear infinite; --accent: hsl(var(--os-hue), 100%, 60%); }

/* baked "realistic" SVG skin: keep the design's own keycaps; the engine just lights keys. Each key's
   .kc is a full-size overlay — transparent at rest (so the design shows), tinted when painted/pressed. */
.custom .key .kc { fill: transparent; }
.custom .key.painted .kc { fill: var(--ka); fill-opacity: 0.5; }
.rig.glow-full .custom .key.pressed .kc { fill: var(--ka, var(--accent)); fill-opacity: 0.72; stroke: var(--ka, var(--accent)); stroke-opacity: 0.9; }
.rig:not(.glow-full) .custom .key.pressed .kc { stroke: var(--ka, var(--accent)); stroke-opacity: 0.95; stroke-width: 2.6; }

/* recolor a baked keyboard skin from the Case / Keycaps pickers (CSS overrides the baked fills; the
   bevels/gloss stay for depth). Only kicks in once a picker is moved off its default (see keyboard-render.js). */
.kbd.custom.recolor-keycap .key .kb { fill: var(--keycap); }
.kbd.custom.recolor-case [fill="url(#caseGrad)"] { fill: var(--case); }
