/* Token definitions live in _tokens.designer.css, a static asset that mirrors
   AME.Design / AMEPalette.cs. To change a token value, edit the corresponding
   constant in AME.Design and keep _tokens.designer.css in sync. */
@import url("./_tokens.designer.css");

/* ---------- Sidebar root ---------- */
.ame-sidebar {
    display: flex;
    flex-direction: column;
    background: var(--ame-sidebar-bg);
    color: var(--ame-sidebar-fg);
    border-right: 1px solid var(--ame-sidebar-border);
    position: relative;
    transition: width var(--ame-anim-duration-base) var(--ame-anim-easing);
    overflow: hidden;
}
.ame-sidebar__header { padding: 14px; border-bottom: 1px solid var(--ame-sidebar-border); }
.ame-sidebar__body   { flex: 1 1 auto; overflow-y: auto; }
.ame-sidebar__footer { padding: 8px; border-top: 1px solid var(--ame-sidebar-border); display: flex; justify-content: space-between; align-items: center; gap: 8px; font-size: 11px; color: var(--ame-sidebar-fg-muted); }
.ame-sidebar__footer-controls { display: flex; gap: 4px; }
.ame-sidebar__collapse-toggle,
.ame-sidebar__density-toggle {
    background: transparent; color: inherit; border: none; padding: 4px;
    cursor: pointer; border-radius: 4px;
}
.ame-sidebar__collapse-toggle:hover,
.ame-sidebar__density-toggle:hover { background: var(--ame-hover, rgba(255,255,255,0.06)); }

.ame-sidebar__resize-handle {
    position: absolute; top: 0; right: 0; width: 4px; height: 100%;
    cursor: col-resize; background: transparent;
}
.ame-sidebar__resize-handle:hover { background: var(--ame-sidebar-accent); opacity: 0.4; }

/* SP-2 Task 5 (2026-05-19): retired the .ame-sidebar--collapsed
   collapse-rail display:none block — the legacy single-tier AMESidebar
   is no longer rendered by any page (the two-tier AMENavContextPanel
   owns collapse via its own .is-collapsed class). */

/* ---------- Panel group ---------- */
.ame-panel-group__header {
    display: flex; align-items: center; gap: 8px;
    width: 100%; padding: 6px var(--ame-nav-item-padding-x);
    background: transparent; color: var(--ame-sidebar-fg); border: none;
    cursor: pointer; font-size: var(--ame-font-nav); text-align: left;
}
.ame-panel-group__header:hover { background: var(--ame-hover, rgba(255,255,255,0.04)); }
.ame-panel-group__title { flex: 1 1 auto; }
.ame-panel-group__trailing { color: var(--ame-sidebar-fg-muted); font-size: 11px; }
.ame-panel-group__chevron { transition: transform var(--ame-anim-duration-base) var(--ame-anim-easing); }
.ame-panel-group__chevron--open { transform: rotate(180deg); }
.ame-panel-group__body {
    overflow: hidden;
    transition: max-height var(--ame-anim-duration-base) var(--ame-anim-easing),
                opacity var(--ame-anim-duration-base) var(--ame-anim-easing);
}

/* ---------- Nav item ---------- */
.ame-nav-item {
    display: flex; align-items: center; gap: 10px;
    height: var(--ame-nav-item-height);
    padding: 0 var(--ame-nav-item-padding-x);
    color: var(--ame-sidebar-fg); text-decoration: none; font-size: var(--ame-font-nav);
    background: transparent; border: none; width: 100%; cursor: pointer; text-align: left;
    position: relative;
}
.ame-nav-item:hover { background: var(--ame-hover, rgba(255,255,255,0.06)); }
.ame-nav-item.active { color: var(--ame-sidebar-accent); background: color-mix(in srgb, var(--ame-sidebar-accent) 6%, transparent); }
.ame-nav-item__label { flex: 1 1 auto; }
.ame-nav-item__badge {
    background: var(--ame-sidebar-accent); color: var(--ame-on-accent, #0e1217); font-size: 10px;
    padding: 1px 6px; border-radius: 10px; font-weight: 700;
}
.ame-nav-item--disabled { opacity: 0.5; cursor: not-allowed; pointer-events: none; }

/* SP-2 Task 5 (2026-05-19): retired the legacy collapsed-rail
   :hover::after tooltip (Task 13) — it only fired inside the
   single-tier .ame-sidebar--collapsed rail, which no page renders
   anymore. The two-tier rail uses AMETooltip / native title instead. */

/* ============================================================
   Sub-project C1 — primitive form controls
   (tokens defined in _tokens.designer.css)
   ============================================================ */

/* ---------- Inputs (TextBox / Password / TextArea / Numeric) ---------- */
.ame-input {
    height: var(--ame-form-height);
    padding: 0 var(--ame-form-padding-x);
    background: var(--ame-form-bg);
    color: var(--ame-form-fg);
    border: 1px solid var(--ame-form-border-color);
    border-radius: 4px;
    font-size: var(--ame-form-font-size);
    font-family: inherit;
    box-sizing: border-box;
    transition: border-color var(--ame-anim-duration-base) var(--ame-anim-easing);
}
.ame-input:focus,
.ame-input:focus-visible {
    outline: 2px solid var(--ame-form-border-focus-color);
    outline-offset: 1px;
    border-color: var(--ame-form-border-focus-color);
}
.ame-input.invalid {
    border-color: var(--ame-form-border-invalid-color);
}
.ame-input:disabled,
.ame-input[readonly] {
    opacity: 0.6;
    cursor: not-allowed;
}
.ame-textarea {
    height: auto;
    padding: 8px var(--ame-form-padding-x);
    resize: vertical;
    min-height: 60px;
}

/* ---------- Password reveal toggle ---------- */
.ame-password {
    position: relative;
    display: inline-flex;
    align-items: stretch;
    width: 100%;
}
.ame-password .ame-input {
    flex: 1 1 auto;
    padding-right: 36px;
}
.ame-password__toggle {
    position: absolute;
    right: 4px;
    top: 50%;
    transform: translateY(-50%);
    background: transparent;
    border: none;
    cursor: pointer;
    padding: 4px;
    color: var(--ame-form-fg);
    border-radius: 3px;
}
.ame-password__toggle:hover {
    background: var(--ame-hover, rgba(0, 0, 0, 0.05));
}
.ame-password__toggle:disabled {
    opacity: 0.5;
    cursor: not-allowed;
}

/* ---------- SearchBox ----------
   A bordered field that mirrors .ame-input chrome but hosts an inline search
   glyph, a borderless text input, and a clear button. The native <input
   type=search> cancel widget is suppressed so only our × button clears. */
.ame-search {
    display: inline-flex;
    align-items: center;
    gap: 6px;
    height: var(--ame-form-height);
    padding: 0 8px;
    background: var(--ame-form-bg);
    color: var(--ame-form-fg);
    border: 1px solid var(--ame-form-border-color);
    border-radius: 4px;
    box-sizing: border-box;
    transition: border-color var(--ame-anim-duration-base) var(--ame-anim-easing);
}
.ame-search:focus-within {
    outline: 2px solid var(--ame-form-border-focus-color);
    outline-offset: 1px;
    border-color: var(--ame-form-border-focus-color);
}
.ame-search__icon {
    flex: 0 0 auto;
    color: var(--rz-text-tertiary-color, #8B9E9E);
}
.ame-search__input {
    flex: 1 1 auto;
    min-width: 0;
    height: 100%;
    border: none;
    outline: none;
    background: transparent;
    color: inherit;
    font-size: var(--ame-form-font-size);
    font-family: inherit;
    padding: 0;
}
.ame-search__input::placeholder {
    color: var(--rz-text-tertiary-color, #8B9E9E);
}
.ame-search__input::-webkit-search-cancel-button {
    -webkit-appearance: none;
    appearance: none;
}
.ame-search__input:disabled {
    cursor: not-allowed;
}
.ame-search__clear {
    flex: 0 0 auto;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    background: transparent;
    border: none;
    cursor: pointer;
    padding: 2px;
    color: var(--rz-text-tertiary-color, #8B9E9E);
    border-radius: 3px;
}
.ame-search__clear:hover { background: var(--ame-hover, rgba(0, 0, 0, 0.05)); }
.ame-search__clear:disabled {
    opacity: 0.5;
    cursor: not-allowed;
}

/* ---------- Button ---------- */
.ame-button {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    gap: 6px;
    height: var(--ame-form-height);
    padding: 0 14px;
    border: 1px solid transparent;
    border-radius: 4px;
    font-size: var(--ame-form-font-size);
    font-family: inherit;
    cursor: pointer;
    text-decoration: none;
    transition: background-color var(--ame-anim-duration-base) var(--ame-anim-easing),
                color var(--ame-anim-duration-base) var(--ame-anim-easing);
}
.ame-button:disabled {
    opacity: 0.5;
    cursor: not-allowed;
}
.ame-button--sm { height: var(--ame-form-height-sm); padding: 0 10px; font-size: var(--ame-form-font-size-sm); }
.ame-button--md { /* default */ }
.ame-button--lg { height: 48px; padding: 0 18px; font-size: 16px; }

.ame-button--primary   { background: var(--ame-button-bg-primary);   color: var(--ame-button-fg-on-primary); }
.ame-button--secondary { background: var(--ame-button-bg-secondary); color: var(--ame-button-fg-on-primary); }
.ame-button--light     { background: var(--ame-button-bg-light);     color: var(--ame-button-fg-on-light); }
.ame-button--danger    { background: var(--ame-button-bg-danger);    color: var(--ame-button-fg-on-primary); }
.ame-button--success   { background: var(--ame-button-bg-success);   color: var(--ame-button-fg-on-primary); }

.ame-button:hover:not(:disabled) { filter: brightness(0.95); }
.ame-button:focus-visible {
    outline: 2px solid var(--ame-form-border-focus-color);
    outline-offset: 2px;
}

/* ---------- Label ---------- */
.ame-label {
    display: inline-block;
    font-size: var(--ame-form-font-size);
    color: var(--ame-form-fg);
    margin-bottom: 4px;
}
.ame-label__required {
    color: var(--ame-form-border-invalid-color);
    margin-left: 2px;
}

/* ---------- Switch ---------- */
.ame-switch {
    position: relative;
    display: inline-block;
    width: 36px;
    height: 20px;
    background: var(--rz-base-200, #e5e7eb);
    border: 1px solid var(--ame-form-border-color);
    border-radius: 999px;
    cursor: pointer;
    padding: 0;
    transition: background-color var(--ame-anim-duration-base) var(--ame-anim-easing);
    vertical-align: middle;
}
.ame-switch::before {
    content: "";
    position: absolute;
    top: 1px;
    left: 1px;
    width: 16px;
    height: 16px;
    background: var(--ame-on-colored, #ffffff);
    border-radius: 50%;
    box-shadow: 0 1px 2px rgba(0,0,0,0.2);
    transition: transform var(--ame-anim-duration-base) var(--ame-anim-easing);
}
.ame-switch[aria-checked="true"] {
    background: var(--ame-button-bg-primary);
}
.ame-switch[aria-checked="true"]::before {
    transform: translateX(16px);
}
.ame-switch:disabled {
    opacity: 0.5;
    cursor: not-allowed;
}
.ame-switch:focus-visible {
    outline: 2px solid var(--ame-form-border-focus-color);
    outline-offset: 2px;
}

/* Labelled switch field (OnLabel / OffLabel) — only emitted when a label is set.
   The off label is muted while unchecked and the on label is muted while
   checked; the active side picks up the form foreground so the current state
   reads at a glance. State is mirrored off the button aria-checked attribute. */
.ame-switch-field {
    display: inline-flex;
    align-items: center;
    gap: 8px;
    vertical-align: middle;
}
.ame-switch__label {
    font-size: var(--ame-form-font-size);
    line-height: 1;
    color: var(--rz-text-tertiary-color, #8B9E9E);
    user-select: none;
    transition: color var(--ame-anim-duration-base) var(--ame-anim-easing);
}
.ame-switch-field:has(.ame-switch[aria-checked="false"]) .ame-switch__label--off,
.ame-switch-field:has(.ame-switch[aria-checked="true"]) .ame-switch__label--on {
    color: var(--ame-form-fg);
    font-weight: 600;
}

/* ---------- Checkbox ---------- */
.ame-checkbox-wrapper {
    display: inline-flex;
    align-items: center;
    gap: 6px;
    cursor: pointer;
    font-size: var(--ame-form-font-size);
    color: var(--ame-form-fg);
}
.ame-checkbox {
    width: 16px;
    height: 16px;
    accent-color: var(--ame-button-bg-primary);
    cursor: pointer;
}
.ame-checkbox:disabled {
    cursor: not-allowed;
    opacity: 0.5;
}

/* ---------- Radio group ----------
   Native radios grouped in a fieldset (role=radiogroup). Mirrors the checkbox family: the input
   paints the tokened Primary dot via accent-color, the wrapper carries the caption. Vertical is a
   column; horizontal is a wrapping row. Tokens only. */
.ame-radiogroup {
    display: flex;
    border: 0;
    margin: 0;
    padding: 0;
    min-inline-size: 0; /* cancel the fieldset min-content default so a horizontal row can wrap/flex */
}
.ame-radiogroup--vertical {
    flex-direction: column;
    gap: 8px;
}
.ame-radiogroup--horizontal {
    flex-direction: row;
    flex-wrap: wrap;
    align-items: center;
    gap: 6px 16px;
}
.ame-radio-wrapper {
    display: inline-flex;
    align-items: center;
    gap: 6px;
    cursor: pointer;
    font-size: var(--ame-form-font-size);
    color: var(--ame-form-fg);
}
.ame-radio {
    width: 16px;
    height: 16px;
    accent-color: var(--ame-button-bg-primary);
    cursor: pointer;
}
.ame-radio:disabled {
    cursor: not-allowed;
}
/* The dim covers the WHOLE row (input + caption) — dimming only the 16px input left the caption
   full-contrast while the desktops dim the row (the rider a11y parity note). Single opacity here,
   none on the input, so they never stack. */
.ame-radio-wrapper:has(.ame-radio:disabled) {
    cursor: not-allowed;
    opacity: 0.5;
}
.ame-radio:focus-visible {
    outline: 2px solid var(--ame-form-border-focus-color);
    outline-offset: 2px;
}

/* ---------- Badge ----------
   Default badge text is white-on-coloured-bg; the warning variant flips
   to dark-on-light-amber for AA contrast against the lighter warning fill. */
.ame-badge {
    display: inline-block;
    padding: 2px 8px;
    border-radius: 999px;
    font-size: 11px;
    font-weight: 600;
    line-height: 1.5;
    color: var(--ame-on-colored, #ffffff);
}
.ame-badge--info    { background: var(--ame-badge-bg-info); }
.ame-badge--success { background: var(--ame-badge-bg-success); }
.ame-badge--warning { background: var(--ame-badge-bg-warning); color: var(--ame-on-warning, #1f2937); }
.ame-badge--danger  { background: var(--ame-badge-bg-danger); }
.ame-badge--light   { background: var(--ame-badge-bg-light); color: var(--ame-form-fg); }

/* ============================================================
   Sub-project C2 — pickers + feedback controls
   (tokens defined in _tokens.designer.css)
   ============================================================ */

/* ---------- Dropdown ----------
   Custom button + popup implementation. The native <select> popup is
   OS-rendered on Windows and ignores our theme tokens (yields light-on-dark
   bug on dark themes). AMEDropDown.razor renders a button trigger plus a
   <div role="listbox"> popup; the trigger inherits .ame-input + .ame-dropdown
   for sizing/colors. A hidden <select> mirror is included for backward
   compat with bUnit tests and external automation. */
.ame-dropdown {
    color: var(--ame-dropdown-fg);
    background-color: var(--ame-dropdown-bg);
}

.ame-dropdown-wrap {
    position: relative;
    display: inline-block;
}

.ame-dropdown__trigger {
    display: inline-flex;
    align-items: center;
    justify-content: space-between;
    text-align: left;
    cursor: pointer;
    /* inherits .ame-input + .ame-dropdown for sizing/colors */
}

.ame-dropdown__trigger:disabled {
    cursor: not-allowed;
    opacity: 0.55;
}

.ame-dropdown__label {
    flex: 1 1 auto;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.ame-dropdown__chevron {
    flex: 0 0 auto;
    width: 8px;
    height: 8px;
    margin-left: 8px;
    border-right: 2px solid currentColor;
    border-bottom: 2px solid currentColor;
    transform: rotate(45deg) translate(-1px, -1px);
    opacity: 0.7;
}

.ame-dropdown__popup {
    position: absolute;
    top: calc(100% + 4px);
    left: 0;
    min-width: 100%;
    max-height: 280px;
    overflow-y: auto;
    background: var(--ame-dropdown-bg);
    color: var(--ame-dropdown-fg);
    border: 1px solid var(--ame-dropdown-border-color);
    border-radius: 4px;
    box-shadow: 0 4px 16px rgba(0, 0, 0, 0.4);
    z-index: 1000;
    padding: 4px 0;
}

.ame-dropdown__option {
    display: block;
    width: 100%;
    text-align: left;
    background: transparent;
    color: var(--ame-dropdown-fg);
    border: none;
    padding: 6px 12px;
    font: inherit;
    cursor: pointer;
}

.ame-dropdown__option:hover,
.ame-dropdown__option--focused {
    background: var(--ame-accent-subtle, rgba(133, 221, 113, 0.12));
}

.ame-dropdown__option--selected {
    background: var(--ame-form-border-focus-color);
    color: var(--ame-button-fg-on-primary);
}


/* ---------- Spinner ---------- */
.ame-spinner {
    display: inline-block;
    position: relative;
    color: var(--ame-spinner-color);
}
.ame-spinner--sm { width: var(--ame-spinner-size-sm); height: var(--ame-spinner-size-sm); }
.ame-spinner--md { width: var(--ame-spinner-size-md); height: var(--ame-spinner-size-md); }
.ame-spinner--lg { width: var(--ame-spinner-size-lg); height: var(--ame-spinner-size-lg); }

.ame-spinner__dot {
    position: absolute;
    width: 25%;
    height: 25%;
    background: currentColor;
    border-radius: 50%;
    top: 0;
    left: 50%;
    transform-origin: 50% 200%;
    animation: ame-spinner-rotate 1.2s linear infinite;
}
.ame-spinner__dot:nth-child(2) { animation-delay: -0.4s; }
.ame-spinner__dot:nth-child(3) { animation-delay: -0.8s; }

@keyframes ame-spinner-rotate {
    0%   { transform: translate(-50%, 0) rotate(0deg) translate(0, 200%); opacity: 0.2; }
    50%  { opacity: 1; }
    100% { transform: translate(-50%, 0) rotate(360deg) translate(0, 200%); opacity: 0.2; }
}

@media (prefers-reduced-motion: reduce) {
    .ame-spinner__dot { animation: none; opacity: 0.6; }
    .ame-spinner__dot:nth-child(1) { transform: translate(-50%, 200%) rotate(0deg); }
    .ame-spinner__dot:nth-child(2) { transform: translate(-50%, 200%) rotate(120deg); }
    .ame-spinner__dot:nth-child(3) { transform: translate(-50%, 200%) rotate(240deg); }
}

/* ---------- Alert ---------- */
.ame-alert {
    display: flex;
    flex-direction: column;
    gap: 4px;
    padding: 12px 36px 12px 16px;
    border: 1px solid var(--ame-alert-border-info);
    border-left-width: 4px;
    border-radius: 4px;
    background: var(--ame-alert-bg-info);
    color: var(--ame-alert-fg);
    font-size: 14px;
    position: relative;
}
.ame-alert--info    { border-color: var(--ame-alert-border-info);    background: var(--ame-alert-bg-info); }
.ame-alert--success { border-color: var(--ame-alert-border-success); background: var(--ame-alert-bg-success); }
.ame-alert--warning { border-color: var(--ame-alert-border-warning); background: var(--ame-alert-bg-warning); }
.ame-alert--danger  { border-color: var(--ame-alert-border-danger);  background: var(--ame-alert-bg-danger); }
.ame-alert__title { font-weight: 600; }
.ame-alert__body { color: inherit; }
.ame-alert__dismiss {
    position: absolute;
    top: 8px;
    right: 8px;
    background: transparent;
    border: none;
    color: inherit;
    cursor: pointer;
    padding: 2px;
    border-radius: 3px;
}
.ame-alert__dismiss:hover { background: var(--ame-hover, rgba(0,0,0,0.05)); }

/* ---------- Card ---------- */
.ame-card {
    display: flex;
    flex-direction: column;
    background: var(--ame-card-bg);
    border: 1px solid var(--ame-card-border-color);
    border-radius: 6px;
    overflow: hidden;
}
.ame-card--elev-sm { box-shadow: var(--ame-card-shadow-sm); }
.ame-card--elev-md { box-shadow: var(--ame-card-shadow-md); }
.ame-card--elev-lg { box-shadow: var(--ame-card-shadow-lg); }
.ame-card__header {
    padding: var(--ame-card-padding);
    border-bottom: 1px solid var(--ame-card-border-color);
    font-weight: 600;
}
.ame-card__body { padding: var(--ame-card-padding); }
.ame-card__footer {
    padding: var(--ame-card-padding);
    border-top: 1px solid var(--ame-card-border-color);
}

/* ---------- Progress bar ---------- */
.ame-progress {
    width: 100%;
    height: var(--ame-progress-height);
    background: var(--ame-progress-track);
    border-radius: 999px;
    overflow: hidden;
}
.ame-progress__bar {
    height: 100%;
    transition: width var(--ame-anim-duration-base) var(--ame-anim-easing);
    background: var(--ame-progress-bar-info);
}
.ame-progress--info    .ame-progress__bar { background: var(--ame-progress-bar-info); }
.ame-progress--success .ame-progress__bar { background: var(--ame-progress-bar-success); }
.ame-progress--warning .ame-progress__bar { background: var(--ame-progress-bar-warning); }
.ame-progress--danger  .ame-progress__bar { background: var(--ame-progress-bar-danger); }

/* SP-2 Task 5 (2026-05-19): retired the .ame-sidebar-subgroup /
   .ame-sidebar-subgroup-header block. It styled the legacy
   AMESidebarSubGroup primitive (no longer rendered by any page) and
   carried the inverted sub-header font scale the SP-2 type-scale fix
   removes. The two-tier panel uses .ame-nav-panel__caption (token
   sized, correctly smaller than the item token). */

/* ---------- AMETooltip primitive (replaces legacy [title]:hover::after) ----------
   Theme-aware bubble: all colors flow from --ame-tooltip-* tokens so light/dark
   themes pick up their own palette (no hardcoded fallback wins). Side-aware
   keyframes layer on top of a base scale+fade entry/exit pair — the side class
   is set by the JS positioner after viewport-fit auto-flip. T1 (2026-05-14):
   added scale(0.96)→scale(1) entrance, [data-leaving="true"] exit class, and
   per-side transform-origin so the bubble appears to grow out of its anchor. */
.ame-tooltip-host { display: none; }
.ame-tooltip-wrap { display: inline-block; }
.ame-tooltip {
    position: fixed;
    z-index: 9999;
    max-width: 280px;
    padding: 10px 12px;
    font-size: var(--ame-tooltip-font-size, 12px);
    line-height: 1.4;
    color: var(--ame-tooltip-fg);
    background: var(--ame-tooltip-bg);
    border: 1px solid var(--ame-tooltip-border, var(--ame-border-strong, rgba(255,255,255,0.16)));
    border-radius: 6px;
    box-shadow: var(--ame-shadow-2, 0 6px 18px rgba(0,0,0,0.25));
    pointer-events: none;
    white-space: normal;
    opacity: 0;
    transform-origin: center bottom;
    animation: ame-tooltip-in 180ms cubic-bezier(0.2, 0, 0, 1) both;
}

/* Per-side transform-origin so the scale appears to grow out of the
   anchor edge rather than the bubble centre. Side class is written by
   the JS positioner after viewport-fit auto-flip. */
.ame-tooltip--top    { transform-origin: center bottom; }
.ame-tooltip--bottom { transform-origin: center top;    }
.ame-tooltip--left   { transform-origin: right center;  }
.ame-tooltip--right  { transform-origin: left center;   }

/* Exit class — set by the JS layer (or by the C# wrapper-mode hide) for
   the close-delay window so the bubble plays its outro before unmount.
   Faster than the entry (120ms vs 180ms) so a quick mouse-out doesn't
   feel sticky. */
.ame-tooltip[data-leaving="true"] {
    animation: ame-tooltip-out 120ms cubic-bezier(0.4, 0, 1, 1) both;
}

.ame-tooltip-title { display: block; font-weight: 600; margin-bottom: 2px; }
.ame-tooltip-desc  { display: block; opacity: 0.85; white-space: pre-line; }
.ame-tooltip-shortcut {
    display: inline-block;
    margin-top: 6px;
    padding: 1px 6px;
    border-radius: 3px;
    font-family: var(--ame-mono-font, ui-monospace, SFMono-Regular, monospace);
    font-size: 11px;
    background: var(--ame-bg-tertiary, rgba(255,255,255,0.08));
    border: 1px solid var(--ame-border-subtle, rgba(255,255,255,0.06));
}

/* Rich-tooltip head: optional leading icon + title on one row. */
.ame-tooltip__head {
    display: flex;
    align-items: center;
    gap: 6px;
    margin-bottom: 2px;
}
.ame-tooltip__head .ame-tooltip-title { margin-bottom: 0; }
.ame-tooltip__icon { flex: 0 0 auto; }

/* Severity accent — a left border + tinted icon driven by the shared
   --ame-alert-border-{severity} tokens (each falls back to its --rz-* hue).
   Neutral adds no modifier class, so the default bubble is unchanged. */
.ame-tooltip--success { border-left: 3px solid var(--ame-alert-border-success, #10B981); }
.ame-tooltip--warning { border-left: 3px solid var(--ame-alert-border-warning, #F59E0B); }
.ame-tooltip--danger  { border-left: 3px solid var(--ame-alert-border-danger,  #EF4444); }
.ame-tooltip--info    { border-left: 3px solid var(--ame-alert-border-info,    #3B82F6); }
.ame-tooltip--success .ame-tooltip__icon { color: var(--ame-alert-border-success, #10B981); }
.ame-tooltip--warning .ame-tooltip__icon { color: var(--ame-alert-border-warning, #F59E0B); }
.ame-tooltip--danger  .ame-tooltip__icon { color: var(--ame-alert-border-danger,  #EF4444); }
.ame-tooltip--info    .ame-tooltip__icon { color: var(--ame-alert-border-info,    #3B82F6); }

@keyframes ame-tooltip-in    { from { opacity: 0; transform: scale(0.96) translateY(4px); }  to { opacity: 1; transform: scale(1) translateY(0); } }
@keyframes ame-tooltip-out   { from { opacity: 1; transform: scale(1)    translateY(0); }     to { opacity: 0; transform: scale(0.96) translateY(4px); } }

/* Side-aware keyframes retained as opt-in aliases for any consumer that
   still references them by name; the default animation above already
   provides the scale+fade visual. Pinned to the same ame-tooltip-in
   entry shape so removing them later is safe. */
@keyframes ame-tooltip-in-top    { from { opacity: 0; transform: scale(0.96) translateY(4px); }  to { opacity: 1; transform: scale(1) translateY(0); } }
@keyframes ame-tooltip-in-bottom { from { opacity: 0; transform: scale(0.96) translateY(-4px); } to { opacity: 1; transform: scale(1) translateY(0); } }
@keyframes ame-tooltip-in-left   { from { opacity: 0; transform: scale(0.96) translateX(4px); }  to { opacity: 1; transform: scale(1) translateX(0); } }
@keyframes ame-tooltip-in-right  { from { opacity: 0; transform: scale(0.96) translateX(-4px); } to { opacity: 1; transform: scale(1) translateX(0); } }

@media (prefers-reduced-motion: reduce) {
    .ame-tooltip                          { animation: ame-tooltip-fade 80ms linear both; }
    .ame-tooltip[data-leaving="true"]     { animation: ame-tooltip-fade-out 80ms linear both; }
    @keyframes ame-tooltip-fade     { from { opacity: 0; } to { opacity: 1; } }
    @keyframes ame-tooltip-fade-out { from { opacity: 1; } to { opacity: 0; } }
}

/* ============================================================
   Sub-project C3 — data grid + paginator
   (tokens defined in _tokens.designer.css)
   ============================================================ */

/* ---------- DataGrid ---------- */
.ame-datagrid {
    width: 100%;
    border-collapse: collapse;
    background: var(--ame-grid-bg);
    color: var(--ame-grid-fg);
    border: 1px solid var(--ame-grid-border-color);
    font-size: var(--ame-form-font-size);
}

.ame-datagrid thead th {
    background: var(--ame-grid-header-bg);
    color: var(--ame-grid-header-fg);
    font-weight: var(--ame-grid-header-font-weight);
    text-align: left;
    padding: var(--ame-grid-cell-padding-y) var(--ame-grid-cell-padding-x);
    border-bottom: 1px solid var(--ame-grid-border-color);
    position: sticky;
    top: 0;
    user-select: none;
}

.ame-datagrid thead th[aria-sort="ascending"],
.ame-datagrid thead th[aria-sort="descending"] {
    color: var(--ame-grid-sort-indicator-color);
}

.ame-datagrid thead th.ame-datagrid__th--sortable {
    cursor: pointer;
}

.ame-datagrid thead th.ame-datagrid__th--sortable:hover {
    background: color-mix(in srgb, var(--ame-grid-header-bg) 90%, var(--ame-grid-sort-indicator-color));
}

.ame-datagrid thead th:focus-visible {
    outline: 2px solid var(--ame-form-border-focus-color);
    outline-offset: -2px;
}

.ame-datagrid__sort-indicator {
    display: inline-block;
    margin-left: 4px;
    color: var(--ame-grid-sort-indicator-color);
}

/* Multi-sort priority badge (Wave 11 grid-i1): the 1-based rank shown beside the glyph only when
   two or more columns are sorted. A single sort shows no number, preserving the shipped look. The
   badge is aria-hidden; the spoken "priority N" rides the visually-hidden header text. */
.ame-datagrid__sort-priority {
    display: inline-block;
    margin-left: 2px;
    font-size: 0.75em;
    font-weight: var(--ame-grid-header-font-weight);
    color: var(--ame-grid-sort-indicator-color);
    vertical-align: super;
}

.ame-datagrid tbody td {
    padding: var(--ame-grid-cell-padding-y) var(--ame-grid-cell-padding-x);
    border-bottom: 1px solid var(--ame-grid-border-color);
    height: var(--ame-grid-row-height);
    vertical-align: middle;
}

/* Roving focus cell (Wave W.X8): the keyboard cell-nav pattern makes body cells focusable, so the
   focused cell needs the same design-token focus ring every other focusable grid element uses (the
   thead th / search / expander idiom). Scoped to role="grid" so the shipped role="table" grid is
   byte-unchanged; matches the roving td and both lead cells (expander/select). */
.ame-datagrid[role="grid"] tbody td:focus-visible {
    outline: 2px solid var(--ame-form-border-focus-color);
    outline-offset: -2px;
}

.ame-datagrid tbody tr:hover {
    background: var(--ame-grid-row-hover-bg);
}

.ame-datagrid tbody tr.ame-datagrid__row--selected {
    background: var(--ame-grid-row-selected-bg);
}

.ame-datagrid tbody tr.ame-datagrid__row--clickable {
    cursor: pointer;
}

.ame-datagrid__empty {
    text-align: center;
    color: var(--ame-grid-empty-fg);
    padding: 24px var(--ame-grid-cell-padding-x);
    font-style: italic;
}

/* Global quick-search toolbar (Wave 11 grid-i2): a search row above the table, shown only when
   ShowSearch is set. The input reuses the shared form token chain (border, focus ring, surface)
   so it matches the other AME inputs without reaching for AMETextBox (which needs an EditForm). */
.ame-datagrid__toolbar {
    display: flex;
    align-items: center;
    gap: 8px;
    margin-bottom: var(--ame-grid-cell-padding-y);
}

.ame-datagrid__search {
    height: var(--ame-form-height);
    min-width: 14rem;
    padding: 0 var(--ame-form-padding-x);
    background: var(--ame-form-bg);
    color: var(--ame-form-fg);
    border: 1px solid var(--ame-form-border-color);
    border-radius: 4px;
    font-size: var(--ame-form-font-size);
    font-family: inherit;
    box-sizing: border-box;
    transition: border-color var(--ame-anim-duration-base) var(--ame-anim-easing);
}

.ame-datagrid__search:focus,
.ame-datagrid__search:focus-visible {
    outline: 2px solid var(--ame-form-border-focus-color);
    outline-offset: 1px;
    border-color: var(--ame-form-border-focus-color);
}

/* CSV export button (Wave U.U4): the trailing affordance of the same toolbar the quick search uses.
   Reuses the shared form token chain (surface, border, focus ring) so it sits with the search input,
   and margin-left:auto parks it at the end of the bar (and at the right when it is alone there). */
.ame-datagrid__export {
    display: inline-flex;
    align-items: center;
    gap: 6px;
    height: var(--ame-form-height);
    margin-left: auto;
    padding: 0 var(--ame-form-padding-x);
    background: var(--ame-form-bg);
    color: var(--ame-form-fg);
    border: 1px solid var(--ame-form-border-color);
    border-radius: 4px;
    font-size: var(--ame-form-font-size);
    font-family: inherit;
    cursor: pointer;
    box-sizing: border-box;
    transition: border-color var(--ame-anim-duration-base) var(--ame-anim-easing);
}

.ame-datagrid__export:hover {
    border-color: var(--ame-form-border-focus-color);
}

.ame-datagrid__export:focus,
.ame-datagrid__export:focus-visible {
    outline: 2px solid var(--ame-form-border-focus-color);
    outline-offset: 1px;
    border-color: var(--ame-form-border-focus-color);
}

/* Conditional ROW TINTS (Wave U.U4): the class comes from the grid's RowCssClass seam, driven by the
   RowSeverity hook (Neutral emits no class at all). Each rule is scoped :not(selected) so a selected
   row keeps reading as selected regardless of source order, and the tint survives the hover wash. */
.ame-datagrid tbody tr.ame-datagrid__row--success:not(.ame-datagrid__row--selected) {
    background: var(--ame-grid-row-success-bg);
}

.ame-datagrid tbody tr.ame-datagrid__row--warning:not(.ame-datagrid__row--selected) {
    background: var(--ame-grid-row-warning-bg);
}

.ame-datagrid tbody tr.ame-datagrid__row--danger:not(.ame-datagrid__row--selected) {
    background: var(--ame-grid-row-danger-bg);
}

.ame-datagrid tbody tr.ame-datagrid__row--info:not(.ame-datagrid__row--selected) {
    background: var(--ame-grid-row-info-bg);
}

/* HOVER COMPOSITES OVER THE TINT, it never replaces it (Wave U.U4 review) — the same "composite, don't replace"
   rule the frozen cells already follow. The tint rules above out-specify the plain tbody tr:hover wash, so on
   exactly the rows that matter the hover feedback would VANISH: a tinted row would be the one row a user cannot
   tell they are pointing at. Both tokens are translucent washes, so stacking the hover wash ON TOP of the tint
   (one background layer stack, hover first = topmost) keeps the severity readable AND the hover visible. Still
   :not(selected), so a selected row keeps reading as selected. */
.ame-datagrid tbody tr.ame-datagrid__row--success:not(.ame-datagrid__row--selected):hover {
    background:
        linear-gradient(var(--ame-grid-row-hover-bg), var(--ame-grid-row-hover-bg)),
        linear-gradient(var(--ame-grid-row-success-bg), var(--ame-grid-row-success-bg));
}

.ame-datagrid tbody tr.ame-datagrid__row--warning:not(.ame-datagrid__row--selected):hover {
    background:
        linear-gradient(var(--ame-grid-row-hover-bg), var(--ame-grid-row-hover-bg)),
        linear-gradient(var(--ame-grid-row-warning-bg), var(--ame-grid-row-warning-bg));
}

.ame-datagrid tbody tr.ame-datagrid__row--danger:not(.ame-datagrid__row--selected):hover {
    background:
        linear-gradient(var(--ame-grid-row-hover-bg), var(--ame-grid-row-hover-bg)),
        linear-gradient(var(--ame-grid-row-danger-bg), var(--ame-grid-row-danger-bg));
}

.ame-datagrid tbody tr.ame-datagrid__row--info:not(.ame-datagrid__row--selected):hover {
    background:
        linear-gradient(var(--ame-grid-row-hover-bg), var(--ame-grid-row-hover-bg)),
        linear-gradient(var(--ame-grid-row-info-bg), var(--ame-grid-row-info-bg));
}

/* Per-column filter row (Wave 11 grid-i3): a second header row under the column titles, shown only
   when ShowFilterRow is set. Each filterable column gets a plain input that reuses the grid-i2 search
   token chain (the shared form border, focus ring, surface) so it matches the other AME inputs
   without reaching for AMETextBox. The cell keeps the header surface so the row reads as part of the
   header band; the input stretches to the column width. */
.ame-datagrid__filter-row {
    background: var(--ame-grid-header-bg);
}

.ame-datagrid thead th.ame-datagrid__filter-cell {
    position: static;
    padding: var(--ame-grid-cell-padding-y) var(--ame-grid-cell-padding-x);
    border-bottom: 1px solid var(--ame-grid-border-color);
    font-weight: normal;
}

.ame-datagrid__filter-input {
    width: 100%;
    height: var(--ame-form-height);
    padding: 0 var(--ame-form-padding-x);
    background: var(--ame-form-bg);
    color: var(--ame-form-fg);
    border: 1px solid var(--ame-form-border-color);
    border-radius: 4px;
    font-size: var(--ame-form-font-size);
    font-family: inherit;
    box-sizing: border-box;
    transition: border-color var(--ame-anim-duration-base) var(--ame-anim-easing);
}

.ame-datagrid__filter-input:focus,
.ame-datagrid__filter-input:focus-visible {
    outline: 2px solid var(--ame-form-border-focus-color);
    outline-offset: 1px;
    border-color: var(--ame-form-border-focus-color);
}

/* Per-column operator selector (Wave 11 grid-i4): a plain native select styled to match the value
   input above. It stacks over the input in the filter cell, so it carries a small bottom margin and
   reuses the same form-token chain (height, background, border, focus ring). */
.ame-datagrid__filter-op {
    width: 100%;
    height: var(--ame-form-height);
    margin-bottom: 4px;
    padding: 0 var(--ame-form-padding-x);
    background: var(--ame-form-bg);
    color: var(--ame-form-fg);
    border: 1px solid var(--ame-form-border-color);
    border-radius: 4px;
    font-size: var(--ame-form-font-size);
    font-family: inherit;
    box-sizing: border-box;
    cursor: pointer;
    transition: border-color var(--ame-anim-duration-base) var(--ame-anim-easing);
}

.ame-datagrid__filter-op:focus,
.ame-datagrid__filter-op:focus-visible {
    outline: 2px solid var(--ame-form-border-focus-color);
    outline-offset: 1px;
    border-color: var(--ame-form-border-focus-color);
}

/* Group-header row (Wave 11 grid-i5): a SurfaceMuted band spanning the table width, shown above each
   bucket's data rows when grouping is active. Reuses the shared grid tokens (the header surface, the
   text color, the border, the cell padding) so it reads as a band of the grid, not a bespoke element.
   The label is bold; the count and aggregates are quieter, set apart by a small gap. No hover so it does
   not read as an interactive data row. The data rows below render with the shipped row styling. */
.ame-datagrid tbody tr.ame-datagrid__group-header {
    background: var(--ame-grid-header-bg);
}

.ame-datagrid tbody tr.ame-datagrid__group-header:hover {
    background: var(--ame-grid-header-bg);
}

.ame-datagrid tbody td.ame-datagrid__group-header-cell {
    color: var(--ame-grid-fg);
    padding: var(--ame-grid-cell-padding-y) var(--ame-grid-cell-padding-x);
    border-bottom: 1px solid var(--ame-grid-border-color);
}

.ame-datagrid__group-label {
    font-weight: var(--ame-grid-header-font-weight);
}

.ame-datagrid__group-count {
    margin-left: 6px;
    color: var(--ame-grid-sort-indicator-color);
    font-weight: var(--ame-grid-header-font-weight);
}

.ame-datagrid__group-aggregate {
    margin-left: 12px;
    color: var(--ame-grid-sort-indicator-color);
    font-size: 0.875em;
}

/* Checkbox selection column (Wave 11 grid-i6): a narrow leading column, shown only under
   SelectionMode.Multiple, holding the SelectAll header checkbox and a per-row checkbox. Centered, fixed
   to the checkbox width so it does not steal space from the data columns. The header cell keeps the
   sticky header surface (it inherits the thead th rule); the body cell inherits the shared tbody td
   padding/border, so the column reads as part of the grid. */
.ame-datagrid .ame-datagrid__select-cell {
    width: 1px;
    white-space: nowrap;
    text-align: center;
    padding-left: var(--ame-grid-cell-padding-x);
    padding-right: var(--ame-grid-cell-padding-x);
}

.ame-datagrid .ame-datagrid__select-cell input[type="checkbox"] {
    cursor: pointer;
    vertical-align: middle;
    margin: 0;
}

.ame-datagrid .ame-datagrid__select-cell input[type="checkbox"]:focus-visible {
    outline: 2px solid var(--ame-form-border-focus-color);
    outline-offset: 1px;
}

/* Frozen columns + header-edge column resize (Wave U u1 / 20.G2a). EVERY rule below is gated behind a
   class the grid emits ONLY once a column is frozen or one has actually been dragged (LayoutMode), so a
   shipped grid keeps its auto layout, its non-scrolling wrap and its cursor, byte for byte. */

/* The horizontal viewport frozen columns pin against. The shipped wrap has position:relative only (it
   anchors the loading overlay); it never scrolled. */
.ame-datagrid__wrap--scroll {
    overflow-x: auto;
}

/* Fixed layout is what makes the px widths (colgroup + the inline cell widths) authoritative and stops
   the browser from re-flowing a frozen column out from under its sticky offset. */
.ame-datagrid.ame-datagrid--fixed {
    table-layout: fixed;
    width: max-content;
    min-width: 100%;
}

/* The resize GRIP: a real element the header renders only when AllowColumnResize is on (default off), so
   an opted-out grid ships no grip at all. It is the drag's hit test, so it must be a >=24x24 CSS-px
   target (WCAG 2.5.8) — the shipped header th is position:sticky, i.e. a positioned box, which is the
   containing block this absolute grip pins against. It is deliberately NOT painted: it sits over the
   header's right padding and only changes the cursor. */
.ame-datagrid__resize-grip {
    position: absolute;
    top: 0;
    right: 0;
    bottom: 0;
    width: 24px;
    min-height: 24px;
    cursor: col-resize;
    touch-action: none;
    user-select: none;
    background: transparent;
}

/* Sticky in BOTH axes for a header cell (the shipped rule already pins it to top:0); the left offset
   itself rides an inline style computed from AMEGridLayout.LeftOffsets. A sticky cell over scrolling
   content MUST be opaque, or the cells sliding underneath show through it. The frozen column's divider
   is drawn with an inset shadow, never border-right: borders on a sticky cell do not paint reliably
   under the table's border-collapse: collapse. */
.ame-datagrid.ame-datagrid--fixed .ame-datagrid__cell--frozen {
    position: sticky;
    box-shadow: inset -1px 0 0 var(--ame-grid-border-color);
}

.ame-datagrid.ame-datagrid--fixed thead th.ame-datagrid__cell--frozen {
    background: var(--ame-grid-header-bg);
}

.ame-datagrid.ame-datagrid--fixed tbody td.ame-datagrid__cell--frozen {
    background: var(--ame-grid-bg);
}

/* The frozen filter cell re-asserts sticky: the shipped filter-row rule resets these cells to
   position:static, which would leave the filter input behind while its column stayed pinned. */
.ame-datagrid.ame-datagrid--fixed thead th.ame-datagrid__filter-cell.ame-datagrid__cell--frozen {
    position: sticky;
    background: var(--ame-grid-header-bg);
}

/* The sticky CORNER: a frozen header cell sticks on both axes, so it must out-rank the plain header
   (which out-ranks the frozen body cells, which out-rank the plain body cells). Get this wrong and the
   header scrolls under the frozen column. */
.ame-datagrid.ame-datagrid--fixed thead th {
    z-index: 2;
}

.ame-datagrid.ame-datagrid--fixed thead th.ame-datagrid__cell--frozen {
    z-index: 3;
}

.ame-datagrid.ame-datagrid--fixed tbody td.ame-datagrid__cell--frozen {
    z-index: 1;
}

/* The opaque frozen background occludes the row hover/selected wash, so re-assert both on the frozen
   cells (tokens only). Both wash tokens are TRANSLUCENT (a color-mix toward transparent), so they are
   composited OVER the opaque grid surface as a gradient layer instead of REPLACING it: assigning the
   wash alone would drop the frozen cell's alpha below 1 and let the scrolling cells bleed through the
   pinned column on the two states a user is always in. */
.ame-datagrid.ame-datagrid--fixed tbody tr:hover td.ame-datagrid__cell--frozen {
    background:
        linear-gradient(var(--ame-grid-row-hover-bg), var(--ame-grid-row-hover-bg)),
        var(--ame-grid-bg);
}

/* Same story for the Wave U.U4 severity tints: a frozen cell's opaque background would occlude the row
   tint, so composite each wash over the grid surface here too. Declared BETWEEN the hover rule and the
   selected rule, so a tinted row still reads as SELECTED when it is selected (the later rule wins) and
   the tint still beats the hover wash. Without these, a tinted row would lose its tint under exactly the
   pinned columns the user is looking at. */
.ame-datagrid.ame-datagrid--fixed tbody tr.ame-datagrid__row--success td.ame-datagrid__cell--frozen {
    background:
        linear-gradient(var(--ame-grid-row-success-bg), var(--ame-grid-row-success-bg)),
        var(--ame-grid-bg);
}

.ame-datagrid.ame-datagrid--fixed tbody tr.ame-datagrid__row--warning td.ame-datagrid__cell--frozen {
    background:
        linear-gradient(var(--ame-grid-row-warning-bg), var(--ame-grid-row-warning-bg)),
        var(--ame-grid-bg);
}

.ame-datagrid.ame-datagrid--fixed tbody tr.ame-datagrid__row--danger td.ame-datagrid__cell--frozen {
    background:
        linear-gradient(var(--ame-grid-row-danger-bg), var(--ame-grid-row-danger-bg)),
        var(--ame-grid-bg);
}

.ame-datagrid.ame-datagrid--fixed tbody tr.ame-datagrid__row--info td.ame-datagrid__cell--frozen {
    background:
        linear-gradient(var(--ame-grid-row-info-bg), var(--ame-grid-row-info-bg)),
        var(--ame-grid-bg);
}

/* …and the frozen cells of a HOVERED tinted row get the SAME composite as the row above: hover wash, then tint,
   then the opaque grid surface (the tinted frozen rules just above tie with the tr:hover frozen rule and win on
   source order, so without these the pinned columns of a tinted row would lose their hover too). */
.ame-datagrid.ame-datagrid--fixed tbody tr.ame-datagrid__row--success:hover td.ame-datagrid__cell--frozen {
    background:
        linear-gradient(var(--ame-grid-row-hover-bg), var(--ame-grid-row-hover-bg)),
        linear-gradient(var(--ame-grid-row-success-bg), var(--ame-grid-row-success-bg)),
        var(--ame-grid-bg);
}

.ame-datagrid.ame-datagrid--fixed tbody tr.ame-datagrid__row--warning:hover td.ame-datagrid__cell--frozen {
    background:
        linear-gradient(var(--ame-grid-row-hover-bg), var(--ame-grid-row-hover-bg)),
        linear-gradient(var(--ame-grid-row-warning-bg), var(--ame-grid-row-warning-bg)),
        var(--ame-grid-bg);
}

.ame-datagrid.ame-datagrid--fixed tbody tr.ame-datagrid__row--danger:hover td.ame-datagrid__cell--frozen {
    background:
        linear-gradient(var(--ame-grid-row-hover-bg), var(--ame-grid-row-hover-bg)),
        linear-gradient(var(--ame-grid-row-danger-bg), var(--ame-grid-row-danger-bg)),
        var(--ame-grid-bg);
}

.ame-datagrid.ame-datagrid--fixed tbody tr.ame-datagrid__row--info:hover td.ame-datagrid__cell--frozen {
    background:
        linear-gradient(var(--ame-grid-row-hover-bg), var(--ame-grid-row-hover-bg)),
        linear-gradient(var(--ame-grid-row-info-bg), var(--ame-grid-row-info-bg)),
        var(--ame-grid-bg);
}

.ame-datagrid.ame-datagrid--fixed tbody tr.ame-datagrid__row--selected td.ame-datagrid__cell--frozen {
    background:
        linear-gradient(var(--ame-grid-row-selected-bg), var(--ame-grid-row-selected-bg)),
        var(--ame-grid-bg);
}

/* Focus must never land BEHIND the pinned run: the browser's scroll-into-view on focus ignores sticky
   overlap, so a Shift+Tab back to a left-scrolled header/cell would park it under the frozen columns
   (WCAG 2.4.11). The run's width is published by the grid onto the wrap as --ame-grid-frozen-run. */
.ame-datagrid.ame-datagrid--fixed thead th,
.ame-datagrid.ame-datagrid--fixed tbody td {
    scroll-margin-left: var(--ame-grid-frozen-run, 0px);
}

/* Master-DETAIL + the totals FOOTER (Wave U u3 / 20.G3). Every class below is emitted only once the grid opts
   in: the expander column exists only when a DetailTemplate was supplied, and the tfoot only when at least one
   FooterAggregate is configured. A shipped grid matches none of these selectors. */

/* The leading expander column: the shrink-to-fit lead cell (the grid-i6 select column's rule, mirrored). Under
   table-layout:fixed the grid publishes an explicit px width inline instead, so this only governs auto layout. */
.ame-datagrid .ame-datagrid__expander-cell {
    width: 1px;
    white-space: nowrap;
    text-align: center;
    padding-right: 0;
}

/* The row's expander button: the AMETreeList chevron, rotated open. It is the only control in the cell, so it
   is its own >=24x24 pointer target (WCAG 2.5.8). */
.ame-datagrid__expander {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 24px;
    height: 24px;
    padding: 0;
    background: transparent;
    border: none;
    border-radius: 3px;
    color: var(--ame-grid-fg);
    cursor: pointer;
    transition: transform var(--ame-anim-duration-base) var(--ame-anim-easing);
}

.ame-datagrid__expander:focus-visible {
    outline: 2px solid var(--ame-form-border-focus-color);
    outline-offset: -2px;
}

.ame-datagrid__expander--open {
    transform: rotate(90deg);
}

/* The revealed DETAIL row: a full-width band under its row (colspan == SpanColumnCount), set off from the data
   rows by the muted surface. It is not selectable and never carries the row hover/selected wash. */
.ame-datagrid tbody tr.ame-datagrid__detail-row,
.ame-datagrid tbody tr.ame-datagrid__detail-row:hover {
    background: var(--ame-grid-header-bg);
    cursor: default;
}

.ame-datagrid tbody td.ame-datagrid__detail-cell {
    padding: var(--ame-grid-cell-padding-x);
    height: auto;
    text-align: left;
    white-space: normal;
}

/* The totals FOOTER. The shipped grid styles `thead th` / `tbody td` only, so a tfoot cell inherits NOTHING and
   the whole block below is additive. position:sticky bottom:0 is the PINNED affordance: it is inert until the
   consumer constrains the wrap's height, and harmless when they do not. A sticky cell over scrolling content
   must be opaque. */
.ame-datagrid tfoot td {
    position: sticky;
    bottom: 0;
    z-index: 1;
    padding: var(--ame-grid-cell-padding-y) var(--ame-grid-cell-padding-x);
    border-top: 1px solid var(--ame-grid-border-color);
    background: var(--ame-grid-header-bg);
    color: var(--ame-grid-fg);
    font-weight: var(--ame-grid-header-font-weight);
    vertical-align: middle;
}

/* The frozen mirror. The U1 sticky rules are scoped thead th / tbody td, so a frozen FOOTER cell matches none
   of them: it needs its own sticky in the second axis (the inline left offset is the same one the body cells
   carry), its own opaque background, and a z-index above the plain footer cells so the scrolling ones pass
   underneath it. It stays BELOW the header's ladder (thead 2, frozen thead 3). */
.ame-datagrid.ame-datagrid--fixed tfoot td.ame-datagrid__cell--frozen {
    position: sticky;
    z-index: 2;
    background: var(--ame-grid-header-bg);
    box-shadow: inset -1px 0 0 var(--ame-grid-border-color);
}

/* Header drag-REORDER + the column CHOOSER (Wave U u2 / 20.G2b). Like the U1 rules above, every class here is
   emitted only when the grid opts in (AllowColumnReorder, default off), so a shipped header is untouched. */

/* The reorderable header is a pointer-drag target: grab cursor, and touch-action:none so a touch drag moves
   the column instead of scrolling the wrap. */
.ame-datagrid__th--reorderable {
    cursor: grab;
    touch-action: none;
}

/* The column being carried: dimmed, and the cursor switches to grabbing. */
.ame-datagrid__th--dragging {
    cursor: grabbing;
    opacity: 0.6;
}

/* The header the column would drop ONTO. The insertion point is drawn as an inset shadow (a border on a
   sticky/collapsed-border header cell does not paint reliably) in the same accent the sort indicator uses. */
.ame-datagrid__th--drop-target {
    box-shadow: inset 2px 0 0 var(--ame-grid-sort-indicator-color);
}

/* The chooser's checkbox list inside the shipped AMEPopover card (the card itself brings the surface,
   border, radius and shadow — this is only the list). */
.ame-column-chooser__list {
    list-style: none;
    margin: 0;
    padding: 0;
    min-width: 160px;
}

.ame-column-chooser__label {
    display: flex;
    align-items: center;
    gap: var(--ame-grid-cell-padding-y);
    padding: var(--ame-grid-cell-padding-y) var(--ame-grid-cell-padding-x);
    color: var(--ame-form-fg);
    font-size: var(--ame-form-font-size);
    cursor: pointer;
}

.ame-column-chooser__label:hover {
    background: var(--ame-grid-row-hover-bg);
}

.ame-column-chooser__label input[type="checkbox"]:focus-visible {
    outline: 2px solid var(--ame-form-border-focus-color);
    outline-offset: 1px;
}

/* Inline cell editor (Wave 11 grid-i8): the plain input a data cell swaps in when that cell is the open
   editing cell, shown only when a column opts in via IsEditable. It FILLS the cell (no layout shift between
   the display text and the editor) and reuses the shared form token chain (the form border, focus ring,
   surface, font) so it matches the grid-i2 search / grid-i3 filter inputs and the other AME inputs without
   reaching for AMETextBox. A grid with no editable column never renders it (the byte-identical floor). */
.ame-datagrid .ame-datagrid__edit-input {
    width: 100%;
    box-sizing: border-box;
    margin: 0;
    padding: 0 var(--ame-form-padding-x);
    background: var(--ame-form-bg);
    color: var(--ame-form-fg);
    border: 1px solid var(--ame-form-border-color);
    border-radius: 4px;
    font-size: var(--ame-form-font-size);
    font-family: inherit;
    transition: border-color var(--ame-anim-duration-base) var(--ame-anim-easing);
}

.ame-datagrid .ame-datagrid__edit-input:focus,
.ame-datagrid .ame-datagrid__edit-input:focus-visible {
    outline: 2px solid var(--ame-form-border-focus-color);
    outline-offset: 1px;
    border-color: var(--ame-form-border-focus-color);
}

/* ---------- TreeList (Wave 7e) ----------
   A semantic <table> that reuses the whole .ame-datagrid look (header, rows,
   hover/selected, empty). Only the built-in tree first column is bespoke: a flex
   cell with a per-level indent (applied inline as padding-left so the row's
   hover/selected background still spans the full width), a chevron button that
   mirrors the tree-view idiom (rotate 90deg when open), an optional leading icon,
   and the title. The data columns are plain .ame-datagrid cells. */
.ame-tree-list__tree-cell {
    user-select: none;
    white-space: nowrap;
}
.ame-tree-list__tree-cell > * {
    vertical-align: middle;
}
.ame-tree-list__chevron {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 18px;
    height: 18px;
    margin-right: 4px;
    padding: 0;
    background: transparent;
    border: none;
    color: var(--ame-text-muted, #8b9e9e);
    cursor: pointer;
    border-radius: 3px;
    transition: transform var(--ame-anim-duration-base) var(--ame-anim-easing);
}
.ame-tree-list__chevron:hover { background: var(--ame-hover, rgba(0, 0, 0, 0.06)); }
.ame-tree-list__chevron:focus-visible {
    outline: 2px solid var(--ame-form-border-focus-color);
    outline-offset: -2px;
}
.ame-tree-list__chevron--open { transform: rotate(90deg); }
.ame-tree-list__chevron-spacer {
    display: inline-block;
    width: 18px;
    height: 18px;
    margin-right: 4px;
}
.ame-tree-list__icon {
    color: var(--ame-text-muted, #8b9e9e);
    margin-right: 6px;
}
.ame-datagrid__row--selected .ame-tree-list__icon { color: inherit; }
.ame-tree-list__title { vertical-align: middle; }

/* ---------- Paginator ---------- */
.ame-paginator {
    display: flex;
    justify-content: space-between;
    align-items: center;
    background: var(--ame-paginator-bg);
    padding: 8px var(--ame-grid-cell-padding-x);
    border: 1px solid var(--ame-grid-border-color);
    border-top: none;
    font-size: var(--ame-form-font-size);
    gap: 12px;
}

.ame-paginator__pagesize {
    display: flex;
    align-items: center;
    gap: 8px;
}

.ame-paginator__pagesize select {
    height: var(--ame-form-height-sm);
    padding: 0 6px;
    background: var(--ame-form-bg);
    border: 1px solid var(--ame-form-border-color);
    border-radius: 3px;
    font-size: inherit;
    color: var(--ame-form-fg);
}

.ame-paginator__nav {
    display: flex;
    align-items: center;
    gap: 6px;
}

.ame-paginator__indicator {
    color: var(--ame-grid-empty-fg);
    margin: 0 8px;
    white-space: nowrap;
}

/* ============================================================
   Sub-project C4 — navigation tabs
   (tokens defined in _tokens.designer.css)
   ============================================================ */

.ame-tabs {
    display: flex;
    flex-direction: column;
}

.ame-tabs__list {
    display: flex;
    flex-wrap: wrap;
    align-items: stretch;
    border-bottom: 1px solid var(--ame-tabs-border-color);
    gap: 0;
    margin: 0;
    padding: 0;
    list-style: none;
}

/* Holds a tab and its optional close (X); the X is a sibling button so it can't nest in the
   tab button. The wrap adds no chrome of its own, so a non-closable tab is unchanged. */
.ame-tabs__tab-wrap {
    display: inline-flex;
    align-items: stretch;
}

.ame-tabs__tab {
    background: transparent;
    border: none;
    border-bottom: var(--ame-tabs-active-border-width) solid transparent;
    padding: var(--ame-tabs-tab-padding-y) var(--ame-tabs-tab-padding-x);
    color: var(--ame-tabs-tab-fg);
    font-size: var(--ame-form-font-size);
    font-family: inherit;
    cursor: pointer;
    display: inline-flex;
    align-items: center;
    gap: 6px;
    margin-bottom: -1px;
    transition: color var(--ame-anim-duration-base) var(--ame-anim-easing),
                border-color var(--ame-anim-duration-base) var(--ame-anim-easing);
}

/* Closable tabs shed the right padding so the X tucks in tight against the label. */
.ame-tabs__tab-wrap--closable .ame-tabs__tab {
    padding-right: 4px;
}

.ame-tabs__tab:hover:not(:disabled) {
    color: var(--ame-tabs-tab-fg-active);
}

.ame-tabs__tab:focus-visible {
    outline: 2px solid var(--ame-form-border-focus-color);
    outline-offset: -2px;
}

.ame-tabs__tab[aria-selected="true"] {
    color: var(--ame-tabs-tab-fg-active);
    border-bottom-color: var(--ame-tabs-active-color);
    font-weight: 600;
}

.ame-tabs__tab:disabled {
    opacity: 0.5;
    cursor: not-allowed;
}

/* ---------- Close (X) button ---------- */
.ame-tabs__close {
    background: transparent;
    border: none;
    border-bottom: var(--ame-tabs-active-border-width) solid transparent;
    margin-bottom: -1px;
    padding: 0 var(--ame-tabs-tab-padding-x) 0 2px;
    display: inline-flex;
    align-items: center;
    color: var(--ame-text-muted, var(--rz-text-tertiary-color, #8b9e9e));
    cursor: pointer;
    transition: color var(--ame-anim-duration-base) var(--ame-anim-easing);
}

.ame-tabs__close:hover {
    color: var(--rz-danger, #ef4444);
}

.ame-tabs__close:focus-visible {
    outline: 2px solid var(--ame-form-border-focus-color);
    outline-offset: -2px;
}

/* ---------- Add (+) button ---------- */
.ame-tabs__add {
    background: transparent;
    border: none;
    border-bottom: var(--ame-tabs-active-border-width) solid transparent;
    margin-bottom: -1px;
    padding: var(--ame-tabs-tab-padding-y) var(--ame-tabs-tab-padding-x);
    display: inline-flex;
    align-items: center;
    color: var(--ame-text-muted, var(--rz-text-tertiary-color, #8b9e9e));
    cursor: pointer;
    transition: color var(--ame-anim-duration-base) var(--ame-anim-easing);
}

.ame-tabs__add:hover {
    color: var(--rz-primary, #4361ee);
}

.ame-tabs__add:focus-visible {
    outline: 2px solid var(--ame-form-border-focus-color);
    outline-offset: -2px;
}

.ame-tabs__panel {
    padding: 16px 0;
}

/* ============================================================
   Sub-project C5 — services (notification + dialog hosts)
   (tokens defined in _tokens.designer.css)
   ============================================================ */

/* ---------- Notification host ---------- */
.ame-notification-host {
    position: fixed;
    top: 16px;
    right: 16px;
    z-index: 10000;
    display: flex;
    flex-direction: column;
    gap: 8px;
    pointer-events: none;
    max-width: 420px;
}

.ame-notification {
    pointer-events: auto;
    display: flex;
    align-items: flex-start;
    gap: 12px;
    background: var(--ame-notification-bg);
    color: var(--ame-notification-fg);
    border: 1px solid var(--ame-notification-border-info);
    border-left-width: 4px;
    border-radius: 4px;
    padding: 12px 16px;
    box-shadow: var(--ame-notification-shadow);
    font-size: var(--ame-form-font-size);
}

/* Wave 18.5 entry motion, mirroring the desktop hosts (Opacity 0 to 1 + an 8px drop-in over
   AMEMotion.StandardMs). Gated on the user's motion preference; the host marks each toast
   with a per-id key so the entrance replays only for genuinely new toasts. */
@media (prefers-reduced-motion: no-preference) {
    .ame-notification {
        animation: ame-notification-in var(--ame-anim-duration-base, 220ms) var(--ame-anim-easing, cubic-bezier(0.2, 0, 0, 1)) both;
    }
}
@keyframes ame-notification-in { from { opacity: 0; transform: translateY(-8px); } to { opacity: 1; transform: translateY(0); } }

.ame-notification--info    { border-color: var(--ame-notification-border-info); }
.ame-notification--success { border-color: var(--ame-notification-border-success); }
.ame-notification--warning { border-color: var(--ame-notification-border-warning); }
.ame-notification--danger  { border-color: var(--ame-notification-border-danger); }

.ame-notification__icon {
    flex-shrink: 0;
    color: var(--ame-notification-border-info);
}
.ame-notification--success .ame-notification__icon { color: var(--ame-notification-border-success); }
.ame-notification--warning .ame-notification__icon { color: var(--ame-notification-border-warning); }
.ame-notification--danger  .ame-notification__icon { color: var(--ame-notification-border-danger); }

.ame-notification__content { flex: 1; min-width: 0; }
.ame-notification__summary { font-weight: 600; }
.ame-notification__detail { opacity: 0.85; margin-top: 2px; font-size: 0.95em; }

.ame-notification__close {
    flex-shrink: 0;
    background: transparent;
    border: none;
    color: inherit;
    cursor: pointer;
    padding: 2px;
    border-radius: 3px;
}
.ame-notification__close:hover { background: var(--ame-hover, rgba(0,0,0,0.05)); }

/* ---------- Dialog host ---------- */
.ame-dialog-overlay {
    position: fixed;
    inset: 0;
    background: var(--ame-dialog-overlay-bg);
    display: flex;
    align-items: center;
    justify-content: center;
    z-index: 11000;
    padding: 16px;
}

.ame-dialog {
    background: var(--ame-dialog-bg);
    color: var(--ame-dialog-fg);
    border: 1px solid var(--ame-dialog-border-color);
    border-radius: 6px;
    box-shadow: var(--ame-dialog-shadow);
    min-width: 340px;
    max-width: min(720px, 100%);
    max-height: 85vh;
    display: flex;
    flex-direction: column;
    overflow: hidden;
}

/* Wave 18.5 entry motion, mirroring the desktop hosts (Opacity 0 to 1 + a 0.96 to 1 scale over
   AMEMotion.StandardMs); motion-preference gated. The host keys each card by its request id so
   the entrance replays only for a genuinely new dialog, never for stack survivors. */
@media (prefers-reduced-motion: no-preference) {
    .ame-dialog {
        animation: ame-dialog-in var(--ame-anim-duration-base, 220ms) var(--ame-anim-easing, cubic-bezier(0.2, 0, 0, 1)) both;
    }
}
@keyframes ame-dialog-in { from { opacity: 0; transform: scale(0.96); } to { opacity: 1; transform: scale(1); } }

.ame-dialog__header {
    display: flex;
    align-items: center;
    justify-content: space-between;
    padding: 12px 16px;
    border-bottom: 1px solid var(--ame-dialog-border-color);
}

.ame-dialog__title {
    margin: 0;
    font-size: 1rem;
    font-weight: 600;
    color: inherit;
}

.ame-dialog__close {
    background: transparent;
    border: none;
    cursor: pointer;
    padding: 4px;
    border-radius: 3px;
    color: inherit;
}
.ame-dialog__close:hover { background: var(--ame-hover, rgba(0,0,0,0.05)); }

.ame-dialog__body {
    padding: 16px;
    overflow-y: auto;
    flex: 1;
}

.ame-dialog__footer {
    display: flex;
    justify-content: flex-end;
    gap: 8px;
    padding: 12px 16px;
    border-top: 1px solid var(--ame-dialog-border-color);
}

/* ---------- Bulk confirm dialog body ----------
   Body content for AMEBulkConfirmDialog (the footer reuses .ame-dialog__footer).
   The target list and overflow footer use the muted text token so the names read
   as secondary to the title; the detail line keeps the dialog foreground. */
.ame-bulk-confirm__detail {
    margin: 0 0 12px;
    color: var(--ame-dialog-fg);
}
.ame-bulk-confirm__targets {
    margin: 0;
    padding-left: 20px;
    display: flex;
    flex-direction: column;
    gap: 2px;
}
.ame-bulk-confirm__target {
    color: var(--ame-dialog-fg);
    word-break: break-word;
}
.ame-bulk-confirm__overflow {
    margin: 8px 0 0;
    color: var(--rz-text-tertiary-color, #8B9E9E);
    font-size: var(--ame-form-font-size-sm);
}

/* ---------- DataGrid loading overlay (C3.5) ---------- */
.ame-datagrid__wrap {
    position: relative;
}
.ame-datagrid__loading-overlay {
    position: absolute;
    inset: 0;
    display: flex;
    align-items: center;
    justify-content: center;
    background: color-mix(in srgb, var(--ame-grid-bg, #ffffff) 70%, transparent);
    z-index: 10;
}

/* ---------- Radial gauge ---------- */
.ame-radial-gauge {
    display: inline-block;
    vertical-align: middle;
}

/* ---------- Donut chart ---------- */
.ame-donut-chart {
    display: inline-block;
    vertical-align: middle;
}

/* ---------- Help mini-demo (SP-4 wave) ----------
   Collapsible <details> wrapper rendered inside HelpPanel.razor
   below a topic's Overview body. The body slot hosts one of the
   four BlazorControls/Help demo components (TopicWildcardMatcher,
   NCalcExpressionSandbox, AclRuleTester, PayloadJsonValidator).
*/
.ame-mini-demo {
    margin: 16px 0;
    border: 1px solid var(--ame-border, #d0d7de);
    border-radius: 6px;
    background: var(--ame-surface-2, #f6f8fa);
    overflow: hidden;
}

.ame-mini-demo__summary {
    display: flex;
    align-items: center;
    gap: 8px;
    padding: 10px 12px;
    cursor: pointer;
    font-weight: 600;
    color: var(--ame-text, inherit);
    list-style: none;
    user-select: none;
}

.ame-mini-demo__summary::-webkit-details-marker { display: none; }

.ame-mini-demo__title { flex: 1; }

.ame-mini-demo__body {
    padding: 12px;
    border-top: 1px solid var(--ame-border, #d0d7de);
    background: var(--ame-surface, #ffffff);
}

.ame-demo {
    display: flex;
    flex-direction: column;
    gap: 10px;
}

.ame-demo__row {
    display: flex;
    gap: 12px;
    flex-wrap: wrap;
}

.ame-demo__field {
    display: flex;
    flex-direction: column;
    gap: 4px;
    flex: 1 1 200px;
    min-width: 0;
}

.ame-demo__label {
    font-size: 12px;
    font-weight: 600;
    color: var(--ame-text-muted, #57606a);
}

.ame-demo__hint {
    margin: 4px 0 0;
    font-size: 12px;
    color: var(--ame-text-muted, #57606a);
}

.ame-demo__hint code {
    padding: 1px 4px;
    background: var(--ame-surface-2, #f6f8fa);
    border-radius: 3px;
    font-family: var(--ame-mono, monospace);
}

.ame-demo__errors {
    margin: 4px 0 0;
    padding-left: 20px;
    color: var(--ame-danger, #d1242f);
    font-size: 13px;
}

/* ============================================================
   W3 — Sidebar motion (2026-05-13).
   Honours prefers-reduced-motion. Uses only existing tokens —
   --ame-sidebar-accent / --ame-hover / --ame-anim-* — so the
   W5 colour-drift test stays green (no new hex / rgba literals).
   ============================================================ */
.ame-nav-item {
    transition: background-color 80ms linear, color 80ms linear;
}
/* SP-2 Task 5 (2026-05-19): retired the .ame-nav-item.active::before
   left-accent bar. It belonged to the legacy single-tier nav item (no
   page renders AMENavItem anymore); the two-tier surfaces use the
   single sliding .ame-nav-rail__tick / .ame-nav-panel__indicator
   instead. The icon-pop keyframes below are kept (harmless, unused). */
.ame-nav-item.active .ame-icon {
    animation: ame-icon-pop 200ms ease-out;
}
@keyframes ame-icon-pop {
    0%   { transform: scale(1);    }
    50%  { transform: scale(1.08); }
    100% { transform: scale(1);    }
}

/* Pin button micro-interaction. .ame-panel-group__chevron already
   has the rotate transition declared above; we only add the pin-btn
   motion + the explicit "pinned" colour state here. */
.ame-pin-btn {
    transition: transform var(--ame-anim-duration-fast) var(--ame-anim-easing),
                color var(--ame-anim-duration-fast) var(--ame-anim-easing);
}
.ame-pin-btn.pinned {
    color: var(--ame-sidebar-accent);
}
.ame-pin-btn:active {
    transform: scale(0.85);
}

@media (prefers-reduced-motion: reduce) {
    .ame-nav-item,
    .ame-nav-item.active::before,
    .ame-panel-group__chevron,
    .ame-pin-btn,
    .ame-sidebar { transition: none; }
    .ame-nav-item.active .ame-icon { animation: none; }
}

/* ============================================================
   Loading + empty-state primitives — AMESkeleton + AMEEmptyState
   (2026-06-11). Backs the AMESkeleton.razor / AMEEmptyState.razor
   primitives (which previously rendered unstyled) and mirrors the
   desktop defaults committed alongside (Avalonia/WPF AMESkeleton +
   AMEEmptyState themes). Token-driven; raw px only for the fixed
   design dimensions (bar/card/avatar sizes, shimmer timing).
   ============================================================ */

/* ---------- Skeleton ----------
   Muted rounded block that clips a left-to-right shimmer sweep — the
   web twin of the desktop AMESkeleton (base = muted surface, highlight =
   a faded-white gradient band translated forever). The base fill uses the
   same muted-surface token as the switch track / light badge; the shimmer
   highlight mirrors the desktop gradient mid-stop (#B3FFFFFF). */
.ame-skeleton {
    display: block;
    position: relative;
    overflow: hidden;
    background: var(--rz-base-200, #e5e7eb);
    border-radius: 4px;
}

/* Shimmer band: an absolutely-positioned transparent -> light -> transparent
   gradient swept across the track. ~1.4s linear loop, matching the desktop
   TranslateTransform.X animation (the design timing, like the spinner's 1.2s). */
.ame-skeleton::after {
    content: "";
    position: absolute;
    inset: 0;
    background: linear-gradient(
        90deg,
        transparent 0%,
        rgba(255, 255, 255, 0.7) 50%,
        transparent 100%);
    transform: translateX(-100%);
}

@media (prefers-reduced-motion: no-preference) {
    .ame-skeleton::after {
        animation: ame-skeleton-shimmer 1.4s linear infinite;
    }
}

@keyframes ame-skeleton-shimmer {
    0%   { transform: translateX(-100%); }
    100% { transform: translateX(100%); }
}

@media (prefers-reduced-motion: reduce) {
    .ame-skeleton::after { animation: none; }
}

/* Variant default sizes — mirror the desktop ^[Variant=...] setters:
   text = one line, card = block, circle = round avatar. A consumer's
   Width/Height inline override (merged in the razor) wins over these. */
.ame-skeleton-text   { height: 14px; width: 80%; }
.ame-skeleton-card   { height: 80px; width: 100%; }
.ame-skeleton-circle { height: 40px; width: 40px; border-radius: 50%; }

/* ---------- Empty state ----------
   Centered illustration + title + optional description + action slot.
   The art (inline SVG illustrations, or a 64px AMEIcon) draws in
   currentColor, so colouring the __art container in the muted-text token
   tints the whole scene — the web twin of the desktop control's muted
   Foreground. The --no-data / --no-results / --error modifier classes are
   reserved for per-variant tweaks; the shared layout below covers all three. */
.ame-empty-state {
    display: flex;
    flex-direction: column;
    align-items: center;
    text-align: center;
    max-width: 420px;
    margin: 0 auto;
    padding: 24px;
}

.ame-empty-state__art {
    color: var(--ame-text-muted, var(--rz-text-tertiary-color, #8b9e9e));
    margin-bottom: 8px;
}

.ame-empty-state__icon {
    display: block;
}

.ame-empty-state__title {
    margin: 4px 0;
    font-size: 18px;
    font-weight: 600;
    color: var(--ame-form-fg);
}

.ame-empty-state__description {
    margin: 4px 0 0;
    font-size: 14px;
    color: var(--ame-text-muted, var(--rz-text-tertiary-color, #8b9e9e));
}

.ame-empty-state__action {
    margin-top: 16px;
}


/* ============================================================
   Wizard (AMEWizard / AMEWizardStep)
   The wizard-* structural classes were historically expected in the
   consuming app's app.css (see the AMEWizard.razor header); shipping
   them here makes the primitive self-contained. Values mirror the
   desktop AMEWizard themes: 28px numbered circles, primary for the
   current/completed steps, muted future steps, border-token connectors.
   ============================================================ */

.ame-wizard {
    display: flex;
    flex-direction: column;
}

.wizard-stepper {
    display: flex;
    align-items: center;
    gap: 8px;
    margin-bottom: 16px;
}

.wizard-step {
    display: inline-flex;
    align-items: center;
    gap: 8px;
    background: transparent;
    border: none;
    padding: 4px 6px;
    font-family: inherit;
    font-size: var(--ame-form-font-size, 14px);
    color: var(--ame-text-muted, var(--rz-text-tertiary-color, #8b9e9e));
    cursor: pointer;
}

.wizard-step:disabled {
    cursor: default;
}

.wizard-step-number {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 28px;
    height: 28px;
    border-radius: 50%;
    border: 1px solid var(--rz-border, #e5e7eb);
    background: var(--ame-card-bg, #ffffff);
    font-size: 13px;
    font-weight: 600;
}

.wizard-step.active .wizard-step-number,
.wizard-step.completed .wizard-step-number {
    background: var(--ame-button-bg-primary, var(--rz-primary, #4361ee));
    border-color: var(--ame-button-bg-primary, var(--rz-primary, #4361ee));
    color: #ffffff;
}

.wizard-step.active {
    color: var(--ame-form-fg, #1f2937);
    font-weight: 600;
}

.wizard-step.completed {
    color: var(--ame-form-fg, #1f2937);
}

.wizard-step-connector {
    width: 32px;
    height: 1px;
    background: var(--rz-border, #e5e7eb);
}

.wizard-step-connector.completed {
    background: var(--ame-button-bg-primary, var(--rz-primary, #4361ee));
}

.wizard-step-content {
    padding: 8px 0 16px;
}

.ame-wizard__footer {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 8px;
    border-top: 1px solid var(--rz-border, #e5e7eb);
    padding-top: 12px;
}

/* ============================================================
   Wave 5 batch 1 — Expander / Accordion, Breadcrumb,
   Chip / ChipList, Slider (tokens in _tokens.designer.css)
   ============================================================ */

/* ---------- Expander ---------- */
.ame-expander {
    background: var(--ame-card-bg, #ffffff);
    border: 1px solid var(--ame-form-border-color, #e5e7eb);
    border-radius: var(--ame-control-corner-radius, 6px);
    overflow: hidden;
}
.ame-expander__header {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 8px;
    width: 100%;
    padding: 10px 12px;
    background: transparent;
    border: none;
    color: var(--ame-form-fg, #1f2937);
    font-family: inherit;
    font-size: 14px;
    font-weight: 600;
    text-align: left;
    cursor: pointer;
}
.ame-expander__header:hover:not(:disabled) {
    background: var(--ame-hover, rgba(0, 0, 0, 0.03));
}
.ame-expander__header:focus-visible {
    outline: 2px solid var(--ame-form-border-focus-color);
    outline-offset: -2px;
}
.ame-expander__header:disabled {
    opacity: 0.5;
    cursor: not-allowed;
}
.ame-expander__title { flex: 1 1 auto; }
.ame-expander__chevron {
    flex: 0 0 auto;
    color: var(--ame-text-muted, #8b9e9e);
    transition: transform var(--ame-anim-duration-base) var(--ame-anim-easing);
}
.ame-expander__chevron--open { transform: rotate(180deg); }
.ame-expander__body {
    padding: 12px 8px;
    border-top: 1px solid var(--ame-form-border-color, #e5e7eb);
}

/* ---------- Accordion ---------- */
.ame-accordion {
    display: flex;
    flex-direction: column;
    gap: 8px;
}

/* ---------- Breadcrumb ---------- */
.ame-breadcrumb__list {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 4px;
    margin: 0;
    padding: 0;
    list-style: none;
    font-size: var(--ame-form-font-size, 14px);
}
.ame-breadcrumb__item {
    display: inline-flex;
    align-items: center;
    gap: 4px;
}
.ame-breadcrumb__link {
    display: inline-flex;
    align-items: center;
    gap: 4px;
    background: transparent;
    border: none;
    padding: 0;
    font: inherit;
    color: var(--ame-text-muted, #8b9e9e);
    cursor: pointer;
    transition: color var(--ame-anim-duration-base) var(--ame-anim-easing);
}
.ame-breadcrumb__link:hover { color: var(--ame-button-bg-primary, #4361ee); }
.ame-breadcrumb__link:focus-visible {
    outline: 2px solid var(--ame-form-border-focus-color);
    outline-offset: 2px;
    border-radius: 2px;
}
.ame-breadcrumb__current {
    display: inline-flex;
    align-items: center;
    gap: 4px;
    color: var(--ame-form-fg, #1f2937);
    font-weight: 600;
}
.ame-breadcrumb__separator { color: var(--ame-text-muted, #8b9e9e); }

/* ---------- Chip ---------- */
.ame-chip {
    display: inline-flex;
    align-items: center;
    gap: 6px;
    height: 28px;
    padding: 0 12px;
    border-radius: 999px;
    background: var(--ame-surface-2, #f3f4f6);
    color: var(--ame-form-fg, #1f2937);
    border: 1px solid var(--ame-form-border-color, #e5e7eb);
    font-size: var(--ame-form-font-size-sm, 12px);
    line-height: 1;
    white-space: nowrap;
    box-sizing: border-box;
    transition: background-color var(--ame-anim-duration-base) var(--ame-anim-easing),
                color var(--ame-anim-duration-base) var(--ame-anim-easing),
                border-color var(--ame-anim-duration-base) var(--ame-anim-easing);
}
.ame-chip[role="button"] { cursor: pointer; }
.ame-chip[role="button"]:focus-visible {
    outline: 2px solid var(--ame-form-border-focus-color);
    outline-offset: 1px;
}
.ame-chip--selected {
    background: color-mix(in srgb, var(--ame-button-bg-primary, #4361ee) 12%, transparent);
    color: var(--ame-button-bg-primary, #4361ee);
    border-color: var(--ame-button-bg-primary, #4361ee);
}
.ame-chip__icon { flex: 0 0 auto; }
.ame-chip__label { flex: 0 1 auto; }
.ame-chip__remove {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    flex: 0 0 auto;
    width: 16px;
    height: 16px;
    margin-right: -4px;
    padding: 0;
    background: transparent;
    border: none;
    border-radius: 999px;
    color: inherit;
    cursor: pointer;
    opacity: 0.7;
    transition: opacity var(--ame-anim-duration-base) var(--ame-anim-easing),
                background-color var(--ame-anim-duration-base) var(--ame-anim-easing);
}
.ame-chip__remove:hover {
    opacity: 1;
    background: var(--ame-hover, rgba(0, 0, 0, 0.08));
}
.ame-chip__remove:focus-visible {
    outline: 2px solid var(--ame-form-border-focus-color);
    outline-offset: 1px;
}

/* ---------- ChipList ---------- */
.ame-chip-list {
    display: flex;
    flex-wrap: wrap;
    gap: 8px;
}

/* ---------- Slider ---------- */
.ame-slider {
    width: 100%;
    height: 16px;
    background: transparent;
    cursor: pointer;
    accent-color: var(--ame-button-bg-primary, #4361ee);
    -webkit-appearance: none;
    appearance: none;
}
.ame-slider:focus-visible { outline: none; }
/* WebKit track + thumb */
.ame-slider::-webkit-slider-runnable-track {
    height: 4px;
    border-radius: 999px;
    background: var(--ame-form-border-color, #e5e7eb);
}
.ame-slider::-webkit-slider-thumb {
    -webkit-appearance: none;
    appearance: none;
    width: 16px;
    height: 16px;
    margin-top: -6px;
    border-radius: 50%;
    background: var(--ame-card-bg, #ffffff);
    border: 2px solid var(--ame-button-bg-primary, #4361ee);
    transition: background-color var(--ame-anim-duration-base) var(--ame-anim-easing);
}
.ame-slider:hover::-webkit-slider-thumb {
    background: var(--ame-button-bg-primary, #4361ee);
}
.ame-slider:focus-visible::-webkit-slider-thumb {
    box-shadow: 0 0 0 3px color-mix(in srgb, var(--ame-button-bg-primary, #4361ee) 30%, transparent);
}
/* Firefox track + thumb */
.ame-slider::-moz-range-track {
    height: 4px;
    border-radius: 999px;
    background: var(--ame-form-border-color, #e5e7eb);
}
.ame-slider::-moz-range-progress {
    height: 4px;
    border-radius: 999px;
    background: var(--ame-button-bg-primary, #4361ee);
}
.ame-slider::-moz-range-thumb {
    width: 16px;
    height: 16px;
    border-radius: 50%;
    background: var(--ame-card-bg, #ffffff);
    border: 2px solid var(--ame-button-bg-primary, #4361ee);
    transition: background-color var(--ame-anim-duration-base) var(--ame-anim-easing);
}
.ame-slider:hover::-moz-range-thumb {
    background: var(--ame-button-bg-primary, #4361ee);
}
.ame-slider:focus-visible::-moz-range-thumb {
    box-shadow: 0 0 0 3px color-mix(in srgb, var(--ame-button-bg-primary, #4361ee) 30%, transparent);
}
.ame-slider:disabled {
    opacity: 0.5;
    cursor: not-allowed;
}

/* ============================================================
   Wave 5 batch 2 — TreeView, Menu / ContextMenu, SplitPane,
   Drawer, Popover (tokens in _tokens.designer.css).
   PrimarySoft = color-mix(Primary 12% / transparent) — the
   established soft-accent recipe used across the suite.
   ============================================================ */

/* ---------- TreeView ----------
   Nested ul/li tree. Rows span full width so hover/selected fills
   edge-to-edge; the 20px-per-level indent is applied as row
   padding-left (computed in AMETreeNodeView). */
.ame-treeview,
.ame-treeview__group {
    list-style: none;
    margin: 0;
    padding: 0;
}
.ame-treeview {
    font-size: var(--ame-form-font-size, 14px);
    color: var(--ame-form-fg, #1f2937);
    user-select: none;
}
.ame-treeview__item { display: block; }
.ame-treeview__row {
    display: flex;
    align-items: center;
    gap: 6px;
    height: 30px;
    padding-right: 10px;
    border-radius: var(--ame-control-corner-radius, 6px);
    cursor: pointer;
    transition: background-color var(--ame-anim-duration-fast, 150ms) var(--ame-anim-easing, ease),
                color var(--ame-anim-duration-fast, 150ms) var(--ame-anim-easing, ease);
}
.ame-treeview__row:hover {
    background: var(--ame-surface-2, #f3f4f6);
}
.ame-treeview__row--selected,
.ame-treeview__row--selected:hover {
    background: color-mix(in srgb, var(--ame-button-bg-primary, #4361ee) 12%, transparent);
    color: var(--ame-button-bg-primary, #4361ee);
}
/* Roving keyboard focus (the WAI-ARIA tree pattern): the tree is the single tab stop, so its own
   outline is suppressed and the focus ring paints on the ACTIVE row instead — only while the tree holds
   focus, and independent of the selected-row wash. Uses the shared focus token (as the chevron does). */
.ame-treeview:focus {
    outline: none;
}
.ame-treeview:focus .ame-treeview__row--active {
    outline: 2px solid var(--ame-form-border-focus-color, var(--rz-primary, #4361ee));
    outline-offset: -2px;
}
.ame-treeview__chevron {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    flex: 0 0 auto;
    width: 18px;
    height: 18px;
    padding: 0;
    background: transparent;
    border: none;
    color: var(--ame-text-muted, #8b9e9e);
    cursor: pointer;
    border-radius: 3px;
    transition: transform var(--ame-anim-duration-base) var(--ame-anim-easing);
}
.ame-treeview__chevron:hover { background: var(--ame-hover, rgba(0, 0, 0, 0.06)); }
.ame-treeview__chevron:focus-visible {
    outline: 2px solid var(--ame-form-border-focus-color);
    outline-offset: -2px;
}
.ame-treeview__chevron--open { transform: rotate(90deg); }
.ame-treeview__chevron-spacer { display: inline-block; flex: 0 0 auto; width: 18px; height: 18px; }
.ame-treeview__icon { flex: 0 0 auto; color: var(--ame-text-muted, #8b9e9e); }
.ame-treeview__row--selected .ame-treeview__icon { color: inherit; }
.ame-treeview__title { flex: 1 1 auto; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

/* ---------- Menu (bar) + dropdown card ---------- */
.ame-menu {
    display: flex;
    align-items: stretch;
    gap: 2px;
    background: var(--ame-card-bg, #ffffff);
    border-bottom: 1px solid var(--ame-form-border-color, #e5e7eb);
    font-size: var(--ame-form-font-size, 14px);
    color: var(--ame-form-fg, #1f2937);
    user-select: none;
}
.ame-menu__group { position: relative; display: flex; }
.ame-menu__top {
    display: inline-flex;
    align-items: center;
    gap: 6px;
    height: 34px;
    padding: 0 12px;
    background: transparent;
    border: none;
    color: inherit;
    font: inherit;
    cursor: pointer;
    transition: background-color var(--ame-anim-duration-fast, 150ms) var(--ame-anim-easing, ease);
}
.ame-menu__top:hover:not(:disabled),
.ame-menu__top--open { background: var(--ame-surface-2, #f3f4f6); }
.ame-menu__top:disabled { opacity: 0.5; cursor: not-allowed; }

/* Dropdown card — opens below its top-level button; nested cards flank to the side. */
.ame-menu__dropdown {
    position: absolute;
    top: 100%;
    left: 0;
    z-index: 1200;
    min-width: 200px;
    padding: 4px;
    background: var(--ame-card-bg, #ffffff);
    border: 1px solid var(--ame-card-border-color, #e5e7eb);
    border-radius: var(--ame-control-corner-radius, 6px);
    box-shadow: var(--ame-card-shadow-lg, 0 8px 24px rgba(0, 0, 0, 0.14));
    display: flex;
    flex-direction: column;
    gap: 1px;
}
.ame-menu__submenu { position: relative; }
.ame-menu__dropdown--nested {
    top: -4px;
    left: 100%;
    /* Hidden until the parent submenu row is hovered. */
    display: none;
}
.ame-menu__submenu:hover > .ame-menu__dropdown--nested,
.ame-menu__submenu:focus-within > .ame-menu__dropdown--nested {
    display: flex;
}

/* Menu rows (shared by AMEMenu dropdown + AMEContextMenu card). */
.ame-menu__item {
    display: flex;
    align-items: center;
    gap: 8px;
    width: 100%;
    padding: 6px 10px;
    background: transparent;
    border: none;
    border-radius: 4px;
    color: var(--ame-form-fg, #1f2937);
    font: inherit;
    text-align: left;
    white-space: nowrap;
    cursor: pointer;
    transition: background-color var(--ame-anim-duration-fast, 150ms) var(--ame-anim-easing, ease);
}
.ame-menu__item:hover:not(:disabled),
.ame-menu__item:focus-visible {
    background: var(--ame-surface-2, #f3f4f6);
    outline: none;
}
.ame-menu__item:disabled { opacity: 0.5; cursor: not-allowed; }
.ame-menu__item-icon { flex: 0 0 auto; width: 16px; height: 16px; color: var(--ame-text-muted, #8b9e9e); }
.ame-menu__item-icon--empty { display: inline-block; }
.ame-menu__item-label { flex: 1 1 auto; }
/* Shortcut: right-aligned, muted. */
.ame-menu__item-shortcut {
    flex: 0 0 auto;
    margin-left: 16px;
    color: var(--ame-text-muted, #8b9e9e);
    font-size: var(--ame-form-font-size-sm, 12px);
    font-variant-numeric: tabular-nums;
}
.ame-menu__item-arrow { flex: 0 0 auto; margin-left: 16px; color: var(--ame-text-muted, #8b9e9e); }
.ame-menu__separator {
    height: 1px;
    margin: 4px 6px;
    background: var(--ame-form-border-color, #e5e7eb);
}
.ame-menu__backdrop {
    position: fixed;
    inset: 0;
    z-index: 1150;
    background: transparent;
}

/* ---------- ContextMenu (positioned card) ---------- */
.ame-context-menu-host { position: relative; }
.ame-context-menu {
    position: absolute;
    z-index: 1200;
    min-width: 180px;
    padding: 4px;
    background: var(--ame-card-bg, #ffffff);
    border: 1px solid var(--ame-card-border-color, #e5e7eb);
    border-radius: var(--ame-control-corner-radius, 6px);
    box-shadow: var(--ame-card-shadow-lg, 0 8px 24px rgba(0, 0, 0, 0.14));
    display: flex;
    flex-direction: column;
    gap: 1px;
}
.ame-context-menu__backdrop {
    position: fixed;
    inset: 0;
    z-index: 1190;
    background: transparent;
}

/* ---------- SplitPane ---------- */
.ame-splitpane {
    display: flex;
    width: 100%;
    height: 100%;
    min-height: 0;
    overflow: hidden;
}
.ame-splitpane--horizontal { flex-direction: row; }
.ame-splitpane--vertical { flex-direction: column; }
.ame-splitpane__pane {
    overflow: auto;
    min-width: 0;
    min-height: 0;
}
.ame-splitpane__pane--first {
    flex: 0 0 var(--ame-splitpane-first, 50%);
}
.ame-splitpane__pane--second {
    flex: 1 1 0;
}
/* MinPaneSize enforcement — keeps either pane from collapsing past the token. */
.ame-splitpane--horizontal > .ame-splitpane__pane { min-width: var(--ame-splitpane-min, 80px); }
.ame-splitpane--vertical > .ame-splitpane__pane { min-height: var(--ame-splitpane-min, 80px); }
/* 6px hairline divider, Border bg, hover Primary. */
.ame-splitpane__divider {
    flex: 0 0 6px;
    background: var(--ame-form-border-color, #e5e7eb);
    transition: background-color var(--ame-anim-duration-fast, 150ms) var(--ame-anim-easing, ease);
    touch-action: none;
}
.ame-splitpane--horizontal > .ame-splitpane__divider { cursor: col-resize; }
.ame-splitpane--vertical > .ame-splitpane__divider { cursor: row-resize; }
.ame-splitpane__divider:hover,
.ame-splitpane--dragging .ame-splitpane__divider {
    background: var(--ame-button-bg-primary, #4361ee);
}
.ame-splitpane__divider:focus-visible {
    outline: 2px solid var(--ame-button-bg-primary, #4361ee);
    outline-offset: -2px;
}

/* ---------- Drawer ----------
   Root fills its nearest positioned ancestor; scrim + edge-pinned panel.
   Closed = scrim transparent/non-interactive + panel translated off its edge
   and inert. CSS transforms drive the slide. */
.ame-drawer {
    position: absolute;
    inset: 0;
    z-index: 1000;
    pointer-events: none;
    /* Clip the off-edge panel so a closed drawer never bleeds outside its host. */
    overflow: hidden;
}
.ame-drawer__scrim {
    position: absolute;
    inset: 0;
    background: var(--ame-dialog-overlay-bg, rgba(0, 0, 0, 0.4));
    opacity: 0;
    pointer-events: none;
    transition: opacity var(--ame-anim-duration-base) var(--ame-anim-easing);
}
.ame-drawer--open { pointer-events: auto; }
.ame-drawer--open .ame-drawer__scrim { opacity: 1; pointer-events: auto; }
.ame-drawer__panel {
    position: absolute;
    background: var(--ame-card-bg, #ffffff);
    color: var(--ame-form-fg, #1f2937);
    box-shadow: var(--ame-card-shadow-lg, 0 8px 24px rgba(0, 0, 0, 0.14));
    overflow: auto;
    transition: transform var(--ame-anim-duration-base) var(--ame-anim-easing);
    pointer-events: auto;
}
/* Edge geometry + closed off-screen transform; --open resets to identity. */
.ame-drawer--left .ame-drawer__panel {
    top: 0; bottom: 0; left: 0;
    border-right: 1px solid var(--ame-card-border-color, #e5e7eb);
    transform: translateX(-100%);
}
.ame-drawer--right .ame-drawer__panel {
    top: 0; bottom: 0; right: 0;
    border-left: 1px solid var(--ame-card-border-color, #e5e7eb);
    transform: translateX(100%);
}
.ame-drawer--top .ame-drawer__panel {
    top: 0; left: 0; right: 0;
    border-bottom: 1px solid var(--ame-card-border-color, #e5e7eb);
    transform: translateY(-100%);
}
.ame-drawer--bottom .ame-drawer__panel {
    bottom: 0; left: 0; right: 0;
    border-top: 1px solid var(--ame-card-border-color, #e5e7eb);
    transform: translateY(100%);
}
.ame-drawer--open .ame-drawer__panel { transform: none; }

/* ---------- Popover ----------
   Relative wrapper; absolutely-positioned token card per placement. */
.ame-popover { position: relative; display: inline-flex; }
.ame-popover__trigger { display: inline-flex; cursor: pointer; }
.ame-popover__backdrop {
    position: fixed;
    inset: 0;
    z-index: 1090;
    background: transparent;
}
.ame-popover__card {
    position: absolute;
    z-index: 1100;
    min-width: 180px;
    max-width: 320px;
    padding: 10px 12px;
    background: var(--ame-card-bg, #ffffff);
    color: var(--ame-form-fg, #1f2937);
    border: 1px solid var(--ame-card-border-color, #e5e7eb);
    border-radius: var(--ame-control-corner-radius, 6px);
    box-shadow: var(--ame-card-shadow-md, 0 2px 8px rgba(0, 0, 0, 0.1));
    font-size: var(--ame-form-font-size, 14px);
    animation: ame-popover-fadein var(--ame-anim-duration-fast, 150ms) var(--ame-anim-easing, ease);
}
.ame-popover__card--bottom { top: calc(100% + 8px); left: 50%; transform: translateX(-50%); }
.ame-popover__card--top { bottom: calc(100% + 8px); left: 50%; transform: translateX(-50%); }
.ame-popover__card--right { top: 50%; left: calc(100% + 8px); transform: translateY(-50%); }
.ame-popover__card--left { top: 50%; right: calc(100% + 8px); transform: translateY(-50%); }
@keyframes ame-popover-fadein {
    from { opacity: 0; }
    to { opacity: 1; }
}
@media (prefers-reduced-motion: reduce) {
    .ame-popover__card { animation: none; }
    .ame-drawer__panel,
    .ame-drawer__scrim,
    .ame-treeview__chevron,
    .ame-datagrid__expander,
    .ame-tree-list__chevron { transition: none; }
}

/* ============================================================
   Wave 5 batch 3 — TimePicker, DateRangePicker, AutoComplete,
   MultiSelect, RangeSlider, MaskedInput. Picker/input family;
   popups reuse the JS-free backdrop light-dismiss + ESC pattern
   established by Menu / Popover. Fields inherit the .ame-input
   shell; only the deltas live here.
   ============================================================ */

/* ---------- TimePicker ----------
   Native input[type=time] re-skinned to the .ame-input shell; the
   browser owns the clock chrome. The spinner/clear affordances stay
   the platform default — only sizing is normalized. */
.ame-timepicker {
    width: 100%;
    line-height: normal;
}
/* Keep the inner WebKit fields aligned to the Text token. */
.ame-timepicker::-webkit-datetime-edit { color: var(--ame-form-fg, #1f2937); }
.ame-timepicker::-webkit-calendar-picker-indicator {
    color: var(--ame-text-muted, #8b9e9e);
    cursor: pointer;
}

/* ---------- DateRangePicker ----------
   Two date fields share a row with an en-dash separator; each field
   flexes so the pair fills the container. */
.ame-daterange {
    display: inline-flex;
    align-items: center;
    gap: 8px;
    width: 100%;
}
.ame-daterange__field {
    flex: 1 1 0;
    min-width: 0;
}
.ame-daterange__separator {
    flex: 0 0 auto;
    color: var(--ame-text-muted, #8b9e9e);
    user-select: none;
}

/* ---------- AutoComplete ----------
   .ame-input field + an absolutely-positioned suggestion card. The
   transparent backdrop catches outside clicks (light-dismiss); the
   card sits above it. Rows are full-width buttons, hover = SurfaceMuted. */
.ame-autocomplete {
    position: relative;
    display: inline-block;
    width: 100%;
}
.ame-autocomplete__input { width: 100%; }
.ame-autocomplete__backdrop {
    position: fixed;
    inset: 0;
    z-index: 1090;
    background: transparent;
}
.ame-autocomplete__list {
    position: absolute;
    z-index: 1100;
    top: calc(100% + 4px);
    left: 0;
    right: 0;
    max-height: 240px;
    overflow-y: auto;
    padding: 4px;
    background: var(--ame-card-bg, #ffffff);
    border: 1px solid var(--ame-card-border-color, #e5e7eb);
    border-radius: var(--ame-control-corner-radius, 6px);
    box-shadow: var(--ame-card-shadow-md, 0 2px 8px rgba(0, 0, 0, 0.1));
    display: flex;
    flex-direction: column;
    gap: 1px;
}
.ame-autocomplete__option {
    display: block;
    width: 100%;
    text-align: left;
    padding: 6px 10px;
    background: transparent;
    border: none;
    border-radius: 4px;
    color: var(--ame-form-fg, #1f2937);
    font: inherit;
    cursor: pointer;
    transition: background-color var(--ame-anim-duration-fast, 150ms) var(--ame-anim-easing, ease);
}
.ame-autocomplete__option:hover,
.ame-autocomplete__option:focus-visible {
    background: var(--ame-surface-2, #f3f4f6);
    outline: none;
}

/* ---------- MultiSelect ----------
   Field = a wrapping flex of AMEChips + a trailing toggle; popup =
   a check-row card (same backdrop light-dismiss as AutoComplete). */
.ame-multiselect {
    position: relative;
    display: inline-block;
    width: 100%;
}
.ame-multiselect__field {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 6px;
    min-height: var(--ame-form-height, 38px);
    padding: 4px 8px;
    background: var(--ame-form-bg, #ffffff);
    border: 1px solid var(--ame-form-border-color, #e5e7eb);
    border-radius: 4px;
    box-sizing: border-box;
    cursor: pointer;
}
.ame-multiselect__field:focus-within {
    outline: 2px solid var(--ame-form-border-focus-color);
    outline-offset: 1px;
    border-color: var(--ame-form-border-focus-color);
}
.ame-multiselect__placeholder {
    color: var(--ame-text-muted, #8b9e9e);
    flex: 1 1 auto;
}
.ame-multiselect__toggle {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    flex: 0 0 auto;
    margin-left: auto;
    padding: 2px;
    background: transparent;
    border: none;
    color: var(--ame-text-muted, #8b9e9e);
    cursor: pointer;
    border-radius: 3px;
}
.ame-multiselect__toggle:disabled { opacity: 0.5; cursor: not-allowed; }
.ame-multiselect__backdrop {
    position: fixed;
    inset: 0;
    z-index: 1090;
    background: transparent;
}
.ame-multiselect__list {
    position: absolute;
    z-index: 1100;
    top: calc(100% + 4px);
    left: 0;
    right: 0;
    max-height: 240px;
    overflow-y: auto;
    padding: 4px;
    background: var(--ame-card-bg, #ffffff);
    border: 1px solid var(--ame-card-border-color, #e5e7eb);
    border-radius: var(--ame-control-corner-radius, 6px);
    box-shadow: var(--ame-card-shadow-md, 0 2px 8px rgba(0, 0, 0, 0.1));
    display: flex;
    flex-direction: column;
    gap: 1px;
}
.ame-multiselect__option {
    display: flex;
    align-items: center;
    gap: 8px;
    width: 100%;
    text-align: left;
    padding: 6px 10px;
    background: transparent;
    border: none;
    border-radius: 4px;
    color: var(--ame-form-fg, #1f2937);
    font: inherit;
    cursor: pointer;
    transition: background-color var(--ame-anim-duration-fast, 150ms) var(--ame-anim-easing, ease);
}
.ame-multiselect__option:hover,
.ame-multiselect__option:focus-visible {
    background: var(--ame-surface-2, #f3f4f6);
    outline: none;
}
.ame-multiselect__option--selected { color: var(--ame-button-bg-primary, #4361ee); }
/* Fixed-width check gutter so labels align whether or not the row is ticked. */
.ame-multiselect__check {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    flex: 0 0 auto;
    width: 16px;
    height: 16px;
    color: var(--ame-button-bg-primary, #4361ee);
}
.ame-multiselect__option-label { flex: 1 1 auto; }

/* ---------- RangeSlider (dual-overlay input[type=range]) ----------
   Two native range inputs are stacked over one track box. The inputs
   carry pointer-events:none so the TRACK is inert; only the thumb
   pseudo-elements re-enable pointer-events, letting each thumb drag
   independently. --ame-rangeslider-lo/-hi (set inline as percents)
   position the Primary fill between the thumbs over the Border track. */
.ame-rangeslider {
    position: relative;
    width: 100%;
    height: 16px;
    --ame-rangeslider-lo: 0%;
    --ame-rangeslider-hi: 100%;
}
/* Shared 4px track + the Primary fill slice between the thumbs. */
.ame-rangeslider__track,
.ame-rangeslider__fill {
    position: absolute;
    top: 50%;
    height: 4px;
    transform: translateY(-50%);
    border-radius: 999px;
    pointer-events: none;
}
.ame-rangeslider__track {
    left: 0;
    right: 0;
    background: var(--ame-form-border-color, #e5e7eb);
}
.ame-rangeslider__fill {
    left: var(--ame-rangeslider-lo, 0%);
    right: calc(100% - var(--ame-rangeslider-hi, 100%));
    background: var(--ame-button-bg-primary, #4361ee);
}
/* Both inputs overlap, transparent track, no pointer-events except thumbs. */
.ame-rangeslider__input {
    position: absolute;
    top: 0;
    left: 0;
    width: 100%;
    height: 16px;
    margin: 0;
    background: transparent;
    pointer-events: none;
    -webkit-appearance: none;
    appearance: none;
}
.ame-rangeslider__input:focus-visible { outline: none; }
/* WebKit: invisible track, grabbable thumb. */
.ame-rangeslider__input::-webkit-slider-runnable-track {
    height: 4px;
    background: transparent;
}
.ame-rangeslider__input::-webkit-slider-thumb {
    -webkit-appearance: none;
    appearance: none;
    pointer-events: auto;
    width: 16px;
    height: 16px;
    margin-top: -6px;
    border-radius: 50%;
    background: var(--ame-card-bg, #ffffff);
    border: 2px solid var(--ame-button-bg-primary, #4361ee);
    cursor: pointer;
    transition: background-color var(--ame-anim-duration-base) var(--ame-anim-easing);
}
.ame-rangeslider__input:hover::-webkit-slider-thumb { background: var(--ame-button-bg-primary, #4361ee); }
.ame-rangeslider__input:focus-visible::-webkit-slider-thumb {
    box-shadow: 0 0 0 3px color-mix(in srgb, var(--ame-button-bg-primary, #4361ee) 30%, transparent);
}
/* Firefox: invisible track, grabbable thumb. */
.ame-rangeslider__input::-moz-range-track { height: 4px; background: transparent; }
.ame-rangeslider__input::-moz-range-thumb {
    pointer-events: auto;
    width: 16px;
    height: 16px;
    border-radius: 50%;
    background: var(--ame-card-bg, #ffffff);
    border: 2px solid var(--ame-button-bg-primary, #4361ee);
    cursor: pointer;
    transition: background-color var(--ame-anim-duration-base) var(--ame-anim-easing);
}
.ame-rangeslider__input:hover::-moz-range-thumb { background: var(--ame-button-bg-primary, #4361ee); }
.ame-rangeslider__input:focus-visible::-moz-range-thumb {
    box-shadow: 0 0 0 3px color-mix(in srgb, var(--ame-button-bg-primary, #4361ee) 30%, transparent);
}
.ame-rangeslider__input:disabled { opacity: 0.5; }
.ame-rangeslider__input:disabled::-webkit-slider-thumb { cursor: not-allowed; }
.ame-rangeslider__input:disabled::-moz-range-thumb { cursor: not-allowed; }

/* ---------- MaskedInput ----------
   Plain .ame-input shell; the masked text is monospaced so prompt
   slots and literals stay column-aligned as the value fills. */
.ame-maskedinput {
    width: 100%;
    font-variant-numeric: tabular-nums;
    letter-spacing: 0.02em;
}

/* ========================================================================
   Wave 6a — Rating, ColorPicker, ListView, Calendar
   ======================================================================== */

/* ---------- Rating (star toggles) ----------
   A row of star buttons drawing the shared AMERating.StarPath. Filled
   stars (.--on, set by Value or hover preview) paint the Warning token;
   empty stars draw the Border outline. The control resets the preview on
   pointer-leave (handled in C#); CSS only colors the current fill. */
.ame-rating {
    display: inline-flex;
    align-items: center;
    gap: 2px;
    line-height: 0;
}
.ame-rating__star {
    display: inline-flex;
    padding: 2px;
    background: transparent;
    border: none;
    color: var(--ame-form-border-color, #e5e7eb); /* empty outline tint */
    cursor: pointer;
    border-radius: 4px;
    transition: color var(--ame-anim-duration-fast, 150ms) var(--ame-anim-easing, ease);
}
.ame-rating__star:focus-visible {
    outline: none;
    box-shadow: 0 0 0 2px color-mix(in srgb, var(--ame-button-bg-primary, #4361ee) 35%, transparent);
}
.ame-rating__glyph {
    width: 20px;
    height: 20px;
    display: block;
    fill: currentColor;
}
/* Filled star: Warning token fill with a slightly darker amber stroke so
   adjacent filled stars stay visually separated. */
.ame-rating__star--on {
    color: var(--ame-badge-bg-warning, #f59e0b);
}
.ame-rating--readonly .ame-rating__star { cursor: default; }

/* ---------- ColorPicker (swatch popup) ----------
   A color-well button opens a light-dismiss card (transparent backdrop +
   ESC, the AMEPopover mechanism) holding a swatch grid and a hex row. */
.ame-colorpicker {
    position: relative;
    display: inline-block;
}
.ame-colorpicker__well {
    display: inline-flex;
    align-items: center;
    gap: 8px;
    height: var(--ame-form-height, 38px);
    padding: 0 10px;
    background: var(--ame-form-bg, #ffffff);
    border: 1px solid var(--ame-form-border-color, #e5e7eb);
    border-radius: var(--ame-control-corner-radius, 4px);
    color: var(--ame-form-fg, #1f2937);
    cursor: pointer;
    font: inherit;
}
.ame-colorpicker__well:disabled { opacity: 0.5; cursor: not-allowed; }
.ame-colorpicker__well:focus-visible {
    outline: none;
    border-color: var(--ame-form-border-focus-color, #4361ee);
    box-shadow: 0 0 0 3px color-mix(in srgb, var(--ame-button-bg-primary, #4361ee) 30%, transparent);
}
/* The fill chip uses a checkerboard underlay so translucent / white values
   are still visible against the white well. */
.ame-colorpicker__swatch-fill {
    width: 18px;
    height: 18px;
    border-radius: 3px;
    border: 1px solid var(--ame-form-border-color, #e5e7eb);
    background-image: linear-gradient(45deg, #ddd 25%, transparent 25%),
                      linear-gradient(-45deg, #ddd 25%, transparent 25%),
                      linear-gradient(45deg, transparent 75%, #ddd 75%),
                      linear-gradient(-45deg, transparent 75%, #ddd 75%);
    background-size: 8px 8px;
    background-position: 0 0, 0 4px, 4px -4px, -4px 0;
}
.ame-colorpicker__value {
    font-variant-numeric: tabular-nums;
    text-transform: uppercase;
    font-size: var(--ame-form-font-size, 14px);
}
.ame-colorpicker__backdrop {
    position: fixed;
    inset: 0;
    z-index: 1090;
    background: transparent;
}
.ame-colorpicker__card {
    position: absolute;
    z-index: 1100;
    top: calc(100% + 4px);
    left: 0;
    padding: 10px;
    background: var(--ame-card-bg, #ffffff);
    border: 1px solid var(--ame-card-border-color, #e5e7eb);
    border-radius: var(--ame-control-corner-radius, 6px);
    box-shadow: var(--ame-card-shadow-md, 0 2px 8px rgba(0, 0, 0, 0.1));
    display: flex;
    flex-direction: column;
    gap: 10px;
}
.ame-colorpicker__grid {
    display: grid;
    grid-template-columns: repeat(6, 24px);
    gap: 6px;
}
.ame-colorpicker__chip {
    width: 24px;
    height: 24px;
    padding: 0;
    border: 1px solid var(--ame-card-border-color, #e5e7eb);
    border-radius: 4px;
    cursor: pointer;
    transition: transform var(--ame-anim-duration-fast, 150ms) var(--ame-anim-easing, ease);
}
.ame-colorpicker__chip:hover { transform: scale(1.08); }
.ame-colorpicker__chip:focus-visible { outline: none; }
/* Selected swatch: Primary ring (a doubled box-shadow keeps the ring off
   the swatch fill so light swatches still read as selected). */
.ame-colorpicker__chip--selected {
    box-shadow: 0 0 0 2px var(--ame-card-bg, #ffffff),
                0 0 0 4px var(--ame-button-bg-primary, #4361ee);
}
.ame-colorpicker__hexrow { display: flex; }
.ame-colorpicker__hex {
    width: 100%;
    text-transform: uppercase;
    font-variant-numeric: tabular-nums;
}

/* ---------- ListView (card-row list) ----------
   A vertical stack of Surface rows with hairline Border separators and a
   SurfaceMuted hover; .--striped tints alternate rows. No selection / no
   events in v1 — the consumer's ItemTemplate owns the row content. */
.ame-listview {
    display: flex;
    flex-direction: column;
    background: var(--ame-card-bg, #ffffff);
    border: 1px solid var(--ame-card-border-color, #e5e7eb);
    border-radius: var(--ame-control-corner-radius, 6px);
    overflow: hidden;
}
.ame-listview__row {
    padding: 12px;
    color: var(--ame-form-fg, #1f2937);
    border-top: 1px solid var(--ame-card-border-color, #e5e7eb);
    transition: background-color var(--ame-anim-duration-fast, 150ms) var(--ame-anim-easing, ease);
}
.ame-listview__row:first-child { border-top: none; }
.ame-listview__row:hover { background: var(--ame-surface-2, #f3f4f6); }
/* Striped: even rows (1-based 2nd, 4th, …) take the muted tint; hover still
   wins for feedback. */
.ame-listview--striped .ame-listview__row:nth-child(even) {
    background: var(--ame-surface-2, #f3f4f6);
}
.ame-listview--striped .ame-listview__row:hover {
    background: color-mix(in srgb, var(--ame-surface-2, #f3f4f6) 80%, #000);
}
.ame-listview__empty {
    padding: 24px 12px;
    text-align: center;
    color: var(--ame-text-muted, #8b9e9e);
    font-size: var(--ame-form-font-size, 14px);
}

/* ---------- Calendar (month grid) ----------
   Header (label + prev/next chevrons) over a two-letter weekday row and a
   6×7 day grid. Adjacent-month days mute; today rings in Border; the
   selected day is Primary bg + white fg; out-of-range days dim to 0.4 and
   are non-clickable (disabled in markup). */
.ame-calendar {
    display: inline-block;
    padding: 10px;
    background: var(--ame-card-bg, #ffffff);
    border: 1px solid var(--ame-card-border-color, #e5e7eb);
    border-radius: var(--ame-control-corner-radius, 6px);
    color: var(--ame-form-fg, #1f2937);
    user-select: none;
}
.ame-calendar__header {
    display: flex;
    align-items: center;
    justify-content: space-between;
    margin-bottom: 8px;
}
.ame-calendar__title {
    font-weight: 600;
    font-size: var(--ame-form-font-size, 14px);
}
.ame-calendar__nav {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 28px;
    height: 28px;
    background: transparent;
    border: none;
    border-radius: 4px;
    color: var(--ame-form-fg, #1f2937);
    cursor: pointer;
    transition: background-color var(--ame-anim-duration-fast, 150ms) var(--ame-anim-easing, ease);
}
.ame-calendar__nav:hover { background: var(--ame-surface-2, #f3f4f6); }
.ame-calendar__nav:focus-visible {
    outline: none;
    box-shadow: 0 0 0 2px color-mix(in srgb, var(--ame-button-bg-primary, #4361ee) 35%, transparent);
}
/* Chevrons drawn as rotated CSS borders so no glyph font/asset is needed. */
.ame-calendar__chevron {
    width: 8px;
    height: 8px;
    border-top: 2px solid currentColor;
    border-right: 2px solid currentColor;
}
.ame-calendar__chevron--prev { transform: rotate(-135deg); margin-left: 3px; }
.ame-calendar__chevron--next { transform: rotate(45deg); margin-right: 3px; }
/* Both rows share the 7-column track so headers align with the day cells. */
.ame-calendar__weekdays,
.ame-calendar__grid {
    display: grid;
    grid-template-columns: repeat(7, 32px);
    gap: 2px;
}
.ame-calendar__weekday {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    height: 24px;
    font-size: var(--ame-form-font-size-sm, 12px);
    font-weight: 600;
    color: var(--ame-text-muted, #8b9e9e);
}
.ame-calendar__day {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 32px;
    height: 32px;
    background: transparent;
    border: 1px solid transparent;
    border-radius: 4px;
    color: var(--ame-form-fg, #1f2937);
    font-size: var(--ame-form-font-size, 14px);
    cursor: pointer;
    transition: background-color var(--ame-anim-duration-fast, 150ms) var(--ame-anim-easing, ease);
}
.ame-calendar__day:hover { background: var(--ame-surface-2, #f3f4f6); }
.ame-calendar__day:focus-visible {
    outline: none;
    box-shadow: 0 0 0 2px color-mix(in srgb, var(--ame-button-bg-primary, #4361ee) 35%, transparent);
}
.ame-calendar__day--adjacent { color: var(--ame-text-muted, #8b9e9e); }
/* Today: Border ring (distinct from the Primary-fill selection). */
.ame-calendar__day--today { border-color: var(--ame-form-border-color, #e5e7eb); }
/* Selected wins over today / hover: Primary fill, white text. */
.ame-calendar__day--selected,
.ame-calendar__day--selected:hover {
    background: var(--ame-button-bg-primary, #4361ee);
    border-color: var(--ame-button-bg-primary, #4361ee);
    color: var(--ame-button-fg-on-primary, #ffffff);
}
.ame-calendar__day--disabled,
.ame-calendar__day--disabled:hover {
    opacity: 0.4;
    background: transparent;
    cursor: not-allowed;
}

/* ---------- CandlestickChart (Wave 6b) ----------
   Minimal SVG OHLC chart. The <svg> stretches to the container width
   (preserveAspectRatio="none" in markup) at a fixed pixel Height; the
   per-candle layout / normalization is computed in the code-behind. Up candles
   paint the Success token, down candles Danger — set on the wrapping <g> so the
   wick and body share the fill/stroke. */
.ame-candlestick {
    display: block;
    width: 100%;
}
.ame-candlestick__svg {
    display: block;
    width: 100%;
}
.ame-candlestick__wick {
    stroke-width: 1;
    stroke: currentColor;
}
.ame-candlestick__body {
    fill: currentColor;
    stroke: currentColor;
    stroke-width: 1;
}
/* Up = Success, down = Danger; currentColor flows to wick + body. */
.ame-candlestick__up   { color: var(--rz-success, #10b981); }
.ame-candlestick__down { color: var(--rz-danger, #ef4444); }
/* RenderStyle marks (Wave W·X11): Ohlc reuses the wick + the Up/Down color via currentColor;
   Line/Mountain are direction-less, so they take the Primary accent token instead. */
.ame-candlestick__ohlc-tick {
    stroke: currentColor;
    stroke-width: 1.5;
}
.ame-candlestick__line {
    fill: none;
    stroke: var(--rz-primary, #4361ee);
    stroke-width: 1.5;
}
.ame-candlestick__area {
    fill: var(--rz-primary, #4361ee);
    fill-opacity: 0.2;
    stroke: none;
}
.ame-candlestick__xlabels {
    display: flex;
    justify-content: space-between;
    margin-top: 4px;
    font-size: var(--ame-form-font-size-sm, 12px);
    color: var(--ame-text-muted, #8b9e9e);
}
.ame-candlestick__xlabel {
    flex: 1 1 0;
    text-align: center;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}
.ame-candlestick__empty {
    padding: 24px;
    text-align: center;
    font-size: var(--ame-form-font-size-sm, 12px);
    color: var(--ame-text-muted, #8b9e9e);
}

/* ---------- BoxPlotChart (Wave 14) ----------
   Minimal SVG box-and-whisker plot, the statistical sibling of the candlestick.
   The <svg> stretches to the container width (preserveAspectRatio="none" in
   markup) at a fixed pixel Height; the per-box layout / normalization (spanning
   boxes AND outliers) is computed in the code-behind. Single-direction palette:
   box / median / whiskers paint Primary, outlier dots Danger. currentColor flows
   from the .ame-boxplot__box color so the box / median / whiskers share the
   token; the outlier dots set their own color on .ame-boxplot__outlier. */
.ame-boxplot {
    display: block;
    width: 100%;
}
.ame-boxplot__svg {
    display: block;
    width: 100%;
}
/* Box / median / whiskers track the Primary token via currentColor. */
.ame-boxplot__box { color: var(--rz-primary, #4361ee); }
.ame-boxplot__rect {
    fill: var(--rz-primary, #4361ee);
    fill-opacity: 0.12;
    stroke: currentColor;
    stroke-width: 1;
}
.ame-boxplot__median {
    stroke: currentColor;
    stroke-width: 1.5;
}
.ame-boxplot__whisker,
.ame-boxplot__cap {
    stroke: currentColor;
    stroke-width: 1;
}
/* Outliers read as out of band: the Danger token. */
.ame-boxplot__outlier {
    fill: var(--rz-danger, #ef4444);
    stroke: var(--rz-danger, #ef4444);
}
.ame-boxplot__xlabels {
    display: flex;
    justify-content: space-between;
    margin-top: 4px;
    font-size: var(--ame-form-font-size-sm, 12px);
    color: var(--ame-text-muted, #8b9e9e);
}
.ame-boxplot__xlabel {
    flex: 1 1 0;
    text-align: center;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}
.ame-boxplot__empty {
    padding: 24px;
    text-align: center;
    font-size: var(--ame-form-font-size-sm, 12px);
    color: var(--ame-text-muted, #8b9e9e);
}

/* ---------- WaterfallChart (Wave 14) ----------
   Minimal SVG waterfall (bridge) chart, the cumulative sibling of the box plot.
   The svg stretches to the container width (preserveAspectRatio="none" in
   markup) at a fixed pixel Height; the per-bar layout / normalization (spanning
   every bar AND the baseline 0 plus negatives) is computed in the code-behind.
   Directional palette: a Rise bar paints Success, a Fall Danger, a Total Primary,
   each via its own __bar modifier so the floating rects track the theme tokens. */
.ame-waterfall {
    display: block;
    width: 100%;
}
.ame-waterfall__svg {
    display: block;
    width: 100%;
}
/* Each floating bar fills by direction (the three semantic tokens). */
.ame-waterfall__bar {
    stroke: none;
}
.ame-waterfall__bar--rise {
    fill: var(--rz-success, #10b981);
}
.ame-waterfall__bar--fall {
    fill: var(--rz-danger, #ef4444);
}
.ame-waterfall__bar--total {
    fill: var(--rz-primary, #4361ee);
}
/* Optional direction legend (off by default; on via ShowLegend). */
.ame-waterfall__legend {
    display: flex;
    gap: 16px;
    margin-bottom: 4px;
    font-size: var(--ame-form-font-size-sm, 12px);
    color: var(--ame-text-muted, #8b9e9e);
}
.ame-waterfall__legend-item {
    display: inline-flex;
    align-items: center;
    gap: 6px;
}
.ame-waterfall__swatch {
    width: 10px;
    height: 10px;
    border-radius: 2px;
    display: inline-block;
}
.ame-waterfall__swatch--rise { background: var(--rz-success, #10b981); }
.ame-waterfall__swatch--fall { background: var(--rz-danger, #ef4444); }
.ame-waterfall__swatch--total { background: var(--rz-primary, #4361ee); }
.ame-waterfall__xlabels {
    display: flex;
    justify-content: space-between;
    margin-top: 4px;
    font-size: var(--ame-form-font-size-sm, 12px);
    color: var(--ame-text-muted, #8b9e9e);
}
.ame-waterfall__xlabel {
    flex: 1 1 0;
    text-align: center;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}
.ame-waterfall__empty {
    padding: 24px;
    text-align: center;
    font-size: var(--ame-form-font-size-sm, 12px);
    color: var(--ame-text-muted, #8b9e9e);
}

/* ---------- IndicatorChart (Wave 14) ----------
   Minimal SVG technical-analysis chart: a price line with an SMA line and a
   Bollinger band OVERLAY, the indicator sibling of the box plot / waterfall.
   The svg stretches to the container width (preserveAspectRatio="none" in
   markup) at a fixed pixel Height; the per-overlay points strings and the
   normalization (spanning the prices AND every defined band Upper / Lower) are
   computed in the code-behind BuildLayout seam, into a fixed 300x100 viewBox.
   The moving-average / Bollinger warmup is a render gap (the SMA / EMA line /
   band polygon start at index period minus 1). Palette: the price line paints
   Primary, the SMA line Info, the EMA line Success, the band fill a translucent
   Info, each via its own element class so the overlays track the theme tokens.
   The band polygon is drawn first (behind). */
.ame-indicator {
    display: block;
    width: 100%;
}
.ame-indicator__svg {
    display: block;
    width: 100%;
}
/* The Bollinger band: a translucent Info fill behind the lines, no stroke. */
.ame-indicator__band {
    fill: var(--rz-info, #3b82f6);
    fill-opacity: 0.14;
    stroke: none;
}
/* The price line: a Primary stroke, no fill (a polyline, not an area). */
.ame-indicator__price {
    fill: none;
    stroke: var(--rz-primary, #4361ee);
    stroke-width: 1.5;
    stroke-linejoin: round;
    stroke-linecap: round;
}
/* The SMA overlay line: an Info stroke, no fill. */
.ame-indicator__sma {
    fill: none;
    stroke: var(--rz-info, #3b82f6);
    stroke-width: 1.25;
    stroke-linejoin: round;
    stroke-linecap: round;
}
/* The EMA overlay line: a Success stroke, no fill (the SMA's twin, a distinct hue). */
.ame-indicator__ema {
    fill: none;
    stroke: var(--rz-success, #10b981);
    stroke-width: 1.25;
    stroke-linejoin: round;
    stroke-linecap: round;
}
/* The overlay legend (on by default; off via ShowLegend). */
.ame-indicator__legend {
    display: flex;
    gap: 16px;
    margin-bottom: 4px;
    font-size: var(--ame-form-font-size-sm, 12px);
    color: var(--ame-text-muted, #8b9e9e);
}
.ame-indicator__legend-item {
    display: inline-flex;
    align-items: center;
    gap: 6px;
}
.ame-indicator__swatch {
    width: 10px;
    height: 10px;
    border-radius: 2px;
    display: inline-block;
}
.ame-indicator__swatch--price { background: var(--rz-primary, #4361ee); }
.ame-indicator__swatch--sma { background: var(--rz-info, #3b82f6); }
.ame-indicator__swatch--ema { background: var(--rz-success, #10b981); }
/* The band swatch reads as the translucent fill (a tinted block). */
.ame-indicator__swatch--band {
    background: var(--rz-info, #3b82f6);
    opacity: 0.3;
}
.ame-indicator__xlabels {
    display: flex;
    justify-content: space-between;
    margin-top: 4px;
    font-size: var(--ame-form-font-size-sm, 12px);
    color: var(--ame-text-muted, #8b9e9e);
}
.ame-indicator__xlabel {
    flex: 1 1 0;
    text-align: center;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}
.ame-indicator__empty {
    padding: 24px;
    text-align: center;
    font-size: var(--ame-form-font-size-sm, 12px);
    color: var(--ame-text-muted, #8b9e9e);
}

/* ---------- TreemapChart (Wave 14) ----------
   Minimal SVG treemap (nested-rectangle) chart. LiveCharts has no treemap series,
   so all three stacks hand-draw (Blazor SVG, desktop a code-built Canvas of rects).
   The svg stretches to the container width (preserveAspectRatio="none" in markup)
   at a fixed pixel Height; the squarified per-cell layout is computed in the
   code-behind BuildLayout seam into a fixed 300x200 viewBox. The per-cell fill hex
   is emitted inline (the item ColorToken via AMEPalette.HexFor, else the chart
   rotation ramp by index) the way the heatmap emits its cell hex, so the only token
   references here are the cell separator hairline and the muted chrome. The label
   color is the readable-on-fill choice (light text on a dark cell, dark text on a
   light cell) the code-behind picks per cell by the fill luminance. */
.ame-treemap {
    display: block;
    width: 100%;
}
.ame-treemap__svg {
    display: block;
    width: 100%;
}
/* A thin Surface hairline separates abutting cells (the fill is emitted inline). */
.ame-treemap__cell {
    stroke: var(--ame-card-bg, #ffffff);
    stroke-width: 1;
}
/* The cell label; the contrast class (light/dark) is chosen per cell in the code-behind. */
.ame-treemap__label {
    font-size: 11px;
    font-family: inherit;
    pointer-events: none;
}
.ame-treemap__label--light { fill: var(--ame-on-colored, #ffffff); }
.ame-treemap__label--dark { fill: var(--rz-text-color, #1f2937); }
/* The optional value line under the label reads a touch smaller / lighter. */
.ame-treemap__value {
    font-size: 9px;
    opacity: 0.85;
}
/* Optional swatch+label legend (off by default; on via ShowLegend). */
.ame-treemap__legend {
    display: flex;
    flex-wrap: wrap;
    gap: 16px;
    margin-top: 6px;
    font-size: var(--ame-form-font-size-sm, 12px);
    color: var(--ame-text-muted, #8b9e9e);
}
.ame-treemap__legend-item {
    display: inline-flex;
    align-items: center;
    gap: 6px;
}
.ame-treemap__swatch {
    width: 10px;
    height: 10px;
    border-radius: 2px;
    display: inline-block;
}
.ame-treemap__empty {
    padding: 24px;
    text-align: center;
    font-size: var(--ame-form-font-size-sm, 12px);
    color: var(--ame-text-muted, #8b9e9e);
}

/* ---------- SunburstChart (Wave 14) ----------
   The radial sibling of the treemap: nested ring arcs hand-drawn as SVG paths whose `d`
   comes from AMESunburst.ArcPath. Each arc fill is emitted inline (the node ColorToken
   via AMEPalette.HexFor, else the chart rotation ramp by root index) the way the treemap
   emits its cell fill, so the only token references here are the arc separator hairline and
   the muted chrome. The label color is the readable-on-fill choice (light text on a dark
   arc, dark text on a light arc) the code-behind picks per arc by the fill luminance. The
   SQUARE viewBox keeps the ring round under preserveAspectRatio="xMidYMid meet". */
.ame-sunburst {
    display: block;
    width: 100%;
}
.ame-sunburst__svg {
    display: block;
    width: 100%;
}
/* A thin Surface hairline separates abutting arcs (the fill is emitted inline). */
.ame-sunburst__arc {
    stroke: var(--ame-card-bg, #ffffff);
    stroke-width: 1;
}
/* The arc label; the contrast class (light/dark) is chosen per arc in the code-behind. */
.ame-sunburst__label {
    font-size: 11px;
    font-family: inherit;
    pointer-events: none;
}
.ame-sunburst__label--light { fill: var(--ame-on-colored, #ffffff); }
.ame-sunburst__label--dark { fill: var(--rz-text-color, #1f2937); }
/* The optional value line under the label reads a touch smaller / lighter. */
.ame-sunburst__value {
    font-size: 9px;
    opacity: 0.85;
}
/* Optional swatch+label legend (off by default; on via ShowLegend) — one entry per root. */
.ame-sunburst__legend {
    display: flex;
    flex-wrap: wrap;
    gap: 16px;
    margin-top: 6px;
    font-size: var(--ame-form-font-size-sm, 12px);
    color: var(--ame-text-muted, #8b9e9e);
}
.ame-sunburst__legend-item {
    display: inline-flex;
    align-items: center;
    gap: 6px;
}
.ame-sunburst__swatch {
    width: 10px;
    height: 10px;
    border-radius: 2px;
    display: inline-block;
}
.ame-sunburst__empty {
    padding: 24px;
    text-align: center;
    font-size: var(--ame-form-font-size-sm, 12px);
    color: var(--ame-text-muted, #8b9e9e);
}

/* ---------- SankeyChart (Wave 14) ----------
   The flow sibling of the treemap / sunburst: value-weighted NODES in layers connected by
   weighted LINK RIBBONS, all hand-drawn as SVG (NO LiveCharts sankey series exists). Each
   node fill + ribbon tint is emitted inline (the node ColorToken via AMEPalette.HexFor, else
   the chart rotation ramp by index) the way the treemap emits its cell fill, so the only token
   references here are the node separator hairline, the ribbon translucency, and the muted
   chrome. The ribbons draw FIRST (behind the nodes) and are made TRANSLUCENT via fill-opacity so
   overlapping flows + the node bars read through. The label color is the readable-on-fill choice
   (light text on a dark node, dark text on a light node) the code-behind picks per node by the
   fill luminance. The WIDE viewBox stretches to fill via preserveAspectRatio="none". */
.ame-sankey {
    display: block;
    width: 100%;
}
.ame-sankey__svg {
    display: block;
    width: 100%;
}
/* The link ribbons sit behind the nodes and read translucent so overlapping flows show through
   (the fill is emitted inline, tinted from the source node). */
.ame-sankey__ribbon {
    fill-opacity: 0.45;
}
/* A thin Surface hairline outlines each node bar (the fill is emitted inline). */
.ame-sankey__node {
    stroke: var(--ame-card-bg, #ffffff);
    stroke-width: 1;
}
/* The node label; the contrast class (light/dark) is chosen per node in the code-behind. */
.ame-sankey__label {
    font-size: 11px;
    font-family: inherit;
    pointer-events: none;
}
.ame-sankey__label--light { fill: var(--ame-on-colored, #ffffff); }
.ame-sankey__label--dark { fill: var(--rz-text-color, #1f2937); }
/* The optional value line under the label reads a touch smaller / lighter. */
.ame-sankey__value {
    font-size: 9px;
    opacity: 0.85;
}
.ame-sankey__empty {
    padding: 24px;
    text-align: center;
    font-size: var(--ame-form-font-size-sm, 12px);
    color: var(--ame-text-muted, #8b9e9e);
}

/* ---------- HeatmapChart (Wave 6b) ----------
   CSS-grid of cells; each cell background is a C# hex lerp (SurfaceMuted →
   Primary) emitted inline by the code-behind, so the only token references here
   are the muted labels and the cell border. Mirrors the desktop HeatSeries. */
.ame-heatmap {
    display: block;
    width: 100%;
}
.ame-heatmap__layout {
    display: flex;
    gap: 6px;
    align-items: stretch;
}
.ame-heatmap__ylabels {
    display: flex;
    flex-direction: column;
    justify-content: space-around;
    font-size: var(--ame-form-font-size-sm, 12px);
    color: var(--ame-text-muted, #8b9e9e);
    text-align: right;
}
.ame-heatmap__ylabel {
    flex: 1 1 0;
    display: flex;
    align-items: center;
    justify-content: flex-end;
    white-space: nowrap;
}
.ame-heatmap__grid {
    flex: 1 1 auto;
    display: grid;
    gap: 2px;
}
.ame-heatmap__cell {
    aspect-ratio: 1 / 1;
    border-radius: 2px;
    border: 1px solid var(--ame-card-border-color, #e5e7eb);
}
.ame-heatmap__xlabels {
    display: flex;
    gap: 6px;
    margin-top: 4px;
}
/* When Y labels are present the X-label row is inset by their column width. */
.ame-heatmap__ylabel-spacer { flex: 0 0 auto; min-width: 2ch; }
.ame-heatmap__xlabels-row {
    flex: 1 1 auto;
    display: grid;
    gap: 2px;
    font-size: var(--ame-form-font-size-sm, 12px);
    color: var(--ame-text-muted, #8b9e9e);
}
.ame-heatmap__xlabel {
    text-align: center;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}
.ame-heatmap__empty {
    padding: 24px;
    text-align: center;
    font-size: var(--ame-form-font-size-sm, 12px);
    color: var(--ame-text-muted, #8b9e9e);
}
.ame-heatmap-colorscale {
    height: 8px;
    margin-top: 6px;
    border-radius: 2px;
    border: 1px solid var(--ame-card-border-color, #e5e7eb);
}

/* ---------- Upload (Wave 6b) ----------
   A styled InputFile in a dashed Border-token drop zone; chosen files render as
   removable AMEChips below. The native <input> is visually hidden but still the
   click/drop target (it fills the label). */
.ame-upload {
    display: flex;
    flex-direction: column;
    gap: 10px;
}
.ame-upload__zone {
    position: relative;
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: 6px;
    padding: 24px;
    border: 2px dashed var(--ame-card-border-color, #e5e7eb);
    border-radius: var(--ame-card-corner-radius, 6px);
    background: var(--ame-surface-2, #f3f4f6);
    color: var(--ame-text-muted, #8b9e9e);
    cursor: pointer;
    text-align: center;
    transition: border-color var(--ame-anim-duration-fast, 150ms) var(--ame-anim-easing, ease),
                background-color var(--ame-anim-duration-fast, 150ms) var(--ame-anim-easing, ease);
}
.ame-upload__zone:hover {
    border-color: var(--ame-form-border-focus-color, #4361ee);
    background: var(--ame-surface, #ffffff);
}
.ame-upload__zone:focus-within {
    outline: none;
    border-color: var(--ame-form-border-focus-color, #4361ee);
    box-shadow: 0 0 0 3px color-mix(in srgb, var(--ame-button-bg-primary, #4361ee) 25%, transparent);
}
.ame-upload__zone--disabled {
    opacity: 0.5;
    cursor: not-allowed;
}
.ame-upload__zone--disabled:hover {
    border-color: var(--ame-card-border-color, #e5e7eb);
    background: var(--ame-surface-2, #f3f4f6);
}
/* The actual file input overlays the whole label so the entire zone is clickable,
   but stays invisible (the styled prompt shows through). */
.ame-upload__input {
    position: absolute;
    inset: 0;
    width: 100%;
    height: 100%;
    opacity: 0;
    cursor: inherit;
}
.ame-upload__icon { color: var(--ame-text-muted, #8b9e9e); }
.ame-upload__prompt {
    font-size: var(--ame-form-font-size, 14px);
    color: var(--ame-form-fg, #1f2937);
}
.ame-upload__hint {
    font-size: var(--ame-form-font-size-sm, 12px);
    color: var(--ame-text-muted, #8b9e9e);
}
.ame-upload__chips {
    display: flex;
    flex-wrap: wrap;
    gap: 8px;
}

/* ---------- Upload async progress (Wave 20 · W20-G16, opt-in AUGMENT) ----------
   Only rendered when UploadHandler is set (see AMEUpload.razor's handler-gated branch);
   the byte-identity floor keeps .ame-upload__chips above untouched. Each row reuses
   AMEProgressBar for the bar itself (role=progressbar rides along via its own markup);
   this block only styles the row layout, the name/status/error text, and the
   retry/remove buttons. Token-only, no new hardcoded hex. */
.ame-upload__files {
    display: flex;
    flex-direction: column;
    gap: 8px;
}
.ame-upload__file {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 8px;
    padding: 8px 10px;
    border: 1px solid var(--ame-card-border-color, #e5e7eb);
    border-radius: var(--ame-card-corner-radius, 6px);
    background: var(--ame-surface, #ffffff);
    font-size: var(--ame-form-font-size-sm, 12px);
}
.ame-upload__file-name {
    flex: 1 1 160px;
    min-width: 0;
    color: var(--ame-form-fg, #1f2937);
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}
.ame-upload__file-progress {
    flex: 1 1 120px;
    min-width: 80px;
}
.ame-upload__file-status {
    display: inline-flex;
    align-items: center;
    gap: 4px;
    flex: 0 0 auto;
    color: var(--ame-text-muted, #8b9e9e);
    white-space: nowrap;
}
.ame-upload__file-error {
    flex: 1 1 100%;
    color: var(--rz-danger, #ef4444);
    font-weight: 600;
}
.ame-upload__retry,
.ame-upload__remove {
    display: inline-flex;
    align-items: center;
    gap: 2px;
    flex: 0 0 auto;
    padding: 2px 6px;
    border: 0;
    border-radius: var(--ame-control-radius, 6px);
    background: transparent;
    color: inherit;
    cursor: pointer;
    font-size: var(--ame-form-font-size-sm, 12px);
    opacity: 0.8;
    transition: opacity var(--ame-anim-duration-base, 150ms) var(--ame-anim-easing, ease),
                background-color var(--ame-anim-duration-base, 150ms) var(--ame-anim-easing, ease);
}
.ame-upload__retry {
    color: var(--rz-danger, #ef4444);
}
.ame-upload__retry:hover,
.ame-upload__remove:hover {
    opacity: 1;
    background: var(--ame-hover, rgba(0, 0, 0, 0.08));
}
.ame-upload__retry:focus-visible,
.ame-upload__remove:focus-visible {
    outline: 2px solid var(--ame-form-border-focus-color, #4361ee);
    outline-offset: 1px;
}

/* ---------- PolarChart (Wave 7a) ----------
   Minimal inline-SVG radar. The <svg> is a square (viewBox 100×100) sized by Height;
   the geometry (rings / spokes / per-series polygons / rim-label anchors) is computed
   in the code-behind. Reference rings + spokes are Border-token hairlines. Each series
   polygon's accent comes either from a rotation class (.ame-polar__s0..s4, which set
   `color` so stroke/fill flow via currentColor) or from an inline stroke/fill emitted by
   the component when the descriptor pins an explicit token. Line = fill:none; Area adds the
   same colour at .2 opacity (the --area class). Rim labels + legend use the muted token. */
.ame-polar {
    display: flex;
    flex-direction: column;
    align-items: center;
    width: 100%;
}
.ame-polar__svg {
    display: block;
    width: auto;
    max-width: 100%;
    aspect-ratio: 1 / 1;
    overflow: visible; /* let rim labels spill past the square plot */
}
/* Concentric reference rings + radial spokes: Border-token hairlines, no fill. */
.ame-polar__ring {
    fill: none;
    stroke: var(--ame-card-border-color, #e5e7eb);
    stroke-width: 0.5;
}
.ame-polar__spoke {
    stroke: var(--ame-card-border-color, #e5e7eb);
    stroke-width: 0.5;
}
/* Series polygon: stroke = the accent (currentColor from the rotation class, or an inline
   stroke from the component for an explicit token). Line keeps fill:none. */
.ame-polar__series {
    fill: none;
    stroke: currentColor;
    stroke-width: 1.5;
    stroke-linejoin: round;
}
/* Area kind: fill the polygon in the same colour at ~20% alpha. The inline path sets the
   fill colour for explicit tokens; the rotation path fills via currentColor. */
.ame-polar__series--area {
    fill: currentColor;
    fill-opacity: 0.2;
}
/* Palette rotation (Primary → Info → Success → Warning → Danger) — set `color` so both the
   polygon stroke/fill (currentColor) and the legend swatch (background:currentColor) track it. */
.ame-polar__s0 { color: var(--rz-primary, #4361ee); }
.ame-polar__s1 { color: var(--rz-info,    #3b82f6); }
.ame-polar__s2 { color: var(--rz-success, #10b981); }
.ame-polar__s3 { color: var(--rz-warning, #f59e0b); }
.ame-polar__s4 { color: var(--rz-danger,  #ef4444); }
/* Rim category labels at the spoke ends: muted, small. */
.ame-polar__label {
    fill: var(--ame-text-muted, #8b9e9e);
    font-size: 7px; /* ~Sm at the 100-unit viewBox scale */
}
/* Legend: a simple flex row of swatch + name beneath the plot. */
.ame-polar__legend {
    display: flex;
    flex-wrap: wrap;
    justify-content: center;
    gap: 12px;
    margin-top: 8px;
    font-size: var(--ame-form-font-size-sm, 12px);
    color: var(--ame-text-muted, #8b9e9e);
}
.ame-polar__legend-item {
    display: inline-flex;
    align-items: center;
    gap: 6px;
}
.ame-polar__legend-swatch {
    width: 12px;
    height: 12px;
    border-radius: 2px;
    background: currentColor; /* rotation class sets `color`; explicit token sets background inline */
    flex: 0 0 auto;
}
.ame-polar__legend-name {
    color: var(--ame-form-fg, #1f2937);
}
.ame-polar__empty {
    padding: 24px;
    text-align: center;
    font-size: var(--ame-form-font-size-sm, 12px);
    color: var(--ame-text-muted, #8b9e9e);
}

/* ---------- Scheduler (Wave 7b·i1, day view) ----------
   A single-day calendar. AMEScheduleMath (AME.Design) owns all geometry; the component turns
   its window-relative fractions into the percentages/pixels below. Frame = Surface on a Border
   hairline. An optional all-day strip sits above a scrollable time grid: a CSS grid of an
   hour-gutter column + a 1fr day column, its explicit height set inline (visibleHours × HourHeight).
   The gutter holds "HH:00" labels (muted, Sm); the day column holds clickable slot cells (top
   hairline; full-hour slots a stronger top border via --hour) plus absolutely-positioned blocks.
   Each block/chip carries its accent + 15%-alpha tint as inline custom properties (--ame-sch-accent
   / --ame-sch-tint) the rules below consume — explicit per-item colour by inline style is expected,
   the same pattern as the polar series stroke. */
.ame-scheduler {
    display: flex;
    flex-direction: column;
    width: 100%;
    background: var(--ame-card-bg, #ffffff);
    border: 1px solid var(--ame-card-border-color, #e5e7eb);
    border-radius: 6px;
    overflow: hidden;
    box-sizing: border-box;
}
/* All-day strip: tinted chips above the grid, separated by a hairline. */
.ame-scheduler__allday {
    display: flex;
    flex-wrap: wrap;
    gap: 6px;
    padding: 8px;
    border-bottom: 1px solid var(--ame-card-border-color, #e5e7eb);
}
.ame-scheduler__allday-chip {
    max-width: 100%;
    padding: 2px 8px;
    border-left: 3px solid var(--ame-sch-accent, var(--rz-primary, #4361ee));
    border-radius: 4px;
    background: var(--ame-sch-tint, rgba(67, 97, 238, 0.15));
    color: var(--ame-form-fg, #1f2937);
    font-size: 12px;
    line-height: 1.4;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
    box-sizing: border-box;
}
/* Scroll viewport: the grid is taller than this, so it scrolls vertically. */
.ame-scheduler__scroll {
    max-height: 420px;
    overflow-y: auto;
}
/* The time grid: auto hour-gutter column + 1fr day column. Explicit height arrives inline. */
.ame-scheduler__grid {
    display: grid;
    grid-template-columns: auto 1fr;
    position: relative;
}
/* Hour-gutter cell: right-aligned "HH:00", muted Sm, top hairline. Height set inline. */
.ame-scheduler__hour {
    grid-column: 1;
    display: flex;
    justify-content: flex-end;
    padding: 0 8px;
    border-top: 1px solid var(--ame-card-border-color, #e5e7eb);
    font-size: var(--ame-form-font-size-sm, 12px);
    color: var(--ame-text-muted, #8b9e9e);
    box-sizing: border-box;
    user-select: none;
}
/* Day column: the slot/block stack. The grid pins it to column 2, all rows; blocks position
   against it (position: relative). */
.ame-scheduler__day {
    grid-column: 2;
    grid-row: 1 / -1;
    position: relative;
    border-left: 1px solid var(--ame-card-border-color, #e5e7eb);
}
/* Slot cell: a clickable strip. Sub-hour slots keep a hairline top border; full-hour slots
   (--hour) take a stronger one to anchor each hour visually. Height set inline. */
.ame-scheduler__slot {
    border-top: 1px solid rgba(229, 231, 235, 0.4); /* Border #E5E7EB at 40% (sub-hour hairline) */
    cursor: pointer;
    box-sizing: border-box;
}
.ame-scheduler__slot:hover {
    background: var(--ame-surface-muted, #f3f4f6);
}
.ame-scheduler__slot--hour {
    border-top-color: var(--ame-card-border-color, #e5e7eb);
}
/* Appointment block: absolutely positioned over the slots; geometry (top/height/left/width) is
   inline from the engine fractions. Left accent + tint background come from the per-block custom
   properties; the title clips with an ellipsis. It is a real <button> (keyboard + AT reachable), so
   the UA button chrome is reset here and the focus ring is the shared form focus token. */
.ame-scheduler__block {
    position: absolute;
    min-height: 16px;
    appearance: none;
    border: 0;
    border-left: 3px solid var(--ame-sch-accent, var(--rz-primary, #4361ee));
    border-radius: 4px;
    background: var(--ame-sch-tint, rgba(67, 97, 238, 0.15));
    padding: 2px 6px;
    font-family: inherit;
    font-size: 12px;
    line-height: 1.4;
    text-align: left;
    color: var(--ame-form-fg, #1f2937);
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
    cursor: pointer;
    box-sizing: border-box;
}
.ame-scheduler__block:focus-visible {
    outline: 2px solid var(--ame-form-border-focus-color);
    outline-offset: 1px;
}

/* Wave U i7 (drag/resize, opt-in). The draggable modifier is the only thing that changes on the block
   itself: a grab cursor and touch-action none, so a touch drag moves the appointment instead of
   scrolling the grid. Without AllowAppointmentDrag none of this is emitted. */
.ame-scheduler__block--draggable {
    cursor: grab;
    touch-action: none;
}
.ame-scheduler__block--draggable:active {
    cursor: grabbing;
}
/* The block's title span (only rendered in the draggable form, alongside the resize grip). */
.ame-scheduler__block-text {
    display: block;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}
/* The RESIZE grip. Decorative for AT (the keyboard path is Shift+Arrow on the focused block), so it is
   aria-hidden in the markup. The element itself is the HIT TARGET and is 24px tall (WCAG 2.5.8: a 6px
   strip inside the block is far below the 24px minimum and the spacing exception does not apply to a
   target inside another target); what is PAINTED is the thin strip drawn by ::after on the bottom edge.
   The hit area is capped at half the block so a short appointment keeps a body area to MOVE by. */
.ame-scheduler__block-handle {
    position: absolute;
    left: 0;
    right: 0;
    bottom: 0;
    height: 24px;
    max-height: 50%;
    background: transparent;
    cursor: ns-resize;
    touch-action: none;
}
.ame-scheduler__block-handle::after {
    content: "";
    position: absolute;
    left: 0;
    right: 0;
    bottom: 0;
    height: 3px;
    border-radius: 0 0 4px 4px;
    background: var(--ame-sch-accent, var(--rz-primary, #4361ee));
    opacity: 0.55;
}
/* The drag GHOST: where the appointment WOULD land. Dashed and non-interactive, and painted over the
   real block, which never moves (the scheduler raises an intent; it does not edit the data). */
.ame-scheduler__block--preview {
    z-index: 2;
    pointer-events: none;
    opacity: 0.7;
    border: 1px dashed var(--ame-sch-accent, var(--rz-primary, #4361ee));
    border-left-width: 3px;
    border-left-style: solid;
}
/* The MOUSE capture surface: exists only while a drag is in flight AND has actually MOVED (the markup
   renders it then and only then), so an idle grid generates no pointermove traffic and a plain CLICK on
   a block never has it under the mouseup (which would send the synthesized click to the column instead
   of the block, killing AppointmentClicked). It covers the column so a MOUSE may leave the block without
   dropping the drag: the no-JS stand-in for setPointerCapture. Touch never needs it: a touch pointerdown
   takes implicit capture, so its move/up stay on the block, which carries the same handlers. */
.ame-scheduler__dragsurface {
    position: absolute;
    inset: 0;
    z-index: 3;
    cursor: grabbing;
    touch-action: none;
}

/* Wave U i8 (drag-create). The slot LISTBOX: the slots' own wrapper, so the list contains only options (the
   blocks and the previews are siblings, absolutely positioned over the column). It is the SINGLE tab stop for
   the whole column — the roving cursor inside it is the active slot — so the focus ring is painted here, drawn
   INSIDE the box (a negative offset) because a day column has no room to spare around it. */
.ame-scheduler__slots {
    display: block;
}
.ame-scheduler__slots:focus-visible {
    outline: 2px solid var(--ame-form-border-focus-color);
    outline-offset: -2px;
}
/* The roving cursor's slot. Focus lives on the list, so the "focused" slot must be painted, not just named by
   aria-activedescendant: an inset ring, visible only while the list actually has focus. */
.ame-scheduler__slots:focus-visible .ame-scheduler__slot--active {
    outline: 2px solid var(--ame-form-border-focus-color);
    outline-offset: -2px;
}
/* A selectable slot (AllowSlotRangeSelect only): touch-action none, so a touch drag draws a range instead of
   scrolling the grid. Without the opt-in, none of this is emitted and the slot keeps its plain click. */
.ame-scheduler__slot--selectable {
    touch-action: none;
    cursor: cell;
}
/* THE PREVIEW of the pending range — what the appointment WOULD cover. Dashed, non-interactive, and painted over
   the slots; it is a preview and nothing else, so it VANISHES when the drag ends (the scheduler raises an intent;
   only the host can create the appointment). */
.ame-scheduler__range {
    position: absolute;
    left: 0;
    right: 0;
    z-index: 2;
    min-height: 16px;
    padding: 2px 6px;
    border: 1px dashed var(--ame-sch-accent, var(--rz-primary, #4361ee));
    border-left: 3px solid var(--ame-sch-accent, var(--rz-primary, #4361ee));
    border-radius: 4px;
    background: var(--ame-sch-tint, rgba(67, 97, 238, 0.15));
    color: var(--ame-form-fg, #1f2937);
    font-size: 12px;
    line-height: 1.4;
    overflow: hidden;
    white-space: nowrap;
    pointer-events: none;
    box-sizing: border-box;
}
/* The MOUSE select surface: the no-JS stand-in for setPointerCapture, exactly as the appointment drag's surface
   is. It exists only while a selection is live, so an idle grid generates no pointermove traffic; it covers the
   column (blocks included) so the pointer may cross anything without dropping the range. Touch never needs it:
   implicit capture keeps every move on the anchor slot, which carries the same handler. */
.ame-scheduler__selectsurface {
    position: absolute;
    inset: 0;
    z-index: 3;
    cursor: cell;
    touch-action: none;
}

/* Week view (Wave 7b i2): a fixed header row and optional all-day band sit above a vertically
   scrolling time grid. All three rows share the SAME column template (a fixed hour gutter + seven
   equal day columns) so the gutter spacers line up over the scroll grid's "HH:00" labels. The
   blocks/slots reuse the day rules above unchanged. The gutter is fixed (not auto) so the header
   and band spacers match the grid gutter without measuring the labels. */
.ame-scheduler__week {
    display: flex;
    flex-direction: column;
    min-width: 0;
}
/* Header row: gutter spacer + one head cell per day. Pinned above the scroll area by flow. */
.ame-scheduler__week-header {
    display: grid;
    grid-template-columns: 52px repeat(7, 1fr);
    border-bottom: 1px solid var(--ame-card-border-color, #e5e7eb);
}
/* Gutter spacer: empty, same 52px width as the grid hour gutter so columns align across rows. */
.ame-scheduler__week-gutter {
    grid-column: 1;
}
/* Day head cell: clickable "Mon 15" label, centered, with a left hairline between columns. */
.ame-scheduler__week-headcell {
    display: flex;
    align-items: center;
    justify-content: center;
    gap: 4px;
    padding: 6px 4px;
    border-left: 1px solid var(--ame-card-border-color, #e5e7eb);
    font-size: var(--ame-form-font-size-sm, 12px);
    color: var(--ame-form-fg, #1f2937);
    cursor: pointer;
    user-select: none;
    box-sizing: border-box;
}
.ame-scheduler__week-headcell:hover {
    background: var(--ame-surface-muted, #f3f4f6);
}
/* Today column header: a ~15%-alpha Primary wash plus a semibold label, matching the desktop
   parity reference (the single-accent block-tint recipe). The tint is the same literal Primary
   rgba the blocks use as a fallback, so there is no color-mix dependency and no undefined custom
   property (the Wave 7b i1 dark-line lesson). The label keeps the normal text colour. */
.ame-scheduler__week-headcell--today {
    background: rgba(67, 97, 238, 0.15);
    font-weight: 600;
}
/* All-day band: gutter spacer + one cell per day, each flowing its tinted chips. */
.ame-scheduler__week-allday {
    display: grid;
    grid-template-columns: 52px repeat(7, 1fr);
    border-bottom: 1px solid var(--ame-card-border-color, #e5e7eb);
}
.ame-scheduler__week-allcell {
    display: flex;
    flex-direction: column;
    gap: 4px;
    padding: 6px 4px;
    border-left: 1px solid var(--ame-card-border-color, #e5e7eb);
    min-width: 0;
    box-sizing: border-box;
}
/* Week grid: the shared hour gutter + seven equal day columns. Explicit height arrives inline, as
   in the day grid. Vertical scroll only (the parent scroll viewport handles it). */
.ame-scheduler__grid--week {
    grid-template-columns: 52px repeat(7, 1fr);
}
/* Day column inside the week grid: the slot/block stack, one per grid column 2..8. The explicit
   grid placement (grid-column / grid-row 1 / -1) arrives inline per column. Blocks position against
   it (position: relative); a left hairline separates columns. */
.ame-scheduler__week-col {
    position: relative;
    border-left: 1px solid var(--ame-card-border-color, #e5e7eb);
    min-width: 0;
}

/* Month view (Wave U·U5): a 6x7 calendar page, NO time grid. A weekday header row sits above six
   equal rows of seven day cells; the rows share the height evenly (1fr), so the page keeps a FIXED
   height whatever the month — the engine's 42-cell page exists precisely so the scheduler never
   jumps as the user pages through the year. Every affordance inside a cell is a real button: the day
   number (the cell's keyboard entry point and accessible name), each chip, and the "+N more"
   overflow. Chips reuse the day/week accent + tint custom properties. An outside-month day is dimmed
   AND prints its month ("Jun 30"), so colour is never the only signal. */
.ame-scheduler__month {
    display: flex;
    flex-direction: column;
    min-width: 0;
    min-height: 420px;
}
/* The grid is the SINGLE tab stop (tabindex 0) and roves focus over its cells, so it never paints a ring
   itself: the ACTIVE cell wears it (see the active modifier below) while the grid holds focus. */
.ame-scheduler__month:focus {
    outline: none;
}
.ame-scheduler__month-headrow,
.ame-scheduler__month-row {
    display: grid;
    grid-template-columns: repeat(7, 1fr);
}
.ame-scheduler__month-row {
    flex: 1 1 0;
    min-height: 72px;
}
.ame-scheduler__month-headrow {
    border-bottom: 1px solid var(--ame-card-border-color, #e5e7eb);
}
.ame-scheduler__month-headcell {
    padding: 6px 4px;
    text-align: center;
    border-left: 1px solid var(--ame-card-border-color, #e5e7eb);
    font-size: var(--ame-form-font-size-sm, 12px);
    color: var(--ame-text-muted, #8b9e9e);
    user-select: none;
    box-sizing: border-box;
}
.ame-scheduler__month-headcell:first-child {
    border-left: 0;
}
/* Day cell: the chip stack. Clickable as a whole (a pointer convenience); the buttons inside carry
   the keyboard path. min-width 0 lets the chips ellipsis instead of stretching the column. */
.ame-scheduler__month-cell {
    display: flex;
    flex-direction: column;
    align-items: flex-start;
    gap: 2px;
    padding: 4px;
    min-width: 0;
    border-top: 1px solid var(--ame-card-border-color, #e5e7eb);
    border-left: 1px solid var(--ame-card-border-color, #e5e7eb);
    overflow: hidden;
    cursor: pointer;
    box-sizing: border-box;
}
.ame-scheduler__month-cell:hover {
    background: var(--ame-surface-muted, #f3f4f6);
}
/* Adjacent-month day — THE CONVERGED CROSS-STACK DIM (Wave U·U5 review). One treatment everywhere: a muted
   SURFACE token behind muted TEXT tokens. Deliberately NOT an opacity fade: opacity multiplies the whole cell,
   including the month-prefixed label ("Jun 30") that carries the not-colour-alone signal, and at the desktop
   stacks' 0.45 / 0.55 it crushed that text to roughly 1.6:1 — unreadable. Tokens keep it near 4.5:1 and leave
   the chips at full contrast. WPF and Avalonia are brought onto THIS recipe (surface + text tokens, no fade). */
.ame-scheduler__month-cell--outside {
    background: var(--ame-surface-muted, #f3f4f6);
    color: var(--ame-text-muted, #8b9e9e);
}
/* Today: the same 15%-alpha Primary wash the week header uses (no color-mix dependency). */
.ame-scheduler__month-cell--today {
    background: rgba(67, 97, 238, 0.15);
}
/* Day number: a bare-looking but REAL button — the cell's keyboard entry point. */
.ame-scheduler__month-daynum {
    align-self: flex-end;
    padding: 0 4px;
    border: 0;
    border-radius: 4px;
    background: transparent;
    color: inherit;
    font: inherit;
    font-size: var(--ame-form-font-size-sm, 12px);
    line-height: 1.4;
    cursor: pointer;
}
.ame-scheduler__month-cell--today .ame-scheduler__month-daynum {
    font-weight: 600;
}
/* Chip: the block's tint recipe, flowed instead of absolutely positioned. */
.ame-scheduler__month-chip {
    display: block;
    width: 100%;
    max-width: 100%;
    padding: 1px 6px;
    border: 0;
    border-left: 3px solid var(--ame-sch-accent, var(--rz-primary, #4361ee));
    border-radius: 4px;
    background: var(--ame-sch-tint, rgba(67, 97, 238, 0.15));
    color: var(--ame-form-fg, #1f2937);
    font-size: 12px;
    line-height: 1.4;
    text-align: left;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
    cursor: pointer;
    box-sizing: border-box;
}
/* All-day banner: bolder, so a banner reads as a band rather than a timed entry. */
.ame-scheduler__month-chip--allday {
    font-weight: 600;
}
/* Overflow: a real button whose accessible name says what it does (it drills into that day). */
.ame-scheduler__month-more {
    padding: 0 4px;
    border: 0;
    border-radius: 4px;
    background: transparent;
    color: var(--ame-text-muted, #8b9e9e);
    font: inherit;
    font-size: 12px;
    line-height: 1.4;
    text-align: left;
    cursor: pointer;
}
.ame-scheduler__month-more:hover {
    text-decoration: underline;
}
/* The roving keyboard cursor: the one active cell, ringed with the shared focus token ONLY while the grid holds
   focus (an unfocused grid shows no cursor). Inset offset so the ring stays inside the cell's hairline. */
.ame-scheduler__month:focus .ame-scheduler__month-cell--active,
.ame-scheduler__month:focus-within .ame-scheduler__month-cell--active {
    outline: 2px solid var(--ame-form-border-focus-color);
    outline-offset: -2px;
}
/* The in-cell buttons are tabindex -1 (the grid is the single tab stop), but a pointer click still focuses them
   and an AT can still land on them, so they keep a ring of their own. */
.ame-scheduler__month-daynum:focus-visible,
.ame-scheduler__month-chip:focus-visible,
.ame-scheduler__month-more:focus-visible {
    outline: 2px solid var(--ame-form-border-focus-color);
    outline-offset: 1px;
}

/* Agenda view (Wave U·U6): a chronological LIST, so it is styled as one — no grid, no time canvas. Each day the
   engine returned is a section: a sticky date heading (a real button, the drill affordance) over a native list of
   entry buttons. Empty days never appear at all (the engine omits them), so the list is dense by construction; an
   empty WINDOW shows the honest empty message instead of a blank box. Entries reuse the day/week accent and tint
   custom properties, so an appointment looks the same wherever it is rendered. */
.ame-scheduler__agenda {
    display: flex;
    flex-direction: column;
    max-height: 420px;
    overflow-y: auto;
}
/* The honest empty state: a short, centered, muted message. */
.ame-scheduler__agenda-empty {
    margin: 0;
    padding: 24px;
    text-align: center;
    font-size: var(--ame-form-font-size-sm, 12px);
    color: var(--ame-text-muted, #8b9e9e);
}
.ame-scheduler__agenda-day {
    display: flex;
    flex-direction: column;
    border-top: 1px solid var(--ame-card-border-color, #e5e7eb);
}
.ame-scheduler__agenda-day:first-child {
    border-top: 0;
}
/* The date heading is a REAL h3 (so AT can jump day to day by heading) wrapping a REAL button (so the drill is
   keyboard reachable). The h3 carries no type scale of its own; the button below sets it. */
.ame-scheduler__agenda-heading {
    margin: 0;
    font: inherit;
    position: sticky;
    top: 0;
    z-index: 1;
    background: var(--ame-surface-muted, #f3f4f6);
}
.ame-scheduler__agenda-daybtn {
    display: flex;
    width: 100%;
    align-items: baseline;
    justify-content: space-between;
    gap: 8px;
    padding: 6px 10px;
    border: 0;
    background: transparent;
    color: var(--ame-form-fg, #1f2937);
    font: inherit;
    font-size: var(--ame-form-font-size-sm, 12px);
    font-weight: 600;
    line-height: 1.4;
    text-align: left;
    cursor: pointer;
    box-sizing: border-box;
}
.ame-scheduler__agenda-daybtn:hover {
    background: var(--ame-card-bg, #ffffff);
}
/* Today: the same 15%-alpha Primary wash the week header and month cell use (no color-mix dependency). */
.ame-scheduler__agenda-day--today .ame-scheduler__agenda-heading {
    background: rgba(67, 97, 238, 0.15);
}
.ame-scheduler__agenda-count {
    flex: none;
    font-weight: 400;
    color: var(--ame-text-muted, #8b9e9e);
}
/* The day's items: a native list, unstyled to a plain stack. */
.ame-scheduler__agenda-list {
    list-style: none;
    margin: 0;
    padding: 4px 8px 8px 8px;
    display: flex;
    flex-direction: column;
    gap: 4px;
}
.ame-scheduler__agenda-item {
    min-width: 0;
    /* The day heading is sticky inside this 420px scroller, so a Tab onto a row could scroll it to the very top
       of the box and leave it hidden BEHIND that heading (WCAG 2.4.11, the frozen-column species). Reserving the
       heading's own height (its small type scale plus its 6px x 2 padding, rounded up) as scroll margin makes the
       browser stop the scroll short, so the focused row always lands clear of it. */
    scroll-margin-top: 36px;
}
/* One entry: the block tint recipe, flowed. A fixed time column keeps the titles aligned down the day. */
.ame-scheduler__agenda-entry {
    display: flex;
    width: 100%;
    align-items: baseline;
    gap: 8px;
    padding: 4px 8px;
    border: 0;
    border-left: 3px solid var(--ame-sch-accent, var(--rz-primary, #4361ee));
    border-radius: 4px;
    background: var(--ame-sch-tint, rgba(67, 97, 238, 0.15));
    color: var(--ame-form-fg, #1f2937);
    font: inherit;
    font-size: 12px;
    line-height: 1.4;
    text-align: left;
    cursor: pointer;
    /* The FOCUSED element is this button, so it carries the row's scroll margin too: focusing it must never
       scroll it up under the sticky day heading (WCAG 2.4.11). */
    scroll-margin-top: 36px;
    box-sizing: border-box;
}
.ame-scheduler__agenda-time {
    flex: none;
    min-width: 52px;
    color: var(--ame-text-muted, #8b9e9e);
    font-variant-numeric: tabular-nums;
}
.ame-scheduler__agenda-title {
    min-width: 0;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}
/* All-day banner: bolder, so a banner reads as a band rather than a timed entry (the month chip rule). */
.ame-scheduler__agenda-entry--allday .ame-scheduler__agenda-title {
    font-weight: 600;
}
/* Every agenda affordance is a normal tab stop (a list is what Tab is for), so each keeps the shared ring. */
.ame-scheduler__agenda-daybtn:focus-visible,
.ame-scheduler__agenda-entry:focus-visible {
    outline: 2px solid var(--ame-form-border-focus-color);
    outline-offset: 1px;
}

/* ---------- Gantt (Wave 7d i1) ----------
   The horizontal-time-axis cousin of the scheduler. AMETimeScaleMath (AME.Design) owns all
   geometry; the component turns its window-relative fractions into the percentages/pixels below.
   Frame = Surface on a Border hairline, identical to the scheduler. A top axis header (a fixed
   task-column-width spacer + absolutely-positioned tick labels) sits above a scrollable body: a CSS
   grid of a fixed task column + a 1fr lane, its template + height set inline. The task column holds
   one label cell per task (RowHeight px); the lane is one relative cell over every row holding the
   vertical gridlines, the per-row rowline separators, the absolutely-positioned bars, and the today
   line. Each bar carries its accent + 15%-alpha tint as inline custom properties
   (--ame-gantt-accent / --ame-gantt-tint) the rules below consume; the solid progress child fills
   the accent. The today line reuses the literal Primary the scheduler today-column uses, so there is
   no color-mix dependency and no undefined custom property (the Wave 7b i1 dark-line lesson). */
.ame-gantt {
    display: flex;
    flex-direction: column;
    width: 100%;
    background: var(--ame-card-bg, #ffffff);
    border: 1px solid var(--ame-card-border-color, #e5e7eb);
    border-radius: 6px;
    overflow: hidden;
    box-sizing: border-box;
}
/* Axis header: the same grid template as the body (a fixed task column + a 1fr axis), so the axis
   region lines up over the lane. Separated from the body by a hairline. */
.ame-gantt__header {
    display: grid;
    border-bottom: 1px solid var(--ame-card-border-color, #e5e7eb);
}
/* Header spacer: empty, occupying the task column so the axis starts at the lane's left edge. */
.ame-gantt__axis-spacer {
    grid-column: 1;
}
/* Axis region: a relative band for the absolutely-positioned tick labels (column 2). */
.ame-gantt__axis {
    grid-column: 2;
    position: relative;
    height: 28px;
    border-left: 1px solid var(--ame-card-border-color, #e5e7eb);
}
/* Tick label: muted Sm text pinned at its left:%; nudged a hair right of the gridline. */
.ame-gantt__axis-label {
    position: absolute;
    top: 50%;
    transform: translateY(-50%);
    padding-left: 4px;
    font-size: var(--ame-form-font-size-sm, 12px);
    color: var(--ame-text-muted, #8b9e9e);
    white-space: nowrap;
    user-select: none;
    pointer-events: none;
}
/* Scroll viewport: the body can exceed this, so it scrolls vertically. */
.ame-gantt__scroll {
    max-height: 420px;
    overflow-y: auto;
}
/* The body: a fixed task column + a 1fr lane. The template + explicit height arrive inline. */
.ame-gantt__body {
    display: grid;
    position: relative;
}
/* Task column: flow-stacked label cells, pinned to column 1, spanning every row. */
.ame-gantt__tasks {
    grid-column: 1;
    grid-row: 1 / -1;
    display: flex;
    flex-direction: column;
}
/* Task label cell: a clipped title, Sm, with a top hairline so it aligns with the lane rowlines.
   Height set inline (RowHeight). */
.ame-gantt__task {
    display: flex;
    align-items: center;
    padding: 0 8px;
    border-top: 1px solid var(--ame-card-border-color, #e5e7eb);
    font-size: var(--ame-form-font-size-sm, 12px);
    color: var(--ame-form-fg, #1f2937);
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
    box-sizing: border-box;
    user-select: none;
}
/* Lane: one relative cell pinned to column 2 over every row; bars/gridlines/rowlines/today position
   against it. A left hairline separates it from the task column. */
.ame-gantt__lane {
    grid-column: 2;
    grid-row: 1 / -1;
    position: relative;
    border-left: 1px solid var(--ame-card-border-color, #e5e7eb);
    overflow: hidden;
}
/* Vertical gridline at a tick's left:%, full lane height. */
.ame-gantt__gridline {
    position: absolute;
    top: 0;
    bottom: 0;
    width: 0;
    border-left: 1px solid var(--ame-card-border-color, #e5e7eb);
}
/* Horizontal rowline separator at a row's top:%, full lane width. */
.ame-gantt__rowline {
    position: absolute;
    left: 0;
    right: 0;
    height: 0;
    border-top: 1px solid var(--ame-card-border-color, #e5e7eb);
}
/* Task bar: absolutely positioned over the lane; geometry (top/height/left/width) is inline from
   the engine fractions + row index, already inset within the RowHeight band so rows show a gutter.
   The track is the 15%-alpha tint with a 1px accent border; the solid progress child fills from the
   left. The custom properties carry the per-bar accent + tint. */
.ame-gantt__bar {
    position: absolute;
    box-sizing: border-box;
    min-width: 1px;
    border: 1px solid var(--ame-gantt-accent, var(--rz-primary, #4361ee));
    border-radius: 4px;
    background: var(--ame-gantt-tint, rgba(67, 97, 238, 0.15));
    overflow: hidden;
    cursor: pointer;
}
/* Solid progress fill: the accent colour, width:% of the bar inline. Pinned to the bar's full
   height from the left. */
.ame-gantt__progress {
    height: 100%;
    background: var(--ame-gantt-accent, var(--rz-primary, #4361ee));
    border-radius: 3px 0 0 3px;
}
/* Today line: a 2px Primary vertical rule at today's left:%, over the bars. Reuses the literal
   Primary the scheduler today-column uses (no color-mix, no undefined custom property). */
.ame-gantt__today {
    position: absolute;
    top: 0;
    bottom: 0;
    width: 0;
    border-left: 2px solid var(--rz-primary, #4361ee);
    pointer-events: none;
}
/* Dependency-link overlay (Wave U*U11): one <svg> pinned over the whole lane, drawn AFTER the bars
   and BEFORE the today line so an arrow shows over a bar's semi-transparent track but under the
   today rule. Deliberately NO viewBox here (see the component header comment) — the X coordinates
   ride the SVG <line> percentage rule directly, and the Y coordinates are already plain CSS px, so a
   viewBox + preserveAspectRatio="none" would shear the arrowhead. pointer-events:none throughout so a
   link can never swallow a bar click underneath it. */
.ame-gantt__links {
    position: absolute;
    top: 0;
    left: 0;
    pointer-events: none;
}
/* One elbow segment: geometry only (no fill — a line never closes a shape); stroke is an inline
   attribute per-link (Border normal / Danger critical), the existing per-bar accent inline idiom. */
.ame-gantt__link {
    fill: none;
    stroke-width: 1.5;
    pointer-events: none;
}
/* Critical link (Wave U*U11): a slightly thicker stroke on top of its inline Danger color. */
.ame-gantt__link--critical {
    stroke-width: 2;
}

/* ---------- Timeline (Wave 7d i2) ----------
   The trimmed sibling of the Gantt: events on a horizontal date axis, lane-packed into the fewest
   non-overlapping rows. AMETimelineMath (AME.Design) owns all geometry (window fit, lane packing,
   bar fractions) and reuses AMETimeScaleMath for the axis; the component turns its fractions into
   the percentages/pixels below. Frame = Surface on a Border hairline, identical to the Gantt. Unlike
   the Gantt there is NO task-name column (the header is just the axis, the body a single lane) and NO
   progress fill — a bar is just the token-tinted track. A top axis header (a relative band of
   absolutely-positioned tick labels) sits above a scrollable body: a single relative lane (its height
   = LaneCount × RowHeight set inline) holding the vertical gridlines, the absolutely-positioned bars,
   and the today line. Each bar carries its accent + 15%-alpha tint as inline custom properties
   (--ame-timeline-accent / --ame-timeline-tint) the rules below consume. The today line reuses the
   literal Primary the Gantt/scheduler today-rule uses, so there is no color-mix dependency and no
   undefined custom property (the Wave 7b i1 dark-line lesson). */
.ame-timeline {
    display: flex;
    flex-direction: column;
    width: 100%;
    background: var(--ame-card-bg, #ffffff);
    border: 1px solid var(--ame-card-border-color, #e5e7eb);
    border-radius: 6px;
    overflow: hidden;
    box-sizing: border-box;
}
/* Axis header: a relative band for the absolutely-positioned tick labels, the full width (no task
   column). Separated from the body by a hairline. */
.ame-timeline__header {
    display: flex;
    border-bottom: 1px solid var(--ame-card-border-color, #e5e7eb);
}
.ame-timeline__axis {
    position: relative;
    flex: 1 1 auto;
    height: 28px;
}
/* Tick label: muted Sm text pinned at its left:%; nudged a hair right of the gridline. */
.ame-timeline__axis-label {
    position: absolute;
    top: 50%;
    transform: translateY(-50%);
    padding-left: 4px;
    font-size: var(--ame-form-font-size-sm, 12px);
    color: var(--ame-text-muted, #8b9e9e);
    white-space: nowrap;
    user-select: none;
    pointer-events: none;
}
/* Scroll viewport: the lane can exceed this, so it scrolls vertically. */
.ame-timeline__scroll {
    max-height: 420px;
    overflow-y: auto;
}
/* Lane: one relative cell, the full width, over every packed row; bars/gridlines/today position
   against it. Its explicit height (LaneCount × RowHeight) arrives inline. */
.ame-timeline__lane {
    position: relative;
    overflow: hidden;
}
/* Vertical gridline at a tick's left:%, full lane height. */
.ame-timeline__gridline {
    position: absolute;
    top: 0;
    bottom: 0;
    width: 0;
    border-left: 1px solid var(--ame-card-border-color, #e5e7eb);
}
/* Horizontal rowline separator at a lane's top:%, full lane width (the Gantt __rowline precedent). */
.ame-timeline__rowline {
    position: absolute;
    left: 0;
    right: 0;
    height: 0;
    border-top: 1px solid var(--ame-card-border-color, #e5e7eb);
}
/* Event bar: absolutely positioned over the lane; geometry (top/height/left/width) is inline from
   the engine fractions + lane index, already inset within the RowHeight band so lanes show a gutter.
   The track is the 15%-alpha tint with a 1px accent border (no progress fill — the timed Gantt's
   fill is dropped here). The custom properties carry the per-bar accent + tint; the label is clipped
   inside the track. */
.ame-timeline__bar {
    position: absolute;
    box-sizing: border-box;
    min-width: 1px;
    display: flex;
    align-items: center;
    padding: 0 6px;
    border: 1px solid var(--ame-timeline-accent, var(--rz-primary, #4361ee));
    border-radius: 4px;
    background: var(--ame-timeline-tint, rgba(67, 97, 238, 0.15));
    font-size: var(--ame-form-font-size-sm, 12px);
    color: var(--ame-form-fg, #1f2937);
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
    cursor: pointer;
    user-select: none;
}
/* Today line: a 2px Primary vertical rule at today's left:%, over the bars. Reuses the literal
   Primary the Gantt/scheduler today-rule uses (no color-mix, no undefined custom property). */
.ame-timeline__today {
    position: absolute;
    top: 0;
    bottom: 0;
    width: 0;
    border-left: 2px solid var(--rz-primary, #4361ee);
    pointer-events: none;
}

/* ---------- Diagram (Wave 7f i1) ----------
   A pan/zoom inline-SVG canvas of a directed graph: labeled-box nodes + straight, border-clipped
   edges. AMEDiagramMath + AMEViewport (AME.Design) own all geometry/framing and the pan /
   zoom-to-cursor transform; the component is a thin renderer + gesture surface (a single <g> matrix
   maps the whole world-space scene through the viewport). Frame = Surface on a Border hairline,
   matching the Gantt/Timeline. The grab cursor signals the drag-to-pan affordance, and
   touch-action:none lets the pointer handlers own touch drag/zoom (no browser scroll/zoom fighting
   the canvas). Node fills/borders + edge strokes are inline from the palette (per-item color is
   expected, the chart precedent); only the title text picks up a token var here so it tracks the
   form foreground. */
.ame-diagram {
    width: 100%;
    height: 420px;
    border: 1px solid var(--ame-card-border-color, #e5e7eb);
    border-radius: var(--ame-card-corner-radius, 6px);
    background: var(--ame-card-bg, #fff);
    cursor: grab;
    touch-action: none;
}
/* Node title: centered Sm label. pointer-events:none so the text never intercepts the rect's click
   (the box owns the click), and unselectable so a pan drag doesn't smear a text selection. */
.ame-diagram text {
    font-size: 12px;
    fill: var(--ame-form-fg);
    pointer-events: none;
    user-select: none;
}

/* ---------- Map (Wave 7g i1) ----------
   A tile-source-agnostic slippy map: Web-Mercator tiles on a drag-pan / wheel-zoom surface.
   AMEMercator + AMETileMath (AME.Design) own the projection and the visible-tile set over the
   immutable AMEViewport world↔screen transform; the component is a thin renderer + gesture surface.
   UNLIKE the diagram there is NO transformed layer — each tile is absolutely positioned at the
   engine's SCREEN rect (recomputed on every pan/zoom, the slippy-map idiom). Frame = Surface on a
   Border hairline, matching the Gantt/Timeline/Diagram. The grab cursor signals the drag-to-pan
   affordance, overflow:hidden clips the tile layer to the frame, and touch-action:none lets the
   pointer handlers own touch drag/zoom (no browser scroll/zoom fighting the surface). Every tile is
   pointer-events:none so the ROOT owns the gesture and reads root-relative e.OffsetX/Y. */
.ame-map {
    position: relative;
    width: 100%;
    height: 420px;
    overflow: hidden;
    border: 1px solid var(--ame-card-border-color, #e5e7eb);
    border-radius: var(--ame-card-corner-radius, 6px);
    background: var(--ame-card-bg, #fff);
    cursor: grab;
    touch-action: none;
}
/* Placeholder tile (no TileUrlTemplate): a bordered muted square with a centered Z/X/Y label.
   pointer-events:none so the root owns the pan/zoom; unselectable so a pan drag doesn't smear a text
   selection. The engine supplies left/top/width/height inline. */
.ame-map__tile {
    box-sizing: border-box;
    border: 1px solid var(--ame-card-border-color, #e5e7eb);
    background: var(--ame-surface-2, #f3f4f6);
    display: flex;
    align-items: center;
    justify-content: center;
    font-size: 11px;
    color: var(--ame-text-muted, #8b9e9e);
    pointer-events: none;
    user-select: none;
}
/* Image tile: pointer-events:none so the root owns the gesture (the engine supplies the rect inline;
   draggable="false" on the element stops the browser's native image drag). */
.ame-map__tile-img {
    pointer-events: none;
}
/* Marker overlay (i2): one <svg> covering the surface ABOVE the tiles and OUTSIDE any zoom transform,
   so the pins are screen-constant. pointer-events:none so the ROOT keeps the pan/zoom gesture (a marker
   click is hit-tested in the code-behind, not by the svg); overflow:visible lets a pin near an edge
   draw past the svg box. */
.ame-map__markers {
    position: absolute;
    inset: 0;
    pointer-events: none;
    overflow: visible;
}
/* Marker label: the small muted caption next to a pin's head (the engine/markup places it at x=12
   y=-18 within the pin-local <g>). */
.ame-map__marker-label {
    font-size: 11px;
    fill: var(--ame-text-muted, #8b9e9e);
}

/* ---------- RadialMenu (Wave 8 i2) ----------
   A circular pie-menu overlay: a ring of wedge "slices" (icon + label) around a center hub, invoked at
   the cursor, dismissed by click-outside / Esc, navigable by mouse + keyboard. The Blazor sibling of the
   WPF/Avalonia AMERadialMenu; ALL slice geometry comes from the AME.Design engine (consumed in the
   component, never recomputed) and is emitted as one inline <svg viewBox="0 0 320 320">. This section
   owns only the LOOK (token fills, hover/focus/disabled recolor, the open/close keyframes + per-slice
   stagger) and the fixed host/backdrop boxing; geometry is inline on the elements.

   Colors flow through the suite tokens with the correct light-theme literal fallback on every var(), so a
   missing token never ships a wrong color (the Wave 7b i1 undefined-dark-line lesson): Surface
   var(--ame-card-bg,#ffffff), Border var(--ame-card-border-color,#e5e7eb), Primary
   var(--rz-primary,#4361ee), SurfaceMuted var(--ame-surface-2,#f3f4f6), Text var(--ame-form-fg,#1f2937),
   OnPrimary var(--ame-button-fg-on-primary,#ffffff), TextMuted var(--ame-text-muted,#8b9e9e), and the
   new TextDisabled var(--ame-text-disabled,#b0b6b6) (its first consumer; defined in
   _tokens.designer.css). */

/* Light-dismiss backdrop: a transparent fixed layer catching the outside click. High z so it sits over
   the page, just under the host box. */
.ame-radial__backdrop {
    position: fixed;
    inset: 0;
    z-index: 1200;
    background: transparent;
}

/* Host box: position:fixed, the 320 canvas size, placed at the cursor by ame-radialmenu.js (it writes
   left/top). Over the backdrop. */
.ame-radial {
    position: fixed;
    left: 0;
    top: 0;
    width: 320px;
    height: 320px;
    z-index: 1201;
    pointer-events: none; /* the surface inside re-enables pointer events */
}

/* The focusable keyboard surface filling the host box. Re-enables pointer events for the slices; its
   outline is suppressed (the ring itself is the focus affordance, and the focused slice is highlighted). */
.ame-radial__surface {
    position: relative;
    width: 100%;
    height: 100%;
    pointer-events: auto;
    outline: none;
}

.ame-radial__svg {
    width: 100%;
    height: 100%;
    overflow: visible;
}

/* Open animation: scale + fade the whole ring in, keyed on .is-open. The per-slice stagger is layered on
   the slices below; the surface-level transform gives the unified pop. */
.ame-radial__surface.is-open {
    animation: ame-radial-in var(--ame-anim-duration-base, 220ms) var(--ame-anim-easing-spring, cubic-bezier(0.34, 1.56, 0.64, 1)) both;
    transform-origin: 160px 160px;
}

@keyframes ame-radial-in {
    from { opacity: 0; transform: scale(0.6); }
    to   { opacity: 1; transform: scale(1); }
}

/* Backing ring: a Surface-filled disc under all slices so the inter-slice gaps read as a continuous ring
   (the desktop backing-ellipses parity). */
.ame-radial__backing {
    fill: var(--ame-card-bg, #ffffff);
}

/* Slice group: holds the wedge path + the optional icon <g> + the label <text>. `color` is set per state
   so the nested AMEIcon (currentColor) recolors with the slice; a CSS transition gives the ~100ms hover
   fade. The per-slice open stagger is an animation-delay from the --ame-radial-i ordinal. */
.ame-radial__slice {
    color: var(--ame-form-fg, #1f2937); /* enabled icon color (Text) */
    cursor: pointer;
    transition: color var(--ame-anim-duration-fast, 150ms) var(--ame-anim-easing, ease);
    animation: ame-radial-slice-in var(--ame-anim-duration-base, 220ms) var(--ame-anim-easing, ease) both;
    animation-delay: calc(var(--ame-radial-i, 0) * 30ms);
    transform-origin: 160px 160px;
}

@keyframes ame-radial-slice-in {
    from { opacity: 0; transform: scale(0); }
    to   { opacity: 1; transform: scale(1); }
}

/* Close animation: the reverse of the open pop, keyed on .is-closing (set while the ring stays mounted
   for the close-out phase). FastMs + ease-in (the desktop AnimateClose: scale down + fade on an EaseIn);
   the CSS duration matches AMEMotion.FastMs so the deferred unmount lands as the keyframes finish. The
   slices collapse to the center together (no stagger), overriding the base slice-in animation. */
.ame-radial__surface.is-closing {
    animation: ame-radial-out var(--ame-anim-duration-fast, 150ms) ease-in both;
    transform-origin: 160px 160px;
}
.ame-radial__surface.is-closing .ame-radial__slice {
    animation: ame-radial-slice-out var(--ame-anim-duration-fast, 150ms) ease-in both;
    animation-delay: 0s;
}

@keyframes ame-radial-out {
    from { opacity: 1; transform: scale(1); }
    to   { opacity: 0; transform: scale(0.6); }
}

@keyframes ame-radial-slice-out {
    from { opacity: 1; transform: scale(1); }
    to   { opacity: 0; transform: scale(0); }
}

/* The wedge path: Surface fill on a Border hairline normally. The fill/stroke transition gives the hover
   recolor its fade. */
.ame-radial__wedge {
    fill: var(--ame-card-bg, #ffffff);
    stroke: var(--ame-card-border-color, #e5e7eb);
    stroke-width: 1.5;
    transition:
        fill var(--ame-anim-duration-fast, 150ms) var(--ame-anim-easing, ease),
        stroke var(--ame-anim-duration-fast, 150ms) var(--ame-anim-easing, ease);
}

/* Slice icon: a nested AMEIcon <svg>, pointer-transparent so the wedge under it owns the click. It paints
   in currentColor, inherited from the slice group, so the state rules below recolor it. */
.ame-radial__icon {
    pointer-events: none;
}

/* Slice label: TextMuted normally; pointer-transparent so the wedge owns the click. */
.ame-radial__label {
    fill: var(--ame-text-muted, #8b9e9e);
    pointer-events: none;
    user-select: none;
}

/* Hover + keyboard-focus highlight: Primary fill/stroke; the icon recolors to OnPrimary (via the group
   `color`) and the label to OnPrimary so both stay legible on the accent. Only enabled slices (the
   :hover guard plus the is-disabled override below). */
.ame-radial__slice:hover .ame-radial__wedge,
.ame-radial__slice.is-focused .ame-radial__wedge {
    fill: var(--rz-primary, #4361ee);
    stroke: var(--rz-primary, #4361ee);
}
.ame-radial__slice:hover,
.ame-radial__slice.is-focused {
    color: var(--ame-button-fg-on-primary, #ffffff); /* icon -> OnPrimary */
}
.ame-radial__slice:hover .ame-radial__label,
.ame-radial__slice.is-focused .ame-radial__label {
    fill: var(--ame-button-fg-on-primary, #ffffff);
}

/* Disabled slice: SurfaceMuted fill, TextDisabled icon + label, inert (no hover, no click). The
   pointer-events:none stops both the CSS hover and the click reaching the wedge. */
.ame-radial__slice.is-disabled {
    color: var(--ame-text-disabled, #b0b6b6); /* icon -> TextDisabled */
    cursor: default;
    pointer-events: none;
}
.ame-radial__slice.is-disabled .ame-radial__wedge {
    fill: var(--rz-base-100, #f3f4f6);
    stroke: var(--ame-card-border-color, #e5e7eb);
}
.ame-radial__slice.is-disabled .ame-radial__label,
.ame-radial__label.is-disabled {
    fill: var(--ame-text-disabled, #b0b6b6);
}

/* Center hub: a SurfaceMuted disc on a Border hairline; pointer-transparent so a click on it falls
   through to the backdrop (closing) since the hub is a label, not a slice. */
.ame-radial__hub {
    fill: var(--rz-base-100, #f3f4f6);
    stroke: var(--ame-card-border-color, #e5e7eb);
    stroke-width: 1;
    pointer-events: none;
}
.ame-radial__center-icon {
    color: var(--rz-primary, #4361ee); /* center glyph -> Primary accent */
    pointer-events: none;
}
.ame-radial__center-text {
    fill: var(--ame-form-fg, #1f2937);
    font-size: 11px;
    text-anchor: middle;
    pointer-events: none;
    user-select: none;
}
/* Rich center content overlay: centered on the hub, pointer-transparent (a label, not interactive). */
.ame-radial__center-content {
    position: absolute;
    left: 50%;
    top: 50%;
    transform: translate(-50%, -50%);
    max-width: 64px;
    text-align: center;
    font-size: 11px;
    color: var(--ame-form-fg, #1f2937);
    pointer-events: none;
}

@media (prefers-reduced-motion: reduce) {
    .ame-radial__surface.is-open,
    .ame-radial__surface.is-closing,
    .ame-radial__slice,
    .ame-radial__surface.is-closing .ame-radial__slice { animation: none; }
}

/* ---------- PropertyGrid (Wave 8 i4) ---------- */
/* Engine-driven property inspector. A two-column (Name | Value) grid grouped by category, with a
   per-property editor and a recursive nested grid inside collection cells. Surfaces: Card bg on a
   Card-border hairline; header strip = base-100 (SurfaceMuted). All colors are the real theme tokens
   with light-hex fallbacks. */
.ame-propgrid {
    display: flex;
    flex-direction: column;
    background: var(--ame-card-bg, #ffffff);
    border: 1px solid var(--ame-card-border-color, #e5e7eb);
    border-radius: var(--ame-control-corner-radius, 6px);
    color: var(--ame-form-fg, #1f2937);
    font-size: var(--ame-form-font-size, 14px);
    overflow: hidden;
}
/* A nested grid (collection drill-down) drops the chrome — it sits inside a value cell, not as a
   standalone card — so it reads as an inline sub-table. */
.ame-propgrid .ame-propgrid {
    border: none;
    border-radius: 0;
    background: transparent;
}

/* Filter box: root grid only. */
.ame-propgrid__filter {
    padding: 8px;
    border-bottom: 1px solid var(--ame-card-border-color, #e5e7eb);
    background: var(--rz-base-100, #f3f4f6);
}
.ame-propgrid__filter-input {
    box-sizing: border-box;
    width: 100%;
    height: var(--ame-form-height-sm, 28px);
    padding: 0 var(--ame-form-padding-x, 10px);
    background: var(--ame-form-bg, #ffffff);
    color: var(--ame-form-fg, #1f2937);
    border: 1px solid var(--ame-form-border-color, #e5e7eb);
    border-radius: var(--ame-control-corner-radius, 6px);
    font-family: inherit;
    font-size: var(--ame-form-font-size-sm, 12px);
    outline: none;
}
.ame-propgrid__filter-input:focus-visible {
    border-color: var(--ame-form-border-focus-color, #4361ee);
}
.ame-propgrid__filter-input::placeholder { color: var(--ame-text-disabled, #b0b6b6); }

.ame-propgrid__body { display: flex; flex-direction: column; }

/* Category header strip: SurfaceMuted band, muted uppercase label. */
.ame-propgrid__category {
    padding: 6px var(--ame-grid-cell-padding-x, 12px);
    background: var(--rz-base-100, #f3f4f6);
    color: var(--ame-text-muted, #8b9e9e);
    font-size: var(--ame-form-font-size-sm, 12px);
    font-weight: 600;
    text-transform: uppercase;
    letter-spacing: 0.04em;
    border-bottom: 1px solid var(--ame-card-border-color, #e5e7eb);
}

/* One Name | Value line. Two columns; the value column flexes. */
.ame-propgrid__row {
    display: grid;
    grid-template-columns: minmax(96px, 40%) 1fr;
    align-items: center;
    min-height: var(--ame-grid-row-height-sm, 28px);
    border-bottom: 1px solid var(--ame-card-border-color, #e5e7eb);
}
.ame-propgrid__row:last-child { border-bottom: none; }

.ame-propgrid__name {
    padding: 4px var(--ame-grid-cell-padding-x, 12px);
    color: var(--ame-text-muted, #8b9e9e);
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}
.ame-propgrid__value {
    padding: 4px var(--ame-grid-cell-padding-x, 12px);
    min-width: 0; /* let the editors shrink instead of overflowing the cell */
    color: var(--ame-form-fg, #1f2937);
}

/* Editable text + select: borderless until focus so the grid stays calm; full-width within the cell. */
.ame-propgrid__text,
.ame-propgrid__select {
    box-sizing: border-box;
    width: 100%;
    height: var(--ame-form-height-sm, 28px);
    padding: 0 6px;
    background: var(--ame-form-bg, #ffffff);
    color: var(--ame-form-fg, #1f2937);
    border: 1px solid transparent;
    border-radius: 4px;
    font-family: inherit;
    font-size: var(--ame-form-font-size-sm, 12px);
    outline: none;
}
.ame-propgrid__text:hover,
.ame-propgrid__select:hover { border-color: var(--ame-form-border-color, #e5e7eb); }
.ame-propgrid__text:focus-visible,
.ame-propgrid__select:focus-visible { border-color: var(--ame-form-border-focus-color, #4361ee); }

.ame-propgrid__check {
    width: 16px;
    height: 16px;
    margin: 0;
    accent-color: var(--ame-form-border-focus-color, #4361ee);
    cursor: pointer;
}

/* Read-only value: plain muted text, no editor affordance. */
.ame-propgrid__readonly {
    color: var(--ame-form-fg, #1f2937);
    overflow-wrap: anywhere;
}

/* Collection cell: a native <details> expander. Summary = the engine's "[N items]" header. */
.ame-propgrid__collection { width: 100%; }
.ame-propgrid__collection-summary {
    cursor: pointer;
    color: var(--ame-text-muted, #8b9e9e);
    font-size: var(--ame-form-font-size-sm, 12px);
    user-select: none;
    list-style-position: inside;
}
.ame-propgrid__collection-summary:focus-visible {
    outline: 2px solid var(--ame-form-border-focus-color, #4361ee);
    outline-offset: 2px;
    border-radius: 3px;
}
.ame-propgrid__collection-body {
    margin-top: 4px;
    padding-left: 8px;
    border-left: 2px solid var(--ame-card-border-color, #e5e7eb);
}
.ame-propgrid__collection-item { padding: 2px 0; }
.ame-propgrid__collection-item + .ame-propgrid__collection-item {
    border-top: 1px dashed var(--ame-card-border-color, #e5e7eb);
}
.ame-propgrid__leaf {
    color: var(--ame-form-fg, #1f2937);
    font-size: var(--ame-form-font-size-sm, 12px);
    overflow-wrap: anywhere;
}
/* Depth-limit / cycle placeholder: TextDisabled, italic — a dead-end marker, never an editor. */
.ame-propgrid__placeholder {
    color: var(--ame-text-disabled, #b0b6b6);
    font-size: var(--ame-form-font-size-sm, 12px);
    font-style: italic;
}

/* ---------- Closable panel (Wave 8 i5) ----------
   Dockable workspace panel: a SurfaceMuted header (icon + title + optional right content + a
   collapse chevron + a close button) over a Surface body. When collapsed the body is removed from
   the DOM and an edge-strip restore handle takes the dock edge; the body-vs-strip swap is driven by
   the .ame-closable-panel--collapsed class plus an @if in the component. */
.ame-closable-panel {
    display: flex;
    flex-direction: column;
    min-height: 0;
    background: var(--ame-card-bg, #ffffff);
    border: 1px solid var(--ame-card-border-color, #e5e7eb);
    border-radius: var(--ame-control-corner-radius, 6px);
    overflow: hidden;
    box-sizing: border-box;
}
.ame-closable-panel__header {
    display: flex;
    align-items: center;
    gap: 8px;
    flex: 0 0 auto;
    padding: 8px 8px 8px 12px;
    background: var(--rz-base-100, var(--ame-card-bg, #f3f4f6));
    border-bottom: 1px solid var(--ame-card-border-color, #e5e7eb);
    color: var(--ame-form-fg, #1f2937);
    font-size: 14px;
    font-weight: 600;
}
.ame-closable-panel__icon {
    flex: 0 0 auto;
    color: var(--ame-form-fg, #1f2937);
}
.ame-closable-panel__title {
    flex: 1 1 auto;
    min-width: 0;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}
.ame-closable-panel__header-right {
    flex: 0 0 auto;
    display: inline-flex;
    align-items: center;
    gap: 6px;
    font-weight: 400;
}
.ame-closable-panel__btn {
    flex: 0 0 auto;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    padding: 2px;
    background: transparent;
    border: none;
    border-radius: 3px;
    color: var(--ame-text-muted, #8b9e9e);
    cursor: pointer;
    transition: background-color var(--ame-anim-duration-base) var(--ame-anim-easing),
                color var(--ame-anim-duration-base) var(--ame-anim-easing);
}
.ame-closable-panel__btn:hover {
    background: var(--ame-hover, rgba(0, 0, 0, 0.05));
    color: var(--rz-primary, #4361ee);
}
.ame-closable-panel__btn:focus-visible {
    outline: 2px solid var(--ame-form-border-focus-color, #4361ee);
    outline-offset: -2px;
}
.ame-closable-panel__body {
    flex: 1 1 auto;
    min-height: 0;
    padding: 12px;
    background: var(--ame-card-bg, #ffffff);
    overflow: auto;
}

/* Collapsed restore strip: a thin SurfaceMuted handle pinned to the dock edge. The edge modifier
   sets which border the strip thickens and how the panel shrinks to a sliver. */
.ame-closable-panel--collapsed {
    overflow: visible;
}
.ame-closable-panel__edge {
    display: block;
    flex: 0 0 auto;
    padding: 0;
    background: var(--rz-base-100, var(--ame-card-bg, #f3f4f6));
    border: none;
    cursor: pointer;
    transition: background-color var(--ame-anim-duration-base) var(--ame-anim-easing);
}
.ame-closable-panel__edge:hover {
    background: color-mix(in srgb, var(--rz-primary, #4361ee) 12%, var(--rz-base-100, #f3f4f6));
}
.ame-closable-panel__edge:focus-visible {
    outline: 2px solid var(--ame-form-border-focus-color, #4361ee);
    outline-offset: -2px;
}
/* Left/Right docks collapse WIDTH → a vertical sliver strip on that side. */
.ame-closable-panel__edge--left,
.ame-closable-panel__edge--right {
    width: 8px;
    align-self: stretch;
    min-height: 24px;
}
.ame-closable-panel__edge--left { border-right: 1px solid var(--ame-card-border-color, #e5e7eb); }
.ame-closable-panel__edge--right { border-left: 1px solid var(--ame-card-border-color, #e5e7eb); }
/* Top/Bottom/Center docks collapse HEIGHT → a horizontal sliver strip on that edge. */
.ame-closable-panel__edge--top,
.ame-closable-panel__edge--bottom {
    height: 8px;
    width: 100%;
}
.ame-closable-panel__edge--top { border-bottom: 1px solid var(--ame-card-border-color, #e5e7eb); }
.ame-closable-panel__edge--bottom { border-top: 1px solid var(--ame-card-border-color, #e5e7eb); }

/* ---------- Collapsible group (Wave 8 i5) ----------
   An AMEExpander preset: an icon + caption + optional count chip header (the whole button toggles)
   over a body shown via an @if. Reuses the expander chevron-rotation idiom. */
.ame-collapsible-group {
    background: var(--ame-card-bg, #ffffff);
    border: 1px solid var(--ame-card-border-color, #e5e7eb);
    border-radius: var(--ame-control-corner-radius, 6px);
    overflow: hidden;
}
.ame-collapsible-group__header {
    display: flex;
    align-items: center;
    gap: 8px;
    width: 100%;
    padding: 8px 12px;
    background: var(--rz-base-100, var(--ame-card-bg, #f3f4f6));
    border: none;
    color: var(--ame-form-fg, #1f2937);
    font-family: inherit;
    font-size: 14px;
    font-weight: 600;
    text-align: left;
    cursor: pointer;
}
.ame-collapsible-group__header:hover {
    background: var(--ame-hover, rgba(0, 0, 0, 0.03));
}
.ame-collapsible-group__header:focus-visible {
    outline: 2px solid var(--ame-form-border-focus-color, #4361ee);
    outline-offset: -2px;
}
.ame-collapsible-group__icon {
    flex: 0 0 auto;
    color: var(--ame-text-muted, #8b9e9e);
}
.ame-collapsible-group__title {
    flex: 1 1 auto;
    min-width: 0;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}
/* Count chip: PrimarySoft tint + Primary text (matches the chip--selected idiom). */
.ame-collapsible-group__count {
    flex: 0 0 auto;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    min-width: 20px;
    height: 18px;
    padding: 0 6px;
    border-radius: 999px;
    background: color-mix(in srgb, var(--rz-primary, #4361ee) 12%, transparent);
    color: var(--rz-primary, #4361ee);
    font-size: var(--ame-form-font-size-sm, 12px);
    font-weight: 600;
    line-height: 1;
}
.ame-collapsible-group__chevron {
    flex: 0 0 auto;
    color: var(--ame-text-muted, #8b9e9e);
    transition: transform var(--ame-anim-duration-base) var(--ame-anim-easing);
}
.ame-collapsible-group__chevron--open { transform: rotate(180deg); }
.ame-collapsible-group__body {
    padding: 12px;
    background: var(--ame-card-bg, #ffffff);
    border-top: 1px solid var(--ame-card-border-color, #e5e7eb);
}

/* ---------- CheckTreeView (Wave 8 i6) ----------
   A recursive tri-state check tree. The cascade math lives in AME.Design (AMECheckTreeMath, shared by
   the three stacks); this section owns only the LOOK. Each ICheckable node is one role="treeitem" row:
   a tri-state checkbox (the indeterminate dash is a DOM property set from ame-tristate.js, since it has
   no HTML attribute) + a label; nodes with children render the SAME component as a role="group" sublist.
   The root role="tree" is the focusable keyboard surface (Ctrl+A / Ctrl+Shift+A / Space). Indent is row
   padding (set inline per level) so the hover/focus background spans the full row. Colors flow through the
   suite tokens with the light-hex fallback on every var() (the Wave 7b i1 undefined-line lesson): Text
   var(--ame-form-fg,#1f2937), SurfaceMuted var(--ame-surface-2,#f3f4f6), Primary
   var(--ame-button-bg-primary,#4361ee), Border var(--ame-card-border-color,#e5e7eb). */
.ame-checktree,
.ame-checktree__group {
    list-style: none;
    margin: 0;
    padding: 0;
}
.ame-checktree {
    font-size: var(--ame-form-font-size, 14px);
    color: var(--ame-form-fg, #1f2937);
    user-select: none;
    outline: none;
}
/* Root keyboard focus: a soft Primary ring so the operator sees which tree owns Ctrl+A / Space. */
.ame-checktree:focus-visible {
    outline: 2px solid var(--ame-form-border-focus-color, #4361ee);
    outline-offset: -2px;
    border-radius: var(--ame-control-corner-radius, 6px);
}
.ame-checktree__item { display: block; }
/* The clickable row: checkbox + label. Padding-left is set inline (8px + 20px/level) so the hover/focus
   highlight still spans the full width. A 2px transparent left border reserves the marker gutter so the
   focused row's accent marker does not shift the content. */
.ame-checktree__row {
    display: flex;
    align-items: center;
    gap: 8px;
    padding: 4px 8px;
    border-left: 2px solid transparent;
    border-radius: 4px;
    cursor: default;
    transition: background-color var(--ame-anim-duration-fast, 150ms) var(--ame-anim-easing, ease);
}
.ame-checktree__row:hover {
    background: var(--ame-surface-2, #f3f4f6);
}
/* Keyboard-focused node (the one Space acts on): PrimarySoft fill + a Primary left-marker, matching the
   chip--selected / treeview--selected idiom. The marker replaces the reserved transparent left border. */
.ame-checktree__item.is-focused > .ame-checktree__row {
    background: color-mix(in srgb, var(--ame-button-bg-primary, #4361ee) 12%, transparent);
    border-left-color: var(--ame-button-bg-primary, #4361ee);
}
/* The native checkbox: accent the tick/dash with Primary (also colors the indeterminate dash). */
.ame-checktree__box {
    flex: 0 0 auto;
    width: 16px;
    height: 16px;
    margin: 0;
    accent-color: var(--ame-button-bg-primary, #4361ee);
    cursor: pointer;
}
.ame-checktree__label {
    flex: 1 1 auto;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

/* ---------- MultiSelectList (Wave 8 i6) ----------
   A flat list with Extended-mode multi-selection (plain / Ctrl / Shift click + Ctrl+A / Esc). The
   click/range rules live in AME.Design (AMEMultiSelectMath, shared by the three stacks); this section
   owns only the LOOK. DISTINCT from the existing .ame-multiselect dropdown picker — this is a flat,
   always-open role="listbox". A selected row takes the is-selected class: a PrimarySoft fill + a Primary
   left-marker (the chip--selected idiom), with text var(--ame-form-fg) so the label stays legible on the
   soft tint. The host is the focusable keyboard surface; a blank-area click on it clears. Colors flow
   through the suite tokens with the light-hex fallback on every var() (the Wave 7b i1 undefined-line
   lesson). */
.ame-multiselectlist {
    display: flex;
    flex-direction: column;
    gap: 1px;
    padding: 4px;
    background: var(--ame-card-bg, #ffffff);
    border: 1px solid var(--ame-card-border-color, #e5e7eb);
    border-radius: var(--ame-control-corner-radius, 6px);
    font-size: var(--ame-form-font-size, 14px);
    color: var(--ame-form-fg, #1f2937);
    user-select: none;
    outline: none;
}
.ame-multiselectlist:focus-visible {
    outline: 2px solid var(--ame-form-border-focus-color, #4361ee);
    outline-offset: -2px;
}
/* A row: a 3px transparent left border reserves the marker gutter so selecting a row does not shift its
   content; the selected modifier fills that border with Primary. */
.ame-multiselectlist__row {
    display: flex;
    align-items: center;
    gap: 8px;
    padding: 6px 10px;
    border-left: 3px solid transparent;
    border-radius: 4px;
    cursor: pointer;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
    transition: background-color var(--ame-anim-duration-fast, 150ms) var(--ame-anim-easing, ease);
}
.ame-multiselectlist__row:hover {
    background: var(--ame-surface-2, #f3f4f6);
}
/* Selected row: PrimarySoft fill + Primary left-marker (the chip--selected recipe). Text keeps the form
   foreground so the label is legible on the soft tint; hover keeps the selected fill. */
.ame-multiselectlist__row.is-selected,
.ame-multiselectlist__row.is-selected:hover {
    background: color-mix(in srgb, var(--ame-button-bg-primary, #4361ee) 12%, transparent);
    border-left-color: var(--ame-button-bg-primary, #4361ee);
    color: var(--ame-form-fg, #1f2937);
}
.ame-multiselectlist__empty {
    padding: 12px;
    text-align: center;
    font-size: var(--ame-form-font-size-sm, 12px);
    color: var(--ame-text-muted, #8b9e9e);
}

/* ---------- CircularProgressBar ---------- */
/* Token map: track = SurfaceMuted var(--rz-base-100, #f3f4f6) (the gauge/track precedent), progress
   arc = Primary var(--rz-primary, #4361ee), secondary arc = Secondary var(--rz-secondary, #6b7280), percentage
   text = Text var(--ame-form-fg, #1f2937), caption = TextMuted var(--ame-text-muted, #8b9e9e). The
   value tween rides a CSS transition on stroke-dashoffset; indeterminate is a spinning fixed dash. */
.ame-circprog {
    position: relative;
    display: inline-block;
    width: 100px;
    height: 100px;
    line-height: 0;
}
.ame-circprog__svg {
    display: block;
    width: 100%;
    height: 100%;
    overflow: visible;
}
.ame-circprog__track {
    stroke: var(--rz-base-100, #f3f4f6);
}
.ame-circprog__arc {
    stroke: var(--rz-primary, #4361ee);
    transition: stroke-dashoffset var(--ame-anim-duration-base, 220ms) var(--ame-anim-easing-standard, ease);
}
.ame-circprog__sec-track {
    stroke: var(--rz-base-100, #f3f4f6);
}
.ame-circprog__sec-arc {
    stroke: var(--rz-secondary, #6b7280);
    transition: stroke-dashoffset var(--ame-anim-duration-base, 220ms) var(--ame-anim-easing-standard, ease);
}

/* Center overlay: the percentage + optional caption, absolutely centered over the svg. An HTML div
   stack (NOT an SVG text element, which Razor reserves), pointer-transparent so it never eats clicks. */
.ame-circprog__center {
    position: absolute;
    inset: 0;
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: 2px;
    pointer-events: none;
    line-height: 1.1;
    text-align: center;
}
.ame-circprog__value {
    font-size: var(--ame-form-font-size, 14px);
    font-weight: 600;
    color: var(--ame-form-fg, #1f2937);
}
.ame-circprog__caption {
    color: var(--ame-text-muted, #8b9e9e);
}

/* Indeterminate: the fixed quarter-dash arc spins continuously. The arc carries an inline
   transform="rotate(-90 50 50)" base, but the spin sweeps a full 360deg so the parked angle is
   cosmetic; transform-box:fill-box pins the rotation origin to the circle center. */
.ame-circprog--indeterminate .ame-circprog__arc {
    transform-box: fill-box;
    transform-origin: 50% 50%;
    transition: none;
    animation: ame-circprog-spin 1.2s linear infinite;
}
@keyframes ame-circprog-spin {
    from { transform: rotate(0deg); }
    to   { transform: rotate(360deg); }
}

@media (prefers-reduced-motion: reduce) {
    .ame-circprog__arc,
    .ame-circprog__sec-arc {
        transition: none;
    }
    .ame-circprog--indeterminate .ame-circprog__arc {
        animation: none;
    }
}

/* ---------- AnimatedIcon ---------- */
/* Wraps AMEIcon with one of four looping micro-animations, applied only when IsAnimating adds the
   class. transform-origin centers spin/pulse on the glyph; prefers-reduced-motion disables all four. */
.ame-anim {
    display: inline-flex;
    line-height: 0;
    transform-origin: center;
}
.ame-anim--spin   { animation: ame-anim-spin 1.2s linear infinite; }
.ame-anim--pulse  { animation: ame-anim-pulse 1.2s var(--ame-anim-easing-standard, ease-in-out) infinite; }
.ame-anim--bounce { animation: ame-anim-bounce 1s var(--ame-anim-easing-standard, ease-in-out) infinite; }
.ame-anim--fade   { animation: ame-anim-fade 1.4s var(--ame-anim-easing-standard, ease-in-out) infinite; }

@keyframes ame-anim-spin {
    from { transform: rotate(0deg); }
    to   { transform: rotate(360deg); }
}
@keyframes ame-anim-pulse {
    0%, 100% { transform: scale(1); }
    50%      { transform: scale(1.15); }
}
@keyframes ame-anim-bounce {
    0%, 100% { transform: translateY(0); }
    50%      { transform: translateY(-25%); }
}
@keyframes ame-anim-fade {
    0%, 100% { opacity: 1; }
    50%      { opacity: 0.4; }
}

@media (prefers-reduced-motion: reduce) {
    .ame-anim--spin,
    .ame-anim--pulse,
    .ame-anim--bounce,
    .ame-anim--fade {
        animation: none;
    }
}

/* ============================================================
   AMETaskBoard (Wave 10 i1) — static Kanban board: a horizontal
   row of columns; each column = a header (title + count chip +
   WIP indicator) over a vertical stack of default cards. All
   colors flow through the --ame-taskboard-* tokens (which map to
   the --rz-{severity} chain) so every theme variant re-colors the
   board for free. Severity drives the left accent bar via a card
   modifier class; no inline style vars.
   ============================================================ */

.ame-taskboard {
    display: flex;
    flex-direction: row;
    align-items: flex-start;
    gap: 12px;
    overflow-x: auto;
    padding: 4px;
}

.ame-taskboard__column {
    display: flex;
    flex-direction: column;
    flex: 0 0 auto;
    width: 280px;
    max-height: 100%;
    background: var(--ame-taskboard-column-bg);
    border: 1px solid var(--ame-taskboard-column-border);
    border-radius: 8px;
}

.ame-taskboard__column-header {
    display: flex;
    align-items: center;
    gap: 8px;
    padding: 10px 12px;
    border-bottom: 1px solid var(--ame-taskboard-column-border);
}

.ame-taskboard__column-title {
    flex: 1 1 auto;
    font-size: 13px;
    font-weight: 600;
    color: var(--ame-taskboard-column-header-fg);
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.ame-taskboard__count {
    flex: 0 0 auto;
    min-width: 20px;
    padding: 1px 7px;
    border-radius: 999px;
    background: var(--ame-taskboard-count-bg);
    color: var(--ame-taskboard-count-fg);
    font-size: 11px;
    font-weight: 600;
    font-variant-numeric: tabular-nums;
    text-align: center;
}

/* WIP indicator: a count/limit pill. Full tints warning, Over tints danger; Ok stays neutral
   (reuses the count chip palette). None is not rendered at all (gated in the markup). */
.ame-taskboard__wip {
    flex: 0 0 auto;
    padding: 1px 7px;
    border-radius: 999px;
    font-size: 11px;
    font-weight: 600;
    font-variant-numeric: tabular-nums;
    background: var(--ame-taskboard-count-bg);
    color: var(--ame-taskboard-count-fg);
}

.ame-taskboard__wip-sep {
    opacity: 0.6;
    margin: 0 1px;
}

.ame-taskboard__wip--full {
    background: var(--ame-taskboard-wip-full-bg);
    color: var(--ame-taskboard-wip-full-fg);
}

.ame-taskboard__wip--over {
    background: var(--ame-taskboard-wip-over-bg);
    color: var(--ame-taskboard-wip-over-fg);
}

.ame-taskboard__cards {
    display: flex;
    flex-direction: column;
    gap: 8px;
    padding: 10px;
    overflow-y: auto;
}

/* Card: a left accent bar (the __accent element, transparent unless a severity modifier colors it)
   beside the body. The flex row keeps the bar full-height next to the text. */
.ame-taskboard__card {
    display: flex;
    align-items: stretch;
    gap: 0;
    background: var(--ame-taskboard-card-bg);
    border: 1px solid var(--ame-taskboard-card-border);
    border-radius: 6px;
    overflow: hidden;
    box-shadow: var(--ame-card-shadow-sm, 0 1px 2px rgba(0, 0, 0, 0.06));
}

.ame-taskboard__accent {
    flex: 0 0 auto;
    width: 4px;
    align-self: stretch;
    background: transparent;
}

.ame-taskboard__card-body {
    flex: 1 1 auto;
    display: flex;
    flex-direction: column;
    gap: 4px;
    padding: 10px 12px;
    min-width: 0;
}

.ame-taskboard__card-title {
    font-size: 13px;
    font-weight: 600;
    color: var(--ame-taskboard-card-fg);
    word-break: break-word;
}

.ame-taskboard__card-desc {
    font-size: 12px;
    line-height: 1.4;
    color: var(--ame-taskboard-card-desc-fg);
    word-break: break-word;
}

.ame-taskboard__card-tags {
    display: flex;
    flex-wrap: wrap;
    gap: 4px;
    margin-top: 2px;
}

.ame-taskboard__tag {
    padding: 1px 7px;
    border-radius: 999px;
    background: var(--ame-taskboard-tag-bg);
    color: var(--ame-taskboard-tag-fg);
    font-size: 11px;
    line-height: 1.5;
    white-space: nowrap;
}

.ame-taskboard__card-assignee {
    margin-top: 2px;
    font-size: 11px;
    color: var(--ame-taskboard-card-desc-fg);
}

/* Severity accent: the card modifier class colors the left bar from the per-board token (which falls
   back to the --rz-{severity} hue). Neutral / no severity leaves the bar transparent = no accent. */
.ame-taskboard__card--success .ame-taskboard__accent {
    background: var(--ame-taskboard-accent-success);
}

.ame-taskboard__card--warning .ame-taskboard__accent {
    background: var(--ame-taskboard-accent-warning);
}

.ame-taskboard__card--danger .ame-taskboard__accent {
    background: var(--ame-taskboard-accent-danger);
}

.ame-taskboard__card--info .ame-taskboard__accent {
    background: var(--ame-taskboard-accent-info);
}

/* Drag-and-drop (i2). A draggable card shows the grab affordance; the keyboard-focusable card draws the
   shared focus ring (the same outline color var + thickness every AME control uses — NOT a hardcoded
   ad-hoc value). Only present when AllowDragDrop renders draggable/tabindex on the card. */
.ame-taskboard__card[draggable="true"] {
    cursor: grab;
}

.ame-taskboard__card[draggable="true"]:active {
    cursor: grabbing;
}

.ame-taskboard__card:focus-visible {
    outline: 2px solid var(--ame-form-border-focus-color);
    outline-offset: 2px;
}

/* Add / remove affordances (i3). Both are muted ghost buttons that reuse the existing taskboard tokens
   (no new palette, no hardcoded hex): a transparent fill that gains a subtle count-chip wash on hover.
   The add "+" sits at the right of the column header (after the count / WIP chips); the remove "×" is a
   small glyph pinned to the top-right of the card (the last flex item beside the accent + body). */
.ame-taskboard__add {
    flex: 0 0 auto;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 22px;
    height: 22px;
    padding: 0;
    border: 1px solid transparent;
    border-radius: 6px;
    background: transparent;
    color: var(--ame-taskboard-card-desc-fg);
    font-size: 16px;
    line-height: 1;
    cursor: pointer;
}

.ame-taskboard__add:hover {
    background: var(--ame-taskboard-count-bg);
    color: var(--ame-taskboard-column-header-fg);
}

.ame-taskboard__add:focus-visible {
    outline: 2px solid var(--ame-form-border-focus-color);
    outline-offset: 2px;
}

.ame-taskboard__remove {
    flex: 0 0 auto;
    align-self: flex-start;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 20px;
    height: 20px;
    margin: 6px 6px 0 0;
    padding: 0;
    border: none;
    border-radius: 6px;
    background: transparent;
    color: var(--ame-taskboard-card-desc-fg);
    font-size: 14px;
    line-height: 1;
    cursor: pointer;
}

.ame-taskboard__remove:hover {
    background: var(--ame-taskboard-count-bg);
    color: var(--ame-taskboard-card-fg);
}

.ame-taskboard__remove:focus-visible {
    outline: 2px solid var(--ame-form-border-focus-color);
    outline-offset: 1px;
}

/* Swimlanes (i5) — an optional static 2-D (lane x column) grid. When Swimlanes is supplied the board is a
   vertical stack of lanes (each a labeled region); a lane is a header over the EXISTING columns row. The
   lane header reuses the column-header token chain (no new tokens, no hex); __lane-columns mirrors the
   .ame-taskboard flex-row so each lane hosts the i1-i4 columns row unchanged. With no Swimlanes none of
   these rules apply and the single-axis row renders byte-identical to i4. */
.ame-taskboard__lane {
    display: flex;
    flex-direction: column;
    gap: 6px;
    flex: 0 0 auto;
}

.ame-taskboard__lane-header {
    padding: 4px 8px;
    font-size: 12px;
    font-weight: 700;
    color: var(--ame-taskboard-column-header-fg);
    border-bottom: 1px solid var(--ame-taskboard-column-border);
}

.ame-taskboard__lane-columns {
    display: flex;
    flex-direction: row;
    align-items: flex-start;
    gap: 12px;
}

/* In the 2-D path the root .ame-taskboard switches from a row of columns to a column of lanes; the
   columns flex-row lives in __lane-columns instead. The single-axis path keeps the root flex-row. */
.ame-taskboard:has(.ame-taskboard__lane) {
    flex-direction: column;
    align-items: stretch;
}

/* Visually-hidden polite live region (i3) — off-screen but readable by assistive tech, so a refused
   keyboard move can be announced without any visible chrome. The standard sr-only clip pattern. */
.ame-sr-only {
    position: absolute;
    width: 1px;
    height: 1px;
    margin: -1px;
    padding: 0;
    overflow: hidden;
    clip: rect(0, 0, 0, 0);
    white-space: nowrap;
    border: 0;
}

/* ============================================================
   AMEChatThread (Wave 10 chat-i1) — a static conversation log: a
   scrollable vertical list of role-aligned, role-tinted message
   bubbles, or a centered empty state when there are no messages.
   Role tint + alignment flow through the bubble/row modifier classes
   (the StatusPill/Badge/Alert pattern) mapping to the --ame-chat-*
   tokens (which chain to the --rz-* palette), so every theme variant
   re-colors every bubble for free. No inline style vars. The USER
   bubble's text is the on-accent wash so a primary-tinted bubble keeps
   contrast (the a11y rule); the bubble role is announced to AT via a
   visually-hidden role prefix in the markup (.ame-sr-only), not by
   tint/alignment alone. DISPLAY-ONLY: no composer, no status chrome.
   ============================================================ */

.ame-chat {
    display: flex;
    flex-direction: column;
    min-height: 0;
}

.ame-chat__scroll {
    display: flex;
    flex-direction: column;
    gap: 8px;
    padding: 12px;
    overflow-y: auto;
}

/* A row is the full-width flex line; its modifier aligns the bubble to the leading/center/trailing edge
   (align-self in the column flow) and matches the text-align so a multi-line bubble reads from the right
   edge for the user. The bubble itself is capped so a long line wraps instead of spanning the whole log. */
.ame-chat__row {
    display: flex;
    flex-direction: column;
    max-width: 100%;
}

.ame-chat__row--leading {
    align-self: flex-start;
    text-align: left;
}

.ame-chat__row--center {
    align-self: center;
    text-align: center;
}

.ame-chat__row--trailing {
    align-self: flex-end;
    text-align: right;
}

/* The bubble: a rounded card, role-tinted via its modifier. The default (assistant) fill is a neutral
   surface with a hairline border + the base text; user is the accent fill with on-accent text; system is a
   muted, subtle, centered note. max-width keeps long messages readable rather than full-bleed. */
.ame-chat__bubble {
    display: inline-flex;
    align-items: flex-start;
    gap: 8px;
    max-width: 78ch;
    padding: 8px 12px;
    border-radius: 12px;
    background: var(--ame-chat-bubble-bg-assistant);
    color: var(--ame-chat-bubble-fg-assistant);
    border: 1px solid var(--ame-chat-bubble-border);
}

.ame-chat__bubble--user {
    background: var(--ame-chat-bubble-bg-user);
    color: var(--ame-chat-bubble-fg-user);
    border-color: transparent;
}

.ame-chat__bubble--assistant {
    background: var(--ame-chat-bubble-bg-assistant);
    color: var(--ame-chat-bubble-fg-assistant);
}

.ame-chat__bubble--system {
    background: var(--ame-chat-bubble-bg-system);
    color: var(--ame-chat-bubble-fg-system);
    border-color: transparent;
    font-size: 12px;
}

/* The role glyph/initial avatar (ShowAvatar) — a small round badge with the role letter, NOT an image. */
.ame-chat__avatar {
    flex: 0 0 auto;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 24px;
    height: 24px;
    border-radius: 999px;
    background: var(--ame-chat-avatar-bg);
    color: var(--ame-chat-avatar-fg);
    font-size: 11px;
    font-weight: 700;
    line-height: 1;
}

/* The bubble body stacks the optional role label, the content, and the optional timestamp. */
.ame-chat__body {
    display: flex;
    flex-direction: column;
    gap: 2px;
    min-width: 0;
}

.ame-chat__role {
    font-size: 11px;
    font-weight: 600;
    color: var(--ame-chat-role-fg);
}

/* The user bubble's role label / timestamp inherit the on-accent foreground (so they read on the accent
   fill) at a softened opacity rather than re-fetching a muted token that would fail contrast on primary. */
.ame-chat__bubble--user .ame-chat__role,
.ame-chat__bubble--user .ame-chat__time {
    color: inherit;
    opacity: 0.85;
}

.ame-chat__content {
    font-size: 13px;
    line-height: 1.45;
    white-space: pre-wrap;
    word-break: break-word;
}

.ame-chat__time {
    margin-top: 2px;
    font-size: 10px;
    color: var(--ame-chat-time-fg);
    font-variant-numeric: tabular-nums;
}

/* The per-message action row (chat-i4 / i10) — a small inline strip after the content/status/timestamp,
   holding the gated Copy + Retry buttons. Each action borrows the bubble's own foreground (color: inherit)
   so it reads against the User accent fill too (the caret/Failed-label on-accent parity), at a softened
   opacity that lifts to full on hover. Token-only; the lone literal is the neutral translucent hover wash
   (NOT a Primary hardcode — the T3 #3 rule). */
.ame-chat__actions {
    display: inline-flex;
    gap: 4px;
    margin-top: 4px;
}

.ame-chat__action {
    display: inline-flex;
    align-items: center;
    gap: 2px;
    padding: 2px 6px;
    border: 0;
    border-radius: var(--ame-control-radius, 6px);
    background: transparent;
    color: inherit;
    cursor: pointer;
    font-size: var(--rz-text-sm, 0.8rem);
    opacity: 0.7;
}

.ame-chat__action:hover {
    opacity: 1;
    background: rgba(0, 0, 0, 0.06);
}

/* The centered empty state — a muted glyph over the readable EmptyText. */
.ame-chat__empty {
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: 8px;
    padding: 32px 16px;
    color: var(--ame-chat-empty-fg);
    text-align: center;
}

.ame-chat__empty-glyph {
    font-size: 28px;
    line-height: 1;
    opacity: 0.7;
}

.ame-chat__empty-text {
    font-size: 13px;
}

/* The composer (chat-i2 / i8) — a pinned row BELOW the scroll (a sibling, not inside __scroll, so it never
   scrolls away). A stretched multiline input + a trailing send button. Gated by ShowComposer in the markup;
   when absent the thread is byte-identical to chat-i1. All chrome resolves through the --ame-chat-composer-*
   tokens chaining to --rz-* so every theme/density variant recolors it for free. */
.ame-chat__composer {
    flex: 0 0 auto;
    display: flex;
    align-items: flex-end;
    gap: 8px;
    padding: 8px 12px;
    border-top: 1px solid var(--ame-chat-composer-border);
    background: var(--ame-chat-composer-bg);
}

/* The multiline input — a real textarea (Shift+Enter inserts a newline). Resizes vertically only; a sensible
   floor/ceiling keeps the composer from collapsing or eating the thread. Disabled (busy) dims + blocks. */
.ame-chat__input {
    flex: 1 1 auto;
    min-width: 0;
    min-height: 38px;
    max-height: 160px;
    resize: vertical;
    padding: 8px 10px;
    border: 1px solid var(--ame-chat-composer-input-border);
    border-radius: 8px;
    background: var(--ame-chat-composer-input-bg);
    color: var(--ame-chat-composer-input-fg);
    font-family: inherit;
    font-size: 13px;
    line-height: 1.45;
}

.ame-chat__input::placeholder {
    color: var(--ame-chat-composer-placeholder-fg);
}

.ame-chat__input:focus {
    outline: none;
    border-color: var(--ame-chat-composer-focus-border);
}

.ame-chat__input:disabled {
    cursor: not-allowed;
    opacity: 0.6;
}

/* The send button — the accent/primary fill with on-accent text (the user-bubble token pair), squared to the
   input's min-height. The disabled state (busy / nothing to send) resolves to a muted, non-interactive fill. */
.ame-chat__send {
    flex: 0 0 auto;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 38px;
    height: 38px;
    padding: 0;
    border: 1px solid transparent;
    border-radius: 8px;
    cursor: pointer;
    background: var(--ame-chat-composer-send-bg);
    color: var(--ame-chat-composer-send-fg);
}

.ame-chat__send:hover:not(:disabled) {
    background: var(--ame-chat-composer-send-bg-hover);
}

.ame-chat__send:disabled {
    cursor: not-allowed;
    background: var(--ame-chat-composer-send-bg-disabled);
    color: var(--ame-chat-composer-send-fg-disabled);
}

/* ---------- Streaming / status chrome (chat-i3 / i9) ----------
   All ADDITIVE: a null/Sent message carries none of these classes, so a settled bubble is byte-identical to
   chat-i2. The typing dots + the streaming caret pulse with a CSS @keyframes animation (NO JS timer) guarded
   by prefers-reduced-motion (the ame-spinner precedent). Colors resolve through the ame-chat status tokens
   (the streaming/typing cue = the assistant foreground; Failed = the rz-danger palette chain) so every theme
   recolors for free. */

/* The typing pseudo-row's bubble: a tight inline row of three pulsing dots, assistant-aligned. */
.ame-chat__typing {
    display: inline-flex;
    gap: 4px;
    align-items: center;
}

.ame-chat__typing-dot {
    width: 6px;
    height: 6px;
    border-radius: 999px;
    background: var(--ame-chat-typing-dot);
    opacity: 0.4;
    animation: ame-chat-typing 1.2s ease-in-out infinite;
}

.ame-chat__typing-dot:nth-child(2) { animation-delay: 0.2s; }
.ame-chat__typing-dot:nth-child(3) { animation-delay: 0.4s; }

@keyframes ame-chat-typing {
    0%, 80%, 100% { opacity: 0.3; }
    40%           { opacity: 1; }
}

/* Streaming caret — a thin blinking bar appended to the streaming bubble's content (a live/cursor cue). */
.ame-chat__caret {
    display: inline-block;
    width: 2px;
    height: 1em;
    margin-left: 1px;
    vertical-align: text-bottom;
    background: var(--ame-chat-caret);
    animation: ame-chat-caret 1s step-end infinite;
}

@keyframes ame-chat-caret {
    0%, 100% { opacity: 1; }
    50%      { opacity: 0; }
}

/* Sending — a subdued/pending wash on the whole bubble (reduced opacity), no extra element. */
.ame-chat__bubble--status-sending {
    opacity: 0.6;
}

/* Failed — a Danger-token border/accent on the bubble; reinforced by a visible "Failed" text node (so the
   status is not conveyed by color alone). The border chains to --rz-danger (never a hardcoded hex). */
.ame-chat__bubble--status-failed {
    border-color: var(--ame-chat-status-failed-border);
}

.ame-chat__status--failed {
    margin-top: 2px;
    font-size: 10px;
    font-weight: 600;
    color: var(--ame-chat-status-failed-fg);
}

/* The user bubble's "Failed" label still reads against the accent fill (inherit the on-accent wash), so a
   failed USER message's label stays legible rather than fetching the danger token on the colored bubble. */
.ame-chat__bubble--user .ame-chat__status--failed {
    color: inherit;
    opacity: 0.95;
}

/* Reduced-motion: stop the typing-dot + caret pulses (the ame-spinner / ame-anim precedent). The dots
   settle at a steady mid-opacity (still visible), the caret stays solid. */
@media (prefers-reduced-motion: reduce) {
    .ame-chat__typing-dot { animation: none; opacity: 0.6; }
    .ame-chat__caret      { animation: none; opacity: 1; }
}

/* ========================================================================
   AMEAiPrompt (Wave 10 prompt-i1 / i16) — a controlled multiline prompt-input surface: a strip of clickable
   suggested-prompt chips ABOVE a multiline textarea + a busy-gated submit button. It is the AMEChatThread
   composer, standalone, with a suggestion strip. The input + submit reuse the chat composer's token chain
   (--ame-chat-composer-* → --rz-*) so the two surfaces stay visually identical; the chips add their own
   --ame-ai-prompt-chip-* tokens (also chaining to --rz-*) so every theme/density variant recolors the whole
   surface for free. Reading order = DOM order = visual order: chips first (suggest → compose), then the
   composer row. NO inline hex; the focus ring on the chips + the input resolves through the shared focus
   token (--ame-form-border-focus-color). */
.ame-ai-prompt {
    display: flex;
    flex-direction: column;
    gap: 8px;
}

/* The suggestion chip strip — a wrapping inline row above the input. Absent (no element) when there are no
   suggestions, so the surface is byte-identical to "no suggestions". */
.ame-ai-prompt__chips {
    display: flex;
    flex-wrap: wrap;
    gap: 6px;
}

/* Each chip is a real <button> (Tab-focusable + Enter/Space for free) — a pill the user clicks to INSERT its
   prompt (or submit it, when SubmitOnSelect). The NEUTRAL surface (SurfaceMuted bg / Text fg / Border, matching
   the desktop chips) resolves through the prompt chip tokens. Disabled (busy) dims + blocks the click. */
.ame-ai-prompt__chip {
    display: inline-flex;
    align-items: center;
    height: 28px;
    max-width: 100%;
    padding: 0 12px;
    border: 1px solid var(--ame-ai-prompt-chip-border);
    border-radius: 999px;
    background: var(--ame-ai-prompt-chip-bg);
    color: var(--ame-ai-prompt-chip-fg);
    font-family: inherit;
    font-size: 12px;
    line-height: 1;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
    cursor: pointer;
}

.ame-ai-prompt__chip:hover:not(:disabled) {
    background: var(--ame-ai-prompt-chip-bg-hover);
    border-color: var(--ame-ai-prompt-chip-border-hover);
}

.ame-ai-prompt__chip:focus-visible {
    outline: 2px solid var(--ame-form-border-focus-color);
    outline-offset: 1px;
}

.ame-ai-prompt__chip:disabled {
    cursor: not-allowed;
    opacity: 0.55;
}

/* The Browse affordance (prompt-i4) — a real <button> at the chip strip's trailing edge that opens the grouped
   prompt-library panel. A subtle accent-tinted pill (a primary wash + primary text, the affordance-into-the
   -library treatment) sized to sit inline with the chips; present only when grouping is meaningful (HasLibrary),
   so it is absent for ungrouped/null Suggestions (byte-identical to i18). The focus ring rides the shared form
   focus token (the chip/input/submit convention). */
.ame-ai-prompt__browse {
    display: inline-flex;
    align-items: center;
    height: 28px;
    max-width: 100%;
    padding: 0 12px;
    border: 1px solid var(--ame-ai-prompt-browse-border);
    border-radius: 999px;
    background: var(--ame-ai-prompt-browse-bg);
    color: var(--ame-ai-prompt-browse-fg);
    font-family: inherit;
    font-size: 12px;
    font-weight: 600;
    line-height: 1;
    white-space: nowrap;
    cursor: pointer;
}

.ame-ai-prompt__browse:hover {
    background: var(--ame-ai-prompt-browse-bg-hover);
}

.ame-ai-prompt__browse:focus-visible {
    outline: 2px solid var(--ame-form-border-focus-color);
    outline-offset: 1px;
}

/* The prompt-LIBRARY panel (prompt-i4) — a bordered/elevated browse surface that REPLACES the flat strip while
   ShowLibrary. A column of group clusters (header + chips). The surface/border/shadow ride the library tokens so
   the dark toggle + density recolor it for free. */
.ame-ai-prompt__library {
    display: flex;
    flex-direction: column;
    gap: 10px;
    padding: 12px;
    border: 1px solid var(--ame-ai-prompt-library-border);
    border-radius: 10px;
    background: var(--ame-ai-prompt-library-bg);
    box-shadow: var(--ame-ai-prompt-library-shadow);
}

/* The panel header row — a caption on the left, the close (×) button on the right. */
.ame-ai-prompt__library-header {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 8px;
    color: var(--ame-ai-prompt-library-header-fg);
    font-family: inherit;
    font-size: 13px;
    font-weight: 600;
    line-height: 1.2;
}

/* The close (×) affordance — a small accent-tinted button that closes the library (ToggleLibrary), the Browse
   button's mirror. A literal × glyph (not run text), named for assistive tech by aria-label="Close library". */
.ame-ai-prompt__library-close {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 24px;
    height: 24px;
    padding: 0;
    border: 1px solid var(--ame-ai-prompt-browse-border);
    border-radius: 6px;
    background: var(--ame-ai-prompt-browse-bg);
    color: var(--ame-ai-prompt-browse-fg);
    font-family: inherit;
    font-size: 16px;
    line-height: 1;
    cursor: pointer;
}

.ame-ai-prompt__library-close:hover {
    background: var(--ame-ai-prompt-browse-bg-hover);
}

.ame-ai-prompt__library-close:focus-visible {
    outline: 2px solid var(--ame-form-border-focus-color);
    outline-offset: 1px;
}

/* One group cluster — an optional header caption followed by a wrapping row of the group's chips. The chips are
   the SAME .ame-ai-prompt__chip pills as the flat strip (same tokens), so the library is visually consistent. */
.ame-ai-prompt__group {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 6px;
}

/* The group header — a small muted caption above the group's chips. Forced full-width so the chips wrap onto the
   next line below it (the header owns its own row in the flex column). Absent for the ungrouped (blank-name)
   bucket (g.HasName), so an all-ungrouped library shows a single header-less cluster (the flat-strip look). */
.ame-ai-prompt__group-header {
    flex: 0 0 100%;
    color: var(--ame-ai-prompt-group-header-fg);
    font-family: inherit;
    font-size: 11px;
    font-weight: 600;
    letter-spacing: 0.02em;
    text-transform: uppercase;
    line-height: 1.2;
}

/* The composer row — a stretched multiline input + a trailing submit button. Reuses the chat composer's input
   and send chrome tokens so the two surfaces are visually identical. */
.ame-ai-prompt__composer {
    display: flex;
    align-items: flex-end;
    gap: 8px;
}

/* The input field — wraps the textarea + the CSS-overlaid clear (×) button. It carries the flex that used to live
   on the textarea (fills the composer row) and is the positioning context the absolute × anchors to. */
.ame-ai-prompt__field {
    position: relative;
    flex: 1 1 auto;
    min-width: 0;
    display: flex;
}

/* The multiline input — a real textarea (Shift+Enter inserts a newline). Resizes vertically only; a sensible
   floor/ceiling keeps it from collapsing or growing without bound. The trailing padding reserves room for the
   overlaid × so long text never slides under it. Disabled (busy) dims + blocks. */
.ame-ai-prompt__input {
    flex: 1 1 auto;
    width: 100%;
    min-width: 0;
    min-height: 38px;
    max-height: 160px;
    resize: vertical;
    padding: 8px 30px 8px 10px;
    border: 1px solid var(--ame-chat-composer-input-border);
    border-radius: 8px;
    background: var(--ame-chat-composer-input-bg);
    color: var(--ame-chat-composer-input-fg);
    font-family: inherit;
    font-size: 13px;
    line-height: 1.45;
}

.ame-ai-prompt__input::placeholder {
    color: var(--ame-chat-composer-placeholder-fg);
}

.ame-ai-prompt__input:focus-visible {
    outline: 2px solid var(--ame-form-border-focus-color);
    outline-offset: 1px;
    border-color: var(--ame-chat-composer-focus-border);
}

.ame-ai-prompt__input:disabled {
    cursor: not-allowed;
    opacity: 0.6;
}

/* The clear (×) affordance — a small button overlaid at the input's TRAILING edge, pinned to the top-right so it
   stays put as the textarea grows. Present only when CanClearNow (clearable text + not busy); a click wipes the
   draft (ClearInput). The muted glyph tints up on hover/focus through the composer placeholder/fg tokens; the
   focus ring reuses the shared form focus token (the chip/input/submit convention). */
.ame-ai-prompt__clear {
    position: absolute;
    top: 4px;
    right: 4px;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 22px;
    height: 22px;
    padding: 0;
    border: none;
    border-radius: 6px;
    background: transparent;
    color: var(--ame-chat-composer-placeholder-fg);
    font-family: inherit;
    font-size: 16px;
    line-height: 1;
    cursor: pointer;
}

.ame-ai-prompt__clear:hover {
    color: var(--ame-chat-composer-input-fg);
}

.ame-ai-prompt__clear:focus-visible {
    outline: 2px solid var(--ame-form-border-focus-color);
    outline-offset: 1px;
    color: var(--ame-chat-composer-input-fg);
}

/* The submit button — the accent/primary fill with on-accent text (the chat send token pair). The disabled
   state (busy / nothing to submit) resolves to a muted, non-interactive fill. */
.ame-ai-prompt__submit {
    position: relative; /* anchor for the overlaid busy spinner */
    flex: 0 0 auto;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    min-height: 38px;
    padding: 0 16px;
    border: 1px solid transparent;
    border-radius: 8px;
    cursor: pointer;
    background: var(--ame-chat-composer-send-bg);
    color: var(--ame-chat-composer-send-fg);
    font-family: inherit;
    font-size: 13px;
    font-weight: 600;
}

/* Busy: the caption keeps its box (visibility:hidden, NOT display:none) so the button width holds steady;
   the spinner overlays it centered. The spinner takes the text colour so it clears 3:1 on the disabled
   (muted) fill the button shows while busy, in BOTH light and dark (its default Primary dots would fail). */
.ame-ai-prompt__submit--busy .ame-ai-prompt__submit-label {
    visibility: hidden;
}

.ame-ai-prompt__submit-spinner {
    position: absolute;
    inset: 0;
    display: flex;
    align-items: center;
    justify-content: center;
}

.ame-ai-prompt__submit .ame-spinner {
    --ame-spinner-color: var(--rz-text-color, #1f2937);
}

.ame-ai-prompt__submit:hover:not(:disabled) {
    background: var(--ame-chat-composer-send-bg-hover);
}

.ame-ai-prompt__submit:focus-visible {
    outline: 2px solid var(--ame-form-border-focus-color);
    outline-offset: 1px;
}

.ame-ai-prompt__submit:disabled {
    cursor: not-allowed;
    background: var(--ame-chat-composer-send-bg-disabled);
    color: var(--ame-chat-composer-send-fg-disabled);
}

/* ========================================================================
   AMERichTextEditor (Wave 10 rich-i1 / i11) — a READ-ONLY formatted-document viewer. Renders a
   AMERichDocument as selectable paragraphs of bold / italic / underlined / struck-through runs (the markup
   nests semantic strong/em/u/s elements around the escaped run text). NO editing chrome, NO toolbar, NO
   caret. The text is real, selectable text (no user-select:none) so copy works. Colors resolve through the
   ame-rich tokens chaining to the --rz-* palette (with hex fallbacks, the chat-token convention) so every
   theme/density variant recolors the viewer for free. i11 renders every block as a paragraph (block kind +
   alignment are inert until i12). */
.ame-rich {
    --ame-rich-fg: var(--rz-text-color, #1f2937);
    --ame-rich-empty-fg: var(--rz-text-tertiary-color, #8b9e9e);
    /* The rich-i3 / i13 editor surface tokens — the editing block's textarea bg/border, chaining to the SAME rz
       palette tokens the chat composer textarea uses (--rz-base-50 / --rz-base-200) with hex fallbacks (the
       ame-rich-token convention), so the editor sits on theme. The editor fg reuses --ame-rich-fg so the
       typed text matches the read render. The focus ring uses the repo-wide --ame-form-border-focus-color
       token (the convention every other focusable control rides). */
    --ame-rich-editor-bg: var(--rz-base-50, #ffffff);
    --ame-rich-editor-border: var(--rz-base-200, #e5e7eb);
    color: var(--ame-rich-fg);
    font-family: inherit;
    font-size: 14px;
    line-height: 1.5;
}

/* A block = a paragraph (i11). Standard paragraph rhythm; the first/last collapse so the viewer doesn't pad
   itself top/bottom. The text color chains to the --rz-text-color token via --ame-rich-fg. */
.ame-rich__block {
    margin: 0 0 0.65em;
    color: var(--ame-rich-fg);
}

.ame-rich__block:last-child {
    margin-bottom: 0;
}

/* A run = an inline span; the bold/italic/underline/strikethrough formatting is carried by the nested
   semantic strong/em/u/s elements the markup emits (which already weight/slant/decorate the text), so the
   span itself only groups the run. */
.ame-rich__run {
    /* inline by default; no own treatment — the semantic wrappers carry the formatting. */
}

/* The muted empty-state placeholder (shown when AMERichTextModel.IsEmpty). A readable line, not an
   icon-only affordance; the muted token chains to --rz-text-tertiary-color. */
.ame-rich__empty {
    color: var(--ame-rich-empty-fg);
    font-size: 13px;
}

/* i12 block kinds. The viewer renders each AMERichBlock.Kind into a semantic element (h3/h4, ul/li,
   blockquote, p/div) the markup chooses; these classes only carry the visual treatment. Sizes are relative to
   the .ame-rich base (14px), weights/colors chain to the rz palette tokens (with hex fallbacks, the
   ame-rich-token convention). The run-level strong/em/u/s still layer inside every kind. */

/* Heading1 -> a real h3 the screen reader announces; larger + bold. ~1.5x base. */
.ame-rich__h1 {
    margin: 0 0 0.5em;
    color: var(--ame-rich-fg);
    font-size: 1.5em;
    font-weight: 700;
    line-height: 1.3;
}

/* Heading2 -> a real h4; ~1.25x base, bold (700, matching the desktop FontWeight.Bold for parity). */
.ame-rich__h2 {
    margin: 0 0 0.5em;
    color: var(--ame-rich-fg);
    font-size: 1.25em;
    font-weight: 700;
    line-height: 1.35;
}

/* A run of consecutive BulletListItem blocks is grouped into one real ul. Reset the UA list chrome to the
   token rhythm: a small left indent for the hanging bullet, no extra block margin (the items carry it). */
.ame-rich__list {
    margin: 0 0 0.65em;
    padding-left: 1.25em;
    list-style: none;
}

.ame-rich__list:last-child {
    margin-bottom: 0;
}

/* A bullet item: a token bullet rendered via ::before (so the marker recolors with the muted token and the
   text hangs with a clean indent). The glyph is decorative; the li semantics carry the list meaning. */
.ame-rich__bullet {
    position: relative;
    margin: 0 0 0.35em;
    padding-left: 0.25em;
    color: var(--ame-rich-fg);
}

.ame-rich__bullet:last-child {
    margin-bottom: 0;
}

.ame-rich__bullet::before {
    content: "\2022"; /* bullet glyph */
    position: absolute;
    left: -0.9em;
    color: var(--ame-rich-empty-fg);
}

/* A numbered item: a div whose marker text is the explicit computed ordinal (the markup emits "{n}. " in the
   ame-rich__marker span). A hanging indent matches the bullet rhythm; the number text comes from the markup
   so the Blazor/desktop numbers stay identical. */
.ame-rich__number {
    margin: 0 0 0.35em;
    padding-left: 1.5em;
    text-indent: -1.5em;
    color: var(--ame-rich-fg);
}

.ame-rich__number:last-child {
    margin-bottom: 0;
}

/* The numbered marker prefix ("1. "); muted so it reads as a marker, not body text. Inline so it flows before
   the runs and the hanging indent pulls it into the gutter. */
.ame-rich__marker {
    color: var(--ame-rich-empty-fg);
}

/* Quote -> a real blockquote with a left accent border (the primary token) + padding + muted text. */
.ame-rich__quote {
    margin: 0 0 0.65em;
    padding: 0.1em 0 0.1em 0.85em;
    border-left: 3px solid var(--rz-primary, #4361ee);
    color: var(--ame-rich-empty-fg);
}

.ame-rich__quote:last-child {
    margin-bottom: 0;
}

/* Block alignment (AMEChatAlignment). Leading is the default (no class -> left/start); Center and Trailing
   add a text-align modifier on the block element. Applies to every kind (a centered heading, a right-aligned
   paragraph). */
.ame-rich--align-center {
    text-align: center;
}

.ame-rich--align-end {
    text-align: end;
}

/* The editing block's textarea (rich-i3 / i13) — a PLAIN <textarea> the markup swaps in IN PLACE of the read
   element when a block is editing (and IsReadOnly is false). Full width, a token bg/fg/border (the editor fg
   reuses --ame-rich-fg so the typed text matches the read render; the bg/border chain to the rz palette),
   inherits the viewer font + size + line-height (so the draft reads like the block it replaces), a sane
   min-height, and a vertical resize so a long edit grows. Read-only / no block editing never emits this, so
   the i12 render is untouched. */
.ame-rich__editor {
    display: block;
    width: 100%;
    box-sizing: border-box;
    min-height: 2.4em;
    margin: 0 0 0.65em;
    padding: 0.4em 0.55em;
    resize: vertical;
    background: var(--ame-rich-editor-bg);
    color: var(--ame-rich-fg);
    border: 1px solid var(--ame-rich-editor-border);
    border-radius: 4px;
    font-family: inherit;
    font-size: inherit;
    line-height: inherit;
}

/* The focus ring rides the repo-wide focus token (the convention every other focusable control uses), so the
   editing block reads as focused to sighted + keyboard users. */
.ame-rich__editor:focus-visible {
    outline: 2px solid var(--ame-form-border-focus-color);
    outline-offset: 1px;
    border-color: var(--ame-form-border-focus-color);
}

/* rich-i14: the whole-block formatting toolbar (B/I/U/S), shown above the editing block when ShowFormatToolbar
   is set. Token chrome (the editor chain); the pressed (uniform-on) state lights the primary accent, matching
   the desktop AMEPrimary toggle accent. The buttons use @onmousedown:preventDefault so a click keeps focus on
   the editor (no premature blur-commit). */
.ame-rich__toolbar {
    display: flex;
    gap: 0.25em;
    margin: 0 0 0.4em;
}

.ame-rich__fmt {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    min-width: 2em;
    padding: 0.2em 0.5em;
    background: var(--ame-rich-editor-bg);
    color: var(--ame-rich-fg);
    border: 1px solid var(--ame-rich-editor-border);
    border-radius: 4px;
    font-family: inherit;
    font-size: inherit;
    line-height: 1.2;
    cursor: pointer;
}

.ame-rich__fmt:hover {
    border-color: var(--rz-primary, #4361ee);
}

.ame-rich__fmt:focus-visible {
    outline: 2px solid var(--ame-form-border-focus-color);
    outline-offset: 1px;
}

.ame-rich__fmt.is-pressed {
    background: var(--rz-primary, #4361ee);
    border-color: var(--rz-primary, #4361ee);
    color: var(--rz-on-primary, #fff);
}

/* ========================================================================
   AMESmartPaste (Wave 10 smartpaste-i1 / i20) — a controlled paste-to-fields surface: a public Paste(rawText)
   funnel that raises PasteParseRequested and MUTATES NOTHING, plus a read-only render of a consumer-assigned
   Fields labelled list (a definition list), an empty state, an IsBusy gate, and a HasFields read. The fields are a
   two-column label/value grid (the <dl>'s <dt>/<dd> pairs laid into a grid); the labels are muted captions, the
   values are the parsed text (word-break so a long value wraps rather than overflowing). Colors resolve through the
   ame-smart-paste tokens chaining to the --rz-* palette (with hex fallbacks, the ame-rich-token convention), so
   every theme/density variant recolors the surface for free. NO inline hex. */
.ame-smart-paste {
    --ame-smart-paste-fg: var(--rz-text-color, #1f2937);
    --ame-smart-paste-label-fg: var(--rz-text-tertiary-color, #8b9e9e);
    --ame-smart-paste-empty-fg: var(--rz-text-tertiary-color, #8b9e9e);
    --ame-smart-paste-row-border: var(--rz-base-200, #e5e7eb);
    /* Per-field severity accents (smartpaste-i4 / i23). Each chains to the REAL --rz-{severity} hue (the badge /
       alert / progress / TaskBoard-accent convention) with the canonical AMEPalette hex fallback, so the dark
       toggle recolors the accent for free and it is NOT an accent-on-its-own tint. Info reads --rz-info (the
       AMESeverity.Info hue), matching the alert / badge / progress info chain. Consumed by the row modifier
       classes + the severity glyph below; a Neutral field adds NO modifier, so it stays byte-identical. */
    --ame-smart-paste-accent-warning: var(--rz-warning, #f59e0b);
    --ame-smart-paste-accent-danger: var(--rz-danger, #ef4444);
    --ame-smart-paste-accent-success: var(--rz-success, #10b981);
    --ame-smart-paste-accent-info: var(--rz-info, #3b82f6);
    color: var(--ame-smart-paste-fg);
    font-family: inherit;
    font-size: 13px;
    line-height: 1.45;
}

/* The fields list — a real <dl> laid into a two-column grid: a content-sized label column + a stretchy value
   column. The <dt>/<dd> pairs flow into the grid in SUPPLIED order (the parser's order; the markup never sorts).
   A small row gap separates the pairs; a thin token rule under each pair reads them as distinct rows. */
.ame-smart-paste__fields {
    display: grid;
    grid-template-columns: minmax(0, max-content) minmax(0, 1fr);
    gap: 6px 16px;
    margin: 0;
}

/* The field label (the <dt>) — a muted caption in the first column, top-aligned to its (possibly multi-line)
   value. The muted token chains to --rz-text-tertiary-color. */
.ame-smart-paste__label {
    grid-column: 1;
    margin: 0;
    color: var(--ame-smart-paste-label-fg);
    font-size: 11px;
    font-weight: 600;
    letter-spacing: 0.02em;
    text-transform: uppercase;
    line-height: 1.45;
    align-self: start;
}

/* The field value (the <dd>) — the parsed text in the second column. word-break + overflow-wrap keep a long,
   unbroken value (a URL, a token) from overflowing the grid; pre-line preserves the parser's own line breaks. */
.ame-smart-paste__value {
    grid-column: 2;
    margin: 0;
    color: var(--ame-smart-paste-fg);
    white-space: pre-line;
    overflow-wrap: anywhere;
    word-break: break-word;
}

/* The muted empty-state caption (shown when !HasFields — null/empty Fields). A readable line, not an icon-only
   affordance; the muted token chains to --rz-text-tertiary-color. */
.ame-smart-paste__empty {
    color: var(--ame-smart-paste-empty-fg);
    font-size: 13px;
}

/* The OPT-IN "Paste from clipboard" button (smartpaste-i2 / i21) — the accent/primary fill with on-accent text,
   REUSING the chat-composer send token pair (the same chain the AMEAiPrompt submit button rides; a real --rz-primary
   token, NOT an accent-on-its-own-tint pill, so the on-accent foreground keeps AA in both themes). It sits above the
   body with a little gap. Rendered only when ShowPasteButton is true; absent the surface is byte-identical to i20. */
.ame-smart-paste__paste {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    margin-bottom: 12px;
    min-height: 34px;
    padding: 0 14px;
    border: 1px solid transparent;
    border-radius: 8px;
    cursor: pointer;
    background: var(--ame-chat-composer-send-bg);
    color: var(--ame-chat-composer-send-fg);
    font-family: inherit;
    font-size: 13px;
    font-weight: 600;
}

.ame-smart-paste__paste:hover:not(:disabled) {
    background: var(--ame-chat-composer-send-bg-hover);
}

.ame-smart-paste__paste:focus-visible {
    outline: 2px solid var(--ame-form-border-focus-color);
    outline-offset: 1px;
}

/* Busy (IsBusy) — the disabled, muted, non-interactive fill (the send disabled token pair). */
.ame-smart-paste__paste:disabled {
    cursor: not-allowed;
    background: var(--ame-chat-composer-send-bg-disabled);
    color: var(--ame-chat-composer-send-fg-disabled);
}

/* ---------- Editable fields + Confirm (smartpaste-i3 / i22) ----------
   ADDITIVE: these classes only appear when IsEditable is true, so the default (read-only) render is byte-identical
   to i20-i21. The editable value cell holds a text input bound to the LOCAL draft; the input + the Confirm button
   reuse EXISTING token chains (the chat composer input chain for the field, the chat composer send chain for the
   Confirm primary button — the same chains the i21 paste button rides) so the surface recolors with every theme.
   NO inline hex; the focus ring resolves through the shared form focus token (the i21 paste button's ring). */

/* The editable value cell (the <dd> wrapper) — the second grid column, holding the input (replaces the read-only
   value text when IsEditable). The cell stretches so the input fills the value column. */
.ame-smart-paste__value-cell {
    grid-column: 2;
    margin: 0;
    align-self: center;
}

/* The editable value input — a single-line text field reusing the chat composer input token chain (border / bg /
   fg / placeholder / focus-border) so it matches the composer + AiPrompt input surfaces. Fills the value column;
   the focus ring resolves through the shared form focus token (the i21 paste button's ring). Disabled (busy) dims
   + blocks, mirroring the composer input disabled treatment. */
.ame-smart-paste__value-input {
    width: 100%;
    min-width: 0;
    box-sizing: border-box;
    min-height: 32px;
    padding: 6px 10px;
    border: 1px solid var(--ame-chat-composer-input-border);
    border-radius: 8px;
    background: var(--ame-chat-composer-input-bg);
    color: var(--ame-chat-composer-input-fg);
    font-family: inherit;
    font-size: 13px;
    line-height: 1.45;
}

.ame-smart-paste__value-input::placeholder {
    color: var(--ame-chat-composer-placeholder-fg);
}

.ame-smart-paste__value-input:focus {
    outline: none;
    border-color: var(--ame-chat-composer-focus-border);
}

.ame-smart-paste__value-input:focus-visible {
    outline: 2px solid var(--ame-form-border-focus-color);
    outline-offset: 1px;
}

.ame-smart-paste__value-input:disabled {
    cursor: not-allowed;
    opacity: 0.6;
}

/* The Confirm button (i22) — the accent/primary fill with on-accent text, REUSING the chat-composer send token
   pair (the same chain the i21 paste button rides; a real --rz-primary token, NOT an accent-on-its-own-tint pill,
   so the on-accent foreground keeps AA in both themes). It sits below the fields with a little gap. Rendered only
   when IsEditable AND HasFields; absent the surface is byte-identical to i20-i21. */
.ame-smart-paste__confirm {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    margin-top: 12px;
    min-height: 34px;
    padding: 0 14px;
    border: 1px solid transparent;
    border-radius: 8px;
    cursor: pointer;
    background: var(--ame-chat-composer-send-bg);
    color: var(--ame-chat-composer-send-fg);
    font-family: inherit;
    font-size: 13px;
    font-weight: 600;
}

.ame-smart-paste__confirm:hover:not(:disabled) {
    background: var(--ame-chat-composer-send-bg-hover);
}

.ame-smart-paste__confirm:focus-visible {
    outline: 2px solid var(--ame-form-border-focus-color);
    outline-offset: 1px;
}

/* Busy (IsBusy) — the disabled, muted, non-interactive fill (the send disabled token pair). */
.ame-smart-paste__confirm:disabled {
    cursor: not-allowed;
    background: var(--ame-chat-composer-send-bg-disabled);
    color: var(--ame-chat-composer-send-fg-disabled);
}

/* ---------- Per-field severity accent + glyph (smartpaste-i4 / i23) ----------
   ADDITIVE: these modifier classes only attach when a field's Severity is non-Neutral (RowSeverityClass returns
   "" for Neutral, so the row keeps the bare base class) ⇒ the default render is byte-identical to i20-i22. The
   row accent is a left bar on the LABEL cell (the leftmost grid column), tinting the row's left edge across both
   columns; it resolves through the --ame-smart-paste-accent-{severity} tokens (which chain to --rz-{severity}
   with the canonical hex fallback), so the dark toggle recolors the accent for free. The accent is paired with a
   per-severity glyph beside the value (the non-color WCAG 1.4.1 cue; the severity WORD rides the value's
   accessible name). NO inline hex in the markup; the colour lives entirely in these token-backed rules. */

.ame-smart-paste__label.ame-smart-paste__row--warning {
    border-left: 3px solid var(--ame-smart-paste-accent-warning);
    padding-left: 8px;
}

.ame-smart-paste__label.ame-smart-paste__row--danger {
    border-left: 3px solid var(--ame-smart-paste-accent-danger);
    padding-left: 8px;
}

.ame-smart-paste__label.ame-smart-paste__row--success {
    border-left: 3px solid var(--ame-smart-paste-accent-success);
    padding-left: 8px;
}

.ame-smart-paste__label.ame-smart-paste__row--info {
    border-left: 3px solid var(--ame-smart-paste-accent-info);
    padding-left: 8px;
}

/* When the EDITABLE value cell carries a severity modifier it lays the input + the trailing glyph in a flex row so
   the glyph sits beside (not below) the full-width input. Scoped to the severity-modified cell so a Neutral cell
   keeps the i22 block layout (the editable Neutral render stays byte-identical). The read-only <dd> needs no such
   rule — its value text + glyph are inline-flow already. */
.ame-smart-paste__value-cell.ame-smart-paste__row--warning,
.ame-smart-paste__value-cell.ame-smart-paste__row--danger,
.ame-smart-paste__value-cell.ame-smart-paste__row--success,
.ame-smart-paste__value-cell.ame-smart-paste__row--info {
    display: flex;
    align-items: center;
}

/* The severity glyph (the non-color cue) — a small inline icon trailing the value, vertically centred against the
   value text, coloured by the row's severity token. It is decorative (aria-hidden via AMEIcon's no-AriaLabel
   branch); the severity WORD is announced through the value's prefixed accessible name. The glyph lives inside the
   value cell (the read-only <dd> or the editable <dd> wrapper), which carries the row modifier class — so the
   descendant selectors below tint it by the same severity token as the accent bar. */
.ame-smart-paste__sev-icon {
    flex: 0 0 auto;
    margin-left: 6px;
    vertical-align: text-bottom;
}

.ame-smart-paste__row--warning .ame-smart-paste__sev-icon {
    color: var(--ame-smart-paste-accent-warning);
}

.ame-smart-paste__row--danger .ame-smart-paste__sev-icon {
    color: var(--ame-smart-paste-accent-danger);
}

.ame-smart-paste__row--success .ame-smart-paste__sev-icon {
    color: var(--ame-smart-paste-accent-success);
}

.ame-smart-paste__row--info .ame-smart-paste__sev-icon {
    color: var(--ame-smart-paste-accent-info);
}

/* ========================================================================
   AMESmartQuery (Wave 14 w14-i9) — the marquee SMART-QUERY BAR: a natural-language text input that parses an
   English query into the SHIPPED AMEGridState a AMEDataGrid applies, plus the recognized clauses shown as
   read-only chips (the legible "what I understood" parse). Layout = a column (the input row, then the chip strip).
   The input + submit REUSE the chat composer token chain (the same chain the AiPrompt / SmartPaste input + send
   ride), so the box stays visually identical to the other input surfaces and recolors with every theme/density
   variant for free; the chip strip is the shipped AMEChipList, which themes itself via AMEChip. NO inline hex;
   the focus ring on the input + the button resolves through the shared form focus token. The chips are read-only
   (Removable=false), so there is no remove-button chrome here. */
.ame-smart-query {
    display: flex;
    flex-direction: column;
    gap: 8px;
}

/* The query row — a stretched text input + a trailing search button (the AiPrompt composer-row shape, single-line). */
.ame-smart-query__row {
    display: flex;
    align-items: center;
    gap: 8px;
}

/* The natural-language input — a single-line text field reusing the chat composer input token chain (border / bg /
   fg / placeholder / focus-border) so it matches the composer + AiPrompt + SmartPaste input surfaces. Fills the
   row; the focus ring resolves through the shared form focus token (the input/submit convention). */
.ame-smart-query__input {
    flex: 1 1 auto;
    width: 100%;
    min-width: 0;
    box-sizing: border-box;
    min-height: 38px;
    padding: 8px 12px;
    border: 1px solid var(--ame-chat-composer-input-border);
    border-radius: 8px;
    background: var(--ame-chat-composer-input-bg);
    color: var(--ame-chat-composer-input-fg);
    font-family: inherit;
    font-size: 13px;
    line-height: 1.45;
}

.ame-smart-query__input::placeholder {
    color: var(--ame-chat-composer-placeholder-fg);
}

.ame-smart-query__input:focus {
    outline: none;
    border-color: var(--ame-chat-composer-focus-border);
}

.ame-smart-query__input:focus-visible {
    outline: 2px solid var(--ame-form-border-focus-color);
    outline-offset: 1px;
    border-color: var(--ame-chat-composer-focus-border);
}

/* The search button — the accent/primary fill with on-accent text (the chat composer send token pair; a real
   --rz-primary token, NOT an accent-on-its-own tint, so the on-accent foreground keeps AA in both themes). A real
   <button> (Tab-reachable, Enter/Space activates), square to hold the search glyph; the focus ring rides the shared
   form focus token (the input/submit convention). */
.ame-smart-query__submit {
    flex: 0 0 auto;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    min-width: 38px;
    min-height: 38px;
    padding: 0 12px;
    border: 1px solid transparent;
    border-radius: 8px;
    cursor: pointer;
    background: var(--ame-chat-composer-send-bg);
    color: var(--ame-chat-composer-send-fg);
    font-family: inherit;
    font-size: 13px;
    font-weight: 600;
}

.ame-smart-query__submit:hover:not(:disabled) {
    background: var(--ame-chat-composer-send-bg-hover);
}

.ame-smart-query__submit:focus-visible {
    outline: 2px solid var(--ame-form-border-focus-color);
    outline-offset: 1px;
}

/* The recognized-clause chip strip — wraps the shipped AMEChipList (Removable=false). An aria-live="polite" region
   so AT announces the new clauses after each parse; the chips wrap onto the next line as needed (AMEChipList is a
   flex-wrap row). Absent until the first parse yields a chip (no empty strip). The chips theme themselves via
   AMEChip, so this wrapper carries only the live-region role. */
.ame-smart-query__chips {
    display: flex;
    flex-wrap: wrap;
}

/* ========================================================================
   AMESpeechInput (Wave 10 speech-i1 / i25) — a controlled voice-input surface: a mic toggle that raises Listen/
   Stop INTENTS (the consumer drives THEIR recognizer) + renders a CONSUMER-ASSIGNED Transcript (final) +
   InterimTranscript (in-flight, DISTINCT) + reflects IsListening. It owns neither the recognizer nor the
   transcript. The transcript region is a column of text (the final reads as body text; the interim reads muted +
   italic so it is visibly the in-flight guess); the mic is a real round button. Colors resolve through the
   ame-speech tokens chaining to the --rz-* palette (with hex fallbacks, the ame-smart-paste-token convention),
   so every theme/density variant recolors the surface for free. The mic accent is built from the REAL --rz-primary
   token (NOT an accent-on-its-own-tint), so the on-accent foreground keeps AA in both themes. NO inline hex. */
.ame-speech {
    --ame-speech-fg: var(--rz-text-color, #1f2937);
    --ame-speech-interim-fg: var(--rz-text-tertiary-color, #8b9e9e);
    --ame-speech-placeholder-fg: var(--rz-text-tertiary-color, #8b9e9e);
    /* The mic chrome. Idle: a token surface + border (the SurfaceMuted / Border pair the chips ride). Listening:
       the REAL --rz-primary accent fill with on-accent text (the chat-send / smart-paste-paste token pair), NOT an
       accent-on-its-own tint, so the glyph keeps AA on the fill in both themes. The dark toggle recolors all of it
       for free. */
    --ame-speech-mic-bg: var(--rz-base-100, #f3f4f6);
    --ame-speech-mic-border: var(--rz-base-200, #e5e7eb);
    --ame-speech-mic-fg: var(--rz-text-color, #1f2937);
    --ame-speech-mic-bg-hover: var(--rz-base-200, #e5e7eb);
    --ame-speech-mic-listening-bg: var(--rz-primary, #4361ee);
    --ame-speech-mic-listening-fg: var(--rz-on-primary, #ffffff);
    display: flex;
    align-items: center;
    gap: 12px;
    color: var(--ame-speech-fg);
    font-family: inherit;
    font-size: 14px;
    line-height: 1.5;
}

/* The transcript region — a stretchy column holding the final + interim runs (or the placeholder). It is a polite
   live region so a streamed interim announces as it lands. Stretches to fill the row so the mic sits at the
   trailing edge; word-break keeps a long, unbroken utterance from overflowing. */
.ame-speech__transcript {
    flex: 1 1 auto;
    min-width: 0;
    overflow-wrap: anywhere;
    word-break: break-word;
}

/* The FINAL transcript — body text in the surface fg. Inline so the interim run flows directly after it (one
   continuous utterance), with a trailing space so the two runs do not abut. */
.ame-speech__final {
    color: var(--ame-speech-fg);
}

.ame-speech__final:not(:empty)::after {
    content: " "; /* separate the final from the interim run without the model composing the join. */
}

/* The INTERIM transcript — the in-flight guess, rendered DISTINCTLY (muted + italic) so it reads visibly apart
   from the final. The muted token chains to --rz-text-tertiary-color (the placeholder/empty convention). */
.ame-speech__interim {
    color: var(--ame-speech-interim-fg);
    font-style: italic;
}

/* The muted empty-state placeholder (shown when !HasTranscript). A readable line, not an icon-only affordance;
   the muted token chains to --rz-text-tertiary-color. */
.ame-speech__placeholder {
    color: var(--ame-speech-placeholder-fg);
    font-size: 13px;
}

/* The mic toggle — a real round <button> (Tab-reachable; Enter/Space activate). Idle: the token surface + border.
   The focus ring rides the shared form focus token (the repo-wide convention every focusable control uses). */
.ame-speech__mic {
    flex: 0 0 auto;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 44px;
    height: 44px;
    padding: 0;
    border: 1px solid var(--ame-speech-mic-border);
    border-radius: 50%;
    background: var(--ame-speech-mic-bg);
    color: var(--ame-speech-mic-fg);
    cursor: pointer;
}

.ame-speech__mic:hover:not(.ame-speech__mic--listening) {
    background: var(--ame-speech-mic-bg-hover);
}

.ame-speech__mic:focus-visible {
    outline: 2px solid var(--ame-form-border-focus-color);
    outline-offset: 2px;
}

/* Listening: the accent fill with on-accent glyph (the REAL --rz-primary pair) + the DECLARATIVE pulsing ring
   (the keyframes below). The listening state is ALSO announced via the visually-hidden aria-live status in the
   markup, so the cue is not colour/animation alone (WCAG 1.4.1). */
.ame-speech__mic--listening {
    border-color: var(--ame-speech-mic-listening-bg);
    background: var(--ame-speech-mic-listening-bg);
    color: var(--ame-speech-mic-listening-fg);
    animation: ame-speech-pulse 1.6s ease-out infinite;
}

/* The mic glyph — sized inside the button, coloured by currentColor (the button fg), so it inverts to the on-
   accent colour while listening for free. Decorative (aria-hidden in the markup). */
.ame-speech__mic-icon {
    display: block;
}

/* The DECLARATIVE pulsing ring (the ame-spinner precedent — CSS only, NO JS timer). An expanding, fading box-
   shadow halo in the accent hue rings the listening mic. The accent is read from the listening-bg token, so the
   halo recolors with the theme. prefers-reduced-motion disables it below (the aria-live status carries the real
   signal). */
@keyframes ame-speech-pulse {
    0%   { box-shadow: 0 0 0 0 var(--ame-speech-mic-listening-bg); }
    70%  { box-shadow: 0 0 0 10px transparent; }
    100% { box-shadow: 0 0 0 0 transparent; }
}

@media (prefers-reduced-motion: reduce) {
    .ame-speech__mic--listening { animation: none; }
}

/* ========================================================================
   AMEFileManager (Wave 13 w13-i1) -- a master-detail file-browser SHELL that COMPOSES the existing controls
   (AMESplitPane / AMETreeView / AMEBreadcrumb / AMEListView / AMEIcon) and adds ZERO net-new rendering.
   So this stylesheet owns only the WRAPPER + the file-list ROW (the one surface the FileManager supplies as
   the AMEListView ItemTemplate, since the ListView has no row chrome of its own). Everything else is the
   composed children's own CSS. The row reuses the repo-wide ame tokens (surface / text / muted / border /
   radius / the form focus ring) with the canonical hex fallbacks (the chat/listview convention), so the dark
   toggle + density recolor it for free. NO inline hex; the two slashes rule keeps these comments slash-free. */
.ame-filemanager {
    display: flex;
    flex-direction: column;
    min-height: 0;
}

/* The split pane stretches to the wrapper so the two panes fill the browser (Wave 20 G17: min-height:0 lets
   it shrink under the toolbar strip in the column-flex wrapper, so its own panes keep scrolling instead of
   pushing the whole shell taller). */
.ame-filemanager > .ame-splitpane {
    flex: 1 1 auto;
    min-width: 0;
    min-height: 0;
}

/* Wave 20 G17: the operations toolbar strip (New folder / Rename / Delete / Upload / Download / the
   List<->Details toggle) -- gated by ShowToolbar (default true). Never grows/shrinks; a hairline separates it
   from the split pane below, using the repo-wide border token (the filemanager row convention). */
.ame-filemanager__toolbar {
    flex: 0 0 auto;
    padding: 4px;
    border-bottom: 1px solid var(--ame-border, #e5e7eb);
}

/* The folder pane (the AMESplitPane First slot) -- a scrolling column hosting the tree. */
.ame-filemanager__tree {
    height: 100%;
    overflow: auto;
    padding: 4px;
}

/* The detail pane (the Second slot) -- the breadcrumb path bar stacked over the file list, the list
   scrolling under a pinned breadcrumb. */
.ame-filemanager__detail {
    display: flex;
    flex-direction: column;
    min-width: 0;
    height: 100%;
    gap: 8px;
    padding: 4px;
}

/* The file list fills the remaining height under the breadcrumb and scrolls. */
.ame-filemanager__detail > .ame-listview {
    flex: 1 1 auto;
    min-height: 0;
    overflow: auto;
}

/* Wave 20 G17: the Details view swaps the list for a AMEDataGrid -- same fill-and-scroll treatment. The
   direct child is the grid's own .ame-datagrid__wrap (the <table> itself carries .ame-datagrid). */
.ame-filemanager__detail > .ame-datagrid__wrap {
    flex: 1 1 auto;
    min-height: 0;
    overflow: auto;
}

/* The ROW -- the FileManager's only authored surface (the AMEListView ItemTemplate). A real <button> (so
   it is Tab-reachable + Enter/Space activate), laid out as a flex line: a leading glyph, the stretchy name,
   then the trailing file metadata. It is a full-bleed transparent button so the AMEListView__row card chrome
   (the hairline separators + hover wash) still reads through; the button only adds the click + focus. */
.ame-filemanager__row {
    display: flex;
    align-items: center;
    gap: 8px;
    width: 100%;
    margin: 0;
    padding: 0;
    border: 0;
    background: transparent;
    color: var(--ame-form-fg, #1f2937);
    font-family: inherit;
    font-size: var(--ame-form-font-size, 14px);
    line-height: 1.4;
    text-align: left;
    cursor: pointer;
}

.ame-filemanager__row:focus-visible {
    outline: 2px solid var(--ame-form-border-focus-color);
    outline-offset: -2px;
    border-radius: var(--ame-control-corner-radius, 6px);
}

/* The Details-view Name cell is a real <button> (Wave 20 G17) so folder-navigate / file-select is reachable
   by keyboard (Tab + Enter/Space) inside the grid, which has no row keyboard event of its own. Same
   transparent-button + focus-ring treatment as the List-view row; the folder glyph carries the same accent. */
.ame-filemanager__namebtn {
    display: flex;
    align-items: center;
    gap: 8px;
    width: 100%;
    margin: 0;
    padding: 0;
    border: 0;
    background: transparent;
    color: var(--ame-form-fg, #1f2937);
    font-family: inherit;
    font-size: var(--ame-form-font-size, 14px);
    line-height: 1.4;
    text-align: left;
    cursor: pointer;
}

.ame-filemanager__namebtn:focus-visible {
    outline: 2px solid var(--ame-form-border-focus-color);
    outline-offset: -2px;
    border-radius: var(--ame-control-corner-radius, 6px);
}

.ame-filemanager__namebtn--folder .ame-filemanager__row-icon {
    color: var(--rz-primary, #4361ee);
}

/* The leading folder/file glyph -- muted so the name carries the row; a folder reads a touch stronger via
   the accent below. Decorative (AMEIcon emits aria-hidden in its no-AriaLabel branch). */
.ame-filemanager__row-icon {
    flex: 0 0 auto;
    color: var(--ame-text-muted, #8b9e9e);
}

/* A folder glyph leans on the accent so the navigable rows are visually distinct from the file leaves; the
   accent chains to the primary token (the rich-quote / fmt-pressed convention), NOT a hardcoded hue. */
.ame-filemanager__row--folder .ame-filemanager__row-icon {
    color: var(--rz-primary, #4361ee);
}

/* The name -- the row's body text; ellipsizes when the pane is narrow so a long name never overflows. */
.ame-filemanager__row-name {
    flex: 1 1 auto;
    min-width: 0;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

/* The trailing file metadata (size / modified date) -- muted captions, right-aligned, tabular so columns of
   sizes/dates line up. Folders render no metadata (the markup omits it), so only files carry these. */
.ame-filemanager__row-meta {
    flex: 0 0 auto;
    color: var(--ame-text-muted, #8b9e9e);
    font-size: 12px;
    font-variant-numeric: tabular-nums;
    white-space: nowrap;
}

/* A fixed-ish width on the size so the date column aligns across rows; the date sits at the trailing edge. */
.ame-filemanager__row-size {
    min-width: 64px;
    text-align: right;
}

/* ========================================================================
   AMESpreadsheet (Wave 13 w13-i3) -- a lightweight spreadsheet / editable cell grid: a hand-built table (NO
   native widget) of R x C string cells, framed by column headers (A, B, .., AA), 1-based row headers, and a
   corner cell. Exactly ONE active cell (the selection) gets the wash + border; a click selects, a double-click
   or a typed char opens an inline editor. EVERY behavioral invariant lives in the AME.Design lift; this is
   pure presentation. Reuses the repo-wide ame tokens (surface / text / muted / border / radius / the form
   focus ring) with the canonical hex fallbacks (the filemanager convention), so the dark toggle + density
   recolor it for free. The active wash chains the per-component tint to the primary rgba (the scheduler /
   gantt tint convention), NOT a hardcoded hue. NO inline hex; the two slashes rule keeps these comments
   slash-free (and free of a bare double dash inside the prose). */

/* The focusable grid wrapper -- tabindex 0 so the active-cell keyboard (OnGridKeyDown) has a focus host; the
   table scrolls inside it when the grid outgrows the viewport. The focus ring rides the wrapper (the roving
   active cell is the real selection, drawn by the cell modifier below). */
.ame-spreadsheet-wrap {
    display: inline-block;
    max-width: 100%;
    overflow: auto;
    border: 1px solid var(--ame-border, #e5e7eb);
    border-radius: var(--ame-control-corner-radius, 6px);
    background: var(--ame-surface, #ffffff);
}

.ame-spreadsheet-wrap:focus-visible {
    outline: 2px solid var(--ame-form-border-focus-color, #4361ee);
    outline-offset: -2px;
}

/* The table itself -- a fixed layout so every column is an even width and the cells line up; collapsed borders
   draw the single-pixel grid lines. */
.ame-spreadsheet {
    border-collapse: collapse;
    table-layout: fixed;
    font-family: inherit;
    font-size: var(--ame-form-font-size, 14px);
    color: var(--ame-form-fg, #1f2937);
}

/* The header cells (column headers, row headers, the corner) -- a muted tinted strip so the data cells carry
   the eye; the corner is the join of the two header axes. */
.ame-spreadsheet__corner,
.ame-spreadsheet__col-header,
.ame-spreadsheet__row-header {
    background: var(--ame-surface-muted, #f3f4f6);
    color: var(--ame-text-muted, #8b9e9e);
    font-weight: 600;
    text-align: center;
    border: 1px solid var(--ame-border, #e5e7eb);
    padding: 4px 8px;
    user-select: none;
}

.ame-spreadsheet__col-header {
    min-width: 88px;
}

/* The row header is narrow (it carries only the 1-based number) and right-aligns the digits. */
.ame-spreadsheet__row-header {
    min-width: 36px;
    text-align: right;
    font-variant-numeric: tabular-nums;
}

/* A body cell -- a single grid square; the value ellipsizes so a long string never bursts the column. The
   cell is clickable (a click selects it; a double-click edits). */
.ame-spreadsheet__cell {
    border: 1px solid var(--ame-border, #e5e7eb);
    padding: 0;
    height: 30px;
    background: var(--ame-surface, #ffffff);
    cursor: cell;
    overflow: hidden;
}

/* The static cell text -- padded inside the cell, single-line with an ellipsis. */
.ame-spreadsheet__cell-text {
    display: block;
    padding: 4px 8px;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}

/* The ACTIVE cell -- the selection wash (the primary tint) + a stronger primary border drawn over the grid
   line via an inset box-shadow (so the 1px grid border is not visually doubled). The wash token chains to the
   primary rgba, NOT a hardcoded hue. */
.ame-spreadsheet__cell--active {
    background: var(--ame-spreadsheet-active-bg, rgba(67, 97, 238, 0.15));
    box-shadow: inset 0 0 0 2px var(--ame-form-border-focus-color, #4361ee);
}

/* The inline editor -- a full-bleed text input that fills the cell so the edit-in-place reads seamless; no
   border of its own (the cell border + the active wash frame it), and it inherits the cell font. */
.ame-spreadsheet__editor {
    box-sizing: border-box;
    width: 100%;
    height: 100%;
    margin: 0;
    border: 0;
    padding: 4px 8px;
    background: var(--ame-surface, #ffffff);
    color: var(--ame-form-fg, #1f2937);
    font-family: inherit;
    font-size: var(--ame-form-font-size, 14px);
    outline: none;
}

/* ============================================================
   AMEDocumentViewer (Wave 13 w13-i4) -- a page-image document VIEWER: the consumer supplies an ORDERED list
   of pre-rendered page-image SOURCES; the control DISPLAYS one page in a scrollable viewport, NAVIGATES pages
   (a toolbar of first/prev/next/last + a "Page n of m" indicator + a zoom-mode segmented set), applies a
   fit-zoom MODE (FitWidth / FitPage / Actual) via a page modifier class, and offers an optional thumbnail
   RAIL (the current page highlighted; click to jump). It is a DISPLAYER, NOT a renderer -- pure presentation
   over the AME.Design page-index lift. Reuses the repo-wide ame tokens (surface / text / muted / border /
   radius / the form focus ring) with the canonical hex fallbacks (the spreadsheet / filemanager convention),
   so the dark toggle recolors it for free. The active-thumbnail highlight chains to the primary tint (the
   spreadsheet active-wash convention), NOT a hardcoded hue. The fit MATH is per-stack: here the three modes
   are CSS sizing rules on the page image. NO inline hex; the two slashes rule keeps these comments slash-free
   (and free of a bare double dash inside the prose). */

/* The focusable viewer wrapper -- tabindex 0 so the page-nav keyboard (OnKeyDown) has a focus host. A column
   stack: the toolbar over the body (rail + viewport). The focus ring rides the wrapper. */
.ame-docviewer {
    display: flex;
    flex-direction: column;
    border: 1px solid var(--ame-border, #e5e7eb);
    border-radius: var(--ame-control-corner-radius, 6px);
    background: var(--ame-surface, #ffffff);
    overflow: hidden;
}

.ame-docviewer:focus-visible {
    outline: 2px solid var(--ame-form-border-focus-color, #4361ee);
    outline-offset: -2px;
}

/* The toolbar -- the nav group on the left, the zoom-mode group on the right, with a muted strip background so
   the viewport carries the eye. */
.ame-docviewer__toolbar {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    justify-content: space-between;
    gap: 8px;
    padding: 6px 8px;
    background: var(--ame-surface-muted, #f3f4f6);
    border-bottom: 1px solid var(--ame-border, #e5e7eb);
}

.ame-docviewer__nav,
.ame-docviewer__zoom {
    display: flex;
    align-items: center;
    gap: 4px;
}

/* A nav button -- first/prev/next/last; the chevron-bearing ones center the glyph, the text ones (first/last)
   carry the bracket labels. Disabled fades + drops the pointer (the can-prev/can-next edge). */
.ame-docviewer__btn {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    min-width: 30px;
    height: 28px;
    padding: 0 8px;
    border: 1px solid var(--ame-border, #e5e7eb);
    border-radius: var(--ame-control-corner-radius, 6px);
    background: var(--ame-surface, #ffffff);
    color: var(--ame-text, #1f2937);
    font-size: 13px;
    font-weight: 600;
    cursor: pointer;
}

.ame-docviewer__btn:hover:not(:disabled) {
    background: var(--ame-surface-muted, #f3f4f6);
}

.ame-docviewer__btn:focus-visible {
    outline: 2px solid var(--ame-form-border-focus-color, #4361ee);
    outline-offset: 1px;
}

.ame-docviewer__btn:disabled {
    opacity: 0.45;
    cursor: default;
}

/* The "Page n of m" position indicator -- tabular digits so it does not jitter as the page count changes. */
.ame-docviewer__indicator {
    padding: 0 8px;
    color: var(--ame-text-muted, #8b9e9e);
    font-size: 13px;
    font-variant-numeric: tabular-nums;
    white-space: nowrap;
    user-select: none;
}

/* A zoom-mode button -- a flat segmented set; the active mode gets the primary wash + border. */
.ame-docviewer__zoom-btn {
    height: 28px;
    padding: 0 10px;
    border: 1px solid var(--ame-border, #e5e7eb);
    border-radius: var(--ame-control-corner-radius, 6px);
    background: var(--ame-surface, #ffffff);
    color: var(--ame-text, #1f2937);
    font-size: 12px;
    font-weight: 600;
    cursor: pointer;
}

.ame-docviewer__zoom-btn:hover {
    background: var(--ame-surface-muted, #f3f4f6);
}

.ame-docviewer__zoom-btn:focus-visible {
    outline: 2px solid var(--ame-form-border-focus-color, #4361ee);
    outline-offset: 1px;
}

.ame-docviewer__zoom-btn--active {
    background: var(--ame-spreadsheet-active-bg, rgba(67, 97, 238, 0.15));
    border-color: var(--ame-form-border-focus-color, #4361ee);
    color: var(--ame-form-border-focus-color, #4361ee);
}

/* The body -- the rail beside the viewport; the viewport flexes to fill the rest. A fixed height so the
   scrollable viewport has room (the consumer can override via the splatted class/style). */
.ame-docviewer__body {
    display: flex;
    flex-direction: row;
    align-items: stretch;
    min-height: 0;
    height: 460px;
}

/* The thumbnail rail -- a vertical scroll of small page buttons down the left edge. */
.ame-docviewer__rail {
    display: flex;
    flex-direction: column;
    gap: 6px;
    flex: 0 0 auto;
    width: 116px;
    padding: 8px;
    overflow-y: auto;
    background: var(--ame-surface-muted, #f3f4f6);
    border-right: 1px solid var(--ame-border, #e5e7eb);
}

/* A thumbnail -- a flat button wrapping the small page image + its 1-based number; the current page gets the
   primary-tinted border + wash (the active-thumbnail highlight, conveyed to AT via aria-current too). */
.ame-docviewer__thumb {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 2px;
    flex: 0 0 auto;
    padding: 4px;
    border: 2px solid transparent;
    border-radius: var(--ame-control-corner-radius, 6px);
    background: var(--ame-surface, #ffffff);
    cursor: pointer;
}

.ame-docviewer__thumb:hover {
    border-color: var(--ame-border-strong, #d0d7de);
}

.ame-docviewer__thumb:focus-visible {
    outline: 2px solid var(--ame-form-border-focus-color, #4361ee);
    outline-offset: 1px;
}

.ame-docviewer__thumb--active {
    border-color: var(--ame-form-border-focus-color, #4361ee);
    background: var(--ame-spreadsheet-active-bg, rgba(67, 97, 238, 0.15));
}

.ame-docviewer__thumb-img {
    display: block;
    width: 100%;
    height: auto;
    border: 1px solid var(--ame-border, #e5e7eb);
    background: var(--ame-surface, #ffffff);
}

.ame-docviewer__thumb-num {
    font-size: 11px;
    font-variant-numeric: tabular-nums;
    color: var(--ame-text-muted, #8b9e9e);
}

/* The viewport -- the scrollable page area. FitWidth and Actual overflow it (so it scrolls); FitPage fits the
   page inside, so it does not. Centered so a letterboxed FitPage / a narrow Actual page sits in the middle. */
.ame-docviewer__viewport {
    flex: 1 1 auto;
    min-width: 0;
    display: flex;
    align-items: flex-start;
    justify-content: center;
    overflow: auto;
    padding: 12px;
    background: var(--ame-surface-muted, #f3f4f6);
}

/* The page image -- the fit modifier classes select the per-stack fit MATH:
   - fit-width: fills the viewport width, height follows (vertical overflow scrolls).
   - fit-page:  the WHOLE page fits inside the viewport (letterboxed via object-fit, no scroll needed).
   - actual:    intrinsic / 100% natural pixels (both axes may overflow and scroll). */
.ame-docviewer__page {
    display: block;
    background: var(--ame-surface, #ffffff);
    box-shadow: var(--ame-card-shadow-sm, 0 1px 2px rgba(0, 0, 0, 0.06));
}

.ame-docviewer__page--fit-width {
    width: 100%;
    height: auto;
}

.ame-docviewer__page--fit-page {
    max-width: 100%;
    max-height: 100%;
    width: auto;
    height: auto;
    object-fit: contain;
}

.ame-docviewer__page--actual {
    width: auto;
    height: auto;
    max-width: none;
    max-height: none;
}

/* The empty state -- a centered muted note when there are no pages. */
.ame-docviewer__empty {
    margin: auto;
    color: var(--ame-text-muted, #8b9e9e);
    font-size: 13px;
}

/* ========================================================================
   Command surface (Wave U·U12 = W20-G9): AMEToggleButton, AMEButtonGroup, AMEDropDownButton,
   AMESplitButton, AMEToolbar. All five COMPOSE the shipped .ame-button / .ame-menu__* /
   .ame-popover__* token chains established above -- no new color tokens, no inline hex. The one
   shared modifier, .is-active, marks a pressed AMEToggleButton row OR a selected AMEButtonGroup
   (Single mode) row with the same real --rz-primary accent fill AMEButton--primary already rides,
   so a toggled-on affordance always reads as "the accent color", on every theme/density variant, for
   free. */

/* ---------- ToggleButton pressed state ----------
   Applies over whatever AMEButtonStyle the consumer picked -- Active always wins visually via the
   real primary token pair (the on-accent-foreground-keeps-AA convention used throughout this file),
   so a pressed toggle reads unambiguously regardless of its base Style. */
.ame-button.is-active {
    background: var(--ame-button-bg-primary, #4361ee);
    color: var(--ame-button-fg-on-primary, #ffffff);
}

/* ---------- ButtonGroup ----------
   A seamless strip: 1px hairline borders between rows (via box-shadow, so adjoining borders do not
   double up), square interior corners, rounded only at the two strip ends. */
.ame-button-group {
    display: inline-flex;
    align-items: stretch;
}
.ame-button-group__item {
    border-radius: 0;
    box-shadow: -1px 0 0 0 rgba(0, 0, 0, 0.12);
}
.ame-button-group__item:first-child {
    border-top-left-radius: 4px;
    border-bottom-left-radius: 4px;
    box-shadow: none;
}
.ame-button-group__item:last-child {
    border-top-right-radius: 4px;
    border-bottom-right-radius: 4px;
}
.ame-button-group__separator {
    width: 1px;
    align-self: stretch;
    background: var(--ame-form-border-color, #e5e7eb);
}

/* ---------- DropDownButton / SplitButton menu body ----------
   The popover CARD already supplies the position + border + shadow (AMEPopover.__card); this is
   just the flex column of .ame-menu__* rows inside it -- no position:absolute here (unlike
   .ame-menu__dropdown, which assumes it IS the positioned card, not nested inside one). */
.ame-dropdown-menu {
    display: flex;
    flex-direction: column;
    gap: 1px;
    min-width: 160px;
    margin: -4px -6px;
}

/* ---------- SplitButton ----------
   The primary AMEButton + the arrow read as one composed control: the arrow sits flush against the
   primary's trailing edge, sharing its corner radii on the OUTER edge only, with a hairline seam
   between the two (INV-4: they are still two fully distinct, independently reachable targets). */
.ame-split-button {
    display: inline-flex;
    align-items: stretch;
}
.ame-split-button__arrow {
    padding: 0 8px;
    border-top-left-radius: 0;
    border-bottom-left-radius: 0;
    box-shadow: -1px 0 0 0 rgba(0, 0, 0, 0.12);
}
.ame-split-button > .ame-button:first-child {
    border-top-right-radius: 0;
    border-bottom-right-radius: 0;
}

/* ---------- Toolbar ----------
   A single-row flex strip; AllowOverflow pulls anything past the measured cut out of the flex flow
   (position:absolute + visibility:hidden) rather than unmounting it, so it stays measurable -- see
   AMEToolbar.razor's header comment. */
.ame-toolbar {
    position: relative;
    display: flex;
    align-items: center;
    gap: 4px;
    padding: 4px;
    background: var(--ame-card-bg, #ffffff);
    border: 1px solid var(--ame-card-border-color, #e5e7eb);
    border-radius: var(--ame-control-corner-radius, 6px);
    overflow: hidden;
}
.ame-toolbar__item {
    display: inline-flex;
    align-items: center;
    flex: 0 0 auto;
}
.ame-toolbar__item--overflowed {
    position: absolute;
    visibility: hidden;
    pointer-events: none;
}
.ame-toolbar__separator {
    width: 1px;
    align-self: stretch;
    margin: 2px 2px;
    background: var(--ame-form-border-color, #e5e7eb);
}
.ame-toolbar__overflow-btn {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 32px;
    height: var(--ame-form-height, 38px);
    padding: 0;
    background: transparent;
    border: 1px solid transparent;
    border-radius: 4px;
    color: var(--ame-form-fg, #1f2937);
    cursor: pointer;
    transition: background-color var(--ame-anim-duration-fast, 150ms) var(--ame-anim-easing, ease);
}
.ame-toolbar__overflow-btn:hover {
    background: var(--ame-surface-2, #f3f4f6);
}
.ame-toolbar__overflow-btn:focus-visible {
    outline: 2px solid var(--ame-form-border-focus-color);
    outline-offset: 2px;
}
.ame-toolbar__overflow-glyph {
    font-size: 18px;
    line-height: 1;
}
/* The permanent measurement probe -- same box as the real trigger so its width matches exactly, but
   never painted / never reachable (G-a/G-b: its width must be knowable before the real trigger
   exists in the DOM at all). */
.ame-toolbar__overflow-probe {
    position: absolute;
    visibility: hidden;
    pointer-events: none;
}
.ame-toolbar__overflow-menu {
    display: flex;
    flex-direction: column;
    gap: 1px;
    min-width: 160px;
}
.ame-toolbar__overflow-row {
    display: flex;
}
.ame-toolbar__overflow-row .ame-button,
.ame-toolbar__overflow-row .ame-toolbar__separator {
    width: 100%;
    justify-content: flex-start;
}

@media (prefers-reduced-motion: reduce) {
    .ame-toolbar__overflow-btn { transition: none; }
}

/* ---------- Sparkline (Wave 20·W20-G12) ----------
   A tiny, axis-less inline micro-chart (Line / Bar / WinLoss). The <svg> uses a 1:1 PIXEL viewBox
   (no preserveAspectRatio="none" -- unlike the treemap's stretched nominal box, a sparkline's strokes
   must stay crisp and its markers true circles) sized to the Width/Height params; every fill/stroke
   color is emitted INLINE per element (the effective Color token, or per-bar/marker Success/Danger),
   so this block only carries structural chrome -- stroke width, linecap/join, and pointer-events. */
.ame-sparkline {
    display: inline-block;
    line-height: 0;
}
.ame-sparkline__svg {
    display: block;
}
.ame-sparkline__line {
    stroke-width: 1.5;
    stroke-linecap: round;
    stroke-linejoin: round;
}
.ame-sparkline__bar,
.ame-sparkline__marker {
    pointer-events: none;
}

/* ---------- DropDownTree (Wave 20 · W20-G15) ----------
   A closed-field trigger (button, .ame-input for the shared form-control sizing/colors/focus-ring)
   whose AMEPopover popup hosts the shipped AMETreeView body verbatim -- no new tree chrome here,
   only the closed field's own layout. Mirrors .ame-dropdown__trigger/__label/__chevron but stays a
   distinct class so this tree-hosting popup's card sizing never entangles with the listbox
   AMEDropDown's. */
.ame-dropdowntree__trigger {
    display: inline-flex;
    align-items: center;
    justify-content: space-between;
    width: 100%;
    text-align: left;
    cursor: pointer;
}
.ame-dropdowntree__trigger:disabled {
    cursor: not-allowed;
    /* opacity comes from the shared .ame-input:disabled (0.6) — do not override it here. */
}
.ame-dropdowntree__label {
    flex: 1 1 auto;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
    color: var(--ame-form-fg);
}
.ame-dropdowntree__chevron {
    flex: 0 0 auto;
    width: 8px;
    height: 8px;
    margin-left: 8px;
    border-right: 2px solid currentColor;
    border-bottom: 2px solid currentColor;
    transform: rotate(45deg) translate(-1px, -1px);
    opacity: 0.7;
    transition: transform var(--ame-anim-duration-base, 150ms) var(--ame-anim-easing, ease);
}
.ame-dropdowntree__trigger[aria-expanded="true"] .ame-dropdowntree__chevron {
    transform: rotate(225deg) translate(-1px, -1px);
}
@media (prefers-reduced-motion: reduce) {
    .ame-dropdowntree__chevron { transition: none; }
}

/* ========================================================================
   AMEFilterBuilder (Wave 20 G11) -- the visual RULE-TREE filter builder, the AMESmartQuery sibling. A column of
   the recursive tree (AMEFilterNodeView instances) then the Apply toolbar. Every group is a bordered, slightly
   muted panel so nesting reads as a box-within-a-box (in addition to the per-level margin-left indent below and
   the role=group aria-label carrying the combinator + level for AT); a condition is a plain row of the shared
   form-token editors (the grid filter-row token chain -- no bespoke input styling). No inline hex anywhere. */
.ame-filter-builder {
    display: flex;
    flex-direction: column;
    gap: 10px;
}

/* A group container -- bordered + a muted surface so nesting is visible as concentric boxes; the per-level
   margin-left (set inline by AMEFilterNodeView) adds the indent on top of the border. */
.ame-filter-builder__group {
    display: flex;
    flex-direction: column;
    gap: 8px;
    padding: 8px;
    border: 1px solid var(--ame-border, #e5e7eb);
    border-radius: var(--ame-control-corner-radius, 6px);
    background: var(--ame-surface-muted, #f3f4f6);
}

/* The group's own toolbar row -- the And/Or combinator select + the add-condition/add-group/remove buttons. */
.ame-filter-builder__group-toolbar {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 6px;
}

/* Nested children stack vertically with a small gap; each child (group or condition) carries its own indent. */
.ame-filter-builder__children {
    display: flex;
    flex-direction: column;
    gap: 8px;
}

/* A condition row -- column select + operator select + value input + remove, laid out inline and wrapping on
   narrow hosts. Sits directly on the builder background (no extra panel -- only groups get the bordered box). */
.ame-filter-builder__condition {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 6px;
}

/* The combinator, column, and operator selects -- native <select>s themed via the shared grid filter-row form
   token chain (height / background / foreground / border / focus ring), so they match the grid's own per-column
   filter operator select without reaching for AMEDropDown (which would force an EditForm). */
.ame-filter-builder__combine,
.ame-filter-builder__column,
.ame-filter-builder__operator {
    height: var(--ame-form-height, 32px);
    padding: 0 var(--ame-form-padding-x, 8px);
    background: var(--ame-form-bg, #ffffff);
    color: var(--ame-form-fg, #1f2937);
    border: 1px solid var(--ame-form-border-color, #d0d7de);
    border-radius: 4px;
    font-size: var(--ame-form-font-size, 13px);
    font-family: inherit;
    box-sizing: border-box;
    cursor: pointer;
    transition: border-color var(--ame-anim-duration-base, 150ms) var(--ame-anim-easing, ease);
}

/* The value text input -- the same token chain as the selects above, sized to grow with the row. */
.ame-filter-builder__value {
    flex: 1 1 160px;
    min-width: 100px;
    height: var(--ame-form-height, 32px);
    padding: 0 var(--ame-form-padding-x, 8px);
    background: var(--ame-form-bg, #ffffff);
    color: var(--ame-form-fg, #1f2937);
    border: 1px solid var(--ame-form-border-color, #d0d7de);
    border-radius: 4px;
    font-size: var(--ame-form-font-size, 13px);
    font-family: inherit;
    box-sizing: border-box;
    transition: border-color var(--ame-anim-duration-base, 150ms) var(--ame-anim-easing, ease);
}

.ame-filter-builder__combine:focus-visible,
.ame-filter-builder__column:focus-visible,
.ame-filter-builder__operator:focus-visible,
.ame-filter-builder__value:focus,
.ame-filter-builder__value:focus-visible {
    outline: 2px solid var(--ame-form-border-focus-color, #4361ee);
    outline-offset: 1px;
    border-color: var(--ame-form-border-focus-color, #4361ee);
}

/* The add-condition / add-group actions -- quiet outline buttons (no accent fill; they are frequent, low-stakes
   affordances, not the primary call to action -- that is the builder's Apply button below). */
.ame-filter-builder__add-condition,
.ame-filter-builder__add-group {
    height: var(--ame-form-height, 32px);
    padding: 0 10px;
    background: var(--ame-form-bg, #ffffff);
    color: var(--ame-form-fg, #1f2937);
    border: 1px solid var(--ame-form-border-color, #d0d7de);
    border-radius: 4px;
    font-size: var(--ame-form-font-size, 13px);
    font-family: inherit;
    cursor: pointer;
}

.ame-filter-builder__add-condition:hover,
.ame-filter-builder__add-group:hover {
    border-color: var(--ame-form-border-focus-color, #4361ee);
}

/* Remove (per-row and per-group) -- the danger token so the destructive action reads distinctly from the quiet
   add buttons, never conveyed by color alone (the text itself reads "Remove"). */
.ame-filter-builder__remove {
    height: var(--ame-form-height, 32px);
    padding: 0 10px;
    background: var(--ame-form-bg, #ffffff);
    color: var(--rz-danger, #ef4444);
    border: 1px solid var(--ame-form-border-color, #d0d7de);
    border-radius: 4px;
    font-size: var(--ame-form-font-size, 13px);
    font-family: inherit;
    cursor: pointer;
}

.ame-filter-builder__remove:hover {
    border-color: var(--rz-danger, #ef4444);
}

.ame-filter-builder__add-condition:focus-visible,
.ame-filter-builder__add-group:focus-visible,
.ame-filter-builder__remove:focus-visible {
    outline: 2px solid var(--ame-form-border-focus-color, #4361ee);
    outline-offset: 1px;
}

/* The builder's own toolbar -- the Apply button, the ONE emit funnel (the shared chat-composer "send" token chain,
   the same accent pair AMESmartQuery's submit button rides, so both emitters look consistent). */
.ame-filter-builder__toolbar {
    display: flex;
    justify-content: flex-end;
}

.ame-filter-builder__apply {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    min-height: 38px;
    padding: 0 16px;
    border: 1px solid transparent;
    border-radius: 8px;
    cursor: pointer;
    background: var(--ame-chat-composer-send-bg, #4361ee);
    color: var(--ame-chat-composer-send-fg, #ffffff);
    font-family: inherit;
    font-size: 13px;
    font-weight: 600;
}

.ame-filter-builder__apply:hover {
    background: var(--ame-chat-composer-send-bg-hover, #3651d4);
}

.ame-filter-builder__apply:focus-visible {
    outline: 2px solid var(--ame-form-border-focus-color, #4361ee);
    outline-offset: 1px;
}

/* ============================================================
   AMETileLayout (Wave 20·G10, U19) — a CSS-grid dashboard: Tiles
   pack into a Columns-wide unit grid (AMETileLayoutMath.Reflow in
   AME.Design). The control owns the tile CHROME (bordered shell +
   drag affordance + focus ring); the consumer's TileTemplate fills
   only the BODY. AllowDragMove default false -> a static grid (no
   draggable/tabindex/handlers). Ctrl+Arrow move + pointer drag-drop
   funnel through the single PerformMove seam (U19). Resize handles
   ship dark until U20.
   ============================================================ */

.ame-tilelayout {
    display: grid;
    box-sizing: border-box;
}

.ame-tilelayout__tile {
    box-sizing: border-box;
    display: flex;
    flex-direction: column;
    min-width: 0;
    min-height: 0;
    overflow: hidden;
    background: var(--ame-tilelayout-tile-bg);
    border: 1px solid var(--ame-tilelayout-tile-border);
    border-radius: 8px;
    color: var(--ame-tilelayout-tile-fg);
}

/* Only present when AllowDragMove renders draggable/tabindex on the tile (the AMETaskBoard card
   precedent) -- the grab affordance + the shared focus ring (never a hardcoded outline). touch-action:
   none guards the G6 drag-vs-scroll fight -- a tile drag inside a scrollable dashboard must not also
   scroll the page on touch input. */
.ame-tilelayout__tile[draggable="true"] {
    cursor: grab;
    touch-action: none;
}

.ame-tilelayout__tile[draggable="true"]:active {
    cursor: grabbing;
}

.ame-tilelayout__tile:focus-visible {
    outline: 2px solid var(--ame-form-border-focus-color);
    outline-offset: 2px;
}

.ame-tilelayout__tile-body {
    flex: 1 1 auto;
    min-width: 0;
    min-height: 0;
    overflow: auto;
    padding: 10px 12px;
}

/* Only present when AllowDragResize renders the grip (U20). The tile must anchor the absolutely-placed
   handle, so the resizable modifier turns on positioning context. Non-breaking: absent when resize is off. */
.ame-tilelayout__tile--resizable {
    position: relative;
}

/* The bottom-right resize grip. The interactive hit-target is a full 24x24 (the a11y minimum); the visible
   glyph is the small corner mark drawn by the ::after wedge, tinted with the tile border token (never a
   hardcoded color). touch-action:none guards the G6 drag-vs-scroll fight on touch input. */
.ame-tilelayout__resize-handle {
    position: absolute;
    right: 0;
    bottom: 0;
    width: 24px;
    height: 24px;
    box-sizing: border-box;
    cursor: nwse-resize;
    touch-action: none;
    background: transparent;
}

.ame-tilelayout__resize-handle::after {
    content: "";
    position: absolute;
    right: 3px;
    bottom: 3px;
    width: 8px;
    height: 8px;
    border-right: 2px solid var(--ame-tilelayout-tile-border);
    border-bottom: 2px solid var(--ame-tilelayout-tile-border);
}

.ame-tilelayout__resize-handle:hover::after,
.ame-tilelayout__resize-handle:focus-visible::after {
    border-right-color: var(--ame-form-border-focus-color);
    border-bottom-color: var(--ame-form-border-focus-color);
}

/* ---------- Barcode (Wave 20·W20-G14·U21) ----------
   A Code 128 barcode (ISO/IEC 15417) rendered as inline SVG rects + one HRI <text> line beneath them. The
   <svg> uses a 1:1 PIXEL viewBox (no preserveAspectRatio) with shape-rendering="crispEdges" so bars stay
   pixel-crisp. Every fill (bars / background / HRI text) is emitted INLINE as a FIXED hex resolved by
   AMEBarcode.razor.cs (never a --ame-* var -- a barcode must stay dark-on-light to remain scannable, so it
   deliberately does NOT theme-switch), so this block only carries structural chrome. */
.ame-barcode {
    display: inline-block;
    line-height: 0;
}
.ame-barcode__svg {
    display: block;
}
.ame-barcode__bar {
    pointer-events: none;
}
.ame-barcode__text {
    font-family: inherit;
    font-size: 12px;
    pointer-events: none;
}
