/* Deck widget: programmable key grid with Option-B navigation chrome. */
/* The Deck draws its own device chassis, so the hosting dashboard tile must be a
   bare, transparent frame — no panel background, border, shadow, or category
   accent bar (the cyan strip). !important defeats the shared .material-oled rules
   regardless of class/specificity. */
.deck-panel {
  display: flex; padding: 0 !important;
  background: transparent !important; border: 0 !important; box-shadow: none !important;
}
.deck-panel::before, .deck-panel::after { display: none !important; }

/* ── Deck inside a tab-group: the console keeps its own identity ──────
   While the Deck is the ACTIVE tab, the hosting glass tile recedes into a
   matte chassis mount (same material family as .deck-device) so the device
   never looks like a gadget floating inside a glass card, and the tab bar
   reads as the device's faceplate strip. `.deck-tab-active` is toggled by
   renderGroupTile() (dashboard-tabgroups.js) whenever the active member
   changes — a plain class, deliberately not :has(), so the constant
   data-dashboard-hidden flips never pay :has() invalidation walks. */
/* The mount is a SECOND body the user reads as part of the device, so it fades
   with it — without this the transparency slider looked broken for anyone whose
   Deck lives in a tab group: the chassis went and this took its place. It also
   overrides the tile's own opacity control, so this slider is the only way to see
   through it. --deck-mount-alpha is stamped here by JS (deck.js on render,
   dashboard-tabgroups.js when the shown member changes): the value belongs to the
   .deck-root INSIDE this tile, and a custom property does not flow upward. It is
   its own property rather than --deck-plate-alpha because the "Nessuna" faceplate
   drops the mount outright, whatever the slider says. */
.panel.tabgroup.deck-tab-active {
  --mount-mix: calc(var(--deck-mount-alpha, 1) * 100%);
  background:
    repeating-linear-gradient(90deg, color-mix(in srgb, rgba(255,255,255,.012) var(--mount-mix), transparent) 0 1px, transparent 1px 3px),
    linear-gradient(180deg,
      color-mix(in srgb, #202329 var(--mount-mix), transparent),
      color-mix(in srgb, #131519 var(--mount-mix), transparent) 55%,
      color-mix(in srgb, #0b0d10 var(--mount-mix), transparent));
  border-color: color-mix(in srgb, rgba(0, 0, 0, 0.55) var(--mount-mix), transparent);
  box-shadow:
    inset 0 1.5px 0 color-mix(in srgb, rgba(255, 255, 255, 0.07) var(--mount-mix), transparent),
    var(--panel-drop);
}
.panel.tabgroup.deck-tab-active .tabgroup-bar {
  background: rgba(0, 0, 0, 0.32);
  border-color: rgba(255, 255, 255, 0.06);
  box-shadow: inset 0 1px 3px rgba(0, 0, 0, 0.5);
}
/* The chassis is a dark island in BOTH appearances (like the deck itself) —
   pin the tab labels to light-on-dark so a light-theme --dim-text/--accent
   never lands dark-on-dark on the graphite bar. */
.panel.tabgroup.deck-tab-active .tabgroup-tab { color: rgba(231, 233, 238, 0.55); }
.panel.tabgroup.deck-tab-active .tabgroup-tab.active {
  color: #e7e9ee;
  background: rgba(255, 255, 255, 0.10);
}
/* Dock the device into the mount: breathing room instead of a double frame,
   and a shorter drop shadow so nothing gets clipped by the tab body's
   overflow:hidden. */
.tabgroup-body > .deck-panel { padding: 4px 10px 10px !important; }
.tabgroup-body > .deck-panel .deck-device::after {
  box-shadow:
    0 6px 16px rgba(0, 0, 0, 0.5),
    inset 0 1.5px 0 rgba(255, 255, 255, 0.09),
    inset 0 0 0 1px rgba(255, 255, 255, 0.03),
    inset 0 -2px 2px rgba(0, 0, 0, 0.6);
}

/* Auto-launch toast ("Avvio OBS…" / "OBS pronto"). Bottom-centre, glassy. */
.deck-toast {
  position: fixed; left: 50%; bottom: 22px; transform: translate(-50%, 12px);
  z-index: 70; padding: 10px 16px; border-radius: 12px;
  background: rgba(20, 24, 32, .92); color: #e7e9ee; font-size: 13px; font-weight: 600;
  border: 1px solid rgba(255, 255, 255, .12); box-shadow: 0 12px 36px rgba(0, 0, 0, .5);
  opacity: 0; transition: opacity .25s ease, transform .25s ease; pointer-events: none;
}
.deck-toast.visible { opacity: 1; transform: translate(-50%, 0); }
.deck-root {
  display: flex; flex-direction: column; width: 100%; height: 100%;
  /* Per-instance knobs (key footprint preset drives the cap scale + gap).
     --deck-gap MUST mirror KEY_GAPS in deck-model.js (7/10/13 by keysize):
     gridForSize counts how many caps fit using those same gaps, so changing
     one side without the other makes the fit disagree with the drawn grid. */
  --deck-gap: 10px;
  --deck-radius: clamp(9px, calc(var(--deck-key-min, 76px) * .16), 18px);
}
.deck-root[data-keysize="sm"] { --deck-gap: 7px; }
.deck-root[data-keysize="lg"] { --deck-gap: 13px; }

/* ── The device chassis: a matte body holding faceplate + key well + screen ── */
.deck-device {
  position: relative; flex: 1; min-height: 0;
  /* Own stacking context so the chassis layer below (z-index:-1) stays INSIDE the
     device instead of falling behind the dashboard tile that hosts it. */
  isolation: isolate;
  display: flex; flex-direction: column; gap: 10px;
  padding: clamp(10px, calc(var(--deck-key-min, 76px) * .15), 18px);
  border-radius: clamp(18px, calc(var(--deck-radius) + 8px), 26px);
  --plate-edge: rgba(0, 0, 0, .6);
}
/* The chassis paint lives on its own layer rather than on .deck-device itself, so
   ONE opacity can fade the body without fading the bar and the keys it holds —
   which is what `opacity` on the element would do. Every faceplate finish further
   down therefore repaints THIS layer, never the element. */
.deck-device::after {
  content: ''; position: absolute; inset: 0; z-index: -1; pointer-events: none;
  border-radius: inherit;
  opacity: var(--deck-plate-alpha, 1);
  /* Matte moulded plastic body: warm-neutral graphite, top-lit, with a fine
     vertical brushed texture and a darker base for a tangible thickness. */
  background:
    linear-gradient(180deg, rgba(255,255,255,.06), transparent 14%),
    repeating-linear-gradient(90deg, rgba(255,255,255,.014) 0 1px, transparent 1px 3px),
    linear-gradient(180deg, #2a2d34, #1a1c22 52%, #0e1014);
  border: 1px solid var(--plate-edge);
  box-shadow:
    0 24px 50px rgba(0, 0, 0, .6),
    0 4px 14px rgba(0, 0, 0, .45),               /* contact shadow: the body sits ON the dashboard */
    inset 0 1.5px 0 rgba(255, 255, 255, .09),
    inset 0 0 0 1px rgba(255, 255, 255, .03),
    inset 2px 0 3px -2px rgba(255, 255, 255, .05),  /* machined left edge catches the light */
    inset -2px 0 3px -2px rgba(0, 0, 0, .5),        /* right edge falls into shade */
    inset 0 -2px 2px rgba(0, 0, 0, .6);
}
/* Static top-edge sheen so the body reads as a moulded object (no animation).
   Part of the body, so it fades with it. */
.deck-device::before {
  content: ''; position: absolute; inset: 0; border-radius: inherit; pointer-events: none;
  opacity: var(--deck-plate-alpha, 1);
  background: radial-gradient(130% 70% at 50% -12%, rgba(255, 255, 255, .07), transparent 50%);
}

/* ── Faceplate header: profile name · page readout · edit ──────────── */
.deck-bar {
  position: relative; z-index: 1;
  display: flex; align-items: center; gap: 8px; min-height: 22px;
  font-size: 12px; color: var(--text-dim, #aeb4c2);
}
.deck-bar .deck-back {
  width: 26px; height: 26px; border-radius: 8px; border: 1px solid rgba(255,255,255,.1);
  background: linear-gradient(180deg, rgba(255,255,255,.1), rgba(255,255,255,.02));
  color: inherit; cursor: pointer; font-size: 15px;
  display: flex; align-items: center; justify-content: center;
  box-shadow: inset 0 1px 0 rgba(255,255,255,.14), 0 1px 2px rgba(0,0,0,.4);
  transition: transform .12s ease, filter .12s ease;
}
.deck-bar .deck-back:hover { filter: brightness(1.18); }
.deck-bar .deck-back:active { transform: translateY(1px); }
.deck-bar .deck-back[hidden] { display: none; }
/* Profile name styled as a device label, prefixed by a small power LED. */
.deck-bar .deck-crumb {
  display: inline-flex; align-items: center; gap: 8px;
  font-weight: 600; font-size: 11.5px; letter-spacing: .09em; text-transform: uppercase;
  color: var(--text, #e7e9ee);
}
.deck-bar .deck-crumb::before {
  content: ''; width: 7px; height: 7px; border-radius: 50%; flex: 0 0 auto;
  background: var(--accent, #7aa2ff);
  box-shadow: 0 0 8px rgba(var(--accent-rgb, 43,108,255), .9), inset 0 0 2px rgba(255,255,255,.6);
}
.deck-bar .deck-spacer { flex: 1; }
.deck-bar .deck-index {
  font-family: ui-monospace, "SF Mono", "JetBrains Mono", Menlo, Consolas, monospace;
  font-size: 10.5px; font-variant-numeric: tabular-nums; letter-spacing: .06em;
  color: color-mix(in srgb, var(--accent, #7aa2ff) 82%, #cfe0ff);
  padding: 3px 8px; border-radius: 6px;
  background: rgba(6, 9, 15, .8); border: 1px solid rgba(255,255,255,.06);
  box-shadow: inset 0 0 8px rgba(var(--accent-rgb, 43,108,255), .25);
  text-shadow: 0 0 6px rgba(var(--accent-rgb, 43,108,255), .55);
}
.deck-bar .deck-edit {
  width: 26px; height: 26px; border-radius: 8px; border: 1px solid rgba(255,255,255,.1); cursor: pointer;
  background: linear-gradient(180deg, rgba(255,255,255,.1), rgba(255,255,255,.02)); color: var(--text-dim, #aeb4c2);
  display: flex; align-items: center; justify-content: center;
  box-shadow: inset 0 1px 0 rgba(255,255,255,.14), 0 1px 2px rgba(0,0,0,.4);
  transition: transform .12s ease, filter .12s ease, background .15s ease, color .15s ease;
}
.deck-bar .deck-edit svg { width: 14px; height: 14px; fill: currentColor; }
.deck-bar .deck-edit:hover { filter: brightness(1.18); color: var(--text, #e7e9ee); }
.deck-bar .deck-edit:active { transform: translateY(1px); }
.deck-root.is-editing .deck-bar .deck-edit {
  background: linear-gradient(180deg, color-mix(in srgb, var(--accent,#2b6cff) 70%, #fff 0%), var(--accent,#2b6cff));
  border-color: transparent; color: #fff;
  box-shadow: inset 0 1px 0 rgba(255,255,255,.3), 0 2px 10px rgba(var(--accent-rgb,43,108,255),.5);
}

/* ── Profile switcher: crumb-as-button + anchored popover ──────────── */
.deck-bar .deck-crumb-btn {
  gap: 7px; margin: -3px 0 -3px -4px; padding: 3px 7px 3px 4px;
  background: none; border: 0; border-radius: 9px; cursor: pointer;
  -webkit-user-select: none; user-select: none;
  transition: background .15s ease;
}
.deck-bar .deck-crumb-btn:hover { background: rgba(255,255,255,.06); }
.deck-bar .deck-crumb-btn.is-open { background: rgba(255,255,255,.09); }
.deck-crumb-caret { font-size: 8px; opacity: .55; transition: transform .15s ease; flex: 0 0 auto; }
.deck-crumb-btn.is-open .deck-crumb-caret { transform: rotate(180deg); }
/* A long profile name must ELLIPSIZE, not shove the page counter and the edit
   button off the right edge of a narrow deck — the "button on the right of the
   profile is almost out of range" report. The crumb button is allowed to shrink
   and its label truncates; the fixed-width controls keep their place. */
.deck-bar .deck-crumb-btn { min-width: 0; }
.deck-bar .deck-crumb-text { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

/* The popover is portaled to <body> (so the tile's overflow:hidden can't clip it)
   and positioned via JS; fixed + a high z-index keeps it above the dashboard. */
.deck-pmenu {
  position: fixed; top: 0; left: 0; z-index: 80;
  min-width: 184px; max-width: min(86vw, 280px); padding: 8px;
  border-radius: 14px;
  background: rgba(16, 19, 26, .97); border: 1px solid rgba(255, 255, 255, .1);
  box-shadow: 0 18px 44px rgba(0, 0, 0, .6), inset 0 1px 0 rgba(255, 255, 255, .06);
  -webkit-backdrop-filter: blur(12px); backdrop-filter: blur(12px);
  animation: deck-pmenu-in .14s ease;
}
@keyframes deck-pmenu-in { from { opacity: 0; transform: translateY(-6px); } to { opacity: 1; transform: translateY(0); } }
.deck-pmenu-head {
  font-size: 10px; font-weight: 600; letter-spacing: .09em; text-transform: uppercase;
  color: var(--text-dim, #aeb4c2); padding: 2px 8px 7px;
}
/* A second head (the "Profile presets" section) gets a divider above it. */
.deck-pmenu-head ~ .deck-pmenu-head { margin-top: 6px; padding-top: 9px; border-top: 1px solid rgba(255, 255, 255, .08); }
.deck-pmenu-empty { padding: 6px 10px; font-size: 12px; color: var(--text-dim, #aeb4c2); }
.deck-pmenu-list { display: flex; flex-direction: column; gap: 2px; max-height: 192px; overflow-y: auto; }
.deck-pmenu-row { display: flex; align-items: center; gap: 4px; border-radius: 10px; }
.deck-pmenu-row.active { background: rgba(var(--accent-rgb, 43,108,255), .16); }
.deck-pmenu-pick {
  flex: 1; min-width: 0; display: flex; align-items: center; gap: 9px;
  padding: 8px 10px; border: 0; border-radius: 10px; background: none; cursor: pointer;
  color: var(--text, #e7e9ee); font-size: 12.5px; text-align: left;
}
.deck-pmenu-pick:hover { background: rgba(255, 255, 255, .05); }
.deck-pmenu-name { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.deck-pmenu-dot { width: 7px; height: 7px; border-radius: 50%; flex: 0 0 auto; background: rgba(255, 255, 255, .2); }
.deck-pmenu-row.active .deck-pmenu-dot {
  background: var(--accent, #7aa2ff);
  box-shadow: 0 0 7px rgba(var(--accent-rgb, 43,108,255), .9), inset 0 0 2px rgba(255, 255, 255, .6);
}
.deck-pmenu-tools { display: flex; gap: 3px; padding-right: 5px; }
.deck-pmenu-tool {
  width: 26px; height: 26px; border-radius: 7px; border: 1px solid rgba(255, 255, 255, .1); cursor: pointer;
  background: linear-gradient(180deg, rgba(255, 255, 255, .08), rgba(255, 255, 255, .02)); color: var(--text-dim, #aeb4c2);
  display: flex; align-items: center; justify-content: center; font-size: 12px;
  transition: filter .12s ease, color .12s ease;
}
.deck-pmenu-tool svg { width: 12px; height: 12px; fill: currentColor; }
.deck-pmenu-tool:hover { filter: brightness(1.2); color: var(--text, #e7e9ee); }
.deck-pmenu-tool.danger { color: var(--color-danger); }
.deck-pmenu-tool.danger:hover { color: var(--color-danger); background: var(--danger-bg); }
.deck-pmenu-tool:disabled { opacity: .35; cursor: default; filter: none; }
.deck-pmenu-input {
  width: 100%; box-sizing: border-box; padding: 8px 10px; border-radius: 10px;
  border: 1px solid var(--accent, #2b6cff); background: rgba(6, 9, 15, .85);
  color: var(--text, #e7e9ee); font-size: 12.5px; outline: none;
}
.deck-pmenu-add {
  margin-top: 6px; width: 100%; padding: 9px; border-radius: 10px; cursor: pointer;
  font-size: 12px; font-weight: 600; color: var(--text, #e7e9ee);
  border: 1px dashed rgba(255, 255, 255, .18); background: rgba(255, 255, 255, .03);
  transition: background .15s ease, border-color .15s ease;
}
.deck-pmenu-add:hover { background: rgba(255, 255, 255, .07); border-color: rgba(255, 255, 255, .3); }
/* One-tap cleanup of profiles left behind by removed deck tiles (edit mode only). */
.deck-pmenu-clean {
  margin-top: 8px; width: 100%; padding: 9px; border-radius: 10px; cursor: pointer;
  font-size: 12px; font-weight: 600; color: var(--color-danger);
  border: 1px solid rgba(255, 107, 107, .35); background: rgba(255, 107, 107, .08);
  transition: background .15s ease, border-color .15s ease, color .15s ease;
}
.deck-pmenu-clean:hover { background: var(--danger-bg); border-color: rgba(var(--danger-rgb), .6); color: var(--color-danger); }

/* ── Recessed key well: a carved bezel around the key area ─────────── */
/* container-type:size makes the well a query context so the grid can derive a
   perfectly square cap edge from its own content box (cqw/cqh) — no JS measuring. */
.deck-well {
  position: relative; flex: 1; min-height: 0; z-index: 1;
  container-type: size;
  border-radius: clamp(12px, var(--deck-radius), 18px); padding: var(--deck-gap);
  /* The recess fades IN PLACE instead of on its own layer, unlike the chassis
     above: both pseudo-elements of .deck-well are already taken by the comic
     theme's animated panel, and adding a real element would put a node between
     the well and .deck-well-bg for nothing. color-mix(<c> 100%, transparent) is
     exactly <c>, so with the knob untouched this is byte-identical to the paint
     it replaces. */
  --well-mix: calc(var(--deck-well-alpha, 1) * 100%);
  background: linear-gradient(180deg,
    color-mix(in srgb, #070810 var(--well-mix), transparent),
    color-mix(in srgb, #0a0c12 var(--well-mix), transparent));
  box-shadow:
    inset 0 3px 12px color-mix(in srgb, rgba(0, 0, 0, .85) var(--well-mix), transparent),   /* deep recess */
    inset 0 -1px 0 color-mix(in srgb, rgba(255, 255, 255, .04) var(--well-mix), transparent), /* lit lower lip of the groove */
    0 1px 0 color-mix(in srgb, rgba(255, 255, 255, .05) var(--well-mix), transparent);        /* highlight where the body meets the recess */
}
/* Optional free-form picture behind the key grid (below the caps, z 0). */
.deck-well-bg {
  position: absolute; inset: 0; z-index: 0; pointer-events: none;
  border-radius: clamp(12px, var(--deck-radius), 18px);
  background-position: center;
}
.deck-well-bg::after {
  content: ''; position: absolute; inset: 0; border-radius: inherit;
  background: #05070d; opacity: var(--deck-well-dim, .3);
}

/* Square key caps (real Stream Deck). The cap edge is the largest square that fits
   the well at the current column/row count: we take the well's content box via
   container-query units, subtract the inter-key gaps, divide by the count on each
   axis, and pick the limiting axis with min(). The grid is then sized to those
   fixed square tracks and centred — so the caps are always square AND, because the
   now-playing screen reserves its space below, the cap size never shifts when music
   starts or stops. (A plain 1fr fallback keeps it sane on the rare browser without
   size container queries.) */
.deck-grid {
  position: relative; z-index: 1;
  width: 100%; height: 100%; display: grid; gap: var(--deck-gap); min-height: 0;
  --deck-cell: min(
    (100cqw - (var(--deck-cols, 3) - 1) * var(--deck-gap)) / var(--deck-cols, 3),
    (100cqh - (var(--deck-rows, 2) - 1) * var(--deck-gap)) / var(--deck-rows, 2)
  );
  grid-template-columns: repeat(var(--deck-cols, 3), var(--deck-cell, 1fr));
  grid-template-rows: repeat(var(--deck-rows, 2), var(--deck-cell, 1fr));
  justify-content: center; align-content: center;
}
@supports not (width: 1cqw) {
  .deck-grid {
    grid-template-columns: repeat(var(--deck-cols, 3), 1fr);
    grid-template-rows: repeat(var(--deck-rows, 2), 1fr);
  }
}

/* ── The LCD key cap ──────────────────────────────────────────────── */
.deck-key {
  position: relative; min-height: 0; min-width: 0;
  /* Own stacking context so the backdrop-image / animation layers (z-index:-1)
     paint ABOVE the cap's own background instead of falling behind it. Without
     this the cap face is opaque and the backdrop never shows. */
  isolation: isolate;
  border-radius: var(--deck-radius);
  border: 1px solid #000;
  /* Near-black LCD cap with a softly top-lit face. */
  background:
    radial-gradient(135% 90% at 50% -16%, rgba(255,255,255,.14), transparent 52%),
    linear-gradient(180deg, #1d212a, #14171f 58%, #0c0e14);
  display: flex; flex-direction: column; align-items: center; justify-content: center;
  gap: 5px; padding: 6px; cursor: pointer;
  color: var(--text, #e7e9ee); text-align: center;
  font-size: clamp(8px, calc(var(--deck-cell, var(--deck-key-min, 76px)) * .135), 12px);
  overflow: hidden;
  box-shadow:
    inset 0 1px 0 rgba(255,255,255,.14),
    inset 0 -7px 14px rgba(0,0,0,.55),
    0 3px 7px rgba(0,0,0,.55),
    0 1px 0 rgba(0,0,0,.7);
  transition: transform .11s cubic-bezier(.2,.8,.2,1), box-shadow .15s ease, filter .15s ease;
  -webkit-touch-callout: none; -webkit-user-select: none; user-select: none; touch-action: manipulation;
}
/* Convex glass shine — a curved highlight pooled at the top of the cap. */
.deck-key::before {
  content: ''; position: absolute; inset: 0; border-radius: inherit; pointer-events: none;
  background:
    radial-gradient(120% 75% at 50% -25%, rgba(255,255,255,.22), transparent 56%),
    linear-gradient(0deg, rgba(0,0,0,.28), transparent 42%);
  opacity: .9;
}
/* Specular glint: a light band sweeps across the glass as the cap springs back
   from a press (JS toggles .is-glint one-shot on release; cleared on
   animationend). Transform-only — same technique as the anim-shift ambient,
   clipped by the cap's overflow:hidden — and the pseudo-element exists ONLY
   while .is-glint is on, so idle keys carry no extra layer. It sits above
   every face layer (photo caps included) so the key reads as glazed. */
.deck-key.is-glint::after {
  content: ''; position: absolute; top: -30%; bottom: -30%; left: 0; width: 55%;
  z-index: 3; pointer-events: none;
  background: linear-gradient(105deg,
    transparent 0%, rgba(255, 255, 255, .26) 45%, rgba(255, 255, 255, .07) 60%, transparent 100%);
  transform: translateX(-220%) skewX(-14deg);   /* starts fully off the left — invisible */
  animation: deck-key-glint .5s cubic-bezier(.3, .55, .3, 1);
  will-change: transform;
}
@keyframes deck-key-glint {
  from { transform: translateX(-220%) skewX(-14deg); }
  to   { transform: translateX(400%)  skewX(-14deg); }  /* fully off the right — invisible */
}
/* Matte caps don't glaze: the flat theme strips the glass sheen, so it strips
   the glint too. Reduced-motion and Performance Mode kill it like every effect. */
.deck-root[data-capstyle="flat"] .deck-key.is-glint::after { display: none; }
@media (prefers-reduced-motion: reduce) { .deck-key.is-glint::after { display: none; } }
body.perf-mode .deck-key.is-glint::after { display: none; }
/* Icon + label paint above the optional backdrop-image / animation layers,
   which sit at z-index:-1 (above the cap face, below content) so the classic
   paint order — glyph and glass sheen over the label — is preserved untouched.
   --ico-scale is the per-key icon-size preset (S/M/L, set via data-icosize). */
.deck-key .deck-ico {
  font-size: calc(clamp(17px, calc(var(--deck-cell, var(--deck-key-min, 76px)) * .36), 38px) * var(--ico-scale, 1));
  line-height: 1; filter: drop-shadow(0 1px 3px rgba(0,0,0,.7));
}
.deck-key .deck-ico img {
  width: calc(clamp(18px, calc(var(--deck-cell, var(--deck-key-min, 76px)) * .38), 40px) * var(--ico-scale, 1));
  height: calc(clamp(18px, calc(var(--deck-cell, var(--deck-key-min, 76px)) * .38), 40px) * var(--ico-scale, 1));
  object-fit: contain;
  filter: drop-shadow(0 1px 2px rgba(0,0,0,.6));
}
/* Built-in vector icons tint to the cap's text colour and scale with the key. */
.deck-key .deck-ico.is-builtin { display: flex; }
.deck-key .deck-ico.is-builtin svg {
  width: calc(clamp(18px, calc(var(--deck-cell, var(--deck-key-min, 76px)) * .4), 42px) * var(--ico-scale, 1));
  height: calc(clamp(18px, calc(var(--deck-cell, var(--deck-key-min, 76px)) * .4), 42px) * var(--ico-scale, 1));
  fill: currentColor; filter: drop-shadow(0 1px 2px rgba(0,0,0,.6));
}
.deck-key[data-icosize="sm"] { --ico-scale: .72; }
.deck-key[data-icosize="lg"] { --ico-scale: 1.35; }
.deck-key .deck-label {
  max-width: 100%; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
  font-weight: 600; letter-spacing: .01em; text-shadow: 0 1px 2px rgba(0,0,0,.7);
}
/* Per-key label styling: weight, size preset, position (top / hidden). */
.deck-key .deck-label.is-bold { font-weight: 800; }
.deck-key[data-labelsize="sm"] .deck-label { font-size: .82em; }
.deck-key[data-labelsize="lg"] .deck-label { font-size: 1.24em; }
.deck-key[data-labelpos="top"] .deck-label { order: -1; }
.deck-key[data-labelpos="hidden"] .deck-label { display: none; }
/* Hover lift (pointer devices only) + press depression for tactile feel.
   Unlit empty slots stay inert — they're not pressable outside edit mode. */
@media (hover: hover) {
  .deck-key:hover { transform: translateY(-1px); filter: brightness(1.12); }
  .deck-key.is-empty:not(.is-add):hover { transform: none; filter: none; }
}
.deck-key:active, .deck-key.is-pressed {
  transform: translateY(2px) scale(.92);
  filter: brightness(.86);
  box-shadow:
    inset 0 3px 9px rgba(0,0,0,.7),
    inset 0 1px 0 rgba(255,255,255,.05),
    0 1px 2px rgba(0,0,0,.35);
}

/* Accent-tinted caps: JS sets --key-accent; we tint the face + add a rim glow
   so a coloured key reads as a lit LCD instead of a flat fill. */
.deck-key.has-accent {
  background:
    linear-gradient(180deg, rgba(255,255,255,.12), transparent 36%),
    linear-gradient(180deg,
      color-mix(in srgb, var(--key-accent, #2b6cff) 42%, #1b1f27),
      color-mix(in srgb, var(--key-accent, #2b6cff) 18%, #14171d));
  border-color: color-mix(in srgb, var(--key-accent, #2b6cff) 55%, #000);
  box-shadow:
    inset 0 1px 0 rgba(255,255,255,.22),
    inset 0 -3px 8px rgba(0,0,0,.5),
    0 0 12px color-mix(in srgb, var(--key-accent, #2b6cff) 45%, transparent),
    0 2px 4px rgba(0,0,0,.45);
}

/* Two-colour caps: JS sets --key-accent2 + data-grad. The face gradient lives in
   --kg-face so the direction variants (and the cap-style themes below) can swap
   just the shape while the lit-LCD blend (darkened accents, sheen, rim) stays. */
.deck-key.has-gradient {
  --kg-c1: color-mix(in srgb, var(--key-accent, #2b6cff) 50%, #181b23);
  --kg-c2: color-mix(in srgb, var(--key-accent2, #7aa2ff) 46%, #10131a);
  --kg-face: linear-gradient(135deg, var(--kg-c1), var(--kg-c2));
  background:
    linear-gradient(180deg, rgba(255,255,255,.12), transparent 36%),
    var(--kg-face);
  border-color: color-mix(in srgb, var(--key-accent, #2b6cff) 45%, #000);
  box-shadow:
    inset 0 1px 0 rgba(255,255,255,.22),
    inset 0 -3px 8px rgba(0,0,0,.5),
    0 0 10px color-mix(in srgb, var(--key-accent, #2b6cff) 30%, transparent),
    0 0 10px color-mix(in srgb, var(--key-accent2, #7aa2ff) 24%, transparent),
    0 2px 4px rgba(0,0,0,.45);
}
.deck-key.has-gradient[data-grad="v"] { --kg-face: linear-gradient(180deg, var(--kg-c1), var(--kg-c2)); }
.deck-key.has-gradient[data-grad="r"] { --kg-face: radial-gradient(135% 135% at 30% 18%, var(--kg-c1), var(--kg-c2)); }

/* Backdrop picture: a layer under the icon/label. The ::after scrim keeps the
   content readable (opacity = the user's dim setting); with an accent the scrim
   tints, so the photo reads as backlit by the key colour instead of just dark. */
.deck-key .deck-key-bgimg {
  position: absolute; inset: 0; z-index: -1; border-radius: inherit;
  overflow: hidden; pointer-events: none;
}
.deck-key .deck-key-bgimg img {
  width: 100%; height: 100%; object-fit: cover; display: block;
  filter: blur(var(--key-blur, 0px));
}
/* When blurred, scale the image up a touch so the softened edges stay outside the
   cap instead of showing a faded halo against the border. */
.deck-key.has-bgblur .deck-key-bgimg img { transform: scale(1.09); }
.deck-key .deck-key-bgimg::after {
  content: ''; position: absolute; inset: 0;
  background: #06080c; opacity: var(--key-dim, .35);
}
.deck-key.has-accent .deck-key-bgimg::after {
  background: color-mix(in srgb, var(--key-accent, #2b6cff) 45%, #06080c);
}
.deck-key.has-gradient .deck-key-bgimg::after {
  background: linear-gradient(135deg,
    color-mix(in srgb, var(--key-accent, #2b6cff) 55%, #06080c),
    color-mix(in srgb, var(--key-accent2, #7aa2ff) 55%, #06080c));
}
/* A key whose face IS a photo keeps its title scrim readable over the picture. */
.deck-key.has-bgimg .deck-label { text-shadow: 0 1px 3px rgba(0, 0, 0, .9); }

/* ── Ambient cap animations (opt-in per key; cheap opacity/transform layers).
   body.perf-mode pauses these globally like every other dashboard animation. ── */
.deck-key .deck-key-anim {
  position: absolute; inset: 0; z-index: -1; border-radius: inherit;
  overflow: hidden; pointer-events: none;
}
/* When the cap face IS a full-bleed photo, lift the ambient layer above it so
   the breathe rim / sheen is still visible instead of hiding behind the image. */
.deck-key.has-image .deck-key-anim { z-index: 2; }
/* Breathe: the accent rim swells and relaxes. */
.deck-key.anim-breathe .deck-key-anim {
  box-shadow:
    inset 0 0 0 1.5px color-mix(in srgb, var(--key-accent, var(--accent, #7aa2ff)) 85%, #fff),
    inset 0 0 18px color-mix(in srgb, var(--key-accent, var(--accent, #7aa2ff)) 55%, transparent);
  animation: deck-key-breathe 2.8s ease-in-out infinite;
  will-change: opacity;
}
@keyframes deck-key-breathe { 0%, 100% { opacity: .22; } 50% { opacity: 1; } }
/* Shift ("Sheen"): a soft light band sweeps across the face (transform-only). It
   starts fully off the left and ends fully off the right — the parent clips
   (overflow:hidden) — so the band is invisible at BOTH ends of the cycle and the
   loop restart can't be seen (no hard cut). Previously it slid only ±26%, leaving
   the band visible at the wrap point, which read as an abrupt reset. */
.deck-key.anim-shift .deck-key-anim::before {
  content: ''; position: absolute; top: -40%; bottom: -40%; left: 0; width: 45%;
  background: linear-gradient(90deg,
    transparent 0%,
    color-mix(in srgb, var(--key-accent2, var(--key-accent, #7aa2ff)) 36%, transparent) 45%,
    color-mix(in srgb, var(--key-accent, var(--accent, #7aa2ff)) 28%, transparent) 55%,
    transparent 100%);
  transform: translateX(-220%) skewX(-12deg);
  animation: deck-key-shift 5.5s linear infinite;
  will-change: transform;
}
@keyframes deck-key-shift {
  from { transform: translateX(-220%) skewX(-12deg); }  /* off the left — invisible */
  to   { transform: translateX(460%)  skewX(-12deg); }  /* off the right — invisible */
}
/* Reduced-motion and Performance Mode remove the ambient layer entirely — merely
   stopping the keyframes would freeze the breathe rim at full brightness or leave
   the sheen band as a permanent diagonal streak, which is worse than no effect. */
@media (prefers-reduced-motion: reduce) {
  .deck-key .deck-key-anim { opacity: 0; }
}
body.perf-mode .deck-key .deck-key-anim { opacity: 0; }
/* Same treatment for every other freeze state. These two loops (breathe on the
   element, sheen on its ::before) animate CONTINUOUSLY, so leaving them out of a
   freeze set keeps the compositor producing frames for the whole dashboard.
   They were missing from all four of these; the hybrid-GPU toggle even promised
   "key decorations stay still" while this file had no low-power-gpu rule at all.
   `animation: none` is required alongside `opacity: 0` (verified live): the
   breathe keyframes animate opacity, and a running animation overrides the
   static declaration in the cascade, so opacity: 0 alone changes nothing. */
body.overlay-frozen .deck-key .deck-key-anim,
body.ambient-idle .deck-key .deck-key-anim,
body.game-mode .deck-key .deck-key-anim,
body.low-power-gpu .deck-key .deck-key-anim { animation: none; opacity: 0; }
body.overlay-frozen .deck-key.anim-shift .deck-key-anim::before,
body.ambient-idle .deck-key.anim-shift .deck-key-anim::before,
body.game-mode .deck-key.anim-shift .deck-key-anim::before,
body.low-power-gpu .deck-key.anim-shift .deck-key-anim::before { animation: none; }

.deck-key.is-folder {
  border-color: rgba(180, 150, 230, .4);
  box-shadow:
    inset 0 1px 0 rgba(255,255,255,.16), inset 0 -3px 8px rgba(0,0,0,.55),
    0 0 10px rgba(150,110,230,.25), 0 2px 4px rgba(0,0,0,.45);
}
.deck-key.is-folder .deck-ico { text-shadow: 0 0 10px rgba(170,130,240,.5); }

/* Empty slot = an UNLIT key: same moulded cap, screen off. Reads like real
   Stream Deck hardware (dark keys waiting to be assigned) instead of a dashed
   placeholder ghost; the edit-mode "+" cell keeps its dashed add affordance. */
.deck-key.is-empty {
  background:
    radial-gradient(120% 80% at 50% -12%, rgba(255, 255, 255, .05), transparent 50%),
    linear-gradient(180deg, #101319, #0a0c11 58%, #07080c);
  border: 1px solid rgba(0, 0, 0, .85);
  box-shadow:
    inset 0 1px 0 rgba(255, 255, 255, .05),
    inset 0 -5px 10px rgba(0, 0, 0, .5),
    0 2px 5px rgba(0, 0, 0, .5);
  opacity: 1; cursor: default;
}
.deck-key.is-empty::before { opacity: .3; }   /* faint glass over the dark cap */

/* ── Footer: tactile page arrows + LED dots ───────────────────────── */
.deck-foot { position: relative; z-index: 1; display: flex; align-items: center; justify-content: center; gap: 12px; min-height: 20px; }
.deck-arrow {
  width: 24px; height: 22px; border-radius: 7px; cursor: pointer;
  border: 1px solid rgba(255,255,255,.1); color: var(--text-dim, #9aa1b1); font-size: 13px;
  background: linear-gradient(180deg, rgba(255,255,255,.08), rgba(255,255,255,.015));
  box-shadow: inset 0 1px 0 rgba(255,255,255,.1), 0 1px 2px rgba(0,0,0,.4);
  display: flex; align-items: center; justify-content: center;
  transition: transform .12s ease, filter .12s ease;
}
.deck-arrow:hover:not([disabled]) { filter: brightness(1.25); color: var(--text, #e7e9ee); }
.deck-arrow:active:not([disabled]) { transform: translateY(1px); }
.deck-arrow[disabled] { opacity: .3; cursor: default; }
/* Edit mode: a dragged cap held over an arrow moves to that page on release. Same
   accent language as a slot drop target, on the arrow's own 7px radius. */
.deck-arrow.is-drop {
  color: var(--text, #e7e9ee);
  border-color: rgba(var(--accent-rgb, 43, 108, 255), .95);
  background: rgba(var(--accent-rgb, 43, 108, 255), .28);
  box-shadow: inset 0 1px 0 rgba(255,255,255,.1), 0 0 12px rgba(var(--accent-rgb, 43, 108, 255), .5);
}
.deck-dots { display: flex; gap: 6px; align-items: center; }
.deck-dots i {
  width: 6px; height: 6px; border-radius: 50%; background: rgba(255,255,255,.18);
  transition: width .2s ease, background .2s ease, box-shadow .2s ease;
}
.deck-dots i.active {
  background: var(--accent, #7aa2ff); width: 16px; border-radius: 3px;
  box-shadow: 0 0 8px rgba(var(--accent-rgb, 43,108,255), .8);
}

/* OBS scene preview: the on-air scene thumbnail fills the host key, under a dark
   veil so the icon and label stay readable. !important beats the inline accent
   gradient; clearing the class/var restores the accent. */
.deck-key.has-preview {
  background-image: linear-gradient(rgba(8, 10, 14, .42), rgba(8, 10, 14, .66)), var(--deck-preview) !important;
  background-size: cover, cover !important;
  background-position: center, center !important;
  border-color: rgba(255, 255, 255, .22);
}
.deck-key.has-preview .deck-label { text-shadow: 0 1px 3px rgba(0, 0, 0, .8); }

/* Image caps: the picture fills the whole key (Stream Deck style) and the title
   sits in a readable gradient scrim along the bottom, so text stays legible over
   any artwork instead of disappearing into it. */
.deck-key.has-image .deck-ico { position: absolute; inset: 0; filter: none; }
.deck-key.has-image .deck-ico img {
  width: 100%; height: 100%; max-width: none; max-height: none;
  object-fit: cover; border-radius: inherit; filter: none;
}
.deck-key.has-image .deck-label {
  position: absolute; left: 0; right: 0; bottom: 0; z-index: 2;
  padding: 3px 6px 4px;
  background: linear-gradient(0deg, rgba(6, 8, 12, .85), rgba(6, 8, 12, .45) 72%, transparent);
  text-shadow: 0 1px 3px rgba(0, 0, 0, .95);
}
/* An empty title shouldn't render an empty scrim strip. */
.deck-key .deck-label:empty { display: none; }

/* Image fit modes (chosen per key in the editor):
   · cover   → full-bleed (the .has-image default above)
   · contain → the whole picture, padded, centred inside the full-bleed area
   · small   → a compact centred icon (no .has-image): rendered like a glyph with the
               title beneath it, sized to about half the cap. */
.deck-key.has-image.fit-contain .deck-ico img {
  object-fit: contain; box-sizing: border-box; padding: 9%;
}
.deck-key .deck-ico.is-img-small img {
  width: calc(clamp(22px, calc(var(--deck-cell, var(--deck-key-min, 76px)) * .5), 52px) * var(--ico-scale, 1));
  height: calc(clamp(22px, calc(var(--deck-cell, var(--deck-key-min, 76px)) * .5), 52px) * var(--ico-scale, 1));
  object-fit: contain;
}
/* Label-on-top over a full-bleed picture: the scrim flips to the upper edge. */
.deck-key.has-image[data-labelpos="top"] .deck-label {
  top: 0; bottom: auto;
  padding: 4px 6px 3px;
  background: linear-gradient(180deg, rgba(6, 8, 12, .85), rgba(6, 8, 12, .45) 72%, transparent);
}

/* ── Whole-device looks: cap material (data-capstyle on .deck-root) ─────────
   'lcd' is the classic default above; each theme restyles the cap face while
   inline per-key colours (accent/gradient vars) keep working through it. */

/* Flat: quiet matte tiles — no bevel, no gloss, colour as a plain field. */
.deck-root[data-capstyle="flat"] .deck-key {
  background: #171a21;
  border-color: rgba(255, 255, 255, .07);
  box-shadow: none;
}
.deck-root[data-capstyle="flat"] .deck-key::before { display: none; }
.deck-root[data-capstyle="flat"] .deck-key.has-accent {
  background: color-mix(in srgb, var(--key-accent, #2b6cff) 32%, #14171d);
  border-color: color-mix(in srgb, var(--key-accent, #2b6cff) 42%, #14171d);
  box-shadow: none;
}
.deck-root[data-capstyle="flat"] .deck-key.has-gradient {
  background: var(--kg-face);
  box-shadow: none;
}
.deck-root[data-capstyle="flat"] .deck-key:active,
.deck-root[data-capstyle="flat"] .deck-key.is-pressed {
  transform: scale(.94); filter: brightness(.82); box-shadow: none;
}

/* Neon: near-black glass with a lit accent rim — icon and label glow to match.
   Un-tinted keys fall back to the dashboard accent so the deck reads as one rig. */
.deck-root[data-capstyle="neon"] .deck-key {
  --neon: var(--key-accent, var(--accent, #7aa2ff));
  background: linear-gradient(180deg, #0b0d13, #07080d);
  border-color: color-mix(in srgb, var(--neon) 62%, #0a0c12);
  color: color-mix(in srgb, var(--neon) 72%, #eef1f7);
  box-shadow:
    0 0 10px color-mix(in srgb, var(--neon) 28%, transparent),
    inset 0 0 12px color-mix(in srgb, var(--neon) 13%, transparent);
}
.deck-root[data-capstyle="neon"] .deck-key::before { display: none; }
.deck-root[data-capstyle="neon"] .deck-key .deck-ico { filter: drop-shadow(0 0 6px color-mix(in srgb, var(--neon) 55%, transparent)); }
.deck-root[data-capstyle="neon"] .deck-key .deck-label { text-shadow: 0 0 8px color-mix(in srgb, var(--neon) 45%, transparent); }
.deck-root[data-capstyle="neon"] .deck-key.has-gradient {
  border-color: color-mix(in srgb, var(--key-accent, #2b6cff) 55%, #0a0c12);
  box-shadow:
    0 0 10px color-mix(in srgb, var(--key-accent, #2b6cff) 26%, transparent),
    0 0 10px color-mix(in srgb, var(--key-accent2, #7aa2ff) 22%, transparent),
    inset 0 0 12px color-mix(in srgb, var(--key-accent2, #7aa2ff) 12%, transparent);
}
.deck-root[data-capstyle="neon"] .deck-key.is-empty { border-color: rgba(255, 255, 255, .08); box-shadow: none; color: var(--text, #e7e9ee); }
/* Press depression: the theme's resting box-shadow out-ranks the base
   `.deck-key:active` rule on specificity, so restate it per theme. */
.deck-root[data-capstyle="neon"] .deck-key:active,
.deck-root[data-capstyle="neon"] .deck-key.is-pressed {
  transform: translateY(2px) scale(.92); filter: brightness(.86);
  box-shadow: inset 0 3px 9px rgba(0, 0, 0, .7);
}

/* Glass: frosted translucent caps in the dashboard's Liquid Glass language
   (translucent fills + highlights only — no per-key backdrop blur). */
.deck-root[data-capstyle="glass"] .deck-key {
  background: linear-gradient(180deg, rgba(255, 255, 255, .15), rgba(255, 255, 255, .05) 42%, rgba(255, 255, 255, .02));
  border-color: rgba(255, 255, 255, .17);
  box-shadow:
    inset 0 1px 0 rgba(255, 255, 255, .3),
    inset 0 -8px 16px rgba(0, 0, 0, .22),
    0 4px 10px rgba(0, 0, 0, .35);
}
.deck-root[data-capstyle="glass"] .deck-key.has-accent {
  background:
    linear-gradient(180deg, rgba(255, 255, 255, .16), transparent 42%),
    linear-gradient(180deg,
      color-mix(in srgb, var(--key-accent, #2b6cff) 38%, rgba(255, 255, 255, .07)),
      color-mix(in srgb, var(--key-accent, #2b6cff) 20%, rgba(10, 12, 18, .3)));
  border-color: color-mix(in srgb, var(--key-accent, #2b6cff) 42%, rgba(255, 255, 255, .16));
}
.deck-root[data-capstyle="glass"] .deck-key.has-gradient {
  background: linear-gradient(180deg, rgba(255, 255, 255, .2), transparent 45%), var(--kg-face);
  border-color: color-mix(in srgb, var(--key-accent, #2b6cff) 42%, rgba(255, 255, 255, .16));
}
.deck-root[data-capstyle="glass"] .deck-key:active,
.deck-root[data-capstyle="glass"] .deck-key.is-pressed {
  transform: translateY(2px) scale(.92); filter: brightness(.9);
  box-shadow: inset 0 3px 9px rgba(0, 0, 0, .4);
}

/* Empty slots stay UNLIT under every cap theme — the theme selectors above
   out-rank the base .is-empty rule on specificity, so the dark powered-off cap
   is restated here (quieter, to match each theme's flatter material). The
   edit-mode "+" cell keeps its dashed add affordance below. */
.deck-root[data-capstyle="flat"] .deck-key.is-empty,
.deck-root[data-capstyle="neon"] .deck-key.is-empty,
.deck-root[data-capstyle="glass"] .deck-key.is-empty {
  background: linear-gradient(180deg, #0e1016, #090b10);
  border-color: rgba(255, 255, 255, .05);
  box-shadow: none;
  color: var(--text, #e7e9ee);
}
.deck-root[data-capstyle="flat"] .deck-key.is-add,
.deck-root[data-capstyle="neon"] .deck-key.is-add,
.deck-root[data-capstyle="glass"] .deck-key.is-add {
  background: rgba(var(--accent-rgb, 43, 108, 255), .05);
  border-color: rgba(var(--accent-rgb, 43, 108, 255), .45);
  box-shadow: inset 0 0 12px rgba(var(--accent-rgb, 43, 108, 255), .12);
  color: color-mix(in srgb, var(--accent, #7aa2ff) 75%, #cfe0ff);
}

/* ── Cap shape (data-shape on .deck-root) ─────────────────────────── */
/* Square: tighter corners across caps + well (the radius token drives both). */
.deck-root[data-shape="square"] { --deck-radius: clamp(4px, calc(var(--deck-key-min, 76px) * .06), 7px); }
/* Circle: round caps; the delete badge and gesture markers move inward so the
   rim can't clip them. */
.deck-root[data-shape="circle"] .deck-key { border-radius: 50%; }
.deck-root[data-shape="circle"] .deck-key-del { top: 9%; left: 9%; }
.deck-root[data-shape="circle"] .deck-key-trig { right: 13%; bottom: 11%; }
.deck-root[data-shape="circle"] .deck-key .deck-label { max-width: 78%; }
.deck-root[data-shape="circle"] .deck-key.has-image .deck-label { left: 11%; right: 11%; }

/* ── Faceplate finish (data-plate on .deck-root → the device chassis) ── */
/* Carbon: diagonal twill weave under the same top light as graphite. */
.deck-root[data-plate="carbon"] .deck-device::after {
  background:
    linear-gradient(180deg, rgba(255, 255, 255, .05), transparent 16%),
    repeating-linear-gradient(45deg, rgba(255, 255, 255, .05) 0 2px, transparent 2px 5px),
    repeating-linear-gradient(-45deg, rgba(0, 0, 0, .42) 0 2px, transparent 2px 5px),
    linear-gradient(180deg, #17191e, #0d0f13);
}
/* Steel: brushed metal — fine horizontal grain with a cooler, lighter body. */
.deck-root[data-plate="steel"] .deck-device { --plate-edge: rgba(0, 0, 0, .5); }
.deck-root[data-plate="steel"] .deck-device::after {
  background:
    linear-gradient(180deg, rgba(255, 255, 255, .1), transparent 20%),
    repeating-linear-gradient(0deg, rgba(255, 255, 255, .028) 0 1px, transparent 1px 3px),
    linear-gradient(180deg, #383c45, #24272e 55%, #17191f);
}
/* Midnight: pure stealth slab — no texture, near-black. */
.deck-root[data-plate="midnight"] .deck-device::after {
  background: linear-gradient(180deg, #0c0e13, #08090d);
  box-shadow: 0 24px 50px rgba(0, 0, 0, .55), inset 0 1px 0 rgba(255, 255, 255, .05);
}
/* None: the chassis disappears — bare keys floating on the dashboard. It is the
   end point of the transparency slider, reached by dropping the whole layer. */
.deck-root[data-plate="none"] .deck-device { padding: 2px; }
.deck-root[data-plate="none"] .deck-device::after { display: none; }
.deck-root[data-plate="none"] .deck-device::before { display: none; }
.deck-root[data-plate="none"] .deck-well { background: transparent; box-shadow: none; }

/* A failed action (path not found, OBS offline, …) flashes the key red + shakes
   briefly so failures aren't silent. */
.deck-key.has-error { animation: deck-key-error .55s ease both; }
@keyframes deck-key-error {
  0%, 100% { box-shadow: none; transform: none; }
  10%, 55% { box-shadow: inset 0 0 0 2px rgba(255, 59, 48, .95), 0 0 12px rgba(255, 59, 48, .45); }
  20% { transform: translateX(-3px); }
  35% { transform: translateX(3px); }
  70% { transform: translateX(-2px); }
  85% { transform: translateX(2px); }
}

/* ── Edit mode: keys jiggle like a device being reconfigured ───────── */
.deck-key.is-editing {
  cursor: pointer; will-change: transform;
  animation: deck-jiggle .42s ease-in-out infinite alternate;
}
/* Desync the wobble so the grid feels hand-placed, not metronomic. */
.deck-key.is-editing:nth-child(2n)   { animation-delay: -.11s; animation-duration: .47s; }
.deck-key.is-editing:nth-child(3n)   { animation-delay: -.23s; animation-duration: .39s; }
.deck-key.is-editing:nth-child(4n+1) { animation-delay: -.31s; animation-duration: .44s; }
@keyframes deck-jiggle {
  from { transform: rotate(-.7deg) translateY(-.5px); }
  to   { transform: rotate(.7deg) translateY(.5px); }
}
.deck-key.is-editing:active { animation: none; }

/* ── Edit-mode quick actions: delete badge + drag-to-reorder ───────────── */
/* In edit mode a placed key is a DRAG HANDLE, not a pan surface. The dashboard
   pager is a native horizontal scroll-snap container, so with the normal
   `touch-action: manipulation` the browser would scroll to the next page the
   instant you start dragging a key (stealing the gesture, so the reorder never
   starts). `touch-action: none` keeps the touch on the key and lets our pointer
   drag run. (Outside edit mode the keys keep `manipulation` so a swipe still pans.) */
.deck-key.is-editing { touch-action: none; }
/* iOS-style delete badge in the top-left corner of every placed key. */
.deck-key-del {
  position: absolute; top: 5px; left: 5px; z-index: 6; padding: 0;
  width: 20px; height: 20px; border-radius: 50%; cursor: pointer;
  display: flex; align-items: center; justify-content: center;
  color: #fff; background: linear-gradient(180deg, #ff5b52, #e0352b);
  border: 1.5px solid rgba(255, 255, 255, .9);
  box-shadow: 0 2px 6px rgba(0, 0, 0, .55);
  animation: deck-del-in .16s ease both;
}
.deck-key-del svg { width: 11px; height: 11px; }
.deck-key-del:hover { filter: brightness(1.1); }
.deck-key-del:active { transform: scale(.86); }
@keyframes deck-del-in { from { opacity: 0; transform: scale(.3); } to { opacity: 1; transform: none; } }

/* The picked-up cap fades in place (its clone follows the finger); no jiggle. */
.deck-key.is-dragging { opacity: .26; animation: none !important; }
.deck-key.is-dragging .deck-key-del { display: none; }

/* The slot currently under the dragged cap — ready to swap (or move into, if empty). */
.deck-key.is-drop {
  opacity: 1;
  outline: 2px dashed rgba(var(--accent-rgb, 43, 108, 255), .95); outline-offset: -3px;
  box-shadow: inset 0 0 18px rgba(var(--accent-rgb, 43, 108, 255), .5),
              0 0 12px rgba(var(--accent-rgb, 43, 108, 255), .4);
}

/* The floating clone dragged across the grid — lifted off the device. */
.deck-drag-clone {
  z-index: 90; pointer-events: none; animation: none !important;
  transform: scale(1.08) rotate(-2deg);
  box-shadow: 0 18px 40px rgba(0, 0, 0, .6); opacity: .96;
}
.deck-key.is-add {
  color: color-mix(in srgb, var(--accent, #7aa2ff) 75%, #cfe0ff);
  font-size: clamp(20px, calc(var(--deck-cell, var(--deck-key-min, 76px)) * .4), 40px); font-weight: 300;
  background: rgba(var(--accent-rgb, 43,108,255), .05);
  border: 1px dashed rgba(var(--accent-rgb, 43,108,255), .45);
  box-shadow: inset 0 0 12px rgba(var(--accent-rgb, 43,108,255), .12);
  align-items: center; justify-content: center; opacity: 1;
  animation: deck-add-pulse 2.4s ease-in-out infinite;
}
.deck-key.is-add::before { display: none; }
@keyframes deck-add-pulse {
  0%, 100% { box-shadow: inset 0 0 12px rgba(var(--accent-rgb, 43,108,255), .1); border-color: rgba(var(--accent-rgb,43,108,255), .35); }
  50%      { box-shadow: inset 0 0 18px rgba(var(--accent-rgb, 43,108,255), .28); border-color: rgba(var(--accent-rgb,43,108,255), .65); }
}

/* ── Edit toolbar: two tidy rows (grid controls · device look) instead of one
   wrapping soup. Each row scrolls horizontally on narrow tiles so controls keep
   their size and never cram or overlap. ── */
.deck-tools {
  position: relative; z-index: 1;
  display: flex; flex-direction: column; gap: 6px;
  padding: 8px; border-radius: 12px;
  background: linear-gradient(180deg, rgba(255,255,255,.05), rgba(255,255,255,.015));
  border: 1px solid rgba(255,255,255,.08);
  box-shadow: inset 0 1px 0 rgba(255,255,255,.06);
}
.deck-tools-row {
  display: flex; align-items: center; gap: 8px;
  /* Wrap onto more lines rather than scrolling — controls (like the PLATE choices)
     must never be clipped off the right edge of the toolbar. */
  flex-wrap: wrap; row-gap: 6px;
}
.deck-tools-row::-webkit-scrollbar { display: none; }
.deck-tools-row > * { flex: 0 0 auto; }
.deck-tools-grp { display: flex; align-items: center; gap: 6px; }
.deck-tools-cap {
  font-size: 9px; text-transform: uppercase; letter-spacing: .08em;
  color: var(--text-dim, #8b91a1); margin-right: 1px;
}
.deck-seg { display: inline-flex; padding: 2px; border-radius: 9px; gap: 2px;
  background: rgba(8,11,18,.6); border: 1px solid rgba(255,255,255,.07); }
.deck-seg button {
  border: 0; background: none; cursor: pointer; color: var(--text-dim, #9aa1b1);
  font-size: 11px; font-weight: 600; padding: 5px 10px; border-radius: 7px;
  transition: background .15s ease, color .15s ease;
}
.deck-seg button.active {
  color: #fff;
  background: linear-gradient(180deg, color-mix(in srgb, var(--accent,#2b6cff) 78%, #fff 0%), var(--accent,#2b6cff));
  box-shadow: 0 1px 6px rgba(var(--accent-rgb,43,108,255),.45);
}
/* Glyph buttons (the cap-shape picker) — outline icons that tint like text. */
.deck-seg button svg { width: 13px; height: 13px; display: block; fill: none; stroke: currentColor; stroke-width: 1.5; }
.deck-pill {
  display: inline-flex; align-items: center; gap: 6px; cursor: pointer;
  padding: 5px 11px; border-radius: 9px; font-size: 11px; font-weight: 600;
  color: var(--text-dim, #9aa1b1); border: 1px solid rgba(255,255,255,.1);
  background: linear-gradient(180deg, rgba(255,255,255,.07), rgba(255,255,255,.015));
  transition: color .15s ease, border-color .15s ease, background .15s ease;
}
.deck-pill::before {
  content: ''; width: 8px; height: 8px; border-radius: 50%;
  background: rgba(255,255,255,.2); box-shadow: none; transition: all .2s ease;
}
.deck-pill.on {
  color: var(--text, #e7e9ee); border-color: rgba(var(--accent-rgb,43,108,255),.5);
  background: rgba(var(--accent-rgb,43,108,255),.14);
}
.deck-pill.on::before { background: var(--accent, #7aa2ff); box-shadow: 0 0 8px rgba(var(--accent-rgb,43,108,255),.9); }
.deck-step {
  width: 24px; height: 24px; border-radius: 7px; cursor: pointer; font-size: 14px; line-height: 1;
  border: 1px solid rgba(255,255,255,.1); color: var(--text, #e7e9ee);
  background: linear-gradient(180deg, rgba(255,255,255,.08), rgba(255,255,255,.015));
  display: flex; align-items: center; justify-content: center;
}
.deck-step:hover { filter: brightness(1.2); }
.deck-step:active { transform: translateY(1px); }
.deck-step[disabled] { opacity: .3; cursor: default; }
/* Accent swatch in the decor tools row. */
.deck-decor-swatch {
  width: 30px; height: 24px; padding: 0; cursor: pointer; flex: 0 0 auto;
  border: 1px solid rgba(255, 255, 255, .16); border-radius: 7px; background: transparent;
}
.deck-decor-swatch[disabled] { opacity: .35; cursor: default; }
.deck-decor-gradchk { width: 15px; height: 15px; cursor: pointer; accent-color: var(--accent, #1ed760); flex: 0 0 auto; }
/* "Customize" button in the toolbar → opens the appearance modal. */
.deck-customize { font-weight: 700; }
.deck-customize.deck-pill { background: rgba(var(--accent-rgb), .16); border-color: rgba(var(--accent-rgb), .4); color: var(--accent); }
.deck-customize.deck-pill:hover { background: rgba(var(--accent-rgb), .24); }

/* ── Deck appearance modal (theme/shape/plate + background + music) ──────────
   A dialog with a LIVE preview of the deck and its music bar so colour/gradient
   picks are visible while you make them. */
.deck-style-modal {
  position: fixed; inset: 0; z-index: 4000; display: flex;
  align-items: center; justify-content: center; padding: 20px;
  background: rgba(0, 0, 0, .55); backdrop-filter: blur(4px);
}
.deck-style-dialog {
  width: min(560px, 96vw); max-height: 92vh; overflow-y: auto;
  display: flex; flex-direction: column; gap: 14px; padding: 16px;
  background: var(--panel-solid, #14171c); color: var(--text, #e7e9ee);
  border: 1px solid rgba(255, 255, 255, .1); border-radius: 16px;
  box-shadow: 0 24px 70px rgba(0, 0, 0, .5);
}
.deck-style-head { display: flex; align-items: center; justify-content: space-between; gap: 12px; }
.deck-style-title { font-size: 15px; font-weight: 800; }
.deck-style-close {
  width: 30px; height: 30px; flex: 0 0 auto; cursor: pointer; font-size: 15px; line-height: 1;
  border-radius: 8px; border: 1px solid rgba(255, 255, 255, .12);
  background: rgba(255, 255, 255, .05); color: var(--text, #e7e9ee);
}
.deck-style-close:hover { background: rgba(255, 255, 255, .1); }
/* Preview surface — a neutral, checkered backdrop so the deck reads on any theme. */
.deck-style-preview {
  display: flex; justify-content: center; align-items: flex-start;
  flex: 0 0 auto; max-height: min(48vh, 360px);
  padding: 16px; border-radius: 12px; overflow: auto;
  background:
    linear-gradient(rgba(0, 0, 0, .18), rgba(0, 0, 0, .18)),
    conic-gradient(#2a2f38 0 25%, #232830 0 50%, #2a2f38 0 75%, #232830 0) 0 0 / 22px 22px;
}
.deck-style-preview-root { pointer-events: none; width: 100%; max-width: 440px; height: auto !important; }
/* The real deck's caps are sized from the WELL via size container queries (100cqh),
   which need a real height. The modal has no fixed height, so give the well a
   concrete one (rows × cap) — then the native sizing resolves, the caps are square,
   and the well contains its grid so the music bar stacks cleanly below it. */
.deck-style-preview-root .deck-well {
  flex: 0 0 auto !important;
  height: calc(var(--deck-rows, 2) * 74px) !important;
}
.deck-style-preview-root .deck-device { flex: none; width: 100%; gap: 8px; }
.deck-style-body { display: flex; flex-direction: column; gap: 12px; }
.deck-style-grp { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
.deck-style-cap {
  font-size: 10px; text-transform: uppercase; letter-spacing: .06em;
  color: var(--text-dim, #8b91a1); min-width: 70px;
}
.deck-style-subhead {
  font-size: 11px; font-weight: 800; text-transform: uppercase; letter-spacing: .05em;
  opacity: .72; margin-top: 4px; padding-top: 10px; border-top: 1px solid rgba(255, 255, 255, .07);
}
.deck-style-range { flex: 1 1 120px; min-width: 110px; accent-color: var(--accent, #7aa2ff); }
.deck-step-val {
  font-family: ui-monospace, "SF Mono", Menlo, Consolas, monospace;
  font-size: 11px; min-width: 30px; text-align: center; color: var(--text, #e7e9ee);
}

/* ── Now-playing screen: an embedded LCD strip (Stream Deck +-style) ──── */
/* A persistent device screen: it's always mounted while the dock is enabled (an
   idle "standby" state shows when nothing plays) so the cap grid above never
   reflows as playback comes and goes. The black bezel ring + curved top reflection
   make it read as a real recessed glass panel, not a flat strip. */
.deck-np {
  position: relative; z-index: 1; overflow: hidden; flex: 0 0 auto;
  display: flex; align-items: center; gap: 12px; padding: 9px 13px;
  /* Bezel: a hair of black framing the glass, like a moulded screen cutout. */
  border: 1px solid #05070b;
  border-radius: clamp(11px, var(--deck-radius), 16px);
  /* default screen glow when there's no cover colour to sample */
  background: linear-gradient(115deg, #0a3a52, #15224a 48%, #34164a);
  box-shadow:
    inset 0 0 0 1px rgba(255, 255, 255, .07),   /* inner glass rim highlight */
    inset 0 2px 14px rgba(0, 0, 0, .72),         /* depth under the glass */
    inset 0 -10px 20px rgba(0, 0, 0, .35),       /* lower vignette */
    0 1px 0 rgba(255, 255, 255, .05);            /* lit lip where it meets the body */
  transition: filter .4s ease;
}
.deck-np[hidden] { display: none; }
/* Standby: the panel calms to an idle glow and swaps the transport for the active
   output device's name + a live volume meter (a useful at-rest screen, not blank). */
.deck-np.is-idle { filter: saturate(.62) brightness(.86); }
.deck-np.is-idle .deck-np-actions { display: none; }
.deck-np:not(.is-idle) .deck-np-vol,
.deck-np:not(.is-idle) .deck-np-spk { display: none; }
/* Speaker glyph that fills the cover slot while idle (over the ♪ placeholder). */
.deck-np-spk {
  position: absolute; inset: 0; z-index: 1; pointer-events: none;
  display: flex; align-items: center; justify-content: center; color: rgba(255, 255, 255, .92);
}
.deck-np-spk svg { width: 21px; height: 21px; fill: currentColor; filter: drop-shadow(0 1px 2px rgba(0, 0, 0, .5)); }
.deck-np.is-idle .deck-np-cover::after { opacity: 0; }      /* hide the ♪ behind the speaker */
.deck-np.is-muted .deck-np-spk { color: rgba(255, 255, 255, .55); }
/* Live volume meter (idle only): a slim lit track, fill width = master level. */
.deck-np-vol {
  position: relative; z-index: 2; flex: 0 0 auto; width: 74px; height: 6px;
  border-radius: 4px; overflow: hidden;
  background: rgba(255, 255, 255, .16);
  box-shadow: inset 0 1px 2px rgba(0, 0, 0, .55), inset 0 0 0 1px rgba(255, 255, 255, .06);
}
.deck-np-vol-fill {
  height: 100%; width: 0; border-radius: 4px;
  background: linear-gradient(90deg, rgba(255, 255, 255, .85), #fff);
  box-shadow: 0 0 8px rgba(255, 255, 255, .55);
  transition: width .28s cubic-bezier(.2, .8, .2, 1);
}
.deck-np.is-muted .deck-np-vol-fill { background: rgba(255, 255, 255, .4); box-shadow: none; }
/* Blurred album art tints the whole screen with the track's colours. */
.deck-np-bg {
  position: absolute; inset: 0; z-index: 0;
  background: center/cover no-repeat;
  filter: blur(22px) saturate(1.6) brightness(.78);
  transform: scale(1.4); opacity: 0; transition: opacity .45s ease;
}
.deck-np.has-cover .deck-np-bg { opacity: 1; }
/* User backdrop for the strip: an underlay behind the album backdrop, so it shows
   in standby and tints through when a track plays. */
.deck-np-userbg {
  position: absolute; inset: 0; z-index: 0; pointer-events: none;
  background: center/cover no-repeat;
}
.deck-np-userbg::after {
  content: ''; position: absolute; inset: 0; background: #05070d; opacity: var(--deck-np-userdim, .4);
}
/* Accent tint for the strip (volume fill, standby speaker, glass wash + rim). */
.deck-np.has-accent .deck-np-vol-fill { background: var(--deck-np-accent); box-shadow: 0 0 8px var(--deck-np-accent); }
.deck-np.has-accent .deck-np-spk { color: var(--deck-np-accent); }
.deck-np.has-accent::before {
  background:
    radial-gradient(150% 100% at 50% -40%, rgba(255, 255, 255, .16), transparent 55%),
    linear-gradient(115deg, color-mix(in srgb, var(--deck-np-accent) 40%, transparent), transparent 46%),
    linear-gradient(180deg, rgba(255, 255, 255, .1), transparent 24%),
    linear-gradient(90deg, rgba(0, 0, 0, .42), rgba(0, 0, 0, .12) 38%, rgba(0, 0, 0, .42));
  box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--deck-np-accent) 55%, transparent);
}
/* Colour wash + readability veil + a curved top reflection → reads as lit glass. */
.deck-np::before {
  content: ''; position: absolute; inset: 0; z-index: 1; pointer-events: none;
  background:
    radial-gradient(150% 100% at 50% -40%, rgba(255, 255, 255, .16), transparent 55%),
    linear-gradient(115deg, rgba(0, 190, 255, .22), transparent 42%, rgba(170, 80, 255, .26)),
    linear-gradient(180deg, rgba(255, 255, 255, .1), transparent 24%),
    linear-gradient(90deg, rgba(0, 0, 0, .42), rgba(0, 0, 0, .12) 38%, rgba(0, 0, 0, .42));
}
.deck-np-cover, .deck-np-info, .deck-np-actions { position: relative; z-index: 2; }

.deck-np-cover {
  width: 40px; height: 40px; flex: 0 0 auto; border-radius: 8px;
  background: rgba(255, 255, 255, .08) center/cover no-repeat;
  border: 1px solid rgba(255, 255, 255, .25); overflow: hidden;
  box-shadow: 0 2px 8px rgba(0, 0, 0, .5);
}
.deck-np-cover::after {
  content: '♪'; position: absolute; inset: 0; display: flex; align-items: center; justify-content: center;
  font-size: 17px; color: rgba(255, 255, 255, .8); opacity: 1; transition: opacity .2s ease;
}
.deck-np-cover.has-image::after { opacity: 0; }
.deck-np-info { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: 1px; }
.deck-np-title {
  font-size: 12.5px; font-weight: 700; color: #fff;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap; text-shadow: 0 1px 3px rgba(0, 0, 0, .55);
}
.deck-np-artist {
  font-size: 10.5px; color: rgba(255, 255, 255, .82);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap; text-shadow: 0 1px 2px rgba(0, 0, 0, .5);
}
.deck-np-actions { display: flex; align-items: center; gap: 6px; flex: 0 0 auto; }
.deck-np-btn {
  width: 30px; height: 30px; border-radius: 50%; cursor: pointer; padding: 0;
  border: 1px solid rgba(255, 255, 255, .35); color: #fff; background: rgba(255, 255, 255, .16);
  -webkit-backdrop-filter: blur(4px); backdrop-filter: blur(4px);
  display: flex; align-items: center; justify-content: center;
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, .3), 0 1px 3px rgba(0, 0, 0, .35);
  transition: transform .12s ease, background .15s ease;
}
.deck-np-btn:hover { background: rgba(255, 255, 255, .28); }
.deck-np-btn:active { transform: scale(.9); }
.deck-np-btn svg { width: 14px; height: 14px; fill: currentColor; }
.deck-np-btn.primary {
  width: 34px; height: 34px; color: #15171d;
  background: rgba(255, 255, 255, .95); border-color: transparent;
  box-shadow: 0 2px 8px rgba(0, 0, 0, .4), inset 0 1px 0 rgba(255, 255, 255, .8);
}
.deck-np-btn.primary:hover { background: #fff; }
.deck-np-btn.primary svg { width: 16px; height: 16px; }

/* ── Key editor: a wide two-pane studio — live preview rail on the left, a
   tabbed form (Actions · Look · Effects) on the right, save bar always docked.
   Replaces the old single 460px column the whole form used to scroll inside. ── */
.deck-ed-backdrop {
  position: fixed; inset: 0; z-index: 60;
  background: rgba(8, 10, 14, .55);
  -webkit-backdrop-filter: blur(22px) saturate(120%);
  backdrop-filter: blur(22px) saturate(120%);
  display: flex; align-items: center; justify-content: center; padding: 14px;
  animation: deck-ed-fade .18s ease;
}
@keyframes deck-ed-fade { from { opacity: 0; } to { opacity: 1; } }
.deck-ed-modal {
  width: min(880px, 96vw); height: min(660px, 94vh);
  display: flex; flex-direction: column; overflow: hidden;
  background:
    radial-gradient(120% 90% at 50% -10%, rgba(var(--accent-rgb, 43, 108, 255), .16), transparent 60%),
    linear-gradient(180deg, rgba(28, 32, 42, .97), rgba(16, 18, 24, .97));
  border: 1px solid rgba(255, 255, 255, .12);
  border-radius: 22px; padding: 0;
  box-shadow: 0 26px 70px rgba(0, 0, 0, .6), inset 0 1px 0 rgba(255, 255, 255, .06);
  color: var(--text, #e7e9ee);
  animation: deck-ed-pop .22s cubic-bezier(.2, .8, .2, 1);
}
@keyframes deck-ed-pop { from { opacity: 0; transform: translateY(10px) scale(.97); } to { opacity: 1; transform: none; } }
/* Header: title + close, fixed above the panes. */
.deck-ed-head {
  flex: 0 0 auto; display: flex; align-items: center; justify-content: space-between; gap: 10px;
  padding: 14px 18px 12px; border-bottom: 1px solid rgba(255, 255, 255, .08);
}
.deck-ed-title { margin: 0; padding: 0; border: 0; font-size: 16px; font-weight: 700; }
.deck-ed-close {
  width: 34px; height: 34px; flex: 0 0 auto; border-radius: 50%; cursor: pointer;
  background: rgba(255, 255, 255, .06); border: 1px solid rgba(255, 255, 255, .12);
  color: var(--text, #e7e9ee); font-size: 17px; line-height: 1;
  display: flex; align-items: center; justify-content: center;
  transition: background .15s ease;
}
.deck-ed-close:hover { background: rgba(255, 255, 255, .12); }
/* Body: side rail + tabbed main, each scrolling on its own. */
.deck-ed-body { flex: 1; min-height: 0; display: flex; }
.deck-ed-side {
  flex: 0 0 216px; min-height: 0; overflow-y: auto; overflow-x: hidden;
  display: flex; flex-direction: column; align-items: center; gap: 14px;
  padding: 16px 14px;
  border-right: 1px solid rgba(255, 255, 255, .07);
  background: rgba(0, 0, 0, .18);
}
/* One thin-scrollbar recipe for every scroll region in the editor. */
.deck-ed-side, .deck-ed-pane, .deck-ed-pickscroll {
  scrollbar-width: thin; scrollbar-color: rgba(255, 255, 255, .2) transparent;
}
.deck-ed-main {
  flex: 1; min-width: 0; min-height: 0;
  display: flex; flex-direction: column;
  padding: 14px 18px 12px;
}
.deck-ed-main > .deck-ed-field { flex: 0 0 auto; }
/* Tab bar: fixed under the name field; only the active pane scrolls. */
.deck-ed-tabs {
  flex: 0 0 auto; display: flex; gap: 3px; padding: 3px; margin-bottom: 14px;
  background: rgba(8, 11, 18, .6); border: 1px solid rgba(255, 255, 255, .07);
  border-radius: 12px;
}
.deck-ed-tab {
  flex: 1; padding: 9px 12px; border: 0; border-radius: 9px; cursor: pointer;
  background: none; color: var(--text-dim, #9aa1b1);
  font-size: 12.5px; font-weight: 700; letter-spacing: .02em; white-space: nowrap;
  transition: background .15s ease, color .15s ease;
}
.deck-ed-tab.active {
  color: #fff;
  background: linear-gradient(180deg, color-mix(in srgb, var(--accent, #2b6cff) 78%, #fff 0%), var(--accent, #2b6cff));
  box-shadow: 0 1px 8px rgba(var(--accent-rgb, 43, 108, 255), .45);
}
.deck-ed-pane {
  flex: 1; min-height: 0; overflow-y: auto; padding: 2px 6px 8px 2px;
  scrollbar-gutter: stable;
}
/* Each section reads as its own quiet card — bigger labels, clear boundaries. */
.deck-ed-field { display: flex; flex-direction: column; gap: 8px; margin-bottom: 15px; }
.deck-ed-pane > .deck-ed-field {
  padding: 12px; margin-bottom: 10px; border-radius: 13px;
  background: rgba(255, 255, 255, .025); border: 1px solid rgba(255, 255, 255, .06);
}
.deck-ed-label { font-size: 11px; font-weight: 600; text-transform: uppercase; letter-spacing: .08em; color: var(--text-dim, #9aa1b1); }
.deck-ed-input {
  width: 100%; box-sizing: border-box; padding: 9px 11px;
  background: rgba(255, 255, 255, .045); border: 1px solid rgba(255, 255, 255, .12);
  border-radius: 10px; color: var(--text, #e7e9ee); font-size: 13px;
  transition: border-color .15s ease, box-shadow .15s ease;
}
.deck-ed-input:focus {
  outline: none; border-color: rgba(var(--accent-rgb, 43, 108, 255), .6);
  box-shadow: 0 0 0 3px rgba(var(--accent-rgb, 43, 108, 255), .14);
}
/* Selects re-apply the shared chevron (the .deck-ed-input `background` shorthand
   above would otherwise clear it) so they match every other dropdown in the app. */
select.deck-ed-input {
  -webkit-appearance: none; appearance: none;
  padding-right: 34px; cursor: pointer;
  background-image: var(--select-chevron);
  background-repeat: no-repeat; background-position: right 11px center; background-size: 14px;
}
.deck-ed-input[type="color"] { height: 40px; padding: 4px; cursor: pointer; }
.deck-ed-colorrow { display: flex; gap: 8px; align-items: center; }
.deck-ed-colorrow .deck-ed-input[type="color"] { flex: 1; }
.deck-ed-input[type="color"].is-unset { opacity: .4; }
.deck-ed-colorclear {
  width: 40px; height: 40px; border-radius: 10px; cursor: pointer; flex: 0 0 auto;
  background: rgba(255, 255, 255, .05); border: 1px solid rgba(255, 255, 255, .12);
  color: var(--text-dim, #8b91a1); font-size: 13px;
}
.deck-ed-colorclear:hover { background: rgba(255, 255, 255, .1); color: var(--text, #e7e9ee); }
/* Native <select> popup: force a dark, readable option list (Chromium/WebView). */
.deck-ed-input option { background: #12161f; color: #e7e9ee; }
.deck-ed-params { display: flex; flex-direction: column; }

/* Icon picker: emoji / built-in icons / image toggle, category grids, upload */
.deck-ed-seg { display: flex; gap: 6px; margin-bottom: 8px; }
.deck-ed-segbtn {
  width: 40px; height: 34px; border-radius: 9px; cursor: pointer; font-size: 17px;
  background: rgba(255, 255, 255, .045); border: 1px solid rgba(255, 255, 255, .12); color: inherit;
  display: flex; align-items: center; justify-content: center;
}
.deck-ed-segbtn.active { background: rgba(var(--accent-rgb, 43, 108, 255), .22); border-color: rgba(var(--accent-rgb, 43, 108, 255), .55); }
.deck-ed-segbtn-icon svg { width: 18px; height: 18px; fill: currentColor; }
/* Text segments (Colore/Gradiente, label position, size presets): the base
   segbtn is a fixed 40px square built for the emoji/icon tabs, so word labels
   overflow and overlap. Here the buttons size to content and share the row. */
.deck-ed-seg-text { width: 100%; }
.deck-ed-seg-text .deck-ed-segbtn {
  flex: 1; width: auto; min-width: 0; padding: 0 10px;
  font-size: 12.5px; font-weight: 600; white-space: nowrap;
}
/* Scrollable picker body so a large emoji/icon library never blows up the modal. */
.deck-ed-pickscroll {
  max-height: 192px; overflow-y: auto; margin-bottom: 8px;
  padding-right: 4px;
}
.deck-ed-pickscroll::-webkit-scrollbar { width: 8px; }
.deck-ed-pickscroll::-webkit-scrollbar-thumb { background: rgba(255, 255, 255, .16); border-radius: 8px; }
.deck-ed-cat {
  font-size: 10px; font-weight: 700; letter-spacing: .08em; text-transform: uppercase;
  color: var(--text-dim, #aeb4c2); opacity: .8;
  margin: 8px 2px 5px; position: sticky; top: 0;
  background: linear-gradient(180deg, #181b23 72%, transparent);
  padding-bottom: 3px; z-index: 1;
}
.deck-ed-cat:first-child { margin-top: 0; }
.deck-ed-emojis {
  display: grid; grid-template-columns: repeat(7, 1fr); gap: 5px;
}
.deck-ed-emoji {
  aspect-ratio: 1; border-radius: 9px; cursor: pointer; font-size: 17px; line-height: 1;
  background: rgba(255, 255, 255, .04); border: 1px solid rgba(255, 255, 255, .08); color: inherit;
  display: flex; align-items: center; justify-content: center;
  transition: background .12s ease, border-color .12s ease, transform .08s ease;
}
.deck-ed-emoji:hover { background: rgba(255, 255, 255, .09); transform: translateY(-1px); }
.deck-ed-emoji.sel { border-color: rgba(var(--accent-rgb, 43, 108, 255), .7); background: rgba(var(--accent-rgb, 43, 108, 255), .2); }
.deck-ed-icon svg { width: 20px; height: 20px; fill: currentColor; }
/* Installed icon-pack cells: bitmap/SVG files served by /icon-pack/, same cell
   geometry as the built-in vector icons. */
.deck-ed-packicon img { width: 20px; height: 20px; object-fit: contain; pointer-events: none; }
.deck-ed-imgpick { display: flex; align-items: center; gap: 10px; }
.deck-ed-file { display: none; }
/* "Use the app's own icon" shortcut — sits under the icon-mode tabs, only for
   launch keys. Accent-tinted so it reads as the recommended one-tap option. */
.deck-ed-appicon-row { margin: 8px 0 2px; }
.deck-ed-appicon-btn {
  width: 100%;
  border-color: rgba(var(--accent-rgb, 43, 108, 255), .45);
  background: rgba(var(--accent-rgb, 43, 108, 255), .12);
}
.deck-ed-appicon-btn:hover { background: rgba(var(--accent-rgb, 43, 108, 255), .2); }
.deck-ed-appicon-btn:disabled { opacity: .6; cursor: default; }
.deck-ed-imgprev {
  width: 44px; height: 44px; border-radius: 9px; object-fit: cover;
  border: 1px solid rgba(255, 255, 255, .14);
}

/* Footer save bar: docked under the panes, always visible (no more scrolling to
   the bottom of a long form to find Save). */
.deck-ed-actions {
  flex: 0 0 auto; display: flex; gap: 8px;
  padding: 12px 18px 14px; border-top: 1px solid rgba(255, 255, 255, .08);
  background: rgba(0, 0, 0, .14);
}
.deck-ed-btn {
  flex: 1; padding: 10px; border-radius: 10px; cursor: pointer; font-weight: 600;
  border: 1px solid rgba(255, 255, 255, .12); background: rgba(255, 255, 255, .05);
  color: var(--text, #e7e9ee); font-size: 13px;
  transition: filter .15s ease, background .15s ease;
}
.deck-ed-btn:hover { background: rgba(255, 255, 255, .1); }
.deck-ed-btn.primary { background: var(--accent, #2b6cff); border-color: transparent; color: var(--on-accent, #fff); }
.deck-ed-btn.primary:hover { filter: brightness(1.08); background: var(--accent, #2b6cff); }
.deck-ed-btn.danger { color: var(--color-danger); border-color: rgba(var(--danger-rgb), .4); flex: 0 0 auto; padding: 10px 14px; }

/* discordSound param: the sound dropdown + a preview (▶) button that auditions the
   selected sound locally. Row keeps the (enhanced) select filling the width and the
   preview a fixed square beside it. */
.deck-ed-sound-row { display: flex; align-items: center; gap: 6px; }
.deck-ed-sound-row > .deck-ed-input,
.deck-ed-sound-row > .cs-wrap { flex: 1 1 auto; min-width: 0; }
.deck-ed-sound-preview {
  flex: 0 0 auto; width: 40px; padding: 0; display: inline-flex; align-items: center; justify-content: center;
}
.deck-ed-sound-preview svg { width: 15px; height: 15px; }

/* Saved key presets strip at the top of the editor: load a saved key into the form. */
.deck-ed-presets { display: flex; flex-wrap: wrap; align-items: center; gap: 6px; }
.deck-ed-presets-label {
  width: 100%; font-size: 10px; font-weight: 600; letter-spacing: .09em; text-transform: uppercase;
  color: var(--text-dim, #aeb4c2);
}
.deck-ed-preset-chip {
  position: relative; display: inline-flex; align-items: center; gap: 6px; max-width: 100%;
  padding: 5px 26px 5px 10px; cursor: pointer;
  border: 1px solid rgba(var(--accent-rgb, 43,108,255), .4); border-radius: 999px;
  background: rgba(var(--accent-rgb, 43,108,255), .12); color: var(--text, #e7e9ee);
  font-size: 12px; font-weight: 600; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.deck-ed-preset-chip:hover { background: rgba(var(--accent-rgb, 43,108,255), .2); }
.deck-ed-preset-del {
  position: absolute; right: 4px; top: 50%; transform: translateY(-50%);
  width: 18px; height: 18px; display: grid; place-items: center; border-radius: 50%;
  color: #ff6b6b; font-size: 13px; font-weight: 700; line-height: 1;
}
.deck-ed-preset-del:hover { background: rgba(255, 107, 107, .2); }

/* Confirmation is a soft glow that swells then fades — no scale/brightness punch, so
   it never fights the press depression (the key handles that itself). This is the
   default ('glow') tap feedback, a ONE-SHOT pulse in the key's effect colour
   (--fx-color, set per key; falls back to the theme accent). */
.deck-key.is-running { animation: deck-key-run .5s ease-out; }
@keyframes deck-key-run {
  0%   { box-shadow: inset 0 0 0 0 transparent; }
  30%  { box-shadow: inset 0 0 0 2px var(--fx-color, rgba(var(--accent-rgb, 43, 108, 255), .85)),
                     0 0 16px var(--fx-color, rgba(var(--accent-rgb, 43, 108, 255), .5)); }
  100% { box-shadow: inset 0 0 0 0 transparent; }
}

/* ── Persistent (latching) tap effects ──────────────────────────────────
   These stay applied after a tap and clear on the next tap (toggled in JS), so the
   cap holds its state. All honour the per-key --fx-color (default white). */

/* "Rimani premuto": the cap stays pushed in, with a coloured inner rim, until tapped
   again. !important defeats the bevel/accent box-shadows so it reads as held. */
.deck-key.fx-hold {
  transform: translateY(2px) scale(.965);
  filter: brightness(.92);
  box-shadow:
    inset 0 3px 10px rgba(0, 0, 0, .7),
    inset 0 0 0 2px var(--fx-color, rgba(255, 255, 255, .5)),
    inset 0 0 18px color-mix(in srgb, var(--fx-color, #fff) 45%, transparent) !important;
}
/* "Lampeggia": the cap blinks in a continuous loop in the effect colour.
   The pulse lives on ::after animating only opacity (compositor-only) — the old
   box-shadow keyframes ticked the main-thread style→paint pipeline every display
   frame for as long as the key stayed latched (GitHub #99). The outer halo can't
   ride ::after (the cap clips it with overflow: hidden), so it sits statically on
   the key; the pulsing inset ring + flood carry the blink. :not(.is-glint) yields
   ::after to the one-shot glint for its 0.5s sweep, then the pulse resumes. */
.deck-key.fx-blink {
  box-shadow:
    inset 0 1px 0 rgba(255,255,255,.14),
    inset 0 -7px 14px rgba(0,0,0,.55),
    0 3px 7px rgba(0,0,0,.55),
    0 1px 0 rgba(0,0,0,.7),
    0 0 16px color-mix(in srgb, var(--fx-color, #fff) 45%, transparent);
}
.deck-key.fx-blink:not(.is-glint)::after {
  content: ''; position: absolute; inset: 0; border-radius: inherit;
  z-index: 3; pointer-events: none;
  box-shadow: inset 0 0 0 2px var(--fx-color, #fff),
              inset 0 0 26px color-mix(in srgb, var(--fx-color, #fff) 55%, transparent);
  opacity: 0;
  animation: deck-key-blink 1s ease-in-out infinite;
  will-change: opacity;
}
@keyframes deck-key-blink { 0%, 100% { opacity: 0; } 50% { opacity: 1; } }
/* Reduced motion: hold the ring steady so the latched state stays readable. */
@media (prefers-reduced-motion: reduce) {
  .deck-key.fx-blink:not(.is-glint)::after { animation: none; opacity: 1; }
}
/* "Spegni": the cap stays dark (screen powered off) until tapped again; its icon and
   label dim too. The big inset spread floods the whole face black. */
.deck-key.fx-dark {
  filter: saturate(.35);
  box-shadow: inset 0 0 0 999px rgba(0, 0, 0, .86), inset 0 0 0 1px rgba(255, 255, 255, .04) !important;
}
.deck-key.fx-dark .deck-ico, .deck-key.fx-dark .deck-label { opacity: .22; }
.deck-key.fx-dark::before { opacity: 0; }

/* Dynamic state: the `is-on` class is still toggled on a state-bound key (it drives
   the state-following LED reaction and the OBS scene-preview host in JS), but it no
   longer paints an automatic on-screen indicator. A key's appearance is determined
   solely by the effects the user configures during creation — accent colour, touch
   effect (Stay pressed / Blink / Dark / Glow) and the LED reaction — so the default
   accent dot + border + breathing no longer overrides those deliberate choices. */

/* Key Logic: trigger buttons (name + the action assigned to that gesture) */
.deck-ed-trigbtn {
  flex: 1; padding: 7px 6px; border-radius: 9px; cursor: pointer; position: relative;
  background: rgba(255, 255, 255, .045); border: 1px solid rgba(255, 255, 255, .12);
  color: var(--text, #e7e9ee);
  display: flex; flex-direction: column; align-items: center; gap: 2px; line-height: 1.2;
}
.deck-ed-trigbtn.active { background: rgba(var(--accent-rgb, 43, 108, 255), .22); border-color: rgba(var(--accent-rgb, 43, 108, 255), .55); }
.deck-ed-trigname { font-size: 12px; }
.deck-ed-trigact { font-size: 9px; color: var(--text-dim, #8b91a1); max-width: 100%; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.deck-ed-trigbtn.active .deck-ed-trigact { color: var(--text, #e7e9ee); }

/* On-key gesture hints (bottom-right): double = two dots, hold = a bar */
.deck-key-trig { position: absolute; right: 4px; bottom: 4px; display: flex; gap: 4px; align-items: center; opacity: .6; pointer-events: none; }
.deck-key-trig .tind-double { width: 8px; height: 3px; position: relative; }
.deck-key-trig .tind-double::before, .deck-key-trig .tind-double::after {
  content: ''; position: absolute; top: 0; width: 3px; height: 3px; border-radius: 50%; background: var(--text-dim, #8b91a1);
}
.deck-key-trig .tind-double::before { left: 0; }
.deck-key-trig .tind-double::after { right: 0; }
.deck-key-trig .tind-hold { width: 3px; height: 9px; border-radius: 2px; background: var(--text-dim, #8b91a1); }

/* ── Live value badge (timer countdown / SDK widget state text) ── */
.deck-key-live {
  position: absolute; left: 8%; right: 8%; top: 6px;
  font-size: 12px; font-weight: 700; font-variant-numeric: tabular-nums;
  text-align: center; letter-spacing: .02em; color: var(--text, #e8eaf0);
  text-shadow: 0 1px 3px rgba(0, 0, 0, .55);
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis; pointer-events: none;
  opacity: 0; transition: opacity .18s ease;
}
.deck-key.has-live .deck-key-live { opacity: 1; }
/* When the label sits on top, the badge moves to the bottom edge instead. */
.deck-key[data-labelpos="top"] .deck-key-live { top: auto; bottom: 6px; }
/* The badge floats over the cap face, but the cap centres its icon and label in
   the FULL height — so a key carrying a badge drew the glyph straight under it,
   both fighting for the same pixels (a soundboard key's slot tag sitting on its
   icon, a state word crossing a star). Reserve the strip the badge occupies and
   the centred content clears it on its own. Sliders are exempt: their icon is
   hidden and their badge already sits low, over the track. */
.deck-key.has-live:not(.is-slider) { padding-top: 22px; }
.deck-key[data-labelpos="top"].has-live:not(.is-slider) { padding-top: 6px; padding-bottom: 22px; }

/* ── Touch faders (slider keys) ── */
.deck-key.is-slider { touch-action: none; cursor: grab; }
.deck-key.is-slider:active { cursor: grabbing; }
.deck-key.is-slider .deck-ico { display: none; }
.deck-slider {
  position: absolute; inset: 14% 32%;
  border-radius: 8px; overflow: hidden;
  background: rgba(255, 255, 255, .08);
  box-shadow: inset 0 1px 4px rgba(0, 0, 0, .45);
  pointer-events: none;   /* the KEY owns the pointer (capture) — the track is visual */
}
.deck-key.is-slider[data-orient="h"] .deck-slider { inset: 36% 12%; }
.deck-slider-fill {
  position: absolute; left: 0; right: 0; bottom: 0; height: 50%;
  background: linear-gradient(180deg, color-mix(in srgb, var(--key-accent, var(--accent, #2b6cff)) 85%, #fff 15%), var(--key-accent, var(--accent, #2b6cff)));
  transition: height .08s linear, width .08s linear;
}
.deck-key.is-slider[data-orient="h"] .deck-slider-fill { top: 0; bottom: 0; left: 0; right: auto; height: auto; width: 50%; }
.deck-key.is-slider .deck-key-live { top: auto; bottom: 22%; font-size: 11px; }
.deck-key.is-slider[data-orient="h"] .deck-key-live { bottom: 8%; }
.deck-key.is-slider .deck-label { z-index: 2; }

/* ── Smart Profiles dialog (app → profile auto-switch rules) ── */
.deck-asw { max-width: 420px; width: min(92vw, 420px); display: flex; flex-direction: column; gap: 10px; }
.deck-asw-row { display: flex; align-items: center; gap: 8px; }
.deck-asw-enable { cursor: pointer; user-select: none; }
.deck-asw-enable input { width: 18px; height: 18px; accent-color: var(--accent, #2b6cff); }
.deck-asw-rules { display: flex; flex-direction: column; gap: 6px; max-height: 40vh; overflow-y: auto; }
.deck-asw-rule { display: grid; grid-template-columns: 1fr 1fr auto; gap: 6px; align-items: center; }
.deck-pmenu-add.is-on { color: var(--accent, #2b6cff); }

/* Accent preset palette */
.deck-ed-swatches { display: grid; grid-template-columns: repeat(auto-fill, minmax(32px, 1fr)); gap: 6px; }   /* ≥32px: finger-sized swatches */
.deck-ed-swatch { aspect-ratio: 1; border-radius: 7px; cursor: pointer; padding: 0; border: 1px solid rgba(255, 255, 255, .15); }
.deck-ed-swatch.sel { box-shadow: 0 0 0 2px var(--panel, #14161c), 0 0 0 4px rgba(var(--accent-rgb, 43, 108, 255), .9); }
.deck-ed-swatch-none {
  background: rgba(255, 255, 255, .05); color: var(--text-dim, #8b91a1);
  display: flex; align-items: center; justify-content: center; font-size: 11px;
}

/* ── Gradient face controls ── */
.deck-ed-gradwrap { display: flex; flex-direction: column; gap: 7px; }
.deck-ed-gradwrap > .deck-ed-label { margin-top: 3px; }
.deck-ed-gradchips { display: grid; grid-template-columns: repeat(auto-fill, minmax(38px, 1fr)); gap: 6px; }
.deck-ed-gradchip {
  height: 30px; border-radius: 8px; cursor: pointer; padding: 0;
  border: 1px solid rgba(255, 255, 255, .18);
}
.deck-ed-gradchip.sel { box-shadow: 0 0 0 2px var(--panel, #14161c), 0 0 0 4px rgba(var(--accent-rgb, 43, 108, 255), .9); }

/* Backdrop-picture controls: preview trash + dim slider + auto-tint. */
.deck-ed-imgdel { flex: 0 0 auto; width: 34px; padding: 8px 0; font-size: 15px; line-height: 1; }
.deck-ed-dimrow { display: flex; align-items: center; gap: 10px; }
.deck-ed-dimrow .deck-ed-label { flex: 0 0 auto; }
.deck-ed-range { flex: 1; accent-color: var(--accent, #2b6cff); min-width: 0; }
.deck-ed-autotint { align-self: flex-start; }

/* Bold toggle sits with the label-size segment; a serif "B" reads as weight. */
.deck-ed-boldbtn { font-weight: 800; font-family: Georgia, 'Times New Roman', serif; min-width: 38px; }

/* ── Live key preview: leads the side rail — always in view while editing,
   on every screen (the old floating preview vanished under 760px, i.e. on the
   Xeneon itself). The cap scales with the viewport so short screens still fit. */
.deck-ed-preview {
  flex: 0 0 auto; width: 100%;
  display: flex; flex-direction: column; align-items: center; gap: 10px;
}
.deck-ed-preview-caption {
  font-size: 10px; font-weight: 600; text-transform: uppercase; letter-spacing: .09em;
  color: var(--text-dim, #8b91a1);
}
.deck-ed-preview-stage {
  padding: clamp(10px, 3vh, 20px); border-radius: 20px;
  background:
    linear-gradient(180deg, rgba(255, 255, 255, .05), transparent 16%),
    linear-gradient(180deg, #2a2d34, #1a1c22 52%, #0e1014);
  border: 1px solid rgba(0, 0, 0, .6);
  box-shadow: 0 18px 44px rgba(0, 0, 0, .5), inset 0 1.5px 0 rgba(255, 255, 255, .08);
}
/* The preview cap is sized off a fixed footprint (no grid to derive it from). */
.deck-ed-preview-root { --ed-cap: clamp(84px, 24vh, 132px); --deck-key-min: var(--ed-cap); }
.deck-ed-preview-root .deck-key { width: var(--ed-cap); height: var(--ed-cap); cursor: default; }
/* No edit-mode jiggle / hover lift in a static preview. */
.deck-ed-preview-root .deck-key:hover { transform: none; filter: none; }

/* Style clipboard: copy / paste / apply-to-page — stacked in the side rail. */
.deck-ed-styletools { display: flex; flex-direction: column; gap: 6px; width: 100%; }
.deck-ed-styletools .deck-ed-btn { flex: 0 0 auto; width: 100%; }
.deck-ed-styletools .deck-ed-btn:disabled { opacity: .4; cursor: default; }
/* Saved key presets live in the rail too: full-width chips under the preview. */
.deck-ed-side .deck-ed-presets { width: 100%; margin: 0; }

/* Narrow viewport: the rail folds into a compact horizontal strip above the
   form — the preview stays visible, presets/tools scroll beside it. */
@media (max-width: 760px) {
  .deck-ed-modal { width: 96vw; height: 94vh; }
  .deck-ed-body { flex-direction: column; }
  .deck-ed-side {
    flex: 0 0 auto; flex-direction: row; align-items: center; gap: 12px;
    padding: 10px 14px; overflow-x: auto; overflow-y: hidden;
    border-right: 0; border-bottom: 1px solid rgba(255, 255, 255, .07);
  }
  .deck-ed-side .deck-ed-presets { width: auto; flex: 0 0 auto; flex-wrap: nowrap; }
  .deck-ed-preview { width: auto; }
  .deck-ed-preview-caption { display: none; }
  .deck-ed-preview-stage { padding: 8px; border-radius: 14px; }
  .deck-ed-preview-root { --ed-cap: 64px; }
  .deck-ed-styletools { flex-direction: row; width: auto; }
  .deck-ed-styletools .deck-ed-btn { width: auto; }
}
/* Short viewports (the Xeneon Edge is wide but low): trim the chrome so the
   panes keep usable height. */
@media (max-height: 520px) {
  .deck-ed-backdrop { padding: 8px; }
  .deck-ed-modal { height: 96vh; border-radius: 16px; }
  .deck-ed-head { padding: 10px 16px 8px; }
  .deck-ed-actions { padding: 8px 16px 10px; }
  .deck-ed-main { padding: 10px 16px 8px; }
  .deck-ed-tabs { margin-bottom: 10px; }
}

/* Keys suppress the native long-press menu / callout / text-select, but keep
   'manipulation' so a swipe still pans the dashboard pager (a STATIONARY press
   doesn't pan, so press-and-hold still fires); 'none' froze the pager swipe. */
.deck-key { -webkit-touch-callout: none; -webkit-user-select: none; user-select: none; touch-action: manipulation; }

/* Multi-Action: a list of step cards inside a trigger's action editor */
/* Custom-select dropdowns inside the editor: fill the field and match the inputs
   (the styled trigger replaces the hidden native <select>). */
.deck-ed-modal .cs-wrap { width: 100%; }
.deck-ed-step-head .cs-wrap { flex: 1; width: auto; min-width: 0; }
.deck-ed-modal .cs-trigger {
  width: 100%; min-width: 0;
  padding: 9px 11px; font-size: 13px; font-weight: 500;
  background: rgba(255, 255, 255, .045);
  border: 1px solid rgba(255, 255, 255, .12);
  border-radius: 10px; color: var(--text, #e7e9ee);
}
.deck-ed-modal .cs-trigger:hover { border-color: rgba(var(--accent-rgb, 43, 108, 255), .4); }
.deck-ed-modal .cs-wrap.cs-open .cs-trigger {
  border-color: rgba(var(--accent-rgb, 43, 108, 255), .6);
  box-shadow: 0 0 0 3px rgba(var(--accent-rgb, 43, 108, 255), .14);
}
.deck-ed-modal .cs-label { flex: 1; overflow: hidden; text-overflow: ellipsis; }

.deck-ed-steps { display: flex; flex-direction: column; gap: 8px; }
.deck-ed-step { border: 1px solid rgba(255, 255, 255, .1); border-radius: 11px; padding: 9px; background: rgba(255, 255, 255, .025); }
.deck-ed-step-head { display: flex; gap: 8px; align-items: center; }
.deck-ed-step-head .deck-ed-input { flex: 1; }
.deck-ed-step-tools { display: flex; gap: 4px; flex: 0 0 auto; }
.deck-ed-stepbtn {
  width: 34px; height: 36px; border-radius: 8px; cursor: pointer;   /* touch-sized reorder/remove */
  background: rgba(255, 255, 255, .05); border: 1px solid rgba(255, 255, 255, .12); color: var(--text, #e7e9ee);
}
.deck-ed-stepbtn[disabled] { opacity: .3; cursor: default; }
.deck-ed-stepbtn.danger { color: var(--color-danger); border-color: rgba(var(--danger-rgb), .35); }
.deck-ed-stepdelay { display: flex; flex-direction: column; gap: 4px; margin-bottom: 8px; }
.deck-ed-step .deck-ed-params { margin-top: 8px; }
.deck-ed-addstep {
  padding: 9px; border-radius: 10px; cursor: pointer; font-weight: 600;
  background: rgba(var(--accent-rgb, 43, 108, 255), .14); border: 1px dashed rgba(var(--accent-rgb, 43, 108, 255), .5);
  color: var(--text, #e7e9ee);
}
.deck-ed-addstep:hover { background: rgba(var(--accent-rgb, 43, 108, 255), .22); }

/* LED reaction editor section: tighter sub-rows + a muted hint line. */
.deck-ed-subfield { margin-top: 8px; }
.deck-ed-hint { margin-top: 6px; font-size: 11px; line-height: 1.35; opacity: .6; }

/* Hotkey composer: modifier toggle chips + a single main-key capture field + a live
   preview of the resulting combo. Chips avoid having to physically hold Win, which
   would otherwise fire the OS shortcut before the page could capture it. */
.deck-ed-hotkey { display: flex; flex-direction: column; gap: 8px; }
.deck-ed-hotkey-mods { display: flex; gap: 6px; flex-wrap: wrap; }
.deck-ed-hotkey-mod {
  flex: 1 1 0; min-width: 56px; padding: 8px 6px; border-radius: 10px; cursor: pointer;
  background: rgba(255, 255, 255, .05); border: 1px solid rgba(255, 255, 255, .12);
  color: var(--text-dim, #8b91a1); font-weight: 600; font-size: 13px;
  transition: background .15s, border-color .15s, color .15s;
}
.deck-ed-hotkey-mod:hover { color: var(--text, #e7e9ee); border-color: rgba(255, 255, 255, .22); }
.deck-ed-hotkey-mod.active {
  background: rgba(var(--accent-rgb, 43, 108, 255), .18);
  border-color: rgba(var(--accent-rgb, 43, 108, 255), .55);
  color: var(--text, #e7e9ee);
}
.deck-ed-hotkey-row { display: flex; gap: 8px; align-items: stretch; }
.deck-ed-hotkey-input { flex: 1; cursor: pointer; text-align: center; text-transform: uppercase; font-variant-numeric: tabular-nums; letter-spacing: .04em; }
.deck-ed-hotkey-input:focus { caret-color: transparent; }
.deck-ed-hotkey-clear {
  flex: 0 0 auto; width: 38px; border-radius: 10px; cursor: pointer;
  background: rgba(255, 255, 255, .05); border: 1px solid rgba(255, 255, 255, .12);
  color: var(--text-dim, #8b91a1); font-size: 13px;
}
.deck-ed-hotkey-clear:hover { color: var(--color-danger); border-color: rgba(var(--danger-rgb), .35); }
.deck-ed-hotkey-preview {
  text-align: center; font-weight: 700; letter-spacing: .04em; font-variant-numeric: tabular-nums;
  padding: 6px; border-radius: 8px; background: rgba(255, 255, 255, .04);
  color: var(--text, #e7e9ee); text-transform: uppercase; font-size: 13px;
}
.deck-ed-hotkey-preview.is-empty { color: var(--text-dim, #8b91a1); font-weight: 500; text-transform: none; letter-spacing: 0; }

/* ---- Mini-mixer overlay (appMixer key): live per-app volume faders ---- */
/* Mirrors the editor's glass modal; faders are enlarged from the compact audio
   panel rows so they're comfortable on the Xeneon touchscreen. */
.deck-mix-backdrop {
  position: fixed; inset: 0; z-index: 62;
  background: rgba(8, 10, 14, .55);
  -webkit-backdrop-filter: blur(22px) saturate(120%);
  backdrop-filter: blur(22px) saturate(120%);
  display: flex; align-items: center; justify-content: center; padding: 16px;
  opacity: 0; transition: opacity .18s ease;
}
.deck-mix-backdrop.visible { opacity: 1; }
.deck-mix-modal {
  width: min(420px, 95vw); max-height: 86vh; overflow-y: auto;
  background:
    radial-gradient(120% 90% at 50% -10%, rgba(var(--accent-rgb, 43, 108, 255), .16), transparent 60%),
    linear-gradient(180deg, rgba(28, 32, 42, .96), rgba(16, 18, 24, .96));
  border: 1px solid rgba(255, 255, 255, .12);
  border-radius: 22px; padding: 18px 20px 20px;
  box-shadow: 0 26px 70px rgba(0, 0, 0, .6), inset 0 1px 0 rgba(255, 255, 255, .06);
  color: var(--text, #e7e9ee);
  transform: translateY(10px) scale(.97); transition: transform .22s cubic-bezier(.2, .8, .2, 1);
  scrollbar-gutter: stable;
}
.deck-mix-backdrop.visible .deck-mix-modal { transform: none; }
.deck-mix-head { display: flex; align-items: center; justify-content: space-between; margin-bottom: 14px; }
.deck-mix-title { margin: 0; font-size: 15px; font-weight: 700; }
.deck-mix-close {
  width: 30px; height: 30px; border-radius: 50%; cursor: pointer; flex: 0 0 auto;
  background: rgba(255, 255, 255, .06); border: 1px solid rgba(255, 255, 255, .12);
  color: var(--text, #e7e9ee); font-size: 14px; line-height: 1;
}
.deck-mix-close:hover { background: rgba(255, 255, 255, .12); }
.deck-mix-apps { display: flex; flex-direction: column; gap: 14px; }
.deck-mix-empty { padding: 24px 8px; text-align: center; font-size: 13px; opacity: .6; }
/* Enlarge the reused .app-mix-* rows for touch inside the overlay. */
.deck-mix-apps .app-mix-row { grid-template-columns: 30px minmax(60px, 110px) 1fr auto 36px; gap: 12px; }
.deck-mix-apps .app-mix-icon { width: 30px; height: 30px; border-radius: 8px; }
.deck-mix-apps .app-mix-name { font-size: 13px; }
.deck-mix-apps .app-mix-slider { height: 6px; }
.deck-mix-apps .app-mix-slider::-webkit-slider-thumb { width: 22px; height: 22px; }
.deck-mix-apps .app-mix-slider::-moz-range-thumb { width: 22px; height: 22px; }
.deck-mix-apps .app-mix-vol { font-size: 12px; min-width: 36px; }
.deck-mix-apps .app-mix-mute { width: 36px; height: 36px; }
.deck-mix-apps .app-mix-mute svg { width: 16px; height: 16px; }
