/* ===================================================================================================
   PreemptiveScheduler control skin (feature K) — the ONE input/select/textarea/check look. A control
   opts in by carrying .ps-control wherever it is written (FormField slots, FilterBar children, page
   markup); the explicit class beats selector-context magic and reads at the call site. LAYOUT (widths,
   grid spans) stays the consuming pattern's job — see FormField.razor.css for the fill-the-field rule.
   Unlayered (not in @layer ps-*) so the class skin sits at normal author-cascade weight, beating the
   ps-base element defaults by class specificity. --ps-font-size-input is the ≥16px iOS no-zoom floor.
   =================================================================================================== */

.ps-control:where(input:not([type="checkbox"]):not([type="radio"]), select, textarea) {
    min-block-size: var(--ps-control-height);
    padding-block: var(--ps-space-1);
    padding-inline: var(--ps-space-3);
    border: 1px solid var(--ps-color-border-strong);
    border-radius: var(--ps-radius-md);
    background: var(--ps-color-surface-raised);
    color: var(--ps-color-text);
    font-family: inherit;
    font-size: var(--ps-font-size-input);
    line-height: var(--ps-line-dense);
}

.ps-control::placeholder {
    color: var(--ps-color-text-faint);
}

/* WCAG 2.5.8-sized check controls, custom-drawn so checked vs unchecked is UNAMBIGUOUS in both schemes. The native
   UA checkbox renders an unchecked box as a grey-filled square in the dark scheme (it reads as checked/indeterminate),
   so we replace the native paint (appearance:none) with an explicit outline-empty (unchecked) / accent-filled-with-glyph
   (checked) look on the theme tokens. The glyph is centered via the grid placement and the :checked ::before. */
.ps-control:where([type="checkbox"], [type="radio"]) {
    appearance: none;
    inline-size: var(--ps-target-min);
    block-size: var(--ps-target-min);
    flex: none;
    margin: 0;
    /* Unchecked: an empty box/circle on the raised surface with a strong border — clearly NOT filled. */
    border: 1px solid var(--ps-color-border-strong);
    background: var(--ps-color-surface-raised);
    cursor: pointer;
    display: inline-grid;
    place-content: center;
    transition:
        background-color var(--ps-motion-fast) var(--ps-ease),
        border-color var(--ps-motion-fast) var(--ps-ease);
}

.ps-control:where([type="checkbox"]) {
    border-radius: var(--ps-radius-sm);
}

.ps-control:where([type="radio"]) {
    border-radius: var(--ps-radius-full);
}

/* Checked: the accent fill plus a drawn glyph (a check for the box, a dot for the radio) — unmistakably ON. */
.ps-control:where([type="checkbox"], [type="radio"]):checked {
    background: var(--ps-color-accent);
    border-color: var(--ps-color-accent);
}

/* The glyph is sized from the TARGET TOKEN, not in percent: the box is a grid container whose single track is
   auto-sized, so a percentage resolves against an indefinite grid area and collapses the glyph to nothing — the
   filled box then reads the same checked, indeterminate, or (for a radio) selected, which is precisely the
   distinction these marks exist to draw. calc() off the same token keeps them scaling with the coarse-pointer
   target size. */
.ps-control:where([type="checkbox"]):checked::before {
    content: "";
    inline-size: calc(var(--ps-target-min) * 0.6);
    block-size: calc(var(--ps-target-min) * 0.6);
    background: var(--ps-color-on-accent);
    /* A check mark drawn from the on-accent color (a clipped tick) so it reads in both schemes and scales with the box. */
    clip-path: polygon(14% 44%, 0 60%, 40% 100%, 100% 22%, 84% 8%, 38% 70%);
}

.ps-control:where([type="radio"]):checked::before {
    content: "";
    inline-size: calc(var(--ps-target-min) * 0.45);
    block-size: calc(var(--ps-target-min) * 0.45);
    border-radius: var(--ps-radius-full);
    background: var(--ps-color-on-accent);
}

/* Indeterminate (the tri-state parent of a checkbox tree, e.g. the candidate-GPU picker's cluster/node boxes):
   the accent fill with a drawn DASH — unmistakably "some, not all". `indeterminate` is a DOM property with no
   attribute form, so Blazor stamps it over interop (site.js setIndeterminate); this rule is what paints it,
   since appearance:none above discards the UA's native dash. */
.ps-control:where([type="checkbox"]):indeterminate {
    background: var(--ps-color-accent);
    border-color: var(--ps-color-accent);
}

.ps-control:where([type="checkbox"]):indeterminate::before {
    content: "";
    inline-size: calc(var(--ps-target-min) * 0.55);
    block-size: 2px;
    border-radius: var(--ps-radius-full);
    background: var(--ps-color-on-accent);
    clip-path: none;
}

.ps-control:where([type="checkbox"], [type="radio"]):focus-visible {
    outline: 2px solid var(--ps-color-focus);
    outline-offset: 2px;
}

.ps-control:where([type="checkbox"], [type="radio"]):disabled {
    cursor: not-allowed;
    opacity: 0.55;
}

/* ===================================================================================================
   The .ps-button family is GLOBAL, not scoped, because TWO primitives wear it: Button (a <button>) and
   LinkButton (a real <a href>, for navigations that must support middle-click / new-tab). A scoped sheet
   would only attach to one component's element. Unlayered for the same reason .ps-control is — the class
   skin must outrank the ps-base <a>/<button> element defaults by specificity.
   =================================================================================================== */
.ps-button {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    gap: var(--ps-space-2);
    border: 1px solid transparent;
    border-radius: var(--ps-radius-md);
    font-family: inherit;
    font-weight: var(--ps-weight-medium);
    line-height: var(--ps-line-dense);
    white-space: nowrap;
    cursor: pointer;
    transition:
        background-color var(--ps-motion-fast) var(--ps-ease),
        border-color var(--ps-motion-fast) var(--ps-ease),
        color var(--ps-motion-fast) var(--ps-ease);
}

/* LinkButton resets: an <a> brings link defaults (underline, link color) that must yield to the variants. */
a.ps-button--link {
    text-decoration: none;
}

.ps-button:focus-visible {
    outline: 2px solid var(--ps-color-focus);
    outline-offset: 2px;
}

.ps-button:disabled,
.ps-button--link[aria-disabled="true"] {
    cursor: not-allowed;
    opacity: 0.55;
}

/* --- Sizes (both tokens bump to the 44px tier on coarse pointers). --- */
.ps-button--medium {
    min-block-size: var(--ps-control-height);
    padding-inline: var(--ps-space-3);
    font-size: var(--ps-text-sm);
}

.ps-button--small {
    min-block-size: var(--ps-target-min);
    padding-inline: var(--ps-space-2);
    font-size: var(--ps-text-xs);
}

/* Icon-only buttons render square at their size's height. */
.ps-button--icon-only {
    padding-inline: 0;
    aspect-ratio: 1;
}

/* --- Variants. --- */
.ps-button--primary {
    background: var(--ps-color-accent);
    border-color: var(--ps-color-accent);
    color: var(--ps-color-on-accent);
}

.ps-button--primary:hover:not(:disabled):not([aria-disabled="true"]) {
    background: var(--ps-color-accent-emphasis);
    border-color: var(--ps-color-accent-emphasis);
}

.ps-button--secondary {
    background: var(--ps-color-surface-raised);
    border-color: var(--ps-color-border-strong);
    color: var(--ps-color-text);
}

.ps-button--secondary:hover:not(:disabled):not([aria-disabled="true"]) {
    background: var(--ps-color-surface-sunken);
}

.ps-button--ghost {
    background: transparent;
    color: var(--ps-color-accent);
}

.ps-button--ghost:hover:not(:disabled):not([aria-disabled="true"]) {
    background: var(--ps-color-accent-soft);
}

.ps-button--danger {
    background: var(--ps-color-danger);
    border-color: var(--ps-color-danger);
    color: var(--ps-color-on-danger);
}

.ps-button--danger:hover:not(:disabled):not([aria-disabled="true"]) {
    background: color-mix(in srgb, var(--ps-color-danger) 85%, var(--ps-color-text));
    border-color: color-mix(in srgb, var(--ps-color-danger) 85%, var(--ps-color-text));
}

/* The Busy spinner: rotation rides the motion tokens, so reduced-motion renders it static (the disabled
   state + aria-busy still convey progress). */
.ps-button__spinner {
    display: inline-flex;
    animation: ps-button-spin calc(var(--ps-motion-slow) * 3) linear infinite;
}

@keyframes ps-button-spin {
    to {
        transform: rotate(360deg);
    }
}

/* ===================================================================================================
   Anchored panels — the ONE placement every top-layer panel beside a trigger uses. A panel opts in by
   carrying .ps-anchored-panel wherever it is written; the panel and its trigger name each other inline
   (anchor-name on the trigger, position-anchor on the panel), because each instance anchors to itself.

   Global rather than scoped, for two reasons that are the same reason: a @position-try at-rule's name is
   document-wide however it is authored, so four components declaring "the same fallback" declared four
   names for one ladder; and a panel raised into the TOP LAYER is not in any component's stacking context,
   so what places it is not a component's business either.

   inset:auto + margin:0 replace the user agent's viewport centering. The ladder never puts the panel in
   the trigger's own row: an inline-start/inline-end area would centre it on that row and cover the very
   control a reader just used.

   WHICH EDGE AN AREA ALIGNS TO reads backwards from its name, and is a fact about the areas rather than a
   preference: a span-inline-* area covers the anchor's own column PLUS the named side, so the only anchor
   edge bounding it is the OTHER one — and the box is aligned to that bound. `span-inline-end` therefore
   puts the panel's inline-START edge on the anchor's start edge, and `span-inline-start` puts its END edge
   on the anchor's end edge.

   TWO ANCHOR SHAPES, TWO PLACEMENTS. An ELEMENT-anchored panel — a word, a button, a pin — opens from the
   START edge of the thing it belongs to, in reading order, and moves to the other edge only when staying
   would leave the viewport; that is the ladder below. An anchor wide enough that neither edge is where the
   reader is looking — a meter's track — is placed at the gesture instead (the block after it).
   =================================================================================================== */

.ps-anchored-panel {
    inset: auto;
    margin: 0;
    border: 1px solid var(--ps-color-border);
    border-radius: var(--ps-radius-md);
    background: var(--ps-color-surface-raised);
    box-shadow: var(--ps-shadow-2);
    position-area: block-end span-inline-end;
    position-try-fallbacks: --ps-anchored-below-end, --ps-anchored-above-start, --ps-anchored-above-end;
}

/* The order keeps the eye where it already is — the other horizontal edge first, so a panel near the
   viewport's inline-end slides sideways rather than jumping over its trigger. */
@position-try --ps-anchored-below-end {
    position-area: block-end span-inline-start;
}

@position-try --ps-anchored-above-start {
    position-area: block-start span-inline-end;
}

@position-try --ps-anchored-above-end {
    position-area: block-start span-inline-start;
}

/* The gutter a gesture-placed panel is kept inside, REGISTERED as a length so the script that does the
   clamping reads it computed in pixels rather than re-deriving a token from its own copy of the scale. */
@property --ps-anchored-gutter {
    syntax: "<length>";
    inherits: false;
    initial-value: 0px;
}

/* A panel on a WIDE anchor is placed at the gesture that raised it: below the anchor, centred on the point
   the pointer was at, clamped inside the anchor's extent and the viewport's gutters. What arrives here is
   the finished inline-start in viewport pixels, because the clamp is against the panel's OWN width and CSS
   cannot refer to that — site.js measures the panel it has just raised and writes the answer.
   The placement is settled when the panel is raised and does not follow the pointer afterwards: the panel
   has rows to read, and one that slid about under the pointer could not be read at all. A panel raised with
   no pointer behind it — a keyboard, a re-render — has no gesture, and falls back to the start edge. */
.ps-anchored-panel--at-gesture {
    --ps-anchored-gutter: var(--ps-space-4);
    position-area: none;
    inset-block-start: anchor(end);
    inset-inline-start: var(--ps-anchored-gesture-start, anchor(start));
    position-try-fallbacks: --ps-anchored-gesture-above;
}

/* Only the block axis flips: the inline position is the reader's own gesture, and moving it would answer a
   pointer that never asked. */
@position-try --ps-anchored-gesture-above {
    inset-block-start: auto;
    inset-block-end: anchor(start);
}
