/* Lagos Life shared state model (STRUCTURE.md section 5).
   Seeded by the Phase 4 architect; owner from Phase 5 on: 5c. Every change goes in changelog.html.

   Use: add .ll-interactive to every tappable component (buttons, chips, tabs, rows, pins).
        Add .ll-selectable as well when its selected state is the ink fill.
   A component sets its own elevation through the private variable --_shadow, never box-shadow
   directly, so the focus halo can sit on top of it.

   States come from native pseudo-classes and ARIA, never from .is-* classes:
     hover        :hover inside @media (hover: hover)
     pressed      :active
     focus        :focus-visible
     selected     [aria-pressed="true"] [aria-selected="true"] [aria-current="page"] [aria-checked="true"]
     inactive     [aria-disabled="true"] (and :disabled for native form controls)
     loading      [aria-busy="true"]
     error        [aria-invalid="true"]
   Docs specimens may force a state with data-demo-state="hover|pressed|focus". */

.ll-interactive {
  position: relative;
  isolation: isolate;
  cursor: pointer;
  -webkit-tap-highlight-color: transparent;
  box-shadow: var(--_shadow, none);
  transition:
    transform var(--duration-press) ease,
    box-shadow var(--duration-press) ease,
    background-color var(--duration-state) var(--ease-standard),
    color var(--duration-state) var(--ease-standard);
}

/* State layer: the content colour at 8% (hover) or 10% (pressed), painted over the fill, under the label. */
.ll-interactive::before {
  content: "";
  position: absolute;
  inset: 0;
  z-index: -1;
  border-radius: inherit;
  background: currentColor;
  opacity: 0;
  pointer-events: none;
  transition: opacity var(--duration-state) var(--ease-standard);
}

/* Hit area: always at least --tap-min (44px) square, centred on the visible shape. */
.ll-interactive::after {
  content: "";
  position: absolute;
  left: 50%;
  top: 50%;
  width: max(100%, var(--tap-min));
  height: max(100%, var(--tap-min));
  transform: translate(-50%, -50%);
}

@media (hover: hover) {
  .ll-interactive:not([aria-disabled="true"], :disabled, [aria-busy="true"]):hover::before { opacity: var(--state-hover); }
}
.ll-interactive[data-demo-state="hover"]::before { opacity: var(--state-hover); }

.ll-interactive:not([aria-disabled="true"], :disabled, [aria-busy="true"]):active,
.ll-interactive[data-demo-state="pressed"] { transform: scale(var(--press-scale)); }
.ll-interactive:not([aria-disabled="true"], :disabled, [aria-busy="true"]):active::before,
.ll-interactive[data-demo-state="pressed"]::before { opacity: var(--state-pressed); }

/* Focus: 3px --ring outline, 2px offset, the offset gap filled with --focus-halo so it shows on any world. */
.ll-interactive:focus-visible,
.ll-interactive[data-demo-state="focus"] {
  outline: var(--focus-width) solid var(--ring);
  outline-offset: var(--focus-offset);
  box-shadow: 0 0 0 var(--focus-offset) var(--focus-halo), var(--_shadow, 0 0 transparent);
}

/* Selected: the one selected style. Ink fill, ink-on text. */
.ll-selectable:is([aria-pressed="true"], [aria-selected="true"], [aria-current="page"], [aria-checked="true"]) {
  background-color: var(--ink-fill);
  color: var(--ink-on);
}

/* Inactive: stays focusable, explains why on tap. Mist fill, inactive text (3:1 or better), no elevation, no press. */
.ll-interactive:is([aria-disabled="true"], :disabled) {
  background-color: var(--mist);
  color: var(--ink-inactive);
  --_shadow: none;
  cursor: not-allowed;
}

/* Loading: keeps its size and focus; the component draws its own indicator. */
.ll-interactive[aria-busy="true"] { cursor: progress; }

@media (prefers-reduced-motion: reduce) {
  .ll-interactive,
  .ll-interactive::before { transition: none; }
  /* Same specificity as the press rule above, so the scale really is removed (fixed 15 Oct 2026). */
  .ll-interactive:not([aria-disabled="true"], :disabled, [aria-busy="true"]):active,
  .ll-interactive[data-demo-state="pressed"] { transform: none; }
}
