/*!
 * jst-components - component styles on top of jst-layout, CSS-first.
 * Loads after jst-layout.css. Each component is plain HTML + native platform
 * primitives (dialog, popover, details, Invoker Commands) styled by custom
 * properties, so the SAME markup re-skins to any framework's look by swapping a
 * theme that overrides the --jst-* variables. Demonstrated in
 * examples/components_cross_section.html.
 *
 * Everything here fills the jst.components sub-layer, so unlayered app CSS
 * outranks any of it without specificity games. The order declaration repeats
 * jst-layout.css's so the sub-layer order holds whichever file loads first.
 */
@layer jst.theme, jst.base, jst.primitives, jst.components;

@layer jst.components {

/* ============================================================================
 * Theme skins - each overrides only variables. Same components, different look.
 * (The CSS Zen Garden bet, applied to component frameworks.)
 *
 * The differentiators are border treatment, resting elevation and surface
 * shape - not just accent colour. Each skin sets some of:
 *   --jst-border-width  border thickness on cards/inputs/tabs (0 = hairline off)
 *   --jst-border        border colour (transparent = borderless surfaces)
 *   --jst-radius        corner rounding
 *   --jst-shadow-surface  resting card elevation (see jst-box wiring below)
 *   --jst-shadow        floating elevation for dialogs / popovers / toasts
 *   --jst-font, --jst-ring
 * so the SAME markup reads as w3css (flat, square, solid borders) or material
 * (borderless, elevated) at a glance.
 * ==========================================================================*/

/* Cards (jst-box) are flat by default; a skin's --jst-shadow-surface lifts them.
   Lives in jst.components so it wins over the primitives-layer jst-box rule
   without touching that rule. Borderless skins (material, daisy) still separate
   from the page because jst-box sits on --jst-surface, a distinct tone from the
   page --jst-bg - so a raised card reads in dark mode by tone, in light by
   shadow. */
jst-box { box-shadow: var(--jst-shadow-surface); }

/* JST's own look: the jst-layout defaults, no overrides. Lean radius, compact
   controls, one accent, hairline borders, no resting elevation. Selecting it in
   a theme switcher simply removes the imitations. */
[data-theme="jst"] {
  --jst-ring: 0 0 0 2px oklch(from var(--jst-accent) l c h / 0.45);
}

/* Bootstrap-like: classic 1px borders, familiar blue, its own radius, a faint
   card shadow. Nothing exotic - the framework everyone recognises. */
[data-theme="bootstrap"] {
  --jst-accent: oklch(0.55 0.20 262);     /* ~#0d6efd */
  --jst-radius: 0.375rem;
  --jst-font: system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;
  --jst-border: light-dark(oklch(0.86 0.005 262), oklch(0.38 0.01 262));
  --jst-shadow-surface: 0 0.125rem 0.25rem oklch(0 0 0 / 0.075);
  --jst-shadow: 0 0.5rem 1rem oklch(0 0 0 / 0.15);
  --jst-ring: 0 0 0 0.25rem oklch(0.55 0.20 262 / 0.5);
}

/* shadcn-like: near-monochrome zinc, larger radius, hairline zinc borders and a
   whisper of shadow. Restrained on purpose - the border IS the identity. */
[data-theme="shadcn"] {
  --jst-accent: oklch(0.22 0.006 285);    /* zinc-900-ish, near black */
  --jst-radius: 0.5rem;
  --jst-font: system-ui, sans-serif;
  --jst-border: light-dark(oklch(0.92 0.004 285), oklch(0.30 0.006 285));
  --jst-shadow-surface: 0 1px 2px oklch(0 0 0 / 0.06);
  --jst-shadow: 0 0.25rem 0.75rem oklch(0 0 0 / 0.10);
  --jst-ring: 0 0 0 2px var(--jst-bg), 0 0 0 4px oklch(0.55 0.01 285);
}

/* Shoelace / Web Awesome-like: sky primary, tight radius, thin borders, small
   soft shadow. Airy and neutral. */
[data-theme="shoelace"] {
  --jst-accent: oklch(0.68 0.14 233);     /* sky-ish */
  --jst-radius: 0.25rem;
  --jst-font: system-ui, sans-serif;
  --jst-border: light-dark(oklch(0.90 0.01 233), oklch(0.33 0.01 233));
  --jst-shadow-surface: 0 1px 2px oklch(0.55 0.05 233 / 0.14);
  --jst-shadow: 0 0.25rem 1rem oklch(0.55 0.05 233 / 0.18);
  --jst-ring: 0 0 0 3px oklch(0.68 0.14 233 / 0.4);
}

/* Pico-like: indigo, generous radius, calm and airy. Inputs keep a soft border;
   cards get a big radius + soft diffuse shadow so they read light, not boxed. */
[data-theme="pico"] {
  --jst-accent: oklch(0.52 0.16 277);     /* pico indigo */
  --jst-radius: 0.75rem;
  --jst-font: system-ui, sans-serif;
  --jst-border: light-dark(oklch(0.91 0.01 277), oklch(0.32 0.01 277));
  --jst-shadow-surface: 0 0.125rem 1rem oklch(0.3 0.05 277 / 0.10);
  --jst-shadow: 0 0.5rem 1.5rem oklch(0.3 0.05 277 / 0.12);
  --jst-ring: 0 0 0 3px oklch(0.52 0.16 277 / 0.35);
}

/* W3.CSS-like (W3Schools): dead flat. Square corners, solid visible grey
   borders, NO resting elevation. The anti-skin - everything hard-edged. */
[data-theme="w3css"] {
  --jst-accent: oklch(0.62 0.12 236);     /* w3-blue */
  --jst-radius: 0;
  --jst-font: "Segoe UI", Arial, sans-serif;
  --jst-border-width: 1px;
  --jst-border: light-dark(oklch(0.72 0 0), oklch(0.48 0 0));
  --jst-shadow-surface: none;
  --jst-shadow: 0 2px 5px oklch(0 0 0 / 0.16), 0 2px 10px oklch(0 0 0 / 0.12);
  --jst-ring: 0 0 0 2px oklch(0.62 0.12 236 / 0.6);
}

/* Bulma-like: turquoise primary, crisp visible borders, its signature soft-drop
   card shadow. */
[data-theme="bulma"] {
  --jst-accent: oklch(0.74 0.13 184);     /* bulma turquoise */
  --jst-radius: 0.375rem;
  --jst-font: "Helvetica Neue", system-ui, sans-serif;
  --jst-border: light-dark(oklch(0.86 0.005 184), oklch(0.38 0.01 184));
  --jst-shadow-surface: 0 0.5em 1em -0.125em oklch(0.3 0.02 184 / 0.1), 0 0 0 1px oklch(0.3 0.02 184 / 0.02);
  --jst-shadow: 0 0.5em 1em -0.125em oklch(0.3 0.02 184 / 0.15);
  --jst-ring: 0 0 0 0.125em oklch(0.74 0.13 184 / 0.4);
}

/* ============================================================================
 * The "which JST tech?" badge - the headline of the whole comparison.
 * Greener = less JST (more platform); blue = a real JST component.
 * ==========================================================================*/
.jst-tag {
  display: inline-flex; align-items: center; gap: 0.35em;
  font-family: var(--jst-font-mono); font-size: 0.7rem; font-weight: 600;
  padding: 0.15em 0.6em; border-radius: 99px; white-space: nowrap;
  border: 1px solid transparent;
}
.jst-tag::before { content: "●"; font-size: 0.8em; }
/* No JST at all - the best outcome. */
.jst-tag[data-tech="none"]     { background: oklch(0.95 0.05 150); color: oklch(0.40 0.12 150); border-color: oklch(0.85 0.08 150); }
/* A jst-layout primitive or variable. */
.jst-tag[data-tech="layout"]   { background: oklch(0.95 0.04 300); color: oklch(0.45 0.12 300); border-color: oklch(0.87 0.06 300); }
/* A genuine JST custom-element component. */
.jst-tag[data-tech="jst"]      { background: oklch(0.93 0.06 30);  color: oklch(0.48 0.16 30);  border-color: oklch(0.85 0.09 30); }
@media (prefers-color-scheme: dark) {
  .jst-tag[data-tech="none"]     { background: oklch(0.30 0.06 150); color: oklch(0.85 0.10 150); border-color: oklch(0.40 0.08 150); }
  .jst-tag[data-tech="layout"]   { background: oklch(0.30 0.05 300); color: oklch(0.86 0.08 300); border-color: oklch(0.40 0.07 300); }
  .jst-tag[data-tech="jst"]      { background: oklch(0.32 0.08 30);  color: oklch(0.86 0.10 30);  border-color: oklch(0.42 0.10 30); }
}

/* Focus-visible ring driven by the theme variable. */
:where([data-theme]) :where(button, a, summary, input, [tabindex]):focus-visible {
  outline: none;
  box-shadow: var(--jst-ring, 0 0 0 3px oklch(0.6 0.1 262 / 0.5));
}

/* ============================================================================
 * Modal - native <dialog> + Invoker Commands (command="show-modal"/"close").
 * ==========================================================================*/
.jst-modal {
  margin: auto;
  inline-size: min(32rem, calc(100vw - 2rem));
  padding: 0;
  border: var(--jst-border-width) solid var(--jst-border);
  border-radius: var(--jst-radius-l);
  background: var(--jst-bg);
  color: var(--jst-fg);
  box-shadow: var(--jst-shadow);
}
:where(.jst-modal, .jst-drawer)::backdrop { background: var(--jst-backdrop); }
/* Modal and drawer share their anatomy: one rule set for sections and header. */
:where(.jst-modal, .jst-drawer) > header,
.jst-modal > .jst-modal-body,
.jst-drawer > .jst-drawer-body,
:where(.jst-modal, .jst-drawer) > footer { padding: var(--jst-space-s) var(--jst-space-m); }
:where(.jst-modal, .jst-drawer) > header {
  display: flex; align-items: center; justify-content: space-between;
  gap: var(--jst-space-m);
  border-block-end: var(--jst-border-width) solid var(--jst-border);
  font-weight: 700;
}
.jst-modal > footer {
  display: flex; justify-content: flex-end; gap: var(--jst-space-xs);
  border-block-start: var(--jst-border-width) solid var(--jst-border);
}
/* The dismiss ×: one pattern for modal, drawer, and toast. */
.jst-modal-close, jst-toaster .jst-toast > button {
  background: none; border: none; color: var(--jst-muted);
  font-size: 1.1em; line-height: 1; cursor: pointer; padding: 0.1em 0.3em;
}
.jst-modal-close:hover, jst-toaster .jst-toast > button:hover { color: var(--jst-fg); background: none; }

/* Enter/leave animation via @starting-style + allow-discrete (Baseline 2024). */
@supports (transition-behavior: allow-discrete) {
  .jst-modal {
    opacity: 0; transform: translateY(-0.5rem) scale(0.98);
    transition: opacity var(--jst-motion) var(--jst-ease), transform var(--jst-motion) var(--jst-ease), overlay var(--jst-motion) var(--jst-ease) allow-discrete, display var(--jst-motion) var(--jst-ease) allow-discrete;
  }
  .jst-modal:popover-open, .jst-modal[open] { opacity: 1; transform: none; }
  @starting-style { .jst-modal[open] { opacity: 0; transform: translateY(-0.5rem) scale(0.98); } }
}

/* ============================================================================
 * Accordion - native <details>/<summary>. Exclusive via the shared name attr.
 * ==========================================================================*/
.jst-accordion { border: var(--jst-border-width) solid var(--jst-border); border-radius: var(--jst-radius); overflow: clip; }
.jst-accordion > details + details { border-block-start: var(--jst-border-width) solid var(--jst-border); }
.jst-accordion summary {
  cursor: pointer; padding: var(--jst-space-xs) var(--jst-space-s);
  font-weight: 600; list-style: none;
  display: flex; align-items: center; justify-content: space-between;
}
.jst-accordion summary::-webkit-details-marker { display: none; }
.jst-accordion summary::after { content: "▾"; color: var(--jst-muted); transition: transform var(--jst-motion-fast) var(--jst-ease); }
.jst-accordion details[open] summary::after { transform: rotate(180deg); }
.jst-accordion summary:hover { background: var(--jst-surface); }
.jst-accordion .jst-accordion-body { padding: 0 var(--jst-space-s) var(--jst-space-s); color: var(--jst-muted); }

/* ============================================================================
 * Dropdown menu - native [popover] + command="toggle-popover". The invoker is
 * the popover's implicit anchor, so no ids/anchor-names are needed to place it.
 * ==========================================================================*/
/* The floating-panel surface, shared by the dropdown menu, the combobox
   listbox, and the command palette. Declared once. */
.jst-menu[popover], jst-combobox [role="listbox"], .jst-palette {
  padding: var(--jst-space-2xs);
  border: var(--jst-border-width) solid var(--jst-border);
  border-radius: var(--jst-radius-l);
  background: var(--jst-bg); color: var(--jst-fg);
  box-shadow: var(--jst-shadow);
}
/* Items inside a floating panel: same box, different activation cue (menu
   items respond to hover, combobox options to data-active). */
:where(.jst-menu, jst-combobox [role="listbox"], .jst-palette) :where(button, a, [role="option"]) {
  display: flex; align-items: center; gap: var(--jst-space-xs);
  inline-size: 100%; text-align: start;
  padding: var(--jst-space-2xs) var(--jst-space-s);
  background: none; border: none; border-radius: var(--jst-radius-s);
  color: var(--jst-fg); font: inherit; text-decoration: none; cursor: pointer;
}
.jst-menu[popover] {
  margin: 0;
  inline-size: max-content; min-inline-size: 10rem;
}
@supports (position-area: bottom) {
  .jst-menu[popover] {
    position-area: bottom span-right;   /* both keywords physical (mixing physical + logical = invalid, dropped) */
    margin-block-start: var(--jst-space-2xs);
    position-try-fallbacks: flip-block, flip-inline;
  }
}
@supports not (position-area: bottom) {
  /* Fallback: keep it in flow-ish near the trigger. */
  .jst-menu[popover] { position: fixed; inset: auto; }
}
.jst-menu button:hover, .jst-menu a:hover { background: var(--jst-surface); }
.jst-menu hr { border: none; border-block-start: var(--jst-border-width) solid var(--jst-border); margin: var(--jst-space-2xs) 0; }

/* Popover enter/leave. */
@supports (transition-behavior: allow-discrete) {
  .jst-menu[popover] {
    opacity: 0; transform: translateY(-0.25rem);
    transition: opacity var(--jst-motion-fast) var(--jst-ease), transform var(--jst-motion-fast) var(--jst-ease), overlay var(--jst-motion-fast) var(--jst-ease) allow-discrete, display var(--jst-motion-fast) var(--jst-ease) allow-discrete;
  }
  .jst-menu[popover]:popover-open { opacity: 1; transform: none; }
  @starting-style { .jst-menu[popover]:popover-open { opacity: 0; transform: translateY(-0.25rem); } }
}

/* ============================================================================
 * Tabs - a JST component (no native equivalent): roving tabindex + ARIA.
 * ==========================================================================*/
jst-tabs { display: block; }
jst-tabs [role="tablist"] { display: flex; gap: var(--jst-space-2xs); border-block-end: var(--jst-border-width) solid var(--jst-border); }
jst-tabs [role="tab"] {
  background: none; border: none; border-block-end: 2px solid transparent; border-radius: 0;
  margin-block-end: -1px; padding: var(--jst-space-2xs) var(--jst-space-s);
  color: var(--jst-muted); cursor: pointer; font: inherit;
}
jst-tabs [role="tab"][aria-selected="true"] { color: var(--jst-fg); border-block-end-color: var(--jst-accent); font-weight: 600; }
jst-tabs [role="tab"]:hover { color: var(--jst-fg); }
jst-tabs [role="tabpanel"] { padding-block: var(--jst-space-m); color: var(--jst-muted); }

/* ============================================================================
 * Toaster - a JST component: a fixed stack, pushed via a custom command.
 * ==========================================================================*/
jst-toaster {
  position: fixed; inset-block-end: var(--jst-space-l); inset-inline-end: var(--jst-space-l);
  display: flex; flex-direction: column; gap: var(--jst-space-xs); z-index: 9999;
  pointer-events: none;
}
/* Corners and centers via data-position; end-bottom is the default above. */
jst-toaster[data-position~="start"]  { inset-inline: var(--jst-space-l) auto; }
jst-toaster[data-position~="top"]    { inset-block: var(--jst-space-l) auto; }
jst-toaster[data-position~="center"] { inset-inline: 50% auto; translate: -50% 0; align-items: center; }
/* What only the toast adds to the shared status surface: it floats — and as a
   floating container it takes --jst-radius-l, deliberately rounder than the
   in-flow alert's --jst-radius. */
jst-toaster .jst-toast {
  pointer-events: auto;
  box-shadow: var(--jst-shadow);
  min-inline-size: 11rem;
  border-radius: var(--jst-radius-l);
  animation: jst-toast-in var(--jst-motion-fast) ease-out;
}
jst-toaster .jst-toast > button { margin-inline-start: auto; }
@keyframes jst-toast-in { from { opacity: 0; transform: translateY(0.5rem); } }

/* ============================================================================
 * Combobox - a JST component: filter + keyboard + ARIA listbox.
 * ==========================================================================*/
jst-combobox { display: block; position: relative; max-inline-size: 22rem; }
jst-combobox input { inline-size: 100%; }
jst-combobox [role="listbox"] {
  position: absolute; inset-inline: 0; z-index: 20; margin-block-start: var(--jst-space-2xs);
  max-block-size: 14rem; overflow: auto; list-style: none;
}
jst-combobox [role="option"][data-active="true"] { background: var(--jst-accent); color: var(--jst-accent-fg); }
jst-combobox .jst-empty { padding: var(--jst-space-s); color: var(--jst-muted); }

/* ============================================================================
 * Switch - a styled native checkbox. CSS + HTML only.
 * ==========================================================================*/
.jst-switch { display: inline-flex; align-items: center; gap: var(--jst-space-xs); cursor: pointer; }
.jst-switch input { position: absolute; opacity: 0; inline-size: 0; block-size: 0; }
.jst-switch > .track {
  inline-size: 2.1em; block-size: 1.2em; padding: 0.15em; border-radius: 99px;
  background: var(--jst-border); transition: background var(--jst-motion-fast) var(--jst-ease);
  display: inline-flex; align-items: center;
}
.jst-switch > .track::before {
  content: ""; inline-size: 0.95em; block-size: 0.95em; border-radius: 50%;
  background: var(--jst-bg); transition: transform var(--jst-motion-fast) var(--jst-ease); box-shadow: 0 1px 2px oklch(0 0 0 / 0.3);
}
.jst-switch input:checked + .track { background: var(--jst-accent); }
.jst-switch input:checked + .track::before { transform: translateX(0.9em); }
.jst-switch input:focus-visible + .track { box-shadow: var(--jst-ring, 0 0 0 3px oklch(0.6 0.1 262 / 0.5)); }

/* ============================================================================
 * Tooltip - CSS + HTML only (data-tip + ::after on hover/focus).
 * ==========================================================================*/
.jst-tooltip { position: relative; border-block-end: 1px dotted var(--jst-muted); cursor: help; }
.jst-tooltip::after {
  content: attr(data-tip); position: absolute; inset-block-end: calc(100% + 0.4rem); inset-inline-start: 50%;
  translate: -50% 0; inline-size: max-content; max-inline-size: 16rem;
  padding: var(--jst-space-2xs) var(--jst-space-s); border-radius: var(--jst-radius);
  background: var(--jst-fg); color: var(--jst-bg); font-size: 0.8rem;
  opacity: 0; transition: opacity var(--jst-motion-fast) var(--jst-ease); pointer-events: none;
}
.jst-tooltip:hover::after, .jst-tooltip:focus-visible::after { opacity: 1; }

/* ============================================================================
 * Alert / callout - CSS + HTML only. Variants via data-variant.
 * ==========================================================================*/
/* One rule set for the alert (in flow) and the toast (floating): the same
   status surface, so they can only ever match. data-variant drives the edge
   color from the status variables; the toast merely adds float-ness below. */
.jst-alert, jst-toaster .jst-toast {
  display: flex; align-items: flex-start; gap: var(--jst-space-xs);
  font-size: var(--jst-control-size);
  padding: 0.45em 0.8em;
  border: var(--jst-border-width) solid var(--jst-border);
  border-inline-start: 3px solid var(--jst-accent);
  border-radius: var(--jst-radius);
  background: var(--jst-bg); color: var(--jst-fg);
}
.jst-alert[data-variant="success"], jst-toaster .jst-toast[data-variant="success"] { border-inline-start-color: var(--jst-ok); background: color-mix(in oklch, var(--jst-ok) 7%, var(--jst-bg)); }
.jst-alert[data-variant="warning"], jst-toaster .jst-toast[data-variant="warning"] { border-inline-start-color: var(--jst-warn); background: color-mix(in oklch, var(--jst-warn) 8%, var(--jst-bg)); }
.jst-alert[data-variant="error"],   jst-toaster .jst-toast[data-variant="error"]   { border-inline-start-color: var(--jst-error); background: color-mix(in oklch, var(--jst-error) 7%, var(--jst-bg)); }

/* ============================================================================
 * Progress + spinner - native <progress> + a CSS spinner. CSS + HTML only.
 * ==========================================================================*/
progress.jst-progress { inline-size: 100%; block-size: 0.6rem; accent-color: var(--jst-accent); border-radius: 99px; }
.jst-spinner {
  inline-size: 1.5rem; block-size: 1.5rem; border-radius: 50%;
  border: 3px solid var(--jst-border); border-block-start-color: var(--jst-accent);
  animation: jst-spin 0.7s linear infinite;
}
@keyframes jst-spin { to { transform: rotate(360deg); } }

/* ============================================================================
 * Data table (sortable) - a JST component. Header buttons toggle sort.
 * ==========================================================================*/
jst-table { display: block; overflow-x: auto; }
jst-table table { inline-size: 100%; border-collapse: collapse; }
jst-table th, jst-table td { text-align: start; padding: var(--jst-space-2xs) var(--jst-space-s); border-block-end: var(--jst-border-width) solid var(--jst-border); }
jst-table thead th { padding: 0; }
jst-table thead button {
  inline-size: 100%; text-align: start; background: none; border: none; color: var(--jst-muted);
  font: inherit; font-weight: 700; cursor: pointer; padding: var(--jst-space-2xs) var(--jst-space-s);
}
jst-table thead button:hover { color: var(--jst-fg); }
jst-table th[aria-sort="ascending"] button, jst-table th[aria-sort="descending"] button { color: var(--jst-fg); }
jst-table tbody tr:hover { background: var(--jst-surface); }
:where(jst-table[striped], table.jst-striped) tbody tr:nth-child(even) { background: color-mix(in oklch, var(--jst-surface) 55%, var(--jst-bg)); }

/* ============================================================================
 * Skeleton - a shimmering placeholder while content loads. Pairs with
 * <jst-include>: put skeletons inside it as the pre-fetch content.
 * ==========================================================================*/
.jst-skeleton {
  display: block;
  block-size: 1em;
  border-radius: var(--jst-radius-s);
  background: linear-gradient(90deg, var(--jst-surface) 25%, var(--jst-border) 37%, var(--jst-surface) 63%);
  background-size: 400% 100%;
  animation: jst-skeleton 1.4s ease infinite;
  color: transparent;
  user-select: none;
}
@keyframes jst-skeleton { from { background-position: 100% 50%; } to { background-position: 0 50%; } }
@media (prefers-reduced-motion: reduce) { .jst-skeleton { animation: none; } }

/* ============================================================================
 * Badge - status chips. Variants via data-variant; accent by default.
 * ==========================================================================*/
.jst-badge {
  display: inline-flex; align-items: center; gap: 0.35em;
  font-size: 0.65rem; font-weight: 600; line-height: 1.4;
  padding: 0.08em 0.55em; border-radius: 99px; white-space: nowrap;
  background: var(--jst-accent); color: var(--jst-accent-fg);
}
.jst-badge[data-variant="muted"]   { background: var(--jst-surface); color: var(--jst-muted); border: var(--jst-border-width) solid var(--jst-border); }
.jst-badge[data-variant="success"] { background: var(--jst-ok); color: var(--jst-ok-fg); }
.jst-badge[data-variant="warning"] { background: var(--jst-warn); color: var(--jst-warn-fg); }
.jst-badge[data-variant="error"]   { background: var(--jst-error); color: var(--jst-error-fg); }

/* ============================================================================
 * Avatar - a circle with an image, or initials as the fallback. Size via
 * --size; jst-frame already handles arbitrary-ratio crops.
 * ==========================================================================*/
.jst-avatar {
  --size: 2.4rem;
  display: inline-flex; align-items: center; justify-content: center;
  inline-size: var(--size); block-size: var(--size);
  border-radius: 50%; overflow: clip;
  background: var(--jst-surface); color: var(--jst-muted);
  font-weight: 600; font-size: calc(var(--size) * 0.38);
  user-select: none;
}
.jst-avatar > img { inline-size: 100%; block-size: 100%; object-fit: cover; }

/* ============================================================================
 * Breadcrumb - nav > ol with CSS separators; current page via aria-current.
 * ==========================================================================*/
.jst-breadcrumb ol {
  display: flex; flex-wrap: wrap; align-items: center; gap: var(--jst-space-2xs);
  list-style: none; margin: 0; padding: 0; font-size: 0.9rem;
}
.jst-breadcrumb li { display: inline-flex; align-items: center; gap: var(--jst-space-2xs); }
.jst-breadcrumb li + li::before { content: "/"; color: var(--jst-border); }
.jst-breadcrumb a { color: var(--jst-muted); text-decoration: none; }
.jst-breadcrumb a:hover { color: var(--jst-accent-600); text-decoration: underline; }
.jst-breadcrumb [aria-current="page"] { color: var(--jst-fg); font-weight: 600; }

/* ============================================================================
 * Pagination - nav of links/buttons; the current page via aria-current.
 * Server-rendered pages emit this constantly, so it is pure markup + CSS.
 * ==========================================================================*/
.jst-pagination { display: flex; flex-wrap: wrap; align-items: center; gap: 0.15em; font-size: var(--jst-control-size); font-variant-numeric: tabular-nums; }
.jst-pagination :where(a, button, span) {
  display: inline-flex; align-items: center; justify-content: center;
  min-inline-size: 1.7em; padding: 0.15em 0.4em;
  border: none; border-radius: var(--jst-radius);
  background: none; color: var(--jst-muted);
  font: inherit; text-decoration: none; cursor: pointer;
}
.jst-pagination :where(a, button):hover { background: var(--jst-surface); color: var(--jst-fg); }
.jst-pagination [aria-current="page"] { background: var(--jst-accent); color: var(--jst-accent-fg); font-weight: 600; }

/* ============================================================================
 * Drawer - the modal's edge-docked variant: same native <dialog>, same invoker
 * wiring. data-side="end | start | top | bottom" picks the edge (end is the
 * default). Add closedby="any" on the dialog for backdrop light dismiss
 * (Baseline Newly; the demo ships a 3-line fallback for older engines).
 * ==========================================================================*/
.jst-drawer {
  margin: 0;
  padding: 0;
  border: none;
  background: var(--jst-bg); color: var(--jst-fg);
  box-shadow: var(--jst-shadow);
}

/* Edge placement: the dialog's auto margins do the docking. */
.jst-drawer:not([data-side]), .jst-drawer[data-side="end"] {
  margin-inline-start: auto;
  inline-size: min(20rem, 90vw); block-size: 100dvb; max-block-size: 100dvb;
  border-inline-start: var(--jst-border-width) solid var(--jst-border);
}
.jst-drawer[data-side="start"] {
  margin-inline-end: auto;
  inline-size: min(20rem, 90vw); block-size: 100dvb; max-block-size: 100dvb;
  border-inline-end: var(--jst-border-width) solid var(--jst-border);
}
.jst-drawer[data-side="top"] {
  margin-block-end: auto;
  inline-size: 100vw; max-inline-size: 100vw; max-block-size: 80dvb;
  border-block-end: var(--jst-border-width) solid var(--jst-border);
}
.jst-drawer[data-side="bottom"] {
  margin-block-start: auto;
  inline-size: 100vw; max-inline-size: 100vw; max-block-size: 80dvb;
  border-block-start: var(--jst-border-width) solid var(--jst-border);
}

/* Slide in from the docked edge. */
@supports (transition-behavior: allow-discrete) {
  .jst-drawer {
    opacity: 0;
    transition: opacity var(--jst-motion) var(--jst-ease), transform var(--jst-motion) var(--jst-ease), overlay var(--jst-motion) var(--jst-ease) allow-discrete, display var(--jst-motion) var(--jst-ease) allow-discrete;
  }
  .jst-drawer:not([data-side]), .jst-drawer[data-side="end"] { transform: translateX(100%); }
  .jst-drawer[data-side="start"]  { transform: translateX(-100%); }
  .jst-drawer[data-side="top"]    { transform: translateY(-100%); }
  .jst-drawer[data-side="bottom"] { transform: translateY(100%); }
  .jst-drawer[open] { opacity: 1; transform: none; }
  @starting-style {
    .jst-drawer[open]:not([data-side]), .jst-drawer[data-side="end"][open] { opacity: 0; transform: translateX(100%); }
    .jst-drawer[data-side="start"][open]  { opacity: 0; transform: translateX(-100%); }
    .jst-drawer[data-side="top"][open]    { opacity: 0; transform: translateY(-100%); }
    .jst-drawer[data-side="bottom"][open] { opacity: 0; transform: translateY(100%); }
  }
}

/* ============================================================================
 * Join - collapse a row of controls into one unit: input groups with
 * prefix/suffix addons (.jst-addon), and button groups, with one rule set.
 * ==========================================================================*/
.jst-join { display: flex; align-items: stretch; }
.jst-join > * { border-radius: 0; }
.jst-join > * + * { margin-inline-start: calc(var(--jst-border-width) * -1); }
.jst-join > :first-child { border-start-start-radius: var(--jst-radius); border-end-start-radius: var(--jst-radius); }
.jst-join > :last-child { border-start-end-radius: var(--jst-radius); border-end-end-radius: var(--jst-radius); }
.jst-join > :where(input, select) { min-inline-size: 0; flex: 1; }
.jst-join > :focus-visible { z-index: 1; }
.jst-join > .jst-addon {
  display: inline-flex; align-items: center;
  padding-inline: var(--jst-space-s);
  border: var(--jst-border-width) solid var(--jst-border);
  background: var(--jst-surface); color: var(--jst-muted);
}
/* Adjacent accent buttons need a visible seam: derive it from the accent. */
.jst-join > button + button {
  border-inline-start-color: oklch(from var(--jst-accent) calc(l - 0.14) c h);
}

/* Accordion open/close animation once the engine can animate to auto
 * (interpolate-size); scoped so the opt-in doesn't leak. */
.jst-accordion { interpolate-size: allow-keywords; }
@supports (interpolate-size: allow-keywords) {
  .jst-accordion details::details-content {
    block-size: 0; overflow: clip;
    transition: block-size var(--jst-motion) var(--jst-ease), content-visibility var(--jst-motion) var(--jst-ease) allow-discrete;
  }
  .jst-accordion details[open]::details-content { block-size: auto; }
}

/* ============================================================================
 * List - hairline-separated record rows: main content grows, anything marked
 * data-actions trails. The list pattern every CRUD screen reinvents.
 * ==========================================================================*/
.jst-list { list-style: none; margin: 0; padding: 0; }
.jst-list > * {
  display: flex; align-items: center; gap: var(--jst-space-s);
  padding: var(--jst-space-xs) var(--jst-space-2xs);
}
.jst-list > * + * { border-block-start: var(--jst-border-width) solid var(--jst-border); }
.jst-list > *:hover { background: var(--jst-surface); }
.jst-list [data-main] { flex: 1; min-inline-size: 0; }
.jst-list [data-main] small { color: var(--jst-muted); }
.jst-list [data-actions] { display: inline-flex; gap: var(--jst-space-2xs); flex: none; }

/* ============================================================================
 * Empty state - the muted placeholder where a list has nothing yet.
 * ==========================================================================*/
.jst-empty {
  display: grid; place-items: center; gap: var(--jst-space-2xs);
  padding: var(--jst-space-xl) var(--jst-space-m);
  border: var(--jst-border-width) dashed var(--jst-border);
  border-radius: var(--jst-radius);
  color: var(--jst-muted); text-align: center;
}

/* ============================================================================
 * Stat - a labelled figure. data-trend colors the value; compose a row of
 * them with <jst-grid> or <jst-cluster>.
 * ==========================================================================*/
.jst-stat { display: flex; flex-direction: column; gap: 0.1em; }
.jst-stat > .jst-stat-value { font-size: 1.3em; font-weight: 700; font-variant-numeric: tabular-nums; }
.jst-stat[data-trend="up"] > .jst-stat-value { color: var(--jst-ok); }
.jst-stat[data-trend="down"] > .jst-stat-value { color: var(--jst-error); }

/* ============================================================================
 * Page header - eyebrow / title / subtitle, with actions pushed to the end.
 * ==========================================================================*/
.jst-page-header {
  display: flex; flex-wrap: wrap; align-items: flex-end; gap: var(--jst-space-s);
}
.jst-page-header > [data-main] { flex: 1; min-inline-size: 0; }
.jst-page-header h1, .jst-page-header h2 { margin: 0; }
.jst-page-header [data-actions] { display: inline-flex; gap: var(--jst-space-2xs); flex: none; }
.jst-page-header .jst-sub { color: var(--jst-muted); font-size: var(--jst-font-small); margin: 0; }
/* .jst-eyebrow shares the micro-label rule in jst-layout.css. */

/* ============================================================================
 * Command palette - a JST component: global shortcut, filter, keyboard run.
 * The panel and options ride the shared floating-panel rules above.
 * ==========================================================================*/
jst-palette .jst-palette-overlay {
  position: fixed; inset: 0; z-index: 10000;
  background: var(--jst-backdrop);
  display: flex; justify-content: center; align-items: flex-start;
  padding-block-start: 12vb;
}
jst-palette .jst-palette {
  inline-size: min(34rem, 92vw);
  display: flex; flex-direction: column;
  max-block-size: 60vb;
}
jst-palette .jst-palette > input {
  font-size: 1rem;
  border: none; border-block-end: var(--jst-border-width) solid var(--jst-border);
  border-radius: 0; outline: none; box-shadow: none;
  padding: 0.6em 0.8em;
  background: transparent;
}
jst-palette .jst-palette > ul {
  list-style: none; margin: 0; padding: var(--jst-space-2xs);
  overflow: auto;
}
jst-palette .jst-palette [role="option"] { justify-content: space-between; cursor: pointer; }
jst-palette .jst-palette [role="option"][data-active="true"] { background: var(--jst-accent); color: var(--jst-accent-fg); }
jst-palette .jst-palette [role="option"][data-active="true"] kbd { border-color: transparent; background: oklch(from var(--jst-accent) calc(l - 0.1) c h); color: var(--jst-accent-fg); }
jst-palette .jst-empty-line { padding: var(--jst-space-s); color: var(--jst-muted); }

/* ---- More framework skins (#1): one variable-block each. ------------------- */

/* Material Design 3-like: purple primary, large radius, BORDERLESS surfaces with
   layered elevation, and filled inputs. Card depth is carried by shadow (light)
   and raised surface tone (dark); --jst-border stays a faint outline-variant
   because material keeps dividers on lists, tables and accordions - true
   borderlessness is scoped to jst-box below. */
[data-theme="material"] {
  --jst-accent: oklch(0.47 0.15 300);
  --jst-radius: 1rem;
  --jst-font: "Roboto", system-ui, sans-serif;
  --jst-border: light-dark(oklch(0.55 0.01 300 / 0.28), oklch(0.85 0.01 300 / 0.22));
  --jst-surface: light-dark(oklch(0.95 0.006 300), oklch(0.27 0.008 300));
  --jst-shadow-surface: 0 1px 3px oklch(0 0 0 / 0.2), 0 4px 8px oklch(0 0 0 / 0.12);
  --jst-shadow: 0 1px 3px oklch(0 0 0 / 0.3), 0 4px 8px oklch(0 0 0 / 0.15);
  --jst-ring: 0 0 0 3px oklch(0.47 0.15 300 / 0.4);
}

/* Ant Design-like: brand blue, small radius, hairline border plus a soft
   two-part shadow - the enterprise-console look. */
[data-theme="antd"] {
  --jst-accent: oklch(0.58 0.17 258);
  --jst-radius: 0.375rem;
  --jst-font: -apple-system, "Segoe UI", Roboto, sans-serif;
  --jst-border: light-dark(oklch(0.92 0.004 258), oklch(0.31 0.008 258));
  --jst-shadow-surface: 0 1px 2px oklch(0 0 0 / 0.06), 0 1px 6px -1px oklch(0 0 0 / 0.05);
  --jst-shadow: 0 6px 16px oklch(0 0 0 / 0.08), 0 3px 6px oklch(0 0 0 / 0.12);
  --jst-ring: 0 0 0 2px oklch(0.58 0.17 258 / 0.3);
}

/* Tailwind UI-like: indigo-600, medium radius, hairline borders + a tight
   shadow-sm. The default Tailwind card: 1px ring of border, minimal lift. */
[data-theme="tailwind"] {
  --jst-accent: oklch(0.51 0.23 277);
  --jst-radius: 0.5rem;
  --jst-font: ui-sans-serif, system-ui, sans-serif;
  --jst-border: light-dark(oklch(0.93 0.004 277), oklch(0.33 0.008 277));
  --jst-shadow-surface: 0 1px 2px oklch(0 0 0 / 0.05);
  --jst-shadow: 0 10px 15px -3px oklch(0 0 0 / 0.1), 0 4px 6px -4px oklch(0 0 0 / 0.1);
  --jst-ring: 0 0 0 2px var(--jst-bg), 0 0 0 4px oklch(0.51 0.23 277 / 0.5);
}

/* DaisyUI-like: vivid violet, very large radius, BORDERLESS soft cards with a
   gentle single-layer shadow, filled inputs, chunky rounded controls. Softer and
   rounder than material - distinguished by radius, tint and a diffuse shadow.
   As with material, --jst-border keeps a faint divider tone so tables, lists
   and accordions retain their separators; only jst-box goes borderless. */
[data-theme="daisy"] {
  --jst-accent: oklch(0.49 0.24 277);
  --jst-radius: 1rem;
  --jst-font: system-ui, sans-serif;
  --jst-border: light-dark(oklch(0.55 0.015 277 / 0.28), oklch(0.85 0.015 277 / 0.22));
  --jst-surface: light-dark(oklch(0.96 0.01 277), oklch(0.27 0.012 277));
  --jst-shadow-surface: 0 4px 14px oklch(0.4 0.05 277 / 0.12);
  --jst-shadow: 0 4px 12px oklch(0 0 0 / 0.12);
  --jst-ring: 0 0 0 2px oklch(0.49 0.24 277 / 0.4);
}

/* Filled inputs for the borderless skins: with hairline-free cards a plain
   input would look out of place, so fill it with the surface tone - the
   "filled field" look those systems actually use. */
:where([data-theme="material"], [data-theme="daisy"]) :where(input, textarea, select) {
  background: var(--jst-surface);
}

/* Borderless surfaces, scoped: cards drop their outline (depth comes from
   --jst-shadow-surface and the raised surface tone) while the faint
   --jst-border above keeps dividers alive in tables, lists and accordions. */
:where([data-theme="material"], [data-theme="daisy"]) jst-box {
  border-color: transparent;
}

/* Astryx (Meta)-like: the @astryxdesign/theme-neutral default. A GREY page with
   WHITE raised cards in light mode - inverted from JST's own look, where the
   surface sits a shade DARKER than the page - with a near-black accent, a blue
   focus ring, generous radius and whisper-faint borders. Dark mode flips to a
   near-white accent and lifts elevated surfaces with an inset light ring instead
   of a heavier drop shadow. Neutrals are pure grey (--jst-neutral-c: 0), and it
   slows --jst-motion to 300ms to show motion is a theme knob too. Ideas adapted
   from Meta's Astryx design system (MIT). Figtree is Astryx's face; it isn't
   loaded here, so the stack falls back to system-ui. Caveat for surface-inverting
   skins like this one: hover/stripe fills use --jst-surface, so inside a white
   jst-box card those affordances lose contrast (white-on-white); on the page
   background they read correctly. */
[data-theme="astryx"] {
  --jst-neutral-c: 0;
  --jst-bg:      light-dark(oklch(0.958 var(--jst-neutral-c) var(--jst-neutral-h)), oklch(0.222 var(--jst-neutral-c) var(--jst-neutral-h)));
  --jst-surface: light-dark(oklch(1 var(--jst-neutral-c) var(--jst-neutral-h)),     oklch(0.269 var(--jst-neutral-c) var(--jst-neutral-h)));
  --jst-fg:      light-dark(oklch(0.205 var(--jst-neutral-c) var(--jst-neutral-h)), oklch(0.985 var(--jst-neutral-c) var(--jst-neutral-h)));
  --jst-muted:   light-dark(oklch(0.556 var(--jst-neutral-c) var(--jst-neutral-h)), oklch(0.715 var(--jst-neutral-c) var(--jst-neutral-h)));
  --jst-border:  light-dark(oklch(0.940 var(--jst-neutral-c) var(--jst-neutral-h)), oklch(1 0 0 / 0.10));
  /* Same-polarity accent (near-black in light, near-white in dark): the fg must
     flip the OTHER way so text on the accent stays legible - like shadcn's
     near-black accent, but scheme-aware since ours inverts. */
  --jst-accent:    light-dark(oklch(0.269 var(--jst-neutral-c) var(--jst-neutral-h)), oklch(0.940 var(--jst-neutral-c) var(--jst-neutral-h)));
  --jst-accent-fg: light-dark(oklch(0.985 0 0), oklch(0.269 0 0));
  --jst-radius: 0.625rem;
  --jst-radius-l: 0.75rem;   /* Astryx's real container radius, below the *1.6 default */
  --jst-font: Figtree, system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;
  --jst-motion-fast: 125ms;
  --jst-motion: 300ms;
  /* Resting cards: two soft drops, always present; the inset light ring only
     materialises in dark mode (transparent in light). */
  --jst-shadow-surface:
    0 2px 4px oklch(0 0 0 / 0.05),
    0 4px 8px oklch(0 0 0 / 0.10),
    inset 0 0 0 1px light-dark(oklch(1 0 0 / 0), oklch(1 0 0 / 0.08));
  /* Floating surfaces: stronger drops in dark, plus a brighter inset ring. */
  --jst-shadow:
    0 2px 4px light-dark(oklch(0 0 0 / 0.05), oklch(0 0 0 / 0.30)),
    0 4px 12px light-dark(oklch(0 0 0 / 0.10), oklch(0 0 0 / 0.40)),
    inset 0 0 0 1px light-dark(oklch(1 0 0 / 0), oklch(1 0 0 / 0.12));
  --jst-ring: 0 0 0 2px oklch(0.57 0.19 255 / 0.5);   /* Astryx focus blue #0074e2 */
}

} /* @layer jst.components */
