/* `@charcuterie/ui/styles.css` — the four looping affordances, the
 * one-shot highlight pulse and the token-tinted scrollbar: the
 * visuals a Tailwind utility cannot express against our tokens, and
 * nothing else.
 *
 * Every other visual in this package is a Tailwind utility reading
 * a token, which is what makes a scheme/density/variant flip a
 * repaint with no re-render. Two things sit outside that reach.
 * Animation is one: Tailwind's own `animate-spin` and `animate-pulse`
 * hardcode 1s and 2s, ignore `prefers-reduced-motion`, and are
 * therefore beyond the theme's reach — which is exactly why
 * `MotionTokens` carries a separate `loopFast`/`loopSlow` ramp. The
 * scrollbar is the other: the `::-webkit-scrollbar` pseudo-elements
 * (and Firefox's `scrollbar-width`/`scrollbar-color`) have no
 * `@theme`-generated utility, so `.charcuterie-scrollbar` at the foot
 * of this file reads the same `--color-*` roles by hand and flips with
 * `data-scheme` on the same repaint as everything else.
 *
 * Consumers import this once, after `@charcuterie/tokens/theme.css`.
 *
 * The reduced-motion block is not a nicety. `variables.css` already
 * collapses every duration to `0ms`, and for a *transition* that is
 * the whole fix — but `animation: sweep 0ms infinite` still paints
 * its first keyframe forever, and the first keyframe of a sweep is
 * an off-screen bar. So each loop is switched off outright and owes
 * a static fallback that still reads correctly, which is what the
 * `@media` block below supplies. A spinner that stops spinning must
 * still say "loading", and it does: the label is text in a
 * `role="status"`, not the animation. */

@keyframes charcuterie-spin {
  to {
    transform: rotate(360deg);
  }
}

@keyframes charcuterie-shimmer {
  from {
    background-position: 100% 0;
  }

  to {
    background-position: -100% 0;
  }
}

/* Logical, not `translateX`. A sweep is the one animation whose
 * direction carries meaning — it reads as "moving forward" — so in
 * RTL it has to move the other way, and `inset-inline-start` is
 * what makes that free. */
@keyframes charcuterie-sweep {
  from {
    inset-inline-start: -40%;
  }

  to {
    inset-inline-start: 100%;
  }
}

@keyframes charcuterie-pulse {
  0% {
    opacity: 1;
    transform: scale(1);
  }

  50% {
    opacity: 0.45;
    transform: scale(0.82);
  }

  100% {
    opacity: 1;
    transform: scale(1);
  }
}

/* The highlight pulse: "this row just changed". Two blinks, then
 * steady. Only OPACITY moves, and the last keyframe is fully shown,
 * so the element's own resting style IS the static fallback — under
 * reduced motion the animation is switched off below and the ring is
 * simply there, and on ePaper a 0ms run ends on the same frame. That
 * is the "every moving affordance owes a static fallback" rule met
 * by construction rather than by a second rule. */
@keyframes charcuterie-highlight {
  0% {
    opacity: 0;
  }

  25% {
    opacity: 1;
  }

  55% {
    opacity: 0.3;
  }

  100% {
    opacity: 1;
  }
}

.charcuterie-highlight {
  animation: charcuterie-highlight var(--duration-loop-slow)
    var(--easing-standard) 2;
}

.charcuterie-spin {
  animation: charcuterie-spin var(--duration-loop-fast)
    linear infinite;
}

.charcuterie-shimmer {
  background-image: linear-gradient(
    90deg,
    var(--color-surface-sunken) 0%,
    var(--color-surface-raised) 50%,
    var(--color-surface-sunken) 100%
  );
  background-size: 200% 100%;
  animation: charcuterie-shimmer var(--duration-loop-slow)
    var(--easing-standard) infinite;
}

.charcuterie-sweep {
  animation: charcuterie-sweep var(--duration-loop-slow)
    var(--easing-standard) infinite;
}

.charcuterie-pulse {
  animation: charcuterie-pulse var(--duration-loop-slow)
    var(--easing-standard) infinite;
}

@media (prefers-reduced-motion: reduce) {
  .charcuterie-spin,
  .charcuterie-shimmer,
  .charcuterie-sweep,
  .charcuterie-pulse,
  .charcuterie-highlight {
    animation: none;
  }

  /* Flat, not mid-gradient: a stopped shimmer left holding its
   * first keyframe is a lopsided highlight that reads as a
   * rendering bug rather than as a placeholder. */
  .charcuterie-shimmer {
    background-image: none;
    background-color: var(--color-surface-sunken);
  }

  /* The sweep's static fallback carries the whole meaning of the
   * indeterminate state — "working, no measurable progress" — so it
   * fills the track with hatching rather than parking a bar at one
   * end, where it would read as either finished or stuck. */
  .charcuterie-sweep {
    inset-inline-start: 0;
    inline-size: 100%;
    background-image: repeating-linear-gradient(
      135deg,
      currentColor 0,
      currentColor 2px,
      transparent 2px,
      transparent 6px
    );
  }
}

/* `.charcuterie-scrollbar` — a token-tinted scrollbar for any element
 * that scrolls. The fleet grew this twice by hand (gallery-downloader's
 * `scrollbar-thin-token`, image-viewer's global `::-webkit-scrollbar`
 * block); this is the one copy, promoted here so a scheme flip
 * repaints it for free.
 *
 * The *designed* look is the `::-webkit-scrollbar` path: a 12px bar, a
 * rounded thumb, a track-coloured inset, no step arrows. That is what
 * Chromium, Edge, and Safari paint. Chromium 121+ will *prefer*
 * `scrollbar-width` / `scrollbar-color` over these pseudo-elements
 * whenever both are set, which collapses the bar to the thin OS chrome
 * (arrows and all on Windows) — so the standard properties are scoped
 * to Firefox only, where `::-webkit-scrollbar` never runs.
 *
 * Firefox gets the closest standard-property match (thin, token-tinted
 * thumb, transparent track). It cannot round a corner or draw a track
 * inset — a platform limit, not an omission here.
 *
 * `::-webkit-scrollbar-button` is suppressed deliberately: the
 * affordance is the thumb on a track, not step arrows.
 *
 * Both engines read the same three roles — `border-strong` for the
 * thumb, `surface-sunken` for the track, `content-muted` for the thumb
 * on hover — so the bar is themed by `[data-scheme]` on `<html>` with
 * nothing in React observing it. `width`/`height` rather than a
 * logical property is not a lapse: the `::-webkit-scrollbar`
 * pseudo-elements size only by the physical pair, and a vertical bar's
 * thickness is its `width` regardless of writing direction. */

/* The designed bar — Chromium, Edge, Safari. */
.charcuterie-scrollbar::-webkit-scrollbar {
  width: 12px;
  height: 12px;
}

.charcuterie-scrollbar::-webkit-scrollbar-button {
  display: none;
  width: 0;
  height: 0;
}

.charcuterie-scrollbar::-webkit-scrollbar-track {
  background-color: var(--color-surface-sunken);
}

.charcuterie-scrollbar::-webkit-scrollbar-thumb {
  background-color: var(--color-border-strong);
  border: 2px solid var(--color-surface-sunken);
  border-radius: 6px;
}

.charcuterie-scrollbar::-webkit-scrollbar-thumb:hover {
  background-color: var(--color-content-muted);
}

.charcuterie-scrollbar::-webkit-scrollbar-corner {
  background-color: var(--color-surface-sunken);
}

/* Firefox only: it never paints `::-webkit-scrollbar`. `-moz-appearance`
 * is the supports probe that Chromium and Safari reject, so this block
 * does not reintroduce the standard properties on engines that would
 * then drop the designed bar. */
@supports (-moz-appearance: none) {
  .charcuterie-scrollbar {
    scrollbar-width: thin;
    scrollbar-color: color-mix(
        in oklab,
        var(--color-border-strong) 70%,
        transparent
      )
      transparent;
  }
}

/* Comparisons keep their column geometry instead of splitting prose into narrow strips.
   Inline has no block-size clamp: the document owns vertical scrolling. */
.charcuterie-table-scroll {
  overflow-x: auto;
}
.charcuterie-table-content table {
  display: table;
  inline-size: 100%;
  min-inline-size: var(--table-min-inline-size);
  border-collapse: separate;
  border-spacing: 0;
  table-layout: fixed;
  font-size: var(--font-size-sm);
  line-height: 1.65;
}
.charcuterie-table-content :is(th, td) {
  padding: 0.75rem 1rem;
  border-inline-end: 1px solid var(--color-border-subtle);
  border-block-end: 1px solid var(--color-border-subtle);
  vertical-align: top;
  text-align: start;
  overflow-wrap: break-word;
  color: var(--color-content-primary);
  background: var(--color-surface-base);
}
.charcuterie-table-content thead th {
  background: var(--color-surface-raised);
  font-weight: 600;
}
[role="dialog"] .charcuterie-table-content thead th {
  position: sticky;
  inset-block-start: -1rem;
  z-index: 2;
}
[role="dialog"]
  .charcuterie-table-content
  :is(th, td):first-child {
  position: sticky;
  inset-inline-start: -1rem;
  background: var(--color-surface-raised);
  z-index: 1;
}
.charcuterie-table-content thead th:first-child {
  inline-size: clamp(10rem, 18vw, 16rem);
}
[role="dialog"]
  .charcuterie-table-content
  thead
  th:first-child {
  z-index: 3;
}

/* Column width utilities describe comparison columns, never labelled list cells.
   These rules are unlayered, like the component's existing structural styles,
   so a consumer's cq-lg:w-32 cannot crush a list reflowing at cq-xl. */
.charcuterie-data-table[data-layout="stacked"]
  > table
  > :is(thead, tbody)
  > tr
  > :is(th, td) {
  inline-size: auto;
  min-inline-size: 0;
  max-inline-size: none;
}
@container (width < 32rem) {
  .charcuterie-data-table[data-reflow-at="md"]
    > table
    > :is(thead, tbody)
    > tr
    > :is(th, td) {
    inline-size: auto;
    min-inline-size: 0;
    max-inline-size: none;
  }
}
@container (width < 48rem) {
  .charcuterie-data-table[data-reflow-at="lg"]
    > table
    > :is(thead, tbody)
    > tr
    > :is(th, td) {
    inline-size: auto;
    min-inline-size: 0;
    max-inline-size: none;
  }
}
@container (width < 64rem) {
  .charcuterie-data-table[data-reflow-at="xl"]
    > table
    > :is(thead, tbody)
    > tr
    > :is(th, td) {
    inline-size: auto;
    min-inline-size: 0;
    max-inline-size: none;
  }
}
