/* ==========================================================
   IROZON DESIGN SYSTEM — Theme Toggle (canonical, v1)
   Public API:
     <button
       class="iz-theme-toggle"
       type="button"
       aria-label="Switch to dark theme"
       aria-pressed="false"
     >
       <span class="iz-theme-toggle__track" aria-hidden="true">
         <span class="iz-theme-toggle__indicator"></span>
       </span>
     </button>

   A compact Light/Dark control for navigation, app headers and
   product chrome — NOT a general binary setting (that's .iz-switch,
   which stays the canonical settings control: horizontal, always
   shows its track). Theme Toggle's defining behavior is a reveal:
   at rest only the state dot shows, centered in the 44x44px hit
   area, with no visible track — hover/focus-visible reveals a small
   vertical track and the dot moves to its current-theme position;
   leaving hover/focus collapses the track and returns the dot to
   center WITHOUT changing the selected theme. Only an actual click
   changes the theme.

   State model: `aria-pressed` on the button is the CSS state API —
   "true" means dark is the currently SELECTED theme (light -> upper
   dot position, dark -> lower). This is deliberately independent from
   the AMBIENT page theme (`html[data-theme]`/prefers-color-scheme),
   which instead drives the dot's REST color (Ink on a light page,
   Lime on a dark page) via the same guarded selector pattern
   design-tokens/themes.css already uses. In real usage the two always
   agree (the toggle reflects the page's actual theme), but they are
   independent controls so the two-attribute model stays correct even
   if this component were ever swapped between pages mid-transition.

   A real <button>, not a div/span with a click handler — native
   keyboard activation (Space/Enter) and focus come for free. The
   controller that actually flips `data-theme`, persists the choice,
   and updates `aria-pressed`/`aria-label` is the consuming
   application's job (see the docs page for a minimal vanilla-JS
   example) — this file is presentation only, no persistence/FOUC
   logic baked in.
   ========================================================== */

.iz-theme-toggle {
  /* Component-local, not global tokens — same rationale as
     Signature CTA's --iz-signature-duration: a bespoke feel specific
     to this one reveal, not yet proven reusable elsewhere. -travel is
     the dot finding a state position (center->state, state<->state);
     -collapse is the calmer return to center, deliberately shorter
     and without overshoot (see the indicator rules below). */
  --iz-theme-toggle-travel-duration: 380ms;
  --iz-theme-toggle-collapse-duration: 320ms;

  position: relative;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  inline-size: 44px;
  block-size: 44px;
  padding: 0;
  border: none;
  border-radius: var(--r-xs);
  background: none;
  cursor: pointer;
  -webkit-tap-highlight-color: transparent;
}

/* ---------- Track ----------
   Structural only — establishes the 20x32 positioning box (centered
   in the button via inset+auto-margin, the same technique Switch's
   thumb uses) and the containing block the indicator's inset-*
   values below are measured against. It owns no visible chrome and
   no opacity of its own: opacity on this element would apply to its
   entire descendant subtree, including the indicator, which is
   exactly the bug this fixes (a bare `.track { opacity: 0 }` used to
   take the indicator down with it at rest). All revealable chrome
   (background, ring) lives on ::before instead — a sibling layer to
   the indicator in the render tree, not an ancestor, so fading it
   out can never affect the indicator. */
.iz-theme-toggle__track {
  position: absolute;
  inset: 0;
  margin: auto;
  inline-size: 20px;
  block-size: 32px;
  pointer-events: none;
}
.iz-theme-toggle__track::before {
  content: "";
  position: absolute;
  inset: 0;
  border-radius: var(--r-xs);
  background: var(--color-surface-subtle);
  /* An inset ring, not a real `border` — the indicator nests inside
     .track (a sibling of this pseudo-element) and its own
     inset-block-start values (below) are measured against .track's
     padding edge. A real border shrinks that padding box, which is
     exactly what threw Switch's thumb off center before; box-shadow
     paints the same 1px ring without touching the box model, so
     4px/16px below land exactly. */
  box-shadow: inset 0 0 0 1px var(--color-border);
  opacity: 0;
  /* Calm fade only — no spring, no scale. --motion-duration-standard
     (220ms) rather than -fast (150ms): a touch slower than a plain
     hover-state color change, matching the slightly more deliberate
     feel of a reveal instead of a simple tint shift. */
  transition:
    opacity var(--motion-duration-standard) var(--motion-ease-standard),
    background-color var(--motion-duration-standard) var(--motion-ease-standard),
    box-shadow var(--motion-duration-standard) var(--motion-ease-standard);
}
:root[data-theme="dark"] .iz-theme-toggle__track::before {
  background: var(--color-surface-elevated);
}
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]):not([data-theme="dark"]) .iz-theme-toggle__track::before {
    background: var(--color-surface-elevated);
  }
}

/* ---------- Indicator ----------
   Nests inside .track (per the public markup), so its containing
   block is the 20x32 track, not the 44x44 button — but since the
   track is itself always centered in the button (above), an
   indicator centered in the track is transitively centered in the
   button too. Rest: inset:0 + margin:auto, both axes — the track is
   invisible, so this is the only thing on screen at rest, and this
   is what puts it in the control's true visual center. Reveal states
   below override only inset-block-start/-end (values measured from
   the track's own top edge, e.g. 4px / 16px within its 32px height),
   leaving inset-inline-start/-end + margin untouched — horizontal
   position never changes, so it is never re-specified, and vertical
   position is plain `top` values rather than transform, so there is
   nothing for a transform-based movement to conflict with. */
.iz-theme-toggle__indicator {
  position: absolute;
  inset: 0;
  margin: auto;
  inline-size: 12px;
  block-size: 12px;
  border-radius: 50%;
  background: var(--irozon-ink);
  /* Collapse (state -> center) — this base rule is what applies the
     instant hover/focus/active ends, so it owns the "returning home"
     motion: calmer and a touch quicker than the reveal move below,
     and deliberately plain easing (no overshoot) — the dot settling
     back to its neutral position doesn't need a moment of personality,
     only finding a state does. */
  transition: inset-block-start var(--iz-theme-toggle-collapse-duration) var(--motion-ease-standard);
}
:root[data-theme="dark"] .iz-theme-toggle__indicator {
  background: var(--irozon-lime);
}
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]):not([data-theme="dark"]) .iz-theme-toggle__indicator {
    background: var(--irozon-lime);
  }
}

/* ---------- Reveal ----------
   Hover, keyboard focus, and touch (:active covers a tap that never
   triggers :hover) all reveal the same way — one tap must still
   perform the actual toggle via the button's real click handler,
   this is presentation only. Light -> upper position is the default
   reveal target; [aria-pressed="true"] (dark selected) overrides to
   the lower position via higher selector specificity, no !important
   needed. */
.iz-theme-toggle:hover .iz-theme-toggle__track::before,
.iz-theme-toggle:focus-visible .iz-theme-toggle__track::before,
.iz-theme-toggle:active .iz-theme-toggle__track::before {
  opacity: 1;
}
.iz-theme-toggle:hover .iz-theme-toggle__indicator,
.iz-theme-toggle:focus-visible .iz-theme-toggle__indicator,
.iz-theme-toggle:active .iz-theme-toggle__indicator {
  inset-block-start: 4px;
  inset-block-end: auto;
  /* Travel (center -> state, and state <-> state while already
     revealed) — slower than collapse, and --ease-signature (the one
     reserved springy exception in the motion scale) gives the dot a
     single restrained overshoot as it finds its position, rather than
     a mechanical linear-feeling stop. One settle, not a bounce: the
     curve has exactly one overshoot hump, not an oscillation. */
  transition: inset-block-start var(--iz-theme-toggle-travel-duration) var(--ease-signature);
}
.iz-theme-toggle[aria-pressed="true"]:hover .iz-theme-toggle__indicator,
.iz-theme-toggle[aria-pressed="true"]:focus-visible .iz-theme-toggle__indicator,
.iz-theme-toggle[aria-pressed="true"]:active .iz-theme-toggle__indicator {
  inset-block-start: 16px;
}

/* ---------- Focus ----------
   The ring sits on the compact 20x32 track, not the full 44x44 hit
   area — an accessible target can be larger than its own visible
   focus ring. Canonical Green, never Blue. */
.iz-theme-toggle:focus-visible .iz-theme-toggle__track {
  outline: 2px solid var(--color-focus);
  outline-offset: 2px;
}

@media (prefers-reduced-motion: reduce) {
  .iz-theme-toggle__track::before,
  .iz-theme-toggle__indicator,
  .iz-theme-toggle:hover .iz-theme-toggle__indicator,
  .iz-theme-toggle:focus-visible .iz-theme-toggle__indicator,
  .iz-theme-toggle:active .iz-theme-toggle__indicator {
    /* Selectors here deliberately match the hover/focus/active
       indicator rules' own specificity (0,0,3,0), not just the base
       .indicator — otherwise those more-specific rules' own
       transition (the overshoot) would win over this one regardless
       of the media query, since specificity is compared per-rule and
       is unaffected by which rule sits inside a media block. */
    transition: none;
  }
}
