/* ============================================================
   The component kit: the one look of every control in the panel.
   Components/Ui/*.razor render these classes; plain markup may use
   them directly on native elements (<input class="ui-input">).
   Page and editor stylesheets lay things out; they never restyle a
   control.

   One rule against boxes in boxes: a card (.ui-card) is a block on
   the page. Inside a card, a panel or a dialog, groups are flat
   sections (.ui-section) and rows split by lines (.ui-list), never
   another card or a grey box.
   ============================================================ */

/* ---------- Per theme ---------- */

/* Something drawn once per theme (the logo files, assets/brand): only the current theme's one shows. */
:root[data-theme="dark"] .ui-light-only,
:root:not([data-theme="dark"]) .ui-dark-only { display: none !important; }

/* ---------- Buttons ---------- */

.ui-btn {
    --btn-fg: var(--ui-secondary-text);
    --btn-bg: var(--ui-surface);
    --btn-line: var(--ui-line-strong);
    --btn-hover-bg: var(--ui-hover);
    --btn-hover-line: var(--ui-line-hover);
    --btn-pressed-bg: var(--ui-chip-bg);
    --btn-shadow: var(--ui-shadow-xs);
    display: inline-flex;
    align-items: center;
    justify-content: center;
    gap: 0.45rem;
    flex-shrink: 0;
    height: var(--ui-btn-h);
    min-width: 0;
    padding: 0 1rem;
    margin: 0;
    border: 1px solid var(--btn-line);
    border-radius: var(--ui-radius-sm);
    background-color: var(--btn-bg);
    box-shadow: var(--btn-shadow);
    color: var(--btn-fg);
    font-family: var(--font-family);
    font-size: var(--ui-fs-md);
    /* Secondary actions (secondary, soft, ghost, dashed, links) in a lighter weight; the solid ones (primary, danger)
       set their own, bolder. */
    font-weight: 500;
    line-height: 1.2;
    white-space: nowrap;
    text-decoration: none;
    cursor: pointer;
    user-select: none;
    transition:
        background-color var(--ui-duration) ease,
        border-color var(--ui-duration) ease,
        color var(--ui-duration) ease,
        box-shadow var(--ui-duration) ease;
}

.ui-btn > .bi { display: inline-flex; font-size: 1em; line-height: 1; }
.ui-btn > .ui-btn-label { overflow: hidden; text-overflow: ellipsis; }

.ui-btn:hover:not(:disabled, .is-disabled) {
    background-color: var(--btn-hover-bg);
    border-color: var(--btn-hover-line);
    color: var(--btn-fg);
    text-decoration: none;
}

.ui-btn:active:not(:disabled, .is-disabled) { background-color: var(--btn-pressed-bg); }

.ui-btn:focus-visible {
    outline: 0;
    box-shadow: var(--btn-shadow), var(--ui-focus);
}

/* Disabled keeps its pointer events, so the title that says why still shows. */
.ui-btn:disabled,
.ui-btn.is-disabled {
    opacity: var(--ui-disabled-opacity);
    cursor: not-allowed;
}

a.ui-btn.is-disabled { pointer-events: none; }

/* Busy (Loading): disabled, but at full strength with its spinner, so it reads as working, not unavailable. */
.ui-btn[aria-busy="true"]:disabled { opacity: 1; cursor: progress; }

/* Just succeeded (Done): the green of «done» for a moment, on any kind. */
.ui-btn.is-done,
.ui-btn.is-done:hover:not(:disabled, .is-disabled) {
    --btn-fg: var(--ui-ok);
    --btn-bg: var(--ui-ok-soft);
    --btn-line: var(--ui-ok-line);
    --btn-hover-bg: var(--ui-ok-soft);
    --btn-hover-line: var(--ui-ok-line);
}

.ui-btn-primary {
    --btn-fg: var(--ui-primary-fg);
    --btn-bg: var(--ui-primary-bg);
    --btn-line: transparent;
    --btn-hover-bg: var(--ui-primary-hover);
    --btn-hover-line: transparent;
    --btn-pressed-bg: var(--ui-primary-pressed);
    --btn-shadow: var(--ui-primary-shadow);
    font-weight: 600;
}

.ui-btn-danger {
    --btn-fg: var(--ui-on-solid);
    --btn-bg: var(--ui-danger-bg);
    --btn-line: transparent;
    --btn-hover-bg: var(--ui-danger-hover);
    --btn-hover-line: transparent;
    --btn-pressed-bg: var(--ui-danger-pressed);
    --btn-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.14), 0 1px 2px rgba(20, 20, 30, 0.18);
    font-weight: 600;
}

/* Secondary is the default look above: white, a line and a hairline shadow, so it reads on the grey page and on a
   white card alike. Soft is the quiet grey fill for secondary actions packed in a row (toolbars, table rows). */
.ui-btn-soft {
    --btn-bg: var(--ui-secondary-bg);
    --btn-line: transparent;
    --btn-hover-bg: var(--ui-secondary-hover);
    --btn-hover-line: transparent;
    --btn-pressed-bg: var(--ui-secondary-pressed);
    --btn-shadow: 0 0 0 transparent;
}

.ui-btn-danger-soft {
    --btn-fg: var(--ui-error);
    --btn-bg: var(--ui-error-soft);
    --btn-line: transparent;
    --btn-hover-bg: var(--ui-error-hover);
    --btn-hover-line: transparent;
    --btn-pressed-bg: var(--ui-error-line);
    --btn-shadow: 0 0 0 transparent;
}

/* Ghost: no fill until hovered. Icon buttons in rows, headers and toolbars. */
.ui-btn-ghost {
    --btn-fg: var(--ui-muted);
    --btn-bg: transparent;
    --btn-line: transparent;
    --btn-hover-bg: var(--ui-chip-bg);
    --btn-hover-line: transparent;
    --btn-pressed-bg: var(--ui-secondary-pressed);
    --btn-shadow: 0 0 0 transparent;
}

.ui-btn-ghost:hover:not(:disabled, .is-disabled) { --btn-fg: var(--ui-text); }
/* Destructive: red at rest, on any kind (a ghost bin, a soft delete on a tile), and a red tint under the pointer. */
.ui-btn.is-danger:not(:disabled, .is-disabled) { --btn-fg: var(--ui-error); }
.ui-btn.is-danger:hover:not(:disabled, .is-disabled) { --btn-fg: var(--ui-error); --btn-hover-bg: var(--ui-error-soft); --btn-hover-line: var(--ui-error-line); }

.ui-btn-link {
    --btn-fg: var(--ui-accent-strong);
    --btn-bg: transparent;
    --btn-line: transparent;
    --btn-hover-bg: transparent;
    --btn-hover-line: transparent;
    --btn-pressed-bg: transparent;
    --btn-shadow: 0 0 0 transparent;
    height: auto;
    padding: 0.15rem 0.1rem;
    gap: 0.3rem;
}

.ui-btn-link:hover:not(:disabled, .is-disabled) { --btn-fg: var(--ui-primary-hover); text-decoration: underline; text-underline-offset: 2px; }
.ui-btn-link:focus-visible { box-shadow: none; outline: 2px solid var(--ui-accent); outline-offset: 2px; border-radius: 4px; }

/* The dashed «add» row that sits at the end of a list. */
.ui-btn-dashed {
    --btn-fg: var(--ui-muted);
    --btn-bg: transparent;
    --btn-line: var(--ui-line-strong);
    --btn-hover-bg: var(--ui-accent-subtle);
    --btn-hover-line: var(--ui-accent);
    --btn-shadow: 0 0 0 transparent;
    border-style: dashed;
}

.ui-btn-dashed:hover:not(:disabled, .is-disabled) { --btn-fg: var(--ui-accent-strong); }

/* On white (a card, a dialog, a side panel, the editors' bars), a white button with a line reads as one more field:
   there Secondary takes Soft's grey fill and loses its line, and the dashed «add» row is filled grey under its dashes.
   On the grey page both stay white, which is what sets them off from it. */
:is(.ui-card, .ui-dialog, .mx-inspector, .mx-bar, .mx-foot, .mx-nav, .mx-toolbar, .kx-header, .kx-nav, .kx-savebar, .kx-json, .kx-remote-bar)
    .ui-btn-secondary:not([aria-pressed="true"], .is-done) {
    --btn-bg: var(--ui-secondary-bg);
    --btn-line: transparent;
    --btn-hover-bg: var(--ui-secondary-hover);
    --btn-hover-line: transparent;
    --btn-pressed-bg: var(--ui-secondary-pressed);
    --btn-shadow: 0 0 0 transparent;
}

:is(.ui-card, .ui-dialog, .mx-inspector, .mx-bar, .mx-foot, .mx-nav, .mx-toolbar, .kx-header, .kx-nav, .kx-savebar, .kx-json, .kx-remote-bar)
    .ui-btn-dashed {
    --btn-bg: var(--ui-secondary-bg);
    --btn-hover-bg: var(--ui-accent-subtle);
}

/* On a note's tinted fill (a UiAlert, blue, amber or red) a grey button would melt into it: there Secondary is white
   again, framed and lifted, wherever the note stands. As specific as the rule above, and after it. */
.ui-alert .ui-btn-secondary:not([aria-pressed="true"], .is-done) {
    --btn-bg: var(--ui-surface);
    --btn-line: var(--ui-line-strong);
    --btn-hover-bg: var(--ui-surface-raised);
    --btn-hover-line: var(--ui-line-hover);
    --btn-pressed-bg: var(--ui-chip-bg);
    --btn-shadow: var(--ui-shadow-xs);
}

/* Switched on (aria-pressed). */
.ui-btn[aria-pressed="true"]:not(.ui-btn-primary, .ui-btn-danger) {
    --btn-fg: var(--ui-accent-strong);
    --btn-bg: var(--ui-accent-soft);
    --btn-line: var(--ui-info-line);
    --btn-hover-bg: var(--ui-accent-soft);
    --btn-hover-line: var(--ui-accent);
}

/* The one gradient of the UI: the AI assistant, violet into the accent, with a glow of its own. */
.ui-btn-ai {
    --btn-fg: #fff;
    --btn-bg: linear-gradient(135deg, #9b4dff 0%, #5b5ff5 55%, #3f7bff 100%);
    --btn-line: transparent;
    --btn-hover-line: transparent;
    --btn-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.22), 0 4px 14px -4px rgba(91, 95, 245, 0.55);
    background-image: var(--btn-bg);
    background-color: #5b5ff5;
}

.ui-btn-ai:hover:not(:disabled, .is-disabled),
.ui-btn-ai:active:not(:disabled, .is-disabled) { background-image: linear-gradient(135deg, #8a3cf0 0%, #4a4fe6 55%, #2f6af0 100%); }

.ui-btn-sm {
    height: var(--ui-btn-h-sm);
    padding: 0 0.75rem;
    gap: 0.35rem;
    font-size: var(--ui-fs-sm);
}

.ui-btn-block { display: flex; width: 100%; }

/* Icon only: a square. Always with a title and an aria-label (UiButton sets both from Title). */
.ui-btn-icon { width: var(--ui-btn-h); padding: 0; }
.ui-btn-icon.ui-btn-sm { width: var(--ui-btn-h-sm); }
.ui-btn-icon.ui-btn-xs { width: 28px; height: 28px; border-radius: var(--ui-radius-xs); font-size: var(--ui-fs-sm); }
.ui-btn-icon > .bi { font-size: 1rem; }
.ui-btn-icon.ui-btn-xs > .bi { font-size: 0.9rem; }

.ui-btn .ui-spinner { width: 1em; height: 1em; color: inherit; }

/* Buttons side by side. */
.ui-btn-row { display: flex; align-items: center; gap: 0.5rem; flex-wrap: wrap; }
.ui-btn-row.is-end { justify-content: flex-end; }

/* ---------- Fields ---------- */

.ui-input,
.ui-select,
.ui-textarea {
    display: block;
    width: 100%;
    min-width: 0;
    height: var(--ui-field-h);
    padding: 0 0.75rem;
    margin: 0;
    border: 1px solid var(--ui-line-strong);
    border-radius: var(--ui-radius-sm);
    background-color: var(--ui-field-bg);
    box-shadow: var(--ui-shadow-xs);
    color: var(--ui-text);
    font-family: var(--font-family);
    font-size: var(--ui-fs-md);
    line-height: 1.5;
    caret-color: var(--ui-accent);
    outline: none;
    appearance: none;
    transition:
        border-color var(--ui-duration) ease,
        box-shadow var(--ui-duration) ease,
        background-color var(--ui-duration) ease;
}

.ui-input::placeholder,
.ui-textarea::placeholder { color: var(--ui-faint); opacity: 1; }

.ui-select {
    padding-right: 2.25rem;
    background-image: var(--ui-chevron);
    background-repeat: no-repeat;
    background-position: right 0.75rem center;
    background-size: 12px 12px;
    cursor: pointer;
    text-overflow: ellipsis;
}

.ui-textarea {
    height: auto;
    min-height: 96px;
    padding: 0.6rem 0.75rem;
    resize: vertical;
}

/* Browser autofill paints its own fill; cover it with the field's white and keep the text colour. */
.ui-input:-webkit-autofill,
.ui-input:-webkit-autofill:hover,
.ui-input:-webkit-autofill:active {
    -webkit-box-shadow: 0 0 0 30px var(--ui-field-bg) inset !important;
    box-shadow: 0 0 0 30px var(--ui-field-bg) inset !important;
    -webkit-text-fill-color: var(--ui-text) !important;
    transition: background-color 5000s ease-in-out 0s;
}

.ui-input:-webkit-autofill:focus {
    -webkit-box-shadow: 0 0 0 30px var(--ui-field-bg) inset, var(--ui-ring) !important;
    box-shadow: 0 0 0 30px var(--ui-field-bg) inset, var(--ui-ring) !important;
    -webkit-text-fill-color: var(--ui-text) !important;
}

.ui-input[type="number"] { font-variant-numeric: tabular-nums; }

.ui-input-sm,
.ui-select-sm { height: var(--ui-btn-h-sm); font-size: var(--ui-fs-sm); }
.ui-select-sm { padding-right: 2rem; background-position: right 0.6rem center; }

:is(.ui-input, .ui-select, .ui-textarea):hover:not(:disabled, :focus, [readonly], .is-invalid, .invalid, .input-validation-error) {
    border-color: var(--ui-line-hover);
}

:is(.ui-input, .ui-select, .ui-textarea):focus {
    border-color: var(--ui-accent);
    box-shadow: var(--ui-ring);
}

/* Invalid: .is-invalid, Blazor's .invalid, and ASP.NET's .input-validation-error on the Razor login pages. */
:is(.ui-input, .ui-select, .ui-textarea):is(.is-invalid, .invalid, .input-validation-error) {
    border-color: var(--ui-error);
    background-color: var(--ui-error-soft);
}

:is(.ui-input, .ui-select, .ui-textarea):is(.is-invalid, .invalid, .input-validation-error):focus { box-shadow: var(--ui-ring-danger); }

:is(.ui-input, .ui-select, .ui-textarea):disabled,
:is(.ui-input, .ui-textarea)[readonly] {
    background-color: var(--ui-bg);
    border-color: var(--ui-line);
    box-shadow: none;
    color: var(--ui-muted);
}

:is(.ui-input, .ui-select, .ui-textarea):disabled { cursor: not-allowed; }

/* Phones: 16px text in fields, or iOS zooms the page in on focus; regular fields and buttons grow to a 44 px touch
   target (small ones stay: they live in dense rows, beside something as tall). */
@media (max-width: 768px) {
    :is(.ui-input, .ui-select, .ui-textarea) { font-size: 16px; }
    :is(.ui-input, .ui-select):not(.ui-input-sm, .ui-select-sm) { height: 44px; }
    .ui-btn:not(.ui-btn-sm, .ui-btn-link) { height: 44px; }
    .ui-btn-icon:not(.ui-btn-sm) { width: 44px; }
}

/* A field with an icon, a unit or a button inside its edge: the wrapper draws the field. */
.ui-input-group {
    position: relative;
    display: flex;
    align-items: center;
    width: 100%;
    min-width: 0;
}

.ui-input-group > .ui-input { flex: 1; }
.ui-input-group > .ui-input-icon {
    position: absolute;
    left: 0.75rem;
    display: inline-flex;
    color: var(--ui-faint);
    pointer-events: none;
    font-size: 1rem;
}

.ui-input-group > .ui-input-icon ~ .ui-input { padding-left: 2.25rem; }

.ui-input-group > .ui-input-suffix {
    position: absolute;
    right: 0.75rem;
    color: var(--ui-faint);
    font-size: var(--ui-fs-sm);
    pointer-events: none;
}

/* The value from the left, as in every other field; the unit waits at the end. */
.ui-input-group:has(> .ui-input-suffix) > .ui-input { padding-right: 2.5rem; }

/* A small ghost button at the right end, inside the field (clear the search, show the password). */
.ui-input-group > .ui-input-action { position: absolute; right: 4px; }
.ui-input-group:has(> .ui-input-action) > .ui-input { padding-right: 2.5rem; }

/* A field and a button that belong together, flush: [ field ][ Копировать ]. */
.ui-input-join { display: flex; gap: 0.5rem; align-items: center; min-width: 0; }
.ui-input-join > :is(.ui-input, .ui-select, .ui-input-group) { flex: 1; }

/* A labelled field: label above, hint or error under. */
.ui-field {
    display: flex;
    flex-direction: column;
    gap: 0.35rem;
    min-width: 0;
}

.ui-field-head {
    display: flex;
    align-items: center;
    gap: 0.5rem;
    min-height: 1.25rem;
}

.ui-label {
    margin: 0;
    color: var(--ui-text);
    font-size: var(--ui-fs-sm);
    font-weight: 600;
    line-height: 1.4;
}

.ui-label-optional {
    margin-left: 0.35rem;
    color: var(--ui-faint);
    font-size: var(--ui-fs-xs);
    font-weight: 400;
}

.ui-field-aside { margin-left: auto; display: inline-flex; align-items: center; gap: 0.35rem; }

.ui-hint {
    margin: 0;
    color: var(--ui-muted);
    font-size: var(--ui-fs-sm);
    line-height: 1.4;
}

.ui-error,
.ui-field .validation-message {
    margin: 0;
    color: var(--ui-error);
    font-size: var(--ui-fs-sm);
    line-height: 1.4;
}

/* Label on the left, a small control on the right (a switch, a short select). */
.ui-field.is-inline {
    flex-direction: row;
    align-items: center;
    justify-content: space-between;
    gap: 1rem;
}

/* Fields in a stack and in a grid. */
.ui-form { display: flex; flex-direction: column; gap: 1rem; min-width: 0; }
.ui-form-grid { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 1rem; }
.ui-form-grid > .is-full { grid-column: 1 / -1; }

@media (max-width: 640px) {
    .ui-form-grid { grid-template-columns: minmax(0, 1fr); }
}

/* ---------- Checkbox, radio, switch ---------- */

.ui-checkbox,
.ui-radio {
    flex-shrink: 0;
    width: 18px;
    height: 18px;
    margin: 0;
    border: 1.5px solid var(--ui-control-line);
    border-radius: 5px;
    background: var(--ui-field-bg) center / 14px 14px no-repeat;
    appearance: none;
    cursor: pointer;
    transition: background-color var(--ui-duration) ease, border-color var(--ui-duration) ease, box-shadow var(--ui-duration) ease;
}

.ui-radio { border-radius: 50%; }

.ui-checkbox:hover:not(:disabled),
.ui-radio:hover:not(:disabled) { border-color: var(--ui-accent); }

.ui-checkbox:checked,
.ui-checkbox:indeterminate {
    border-color: var(--ui-accent);
    background-color: var(--ui-accent);
    background-image: var(--ui-check);
}

.ui-checkbox:indeterminate {
    background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3E%3Cpath d='M4 8h8' stroke='%23fff' stroke-width='2.2' stroke-linecap='round'/%3E%3C/svg%3E");
}

.ui-radio:checked {
    border: 5px solid var(--ui-accent);
}

.ui-checkbox:focus-visible,
.ui-radio:focus-visible { outline: 0; box-shadow: var(--ui-ring); }

.ui-checkbox:disabled,
.ui-radio:disabled { opacity: var(--ui-disabled-opacity); cursor: not-allowed; }

/* A checkbox or radio with its label: the whole thing is the hit area. */
.ui-check {
    display: flex;
    align-items: flex-start;
    gap: 0.6rem;
    margin: 0;
    color: var(--ui-text);
    font-size: var(--ui-fs-md);
    line-height: 1.4;
    cursor: pointer;
}

.ui-check > :is(.ui-checkbox, .ui-radio) { margin-top: 0.1rem; }
.ui-check.is-disabled { cursor: not-allowed; opacity: var(--ui-disabled-opacity); }

/* The rows are <label>s: app.css gives every label the field-label look (sm/600), which they must not inherit. */
.ui-check,
.ui-switch-row { font-weight: 400; line-height: 1.4; }

.ui-check-text { display: flex; flex-direction: column; gap: 0.15rem; min-width: 0; }
.ui-check-label { font-weight: 500; }
.ui-check-desc { color: var(--ui-muted); font-size: var(--ui-fs-sm); font-weight: 400; }

/* The switch: a checkbox drawn as a track and a knob. */
.ui-switch {
    position: relative;
    flex-shrink: 0;
    width: 36px;
    height: 20px;
    margin: 0;
    border: 0;
    border-radius: 999px;
    background: var(--ui-switch-off);
    appearance: none;
    cursor: pointer;
    transition: background-color var(--ui-duration) ease, box-shadow var(--ui-duration) ease;
}

.ui-switch::before {
    content: "";
    position: absolute;
    top: 2px;
    left: 2px;
    width: 16px;
    height: 16px;
    border-radius: 50%;
    background: #fff;
    box-shadow: 0 1px 2px rgba(16, 24, 40, 0.2);
    transition: transform 0.2s ease;
}

.ui-switch:checked { background: var(--ui-accent); }
.ui-switch:checked::before { transform: translateX(16px); }
.ui-switch.is-danger:checked { background: var(--ui-error); }
.ui-switch:focus-visible { outline: 0; box-shadow: var(--ui-ring); }
.ui-switch:disabled { opacity: var(--ui-disabled-opacity); cursor: not-allowed; }

@media (prefers-reduced-motion: reduce) {
    .ui-switch::before { transition: none; }
}

/* A setting: its name and explanation on the left, the switch on the right. Flat: in a list of them, .ui-list
   draws the lines between. */
.ui-switch-row {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 1rem;
    margin: 0;
    padding: 0.75rem 0;
    cursor: pointer;
}

.ui-switch-row.is-bare { padding: 0; }
.ui-switch-row.is-disabled { cursor: not-allowed; }
.ui-switch-row.is-disabled .ui-check-text { opacity: 0.7; }
.ui-switch-row .ui-check-label { font-size: var(--ui-fs-md); font-weight: 500; color: var(--ui-text); }

/* ---------- Surfaces ---------- */

/* A card: a block on the page. Never inside another card; see the note at the top. */
.ui-card {
    display: flex;
    flex-direction: column;
    min-width: 0;
    border: 1px solid var(--ui-line);
    border-radius: var(--ui-radius);
    background: var(--ui-surface);
    box-shadow: var(--ui-shadow-card);
}

.ui-card-head {
    display: flex;
    align-items: flex-start;
    gap: 0.75rem;
    padding: 1.1rem 1.25rem 0;
}

/* The icon of a card: a small raised tile with a hairline, the glyph in the accent. */
.ui-card-icon {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    flex-shrink: 0;
    width: 32px;
    height: 32px;
    border: 1px solid var(--ui-line);
    border-radius: var(--ui-radius-sm);
    background: linear-gradient(180deg, var(--ui-surface), var(--ui-card-bg));
    box-shadow: var(--ui-shadow-xs);
    color: var(--ui-accent);
    font-size: 1rem;
}

.ui-card-titles { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: 0.2rem; }

.ui-card-title {
    margin: 0;
    color: var(--ui-text);
    font-size: var(--ui-fs-lg);
    font-weight: 700;
    line-height: 1.3;
    letter-spacing: -0.01em;
}

.ui-card-icon + .ui-card-titles .ui-card-title { line-height: 32px; }
.ui-card-icon + .ui-card-titles:has(.ui-card-desc) .ui-card-title { line-height: 1.3; }

.ui-card-desc {
    margin: 0;
    color: var(--ui-muted);
    font-size: var(--ui-fs-sm);
    line-height: 1.45;
}

.ui-card-actions { display: flex; align-items: center; gap: 0.5rem; flex-shrink: 0; margin-left: auto; }

.ui-card-body { padding: 1.1rem 1.25rem 1.25rem; min-width: 0; }
.ui-card-body.is-flush { padding: 0; }
.ui-card-head + .ui-card-body.is-flush { margin-top: 1rem; border-top: 1px solid var(--ui-line); }

.ui-card-foot {
    display: flex;
    align-items: center;
    justify-content: flex-end;
    gap: 0.5rem;
    padding: 0.85rem 1.25rem;
    border-top: 1px solid var(--ui-line);
}

/* A switched-off card: only its header, toned down. */
.ui-card.is-muted { background: var(--ui-card-bg); box-shadow: none; }
.ui-card.is-muted .ui-card-icon { box-shadow: none; background: var(--ui-chip-bg); }
.ui-card.is-muted .ui-card-head { padding-bottom: 1.1rem; }
.ui-card.is-muted .ui-card-icon { background: var(--ui-chip-bg); color: var(--ui-faint); }

@media (max-width: 860px) {
    .ui-card-head { padding: 1rem 1rem 0; }
    .ui-card-body { padding: 1rem; }
    .ui-card-foot { padding: 0.75rem 1rem; }
}

/* A titled card over its content: a hairline under the head, inset to the card's padding, so every titled card reads
   the same whatever its first row is (the kiosk editor's settings rows draw none over their first). A flush body (a
   table) draws its own line, from edge to edge (above). */
.ui-card-head:has(+ .ui-card-body:not(.is-flush)) {
    margin: 0 1.25rem;
    padding: 1.1rem 0 0.85rem;
    border-bottom: 1px solid var(--ui-line);
}

@media (max-width: 860px) {
    .ui-card-head:has(+ .ui-card-body:not(.is-flush)) {
        margin: 0 1rem;
        padding: 1rem 0 0.8rem;
    }
}

/* A group inside a card, a side panel or a dialog: a small heading and its content, split from the group before by
   a line. Flat on purpose. */
.ui-section { display: flex; flex-direction: column; gap: 0.75rem; min-width: 0; }
.ui-section + .ui-section { margin-top: 1.25rem; padding-top: 1.25rem; border-top: 1px solid var(--ui-line); }

.ui-section-head { display: flex; align-items: center; gap: 0.5rem; min-height: 1.5rem; }
.ui-section-head > .bi { color: var(--ui-faint); font-size: 1rem; display: inline-flex; }
.ui-section-title { margin: 0; color: var(--ui-text); font-size: var(--ui-fs-md); font-weight: 600; line-height: 1.35; }
.ui-section-desc { margin: -0.4rem 0 0; color: var(--ui-muted); font-size: var(--ui-fs-sm); line-height: 1.45; }
.ui-section-aside { margin-left: auto; display: inline-flex; align-items: center; gap: 0.5rem; }

/* Rows split by lines: settings, people, files. */
.ui-list { display: flex; flex-direction: column; min-width: 0; }
.ui-list > * + * { border-top: 1px solid var(--ui-line); }

/* A list that stands on its own inside a card body: framed once, rows padded. */
.ui-list.is-framed { border: 1px solid var(--ui-line); border-radius: var(--ui-radius-sm); }
.ui-list.is-framed > * { padding-left: 0.9rem; padding-right: 0.9rem; }

.ui-divider { height: 1px; margin: 1rem 0; border: 0; background: var(--ui-line); opacity: 1; }

/* ---------- Badges ---------- */

.ui-badge {
    --badge-fg: var(--ui-muted);
    --badge-bg: var(--ui-chip-bg);
    display: inline-flex;
    align-items: center;
    gap: 0.35rem;
    flex-shrink: 0;
    height: 22px;
    padding: 0 0.55rem;
    border-radius: 999px;
    background: var(--badge-bg);
    color: var(--badge-fg);
    font-size: var(--ui-fs-xs);
    font-weight: 600;
    line-height: 1;
    white-space: nowrap;
    font-variant-numeric: tabular-nums;
}

.ui-badge > .bi { display: inline-flex; font-size: 0.95em; }
.ui-badge.has-dot::before { content: ""; width: 6px; height: 6px; border-radius: 50%; background: currentColor; }

.ui-badge-accent { --badge-fg: var(--ui-accent-strong); --badge-bg: var(--ui-accent-soft); }
.ui-badge-ok { --badge-fg: var(--ui-ok); --badge-bg: var(--ui-ok-soft); }
.ui-badge-warn { --badge-fg: var(--ui-warn); --badge-bg: var(--ui-warn-soft); }
.ui-badge-error { --badge-fg: var(--ui-error); --badge-bg: var(--ui-error-soft); }
.ui-badge-group { --badge-fg: var(--ui-group); --badge-bg: var(--ui-group-soft); }
.ui-badge-dirty { --badge-fg: var(--ui-dirty-text); --badge-bg: var(--ui-dirty-soft); }
.ui-badge-solid { --badge-fg: var(--ui-primary-fg); --badge-bg: var(--ui-primary-bg); }

.ui-badge-sm { height: 18px; padding: 0 0.4rem; }

/* ---------- Alerts ---------- */

.ui-alert {
    --alert-fg: var(--ui-muted);
    --alert-icon: var(--ui-faint);
    --alert-bg: var(--ui-card-bg);
    --alert-line: var(--ui-line);
    display: flex;
    align-items: flex-start;
    gap: 0.6rem;
    padding: 0.7rem 0.85rem;
    border: 1px solid var(--alert-line);
    border-radius: var(--ui-radius-sm);
    background: var(--alert-bg);
    color: var(--alert-fg);
    font-size: var(--ui-fs-sm);
    line-height: 1.45;
}

.ui-alert > .bi { display: inline-flex; flex-shrink: 0; margin-top: 0.1rem; color: var(--alert-icon); font-size: 1rem; }
.ui-alert-body { flex: 1; min-width: 0; }
.ui-alert-title { display: block; margin-bottom: 0.1rem; color: inherit; font-weight: 600; }
.ui-alert-title:last-child { margin-bottom: 0; }
.ui-alert-actions { display: flex; gap: 0.5rem; align-items: center; flex-shrink: 0; }
.ui-alert p { margin: 0; }
.ui-alert p + p { margin-top: 0.35rem; }

.ui-alert-info { --alert-fg: var(--ui-info-text); --alert-icon: var(--ui-accent-strong); --alert-bg: var(--ui-info-bg); --alert-line: var(--ui-info-line); }
.ui-alert-ok { --alert-fg: var(--ui-ok); --alert-icon: var(--ui-ok); --alert-bg: var(--ui-ok-soft); --alert-line: var(--ui-ok-line); }
.ui-alert-warn { --alert-fg: var(--ui-warn-text); --alert-icon: var(--ui-warn); --alert-bg: var(--ui-warn-soft); --alert-line: var(--ui-warn-line); }
.ui-alert-error { --alert-fg: var(--ui-error-text); --alert-icon: var(--ui-error); --alert-bg: var(--ui-error-soft); --alert-line: var(--ui-error-line); }

/* ---------- Dialogs ---------- */

.ui-dialog-backdrop {
    position: fixed;
    inset: 0;
    z-index: 1990;
    background: var(--ui-overlay);
    backdrop-filter: blur(3px);
    animation: ui-fade-in 0.18s ease;
}

.ui-dialog {
    position: fixed;
    top: 50%;
    left: 50%;
    z-index: 2000;
    display: flex;
    flex-direction: column;
    width: min(460px, calc(100vw - 2rem));
    max-height: calc(100dvh - 2rem);
    border: 1px solid var(--ui-line);
    border-radius: var(--ui-radius-dialog);
    background: var(--ui-surface-raised);
    box-shadow: var(--ui-shadow-dialog);
    color: var(--ui-text);
    transform: translate(-50%, -50%);
    animation: ui-dialog-in 0.24s var(--ui-ease);
    outline: none;
}

.ui-dialog.is-md { width: min(600px, calc(100vw - 2rem)); }
.ui-dialog.is-lg { width: min(820px, calc(100vw - 2rem)); }
.ui-dialog.is-xl { width: min(1100px, calc(100vw - 2rem)); }

.ui-dialog-head {
    display: flex;
    align-items: flex-start;
    gap: 0.75rem;
    padding: 1.3rem 1.4rem 0.55rem;
}

.ui-dialog-titles { flex: 1; min-width: 0; }
.ui-dialog-title { margin: 0; font-size: 17px; font-weight: 700; line-height: 1.35; letter-spacing: -0.01em; color: var(--ui-text); }
.ui-dialog-subtitle { margin: 0.2rem 0 0; color: var(--ui-muted); font-size: var(--ui-fs-sm); line-height: 1.45; }
.ui-dialog-head > .ui-btn { margin: -0.3rem -0.4rem 0 0; }

.ui-dialog-body {
    flex: 1;
    min-height: 0;
    overflow-y: auto;
    padding: 0.75rem 1.4rem 1.35rem;
    font-size: var(--ui-fs-md);
    line-height: 1.5;
}

.ui-dialog-body > p { margin: 0 0 0.75rem; }
.ui-dialog-body > p:last-child { margin-bottom: 0; }

.ui-dialog-foot {
    display: flex;
    align-items: center;
    justify-content: flex-end;
    flex-wrap: wrap;
    gap: 0.5rem;
    padding: 0.9rem 1.4rem;
    border-top: 1px solid var(--ui-line);
    border-radius: 0 0 var(--ui-radius-dialog) var(--ui-radius-dialog);
    background: var(--ui-card-bg);
}

.ui-dialog-foot > .ui-dialog-foot-start { margin-right: auto; display: flex; gap: 0.5rem; align-items: center; }

@media (max-width: 560px) {
    .ui-dialog {
        top: auto;
        bottom: 0;
        width: 100vw;
        max-height: 92dvh;
        border-radius: var(--ui-radius-dialog) var(--ui-radius-dialog) 0 0;
        transform: translate(-50%, 0);
        animation-name: ui-sheet-in;
    }

    .ui-dialog-foot > .ui-btn { flex: 1; }
}

/* Closing: the copy js/uiDialog.js leaves in the dialog's place for a moment (data-leave) fades and sinks away. */
.ui-dialog-backdrop.is-leaving { animation: ui-fade-out 0.16s ease forwards; pointer-events: none; }
.ui-dialog.is-leaving { animation: ui-dialog-out 0.16s ease-in forwards; pointer-events: none; }

@media (max-width: 560px) {
    .ui-dialog.is-leaving { animation-name: ui-sheet-out; animation-duration: 0.2s; }
}

@keyframes ui-fade-in { from { opacity: 0; } }
@keyframes ui-fade-out { to { opacity: 0; } }
@keyframes ui-dialog-in { from { opacity: 0; transform: translate(-50%, calc(-50% + 10px)) scale(0.98); } }
@keyframes ui-dialog-out { to { opacity: 0; transform: translate(-50%, calc(-50% + 8px)) scale(0.98); } }
@keyframes ui-sheet-in { from { transform: translate(-50%, 100%); } }
@keyframes ui-sheet-out { to { transform: translate(-50%, 100%); } }

@media (prefers-reduced-motion: reduce) {
    .ui-dialog, .ui-dialog-backdrop { animation: none; }
}

/* ---------- Feedback ---------- */

.ui-spinner {
    display: inline-block;
    flex-shrink: 0;
    width: 1.25rem;
    height: 1.25rem;
    border: 2px solid currentColor;
    border-right-color: transparent;
    border-radius: 50%;
    color: var(--ui-accent);
    vertical-align: middle;
    animation: ui-spin 0.7s linear infinite;
}

.ui-spinner-sm { width: 1rem; height: 1rem; }
.ui-spinner-lg { width: 1.75rem; height: 1.75rem; }

@keyframes ui-spin { to { transform: rotate(360deg); } }

@media (prefers-reduced-motion: reduce) {
    .ui-spinner { animation-duration: 1.6s; }
}

.ui-loading {
    display: flex;
    align-items: center;
    justify-content: center;
    gap: 0.6rem;
    padding: 2.5rem 1rem;
    color: var(--ui-muted);
    font-size: var(--ui-fs-sm);
}

.ui-empty {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 0.4rem;
    padding: 2.5rem 1rem;
    text-align: center;
}

.ui-empty.is-framed { border: 1.5px dashed var(--ui-line-strong); border-radius: var(--ui-radius); background: var(--ui-surface); }

/* The icon on a raised tile with a hairline, in a faint halo: an empty place, not an error. */
.ui-empty-icon {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 48px;
    height: 48px;
    margin-bottom: 0.6rem;
    border: 1px solid var(--ui-line);
    border-radius: 14px;
    background: linear-gradient(180deg, var(--ui-surface), var(--ui-card-bg));
    box-shadow: var(--ui-shadow-xs), 0 0 0 6px var(--ui-bg);
    color: var(--ui-faint);
    font-size: 1.3rem;
}

.ui-empty.is-ok .ui-empty-icon { border-color: var(--ui-ok-line); background: var(--ui-ok-soft); color: var(--ui-ok); }
.ui-empty.is-warn .ui-empty-icon { border-color: var(--ui-warn-line); background: var(--ui-warn-soft); color: var(--ui-warn); }
.ui-empty.is-error .ui-empty-icon { border-color: var(--ui-error-line); background: var(--ui-error-soft); color: var(--ui-error); }

.ui-empty-title { margin: 0; color: var(--ui-text); font-size: var(--ui-fs-md); font-weight: 600; }
.ui-empty-text { max-width: 46ch; margin: 0; color: var(--ui-muted); font-size: var(--ui-fs-sm); line-height: 1.45; }
.ui-empty > .ui-btn { margin-top: 0.6rem; }

/* ---------- Tabs ---------- */

/* A list's filters (the kiosks' «Активные / Тестовые / …», the users'): text tabs on a hairline, each with its count,
   and a blue bar under the chosen one that slides to the next (js/segmented.js places it through --seg-x and --seg-w;
   until then the chosen tab draws its own). A short switch of two or three options stays a .segmented control. Too
   many for the width, the row scrolls sideways under a hidden scroll bar. */
.ui-tabs {
    position: relative;
    display: flex;
    align-items: stretch;
    gap: 0.25rem;
    min-width: 0;
    overflow-x: auto;
    scrollbar-width: none;
    box-shadow: inset 0 -1px 0 var(--ui-line);
}

.ui-tabs::-webkit-scrollbar { display: none; }

.ui-tab {
    position: relative;
    display: inline-flex;
    flex-shrink: 0;
    align-items: center;
    gap: 0.5rem;
    height: 44px;
    padding: 0 0.7rem;
    border: 0;
    border-radius: var(--ui-radius-sm) var(--ui-radius-sm) 0 0;
    background: transparent;
    color: var(--ui-faint);
    font: inherit;
    font-size: var(--ui-fs-md);
    /* One weight for every state: a bolder chosen tab would change width and nudge the bar. */
    font-weight: 600;
    white-space: nowrap;
    cursor: pointer;
    transition: color var(--ui-duration) ease, background-color var(--ui-duration) ease;
}

.ui-tab:hover:not(.is-active):not(:disabled) {
    color: var(--ui-text);
    background: var(--ui-hover);
}

.ui-tab.is-active {
    color: var(--ui-text);
}

.ui-tab:focus-visible {
    outline: none;
    box-shadow: inset 0 0 0 2px var(--ui-accent);
}

.ui-tab:disabled {
    cursor: not-allowed;
    opacity: var(--ui-disabled-opacity);
}

/* The bar under the chosen tab: its own until the script places the sliding one. */
.ui-tab.is-active::after,
.ui-tabs[data-pill]::after {
    content: "";
    position: absolute;
    bottom: 0;
    height: 2px;
    border-radius: 2px 2px 0 0;
    background: var(--ui-accent);
    pointer-events: none;
}

.ui-tab.is-active::after {
    left: 0.7rem;
    right: 0.7rem;
}

.ui-tabs[data-pill] .ui-tab.is-active::after { display: none; }

/* The sliding bar, as wide as the chosen tab's text (its padding left out). */
.ui-tabs[data-pill]::after {
    left: 0;
    width: calc(var(--seg-w, 0px) - 1.4rem);
    opacity: var(--seg-opacity, 0);
    transform: translateX(calc(var(--seg-x, 0px) + 0.7rem));
}

.ui-tabs[data-pill="ready"]::after {
    transition:
        transform 0.3s cubic-bezier(0.25, 1, 0.4, 1),
        width 0.3s cubic-bezier(0.25, 1, 0.4, 1),
        opacity 0.15s ease;
}

@media (prefers-reduced-motion: reduce) {
    .ui-tabs[data-pill="ready"]::after { transition: none; }
}

/* ---------- Avatar ---------- */

/* A person (UiAvatar): the initial on a two-stop tint of the hue their address picks. */
.ui-avatar {
    --avatar-hue: 230;
    display: inline-flex;
    flex: none;
    align-items: center;
    justify-content: center;
    width: 36px;
    height: 36px;
    border-radius: 50%;
    background: linear-gradient(145deg, hsl(var(--avatar-hue) 90% 95%), hsl(var(--avatar-hue) 80% 88%));
    box-shadow: inset 0 0 0 1px hsl(var(--avatar-hue) 60% 50% / 0.18);
    color: hsl(var(--avatar-hue) 55% 36%);
    font-size: var(--ui-fs-md);
    font-weight: 700;
    line-height: 1;
    user-select: none;
}

.ui-avatar.is-sm { width: 28px; height: 28px; font-size: var(--ui-fs-xs); }
.ui-avatar.is-lg { width: 44px; height: 44px; font-size: var(--ui-fs-lg); }
.ui-avatar.is-invite { background: var(--ui-chip-bg); box-shadow: inset 0 0 0 1px var(--ui-line); color: var(--ui-faint); }
.ui-avatar > .bi { font-size: 0.95em; }

:root[data-theme="dark"] .ui-avatar:not(.is-invite) {
    background: linear-gradient(145deg, hsl(var(--avatar-hue) 45% 26%), hsl(var(--avatar-hue) 40% 20%));
    box-shadow: inset 0 0 0 1px hsl(var(--avatar-hue) 60% 70% / 0.2);
    color: hsl(var(--avatar-hue) 90% 84%);
}

/* An organisation: its first letter on a rounded square in the hue its name picks (UiAvatar.Hue), so an
   organisation has one colour in the kiosk list and the media library. */
.ui-org-mark {
    --avatar-hue: 230;
    display: inline-flex;
    flex: none;
    align-items: center;
    justify-content: center;
    width: 24px;
    height: 24px;
    border-radius: 7px;
    background: hsl(var(--avatar-hue) 85% 95%);
    box-shadow: inset 0 0 0 1px hsl(var(--avatar-hue) 60% 50% / 0.16);
    color: hsl(var(--avatar-hue) 55% 38%);
    font-size: var(--ui-fs-xs);
    font-weight: 700;
    line-height: 1;
}

:root[data-theme="dark"] .ui-org-mark {
    background: hsl(var(--avatar-hue) 40% 22%);
    box-shadow: inset 0 0 0 1px hsl(var(--avatar-hue) 60% 70% / 0.18);
    color: hsl(var(--avatar-hue) 90% 84%);
}

/* ---------- Text helpers ---------- */

.ui-muted { color: var(--ui-muted); }
.ui-faint { color: var(--ui-faint); }
.ui-meta { color: var(--ui-faint); font-size: var(--ui-fs-xs); }
/* Monospace keeps the size it is given: a field's 14px, or add .ui-meta for a 12px ID in text. */
.ui-mono { font-family: ui-monospace, SFMono-Regular, Consolas, monospace; }
.ui-num { font-variant-numeric: tabular-nums; }

/* ---------- Toasts ---------- */

/* Over the dialogs: a result can arrive while one is open. */
.ui-toasts {
    position: fixed;
    top: 1rem;
    right: 1rem;
    z-index: 2100;
    display: flex;
    flex-direction: column;
    gap: 0.5rem;
    width: min(400px, calc(100vw - 2rem));
    pointer-events: none;
}

/* The edge is a graphic, so it takes the bright status colour; the mark is text, so it takes the text-strength one. */
.ui-toast {
    --toast-edge: var(--ui-accent);
    --toast-mark-bg: var(--ui-accent-soft);
    --toast-mark-fg: var(--ui-accent-strong);
    display: flex;
    align-items: flex-start;
    gap: 0.6rem;
    padding: 0.75rem 0.6rem 0.75rem 0.85rem;
    border: 1px solid var(--ui-line);
    border-radius: var(--ui-radius);
    background:
        linear-gradient(90deg, color-mix(in srgb, var(--toast-edge) 9%, transparent), transparent 45%),
        var(--ui-surface-raised);
    box-shadow: var(--ui-shadow-lg);
    color: var(--ui-text);
    font-size: var(--ui-fs-sm);
    line-height: 1.45;
    pointer-events: auto;
    animation: ui-toast-in 0.2s ease-out;
}

.ui-toast.is-ok { --toast-edge: var(--ui-status-ok); --toast-mark-bg: var(--ui-ok-soft); --toast-mark-fg: var(--ui-ok); }
.ui-toast.is-warn { --toast-edge: var(--ui-status-warn); --toast-mark-bg: var(--ui-warn-soft); --toast-mark-fg: var(--ui-warn); }
.ui-toast.is-error { --toast-edge: var(--ui-status-error); --toast-mark-bg: var(--ui-error-soft); --toast-mark-fg: var(--ui-error); }

.ui-toast-mark {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    flex-shrink: 0;
    width: 1.5rem;
    height: 1.5rem;
    margin-top: -0.1rem;
    border-radius: 50%;
    background: var(--toast-mark-bg);
    box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--toast-edge) 35%, transparent);
    color: var(--toast-mark-fg);
    font-size: var(--ui-fs-xs);
    font-weight: 600;
    line-height: 1;
}

.ui-toast-body { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: 0.15rem; overflow-wrap: anywhere; }
.ui-toast-title { font-size: var(--ui-fs-md); font-weight: 600; }
.ui-toast-text { margin: 0; }
.ui-toast > .ui-btn { margin: -0.25rem 0 -0.25rem 0; }

@keyframes ui-toast-in { from { opacity: 0; transform: translateY(-8px) scale(0.98); } }

@media (prefers-reduced-motion: reduce) {
    .ui-toast { animation: none; }
}

/* For screen readers only. */
.visually-hidden {
    position: absolute !important;
    width: 1px !important;
    height: 1px !important;
    padding: 0 !important;
    margin: -1px !important;
    overflow: hidden !important;
    clip: rect(0, 0, 0, 0) !important;
    white-space: nowrap !important;
    border: 0 !important;
}
