@layer components {
  /*
  ▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬
   🅸🅽🅿🆄🆃 🅾🆃🅿
  ▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬
  Stripe-style OTP input: a single hidden <input> handles keyboard, paste,
  and autocomplete while visual cells render each digit with individual borders.

  Usage:
    <l-input-otp>
      <input type="text" inputmode="numeric"
             autocomplete="one-time-code" maxlength="6" />
    </l-input-otp>

  @scope keeps these rules from leaking OUT, and the reset below strips UA default
  chrome off the generated cells / separators / hidden input so the component styles
  from a clean slate. Caveat: this lives in @layer components, so it only shields the
  internals from UA defaults and lower-layer rules — *unlayered* host-page rules (a
  CSS reset, Tailwind preflight, naked `input {}`) still outrank every cascade layer
  and are NOT neutralized here.
  */

  @scope (l-input-otp) {
    /* Reset — :where() keeps specificity at (0,0,0) so the component rules below win
       over it. Strips UA default styling off the generated internals for a clean
       slate. Custom properties are NOT affected by `all` — design tokens cascade
       through. :scope is excluded — the host is a unique custom element name and gets
       its styles from the :scope blocks below without interference. */
    *:where(:not(img, svg, l-icon):not(svg *)),
    *::before,
    *::after {
      all: unset;
      display: revert;
    }

    :scope {
      --digits: 6;
      --cell-size: 2.75rem;
      --cell-bg-color: color-mix(in oklab, var(--l-color-text-primary) 4%, var(--l-color-surface));
      --cell-border-color: var(--l-color-border);
      --cell-border-radius: var(--l-radius-md);
      --cell-focus-color: var(--l-focus-ring);
      --cell-focus-ring: 0 0 0 1px var(--cell-focus-color);
      --_cell-font:
        ui-monospace, 'Cascadia Code', 'Source Code Pro', Menlo, Consolas, 'DejaVu Sans Mono',
        monospace;

      display: inline-block;
      position: relative;
    }

    /*
    ▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬
     🅲🅴🅻🅻🆂 🅲🅾🅽🆃🅰🅸🅽🅴🆁
    ▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬
    */

    .l-input-otp-cells {
      display: inline-flex;
      align-items: center;
      gap: var(--cell-gap, 0.5rem);
      position: relative;
    }

    /*
    ▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬
     🅲🅴🅻🅻
    ▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬
    */

    .l-input-otp-cell {
      display: flex;
      align-items: center;
      justify-content: center;
      inline-size: var(--cell-size);
      block-size: var(--cell-size);
      border: 1px solid var(--cell-border-color);
      border-radius: var(--cell-border-radius);
      background: var(--cell-bg-color);
      font-family: var(--_cell-font);
      font-size: calc(var(--cell-size) * 0.45);
      font-variant-numeric: tabular-nums;
      line-height: 1;
      color: var(--l-color-text-primary);
      transition:
        border-color 150ms ease,
        box-shadow 150ms ease;
      pointer-events: none;
    }

    /*
    ▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬
     🅰🅲🆃🅸🆅🅴 🅲🅴🅻🅻 (focus highlight)
    ▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬
    */

    :scope:focus-within .l-input-otp-cell[data-active] {
      border-color: var(--cell-focus-color);
      box-shadow: var(--cell-focus-ring);
    }

    /*
    ▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬
     🅲🅰🆁🅴🆃 (fake blinking caret in active empty cell)
    ▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬
    The native caret is hidden via `caret-color: transparent`; this stand-in
    gives the missing point-of-insertion cue inside the active cell. Only
    visible while empty — once a digit is typed, it replaces the caret.
    */

    :scope:focus-within .l-input-otp-cell[data-active]:not([data-filled])::after {
      content: '';
      inline-size: 1px;
      block-size: 1em;
      background: currentColor;
      animation: l-input-otp-caret 1s steps(2, jump-none) infinite;
    }

    @media (prefers-reduced-motion: reduce) {
      :scope:focus-within .l-input-otp-cell[data-active]:not([data-filled])::after {
        animation: none;
      }
    }

    /*
    ▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬
     🆂🅴🅿🅰🆁🅰🆃🅾🆁
    ▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬
    */

    .l-input-otp-separator {
      display: flex;
      align-items: center;
      color: var(--l-color-text-tertiary);
      padding-inline: 0.125rem;
      pointer-events: none;

      &::before {
        content: '\2013'; /* en-dash */
      }
    }

    /*
    ▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬
     🅷🅸🅳🅳🅴🅽 🅸🅽🅿🆄🆃
    ▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬
    The native input covers the entire cell area so clicks focus it naturally.
    It is visually transparent — the cells provide all visual feedback.
    */

    :scope:defined .l-input-otp-cells > input {
      position: absolute;
      inset: 0;
      inline-size: 100%;
      block-size: 100%;
      opacity: 0;
      border: 0;
      padding: 0;
      margin: 0;
      caret-color: transparent;
    }

    /*
    ▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬
     🆂🅸🆉🅴🆂
    ▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬
    */

    :scope[size='sm'] {
      --cell-size: 2rem;
    }

    :scope[size='lg'] {
      --cell-size: 3.5rem;
    }

    /*
    ▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬
     🅳🅸🆂🅰🅱🅻🅴🅳
    ▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬
    */

    :scope:has(input:disabled) .l-input-otp-cell {
      opacity: 0.4;
      cursor: not-allowed;
      color: var(--l-color-text-disabled);
    }

    /*
    ▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬
     🅿🆁🅾🅶🆁🅴🆂🆂🅸🆅🅴 🅴🅽🅷🅰🅽🅲🅴🅼🅴🅽🆃 🅵🅰🅻🅻🅱🅰🅲🅺
    ▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬
    Pre-upgrade, the real <input> stays visible and usable with no JS — styled as
    a single field with the cell tokens. The host still reserves the exact box the
    cells will occupy (width scales with --digits / --cell-size / --cell-gap) so
    layout doesn't shift on hydration.
    */

    :scope:not(:defined) {
      display: inline-block;
      flex-shrink: 0;
      inline-size: calc(
        var(--cell-size) * var(--digits) + var(--cell-gap, 0.5rem) * (var(--digits) - 1)
      );
      block-size: var(--cell-size);
    }

    :scope:not(:defined) > input {
      box-sizing: border-box;
      inline-size: 100%;
      block-size: 100%;
      padding-inline: calc(var(--cell-size) * 0.3);
      border: 0;
      border-radius: var(--cell-border-radius);
      background: var(--cell-bg-color);
      color: var(--l-color-text-primary);
      font-family: var(--_cell-font);
      font-size: calc(var(--cell-size) * 0.45);
      font-variant-numeric: tabular-nums;
      letter-spacing: 0.25em;
      text-align: center;
    }

    /* Swap the UA focus ring for the Luxen outline ring. Inset (like input-stepper)
       so the filled field's ring is never clipped by a tight container. */
    :scope:not(:defined) > input:focus-visible {
      outline: 2px solid var(--l-focus-ring);
      outline-offset: -2px;
    }
  }

  @keyframes l-input-otp-caret {
    50% {
      opacity: 0;
    }
  }
}
