/* Pec* wrapper component styles — global (not scoped .razor.css) by design.

   Why global: Class="..." on a Pec* wrapper forwards onto the Fluent web component's own root
   element (<fluent-button>, <fluent-dialog>, ...). A Blazor scoped .razor.css rewrites its
   selectors with a [b-xxxxx] attribute that only PecButton.razor's OWN markup carries — the
   Fluent custom element underneath never gets that attribute, so a scoped rule would be a
   silent dead selector. See .knowledge-base/decisions/2026-07-14-pec-shared-component-library.md
   §4 and .knowledge-base/patterns/fluentui-web-component-shadow-dom-styling.md.

   ::part(control) targets confirmed live against the exact @fluentui/web-components 2.x build
   bundled in Microsoft.FluentUI.AspNetCore.Components 4.14.2 (source-map-extracted, not
   guessed) — see .knowledge-base/research/2026-07-14-fluent-button-dialog-progressring-part-names.md.
   Both <fluent-button> and <fluent-dialog> render as their OWN root element (no inner-element
   indirection like FluentDatePicker's <fluent-text-field>), so selectors below target the
   wrapper's own class directly, no tag-name guessing needed.

   PecButton's Href variant (2026-07-15) renders a plain <a class="pec-btn ..."> instead of
   <fluent-button> — a real HTML anchor has NO shadow DOM, so ::part(control) can never match
   it. Every PecButton visual rule below that targets ::part(control) gets a companion
   `a.pec-btn--xxx` selector added to its selector list (comma-joined, same declaration block —
   not a duplicated ruleset) so the <a> gets pixel-identical styling straight on the element
   itself. .pec-btn's own base rule (no ::part()) already applies to both <button> and <a> for
   free since it targets the host/element directly. */

/* ---------------------------------------------------------------------------------------------
   PecButton (Buttons/PecButton.razor)
--------------------------------------------------------------------------------------------- */

.pec-btn {
    border-radius: var(--radius-button);
    font-size: var(--font-size-button);
    height: 60px;
}

.pec-btn::part(control) {
    border-radius: var(--radius-button);
    font-size: var(--font-size-button);
    gap: var(--space-sm);
    align-items: center;   /* vertically center icon + text (svg baseline sits low otherwise) */
}

/* Iconoir <svg> in a text button aligns to the text baseline by default, floating above the
   line's vertical center; pin it to middle so the icon and label read as one centered row. */
.pec-btn .iconoir {
    vertical-align: middle;
}

/* Href variant only: <a class="pec-btn ..."> has no shadow DOM/::part(control) — this rule
   gives the anchor itself the same box model fluent-button's ::part(control) provides natively
   (flex-centered content, no underline/default link styling, pointer cursor, border-box sizing
   so the height/padding math matches). Scoped to the `a` tag so <FluentButton>'s own shadow
   internals are completely untouched. */
a.pec-btn {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    gap: var(--space-sm);
    box-sizing: border-box;
    text-decoration: none;
    cursor: pointer;
    padding: var(--space-sm) var(--space-lg);
}

a.pec-btn[aria-disabled="true"] {
    opacity: 0.5;
    cursor: not-allowed;
}

/* 6-variant Figma reconcile (ADR Decision Log §D2, 2026-07-14) — replaces the old Wave-1
   primary/secondary/danger/ghost block. "Hold" = both :hover AND :active, same color
   (amendment #2) — combined selector per variant. Token gaps (hex with no exact tokens.css
   match) resolved to the nearest existing token per user's fixed no-new-token rule; each gap
   is commented at its rule (also see ADR §D2.3 for the full matrix + rationale, including
   OQ4 — Cancel/Warning Default==Hold bg is an accepted degradation, not a bug). */

/* ---- FILL variants: bg + text on ::part(control) ---- */

.pec-btn--primary::part(control),
a.pec-btn--primary {
    background: var(--color-navy-blue-900);
    color: var(--color-text-base-white-text);
}

.pec-btn--primary:not([disabled]):hover::part(control),
.pec-btn--primary:not([disabled]):active::part(control),
a.pec-btn--primary:not([aria-disabled="true"]):hover,
a.pec-btn--primary:not([aria-disabled="true"]):active {
    background: var(--color-primary-primary-dark);
}

.pec-btn--cancel::part(control),
a.pec-btn--cancel {
    background: var(--color-text-base-button-cancel);
    color: var(--color-neutral-title-content);
}

/* Gap: Figma bg #ACB0B5/#C0C2C5 have no exact token; nearest existing is the same
   --color-text-base-button-cancel for both Default and Hold, so Cancel's press feedback
   collapses to a single color (OQ4, accepted — not a bug). */
.pec-btn--cancel:not([disabled]):hover::part(control),
.pec-btn--cancel:not([disabled]):active::part(control),
a.pec-btn--cancel:not([aria-disabled="true"]):hover,
a.pec-btn--cancel:not([aria-disabled="true"]):active {
    background: var(--color-text-base-button-cancel);
}

/* Text is white (--color-text-base-white-text), NOT navy, despite the amber fill — matches the
   former hand-rolled .btn-resetpw (app.css, now removed — no remaining call sites) that
   EmployeeDetail's Reset Passwords <PecButton Variant="Warning"> replaced pixel-for-pixel.
   Scoped-override note (CORRECTED 2026-07-16 — the original claim below was only half right):
   .pec-btn--icon.pec-btn--warning's BASE rule (0,2,1) does beat this BASE rule (0,1,1), so the
   tint circle itself is safe. But this variant's own :hover/:active rule right below is (0,3,1)
   — MORE specific than .pec-btn--icon.pec-btn--warning's base rule (0,2,1) — because no
   `.pec-btn--icon` hover rule existed. On hover the icon-only Warning button (EmployeeCertsTab's
   edit-icon) was inheriting THIS variant's solid amber hover fill, flipping the tint circle to
   opaque amber with an amber icon (icon-on-fill, effectively invisible). Fixed by adding
   dedicated `.pec-btn--icon.pec-btn--{variant}:hover` rules (see the IconOnly block below) that
   are specific enough to win. Lesson: a cascade trace must check EVERY pseudo-class variant of a
   rule, not just the base state — an "unaffected" base doesn't imply an unaffected hover. */
.pec-btn--warning::part(control),
a.pec-btn--warning {
    background: var(--color-status-waiting);
    color: var(--color-text-base-white-text);
}

/* Gap: Figma Hold bg #FFB247 has no exact token; nearest existing is the same
   --color-status-waiting, so bg doesn't visibly change on Hold (OQ4, accepted). The Hold
   TEXT color still flips per Figma, so press feedback isn't fully lost. */
.pec-btn--warning:not([disabled]):hover::part(control),
.pec-btn--warning:not([disabled]):active::part(control),
a.pec-btn--warning:not([aria-disabled="true"]):hover,
a.pec-btn--warning:not([aria-disabled="true"]):active {
    background: var(--color-status-waiting);
    color: var(--color-status-alert-vbright);
}

.pec-btn--danger::part(control),
a.pec-btn--danger {
    background: var(--color-status-error);
    color: var(--color-text-base-white-text);
}

.pec-btn--danger:not([disabled]):hover::part(control),
.pec-btn--danger:not([disabled]):active::part(control),
a.pec-btn--danger:not([aria-disabled="true"]):hover,
a.pec-btn--danger:not([aria-disabled="true"]):active {
    background: var(--color-red-400);
}

/* PrimaryLight (2026-07-24) — very-bright blue tint fill + primary-blue text. Softer,
   lower-emphasis member of the primary family (contrast: Primary is navy-fill/white-text).
   Text is --color-primary-primary (NOT white) because the #DBEAFE tint is far too light for
   white text to meet contrast. Hold deepens the tint to --color-primary-primary-bright. */
.pec-btn--primary-light::part(control),
a.pec-btn--primary-light {
    background: var(--color-primary-primary-very-bright);
    color: var(--color-navy-blue-900);
}

.pec-btn--primary-light:not([disabled]):hover::part(control),
.pec-btn--primary-light:not([disabled]):active::part(control),
a.pec-btn--primary-light:not([aria-disabled="true"]):hover,
a.pec-btn--primary-light:not([aria-disabled="true"]):active {
    background: var(--color-primary-primary-bright);
}

/* ---- OUTLINE variants: transparent bg + border + text on ::part(control) ---- */

.pec-btn--secondary::part(control),
a.pec-btn--secondary {
    background: transparent;
    border: 1px solid var(--color-navy-blue-900);
    color: var(--color-navy-blue-900);
}

.pec-btn--secondary:not([disabled]):hover::part(control),
.pec-btn--secondary:not([disabled]):active::part(control),
a.pec-btn--secondary:not([aria-disabled="true"]):hover,
a.pec-btn--secondary:not([aria-disabled="true"]):active {
    border-color: var(--color-primary-primary-dark);
    color: var(--color-primary-primary-dark);
}

.pec-btn--delete::part(control),
a.pec-btn--delete {
    background: transparent;
    border: 1px solid var(--color-status-error);
    color: var(--color-status-error);
}

/* Gap: Figma Hold border/text #D66969 has no exact token; nearest ramp neighbor is
   --color-red-400. */
.pec-btn--delete:not([disabled]):hover::part(control),
.pec-btn--delete:not([disabled]):active::part(control),
a.pec-btn--delete:not([aria-disabled="true"]):hover,
a.pec-btn--delete:not([aria-disabled="true"]):active {
    border-color: var(--color-red-400);
    color: var(--color-red-400);
}

.pec-btn--sm::part(control),
a.pec-btn--sm {
    padding: var(--space-xs) var(--space-sm);
    font-size: var(--font-size-caption);
}

.pec-btn--lg::part(control),
a.pec-btn--lg {
    padding: var(--space-sm) var(--space-lg);
    font-size: var(--font-size-base);
}

.pec-btn__spinner {
    /* sits inside the button's ::part(content) slot in place of ChildContent while Loading */
    vertical-align: middle;
}

/* Width (2026-07-16) — free-form width via PecButton's Width param, applied inline as
   style="width: ...". <fluent-button>'s shadow host is display:inline-flex (confirmed live via
   the shipped FAST bundle source map), so the inline style alone already stretches the HOST box —
   no display:block companion rule needed here (unlike .pec-textfield's inline-block host trap
   above). ::part(control) is a SEPARATE box inside the shadow root that does NOT automatically
   follow the host's width — verified live via getBoundingClientRect() on both boxes (host grew,
   control did not until this rule was added). width:100% here means "fill whatever width the
   inline style gave the host", matching every other field box in this file that sets both a host
   width and a ::part width. The <a> variant has no shadow DOM, so its own inline style is
   sufficient — no companion selector needed (contrast with the variant/size rules above, which DO
   need an `a.pec-btn--x` companion because those styles live only in ::part(control)). */
.pec-btn::part(control) {
    width: 100%;
}

/* IconOnly (ADR Decision Log §D7, 2026-07-15) — circular, 1:1, icon-in-brand-color on a
   same-hue tint. OVERRIDES the variant's normal fill (e.g. a normal Primary is navy-fill/
   white-text; an IconOnly Primary is tint-fill/blue-icon) — deliberate, matches Figma
   Table.css action icons (36px, border-radius:99px, no border, per-action tint). Scoped to
   .pec-btn--icon.pec-btn--{variant} so normal (non-icon) buttons of the same variant are
   visually unaffected. Iconoir strokes with currentColor, so ChildContent's <Iconoir> inherits
   `color` below automatically (ADR §1) — no separate icon-fill rule needed. Zero new tokens. */

.pec-btn--icon::part(control),
a.pec-btn--icon {
    width: 36px;      /* Medium = Figma default */
    height: 36px;
    padding: 0;
    min-width: 0;      /* FluentButton's text-button min-width does not apply to a square icon button */
    border-radius: var(--radius-pill);
    border: none;
}

/* Size the HOST too, not just ::part(control) (2026-08-04). The rules above only shrink the
   control box INSIDE fluent-button's shadow root; the <fluent-button> host itself keeps its own
   default text-button height (measured live: host 60px tall around a 36px control, control pinned
   to the host's top edge). The host is what a parent flex row lays out, so `align-items: center`
   centered the 60px host and left the visible 36px circle sitting ~12px high — next to an
   IconOnly Href button (a plain <a>, correctly 36px) the two looked vertically misaligned.
   Only the fluent-button path needs this; `a.pec-btn--icon` above already IS the visible box.
   Scoped to .pec-btn--icon so normal text buttons keep Fluent's native height. */
.pec-btn--icon:not(a) {
    height: 36px;
    width: 36px;
}

.pec-btn--icon.pec-btn--sm::part(control),
a.pec-btn--icon.pec-btn--sm {
    width: 30px;
    height: 30px;
}

.pec-btn--icon.pec-btn--sm:not(a) {
    height: 30px;
    width: 30px;
}

.pec-btn--icon.pec-btn--lg::part(control),
a.pec-btn--icon.pec-btn--lg {
    width: 44px;
    height: 44px;
}

.pec-btn--icon.pec-btn--lg:not(a) {
    height: 44px;
    width: 44px;
}

.pec-btn--icon.pec-btn--primary::part(control),
a.pec-btn--icon.pec-btn--primary {
    background: var(--color-blue-100);
    color: var(--color-primary-primary);
}

.pec-btn--icon.pec-btn--warning::part(control),
a.pec-btn--icon.pec-btn--warning {
    background: var(--color-status-waiting-vbright);
    color: var(--color-status-waiting);
}

.pec-btn--icon.pec-btn--delete::part(control),
a.pec-btn--icon.pec-btn--delete {
    background: var(--color-status-error-vbright);
    color: var(--color-status-error);
}

/* Gap: no Figma IconOnly spec exists for Secondary/Cancel/Danger (Table.css only defines
   view/edit/delete = Primary/Warning/Delete, per D7.2). Nearest same-hue tint token per the
   ADR's fixed no-new-token rule, flagged here (not a Figma-confirmed value):
   - Secondary (navy) -> --color-navy-blue-100, icon --color-navy-blue-900 (same hue ramp
     Secondary's outline already uses).
   - Cancel (grey) -> --color-neutral-background-over, icon --color-neutral-button-cancel
     (no dedicated "cancel-vbright" tint token exists in tokens.css).
   - Danger reuses Delete's red tint (--color-status-error-vbright) since both are red-hued
     and no separate "danger tint" token exists. */
.pec-btn--icon.pec-btn--secondary::part(control),
a.pec-btn--icon.pec-btn--secondary {
    background: var(--color-navy-blue-100);
    color: var(--color-navy-blue-900);
}

.pec-btn--icon.pec-btn--cancel::part(control),
a.pec-btn--icon.pec-btn--cancel {
    background: var(--color-neutral-background-over);
    color: var(--color-neutral-button-cancel);
}

.pec-btn--icon.pec-btn--danger::part(control),
a.pec-btn--icon.pec-btn--danger {
    background: var(--color-status-error-vbright);
    color: var(--color-status-error);
}

/* IconOnly hover/active (2026-07-16 fix — see the corrected comment above the Warning fill rule
   for the bug this closes). Without a `.pec-btn--icon` hover rule, the variant's own base
   `:hover::part(control)` rule wins the cascade at (0,3,1) vs this block's base rule at (0,2,1),
   flipping the tint circle to the variant's SOLID fill color with the icon still drawn in the
   variant's brand color — e.g. Warning tint #FEF3C7 -> solid #F59E0B fill with a #FEF9C3 icon
   (amber-on-amber, icon effectively invisible); Primary tint #DBEAFE -> solid #1D4ED8 fill with a
   #2563EB icon (blue-on-blue). Design has no Figma spec for an icon-button hover state (same gap
   as the base IconOnly tints above — D7.2). Per user direction: do NOT invent/mint a new
   "hover tint" token and do NOT reuse a token from a different family just to get *a* color
   change — none of Primary/Warning/Delete's tint tokens (--color-blue-100,
   --color-status-waiting-vbright, --color-status-error-vbright) has a same-family "darker tint"
   step in tokens.css, so inventing one would break the no-new-token rule. Safer to keep the base
   (non-hover) background/icon color UNCHANGED on hover than to guess — an icon-on-fill collision
   is worse than "no hover color change". `cursor: pointer` is added explicitly since these are
   FluentButton custom elements (::part(control) has no default pointer cursor guarantee once its
   own color/bg rules are overridden). The `:not([disabled])` / `:not([aria-disabled="true"])`
   guards mirror every other variant hover rule in this file so disabled icon buttons keep the
   dimmed disabled look and do not get a pointer cursor. Delete is an OUTLINE variant elsewhere in
   this file — its hover rule (line ~163) only overrides border-color/color, not background, so
   icon-Delete's tint background was never at risk of the fill-flip; but its `color` at (0,3,1)
   was still winning over icon-Delete's base `color` at (0,2,1), silently shifting the icon from
   --color-status-error to the lighter --color-red-400 on hover — covered below too so the icon
   color also stays put. Only variants with a real production IconOnly call site get a rule here
   (Primary: EmployeeList view icon; Warning: EmployeeCertsTab edit icon; Delete: EmployeeCertsTab
   delete icon) — Secondary/Cancel/Danger have zero IconOnly call sites outside the dev showcase
   (grep-confirmed) and can get the same treatment if/when a real call site appears. */
.pec-btn--icon.pec-btn--primary:not([disabled]):hover::part(control),
.pec-btn--icon.pec-btn--primary:not([disabled]):active::part(control),
a.pec-btn--icon.pec-btn--primary:not([aria-disabled="true"]):hover,
a.pec-btn--icon.pec-btn--primary:not([aria-disabled="true"]):active {
    background: var(--color-blue-100);
    color: var(--color-primary-primary);
    cursor: pointer;
}

.pec-btn--icon.pec-btn--warning:not([disabled]):hover::part(control),
.pec-btn--icon.pec-btn--warning:not([disabled]):active::part(control),
a.pec-btn--icon.pec-btn--warning:not([aria-disabled="true"]):hover,
a.pec-btn--icon.pec-btn--warning:not([aria-disabled="true"]):active {
    background: var(--color-status-waiting-vbright);
    color: var(--color-status-waiting);
    cursor: pointer;
}

.pec-btn--icon.pec-btn--delete:not([disabled]):hover::part(control),
.pec-btn--icon.pec-btn--delete:not([disabled]):active::part(control),
a.pec-btn--icon.pec-btn--delete:not([aria-disabled="true"]):hover,
a.pec-btn--icon.pec-btn--delete:not([aria-disabled="true"]):active {
    background: var(--color-status-error-vbright);
    color: var(--color-status-error);
    cursor: pointer;
}

/* ---------------------------------------------------------------------------------------------
   PecProgressRing (Feedback/PecProgressRing.razor)
--------------------------------------------------------------------------------------------- */

/* PEC only ever uses the indeterminate ring (no numeric Value= anywhere in the codebase) — that
   render branch is a bare <slot>, exposing NO ::part() to hook (confirmed via source-map trace,
   see the research brief cited above). Recoloring it would require a shadow-internal custom-
   property override (v5-risk, ladder step 4) — deliberately not done in Wave 1 since Fluent's
   default accent-colored ring is visually acceptable; escalate to design/reviewer if a
   token-matched ring color becomes a hard requirement. */
.pec-progress-ring {
    display: inline-flex;
}

/* ---------------------------------------------------------------------------------------------
   PecDialog (Feedback/PecDialog.razor)
--------------------------------------------------------------------------------------------- */

.pec-dialog::part(control) {
    border-radius: var(--radius-modal);
    box-shadow: var(--shadow-floating-card);
}

/* Opt-in wide variant, passed via PecDialog's Class parameter — the QR pickers host a five-column
   table, not the single sticker the default width was drawn for.
   Two things make this rule look over-specified, and both are load-bearing.
   (1) The variable is set ON THE PART, not on the host. fluent-dialog declares --dialog-width in its
       own shadow :host block, and a :host declaration beats a value inherited from the host element —
       so setting it on .pec-dialog--wide itself is silently ignored. Setting it through ::part lands
       it on the same element FluentUI's calc reads.
   (2) Two classes plus the element name. FluentUI's own rule is
       `fluent-dialog[b-dsxskpj5rr]::part(control) { width: calc(var(--dialog-width) - 2 * var(--dialog-padding)) }`
       — specificity (0,1,1). A single class is (0,1,0) and loses; (0,2,1) wins regardless of which
       stylesheet loaded first.
   Width is expressed as the variable rather than a flat `width` so FluentUI's own padding
   subtraction still applies and the dialog keeps its normal internal spacing. */
fluent-dialog.pec-dialog.pec-dialog--wide::part(control) {
    --dialog-width: min(880px, 92vw);
    max-width: 92vw;
}

/* Wide variant for form-heavy modals (e.g. lot-receive, 3-column field grid) — opt-in via
   Class="pec-dialog--wide" so the default narrow dialogs are unaffected. FluentDialog sizes its
   control from the --dialog-width/--dialog-height FAST custom props (a fixed 502px default), NOT a
   plain width on ::part(control), so override those vars on the host; also pin ::part(control) with
   !important as a belt-and-braces in case a build ships an inline width. */
.pec-dialog--wide {
    --dialog-width: 760px;
    --dialog-max-width: 92vw;
}
.pec-dialog--wide::part(control) {
    width: 760px !important;
    max-width: 92vw !important;
}

/* Extra-wide variant for the lot-receive modal specifically (MaterialLotDetail.razor). Its form is a
   3-column grid whose fields hold long option text (material type, steel grade, supplier, warehouse),
   which 760px squeezes. Kept as its own class rather than widening .pec-dialog--wide, because that
   class is shared with the two QR picker modals (QrStickerPickerModal / QrAssetPickerModal) whose
   5-column table was what 760px was drawn for — they should not move.
   Selector shape is copied deliberately from the .pec-dialog--wide::part(control) rule ~30 lines
   above, and both of its quirks are load-bearing here for the same reasons documented there: the
   --dialog-width variable must be set ON THE PART (fluent-dialog declares it in its own shadow
   :host block, which beats a value inherited from the host element), and the selector needs the
   element name plus both classes to out-specify FluentUI's own
   `fluent-dialog[b-xxx]::part(control)` rule at (0,1,1). A bare `.pec-dialog--xwide::part(control)`
   is only (0,1,0) and loses to it — that was the first attempt at this rule and it silently did
   nothing, leaving the dialog at its inherited 760px. */
fluent-dialog.pec-dialog.pec-dialog--xwide::part(control) {
    --dialog-width: min(1000px, 92vw);
    width: 1000px !important;
    max-width: 92vw !important;
}

/* ---------------------------------------------------------------------------------------------
   Field frame (Forms/_FieldFrame.razor, internal — was the public PecField before ADR Amendment
   D1 collapsed it into PecTextField, see the header comment in _FieldFrame.razor). The
   .pec-field* selectors that style the frame's own label/error/helper markup live in the
   co-located Forms/_FieldFrame.razor.css scoped stylesheet (wrapper-owned DOM, per the ADR §4
   rule) — only the ONE exception below lives here, because it targets a nested child
   component's render tree, which scoped CSS cannot reach.

   (The showcase's standalone "PecField" demo section — which used a plain <input> here via
   .pec-showcase__native-input — was removed by ADR Amendment D1; that now-dead rule was removed
   from this file too. Every field-frame state is demonstrated through PecTextField's own section
   instead.)
--------------------------------------------------------------------------------------------- */

/* .pec-field__tooltip-icon lives here (not _FieldFrame.razor.css) — it targets <Iconoir>'s own
   rendered <svg>, a child component's render tree that Blazor's scoped-isolation attribute does
   NOT reach. See the comment in _FieldFrame.razor.css for the full explanation. */
.pec-field__tooltip-icon {
    color: var(--color-neutral-description-icon-form-divider);
    cursor: help;
    flex-shrink: 0;
}

/* ---------------------------------------------------------------------------------------------
   PecTextField (Forms/PecTextField.razor) — wraps FluentTextField. Values extracted from the
   Figma export (design/Input.css), see
   .knowledge-base/research/2026-07-14-pectextfield-figma-css-extraction.md for the full matrix
   and token-gap decisions:
     - field border #DEDEDE has no exact token match -> reuse --color-text-base-border (user
       decision, "closest available, no new token")
     - placeholder #BABABA has no exact token match -> reuse --color-text-base-placeholder-text
       (same decision)
     - error border/label-required/error-message #DC2626 -> --color-status-error (exact match)
   Figma has no focus/disabled/hover state (static Figma export, no pseudo-classes) — filled in
   here following the app's existing focus-ring pattern (see app.css's --color-primary-primary
   focus outline convention, e.g. DateInput's fluent-text-field override) since design didn't
   specify one; flagged as a design follow-up, not a Wave-2 blocker per the research brief. */

.pec-textfield {
    /* Host height MUST match ::part(root)'s height below (48px) — confirmed live via
       getBoundingClientRect that these two boxes drift independently (host was hardcoded 32px
       here while ::part(root) below was already 48px): host height governs the LAYOUT box (gap
       between stacked fields, host is display:block so it does not shrink-to-fit like
       .pec-select's inline-flex host does), ::part(root) governs the PAINTED box. A 32-vs-48
       mismatch doesn't clip anything (host has default overflow:visible) — the 48px box just
       visually overflows a 32px layout slot, producing overlap with whatever sits below it. No
       height token exists in tokens.css (color/space/shadow/typography only), so this is a plain
       hardcoded value matching PecSelect/PecDatePicker's already-48px reference. */
    height: 48px;
    /* v5-risk: shadow-internal custom-property override, neutralizes the FAST focus/hover/active
       underline (:host::after reads all 4 of these per interaction state, not just -rest) — see
       the comment above the :focus-within rule below for the full explanation. Matches
       DateInput's existing fix verbatim (app.css:733-736, fluent-text-field.ed-field__input) —
       that override already established all 4 variants are needed; the initial version of this
       rule only had --accent-fill-rest and missed the interaction-state variants, so the FAST
       underline could reappear on hover/active/focus (caught in review). */
    --accent-fill-rest: transparent;
    --accent-fill-hover: transparent;
    --accent-fill-active: transparent;
    --accent-fill-focus: transparent;
    /* fluent-text-field's default host display is inline-block (content-fit), same trap
       independently found for every other field host below — verified live via getComputedStyle
       (host width matched neither its ::part(root) child's own set width nor its _FieldFrame
       .pec-field__control container's width until this pair was added). display:block switches
       the host out of inline-fit sizing so width:100% actually has something to be 100% OF (the
       .pec-field__control container), same fix shape as .pec-textarea below, which already got
       this right. */
    display: block;
    width: 100%;
}

.pec-textfield::part(root) {
    /* FAST's own shadow stylesheet paints "root"'s box via a two-layer `background` (a padding-box
       fill + a border-box gradient "stroke" sized off --stroke-width:1, NOT a plain CSS border —
       confirmed live via shadowRoot.adoptedStyleSheets). Our border below only overrides
       border-color/-width, so FAST's own border-box gray stroke layer was still painting a 1px
       ring UNDER our 2px border, whose gradient math is now misaligned with our wider border,
       producing a visible seam / "double border" between our border and the white fill. Fix:
       explicitly reset background here too, exactly like DateInput's verified fix
       (app.css:740-750, fluent-text-field.ed-field__input::part(root)) — same token, same reason.
       Only the [disabled] variant below already did this by accident; normal/focus/error didn't. */
    background: var(--color-primary-surface);
    border: 1px solid var(--color-text-base-border);
    height: 48px;
    border-radius: 4px;
    margin: 0;
    gap: var(--space-xs);
}

.pec-textfield::part(control) {
    font-family: var(--font-family-base);
    font-size: var(--font-size-input);
    line-height: var(--line-height-none);
    color: var(--color-neutral-title-content);
}

.pec-textfield::part(control)::placeholder {
    color: var(--color-text-base-placeholder-text);
}

/* Figma export has no focus state — this follows the existing app.css convention (accent-colored
   ring on focus-within) rather than inventing a new one. See
   fluentui-web-component-shadow-dom-styling.md — the FAST focus underline is a :host::after
   pseudo-element outside ::part(), so it's neutralized via the --accent-fill-rest token override
   (documented v5-risk) exactly like DateInput's existing fix, instead of fighting the underline
   directly. */
.pec-textfield:focus-within::part(root) {
    border-color: var(--color-primary-primary);
    box-shadow: var(--shadow-active-state);
}

.pec-textfield[disabled]::part(root) {
    background: var(--color-text-base-disabled-bg);
    border-color: var(--color-text-base-border);
}

.pec-textfield[disabled]::part(control) {
    color: var(--color-text-base-disabled-text);
}

/* Error state — field border only; label/help text color is handled by _FieldFrame.razor.css
   (.pec-field--error), NOT here, since the Figma export keeps the label text neutral even in
   error state (only the border + "*" + error message turn red). */
.pec-field--error .pec-textfield::part(root) {
    border-color: var(--color-status-error);
}

.pec-field--error .pec-textfield:focus-within::part(root) {
    border-color: var(--color-status-error);
    box-shadow: var(--shadow-danger-focus);
}

/* ---------------------------------------------------------------------------------------------
   PecSelect (Forms/PecSelect.razor) — DELETED 2026-07-30. Migrated to PecSearchableSelect
   (Forms/PecSearchableSelect.razor + co-located PecSearchableSelect.razor.css) across all 17 former
   call sites, per USER DECISION to make every dropdown in the app searchable — see
   .progress/pecselect-searchable/senior-dev.md for the full migration log. This `.pec-select*`
   block (wraps FluentSelect's `::part()` shadow-DOM contract) is dead weight now: confirmed via
   grep that PecSearchableSelect does NOT reuse this class — it renders its own plain trigger+panel
   DOM (not a Fluent inner element) and copied the same token VALUES into its own scoped
   `.pec-searchable-select*` rules instead (documented in that file's own header comment), so there
   is no shared selector to keep. Removed rather than kept-just-in-case per the explicit migration
   instruction to clean up unused CSS once zero call sites remain (verified via
   `grep -rn "<PecSelect\b"` returning no matches). If a future FluentSelect-based component is ever
   built again, resurrect this block from git history rather than re-deriving the token/`::part()`
   mapping from scratch — see .knowledge-base/research/2026-07-14-pecselect-figma-css-extraction.md
   for the original Figma-extraction rationale, which is still valid research even though the CSS
   itself is gone. */

/* ---------------------------------------------------------------------------------------------
   PecDatePicker (Forms/PecDatePicker.razor) — wraps FluentDatePicker. Values extracted from the
   Figma export (design/dateinput.css), see
   .knowledge-base/research/2026-07-14-pecdatepicker-figma-extraction-and-dateinput-fold.md for
   the full matrix. SCOPE NOTE: this is a new, parallel component — Components/Shared/DateInput.razor
   and its 9 production call sites are untouched (separate migration, not done here).

   Structural family: FluentDatePicker renders FluentTextField internally (root+control part
   split) — same selector family as .pec-textfield. Field height fixed at 48px (matches Select/the
   Figma export; the raw file has 46/54/58px drift across blocks that reads as auto-layout
   height-follows-label-row noise, not an intentional per-variant height — see brief §1).

   Token reuse: border #E2E8F0 -> --color-text-base-border is an EXACT match here (better than
   TextField/Select's #DEDEDE gap) but kept as the same CSS custom property those two already use,
   not a separate value, so the whole family moves together if that token ever changes. Placeholder
   #BABABA -> --color-text-base-placeholder-text, same pre-existing "closest available" gap as
   TextField/Select (not new). Calendar icon #475569 -> --color-neutral-description-icon-form-divider,
   exact match. */

.pec-datepicker {
    /* Same v5-risk shadow-internal override as .pec-textfield — neutralizes the FAST
       focus/hover/active underline (:host::after). Verified identical mechanism (FluentDatePicker
       wraps the same fluent-text-field custom element PecTextField renders directly). */
    --accent-fill-rest: transparent;
    --accent-fill-hover: transparent;
    --accent-fill-active: transparent;
    --accent-fill-focus: transparent;
    /* Same inline-block content-fit trap as .pec-textfield's host (FluentDatePicker wraps the
       same fluent-text-field element) — same fix, verified live. */
    display: block;
    width: 100%;
}

.pec-datepicker::part(root) {
    /* Same FAST two-layer background-as-border trick as .pec-textfield::part(root) (confirmed:
       FluentDatePicker's inner element IS a fluent-text-field, byte-identical shadow template).
       DateInput.razor's own existing override (app.css:716-731, fluent-text-field.ed-field__input
       ::part(root)) already independently discovered and fixed this exact trap for the same
       component, before the Wave-2 PecTextField work even started — confirming the fix here is
       correct, not a guess. Reset background alongside border. */
    background: var(--color-primary-surface);
    border: 1px solid var(--color-text-base-border);
    border-radius: 4px;
    height: 48px;
    margin: 0;
    gap: var(--space-xs);
}

.pec-datepicker::part(control) {
    font-family: var(--font-family-base);
    font-size: var(--font-size-input);
    line-height: var(--line-height-none);
    color: var(--color-neutral-title-content);
}

.pec-datepicker::part(control)::placeholder {
    color: var(--color-text-base-placeholder-text);
}

/* Calendar icon. FluentDatePicker injects a hardcoded <svg slot="end"> string literal from its
   own C# BuildRenderTree (FluentDatePicker.CalendarIcon) — NOT a shadow-DOM ::part() like
   PecSelect's chevron indicator, and NOT currentColor-driven like most light-DOM icons in this
   app. Its <path> carries a literal `fill="var(--neutral-fill-strong-focus)"` SVG attribute
   (confirmed via decompile, research brief §3) — a plain `color:` rule on the host does nothing
   for this (SVG fill attributes only follow `currentColor`, and this one isn't). Two-part fix,
   verified live (see .progress/pec-shared-component-library/senior-dev.md for the check):
     1. Override the CSS custom property the fill reads (`--neutral-fill-strong-focus`) — same
        escalation-ladder technique as the --accent-fill-* underline override above, since the SVG
        is light-DOM and the custom property still cascades onto it normally.
     2. Resize via a plain `svg[slot="end"]` selector — this IS reachable (light DOM, not
        shadow-part-bounded like PecSelect's slotted indicator svg), unlike PecSelect's chevron
        which needed a transform-based workaround. */
.pec-datepicker {
    --neutral-fill-strong-focus: var(--color-neutral-description-icon-form-divider);
}

.pec-datepicker svg[slot="end"] {
    width: 14px;
    height: 14px;
}

.pec-datepicker:focus-within::part(root) {
    border-color: var(--color-primary-primary);
    box-shadow: var(--shadow-active-state);
}

.pec-datepicker[disabled]::part(root) {
    background: var(--color-text-base-disabled-bg);
    border-color: var(--color-text-base-border);
}

.pec-datepicker[disabled]::part(control) {
    color: var(--color-text-base-disabled-text);
}

.pec-field--error .pec-datepicker::part(root) {
    border-color: var(--color-status-error);
}

.pec-field--error .pec-datepicker:focus-within::part(root) {
    border-color: var(--color-status-error);
    box-shadow: var(--shadow-danger-focus);
}

/* ---------------------------------------------------------------------------------------------
   PecTextArea (Forms/PecTextArea.razor) — wraps FluentTextArea. Values extracted from the Figma
   export (design/textbox.css), see
   .knowledge-base/research/2026-07-14-pectextarea-figma-extraction-and-fluenttextarea-verification.md.
   Zero new tokens — pure reuse of the TextField/Select/DatePicker family's 5-token set.

   Structural family: fluent-text-area has exactly ONE visible-box part, `control` (verified via
   the shipped FAST bundle source, brief §3) — same single-part shape as .pec-select, NOT
   .pec-textfield/.pec-datepicker's root+control split. So border/background/height/padding all
   live on ::part(control) directly.

   Background trap: confirmed present on ::part(control) (brief §4, byte-identical FAST two-layer
   padding-box/border-box gradient construction to every other field in this family) — reset
   background alongside border, same fix class as every other .pec-* field. This is the 4th
   confirmed occurrence of this exact trap across the Wave-2 field family. */

.pec-textarea-wrap {
    /* Positions the char counter (below) relative to the field box, in the helper-text gutter
       _FieldFrame already reserves — NOT inside the bordered box itself (brief §6). */
    position: relative;
}

.pec-textarea {
    /* Same v5-risk shadow-internal override as every other field in this family — neutralizes the
       FAST focus/hover/active underline (:host::after). */
    --accent-fill-rest: transparent;
    --accent-fill-hover: transparent;
    --accent-fill-active: transparent;
    --accent-fill-focus: transparent;
    display: block;
    width: 100%;
}

.pec-textarea::part(control) {
    background: var(--color-primary-surface);
    border: 1px solid var(--color-text-base-border);
    border-radius: 4px;
    min-height: 58px;
    width: 100%;
    padding: 5px 12px;
    box-sizing: border-box;
    font-family: var(--font-family-base);
    font-size: var(--font-size-input);
    line-height: var(--line-height-none);
    color: var(--color-neutral-title-content);
}

.pec-textarea::part(control)::placeholder {
    color: var(--color-text-base-placeholder-text);
}

.pec-textarea:focus-within::part(control) {
    border-color: var(--color-primary-primary);
    box-shadow: var(--shadow-active-state);
}

.pec-textarea[disabled]::part(control) {
    background: var(--color-text-base-disabled-bg);
    border-color: var(--color-text-base-border);
    color: var(--color-text-base-disabled-text);
}

.pec-field--error .pec-textarea::part(control) {
    border-color: var(--color-status-error);
}

.pec-field--error .pec-textarea:focus-within::part(control) {
    border-color: var(--color-status-error);
    box-shadow: var(--shadow-danger-focus);
}

/* Char counter — not built-in to FluentTextArea (verified via bundle grep, brief §6), self-
   rendered as a <span> in PecTextArea.razor, conditional on Maxlength being set. Figma positions
   it just outside/below the field's bottom-right corner; -20px lands it in _FieldFrame's own
   bottom gutter rather than overlapping the border or getting clipped by the field box. */
.pec-textarea__counter {
    position: absolute;
    right: 0;
    bottom: -20px;
    font-family: 'Sarabun', var(--font-family-base);
    font-weight: var(--font-weight-medium);
    font-size: var(--font-size-caption);
    line-height: var(--line-height-none);
    color: var(--color-neutral-description-icon-form-divider);
    pointer-events: none;
}

/* ---------------------------------------------------------------------------------------------
   PecDateRangePicker (Forms/PecDateRangePicker.razor) — composite: ONE outer bordered box (this
   component's own <div class="pec-daterange">) holding 2 bare FluentDatePickers + a dash + ONE
   shared calendar icon. Values from the Figma export (design/daterangeinput.css), see
   .knowledge-base/decisions/2026-07-14-pec-shared-component-library.md §D3 (sa sign-off) and
   .knowledge-base/research/2026-07-14-pecdaterangepicker-figma-extraction-and-approach.md.
   Zero new tokens — pure reuse of the TextField/Select/DatePicker/TextArea family's 5-token set.

   Ownership inversion vs. .pec-datepicker (ADR §D3.1 — read before touching either block):
   .pec-datepicker gives its OWN ::part(root) a real visible border because it's a standalone
   field. Here, the OUTER .pec-daterange wrapper owns the one-and-only visible border/background/
   radius/padding/focus-ring/error/disabled; EACH inner .pec-daterange__field is stripped
   completely bare (::part(root) transparent + border:none) so it contributes no visual box of
   its own — same background-trap fix mechanics (reset background alongside border), just applied
   to make the inner box invisible instead of visible. */

.pec-daterange {
    display: flex;
    align-items: center;
    gap: 0;
    width: 100%;
    height: 48px;
    padding: 8px 12px;
    border: 1px solid var(--color-text-base-border);
    border-radius: 4px;
    background: var(--color-primary-surface);
    box-sizing: border-box;
}

.pec-daterange:focus-within {
    border-color: var(--color-primary-primary);
    box-shadow: var(--shadow-active-state);
}

.pec-daterange__field {
    /* Same v5-risk shadow-internal override as every other field in this family. */
    --accent-fill-rest: transparent;
    --accent-fill-hover: transparent;
    --accent-fill-active: transparent;
    --accent-fill-focus: transparent;
    flex: 1 1 0;
    min-width: 0;
}

.pec-daterange__field::part(root) {
    /* INVERTED from .pec-datepicker::part(root): transparent + no border, NOT
       var(--color-primary-surface) + a real border — the outer .pec-daterange is the only
       visible box. Still resets background explicitly (not just border) for the same FAST
       two-layer-gradient reason documented on every other field in this family — a transparent
       background alone would still leave FAST's own gradient "stroke" layer painting a stray
       border-shaped seam if only `border` were reset. */
    background: transparent;
    border: none;
    border-radius: 0;
    height: auto;
    min-height: 0;
    padding: 0;
    margin: 0;
}

.pec-daterange__field::part(control) {
    font-family: var(--font-family-base);
    font-size: var(--font-size-input);
    line-height: var(--line-height-none);
    color: var(--color-neutral-title-content);
    text-align: center;
}

.pec-daterange__field::part(control)::placeholder {
    color: var(--color-text-base-placeholder-text);
}

/* Hide BOTH inner pickers' own baked-in calendar icon — Figma shows exactly ONE icon for the
   whole composite, not one per field (research brief §3). The ONE shared icon is this
   component's own markup (.pec-daterange__icon below), not either inner picker's slot. */
.pec-daterange__field svg[slot="end"] {
    display: none;
}

.pec-daterange__dash {
    width: 12px;
    height: 1px;
    background: var(--color-neutral-description-icon-form-divider);
    flex: none;
    margin: 0 8px;
}

.pec-daterange__icon {
    /* A real <button>, not a styled <span> — keyboard/AT-accessible (Tab-reachable, Enter/Space
       activates onclick natively), matches the "shared, real affordance" intent instead of a
       decorative icon that only works with a mouse. */
    display: inline-flex;
    align-items: center;
    justify-content: center;
    flex: none;
    width: 14px;
    height: 14px;
    padding: 0;
    border: none;
    background: transparent;
    color: var(--color-neutral-description-icon-form-divider);
    cursor: pointer;
}

.pec-daterange__icon svg {
    width: 14px;
    height: 14px;
}

.pec-daterange__icon:disabled {
    cursor: not-allowed;
}

.pec-daterange[disabled],
.pec-daterange:has(.pec-daterange__field[disabled]) {
    background: var(--color-text-base-disabled-bg);
    border-color: var(--color-text-base-border);
}

.pec-daterange:has(.pec-daterange__field[disabled]) .pec-daterange__field::part(control) {
    color: var(--color-text-base-disabled-text);
}

.pec-field--error .pec-daterange {
    border-color: var(--color-status-error);
}

.pec-field--error .pec-daterange:focus-within {
    border-color: var(--color-status-error);
    box-shadow: var(--shadow-danger-focus);
}

/* ---------------------------------------------------------------------------------------------
   PecChoiceGroup (Forms/PecChoiceGroup.razor) — button-style single-select radio group. Values
   from the Figma export (design/something.css), see ADR Decision Log §D4
   (.knowledge-base/decisions/2026-07-14-pec-shared-component-library.md) and
   .knowledge-base/research/2026-07-14-pec-choice-group-figma-extraction-and-approach.md.

   Plain DOM, own scope — NO ::part() anywhere in this block. Unlike every other Wave-2 field,
   this component's control row is not a Fluent web component at all: it's a native
   <input type="radio"> per option, visually hidden, wrapped in a <label> styled as the button
   (the emx-radio-pill house pattern, modernized — see the component's own header comment for the
   full rationale). So there is no shadow DOM, no FAST background-trap, no ::part() selector
   needed anywhere in this block — every selector here targets plain light-DOM elements this
   component renders itself.

   Selected skin reuses PecButton Primary's TOKEN VALUES (navy-blue-900 bg / white text) — NOT its
   ::part(control) selectors, since a <label> is not a FluentButton and has no shadow parts to
   hook. Unselected is intentionally NOT PecButtonVariant.Secondary (Secondary = navy-outline-on-
   transparent; Figma wants white-fill/grey-border/near-black-text) — a new scoped class, per ADR
   §D4.0 ("this is a checked/unchecked STATE, not a 7th brand button").

   Zero new tokens (ADR §D4.6): 3 of 4 colors are exact matches; the unselected border (#DEDEDE)
   has the same small "nearest existing token" gap already accepted for emx-radio-pill's own
   border and every other Pec* field's border gap (D2/PecTextField/PecSelect/PecDatePicker) —
   documented inline, not a new one-off hex. */

.pec-choice {
    display: flex;
    gap: var(--space-lg); /* 24px, exact match to the Figma row gap */
}

.pec-choice--vertical {
    flex-direction: column;
}

.pec-choice--horizontal {
    flex-direction: row;
}

.pec-choice__btn {
    position: relative;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    height: 48px;
    padding: var(--space-sm) var(--space-md);
    border-radius: 4px;
    font-family: var(--font-family-base);
    font-weight: var(--font-weight-bold);
    font-size: var(--font-size-button);
    cursor: pointer;
    flex: 1 1 0;
    user-select: none;
}

/* Native radio input, visually hidden but still a real focusable/keyboard-operable element —
   same technique as emx-radio-pill (app.css, .emx-radio-pill input): position:absolute +
   opacity:0, NOT display:none (display:none would pull it out of the tab order and break native
   keyboard nav, the whole reason this component uses a real <input> instead of a plain <button>). */
.pec-choice__btn input {
    position: absolute;
    opacity: 0;
    pointer-events: none;
}

/* Selected = PecButton Primary token values, copied (not ::part-referenced — see header comment). */
.pec-choice__btn--selected {
    background: var(--color-navy-blue-900);
    color: var(--color-text-base-white-text);
    border: 1px solid var(--color-navy-blue-900);
}

/* Unselected: white fill, grey border, near-black text — intentionally NOT PecButtonVariant.Secondary
   (see block header comment). Gap: Figma #DEDEDE has no exact token match; nearest existing is
   --color-text-base-border (#E2E8F0), same accepted gap as emx-radio-pill's own border and every
   other Pec* field's border (PecTextField/PecSelect/PecDatePicker) — reuses the SAME custom
   property those already use, not a second hardcoded copy. */
.pec-choice__btn--unselected {
    background: var(--color-primary-surface);
    color: var(--color-neutral-title-content);
    border: 1px solid var(--color-text-base-border);
}

.pec-choice__btn:has(input:disabled) {
    cursor: not-allowed;
    opacity: 0.6;
}

/* Keyboard focus ring — visible only for keyboard/AT focus (:focus-visible), not mouse clicks,
   matching the rest of the Pec* field family's focus-ring convention. */
.pec-choice__btn:has(input:focus-visible) {
    box-shadow: var(--shadow-active-state);
}

.pec-field--error .pec-choice__btn--unselected {
    border-color: var(--color-status-error);
}

/* Mobile-only: the default (non-segmented) skin is a `display:flex; flex-wrap: nowrap` (implicit)
   row of `flex: 1 1 0` buttons — flex-shrink alone doesn't stop overflow, since each button's
   automatic minimum width is still its unwrapped Thai-label content (e.g. InstanceCreate.razor's
   6-option "สถานะ" status row: "ระหว่างซ่อม", "ยกเลิก", etc. — 6 buttons + 5x24px gaps run well
   past a 375px viewport with no wrap). Wrap the row and let each button size to its own content
   instead of forcing an equal 1/6th share it can't fit into. Additive/mobile-scoped only — desktop
   (unscoped rules above) keeps its single-row Figma layout untouched, and this is the same fix
   shape as .pec-choice--segmented's own mobile rule below, applied to the OTHER skin that has the
   identical "row of buttons can't shrink" root cause. */
@media (max-width: 560px) {
    .pec-choice--horizontal { flex-wrap: wrap; row-gap: var(--space-sm); }
    .pec-choice__btn { flex: 0 1 auto; }
}

/* .pec-choice--segmented (PecChoiceGroup's opt-in Segmented="true") — joined-pill skin for dense
   repeated controls (B5's 8 inspection-state pickers). Same selected/unselected token classes as
   the default .pec-choice__btn (--selected/--unselected), just a different container/segment
   shape: one continuous rounded outline, segments share borders (no gap, no double border at any
   edge — top/bottom borders come from the container alone, not from each segment, and the
   vertical shared edge between segments is a single divider, not two abutting borders), only the
   outer end of the first/last segment rounded, and a smaller footprint than the default wide
   button (content-fit width, not flex:1 1 0). Opt-in only — every other PecChoiceGroup call site
   (status toggles, QR qty picker) is untouched. */
.pec-choice--segmented {
    display: inline-flex;
    gap: 0;
    border: 1px solid var(--color-text-base-border);
    border-radius: 999px;
    overflow: hidden;
    width: fit-content;
}

.pec-choice--segmented .pec-choice__seg {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    flex: 0 0 auto;
    height: 32px;
    padding: 0 var(--space-md);
    font-family: var(--font-family-base);
    font-weight: var(--font-weight-bold);
    font-size: var(--font-size-caption);
    cursor: pointer;
    user-select: none;
    position: relative;
    /* Each segment's --selected/--unselected class still sets a full 1px border (shared with the
       non-segmented .pec-choice__btn skin) — strip its top/bottom here so only the container's own
       border draws the pill's top/bottom edge; the container's border-radius:999px + overflow:hidden
       still clips the pill's rounded ends without needing per-segment top/bottom borders. */
    border-top: none;
    border-bottom: none;
}

/* Shared border between adjacent segments — every segment except the first gets a left border
   instead of a gap, so segments visually merge into one pill with internal dividers. */
.pec-choice--segmented .pec-choice__seg + .pec-choice__seg {
    border-left: 1px solid var(--color-text-base-border);
}

.pec-choice--segmented .pec-choice__seg input {
    position: absolute;
    opacity: 0;
    pointer-events: none;
}

.pec-choice--segmented .pec-choice__seg:has(input:disabled) {
    cursor: not-allowed;
    opacity: 0.6;
}

.pec-choice--segmented .pec-choice__seg:has(input:focus-visible) {
    box-shadow: var(--shadow-active-state);
}

/* Touch tap-target floor — 32px is comfortable with a mouse but tight for a finger. Bumped only at
   phone widths so desktop's denser, Figma-matched 32px segments (this page's 8-repeated-control
   layout) are unaffected. Segmented is InstanceCreate.razor's only caller today (every other
   PecChoiceGroup call site uses the default, non-segmented skin), so this has no other call site
   to regress.
   The container itself is `width: fit-content` (its default, desktop-only shape above) with 3
   Thai-label segments (e.g. "ยังไม่ตรวจ") that can run right up against a narrow field column's
   edge — widening the tap target with MORE side padding would make that worse, so padding is
   trimmed back down (not grown) here even though the target is taller, and the container is
   allowed to stretch/shrink to the field's own width instead of staying content-fit. `flex: 1 1 0`
   on each segment then splits that width evenly, so 3 short labels wrap onto 2 lines rather than
   pushing the pill past the card edge. */
@media (max-width: 560px) {
    .pec-choice--segmented { width: 100%; }
    .pec-choice--segmented .pec-choice__seg {
        height: 44px;
        padding: 0 var(--space-xs);
        flex: 1 1 0;
        min-width: 0;
        white-space: normal;
        text-align: center;
        line-height: 1.15;
    }
}

/* ---------------------------------------------------------------------------------------------
   PecCheckbox (Forms/PecCheckbox.razor) — wraps FluentCheckbox. Values from the Figma export
   (design/number.css), see
   .knowledge-base/research/2026-07-14-pec-number-css-scope-split-numberfield-checkbox.md
   §C. Does NOT compose _FieldFrame (ADR §D1) — label is inline via FluentCheckbox's own light-DOM
   span, no top label row needed. Zero new tokens: border #DEDEDE -> --color-text-base-border
   (pre-existing gap, same as every other field's border), checked color has no Figma source (no
   checked-state export exists) -> reused --color-primary-primary per the research brief's
   recommendation (same blue this Figma file uses for the slider fill, flagged as a placeholder
   pending a real checked-state design, not independently confirmed against a Figma source). */

.pec-checkbox::part(control) {
    /* FluentCheckbox is a single-element component (no root+control split, same family as
       Select/TextArea/NumberField per the research brief's pattern-based expectation) — verified
       live, see .progress/pec-shared-component-library/senior-dev.md for the confirmed part name
       and whether the background trap applies here too. */
    width: 16px;
    height: 16px;
    border: 1px solid var(--color-text-base-border);
    border-radius: 4px;
    background: var(--color-primary-surface);
}

.pec-checkbox.checked::part(control) {
    background: var(--color-primary-primary);
    border-color: var(--color-primary-primary);
}

.pec-checkbox[disabled] {
    opacity: 0.6;
    cursor: not-allowed;
}

/* FluentCheckbox's own light-DOM label span (see PecCheckbox.razor header comment — rendered
   directly by FluentCheckbox.BuildRenderTree, not a child component's render tree, so this is
   reachable without the "child component render tree" caveat that applies to e.g. Iconoir). */
.pec-checkbox span {
    font-family: var(--font-family-base);
    font-size: var(--font-size-label);
    color: var(--color-neutral-title-content);
}

/* Own error class, NOT _FieldFrame's .pec-field__error (Blazor-scoped to _FieldFrame's own
   render tree — referencing it from PecCheckbox's markup would be a dead selector). Visually
   matches it intentionally. */
.pec-checkbox__error {
    font-size: var(--font-size-tiny);
    color: var(--color-status-error);
}

/* PecSwitch (Forms/PecSwitch.razor) — FluentSwitch ships its own slider shape/animation; only the
   checked-track color is themed to --color-primary-primary (same checked blue as .pec-checkbox,
   no dedicated Figma switch export). FAST's fluent-switch drives its checked track fill from the
   --accent-fill-* design tokens, so setting those on the host recolors the "on" state without
   touching the FAST template. */
.pec-switch {
    --accent-fill-rest: var(--color-primary-primary);
    --accent-fill-hover: var(--color-primary-primary-dark);
    --accent-fill-active: var(--color-primary-primary-dark);
    /* FluentSwitch's FAST host lays the label to the LEFT of the slider by default; flip to put the
       slider first (left) and the label after it (right). row-reverse keeps the two adjacent so the
       existing gap still applies. */
    display: inline-flex;
    flex-direction: row-reverse;
    align-items: center;
    justify-content: flex-end;
    gap: var(--space-sm);
}

.pec-switch[disabled] {
    opacity: 0.6;
    cursor: not-allowed;
}

.pec-switch span {
    font-family: var(--font-family-base);
    font-size: var(--font-size-label);
    color: var(--color-neutral-title-content);
}

/* ---------------------------------------------------------------------------------------------
   PecNumberField (Forms/PecNumberField.razor) — wraps FluentNumberField<TValue>. Values from the
   Figma export (design/number.css), see
   .knowledge-base/research/2026-07-14-pec-number-css-scope-split-numberfield-checkbox.md
   §B. Mechanical sibling of .pec-textfield (same box/border/radius/placeholder spec) — see that
   block's comments for the fuller background-trap rationale, not repeated verbatim here.

   Structural family: FluentNumberField shares its JS interop module with FluentTextField
   (FluentTextField.razor.js, confirmed via decompile) — strong signal it shares the same
   root+control shadow template, confirmed live (see progress log for the verification result). */

.pec-numberfield {
    /* Same v5-risk shadow-internal override as .pec-textfield — neutralizes the FAST
       focus/hover/active underline (:host::after). */
    --accent-fill-rest: transparent;
    --accent-fill-hover: transparent;
    --accent-fill-active: transparent;
    --accent-fill-focus: transparent;
    /* Was 32px, drifted from ::part(root) below (which had NO explicit height at all — it just
       fell back to FAST's own content-driven default, ~32px, matching the host by accident).
       Fixed to 48px to match the rest of the field family (PecTextField/PecSelect/PecDatePicker),
       confirmed live via getBoundingClientRect this was the only field rendering visibly shorter
       than its siblings. No height token exists (tokens.css has no size/height scale) — hardcoded
       to match the sibling fields' same hardcoded value. */
    height: 48px;
    /* Same inline-block content-fit trap as .pec-textfield's host (FluentNumberField shares
       fluent-text-field's shadow template) — same fix, verified live. */
    display: block;
    width: 100%;
}

.pec-numberfield::part(root) {
    /* Same FAST two-layer background-as-border trick as .pec-textfield::part(root) — reset
       background alongside border. height added to match the host above and the rest of the
       field family (was previously unset, the actual root cause of this field rendering short). */
    background: var(--color-primary-surface);
    border: 1px solid var(--color-text-base-border);
    height: 48px;
    border-radius: 4px;
    margin: 0;
    gap: var(--space-xs);
}

.pec-numberfield::part(control) {
    font-family: var(--font-family-base);
    font-size: var(--font-size-input);
    line-height: var(--line-height-none);
    color: var(--color-neutral-title-content);
}

.pec-numberfield::part(control)::placeholder {
    color: var(--color-text-base-placeholder-text);
}

.pec-numberfield:focus-within::part(root) {
    border-color: var(--color-primary-primary);
    box-shadow: var(--shadow-active-state);
}

.pec-numberfield[disabled]::part(root) {
    background: var(--color-text-base-disabled-bg);
    border-color: var(--color-text-base-border);
}

.pec-numberfield[disabled]::part(control) {
    color: var(--color-text-base-disabled-text);
}

.pec-field--error .pec-numberfield::part(root) {
    border-color: var(--color-status-error);
}

.pec-field--error .pec-numberfield:focus-within::part(root) {
    border-color: var(--color-status-error);
    box-shadow: var(--shadow-danger-focus);
}

/* ---------------------------------------------------------------------------------------------
   /dev/components showcase page shell (Pages/Dev/ComponentShowcase.razor) — dev-tool-only
   layout, kept token-driven like everything else but intentionally not reused elsewhere.
--------------------------------------------------------------------------------------------- */

.pec-showcase {
    display: grid;
    grid-template-columns: 200px 1fr;
    gap: var(--space-xl);
    padding: var(--space-lg);
    max-width: 1100px;
    margin: 0 auto;
}

.pec-showcase__rail {
    position: sticky;
    top: var(--space-lg);
    align-self: start;
    display: flex;
    flex-direction: column;
    gap: var(--space-sm);
    padding-top: var(--space-2xl);
}

.pec-showcase__rail-title {
    font-size: var(--font-size-label);
    font-weight: var(--font-weight-semibold);
    color: var(--color-text-base-description-icon-form);
    text-transform: uppercase;
    letter-spacing: 0.05em;
}

.pec-showcase__rail a {
    color: var(--color-text-base-link);
    text-decoration: none;
    font-size: var(--font-size-body);
}

.pec-showcase__rail a:hover {
    text-decoration: underline;
}

.pec-showcase__header {
    margin-bottom: var(--space-xl);
}

.pec-showcase__section {
    margin-bottom: var(--space-2xl);
    padding-bottom: var(--space-xl);
    border-bottom: 1px solid var(--color-text-base-border);
}

.pec-showcase__caption {
    color: var(--color-text-base-description-icon-form);
    font-size: var(--font-size-body);
    margin-bottom: var(--space-md);
}

.pec-showcase__row {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: var(--space-md);
    margin-bottom: var(--space-md);
}

.pec-showcase__row--stack {
    flex-direction: column;
    align-items: stretch;
    max-width: 360px;
}

.pec-showcase__row-label {
    min-width: 90px;
    font-size: var(--font-size-caption);
    color: var(--color-text-base-description-icon-form);
    text-transform: capitalize;
}

.pec-showcase__spacer-top {
    margin-top: var(--space-lg);
}

.pec-showcase__snippet {
    background: var(--color-neutral-background);
    border: 1px solid var(--color-text-base-border);
    border-radius: var(--radius-md);
    padding: var(--space-md);
    font-size: var(--font-size-caption);
    overflow-x: auto;
    white-space: pre;
}

/* ============================================================================
   PecTable (DataDisplay/PecTable.razor) — ADR §D6. A Figma-reconciled COPY of
   app.css's `.list-card`/`.list-table`/`.page-btn` block (PagedListTable's chrome),
   NOT a rename — app.css's originals stay untouched for PagedListTable + its 6 live
   call-sites (§D6.5, deliberate temporary duplication that collapses when PagedListTable
   is deleted after migration). Zero new hex — every color below already exists in
   tokens.css (research-verified, 2026-07-15-pec-table-component.md §B/F).

   Only 2 pixel-value deltas vs the app.css original, both explicit Figma decisions
   (ADR §D6.6):
     - header left accent bar: 8px (Figma) vs app.css's 3px — new component, no
       regression risk, so it follows Figma exactly.
     - pager page-size dropdown: PecSelect (already shipped) instead of a raw <select>,
       aligning the pager with the rest of the Pec* library (§D6.6).
   Everything else (navy header, zebra stripe, row height, pager button sizing) is a
   literal port of the existing, already-Figma-close app.css values. ============================= */

.pec-table__card {
    background: var(--color-component-card-bg);
    border: 1px solid var(--color-text-base-border);
    border-radius: var(--radius-button);
    overflow: hidden;
}

.pec-table__head {
    display: flex;
    align-items: center;
    justify-content: space-between;
    padding: 1rem 1.25rem;
    border-bottom: 1px solid var(--color-text-base-disabled-bg);
}

.pec-table__heading {
    font-size: var(--font-size-base);
    font-weight: var(--font-weight-semibold);
    margin: var(--space-0);
    color: var(--color-neutral-title-content);
}

.pec-table__count {
    color: var(--color-text-base-placeholder-text);
    font-weight: var(--font-weight-regular);
    margin-left: var(--space-xs);
}

.pec-table__head-actions {
    display: flex;
    align-items: center;
    gap: var(--space-md);
}

.pec-table__state {
    padding: 2.5rem;
    text-align: center;
    color: var(--color-text-base-placeholder-text);
}

.pec-table__scroll {
    overflow-x: auto;
}

table.pec-table {
    width: 100%;
    border-collapse: collapse;
    font-size: var(--font-size-table-line);
}

.pec-table thead th {
    text-align: left;
    height: 68px;
    padding: 0 var(--space-md);
    background: var(--color-navy-blue-900);
    color: var(--color-text-base-border);
    font-weight: var(--font-weight-medium);
    white-space: nowrap;
}

/* Red accent bar on the header's left edge — Figma spec is 8px (vs app.css's 3px for the
   older PagedListTable chrome); this new component has no regression risk so it follows
   Figma exactly (ADR §D6.6). */
.pec-table thead th:first-child {
    border-left: 8px solid var(--color-status-error);
}

.pec-table tbody td {
    height: 60px;
    padding: 0 var(--space-md);
    border-bottom: 1px solid var(--color-text-base-disabled-bg);
    color: var(--color-neutral-description-icon-form-divider);
    vertical-align: middle;
}

/* Zebra-striped rows: white / slate-200 (Figma). Declared BEFORE .row--busy so the
   delete-in-flight gray still wins on even rows — same ordering rationale as app.css. */
.pec-table tbody tr:nth-child(even) td {
    background: var(--color-text-base-border);
}

/* Action cells (row-level icon buttons) center their content. text-align alone doesn't move
   <fluent-button> (its host is inline-flex and its ::part(control) is a fixed-width box that
   stays left), so force the cell itself to flex-center. */
/* Center a row-level IconOnly action button. The fluent-button host is display:grid in FAST and
   stretches across the cell; its 36px ::part(control) circle then sits at the grid's start (left).
   Pin the host to the same fixed size as its circle (36 / 30 / 44 for md/sm/lg) and center it in the
   cell with margin:auto — with the host now content-sized, the circle and host coincide and the whole
   thing centers. !important beats FAST's own host sizing. */
.pec-table tbody td.pec-table__action {
    text-align: center;
}
.pec-table tbody td.pec-table__action .pec-btn--icon {
    display: inline-flex !important;
    width: 36px !important;
    min-width: 0 !important;
    margin: 0 auto !important;
    justify-content: center !important;
    align-items: center !important;
}
.pec-table tbody td.pec-table__action .pec-btn--icon.pec-btn--sm { width: 30px !important; }
.pec-table tbody td.pec-table__action .pec-btn--icon.pec-btn--lg { width: 44px !important; }

.pec-table td.pec-table__empty {
    text-align: center;
    color: var(--color-text-base-placeholder-text);
    padding: var(--space-xl);
}

.pec-table tbody tr.pec-table__emptyrow td,
.pec-table tbody tr.pec-table__emptyrow:hover td {
    background: transparent;
}

.pec-table tbody tr:hover td {
    background: var(--color-text-base-disabled-bg);
}

/* Row with an operation in flight (e.g. delete): dim it and block all clicks. */
.pec-table tbody tr.row--busy {
    opacity: .45;
    pointer-events: none;
    background: var(--color-neutral-background);
}

.pec-table__col-check {
    width: 36px;
}

.pec-table__sort-header {
    display: inline-flex;
    align-items: center;
    gap: .4rem;
    cursor: pointer;
    user-select: none;
}

.pec-table__pager {
    display: grid;
    grid-template-columns: 1fr auto 1fr;
    align-items: center;
    padding: .75rem 1.25rem;
}

.pec-table__pager-pages {
    grid-column: 2;
    display: flex;
    gap: var(--space-xs);
}

.pec-table__pager-size {
    grid-column: 3;
    justify-self: end;
    min-width: 130px;
}

/* ============================================================================
   .ed-certs-table (ADR §D9.5/D9.7) — scoped companion for EmployeeCertsTab's <PecTable
   Class="ed-certs-table">. `.pec-table` itself does NOT get `table-layout: fixed` / cell
   ellipsis / `.ed-th-sub` globally — that would shift column widths on EmployeeList + every
   pending PagedListTable migration. Values below are copied VERBATIM from `.ed-table`
   (app.css:798-815, `.ed-table` itself is left untouched — still used until this migration is
   the only cert table). Do not add a 7th column width here without also updating the caller's
   <Head>/<Row> column count. */
.ed-certs-table table.pec-table {
    table-layout: fixed;
}

.ed-certs-table .pec-table tbody td {
    overflow: hidden;
    text-overflow: ellipsis;
}

/* Expired-certificate row text (reviewer log 2026-07-16, should-fix #1) — `.ed-cell-danger` alone
   is (0,1,0), too low-specificity to beat `.pec-table tbody td`'s own `color` at (0,1,2); the
   class was applied by EmployeeCertsTab.razor:53 but silently never rendered red, before or after
   this migration (pre-existing bug on `.ed-table tbody td`, confirmed identical on origin/main —
   not a regression). Scoped under `.ed-certs-table` (not a bare `.pec-table tbody td.ed-cell-danger`
   override) so this doesn't leak red text into any other current/future `.pec-table` usage that
   happens to reuse the `.ed-cell-danger` class name elsewhere in the app. */
.ed-certs-table .pec-table tbody td.ed-cell-danger {
    color: var(--color-status-error);
}

/* Two-line header: Thai label + English gloss in parentheses, stacked (design: Employeedetail.png).
   Empty in the English locale (redundant there) — :empty collapses it so the English header stays
   one line. Selector was `.ed-table thead th .ed-th-sub` (app.css:807) — re-scoped to `.pec-table`. */
.ed-certs-table .pec-table thead th .ed-th-sub {
    display: block;
    font-weight: var(--font-weight-regular);
    font-size: var(--font-size-label);
    color: var(--color-text-base-placeholder-text);
    white-space: nowrap;
}

.ed-certs-table .pec-table thead th .ed-th-sub:empty {
    display: none;
}

.ed-certs-table .pec-table .col-cert-no { width: 13%; }
.ed-certs-table .pec-table td.col-cert-no { white-space: nowrap; }
.ed-certs-table .pec-table .col-cert-issue,
.ed-certs-table .pec-table .col-cert-expiry { width: 15%; }
.ed-certs-table .pec-table .col-cert-attachment { width: 11%; }
.ed-certs-table .pec-table .col-cert-status { width: 10%; }
.ed-certs-table .pec-table .col-cert-action { width: 9%; text-align: right; }
.ed-certs-table .pec-table tbody td.col-cert-action { text-align: right; }

/* ============================================================================
   PecBadge (DataDisplay/PecBadge.razor) — status pill, Figma design/pill.css.
   Research: .knowledge-base/research/2026-07-15-pecbadge-status-pill-figma-reconcile.md.
   NEW, PARALLEL component — does NOT touch or replace `.status-pill`/`.sd-status` (app.css),
   both left completely untouched with their 10 combined live call-sites (see PecBadge.razor's
   own header comment for the exact list). Zero new raw hex: 5 of 7 colors map to EXACT existing
   `--color-status-*`/`--color-status-*-vbright` tokens; Orange and Neutral(gray) have no exact
   token (confirmed via research — no `--color-slate-*`/`--color-gray-*` scale exists at all, and
   Figma's orange bg/dot hexes don't land exactly on any `--color-orange-*` stop) so both use the
   NEAREST existing token (computed via literal RGB Euclidean distance, not eyeballed):
     - Orange bg #FEF0E9  -> --color-orange-50  #FFF7ED (dist 8.1, nearest of the 3 lightest stops)
     - Orange dot/text #F56C1F -> --color-orange-500 #F97316 (dist 12.1, nearest of the mid stops)
     - Neutral(gray) bg #CBD5E1 -> --color-text-base-button-cancel #C9CFD7 (dist 11.8 — nearest
       candidate across every border/neutral/disabled-bg token in tokens.css; there is no gray/slate
       scale to check against)
     - Neutral(gray) dot/text #060D1D -> --color-text-base-title-content (EXACT match)
   No border on the pill (Figma's dashed #9747FF border is a dev-frame annotation artifact, not
   part of the design — confirmed via research, the same class of stale-dev-layer artifact found
   earlier in button.css). ============================= */

.pec-badge {
    display: inline-flex;
    align-items: center;
    gap: 8px;
    padding: 8px 16px;
    border-radius: var(--radius-pill);
    font-family: var(--font-family-base);
    font-weight: var(--font-weight-bold);
    font-size: var(--font-size-status);
    line-height: 15px;
    white-space: nowrap;
}

.pec-badge__dot {
    flex: 0 0 auto;
    width: 12px;
    height: 12px;
}

.pec-badge__text {
    display: inline-block;
}

.pec-badge--danger {
    background: var(--color-status-error-vbright);
    color: var(--color-status-error);
}

.pec-badge--warning {
    background: var(--color-status-alert-vbright);
    color: var(--color-status-waiting);
}

.pec-badge--success {
    background: var(--color-status-success-vbright);
    color: var(--color-status-success);
}

.pec-badge--info {
    background: var(--color-status-info-vbright);
    color: var(--color-status-info);
}

.pec-badge--pending {
    background: var(--color-status-pending-vbright);
    color: var(--color-status-pending);
}

.pec-badge--neutral {
    background: var(--color-text-base-button-cancel);
    color: var(--color-text-base-title-content);
}

.pec-badge--orange {
    background: var(--color-orange-50);
    color: var(--color-orange-500);
}

/* ============================================================================
   PecImageUpload (Forms/PecImageUpload.razor) — image upload card, Figma design/Upload.css.
   Research: .knowledge-base/research/2026-07-15-pec-image-upload-component.md.
   6 declared hexes all map to EXACT existing tokens, no new token added (research §2):
     #2563EB -> --color-primary-primary (Change/Retry/Upload text, Change button border)
     #DBEAFE -> --color-primary-primary-very-bright (Upload button fill — "พื้นฟ้าอ่อน")
     #DC2626 -> --color-status-error (Error card/image border, error badge fill, Retry border/text)
     #E2E8F0 -> --color-text-base-border (normal card/image-area border)
     #94A3B8 -> --color-text-base-placeholder-text (placeholder icon color)
     #F8FAFC -> --color-text-base-surface-muted (Empty-state image-area background)
   The card is treated as a fixed box (not the 3 slightly different literal heights Figma exports
   per state) — that height drift is auto-layout content-fit noise, not a deliberate design signal
   (research §1). The Upload button's light-blue fill has no matching PecButtonVariant and is
   intentionally scoped-local CSS here rather than a 7th variant (see PecImageUpload.razor's own
   header comment) — Change/Retry instead reuse PecButtonVariant.Secondary/Delete verbatim, no new
   button skin needed for those two. The dashed purple border at the top of Upload.css is a Figma
   artboard/frame guide, not part of the design (research §7) — not reproduced here. ============ */

.pec-upload {
    display: inline-flex;
    flex-direction: column;
    align-items: center;
    gap: var(--space-sm);
    width: 138px;
}

.pec-upload__card {
    width: 100%;
    height: 148px;
    box-sizing: border-box;
    background: var(--color-text-base-white-text);
    border: 1px solid var(--color-text-base-border);
    border-radius: 8px;
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: var(--space-sm);
    padding: var(--space-sm);
    position: relative;
}

.pec-upload--error .pec-upload__card {
    border: 1.5px solid var(--color-status-error);
}

/* 2026-07-15 round 2: the whole Empty card is one click/focus target — see the "WHOLE-CARD
   CLICK" note in PecImageUpload.razor's header for why this is Empty-only, not Uploaded/Error. */
.pec-upload__card--clickable {
    cursor: pointer;
}

.pec-upload__card--disabled {
    cursor: not-allowed;
}

.pec-upload__placeholder {
    width: 104px;
    height: 104px;
    background: var(--color-text-base-surface-muted);
    border: 1px solid var(--color-text-base-border);
    border-radius: 16px;
    display: flex;
    align-items: center;
    justify-content: center;
}

.pec-upload__placeholder-inner {
    width: 24px;
    height: 24px;
    background: var(--color-text-base-white-text);
    border: 1px solid var(--color-text-base-border);
    border-radius: 4px;
    display: flex;
    align-items: center;
    justify-content: center;
}

.pec-upload__placeholder-icon {
    color: var(--color-text-base-placeholder-text);
}

.pec-upload__preview {
    width: 112px;
    height: 112px;
    border-radius: 16px;
    background-size: cover;
    background-position: center;
    background-repeat: no-repeat;
}

/* Click-to-zoom trigger (2026-07-31) — matches the app.css `.sd-photo-img--zoomable` convention
   for "this thumbnail opens a full-screen viewer on click". Scoped to the preview only, never the
   Change/Retry button row (.pec-upload__btn-wrap), which stays a separate click target. */
.pec-upload__preview--zoomable {
    position: relative; /* anchors the absolutely-positioned .pec-upload__zoom-overlay child */
    cursor: zoom-in;
}

.pec-upload__preview--zoomable:focus-visible {
    outline: 2px solid var(--color-primary-primary);
    outline-offset: 2px;
}

/* Hover/focus magnifier affordance. `pointer-events: none` so clicks always pass through to the
   preview div underneath — this overlay is decorative only, never a separate hit target (there is
   no token for a translucent dark scrim, so this follows .img-preview's own rgba-backdrop
   convention in app.css). Shown on the PARENT's :hover/:focus-visible, not its own, so keyboard
   focus gets the same affordance a mouse hover does (a11y parity — see header comment note 5). */
.pec-upload__zoom-overlay {
    position: absolute;
    inset: 0;
    display: flex;
    align-items: center;
    justify-content: center;
    border-radius: 16px; /* matches .pec-upload__preview's own radius */
    background: rgba(2, 6, 23, 0.35);
    color: var(--color-text-base-white-text);
    opacity: 0;
    pointer-events: none;
    transition: opacity 0.15s ease;
}

.pec-upload__preview--zoomable:hover .pec-upload__zoom-overlay,
.pec-upload__preview--zoomable:focus-visible .pec-upload__zoom-overlay {
    opacity: 1;
}

.pec-upload--error .pec-upload__preview {
    width: 100px;
    height: 100px;
    border: 1px solid var(--color-status-error);
}

.pec-upload__error-badge {
    position: absolute;
    top: 12px;
    right: 12px;
    width: 24px;
    height: 24px;
    border-radius: 999px;
    background: var(--color-status-error);
    color: var(--color-text-base-white-text);
    display: flex;
    align-items: center;
    justify-content: center;
}

.pec-upload__loading {
    width: 104px;
    height: 104px;
    display: flex;
    align-items: center;
    justify-content: center;
}

.pec-upload__btn-wrap {
    position: relative;
    width: 100%;
}

.pec-upload__btn-wrap .pec-btn {
    width: 100%;
    height: 24px;
}

/* Upload button (Empty state) — light-blue fill, no matching PecButtonVariant (see
   PecImageUpload.razor's header comment for why this stays scoped-local instead of a 7th
   variant). Deliberately NOT routed through <PecButton> — plain <span> styled to match; the real
   click/focus target is the covering .pec-upload__hit-area <InputFile>, not this span (it's
   aria-hidden, purely decorative). */
.pec-upload__upload-btn {
    display: flex;
    align-items: center;
    justify-content: center;
    width: 100%;
    height: 24px;
    background: var(--color-primary-primary-very-bright);
    color: var(--color-primary-primary);
    border-radius: var(--radius-button);
    font-family: var(--font-family-base);
    font-weight: var(--font-weight-bold);
    font-size: var(--font-size-caption);
    line-height: 24px;
}

.pec-upload__card--disabled .pec-upload__upload-btn {
    opacity: 0.5;
}

/* The REAL click/keyboard target — a native, NOT `hidden`-attributed <input type=file>,
   stretched to cover its clickable area (the whole card for Empty; just the button row for
   Change/Retry) and made invisible via opacity, not the `hidden` attribute. Native `hidden`
   strips an element from BOTH the tab order and the accessibility tree (confirmed empirically —
   see PecImageUpload.razor's header comment) — opacity keeps it a real, Tab-reachable,
   Enter/Space-activating form control while looking invisible. This is what gives the whole
   card real keyboard access with zero JS interop: the browser's own native <input type=file>
   keyboard handling does the work, not a custom keydown handler. */
.pec-upload__hit-area {
    position: absolute;
    inset: 0;
    width: 100%;
    height: 100%;
    opacity: 0;
    cursor: pointer;
    margin: 0;
}

.pec-upload__hit-area:disabled {
    cursor: not-allowed;
}

/* Visible keyboard-focus ring on the (invisible) input — without this, a sighted keyboard user
   tabbing to the card gets zero visual feedback that it's focused (the input itself is
   opacity:0). Drawn on the card/button-wrap ancestor via :focus-within so it traces the actual
   clickable region's shape. */
.pec-upload__card--clickable:focus-within,
.pec-upload__btn-wrap:focus-within {
    outline: 2px solid var(--color-primary-primary);
    outline-offset: 2px;
}

.pec-upload__error-text {
    width: 100%;
    color: var(--color-status-error);
    font-size: var(--font-size-caption);
    text-align: center;
}

/* Billing worklist (/billing, Task 13): notes list beside the unreceipted-rounds exception panel.
   The panel reuses .list-card chrome but is tinted so it reads as a warning, not a second data list. */
.billing-layout {
    display: grid;
    grid-template-columns: 2fr 1fr;
    gap: var(--space-lg);
    align-items: start;
}

@media (max-width: 900px) {
    .billing-layout {
        grid-template-columns: 1fr;
    }
}

.list-card--warning {
    border-color: var(--color-status-error);
}

.list-card--warning .list-card__heading {
    color: var(--color-status-error);
}

.billing-exceptions {
    list-style: none;
    margin: 0;
    padding: var(--space-md) var(--space-md);
    display: flex;
    flex-direction: column;
    gap: var(--space-sm);
}

.billing-exceptions__item {
    display: flex;
    flex-direction: column;
    gap: 2px;
    padding: var(--space-sm);
    border: 1px solid var(--color-status-error-vbright);
    border-radius: var(--radius-button);
    background: var(--color-status-error-vbright);
    color: var(--color-status-error);
    font-size: var(--font-size-caption);
}

/* Negative amounts (corrections/credits, ADR 0039 Decision 7) must never render like a positive charge. */
.amount--negative {
    color: var(--color-status-error);
    font-weight: var(--font-weight-medium);
}

/* ---------------------------------------------------------------------------------------------
   PecToast (Feedback/PecToast.razor, PecToastProvider.razor) — 2026-08-05 PecToast ADR §9,
   rev 3 (ADR §8/§9.5, §13): 3-wide vertical stack, not one-at-a-time.
   Global (not scoped .razor.css): the stack is position:fixed at the layout root. z-index: 9000
   sits above ordinary page content but below the app's modal-overlay ceiling (z-index: 9999,
   e.g. app.css .emx-overlay), so an open modal still layers over a toast.
--------------------------------------------------------------------------------------------- */

.pec-toast-stack {
    position: fixed;
    top: 24px;
    right: 24px;
    z-index: 9000;
    display: flex;
    flex-direction: column; /* NOT column-reverse — list order is oldest-first and is rendered as-is;
                                the newest entry is appended LAST, which places it at the BOTTOM
                                (ADR §8.1.2). Reversing here would put the newest at the TOP, the
                                exact bug this stack replaces. */
    align-items: flex-end;
    gap: 12px;
    pointer-events: none; /* gaps between cards must not block clicks on content underneath */
}

.pec-toast-stack > * {
    pointer-events: auto;
}

.pec-toast {
    display: flex;
    align-items: flex-start;
    gap: 13px;
    min-width: 287px;
    max-width: min(287px, calc(100vw - 32px));
    padding: var(--space-md);
    border-radius: var(--radius-lg);
    box-shadow: var(--shadow-floating-card);
    font-family: var(--font-family-base);
    animation: pec-toast-enter 180ms ease-out;
}

@keyframes pec-toast-enter {
    from {
        opacity: 0;
        transform: translateY(12px); /* enters from BELOW, matching "new toast enters at the bottom" */
    }
    to {
        opacity: 1;
        transform: translateY(0);
    }
}

@media (prefers-reduced-motion: reduce) {
    .pec-toast {
        animation: none;
    }
}

.pec-toast__icon {
    flex-shrink: 0;
    display: flex;
}

.pec-toast__text {
    flex: 1 1 auto;
    min-width: 0;
    display: flex;
    flex-direction: column;
    gap: 2px;
    overflow-wrap: anywhere;
}

.pec-toast__headline {
    font-weight: var(--font-weight-bold);
    font-size: var(--font-size-body);
    line-height: var(--line-height-base);
}

.pec-toast__message {
    font-weight: var(--font-weight-regular);
    font-size: var(--font-size-body);
    line-height: var(--line-height-tight);
    display: -webkit-box;
    -webkit-line-clamp: 3;
    -webkit-box-orient: vertical;
    overflow: hidden;
}

.pec-toast__close {
    flex-shrink: 0;
    display: flex;
    align-items: center;
    justify-content: center;
    width: 20px;
    height: 20px;
    padding: 0;
    border: none;
    background: transparent;
    cursor: pointer;
    color: inherit;
}

.pec-toast--success {
    background: var(--color-neutral-title-content);
}

.pec-toast--success .pec-toast__icon,
.pec-toast--success .pec-toast__headline {
    color: var(--color-status-success);
}

.pec-toast--success .pec-toast__message,
.pec-toast--success .pec-toast__close {
    color: var(--color-status-success-vbright);
}

.pec-toast--warning {
    background: var(--color-status-waiting);
}

.pec-toast--warning .pec-toast__icon,
.pec-toast--warning .pec-toast__headline {
    color: var(--color-primary-navigation-bg);
}

.pec-toast--warning .pec-toast__message,
.pec-toast--warning .pec-toast__close {
    color: var(--color-status-waiting-on-fill);
}

.pec-toast--error {
    background: var(--color-status-error);
}

.pec-toast--error .pec-toast__icon,
.pec-toast--error .pec-toast__headline,
.pec-toast--error .pec-toast__message,
.pec-toast--error .pec-toast__close {
    color: var(--color-status-error-vbright);
}

@media (max-width: 480px) {
    .pec-toast-stack {
        left: 16px;
        right: 16px;
        top: 16px;
        align-items: stretch;
    }

    .pec-toast {
        max-width: none;
    }
}

/* ---------------------------------------------------------------------------------------------
   PecToastPopover (Feedback/PecToastPopover.razor) — 2s anchored acknowledgement, ADR §6.2.
   Same "system feedback" surface colour as the success toast, deliberately different geometry
   (6px radius vs the toast's 8px) so the two read as related but distinguishable at a glance.
--------------------------------------------------------------------------------------------- */

.pec-popover {
    background: var(--color-neutral-title-content);
    color: var(--color-text-base-white-text);
    font-family: var(--font-family-base);
    font-size: var(--font-size-body);
    font-weight: var(--font-weight-regular);
    padding: 8px 12px;
    border-radius: var(--radius-md);
    max-width: 240px;
    box-shadow: var(--shadow-card-soft);
}

.pec-popover__message {
    display: block;
}
