/* * ui-lib.css * Stylesheet for the widgets added to ui-lib.pl: page header, grids, * section titles, cards, stat tiles, description lists, badges, action * buttons, chips, lists, activity feeds, empty states, progress bars, * toggles, segmented controls, search boxes, stacked fields and the * boxed choice, radio list and select switch widgets. * * Widgets use their own classes, with shared spacing and margin resets * inside .ui_page. Existing tabs, forms, tables and buttons retain their * theme styling. Widgets use the theme's --font-family and * --font-family-mono when defined, otherwise text inherits the page font * and code uses the monospace fallback below. * * .ui_page and standalone .ui_multi lists, .ui_toggle switches and * .ui_action controls supply the widget tokens. * The palette and shared spacing come from --ui-* custom properties, * which a theme may redefine on .ui_page to restyle every * widget at once. Setting data-ui-scheme="dark" (or "auto") on any * ancestor switches to the built-in dark palette. */ /* Nested widgets inherit their container's tokens. */ .ui_page, .ui_multi:not(:where(.ui_page *)), .ui_toggle:not(:where(.ui_page *, .ui_multi *)), .ui_action:not(:where(.ui_page *, .ui_multi *, .ui_toggle *)), .ui_dropdown:not(:where(.ui_page *)), .ui_dialog:not(:where(.ui_page *)), .ui_breadcrumb:not(:where(.ui_page *)), .ui_pager:not(:where(.ui_page *)) { /* Palette tokens read the theme's own custom properties first - the * names Authentic defines for its palettes and night mode - and * fall back to the colors of the gray theme, which defines only the * four --text-color-* state colors itself. */ /* Core palette */ --ui-canvas: var(--bg-color, #f2f3f5); --ui-surface: var(--main-content, #ffffff); --ui-surface-2: var(--bg-table-header, #f5f5f5); --ui-border: var(--border-color, #d7dbe0); --ui-border-soft: var(--border-color-light, #e8eaee); --ui-border-strong: var(--border-color-darker, #b3bac2); --ui-fg: var(--text-color, #212121); --ui-fg-muted: var(--text-color-muted, #4b5563); /* Accent */ --ui-accent: var(--link-color, #0b46ab); --ui-accent-soft: var(--element-highlight-bg-color, #e8f1fb); --ui-ring: var(--border-color-focused, rgba(11, 70, 171, 0.25)); /* Primary controls use the theme's quieter info palette when available, * with its accent fill as the fallback. */ --ui-primary: var(--bg-color-info, var(--accent-color, var(--toggle-bg-color-checked, var(--ui-accent)))); --ui-primary-text: var(--text-color-stressed-more, var(--text-color-info-stressed, var(--text-color-inverse, #ffffff))); --ui-primary-line: var(--border-color-info, var(--accent-color-hover, var(--ui-primary))); --ui-primary-hover: var(--bg-color-info, var(--ui-primary)); --ui-primary-hover-text: var(--text-color-stressed-more, var(--text-color-info-stressed, var(--ui-primary-text))); --ui-primary-hover-line: var(--border-color-focused, var(--text-color-info, var(--accent-color-hover, var(--ui-primary-line)))); --ui-primary-active: var(--ui-primary-hover); --ui-primary-active-text: var(--ui-primary-hover-text); --ui-primary-active-line: var(--ui-primary-hover-line); /* States. The base color is used for icons, dots and bars; the * -text variant is a darker shade for text sitting on the -soft * background, as in badges. */ --ui-success: var(--text-color-success, #3c763d); --ui-success-text: var(--text-color-success-stressed, #1e4f18); --ui-success-soft: var(--bg-color-success, #edf7e7); --ui-success-line: var(--border-color-success, #b9e0a4); --ui-warning: var(--text-color-warning, #b58900); --ui-warning-text: var(--text-color-warning-stressed, #795600); --ui-warning-soft: var(--bg-color-warning, #fdf4d8); --ui-warning-line: var(--border-color-warning, #eed483); --ui-danger: var(--text-color-danger, #bc0303); --ui-danger-text: var(--text-color-danger-stressed, #7d1007); --ui-danger-soft: var(--bg-color-danger, #fbeae8); --ui-danger-line: var(--border-color-danger, #f2bcb6); --ui-info: var(--text-color-info, #108eda); --ui-info-text: var(--text-color-info-stressed, #00477e); --ui-info-soft: var(--bg-color-info, #e8f1fb); --ui-info-line: var(--border-color-info, #b8d6f2); --ui-neutral: var(--text-color-muted, #4b5563); --ui-neutral-text: var(--text-color-neutral-stressed, #40474f); --ui-neutral-soft: var(--bg-color-neutral, var(--ui-surface-2)); --ui-neutral-line: var(--border-color-neutral, #d7dbe0); /* Controls */ --ui-input-bg: var(--bg-color-input, var(--ui-surface)); --ui-input-border: var(--border-color-input, var(--ui-border-strong)); --ui-toggle-bg: var(--toggle-bg-color, var(--ui-border-strong)); /* Keep the default close to the unchecked track. */ --ui-toggle-bg-checked: color-mix(in srgb, var(--ui-toggle-bg) 75%, var(--ui-neutral)); --ui-toggle-thumb: var(--toggle-thumb-color, #ffffff); --ui-disabled-bg: var(--bg-color-disabled, var(--ui-surface-2)); --ui-disabled-border: var(--border-color-disabled, var(--ui-border)); --ui-disabled-text: var(--text-color-disabled, var(--ui-fg-muted)); /* Shape and rhythm. Boxed widgets stay square; toggle corners are softened. */ --ui-gap: 7px; /* Shared inset for boxed choice widgets: padding, option spacing, * vertical field gaps and gaps between labels and inputs */ --ui-inset: 5px; /* Where the label text starts after a theme radio, so descriptions * and fields line up under it */ --ui-radio-indent: 1.6em; /* Fonts. A theme that defines --font-family and --font-family-mono * (Authentic does) sets the widgets' text and code fonts with them; * otherwise text inherits the page font and code uses the system * monospace stack. */ --ui-font: var(--font-family, inherit); --ui-font-mono: var(--font-family-mono, ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, "Liberation Mono", monospace); color-scheme: light; color: var(--ui-fg); font-family: var(--ui-font); text-align: left; } /* Shared CSS tooltips. Theme tokens keep the bubble and its arrow readable * in light and dark palettes. Ordinary help bubbles retain their theme style. */ .ui_tip[data-tooltip][aria-label] { position: relative; display: inline-block; vertical-align: baseline; --ui-tip-bg: var(--ui-fg, var(--text-color, #212121)); --ui-tip-fg: var(--ui-surface, var(--main-content, var(--bg-color, #fff))); } .ui_tip[data-tooltip][aria-label]::before, .ui_tip[data-tooltip][aria-label]::after { position: absolute; pointer-events: none; display: none; z-index: 1060; } .ui_tip[data-tooltip][aria-label]::before { content: attr(aria-label); bottom: calc(100% + 8px); left: 0; width: max-content; box-sizing: border-box; max-width: min(32rem, calc(100vw - 24px)); padding: 7px 10px; border-radius: 5px; /* Theme text colors may have alpha; paint over a solid surface. */ background: linear-gradient(var(--ui-tip-bg), var(--ui-tip-bg)), var(--ui-tip-fg); color: var(--ui-tip-fg); font-size: 13px; line-height: 1.45; font-weight: normal; font-style: normal; text-align: left; text-transform: none; text-shadow: none; white-space: normal; overflow-wrap: anywhere; } .ui_tip[data-tooltip][aria-label]::after { content: ''; bottom: calc(100% + 4px); left: 12px; width: 8px; height: 8px; background: linear-gradient(var(--ui-tip-bg), var(--ui-tip-bg)), var(--ui-tip-fg); transform: rotate(45deg); } .ui_tip[data-tooltip][aria-label]:is(:hover, :focus-within)::before, .ui_tip[data-tooltip][aria-label]:is(:hover, :focus-within)::after { display: block; } /* A hovered row takes priority over the checkbox that still has focus. */ .ui_multi:has(.ui_tip:hover) .ui_tip:not(:hover)::before, .ui_multi:has(.ui_tip:hover) .ui_tip:not(:hover)::after { display: none; } /* Fixed anchors escape scrolling lists. Scope each pair so repeated entries * cannot attach to another row's tooltip. Older browsers use the absolute * positioning above. */ @supports (anchor-scope: --ui-tip) { .ui_tip[data-tooltip][aria-label] { anchor-name: --ui-tip; anchor-scope: --ui-tip, --ui-tip-box, --ui-tip-input; } .ui_tip[data-tooltip][aria-label]::before { position: fixed; position-anchor: --ui-tip; anchor-name: --ui-tip-box; bottom: anchor(top); left: anchor(left); margin-bottom: 8px; position-try-fallbacks: --ui-tip-right, --ui-tip-fit, --ui-tip-below, --ui-tip-below-right, --ui-tip-below-fit; position-visibility: anchors-visible; } .ui_tip[data-tooltip][aria-label]::after { position: fixed; position-anchor: --ui-tip; bottom: auto; /* Attach the arrow to the nearest bubble edge after a position change. */ top: calc(clamp(anchor(--ui-tip-box top), anchor(bottom), anchor(--ui-tip-box bottom)) - 4px); left: clamp(calc(anchor(--ui-tip-box left) + 8px), calc(anchor(left) + 12px), calc(anchor(--ui-tip-box right) - 16px)); position-visibility: anchors-visible; } /* Point list descriptions at the checkbox, whose size varies by theme. * Leave room beside it for the bubble's rounded corner. */ .ui_multi .ui_tip input[type="checkbox"] { anchor-name: --ui-tip-input; } .ui_multi .ui_tip[data-tooltip][aria-label]::before { left: calc(anchor(--ui-tip-input center) - 12px); } .ui_multi .ui_tip[data-tooltip][aria-label]::after { left: clamp(calc(anchor(--ui-tip-box left) + 8px), calc(anchor(--ui-tip-input center) - 4px), calc(anchor(--ui-tip-box right) - 16px)); } @position-try --ui-tip-right { left: auto; right: anchor(right); } /* A long description can need more room than either side of its label. */ @position-try --ui-tip-fit { left: 12px; right: 12px; width: auto; } @position-try --ui-tip-below { bottom: auto; top: anchor(bottom); margin-top: 8px; margin-bottom: 0; } @position-try --ui-tip-below-right { bottom: auto; top: anchor(bottom); left: auto; right: anchor(right); margin-top: 8px; margin-bottom: 0; } @position-try --ui-tip-below-fit { bottom: auto; top: anchor(bottom); left: 12px; right: 12px; width: auto; margin-top: 8px; margin-bottom: 0; } } .ui_multi .ui_tip { display: inline-flex; align-items: center; min-width: 0; } .ui_multi .ui_tip > input[type="checkbox"] { margin-right: 6px; } /* Built-in dark palette, enabled with data-ui-scheme="dark" on any * ancestor element (or on the .ui_page wrapper itself, via the scheme * option of ui_page_start). With data-ui-scheme="auto" the dark * palette follows the browser's prefers-color-scheme setting instead. * The two blocks below must be kept identical. */ [data-ui-scheme="dark"] .ui_page, .ui_page[data-ui-scheme="dark"], [data-ui-scheme="dark"] .ui_multi:not(:where(.ui_page *)), .ui_multi[data-ui-scheme="dark"]:not(:where(.ui_page *)), [data-ui-scheme="dark"] .ui_toggle:not(:where(.ui_page *, .ui_multi *)), .ui_toggle[data-ui-scheme="dark"]:not(:where(.ui_page *, .ui_multi *)), [data-ui-scheme="dark"] .ui_action:not(:where(.ui_page *, .ui_multi *, .ui_toggle *)), .ui_action[data-ui-scheme="dark"]:not(:where(.ui_page *, .ui_multi *, .ui_toggle *)), [data-ui-scheme="dark"] :is(.ui_dropdown, .ui_dialog, .ui_breadcrumb, .ui_pager):not(:where(.ui_page *)), :is(.ui_dropdown, .ui_dialog, .ui_breadcrumb, .ui_pager)[data-ui-scheme="dark"]:not(:where(.ui_page *)) { --ui-canvas: #15171b; --ui-surface: #1e2126; --ui-surface-2: #23262c; --ui-border: #363b43; --ui-border-soft: #2a2f36; --ui-border-strong: #4d545e; --ui-fg: #e6e8ea; --ui-fg-muted: #99a3ae; --ui-accent: #5aa2ec; --ui-accent-soft: rgba(90, 162, 236, 0.14); --ui-ring: var(--border-color-focused, rgba(90, 162, 236, 0.35)); --ui-primary: var(--bg-color-info, var(--accent-color, var(--toggle-bg-color-checked, #2f6fdb))); --ui-success: var(--text-color-success, #7ec563); --ui-success-text: var(--text-color-success-stressed, #8fd376); --ui-success-soft: var(--bg-color-success, rgba(126, 197, 99, 0.13)); --ui-success-line: var(--border-color-success, rgba(126, 197, 99, 0.4)); --ui-warning: var(--text-color-warning, #e0a63c); --ui-warning-text: var(--text-color-warning-stressed, #eab54f); --ui-warning-soft: var(--bg-color-warning, rgba(224, 166, 60, 0.13)); --ui-warning-line: var(--border-color-warning, rgba(224, 166, 60, 0.4)); --ui-danger: var(--text-color-danger, #ef7b6f); --ui-danger-text: var(--text-color-danger-stressed, #f28c82); --ui-danger-soft: var(--bg-color-danger, rgba(239, 123, 111, 0.13)); --ui-danger-line: var(--border-color-danger, rgba(239, 123, 111, 0.4)); --ui-info: var(--text-color-info, #5aa2ec); --ui-info-text: var(--text-color-info-stressed, #78b3ef); --ui-info-soft: var(--bg-color-info, rgba(90, 162, 236, 0.13)); --ui-info-line: var(--border-color-info, rgba(90, 162, 236, 0.4)); --ui-neutral: var(--text-color-muted, #aeb6bf); --ui-neutral-text: var(--text-color-neutral-stressed, #b6bec7); --ui-neutral-soft: var(--bg-color-neutral, #272b31); --ui-neutral-line: var(--border-color-neutral, #3c424b); --ui-input-bg: var(--ui-surface); --ui-input-border: var(--ui-border-strong); color-scheme: dark; } @media (prefers-color-scheme: dark) { [data-ui-scheme="auto"] .ui_page, .ui_page[data-ui-scheme="auto"], [data-ui-scheme="auto"] .ui_multi:not(:where(.ui_page *)), .ui_multi[data-ui-scheme="auto"]:not(:where(.ui_page *)), [data-ui-scheme="auto"] .ui_toggle:not(:where(.ui_page *, .ui_multi *)), .ui_toggle[data-ui-scheme="auto"]:not(:where(.ui_page *, .ui_multi *)), [data-ui-scheme="auto"] .ui_action:not(:where(.ui_page *, .ui_multi *, .ui_toggle *)), .ui_action[data-ui-scheme="auto"]:not(:where(.ui_page *, .ui_multi *, .ui_toggle *)), [data-ui-scheme="auto"] :is(.ui_dropdown, .ui_dialog, .ui_breadcrumb, .ui_pager):not(:where(.ui_page *)), :is(.ui_dropdown, .ui_dialog, .ui_breadcrumb, .ui_pager)[data-ui-scheme="auto"]:not(:where(.ui_page *)) { --ui-canvas: #15171b; --ui-surface: #1e2126; --ui-surface-2: #23262c; --ui-border: #363b43; --ui-border-soft: #2a2f36; --ui-border-strong: #4d545e; --ui-fg: #e6e8ea; --ui-fg-muted: #99a3ae; --ui-accent: #5aa2ec; --ui-accent-soft: rgba(90, 162, 236, 0.14); --ui-ring: var(--border-color-focused, rgba(90, 162, 236, 0.35)); --ui-primary: var(--bg-color-info, var(--accent-color, var(--toggle-bg-color-checked, #2f6fdb))); --ui-success: var(--text-color-success, #7ec563); --ui-success-text: var(--text-color-success-stressed, #8fd376); --ui-success-soft: var(--bg-color-success, rgba(126, 197, 99, 0.13)); --ui-success-line: var(--border-color-success, rgba(126, 197, 99, 0.4)); --ui-warning: var(--text-color-warning, #e0a63c); --ui-warning-text: var(--text-color-warning-stressed, #eab54f); --ui-warning-soft: var(--bg-color-warning, rgba(224, 166, 60, 0.13)); --ui-warning-line: var(--border-color-warning, rgba(224, 166, 60, 0.4)); --ui-danger: var(--text-color-danger, #ef7b6f); --ui-danger-text: var(--text-color-danger-stressed, #f28c82); --ui-danger-soft: var(--bg-color-danger, rgba(239, 123, 111, 0.13)); --ui-danger-line: var(--border-color-danger, rgba(239, 123, 111, 0.4)); --ui-info: var(--text-color-info, #5aa2ec); --ui-info-text: var(--text-color-info-stressed, #78b3ef); --ui-info-soft: var(--bg-color-info, rgba(90, 162, 236, 0.13)); --ui-info-line: var(--border-color-info, rgba(90, 162, 236, 0.4)); --ui-neutral: var(--text-color-muted, #aeb6bf); --ui-neutral-text: var(--text-color-neutral-stressed, #b6bec7); --ui-neutral-soft: var(--bg-color-neutral, #272b31); --ui-neutral-line: var(--border-color-neutral, #3c424b); --ui-input-bg: var(--ui-surface); --ui-input-border: var(--ui-border-strong); color-scheme: dark; } body[data-ui-scheme="auto"]:has(.ui_page), [data-ui-scheme="auto"] body:has(.ui_page), body:has(.ui_page[data-ui-scheme="auto"]) { background: var(--ui-canvas, #15171b); } } /* Paint the page canvas behind widget pages. Themes can override the * color by defining --ui-canvas on :root or body. */ body:has(.ui_page) { background: var(--ui-canvas, var(--bg-color, #f2f3f5)); } body[data-ui-scheme="dark"]:has(.ui_page), [data-ui-scheme="dark"] body:has(.ui_page), body:has(.ui_page[data-ui-scheme="dark"]) { background: var(--ui-canvas, #15171b); } /* ---- Spacing between blocks ---- * Top-level blocks are spaced by the page area. Inside any other wrapper * (a tab panel, a table cell) a widget that follows a sibling, and * whatever follows a widget, get the same gap, so rows of cards and the * forms between them line up whatever the theme wraps them in. * Children of grids, stacks, clusters and stat rows use their own gaps. */ /* Space content blocks, ignoring assets, hidden fields and dialogs, * which are not in the flow. */ .ui_page > :where(:nth-child(n + 2 of :not( script, style, link, dialog, [hidden], .ui_hidden, input[type="hidden"] ))) { margin-top: var(--ui-gap); } .ui_page > :is(.ui_grid, .ui_stack, .ui_card, .ui_empty, .ui_stats) + *, .ui_page :not(.ui_grid, .ui_stack, .ui_cluster, .ui_stats) > * + :is(.ui_grid, .ui_stack, .ui_card, .ui_empty, .ui_stats), .ui_page :not(.ui_grid, .ui_stack, .ui_cluster, .ui_stats) > :is(.ui_grid, .ui_stack, .ui_card, .ui_empty, .ui_stats) + * { margin-top: var(--ui-gap); } .ui_page [hidden] { display: none !important; } /* Widgets size their boxes including padding and borders */ .ui_page_head, .ui_grid, .ui_stack, .ui_cluster, .ui_card, .ui_card *, .ui_stats, .ui_stat, .ui_stat *, .ui_dl, .ui_dl *, .ui_badge, .ui_chip, .ui_list, .ui_list *, .ui_feed, .ui_feed *, .ui_empty, .ui_empty *, .ui_progress, .ui_progress *, .ui_toggle, .ui_toggle *, .ui_search, .ui_search *, .ui_dropdown, .ui_dropdown *, .ui_dialog, .ui_dialog_head, .ui_dialog_body, .ui_dialog_foot, .ui_dialog_x, .ui_breadcrumb, .ui_breadcrumb *, .ui_pager, .ui_pager *, .ui_section_title, .ui_section_title *, .ui_segmented, .ui_segmented *, .ui_field, .ui_field * { box-sizing: border-box; } /* Reset widget paragraphs without removing spacing from help and other prose. */ .ui_page h1, .ui_page h2, .ui_page p.ui--p, .ui_page dl, .ui_page dd { margin: 0; } .ui_page svg.ui_svg_icon { flex-shrink: 0; vertical-align: -0.18em; } /* Underline plain text links, but keep links styled as buttons undecorated. */ .ui_dl_row dd a:not(.ui_link, .ui_action, .ui_dropdown_item), .ui_feed_text a:not(.ui_link, .ui_action, .ui_dropdown_item), .ui_list_desc a:not(.ui_link, .ui_action, .ui_dropdown_item), .ui_empty_desc a:not(.ui_link, .ui_action, .ui_dropdown_item), .ui_card_desc a:not(.ui_link, .ui_action, .ui_dropdown_item), .ui_field_help a:not(.ui_link, .ui_action, .ui_dropdown_item) { text-decoration: underline; text-underline-offset: 0.15em; } /* ---- Utilities ---- */ .ui_push { margin-left: auto; } .ui_code { font-family: var(--ui-font-mono); font-size: 0.92em; background: var(--ui-neutral-soft); border: 1px solid var(--ui-border-soft); padding: 1px 5px; } .ui_code_block { font-family: var(--ui-font-mono); font-size: 0.92em; line-height: 1.5; background: var(--ui-surface-2); border: 1px solid var(--ui-border-soft); padding: 10px 12px; margin: 0; overflow: auto; white-space: pre; color: var(--ui-fg); } .ui_fg_success { color: var(--ui-success); } .ui_fg_warning { color: var(--ui-warning); } .ui_fg_danger { color: var(--ui-danger); } .ui_fg_info { color: var(--ui-info); } .ui_fg_neutral { color: var(--ui-neutral); } .ui_bg_success { background: var(--ui-success); } .ui_bg_warning { background: var(--ui-warning); } .ui_bg_danger { background: var(--ui-danger); } .ui_bg_info { background: var(--ui-info); } .ui_bg_neutral { background: var(--ui-neutral); } /* ---- Page header ---- */ .ui_page_head { display: flex; justify-content: space-between; align-items: flex-start; gap: 10px 24px; flex-wrap: wrap; margin: 4px 0 2px; } .ui_page_title { font-size: 1.85em; font-weight: 650; letter-spacing: -0.015em; line-height: 1.2; } .ui_page_titles { flex: 1 1 auto; min-width: 0; } .ui_page_desc { color: var(--ui-fg-muted); margin-top: 4px; } .ui_page_actions { display: flex; align-items: center; gap: 12px; padding-top: 7px; } .ui_page_help { display: inline-flex; align-items: center; gap: 6px; font-weight: 500; } /* ---- Layout primitives ---- */ .ui_grid { display: grid; gap: var(--ui-gap); grid-template-columns: repeat(auto-fit, minmax(min(var(--ui-grid-min, 340px), 100%), 1fr)); align-items: stretch; } .ui_grid_fixed { grid-template-columns: var(--ui-grid-template, repeat(var(--ui-grid-cols, 2), minmax(0, 1fr))); } .ui_stack { display: flex; flex-direction: column; gap: var(--ui-gap); } .ui_cluster { display: flex; flex-wrap: wrap; align-items: center; gap: calc(var(--ui-gap) / 2) var(--ui-gap); } .ui_cluster { --ui-gap: 10px; } /* Separate action forms share the cluster's gap. Theme spacing between stacked * forms must not offset one action vertically inside a horizontal cluster. */ .ui_cluster > form.ui_form { margin: 0 !important; } /* Themes that regroup adjacent submit buttons into one box, as Authentic * does with its btn-group, wrap them inside that box with no vertical * space. Give those rows a gap while keeping the buttons joined. */ .ui_cluster > .btn-group { display: inline-flex; flex-wrap: wrap; row-gap: 7px; } .ui_cluster_center { justify-content: center; } .ui_cluster_end { justify-content: flex-end; } .ui_cluster_between { justify-content: space-between; } @media (max-width: 760px) { .ui_grid_fixed { grid-template-columns: 1fr; } } /* ---- Cards ---- */ .ui_card { background: var(--ui-surface); border: 1px solid var(--ui-border); display: flex; flex-direction: column; min-width: 0; } .ui_card_success { border-left: 3px solid var(--ui-success); } .ui_card_warning { border-left: 3px solid var(--ui-warning); } .ui_card_danger { border-left: 3px solid var(--ui-danger); } .ui_card_info { border-left: 3px solid var(--ui-info); } .ui_card_head { display: flex; justify-content: space-between; align-items: flex-start; gap: 4px 12px; padding: 5px 6px calc(var(--ui-gap) * 1.5) 7px; flex-wrap: wrap; } .ui_card_title { font-size: 1.07697em; font-weight: 650; line-height: 1.3; } .ui_card_desc { color: var(--ui-fg-muted); font-size: 0.93em; margin-top: 3px; } .ui_card_actions { display: flex; align-items: center; gap: 8px; flex-shrink: 0; /* Let taller controls use the space below the title. */ margin-bottom: calc(var(--ui-gap) * -1.5); } /* Keep superscript-based help controls aligned with other card actions. */ .ui_card_actions .ui_help { position: static; vertical-align: middle; } .ui_card_body { padding: 3px 7px 7px; flex: 1; min-width: 0; } .ui_card_body.ui_card_flush { padding: 1px 0 0; } .ui_card_head + .ui_card_body.ui_card_flush { padding-top: 3px; } /* Flush content follows the card's corners, which a theme may round, so * a highlighted first or last row does not stick out of the card. Each * level inherits the corners it touches; a square card passes on zero. */ .ui_card > .ui_card_body:first-child { border-top-left-radius: inherit; border-top-right-radius: inherit; } .ui_card > .ui_card_body:last-child { border-bottom-left-radius: inherit; border-bottom-right-radius: inherit; } .ui_card_flush > :is(.ui_list, .ui_feed) { border-radius: inherit; } .ui_list > .ui_list_item:first-child { border-top-left-radius: inherit; border-top-right-radius: inherit; } .ui_list > .ui_list_item:last-child { border-bottom-left-radius: inherit; border-bottom-right-radius: inherit; } .ui_card_foot { border-top: 1px solid var(--ui-border-soft); padding: 5px; color: var(--ui-fg-muted); font-size: 0.93em; } /* ---- Section titles ---- * A small uppercase heading over a group of widgets, with room for a * link or button at its right. It stands off from what precedes it and * sits close to what it names. */ .ui_section_title { display: flex; flex-wrap: wrap; align-items: center; justify-content: space-between; gap: 4px 16px; padding: 0 1px; /* The gap to what the title names is the title's own, so it does not * depend on the next block carrying a top margin; between blocks the * two margins collapse into one */ margin-bottom: calc(var(--ui-gap) / 2); } /* Stacks space their children with a flex gap, which does not collapse */ .ui_stack > .ui_section_title { margin-bottom: 0; } .ui_section_title_text { margin: 0; color: var(--ui-fg-muted); font-size: 0.75em; font-weight: 650; letter-spacing: 0.08em; line-height: 1.6; text-transform: uppercase; } .ui_section_title_actions { display: flex; align-items: center; gap: 10px; color: var(--ui-fg-muted); } /* A plain link among the actions is drawn small and muted; a small * ui_action_button keeps its own size */ .ui_section_title_actions a:not(.ui_link, .ui_action) { color: var(--ui-fg-muted); font-size: 0.86em; text-decoration: none; } .ui_section_title_actions a:not(.ui_link, .ui_action):hover { color: var(--ui-accent); text-decoration: underline; text-underline-offset: 0.15em; } .ui_page > * + .ui_section_title { margin-top: calc(var(--ui-gap) * 2); } /* ---- Stat tiles ---- */ .ui_stats { display: grid; gap: 12px 24px; grid-template-columns: repeat(auto-fit, minmax(min(var(--ui-grid-min, 150px), 100%), 1fr)); } .ui_stat { display: flex; flex-direction: column; gap: 1px; min-width: 0; color: inherit; text-decoration: none; } .ui_stat_square { border-radius: 0; } .ui_stat_rounded { border-radius: 0.5em; } .ui_stat_value { font-size: 1.85em; font-weight: 500; line-height: 1.2; letter-spacing: -0.01em; display: flex; align-items: center; gap: 8px; } .ui_stat_label { font-weight: 600; } .ui_stat_desc { color: var(--ui-fg-muted); font-size: 0.9em; } a.ui_stat, a.ui_stat:hover { color: inherit !important; text-decoration: none !important; } a.ui_stat:hover .ui_stat_value { color: var(--ui-accent); } /* ---- Description lists ---- */ /* Size rows to their content before deciding whether two can share a line. */ .ui_dl { display: flex; flex-wrap: wrap; align-items: baseline; gap: 10px 20px; } .ui_dl_row { display: flex; flex-wrap: wrap; flex: 1 1 100%; align-content: start; align-items: baseline; gap: 3px 8px; min-width: 0; max-width: 100%; } .ui_dl_cols > .ui_dl_row { flex-basis: auto; min-width: calc((100% - 20px) / 2); } .ui_dl_row dt { flex: 0 0 var(--ui-dl-label, auto); font-weight: 600; } /* A value moves below its label only after the row has used all available space. */ .ui_dl_row dd { flex: 1 1 auto; } .ui_dl_row dt, .ui_dl_row dd { min-width: 0; max-width: 100%; overflow-wrap: anywhere; } /* ---- Badges and chips ---- */ .ui_badge { display: inline-flex; align-items: center; gap: 5px; padding: 2px 10px; border: 1px solid transparent; font-size: 0.9em; font-weight: 600; line-height: 1.5; vertical-align: middle; white-space: nowrap; } .ui_badge_success { background: var(--ui-success-soft); border-color: var(--ui-success-line); color: var(--ui-success-text); } .ui_badge_warning { background: var(--ui-warning-soft); border-color: var(--ui-warning-line); color: var(--ui-warning-text); } .ui_badge_danger { background: var(--ui-danger-soft); border-color: var(--ui-danger-line); color: var(--ui-danger-text); } .ui_badge_info { background: var(--ui-info-soft); border-color: var(--ui-info-line); color: var(--ui-info-text); } .ui_badge_neutral { background: var(--ui-neutral-soft); border-color: var(--ui-neutral-line); color: var(--ui-neutral-text); } /* A smaller badge, so it does not outweigh the text next to it. The * icon scales with the text rather than staying at full size */ .ui_badge_small { padding: 0 8px; font-size: 0.8em; line-height: 1.25; } .ui_badge_small .ui_svg_icon { width: 1em; height: 1em; } /* Rounded ends, only when a caller asks for them. Widgets are drawn * square, apart from the toggle and the badge dot, which are round */ .ui_badge_rounded { border-radius: 999px; } /* ---- Action buttons and links ---- * Keep fallback styles at zero specificity so a theme's native button * classes control the complete surface and interaction palette. */ :where(:is(button, a, summary).ui_action) { --ui-action-text: var(--ui-neutral-text); --ui-action-bg: var(--ui-neutral-soft); --ui-action-border: var(--ui-neutral-line); --ui-action-hover-text: var(--ui-action-text); --ui-action-hover-bg: var(--element-highlight-bg-color-hovered, var(--bg-color-hover, var(--ui-action-bg))); --ui-action-hover-border: var(--ui-action-text); --ui-action-active-text: var(--ui-action-hover-text); --ui-action-active-bg: var(--ui-action-hover-bg); --ui-action-active-border: var(--ui-action-hover-border); --ui-action-focus-ring: var(--ui-ring); --ui-action-disabled-text: var(--ui-disabled-text); --ui-action-disabled-bg: var(--ui-disabled-bg); --ui-action-disabled-border: var(--ui-disabled-border); box-sizing: border-box; appearance: none; margin: 0; padding: 6px 12px; border: 1px solid var(--ui-action-border); border-radius: 0; background: var(--ui-action-bg); box-shadow: none; color: var(--ui-action-text); font: inherit; font-size: inherit; font-weight: 400; line-height: 1.42857143; text-decoration: none; vertical-align: middle; white-space: nowrap; cursor: pointer; } /* Keep icon and label alignment when themes supply native button styles. */ :is(button, a, summary).ui_action { display: inline-flex; align-items: center; justify-content: center; } .ui_action .ui_action_content { display: inline-flex; align-items: center; justify-content: center; box-sizing: border-box; gap: 5px; color: inherit; font: inherit; line-height: inherit; pointer-events: none; } /* Match the badge palette for each action state. */ :is(button, a, summary).ui_action_success { --ui-action-text: var(--ui-success-text); --ui-action-bg: var(--ui-success-soft); --ui-action-border: var(--ui-success-line); --ui-action-hover-bg: var(--ui-success-soft); --ui-action-hover-border: var(--ui-success); } :is(button, a, summary).ui_action_warning { --ui-action-text: var(--ui-warning-text); --ui-action-bg: var(--ui-warning-soft); --ui-action-border: var(--ui-warning-line); --ui-action-hover-bg: var(--ui-warning-soft); --ui-action-hover-border: var(--ui-warning); } :is(button, a, summary).ui_action_danger { --ui-action-text: var(--ui-danger-text); --ui-action-bg: var(--ui-danger-soft); --ui-action-border: var(--ui-danger-line); --ui-action-hover-bg: var(--ui-danger-soft); --ui-action-hover-border: var(--ui-danger); } :is(button, a, summary).ui_action_info { --ui-action-text: var(--ui-info-text); --ui-action-bg: var(--ui-info-soft); --ui-action-border: var(--ui-info-line); --ui-action-hover-bg: var(--ui-info-soft); --ui-action-hover-border: var(--ui-info); } /* The main form action follows the informational action palette through * every fallback interaction state. */ :is(button, a, summary).ui_action_primary { --ui-action-text: var(--ui-primary-text); --ui-action-bg: var(--ui-primary); --ui-action-border: var(--ui-primary-line); --ui-action-hover-text: var(--ui-primary-hover-text); --ui-action-hover-bg: var(--ui-primary-hover); --ui-action-hover-border: var(--ui-primary-hover-line); --ui-action-active-text: var(--ui-primary-active-text); --ui-action-active-bg: var(--ui-primary-active); --ui-action-active-border: var(--ui-primary-active-line); } /* Size and shape remain available to themes without matching classes. */ :is(button, a, summary).ui_action_normal { height: var(--btn-medium-height, 30px); } :where(.ui_action_small) { padding: 1px 5px; font-size: 0.8em; line-height: 1.5; } :where(.ui_action_large) { padding: 10px 16px; font-size: 1.15em; line-height: 1.3333333; } :is(button, a, summary).ui_action_rounded { border-radius: 4px; } :is(button, a, summary).ui_action_pill { border-radius: 999px; } .ui_action .ui_svg_icon { width: 1em; height: 1em; flex-shrink: 0; } .ui_action_small .ui_svg_icon_trash { transform: translateY(-0.25px); } /* Theme button selectors override these zero-specificity fallback states. */ :where(.ui_action:not(:disabled):not([aria-disabled="true"]):hover) { background: var(--ui-action-hover-bg); border-color: var(--ui-action-hover-border); color: var(--ui-action-hover-text); } :where(.ui_action:not(:disabled):not([aria-disabled="true"]):active) { background: var(--ui-action-active-bg); border-color: var(--ui-action-active-border); color: var(--ui-action-active-text); } :where(.ui_action:focus-visible) { outline: 0; box-shadow: 0 0 0 3px var(--ui-action-focus-ring); } :where(:is(button, a, summary).ui_action:is(:disabled, [aria-disabled="true"])) { background: var(--ui-action-disabled-bg); border-color: var(--ui-action-disabled-border); color: var(--ui-action-disabled-text); opacity: 0.55; cursor: not-allowed; } :is(button, a, summary).ui_action[hidden] { display: none; } .ui_chip { display: inline-flex; align-items: center; gap: 5px; padding: 1px 8px; background: var(--ui-neutral-soft); border: 1px solid var(--ui-border); color: var(--ui-neutral-text); font-size: 0.86em; line-height: 1.5; vertical-align: middle; white-space: nowrap; } .ui_chip_dot { display: inline-flex; flex: none; } .ui_chip .ui_svg_icon { flex: none; } /* ---- Lists ---- */ .ui_list { display: flex; flex-direction: column; } .ui_list_item { display: flex; align-items: flex-start; gap: 12px; padding: 5px 0; border-bottom: 1px solid var(--ui-border-soft); } .ui_list_item:last-child { border-bottom: 0; } .ui_list_flush .ui_list_item { padding: 5px 6px 5px 7px; } .ui_list_main { flex: 1; min-width: 0; } .ui_list_title { font-weight: 600; display: flex; align-items: center; gap: 8px; flex-wrap: wrap; overflow-wrap: anywhere; } .ui_list_desc { color: var(--ui-fg-muted); font-size: 0.93em; margin-top: 2px; display: flex; align-items: center; gap: 8px; flex-wrap: wrap; } .ui_list_side { display: flex; align-items: center; gap: 10px; flex-shrink: 0; padding-top: 1px; } .ui_list_meta { color: var(--ui-fg-muted); font-size: 0.9em; } /* Stacked rows put the actions under the meta text, both at the right */ .ui_list_stacked .ui_list_side { flex-direction: column; align-items: flex-end; gap: 4px; } /* Badges in list rows are small, so they do not outweigh the text */ .ui_list_item .ui_badge { padding: 0 8px; gap: 4px; font-size: 0.81em; } .ui_list_item .ui_badge .ui_svg_icon { width: 1em; height: 1em; } /* Description text is 0.93em, so badges and chips inside it are scaled * back by that much to match the ones next to the title */ .ui_list_desc .ui_badge { font-size: calc(0.81em / 0.93); } .ui_list_desc .ui_chip { font-size: calc(0.86em / 0.93); } .ui_list_icon { padding-top: 2px; color: var(--ui-fg-muted); } a.ui_list_link, a.ui_list_link:hover { color: inherit !important; text-decoration: none !important; } a.ui_list_link:hover { color: var(--ui-accent) !important; } /* Icons in square tiles, for rows of files or databases */ .ui_list_icon_box .ui_list_icon { display: inline-flex; align-items: center; justify-content: center; flex: none; align-self: center; width: 2.25em; height: 2.25em; padding: 0; border: 1px solid var(--ui-border-soft); background: var(--ui-surface-2); } .ui_list_icon_box .ui_list_icon .ui_svg_icon { width: 18px; height: 18px; } /* The chevron of a navigation row is stretched over the row, so the * whole entry opens its link; other links and controls stay on top */ .ui_list_chevron { display: inline-flex; align-self: center; color: var(--ui-fg-muted); text-decoration: none !important; } .ui_list_item:has(.ui_list_chevron) { position: relative; } .ui_list_chevron::after { content: ""; position: absolute; inset: 0; } .ui_list_item:has(.ui_list_chevron) :is(a, button, input, select, textarea, label, summary):not(.ui_list_chevron) { position: relative; z-index: 1; } .ui_list_item:has(.ui_list_chevron):hover { background: var(--ui-surface-2); } .ui_list_item:has(.ui_list_chevron):hover .ui_list_chevron { color: var(--ui-accent); } /* Icon tiles and inline code share the highlight shade, so on a * highlighted row they step one shade further to stay visible */ .ui_list_icon_box .ui_list_item:has(.ui_list_chevron):hover .ui_list_icon, .ui_list_item:has(.ui_list_chevron):hover .ui_code { background: var(--ui-border-soft); border-color: var(--ui-border); } /* ---- Activity feed ---- */ .ui_feed { display: flex; flex-direction: column; } .ui_feed_item { position: relative; padding: 0 0 16px 20px; border-left: 2px solid var(--ui-border-soft); margin-left: 5px; } .ui_feed_item:last-child { padding-bottom: 2px; } .ui_feed_item::before { content: ""; position: absolute; left: -6px; top: 4px; width: 10px; height: 10px; border-radius: 999px; background: var(--ui-border-strong); border: 2px solid var(--ui-surface); } .ui_feed_success::before { background: var(--ui-success); } .ui_feed_warning::before { background: var(--ui-warning); } .ui_feed_danger::before { background: var(--ui-danger); } .ui_feed_info::before { background: var(--ui-info); } .ui_feed_when { font-weight: 600; font-size: 1em; } .ui_feed_text { color: var(--ui-fg-muted); font-size: 0.93em; margin-top: 1px; } .ui_feed_flush { padding: 3px 19px 7px; } /* ---- Empty states ---- */ .ui_empty { display: flex; flex-direction: column; align-items: center; gap: 2px; padding: 1px 7px 7px; text-align: center; } .ui_empty_icon { color: var(--ui-border-strong); margin-bottom: 2px; } .ui_empty_title { font-weight: 600; font-size: 1.04em; } .ui_empty_desc { color: var(--ui-fg-muted); font-size: 0.93em; max-width: 46ch; } .ui_empty_actions { margin-top: 6px; } /* ---- Progress ---- */ .ui_progress_head { display: flex; justify-content: space-between; gap: 12px; font-size: 0.93em; font-weight: 600; margin-bottom: 6px; } .ui_progress_value { color: var(--ui-fg-muted); font-weight: 400; } .ui_progress_track { display: flex; height: 4px; background: var(--ui-neutral-soft); overflow: hidden; } .ui_progress_bar { height: 100%; flex-shrink: 0; transition: width 0.4s ease; } /* Inline layout : label, bar and value on one line */ .ui_progress_inline { display: flex; align-items: center; gap: 10px; font-size: 0.93em; } .ui_progress_inline .ui_progress_label { font-weight: 600; white-space: nowrap; } .ui_progress_inline .ui_progress_track { flex: 1 1 auto; } .ui_progress_inline .ui_progress_value { min-width: 3em; text-align: right; white-space: nowrap; } /* Value shown as a small marker riding on the end of the bar, which * keeps the same height as every other bar */ .ui_progress_inside .ui_progress_track { overflow: visible; } .ui_progress_inside .ui_progress_bar { position: relative; } .ui_progress_inside_value { position: absolute; top: 50%; left: 100%; transform: translate(-50%, -50%); padding: 1px 4px; /* The bar's own color, lightened by a white veil, with dark text */ background: inherit; background-image: linear-gradient(rgba(255, 255, 255, 0.6), rgba(255, 255, 255, 0.6)); color: #1c1c1c; font-size: 0.75em; font-weight: 600; line-height: 1.2; white-space: nowrap; } .ui_progress_inside_start .ui_progress_inside_value { transform: translate(0, -50%); } .ui_progress_inside_end .ui_progress_inside_value { transform: translate(-100%, -50%); } /* Legend under a segmented bar */ .ui_progress_legend { display: flex; flex-wrap: wrap; gap: 4px 16px; margin-top: 6px; font-size: 0.9em; } .ui_progress_key { display: inline-flex; align-items: center; gap: 6px; } .ui_progress_dot { display: inline-block; width: 8px; height: 8px; border-radius: 50%; } /* Unknown progress : a bar sliding along the track */ .ui_progress_indeterminate .ui_progress_bar { width: 35%; animation: ui_progress_slide 1.4s ease-in-out infinite; } @keyframes ui_progress_slide { from { transform: translateX(-100%); } to { transform: translateX(300%); } } @media (prefers-reduced-motion: reduce) { .ui_progress_indeterminate .ui_progress_bar { width: 100%; animation: none; opacity: 0.6; background-image: repeating-linear-gradient(45deg, transparent 0 6px, rgba(255, 255, 255, 0.45) 6px 12px); } } /* Ring gauge : the bar is a circle of circumference 100 whose dash * length is the percentage */ .ui_progress_ring { display: inline-flex; flex-direction: column; align-items: center; gap: 4px; } .ui_progress_ring_box { position: relative; display: inline-block; line-height: 0; } .ui_progress_ring_svg { transform: rotate(-90deg); } /* The stroke width is set on the circles by ui_progress, scaled to the * ring size so it is always 3 pixels */ .ui_progress_ring_track { fill: none; stroke: var(--ui-neutral-soft); } .ui_progress_ring_bar { fill: none; stroke: currentColor; stroke-linecap: round; transition: stroke-dasharray 0.4s ease; } .ui_progress_ring.ui_progress_indeterminate .ui_progress_ring_svg { animation: ui_progress_spin 1s linear infinite; } @keyframes ui_progress_spin { from { transform: rotate(-90deg); } to { transform: rotate(270deg); } } .ui_progress_ring_box .ui_progress_value { position: absolute; inset: 0; display: flex; align-items: center; justify-content: center; font-size: 0.8em; font-weight: 600; line-height: 1; color: var(--ui-fg); } .ui_progress_ring .ui_progress_label { font-size: 0.85em; color: var(--ui-fg-muted); } /* ---- Choice lists ---- * One solid box holding a row per option : the theme's own radio button * with its label, the inline inputs beside it, and further fields under * them in as many columns as fit, indented under the label. Everything * stays visible. */ .ui_choice { display: grid; gap: var(--ui-inset); min-width: 0; /* Half the inset above and below, the full inset at the sides */ padding: calc(var(--ui-inset) / 2) var(--ui-inset); border: 1px solid var(--ui-border); background: var(--ui-surface); } .ui_choice_item { min-width: 0; } .ui_choice_head { display: flex; flex-wrap: wrap; align-items: baseline; gap: 4px var(--ui-inset); min-width: 0; } /* Only the option's own label is bold, not labels of inputs in its content */ .ui_choice_head label:not(.ui_choice_content label) { font-weight: 600; } .ui_choice_content { display: inline-flex; flex-wrap: wrap; align-items: center; gap: 4px var(--ui-inset); min-width: 0; } .ui_choice_desc { margin-left: var(--ui-radio-indent); color: var(--ui-fg-muted); font-size: 0.93em; } .ui_choice_fields { display: grid; grid-template-columns: repeat(auto-fit, minmax(min(300px, 100%), 1fr)); gap: var(--ui-inset) 20px; margin: var(--ui-inset) 0 0 var(--ui-radio-indent); } .ui_choice_field { display: flex; flex-wrap: wrap; align-items: center; gap: 4px var(--ui-inset); min-width: 0; } .ui_choice_field_label { color: var(--ui-fg-muted); } .ui_choice_field_input { min-width: 0; } .ui_choice_disabled { opacity: 0.6; } /* ---- Radio lists ---- * The compact form : the theme's radio buttons at the left edge of a * box, one option per line, an optional input after the label */ .ui_radio_list { display: grid; gap: var(--ui-inset); min-width: 0; padding: var(--ui-inset); border: 1px solid var(--ui-border); background: var(--ui-surface); } .ui_radio_list_item { display: flex; flex-wrap: wrap; align-items: baseline; gap: 4px var(--ui-inset); min-width: 0; } .ui_radio_list_item label:not(.ui_radio_list_content label) { font-weight: 600; } .ui_radio_list_content { display: inline-flex; flex-wrap: wrap; align-items: center; gap: 4px var(--ui-inset); min-width: 0; } .ui_radio_list_disabled { opacity: 0.6; } /* ---- Select switches ---- * A select, and under it the block of the chosen option only */ .ui_select_switch { display: grid; justify-items: start; gap: var(--ui-inset); min-width: 0; } .ui_select_switch_panel { display: grid; gap: var(--ui-inset); width: 100%; min-width: 0; padding: var(--ui-inset); border: 1px solid var(--ui-border); background: var(--ui-surface); } .ui_select_switch_desc { color: var(--ui-fg-muted); font-size: 0.93em; } .ui_select_switch_content { display: flex; flex-wrap: wrap; align-items: center; gap: 4px var(--ui-inset); min-width: 0; } .ui_select_switch_fields { display: grid; grid-template-columns: repeat(auto-fit, minmax(min(300px, 100%), 1fr)); gap: var(--ui-inset) 20px; } .ui_select_switch_field { display: flex; flex-wrap: wrap; align-items: center; gap: 4px var(--ui-inset); min-width: 0; } .ui_select_switch_field_label { color: var(--ui-fg-muted); } .ui_select_switch_field_input { min-width: 0; } /* ---- Toggle switch ---- * Some themes wrap checkbox inputs in their own markup with JavaScript, * so the real input - or whatever wrapper it ends up inside - is parked * invisibly over the track, where clicks and keyboard focus still reach * it, and the checked state is read with :has() which does not depend on * the input being the track's sibling. */ .ui_toggle { display: inline-flex; align-items: center; gap: 10px; cursor: pointer; position: relative; font-weight: normal; } /* State colors share the theme-backed palette used by other widgets. */ .ui_toggle_primary { --ui-toggle-bg-checked: var(--toggle-bg-color-checked, var(--ui-accent)); } .ui_toggle_success { --ui-toggle-bg-checked: var(--ui-success); } .ui_toggle_warning { --ui-toggle-bg-checked: var(--ui-warning); } .ui_toggle_danger { --ui-toggle-bg-checked: var(--ui-danger); } .ui_toggle_info { --ui-toggle-bg-checked: var(--ui-info); } .ui_toggle > :not(.ui_toggle_track):not(.ui_toggle_label), .ui_toggle input { position: absolute !important; left: 0 !important; top: 0 !important; width: 28px !important; height: 14px !important; margin: 0 !important; padding: 0 !important; opacity: 0 !important; overflow: hidden; z-index: 1; cursor: pointer; } .ui_toggle_track { display: inline-flex; align-items: center; width: 26px; height: 12px; padding: 2px; border-radius: 3px; background: var(--ui-toggle-bg); transition: background 0.15s ease; flex-shrink: 0; } .ui_toggle_thumb { width: 8px; height: 8px; border-radius: 2px; background: var(--ui-toggle-thumb); box-shadow: 0 1px 1px rgba(16, 24, 40, 0.18); transition: transform 0.15s ease; } .ui_toggle_round .ui_toggle_track { width: 28px; height: 14px; border-radius: 999px; } .ui_toggle_round .ui_toggle_thumb { width: 10px; height: 10px; border-radius: 999px; box-shadow: 0 1px 2px rgba(16, 24, 40, 0.25); } .ui_toggle input:checked ~ .ui_toggle_track, .ui_toggle:has(input:checked) .ui_toggle_track { background: var(--ui-toggle-bg-checked); } .ui_toggle input:checked ~ .ui_toggle_track .ui_toggle_thumb, .ui_toggle:has(input:checked) .ui_toggle_thumb { transform: translateX(14px); } /* Trade one pixel of padding for the border to keep the thumb in place. */ .ui_toggle_outline .ui_toggle_track { padding: 1px; border: 1px solid currentColor; color: var(--ui-toggle-bg); background: transparent; } .ui_toggle_outline .ui_toggle_thumb { background: currentColor; box-shadow: none; } .ui_toggle_outline input:checked ~ .ui_toggle_track, .ui_toggle_outline:has(input:checked) .ui_toggle_track { color: var(--ui-toggle-bg-checked); background: transparent; } .ui_toggle input:focus-visible ~ .ui_toggle_track, .ui_toggle:has(input:focus-visible) .ui_toggle_track { box-shadow: 0 0 0 3px var(--ui-ring); } .ui_toggle input:disabled ~ .ui_toggle_track, .ui_toggle:has(input:disabled) .ui_toggle_track { opacity: 0.5; } /* ---- Segmented control ---- * Radio buttons drawn as one row of joined buttons. As for the toggle, * the real input - or a theme's wrapper around it - is parked invisibly * over its label, and the selected state is read with :has(). */ .ui_segmented { display: inline-flex; align-items: stretch; max-width: 100%; padding: 2px; gap: 2px; border: 1px solid var(--ui-border); background: var(--ui-surface-2); vertical-align: middle; } .ui_segment { position: relative; display: inline-flex; margin: 0; cursor: pointer; font-weight: normal; } .ui_segment > :not(.ui_segment_label), .ui_segment input { position: absolute !important; inset: 0 !important; width: 100% !important; height: 100% !important; margin: 0 !important; padding: 0 !important; opacity: 0 !important; overflow: hidden; z-index: 1; cursor: pointer; } .ui_segment_label { display: inline-flex; align-items: center; gap: 5px; padding: 2px 10px; border: 1px solid transparent; color: var(--ui-fg-muted); font-size: 0.9em; font-weight: 600; line-height: 1.5; white-space: nowrap; transition: background 0.15s ease, color 0.15s ease; } .ui_segment_label .ui_svg_icon { width: 1em; height: 1em; } .ui_segmented_small .ui_segment_label { padding: 0 8px; font-size: 0.8em; line-height: 1.25; } .ui_segment:hover .ui_segment_label { color: var(--ui-fg); } .ui_segment input:checked ~ .ui_segment_label, .ui_segment:has(input:checked) .ui_segment_label { background: var(--ui-surface); border-color: var(--ui-border); color: var(--ui-fg); } .ui_segment input:focus-visible ~ .ui_segment_label, .ui_segment:has(input:focus-visible) .ui_segment_label { box-shadow: 0 0 0 3px var(--ui-ring); } .ui_segment_disabled { opacity: 0.5; } .ui_segment_disabled, .ui_segment_disabled input { cursor: not-allowed; } /* ---- Search box ---- */ .ui_search { position: relative; display: inline-flex; max-width: 100%; } .ui_search .ui_search_icon { position: absolute; left: 10px; top: 50%; transform: translateY(-50%); color: var(--ui-fg-muted); pointer-events: none; } .ui_search .ui_search_input { padding-left: 30px; width: 100%; min-width: 0; font: inherit; color: inherit; background: var(--ui-surface); border: 1px solid var(--ui-border-strong); padding-top: 5px; padding-bottom: 5px; padding-right: 10px; } .ui_search .ui_search_input:focus { outline: none; border-color: var(--ui-accent); box-shadow: 0 0 0 3px var(--ui-ring); } /* ---- Stacked fields ---- * A label above its control and a help line under it. Text inputs in * the control row take the width left by the buttons after them. */ .ui_field { display: grid; gap: 4px; min-width: 0; } .ui_field_label { margin: 0; font-size: 0.93em; font-weight: 600; } .ui_field_control { display: flex; flex-wrap: wrap; align-items: center; gap: 6px; min-width: 0; } .ui_field_control > :is(input:not([type="button"], [type="submit"], [type="reset"], [type="checkbox"], [type="radio"], [type="image"]), select, textarea, .ui_search) { flex: 1 1 12em; min-width: 0; width: auto; } .ui_field_help { color: var(--ui-fg-muted); font-size: 0.93em; } .ui_page .ui_field + .ui_field { margin-top: calc(var(--ui-gap) * 1.5); } /* ---- Multiple selection ---- * Scrollable checkbox lists, also usable outside .ui_page. */ .ui_multi, .ui_multi_body { display: grid; gap: var(--ui-inset); min-width: 0; } .ui_multi [hidden] { display: none !important; } .ui_multi_modes { display: flex; flex-wrap: wrap; align-items: center; gap: 4px 10px; } .ui_multi_count { color: var(--ui-fg-muted); font-size: 0.93em; white-space: nowrap; } .ui_multi_tools { display: flex; flex-wrap: wrap; align-items: center; gap: 4px 0; } /* A hidden count alone does not need a toolbar row. */ .ui_multi_tools:not(:has(> :not([hidden]))) { display: none; } /* ui_links_row handles link spacing; add room for the count. */ .ui_multi_links { min-width: 0; } .ui_multi_tools .ui_multi_count:not(:first-child) { margin-left: 2px; margin-right: 10px; } /* Reserve filter space to prevent toolbar shifts on opening. */ .ui_multi_tools .ui_search { margin-left: auto; flex: 1 1 9em; min-width: 9em; max-width: 14em; } /* Match the height of compact link buttons. */ .ui_multi_tools .ui_search_input { height: 22px; padding: 0 26px; font-size: 0.93em; line-height: 20px; background: var(--ui-input-bg); border-color: var(--ui-input-border); } /* Keep dimensions while hiding closed controls from keyboard navigation. */ .ui_multi_filter:not(.ui_multi_filter_open) .ui_search_input, .ui_multi_filter:not(.ui_multi_filter_open) .ui_multi_filter_clear { visibility: hidden; } .ui_multi_filter .ui_search_input::-webkit-search-cancel-button { -webkit-appearance: none; } .ui_multi_tools .ui_multi_filter_button { appearance: none; position: absolute; top: 0; bottom: 0; display: flex; align-items: center; justify-content: center; width: 26px; margin: 0; padding: 0; border: 1px solid transparent; border-radius: 0; background: transparent; color: var(--ui-fg-muted); font: inherit; cursor: pointer; } .ui_multi_tools .ui_multi_filter_toggle { right: 0; /* Share the input's theme colors. */ border-color: var(--ui-input-border); background: var(--ui-input-bg); } .ui_multi_filter_toggle .ui_svg_icon { fill: currentColor; opacity: 0.75; } .ui_multi_tools .ui_multi_filter_clear { left: 0; } .ui_multi_filter_open .ui_multi_filter_toggle { border-color: transparent; background: transparent; color: var(--ui-accent); } .ui_multi_filter_active .ui_multi_filter_toggle { color: var(--ui-danger); } .ui_multi_filter_button:focus-visible { outline: 2px solid var(--ui-accent); outline-offset: -2px; } .ui_multi_filter_button:disabled { cursor: default; } .ui_multi_list { border: 1px solid var(--ui-border); background: var(--ui-surface); min-width: 0; } .ui_multi_items { max-height: var(--ui-multi-height, 170px); overflow-y: scroll; } .ui_multi_item { display: flex; align-items: center; min-width: 0; padding: calc(var(--ui-inset) / 2) var(--ui-inset) calc(var(--ui-inset) / 2) var(--ui-inset); } /* Space unwrapped checkboxes; theme wrappers handle their own spacing. */ .ui_multi_item > input[type="checkbox"] { margin-right: 6px; } /* Keep themed checkbox glyphs upright when the row text is italic. */ .ui_multi_item input[type="checkbox"] + label::before, .ui_multi_item input[type="checkbox"] + label::after { font-style: normal; } .ui_multi_item:hover { background: var(--ui-surface-2); } .ui_multi_suffix { color: var(--ui-fg-muted); } .ui_multi_note { margin-left: 6px; color: var(--ui-fg-muted); font-size: 0.93em; white-space: nowrap; } .ui_multi_side { margin-left: auto; padding-left: 8px; flex-shrink: 0; } /* Badges share the chip's size and the row's normal padding, so rows with * badges line up with rows without them. */ .ui_multi_badges { display: flex; flex-wrap: wrap; align-items: center; justify-content: flex-end; gap: 6px; flex-shrink: 1; } .ui_multi_badges .ui_badge { padding: 1px 8px; font-size: 0.86em; font-weight: 400; line-height: 1.5; border-color: color-mix(in srgb, currentColor 16%, transparent); } .ui_multi_badges .ui_badge .ui_svg_icon { width: 1em; height: 1em; } .ui_multi_badges .ui_badge_neutral { background: color-mix(in srgb, var(--ui-neutral-soft) 35%, transparent); color: var(--ui-fg-muted); } .ui_multi_badges .ui_badge_success { background: color-mix(in srgb, var(--ui-success-soft) 45%, transparent); } .ui_multi_badges .ui_badge_warning { background: color-mix(in srgb, var(--ui-warning-soft) 45%, transparent); } .ui_multi_badges .ui_badge_info { background: color-mix(in srgb, var(--ui-info-soft) 45%, transparent); } /* Grouping and combined metadata are opt-in; ordinary selectors keep their layout. */ .ui_multi_summary { display: inline-flex; align-items: center; gap: 6px; flex: 1 1 auto; padding-right: 12px; color: var(--ui-fg-muted); } .ui_multi_group { display: flex; align-items: center; gap: 6px; padding: 6px; border-top: 1px solid var(--ui-border); color: var(--ui-fg-muted); font-size: 0.93em; font-weight: 600; } .ui_multi_group:first-child { border-top: 0; } .ui_multi_group_success > .ui_svg_icon { color: var(--ui-success); } .ui_multi_group_warning > .ui_svg_icon { color: var(--ui-warning); } .ui_multi_group_danger > .ui_svg_icon { color: var(--ui-danger); } .ui_multi_group_info > .ui_svg_icon { color: var(--ui-info); } .ui_multi_metadata.ui_badge { gap: 8px; padding: 1px 6px; font-weight: 400; line-height: 1.35; color: var(--ui-fg-muted); background: color-mix(in srgb, var(--ui-neutral-soft) 25%, transparent); border-color: color-mix(in srgb, currentColor 15%, transparent); } .ui_multi_meta { display: inline-flex; align-items: center; gap: 4px; --ui-meta-color: var(--ui-fg-muted); color: var(--ui-meta-color); } /* Theme table rules may override SVG color, so paint icons explicitly. */ .ui_multi_meta > .ui_svg_icon { stroke: var(--ui-meta-color); } .ui_multi_meta > .ui_svg_icon[fill="currentColor"] { fill: var(--ui-meta-color); } /* Mixed metadata keeps each fact's color; the strongest state colors its border. */ .ui_multi_meta_info, .ui_multi_metadata:has(> .ui_multi_meta_info) { --ui-meta-color: var(--ui-info-text); color: var(--ui-meta-color); } .ui_multi_meta_success, .ui_multi_metadata:has(> .ui_multi_meta_success) { --ui-meta-color: var(--ui-success-text); color: var(--ui-meta-color); } .ui_multi_meta_warning, .ui_multi_metadata:has(> .ui_multi_meta_warning) { --ui-meta-color: var(--ui-warning-text); color: var(--ui-meta-color); } .ui_multi_meta_danger, .ui_multi_metadata:has(> .ui_multi_meta_danger) { --ui-meta-color: var(--ui-danger-text); color: var(--ui-meta-color); } .ui_multi_metadata:has(> .ui_multi_meta:not(.ui_multi_meta_neutral)) { border-color: color-mix(in srgb, currentColor 40%, transparent); } .ui_multi_compact .ui_multi_item { padding: 2px 6px; min-height: 24px; } .ui_multi_compact .ui_multi_item:has(input:checked) { background: color-mix(in srgb, var(--ui-accent) 10%, var(--ui-surface)); } @media (max-width: 600px) { .ui_multi_compact .ui_multi_item { flex-wrap: wrap; } .ui_multi_compact .ui_multi_side { max-width: 100%; } .ui_multi_compact .ui_multi_metadata { flex-wrap: wrap; justify-content: flex-end; } } /* Preserve child indentation when compact or badge styles set row padding. */ .ui_multi_item.ui_multi_level1 { padding-left: 27px; } .ui_multi_item.ui_multi_level2 { padding-left: 47px; } .ui_multi_disabled { opacity: 0.6; } .ui_multi_empty { padding: 1px 2px 2px 2px; color: var(--ui-fg-muted); text-align: center; } .ui_multi_empty .ui_svg_icon { vertical-align: -0.18em; transform: scale(0.87); margin-right: 0.025em; } /* ---- Drop-down menus ---- * A details element : the summary is the button, drawn as an action * button, and the menu panel floats under it. */ /* Match an action button's height without a text line around the summary. */ .ui_dropdown { position: relative; display: inline-flex; vertical-align: middle; } .ui_dropdown > summary.ui_dropdown_toggle { list-style: none; cursor: pointer; user-select: none; } .ui_dropdown > summary::-webkit-details-marker, .ui_dropdown > summary::marker { display: none; content: ""; } .ui_dropdown .ui_dropdown_caret { margin-left: -2px; transition: transform 0.12s; } .ui_dropdown[open] .ui_dropdown_caret { transform: rotate(180deg); } .ui_dropdown_menu { position: absolute; top: calc(100% + 4px); left: 0; z-index: 30; display: flex; flex-direction: column; gap: 1px; min-width: 180px; max-width: min(340px, calc(100vw - 16px)); padding: 4px; border: 1px solid var(--ui-border); background: var(--ui-surface); box-shadow: 0 6px 20px rgba(16, 24, 40, 0.14); color: var(--ui-fg); font-weight: 400; text-align: left; white-space: nowrap; } /* A menu at the right end of a toolbar opens leftwards, and a menu * that would leave the window is flipped by ui-lib.js */ .ui_dropdown_end:not(.ui_dropdown_flip) > .ui_dropdown_menu, .ui_dropdown_flip:not(.ui_dropdown_end) > .ui_dropdown_menu { left: auto; right: 0; } :is(a, button).ui_dropdown_item { display: flex; align-items: center; gap: 8px; width: 100%; min-height: 0; margin: 0; padding: 5px 10px; border: 0; border-radius: 0; background: none; box-shadow: none; color: inherit; font: inherit; font-weight: 400; line-height: 1.4; text-align: left; text-decoration: none; cursor: pointer; appearance: none; } :is(a, button).ui_dropdown_item:hover, :is(a, button).ui_dropdown_item:focus-visible { background: var(--ui-accent-soft); color: var(--ui-fg); text-decoration: none; outline: none; } :is(a, button).ui_dropdown_item:focus-visible { box-shadow: inset 0 0 0 2px var(--ui-ring); } .ui_dropdown_item .ui_dropdown_icon { flex-shrink: 0; color: var(--ui-fg-muted); } .ui_dropdown_text { display: flex; flex-direction: column; min-width: 0; } .ui_dropdown_desc { color: var(--ui-fg-muted); font-size: 0.9em; white-space: normal; } .ui_dropdown_danger, .ui_dropdown_danger .ui_dropdown_icon { color: var(--ui-danger-text); } .ui_dropdown_warning, .ui_dropdown_warning .ui_dropdown_icon { color: var(--ui-warning-text); } .ui_dropdown_success, .ui_dropdown_success .ui_dropdown_icon { color: var(--ui-success-text); } .ui_dropdown_info, .ui_dropdown_info .ui_dropdown_icon { color: var(--ui-info-text); } :is(a, button).ui_dropdown_item:is(:disabled, [aria-disabled="true"]) { opacity: 0.5; cursor: not-allowed; background: none; } .ui_dropdown_sep { margin: 4px 0; border: 0; border-top: 1px solid var(--ui-border-soft); } .ui_dropdown_heading { padding: 6px 10px 2px; color: var(--ui-fg-muted); font-size: 0.8em; font-weight: 600; letter-spacing: 0.04em; text-transform: uppercase; } /* ---- Dialogs ---- * A native dialog element, shown modal by ui-lib.js. The width comes * from the size class and never exceeds the window. */ dialog.ui_dialog { --ui-dialog-width: 480px; position: fixed; inset: 0; display: none; flex-direction: column; width: min(var(--ui-dialog-width), calc(100vw - 32px)); max-width: none; max-height: calc(100vh - 32px); margin: auto; padding: 0; border: 1px solid var(--ui-border); border-radius: 0; background: var(--ui-surface); box-shadow: 0 18px 50px rgba(16, 24, 40, 0.28); color: var(--ui-fg); font-family: var(--ui-font); text-align: left; } dialog.ui_dialog[open] { display: flex; } dialog.ui_dialog::backdrop { background: rgba(15, 18, 22, 0.45); } .ui_dialog_small { --ui-dialog-width: 380px; } .ui_dialog_large { --ui-dialog-width: 760px; } .ui_dialog > .ui_dialog_form { display: flex; flex-direction: column; min-height: 0; margin: 0; } .ui_dialog_head { display: flex; align-items: flex-start; gap: 12px; padding: 12px 16px; border-bottom: 1px solid var(--ui-border-soft); } .ui_dialog_titles { flex: 1; min-width: 0; } .ui_dialog_title { display: flex; align-items: center; gap: 8px; margin: 0; font-size: 1.05em; font-weight: 600; line-height: 1.4; } .ui_dialog_desc { margin: 3px 0 0; color: var(--ui-fg-muted); font-size: 0.93em; } /* The state colors the heading icon and marks the top edge */ .ui_dialog_danger { border-top: 3px solid var(--ui-danger); } .ui_dialog_warning { border-top: 3px solid var(--ui-warning); } .ui_dialog_success { border-top: 3px solid var(--ui-success); } .ui_dialog_info { border-top: 3px solid var(--ui-info); } .ui_dialog_danger .ui_dialog_title > .ui_svg_icon { color: var(--ui-danger); } .ui_dialog_warning .ui_dialog_title > .ui_svg_icon { color: var(--ui-warning); } .ui_dialog_success .ui_dialog_title > .ui_svg_icon { color: var(--ui-success); } .ui_dialog_info .ui_dialog_title > .ui_svg_icon { color: var(--ui-info); } button.ui_dialog_x { display: inline-flex; align-items: center; justify-content: center; flex-shrink: 0; width: 28px; height: 28px; min-height: 0; margin: -4px -6px 0 0; padding: 0; border: 0; border-radius: 0; background: none; box-shadow: none; color: var(--ui-fg-muted); font: inherit; line-height: 1; cursor: pointer; appearance: none; } button.ui_dialog_x:hover, button.ui_dialog_x:focus-visible { background: var(--ui-surface-2); color: var(--ui-fg); outline: none; } button.ui_dialog_x:focus-visible { box-shadow: inset 0 0 0 2px var(--ui-ring); } .ui_dialog_body { flex: 1 1 auto; min-height: 0; padding: 14px 16px; overflow: auto; } /* The plain text paragraph, keeping its line breaks, and a gap before * the HTML that follows it */ .ui_dialog_body > .ui_dialog_text { margin: 0; white-space: pre-line; overflow-wrap: anywhere; } .ui_dialog_body > .ui_dialog_text + * { margin-top: 10px; } .ui_dialog_foot { display: flex; flex-wrap: wrap; align-items: center; justify-content: flex-end; gap: 8px; padding: 10px 16px 12px; border-top: 1px solid var(--ui-border-soft); background: var(--ui-surface-2); } /* ---- Breadcrumbs ---- */ .ui_breadcrumb { display: inline-flex; min-width: 0; max-width: 100%; } .ui_breadcrumb_list { display: flex; flex-wrap: wrap; align-items: center; gap: 2px 0; margin: 0; padding: 0; list-style: none; } .ui_breadcrumb_item { display: inline-flex; align-items: center; min-width: 0; overflow-wrap: anywhere; } .ui_breadcrumb_item + .ui_breadcrumb_item::before { content: "/"; margin: 0 7px; color: var(--ui-fg-muted); } .ui_breadcrumb_item > :is(a, span) { display: inline-flex; align-items: center; gap: 5px; padding: 1px 2px; color: var(--ui-accent); text-decoration: none; } .ui_breadcrumb_item > a:hover { color: var(--ui-accent); text-decoration: underline; } .ui_breadcrumb_item > span { color: var(--ui-fg); } .ui_breadcrumb_current > :is(a, span) { font-weight: 600; } /* ---- Pager ---- */ .ui_pager { display: inline-flex; flex-wrap: wrap; align-items: center; gap: 3px; } .ui_pager_link { display: inline-flex; align-items: center; justify-content: center; min-width: 26px; height: 26px; padding: 0 7px; border: 1px solid var(--ui-border); background: var(--ui-surface); color: var(--ui-fg); font-size: 0.93em; line-height: 1; text-decoration: none; } a.ui_pager_link:hover, a.ui_pager_link:focus-visible { border-color: var(--ui-accent); color: var(--ui-accent); text-decoration: none; outline: none; } a.ui_pager_link:focus-visible { box-shadow: 0 0 0 3px var(--ui-ring); } .ui_pager_current { border-color: var(--ui-accent); background: var(--ui-accent); color: #fff; font-weight: 600; } .ui_pager_disabled { opacity: 0.5; } .ui_pager_gap { padding: 0 4px; color: var(--ui-fg-muted); } /* ---- Reduced motion ---- */ @media (prefers-reduced-motion: reduce) { .ui_progress_ring.ui_progress_indeterminate .ui_progress_ring_svg { animation: none; } .ui_page *, .ui_page *::before, .ui_page *::after { transition: none !important; } }