/*! ui-states.css — InkSight shared UI state & motion layer (MOTION2).
 *
 * ONE shared engine for all three verticals (Lorcana /, One Piece /op/,
 * Riftbound /riftbound/). Per-game variation is a single accent token,
 * never a second implementation.
 *
 * Loaded on every page AFTER tokens.css and BEFORE page-local <style>, so
 * page-local rules still win by cascade. Everything here is a *default* —
 * it fills gaps rather than overriding deliberate page design.
 *
 * Contents
 *   1. Motion tokens (house easing + durations)
 *   2. Interactive state defaults (hover / focus-visible / active / disabled)
 *   3. Loading skeletons (geometry-preserving, zero CLS)
 *   4. Empty & error states
 *   5. Reduced-motion gate
 */

/* ── 1. Motion tokens ─────────────────────────────────────────────── */
:root{
  /* House easing for UI state changes. Distinct from tokens.css --ease
     (page/scroll motion) so changing one never silently retunes the other. */
  --ui-ease:cubic-bezier(.2,.7,.3,1);
  --ui-fast:140ms;   /* chips, steppers, small toggles */
  --ui-mid:180ms;    /* buttons, tiles, cards */

  /* Focus ring. Per-game accent, gold default (Lorcana canonical). */
  --ui-accent:var(--gold,#F5B53D);
  --ui-ring-w:2px;
  --ui-ring-offset:3px;
}
html[data-ink-game="op"]        {--ui-accent:#EE5340}
html[data-ink-game="riftbound"] {--ui-accent:#A78BFA}

/* ── 2. Interactive state defaults ────────────────────────────────── */

/* focus-visible: never a bare outline:none. Anything that removed the UA
   outline gets a real ring back here. Keyboard-only — pointer clicks on
   buttons do not trigger :focus-visible in any current engine. */
:where(a[href], button, [role="button"], [role="tab"], [role="switch"],
       input, select, textarea, summary, [tabindex]:not([tabindex="-1"]))
:focus-visible,
:where(a[href], button, [role="button"], [role="tab"], [role="switch"],
       input, select, textarea, summary,
       [tabindex]:not([tabindex="-1"])):focus-visible{
  outline:var(--ui-ring-w) solid var(--ui-accent);
  outline-offset:var(--ui-ring-offset);
  border-radius:inherit;
}
/* Inputs sit flush in wells; a 3px offset ring clips against neighbours. */
:where(input, select, textarea):focus-visible{outline-offset:2px}

/* active: subtle press. Scale is the house tell; brightness for elements
   whose transform is already owned by a hover lift. */
:where(button, [role="button"], .btn, .chip, .mkb-chip, .gchip,
       .setchip, .pstyle, .dlmode, .subtab, .kw-toggle, .tbuy, .bbuy)
:not(:disabled):not([aria-disabled="true"]):active{
  transform:scale(.976);
}
:where(button,[role="button"],.btn,.chip,.mkb-chip,.gchip,.setchip,.pstyle,
       .dlmode,.subtab,.kw-toggle,.tbuy,.bbuy):not(:disabled):not([aria-disabled="true"]):active{
  transform:scale(.976);
}

/* disabled: dimmed, not-allowed, and provably inert on hover. */
:where(button, [role="button"], .btn, input, select, textarea)
:disabled,
:where(button,[role="button"],.btn,input,select,textarea):disabled,
[aria-disabled="true"]{
  opacity:.45;
  cursor:not-allowed;
  filter:none;
}
:where(button,[role="button"],.btn):disabled:hover,
[aria-disabled="true"]:hover{
  transform:none !important;
  filter:none !important;
  box-shadow:none !important;
  border-color:var(--line,rgba(255,255,255,.12)) !important;
}

/* Tap targets: mobile minimum. Applies only where a page hasn't already
   sized the control larger. */
@media(max-width:640px){
  :where(button,[role="button"],.btn,a.btn,.chip,.mkb-chip,.gchip){
    min-height:44px;
  }
}

/* ── 3. Loading skeletons ─────────────────────────────────────────── */
/* Skeletons reuse each grid's REAL tile classes (.tile/.art-wrap/.tfoot/
   .name/.stats…) and only paint blocks inside them. Geometry therefore
   comes from the same CSS the loaded tile uses — zero layout shift on
   data arrival, by construction rather than by measurement. */

.is-sk{pointer-events:none;user-select:none}
.is-sk *{color:transparent !important}

/* The paint: a single static gradient wash. No sweep keyframe by default,
   so a page full of skeletons costs nothing on the compositor. */
.sk-b,
.is-sk .art-wrap::after{
  background:linear-gradient(100deg,
    rgba(255,255,255,.045) 0%,
    rgba(255,255,255,.085) 45%,
    rgba(255,255,255,.045) 100%);
  border-radius:6px;
}
.sk-b{
  display:block;
  min-height:.72em;
  animation:sk-breathe 1.2s var(--ui-ease) infinite alternate;
}
.is-sk .art-wrap{background:#06070C}
.is-sk .art-wrap::after{
  content:"";position:absolute;inset:0;border-radius:0;
  animation:sk-breathe 1.2s var(--ui-ease) infinite alternate;
}
/* Text-run helper: stacked lines inside a real .name/.meta box so the box
   keeps its min-height and line-clamp metrics. */
.sk-line{display:block;height:.72em;margin:.19em 0;border-radius:5px;
  background:rgba(255,255,255,.07);
  animation:sk-breathe 1.2s var(--ui-ease) infinite alternate}

@keyframes sk-breathe{from{opacity:.55}to{opacity:1}}

/* ── 4. Empty & error states ──────────────────────────────────────── */
/* Matches the dashed-border .empty already used on watchlist/trade, but
   adds room for the one useful action every empty state should carry. */
.ink-empty{
  padding:26px 22px;
  text-align:center;
  border:1px dashed var(--line,rgba(255,255,255,.12));
  border-radius:14px;
  background:rgba(255,255,255,.015);
  grid-column:1 / -1;
}
.ink-empty-t{
  font-family:var(--font-display,Sora,system-ui,sans-serif);
  font-weight:600;font-size:14.5px;
  color:var(--text,#F1F4FB);
  margin:0 0 5px;
}
.ink-empty-p{
  font-size:12.5px;line-height:1.55;
  color:var(--dim,#BAC2D6);
  margin:0 auto;max-width:44ch;
}
.ink-empty-a{
  display:inline-flex;align-items:center;gap:6px;
  margin-top:12px;min-height:38px;padding:9px 16px;
  border:1px solid color-mix(in srgb, var(--ui-accent) 40%, transparent);
  border-radius:10px;
  background:color-mix(in srgb, var(--ui-accent) 10%, transparent);
  color:var(--ui-accent);
  font-family:var(--font-display,Sora,system-ui,sans-serif);
  font-weight:600;font-size:12.5px;text-decoration:none;
  transition:background-color var(--ui-fast) var(--ui-ease),
             border-color var(--ui-fast) var(--ui-ease),
             transform var(--ui-fast) var(--ui-ease);
}
.ink-empty-a:hover{
  background:color-mix(in srgb, var(--ui-accent) 18%, transparent);
  border-color:color-mix(in srgb, var(--ui-accent) 65%, transparent);
}
.ink-empty.is-err{border-style:solid;
  border-color:color-mix(in srgb, var(--down,#E5574F) 35%, var(--line,#2D3452))}



/* ── 4b. Named high-traffic controls ──────────────────────────────────
   These four control groups were called out as priority surfaces and each
   was missing at least one state. They are declared here, at normal
   specificity, rather than in their own files because their source is
   fenced and must not be edited:
     .gb-seg   game switcher   -> public/game-bridge.js  (fenced)
     .rip-cta / .opencta  pack lab rip button -> public/*packs.html (fenced)
   Declaring them in the shared layer is the only way to give them states
   without breaching the fence, and it keeps all three games identical. */
.gb-seg:focus-visible,
.rip-cta:focus-visible,
.opencta:focus-visible,
.rk-donebtn:focus-visible{
  outline:2px solid var(--ui-accent);
  outline-offset:3px;
}
.gb-seg:active,
.rip-cta:active,
.opencta:active,
.rk-donebtn:active{ transform:scale(.976) }

.gb-seg[aria-disabled="true"],
.rip-cta:disabled,.rip-cta[aria-disabled="true"],
.opencta:disabled,.opencta[aria-disabled="true"],
.rk-donebtn:disabled{
  opacity:.45; cursor:not-allowed; pointer-events:none;
}

@media (prefers-reduced-motion: reduce){
  .gb-seg:active,.rip-cta:active,.opencta:active,.rk-donebtn:active{transform:none}
}

/* ── 5. Reduced-motion gate ───────────────────────────────────────── */

@media (prefers-reduced-motion: reduce){
  .sk-b,.sk-line,.is-sk .art-wrap::after{animation:none !important;opacity:.8 !important}
  :where(button,[role="button"],.btn,.chip,.mkb-chip,.gchip,.setchip,
         .pstyle,.dlmode,.subtab,.kw-toggle,.tbuy,.bbuy):active{transform:none}
  .ink-empty-a{transition:none}

  /* MOTION2 — global reduced-motion safety net.
     The platform carries ~150 infinite CSS animations across ~40 files
     (foil shimmers, holo drifts, pulses, marquees, spinners). Gating each
     one at its source is both unreliable and impossible on the fenced
     pack-lab/premium files, so we enforce the contract centrally instead.

     Durations collapse to ~0 rather than `animation:none` on purpose:
     several of these animations are the thing that reveals content
     (fade/slide-in), so cancelling them outright would leave elements
     stuck at their invisible from-state. Near-zero duration lets them
     jump straight to their final frame.

     iteration-count:1 is what actually stops the looping/vestibular
     motion this media query exists to prevent. */
  *,*::before,*::after{
    animation-duration:.01ms !important;
    animation-iteration-count:1 !important;
    animation-delay:0s !important;
    transition-duration:.01ms !important;
    transition-delay:0s !important;
    scroll-behavior:auto !important;
  }
}

