/*
 * @civitai/components — attribute-driven, framework-agnostic component CSS.
 *
 * Contract: style is selected by `data-civitai-ui="<name>"` + `data-variant` +
 * `data-size` (+ a few component-specific `data-*`). Theme via an ancestor
 * `[data-theme='light'|'dark']`. Tokens come from @civitai/theme's `--civitai-*`
 * custom properties (link `@civitai/theme/styles.css` alongside this file, or
 * call `injectStyles()` which injects both).
 *
 * ALL rules live in `@layer civitai.components` so a consumer's own unlayered
 * CSS always wins the cascade WITHOUT specificity fights. State colors are
 * derived with `color-mix()` from base tokens (no shade enumeration), and
 * structure uses native CSS nesting (no preprocessor).
 *
 * See MARKUP.md for the full per-component markup + ARIA contract.
 */
@layer civitai.components {
  [data-civitai-ui] {
    box-sizing: border-box;
    font-family: var(--civitai-font);

    & *,
    & *::before,
    & *::after {
      box-sizing: border-box;
    }
  }

  /* ----- Button ----- */
  [data-civitai-ui='button'] {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    gap: 8px;
    border: 1px solid transparent;
    border-radius: var(--civitai-radius);
    font-family: var(--civitai-font);
    font-weight: 600;
    line-height: 1;
    cursor: pointer;
    user-select: none;
    text-decoration: none;
    transition: background-color 120ms ease, border-color 120ms ease, color 120ms ease;

    /*
     * Documented DEFAULTS (MARKUP.md): variant=filled, size=md. Applied on the
     * BASE rule (unconditionally) so BARE markup — a `<button data-civitai-ui=
     * "button">` with no `data-variant`/`data-size` — renders the documented
     * default (filled + md), not an unstyled/zero-size button. The explicit
     * `[data-variant]` / `[data-size]` rules below OVERRIDE (all four variants
     * set their own bg/border/hover, so the base filled default only ever wins
     * for a bare button). Every non-filled variant also sets its own
     * `border-color`, so the base primary border never leaks to them.
     */
    background: var(--civitai-color-primary);
    color: var(--civitai-color-primary-fg);
    border-color: var(--civitai-color-primary);
    height: 36px;
    padding: 0 18px;
    font-size: 14px;

    &:hover:not(:disabled) {
      background: var(--civitai-color-primary-hover);
      border-color: var(--civitai-color-primary-hover);
    }

    &[data-size='sm'] { height: 30px; padding: 0 14px; font-size: 13px; }
    &[data-size='md'] { height: 36px; padding: 0 18px; font-size: 14px; }
    &[data-size='lg'] { height: 44px; padding: 0 22px; font-size: 16px; }
    &[data-full-width='true'] { width: 100%; }

    &[data-variant='filled'] {
      background: var(--civitai-color-primary);
      color: var(--civitai-color-primary-fg);
      border-color: var(--civitai-color-primary);

      &:hover:not(:disabled) {
        background: var(--civitai-color-primary-hover);
        border-color: var(--civitai-color-primary-hover);
      }
    }
    &[data-variant='light'] {
      background: color-mix(in srgb, var(--civitai-color-primary) 12%, transparent);
      color: var(--civitai-color-primary);
      border-color: transparent;

      &:hover:not(:disabled) {
        background: color-mix(in srgb, var(--civitai-color-primary) 22%, transparent);
      }
    }
    &[data-variant='outline'] {
      background: transparent;
      color: var(--civitai-color-primary);
      border-color: var(--civitai-color-primary);

      &:hover:not(:disabled) {
        background: color-mix(in srgb, var(--civitai-color-primary) 10%, transparent);
      }
    }
    &[data-variant='subtle'] {
      background: transparent;
      color: var(--civitai-color-primary);
      border-color: transparent;

      &:hover:not(:disabled) {
        background: color-mix(in srgb, var(--civitai-color-primary) 10%, transparent);
      }
    }
    &:disabled,
    &[aria-busy='true'] {
      opacity: 0.6;
      cursor: not-allowed;
    }
    & [data-civitai-ui-section] {
      display: inline-flex;
      align-items: center;
    }
  }

  /* ----- TextInput / Textarea / NumberInput / Select ----- */
  [data-civitai-ui='text-input'],
  [data-civitai-ui='textarea'],
  [data-civitai-ui='number-input'],
  [data-civitai-ui='select'] {
    display: flex;
    flex-direction: column;
    gap: 4px;
  }
  [data-civitai-ui-label] {
    font-size: 14px;
    font-weight: 600;
    color: var(--civitai-color-text);
  }
  [data-civitai-ui-required] {
    color: var(--civitai-color-error);
    margin-left: 2px;
  }
  [data-civitai-ui-description] {
    font-size: 12px;
    color: var(--civitai-color-text-dimmed);
  }
  [data-civitai-ui-error] {
    font-size: 12px;
    color: var(--civitai-color-error);
  }
  [data-civitai-ui-control] {
    width: 100%;
    font-family: var(--civitai-font);
    font-size: 14px;
    color: var(--civitai-color-text);
    background: var(--civitai-color-surface);
    border: 1px solid var(--civitai-color-border);
    border-radius: var(--civitai-radius);
    padding: 8px 12px;
    transition: border-color 120ms ease;

    &:focus {
      outline: none;
      border-color: var(--civitai-color-primary);
    }
    &[aria-invalid='true'] {
      border-color: var(--civitai-color-error);
    }
    &:disabled {
      opacity: 0.6;
      cursor: not-allowed;
    }
  }
  textarea[data-civitai-ui-control] {
    resize: vertical;
    line-height: 1.5;
  }
  /*
   * Select (issue #181 F6): a native <select> reusing the shared
   * `-control` field chrome (border/background/radius/focus/invalid, above).
   * Keep the native disclosure caret (most robust + accessible); just make the
   * control feel interactive and reserve room on the right so the caret never
   * overlaps a long option label.
   */
  select[data-civitai-ui-control] {
    cursor: pointer;
    padding-right: 28px;
    line-height: 1.4;
  }
  select[data-civitai-ui-control]:disabled {
    cursor: not-allowed;
  }

  /* ----- Checkbox / Radio (issue #181 F6) ----- */
  /*
   * Themed native <input type="checkbox">/<input type="radio">: `accent-color`
   * carries the primary tint (native control, so keyboard/indeterminate/tab all
   * work for free), plus custom sizing, a token focus ring, and disabled state.
   * The box + its inline label sit in a `-choice` row; description + error live
   * below, reusing the shared field markers. The inputs deliberately do NOT
   * carry `data-civitai-ui-control` (that is the full-width field-input chrome).
   */
  [data-civitai-ui='checkbox'],
  [data-civitai-ui='radio'] {
    display: flex;
    flex-direction: column;
    gap: 4px;
  }
  [data-civitai-ui-choice] {
    display: flex;
    align-items: center;
    gap: 8px;
  }
  [data-civitai-ui='checkbox'] [data-civitai-ui-label],
  [data-civitai-ui='radio'] [data-civitai-ui-label] {
    font-weight: 500;
    cursor: pointer;
    user-select: none;
  }
  [data-civitai-ui='checkbox'] input[type='checkbox'],
  [data-civitai-ui='radio'] input[type='radio'] {
    accent-color: var(--civitai-color-primary);
    width: 16px;
    height: 16px;
    margin: 0;
    flex: none;
    cursor: pointer;
  }
  [data-civitai-ui='checkbox'] input[type='checkbox']:focus-visible,
  [data-civitai-ui='radio'] input[type='radio']:focus-visible {
    outline: 2px solid var(--civitai-color-primary);
    outline-offset: 2px;
  }
  [data-civitai-ui='checkbox'] input[type='checkbox']:disabled,
  [data-civitai-ui='radio'] input[type='radio']:disabled {
    cursor: not-allowed;
    opacity: 0.6;
  }
  [data-civitai-ui='checkbox'] input[type='checkbox']:disabled ~ [data-civitai-ui-label],
  [data-civitai-ui='radio'] input[type='radio']:disabled ~ [data-civitai-ui-label] {
    opacity: 0.6;
    cursor: not-allowed;
  }

  /* ----- RadioGroup (issue #181 F6) — role=radiogroup layout ----- */
  [data-civitai-ui='radio-group'] {
    display: flex;
    flex-direction: column;
    gap: 8px;
  }
  [data-civitai-ui-radio-options] {
    display: flex;
    flex-direction: column;
    gap: 8px;

    &[data-orientation='horizontal'] {
      flex-direction: row;
      flex-wrap: wrap;
      gap: 16px;
    }
  }
  /*
   * RadioGroup group-level error (0.2.0): mirrors the field `-error` treatment.
   * The message reuses the shared `[data-civitai-ui-error]` styling (12px, error
   * token); the invalid state (`data-invalid` on the group, set alongside
   * `aria-invalid`) tints the child radios' `accent-color` to the error token so
   * the invalid cue reads on the group, not just the message text — the group
   * analogue of a field control's error-colored border.
   */
  [data-civitai-ui='radio-group'] [data-civitai-ui-error] {
    font-size: 12px;
    color: var(--civitai-color-error);
  }
  [data-civitai-ui='radio-group'][data-invalid] input[type='radio'] {
    accent-color: var(--civitai-color-error);
  }

  /* ----- Card ----- */
  /*
   * F5 (issue #181): in LIGHT mode --civitai-color-surface == --civitai-color-body
   * (both #fefefe), so a borderless card is invisible against the page. Give
   * every card a subtle DEFAULT hairline (a low-alpha mix of the border token)
   * so a card WITHOUT `data-with-border` is still visible. `data-with-border`
   * stays the stronger, explicit, fully-opaque border. Dark mode already
   * differentiates surface from body, so the default hairline is removed there
   * (see the [data-theme='dark'] rule below) — no over-adding. The default
   * (no [data-theme]) light palette keeps the hairline, matching light mode.
   */
  [data-civitai-ui='card'] {
    background: var(--civitai-color-surface);
    border-radius: var(--civitai-radius);
    color: var(--civitai-color-text);
    border: 1px solid color-mix(in srgb, var(--civitai-color-border) 55%, transparent);

    &[data-with-border='true'] {
      border: 1px solid var(--civitai-color-border);
    }
    &[data-padding='sm'] { padding: 10px; }
    &[data-padding='md'] { padding: 16px; }
    &[data-padding='lg'] { padding: 24px; }
  }
  /*
   * Dark already differentiates surface (#1A1B1E) from body — drop the default
   * hairline on a borderless card so dark is visually unchanged. An explicit
   * `data-with-border` still renders its full border.
   */
  [data-theme='dark'] [data-civitai-ui='card']:not([data-with-border='true']) {
    border: 0;
  }

  /* ----- Stack / Group ----- */
  [data-civitai-ui='stack'] {
    display: flex;
    flex-direction: column;
    gap: 12px;

    &[data-gap='sm'] { gap: 8px; }
    &[data-gap='md'] { gap: 16px; }
    &[data-gap='lg'] { gap: 24px; }
  }
  [data-civitai-ui='group'] {
    display: flex;
    flex-direction: row;
    align-items: center;
    gap: 8px;

    &[data-gap='sm'] { gap: 6px; }
    &[data-gap='md'] { gap: 16px; }
    &[data-gap='lg'] { gap: 24px; }
  }

  /* ----- Alert ----- */
  [data-civitai-ui='alert'] {
    display: flex;
    gap: 10px;
    align-items: flex-start;
    padding: 12px 14px;
    border-radius: var(--civitai-radius);
    border: 1px solid transparent;
    font-size: 14px;
    color: var(--civitai-color-text);

    /*
     * Documented DEFAULT (MARKUP.md): color=info (default intent). Applied on
     * the base rule so a BARE alert — `<div data-civitai-ui="alert">` with no
     * `data-color` — renders the info intent (tinted bg + border), not just the
     * neutral chrome. The explicit `[data-color]` rules below OVERRIDE (success
     * / warning / error each set both bg + border-color), so the info default
     * only ever wins when `data-color` is omitted.
     */
    background: color-mix(in srgb, var(--civitai-color-info) 12%, transparent);
    border-color: color-mix(in srgb, var(--civitai-color-info) 35%, transparent);

    &[data-color='info'] {
      background: color-mix(in srgb, var(--civitai-color-info) 12%, transparent);
      border-color: color-mix(in srgb, var(--civitai-color-info) 35%, transparent);
    }
    &[data-color='success'] {
      background: color-mix(in srgb, var(--civitai-color-success) 12%, transparent);
      border-color: color-mix(in srgb, var(--civitai-color-success) 35%, transparent);
    }
    &[data-color='warning'] {
      background: color-mix(in srgb, var(--civitai-color-warning) 14%, transparent);
      border-color: color-mix(in srgb, var(--civitai-color-warning) 35%, transparent);
    }
    &[data-color='error'] {
      background: color-mix(in srgb, var(--civitai-color-error) 12%, transparent);
      border-color: color-mix(in srgb, var(--civitai-color-error) 35%, transparent);
    }
    & [data-civitai-ui-alert-body] {
      flex: 1;
      min-width: 0;
    }
    & [data-civitai-ui-alert-title] {
      font-weight: 600;
      margin-bottom: 2px;
    }
    & [data-civitai-ui-alert-close] {
      background: transparent;
      border: none;
      cursor: pointer;
      color: inherit;
      font-size: 16px;
      line-height: 1;
      padding: 0;
      opacity: 0.7;

      &:hover { opacity: 1; }
    }
  }

  /* ----- Loader ----- */
  [data-civitai-ui='loader'] {
    display: inline-block;
    border-radius: 50%;
    border-style: solid;
    border-color: color-mix(in srgb, currentColor 25%, transparent);
    border-top-color: currentColor;
    color: var(--civitai-color-primary);
    animation: civitai-ui-spin 0.7s linear infinite;

    /*
     * Documented DEFAULT (MARKUP.md): size=md. Applied on the base rule so a
     * BARE loader — `<span data-civitai-ui="loader">` with no `data-size` — has
     * real dimensions (otherwise 0×0 and invisible). sm/lg OVERRIDE below.
     */
    width: 22px;
    height: 22px;
    border-width: 3px;

    &[data-size='sm'] { width: 16px; height: 16px; border-width: 2px; }
    &[data-size='md'] { width: 22px; height: 22px; border-width: 3px; }
    &[data-size='lg'] { width: 32px; height: 32px; border-width: 4px; }
  }
  [data-civitai-ui='button'] [data-civitai-ui='loader'] {
    color: currentColor;
  }
  @keyframes civitai-ui-spin {
    to { transform: rotate(360deg); }
  }

  /* ----- Badge ----- */
  [data-civitai-ui='badge'] {
    display: inline-flex;
    align-items: center;
    border: 1px solid transparent;
    border-radius: 999px;
    font-weight: 600;
    line-height: 1;
    white-space: nowrap;
    text-transform: uppercase;
    letter-spacing: 0.02em;

    /*
     * Documented DEFAULTS (MARKUP.md): variant=filled, size=md. Applied on the
     * base rule so a BARE badge — `<span data-civitai-ui="badge">` with no
     * `data-variant`/`data-size` (MARKUP's own minimal example) — renders
     * filled + md (padding + primary fill), not an unstyled, zero-padding pill.
     * The explicit `[data-variant]`/`[data-size]` rules below OVERRIDE. Because
     * the base now carries a primary `border-color`, the `light` variant (which
     * previously relied on the base transparent border) explicitly resets it to
     * transparent so light badges are visually unchanged.
     */
    height: 22px;
    padding: 0 10px;
    font-size: 11px;
    background: var(--civitai-color-primary);
    color: var(--civitai-color-primary-fg);
    border-color: var(--civitai-color-primary);

    &[data-size='sm'] { height: 18px; padding: 0 8px; font-size: 10px; }
    &[data-size='md'] { height: 22px; padding: 0 10px; font-size: 11px; }
    &[data-size='lg'] { height: 26px; padding: 0 12px; font-size: 13px; }
    &[data-variant='filled'] {
      background: var(--civitai-color-primary);
      color: var(--civitai-color-primary-fg);
      border-color: var(--civitai-color-primary);
    }
    &[data-variant='light'] {
      background: color-mix(in srgb, var(--civitai-color-primary) 14%, transparent);
      color: var(--civitai-color-primary);
      border-color: transparent;
    }
    &[data-variant='outline'] {
      background: transparent;
      color: var(--civitai-color-primary);
      border-color: var(--civitai-color-primary);
    }

    /*
     * Intent color via `data-color`, mirroring Alert's `data-color` contract
     * (info / success / warning / error). Absent `data-color` => the default
     * primary above (non-breaking). Each intent recolors the `filled`, `light`
     * and `outline` variants with the same `color-mix()` token approach Alert
     * uses; `filled` keeps the white `--civitai-color-primary-fg` text. These
     * `[data-color][data-variant]` rules out-specify the plain-variant rules
     * above, so order-independence holds.
     */
    &[data-color='info'] {
      &[data-variant='filled'] {
        background: var(--civitai-color-info);
        border-color: var(--civitai-color-info);
      }
      &[data-variant='light'] {
        background: color-mix(in srgb, var(--civitai-color-info) 14%, transparent);
        color: var(--civitai-color-info);
      }
      &[data-variant='outline'] {
        color: var(--civitai-color-info);
        border-color: var(--civitai-color-info);
      }
    }
    &[data-color='success'] {
      &[data-variant='filled'] {
        background: var(--civitai-color-success);
        border-color: var(--civitai-color-success);
      }
      &[data-variant='light'] {
        background: color-mix(in srgb, var(--civitai-color-success) 14%, transparent);
        color: var(--civitai-color-success);
      }
      &[data-variant='outline'] {
        color: var(--civitai-color-success);
        border-color: var(--civitai-color-success);
      }
    }
    &[data-color='warning'] {
      &[data-variant='filled'] {
        background: var(--civitai-color-warning);
        border-color: var(--civitai-color-warning);
      }
      &[data-variant='light'] {
        background: color-mix(in srgb, var(--civitai-color-warning) 14%, transparent);
        color: var(--civitai-color-warning);
      }
      &[data-variant='outline'] {
        color: var(--civitai-color-warning);
        border-color: var(--civitai-color-warning);
      }
    }
    &[data-color='error'] {
      &[data-variant='filled'] {
        background: var(--civitai-color-error);
        border-color: var(--civitai-color-error);
      }
      &[data-variant='light'] {
        background: color-mix(in srgb, var(--civitai-color-error) 14%, transparent);
        color: var(--civitai-color-error);
      }
      &[data-variant='outline'] {
        color: var(--civitai-color-error);
        border-color: var(--civitai-color-error);
      }
    }
  }

  /* ----- Slider ----- */
  /*
   * A themed native <input type="range">: `accent-color` carries the primary
   * tint (native control, so keyboard arrow-key nav + ARIA come for free), plus
   * a full-width track, a token focus ring, and disabled/invalid states. Like
   * checkbox/radio, the range input deliberately does NOT carry
   * `data-civitai-ui-control` (that is the bordered field-input chrome). The
   * label/description/error reuse the shared field markers, laid out in a
   * column; an optional value read-out sits inline with the label in a header.
   */
  [data-civitai-ui='slider'] {
    display: flex;
    flex-direction: column;
    gap: 6px;
  }
  [data-civitai-ui-slider-header] {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 8px;
  }
  [data-civitai-ui-slider-value] {
    font-size: 13px;
    font-weight: 600;
    color: var(--civitai-color-text-dimmed);
    font-variant-numeric: tabular-nums;
  }
  [data-civitai-ui='slider'] input[type='range'] {
    -webkit-appearance: none;
    appearance: none;
    width: 100%;
    height: 6px;
    margin: 6px 0;
    padding: 0;
    border-radius: 999px;
    accent-color: var(--civitai-color-primary);
    background: var(--civitai-color-gray-2);
    cursor: pointer;
  }
  [data-theme='dark'] [data-civitai-ui='slider'] input[type='range'] {
    background: var(--civitai-color-surface-2);
  }
  [data-civitai-ui='slider'] input[type='range']:focus-visible {
    outline: 2px solid var(--civitai-color-primary);
    outline-offset: 4px;
  }
  [data-civitai-ui='slider'] input[type='range']:disabled {
    cursor: not-allowed;
    opacity: 0.6;
    accent-color: var(--civitai-color-gray-5);
  }
  [data-civitai-ui='slider'][data-invalid] input[type='range'] {
    accent-color: var(--civitai-color-error);
  }

  /* ----- SegmentedControl / Tabs ----- */
  /*
   * A `role=tablist` of segment buttons (`role=tab`). Presentational chrome
   * only — the roving-tabindex + arrow-key nav + selection follow-focus live in
   * the React binding (or must be author-provided for hand HTML). The selected
   * segment (`aria-selected='true'`) lifts to the surface color with a soft
   * shadow; unselected segments read dimmed. Paired tab panels use
   * `data-civitai-ui-tabpanel` (role=tabpanel).
   */
  [data-civitai-ui='segmented-control'] {
    display: inline-flex;
    gap: 2px;
    padding: 4px;
    background: var(--civitai-color-gray-1);
    border-radius: var(--civitai-radius);
    max-width: 100%;
    overflow-x: auto;
  }
  [data-theme='dark'] [data-civitai-ui='segmented-control'] {
    background: var(--civitai-color-surface-2);
  }
  [data-civitai-ui-segment] {
    -webkit-appearance: none;
    appearance: none;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    border: 0;
    background: transparent;
    color: var(--civitai-color-text-dimmed);
    font-family: var(--civitai-font);
    font-weight: 600;
    line-height: 1;
    white-space: nowrap;
    cursor: pointer;
    border-radius: calc(var(--civitai-radius) - 1px);
    transition: background-color 120ms ease, color 120ms ease;

    /* Documented DEFAULT (MARKUP.md): size=md. On the base rule so a bare
       segment renders at md height, not zero-height. sm/lg override below. */
    height: 30px;
    padding: 0 14px;
    font-size: 13px;

    &[data-size='sm'] { height: 26px; padding: 0 10px; font-size: 12px; }
    &[data-size='md'] { height: 30px; padding: 0 14px; font-size: 13px; }
    &[data-size='lg'] { height: 38px; padding: 0 18px; font-size: 15px; }

    &:hover:not(:disabled):not([aria-selected='true']):not([aria-checked='true']) {
      color: var(--civitai-color-text);
    }
    /* Selected state — `aria-selected` in tabs mode (role=tab) OR `aria-checked`
       in toggle mode (role=radio). Same visual treatment for both. */
    &[aria-selected='true'],
    &[aria-checked='true'] {
      background: var(--civitai-color-surface);
      color: var(--civitai-color-text);
      box-shadow: 0 1px 2px rgba(0, 0, 0, 0.12);
    }
    &:focus-visible {
      outline: 2px solid var(--civitai-color-primary);
      outline-offset: 2px;
    }
    &:disabled {
      opacity: 0.5;
      cursor: not-allowed;
    }
  }
  [data-civitai-ui-tabpanel] {
    color: var(--civitai-color-text);

    &:focus-visible {
      outline: 2px solid var(--civitai-color-primary);
      outline-offset: 2px;
    }
    &[hidden] { display: none; }
  }

  /* ----- Toast ----- */
  /*
   * `toast-region` is the fixed-position `aria-live` host (bottom-right stack).
   * The behavior — enqueue, auto-dismiss timers, portal — lives in the React
   * binding's ToastProvider/useToast; the presentational `toast` is a standalone
   * component so hand HTML and React share one visual contract. Each toast is
   * opaque (it sits over content) with an intent-colored left accent, mirroring
   * Alert's `data-color` set.
   */
  [data-civitai-ui='toast-region'] {
    position: fixed;
    bottom: 16px;
    right: 16px;
    z-index: 9999;
    display: flex;
    flex-direction: column;
    gap: 8px;
    width: min(92vw, 380px);
    pointer-events: none;
  }
  [data-civitai-ui='toast'] {
    pointer-events: auto;
    display: flex;
    gap: 10px;
    align-items: flex-start;
    padding: 12px 14px;
    border-radius: var(--civitai-radius);
    background: var(--civitai-color-surface);
    color: var(--civitai-color-text);
    border: 1px solid var(--civitai-color-border);
    border-left: 4px solid var(--civitai-color-border);
    box-shadow: 0 6px 18px rgba(0, 0, 0, 0.18);
    font-size: 14px;

    &[data-color='info'] { border-left-color: var(--civitai-color-info); }
    &[data-color='success'] { border-left-color: var(--civitai-color-success); }
    &[data-color='warning'] { border-left-color: var(--civitai-color-warning); }
    &[data-color='error'] { border-left-color: var(--civitai-color-error); }

    & [data-civitai-ui-toast-body] {
      flex: 1;
      min-width: 0;
    }
    & [data-civitai-ui-toast-title] {
      font-weight: 600;
      margin-bottom: 2px;
    }
    & [data-civitai-ui-toast-close] {
      background: transparent;
      border: none;
      cursor: pointer;
      color: inherit;
      font-size: 16px;
      line-height: 1;
      padding: 0;
      opacity: 0.7;

      &:hover { opacity: 1; }
    }
  }

  /* ----- Tooltip ----- */
  /*
   * A hover/focus tooltip: a positioned `role=tooltip` bubble revealed when the
   * wrapper is hovered or contains focus (pure-CSS reveal), or explicitly via
   * `data-open='true'` (the React binding also wires `aria-describedby` +
   * Escape-to-dismiss). The bubble is a dark chip (gray-9) with light text in
   * both themes.
   */
  [data-civitai-ui='tooltip'] {
    position: relative;
    display: inline-flex;
  }
  [data-civitai-ui-tooltip-bubble] {
    position: absolute;
    bottom: calc(100% + 6px);
    left: 50%;
    transform: translateX(-50%);
    z-index: 200;
    max-width: 260px;
    width: max-content;
    padding: 4px 8px;
    border-radius: var(--civitai-radius);
    background: var(--civitai-color-gray-9);
    color: var(--civitai-color-primary-fg);
    font-size: 12px;
    font-weight: 500;
    line-height: 1.4;
    text-align: center;
    pointer-events: none;
    opacity: 0;
    visibility: hidden;
    transition: opacity 120ms ease;
  }
  /*
   * Reveal on hover/focus or an explicit `data-open='true'` — BUT
   * `data-dismissed='true'` overrides (gates every reveal selector), so the
   * React binding's Escape-to-dismiss can force-HIDE the bubble even while the
   * pointer still hovers or focus is still within. The dismissed flag is cleared
   * on the next hover/focus so the tooltip can re-open normally.
   */
  [data-civitai-ui='tooltip']:hover [data-civitai-ui-tooltip-bubble]:not([data-dismissed='true']),
  [data-civitai-ui='tooltip']:focus-within [data-civitai-ui-tooltip-bubble]:not([data-dismissed='true']),
  [data-civitai-ui-tooltip-bubble][data-open='true']:not([data-dismissed='true']) {
    opacity: 1;
    visibility: visible;
  }

  /* ----- Image ----- */
  /*
   * A media container with a token placeholder background (visible while the
   * image loads), object-fit control, and a broken-image fallback. `data-status`
   * (loading|loaded|error) — set by the React binding's onLoad/onError, or by
   * the author for hand HTML — fades the <img> in on load and swaps to the
   * fallback overlay on error. Bare markup (no `data-status`) shows the image.
   */
  [data-civitai-ui='image'] {
    position: relative;
    display: block;
    overflow: hidden;
    background: var(--civitai-color-gray-2);
    border-radius: var(--civitai-radius);
  }
  [data-theme='dark'] [data-civitai-ui='image'] {
    background: var(--civitai-color-surface-2);
  }
  [data-civitai-ui-image-img] {
    display: block;
    width: 100%;
    height: 100%;
    object-fit: cover;
    opacity: 1;
    transition: opacity 200ms ease;

    &[data-fit='contain'] { object-fit: contain; }
    &[data-fit='cover'] { object-fit: cover; }
  }
  [data-civitai-ui='image'][data-status='loading'] [data-civitai-ui-image-img],
  [data-civitai-ui='image'][data-status='error'] [data-civitai-ui-image-img] {
    opacity: 0;
  }
  [data-civitai-ui-image-fallback] {
    position: absolute;
    inset: 0;
    display: none;
    align-items: center;
    justify-content: center;
    padding: 8px;
    color: var(--civitai-color-text-dimmed);
    font-size: 13px;
    text-align: center;
  }
  [data-civitai-ui='image'][data-status='error'] [data-civitai-ui-image-fallback] {
    display: flex;
  }
}
