mirror of
https://github.com/lrnselfreliance/wrolpi.git
synced 2026-10-26 19:01:39 -04:00
Greptile's three findings on #590, all confirmed real before fixing, and each one verified red against the original code afterwards. P1, the directory fields had no accessible names. All five rendered their visible label as a bare <label> with no htmlFor while passing no label prop, so their inputs had a description ("/media/wrolpi/") and no name at all -- five fields in one grid, holding similar relative paths, indistinguishable to a screen reader. The labels now belong to the inputs, InfoPopup content and all. This predates PathInput; the old TextInput call sites had the same gap, and moving to a component that supports labels is what made it fixable. P1, description and error were rendered but never referenced. aria-describedby pointed only at the prefix, so the help text listing the variables a path accepts, and the reason a path was rejected, were both invisible to assistive technology. Both are now in the description, and an errored field reports aria-invalid. P2 was worse than reported: at `flex: none` a long prefix took the entire row and left the input eighteen pixels wide -- not "consumes the editable area" but a field nobody can type in. The media directory is whatever the API reports, so its length was never ours to assume. The prefix now shrinks and ellipsises at half the control, with the full path on `title` and in the accessible description. The naming fault could not have been caught where the others were. jsdom cannot see layout, and the Settings page needs enough mocking that it had no test at all -- which is why these five fields were uncovered when they broke. The new e2e spec intercepts the API and lets the real page render, so it measures the names and the geometry a user actually gets. Reverting one field to a bare label fails it; a long prefix fails the browser spec at 18px. 962 jest, 15 browser component, 46 e2e, clean cache-busted build, tsc clean. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
985 lines
24 KiB
CSS
985 lines
24 KiB
CSS
/*
|
|
* Styles for the token-driven component library.
|
|
*
|
|
* Rules here encode design decisions Mantine's variables cannot express on their
|
|
* own — chiefly the two that change *form* rather than color between themes:
|
|
* the dashed danger treatment and status-by-brightness. See ui-design.md.
|
|
*
|
|
* Nothing in this file hardcodes a color; every value comes from a token.
|
|
*/
|
|
|
|
/* ------------------------------------------------------------------ Icons */
|
|
|
|
.wrolpi-icon-spin {
|
|
animation: wrolpi-icon-spin 0.9s linear infinite;
|
|
}
|
|
|
|
@keyframes wrolpi-icon-spin {
|
|
to {
|
|
transform: rotate(360deg);
|
|
}
|
|
}
|
|
|
|
@media (prefers-reduced-motion: reduce) {
|
|
.wrolpi-icon-spin {
|
|
animation: none;
|
|
}
|
|
}
|
|
|
|
.wrolpi-icon-stack {
|
|
position: relative;
|
|
display: inline-flex;
|
|
line-height: 0;
|
|
}
|
|
|
|
/*
|
|
* The corner glyph sits on the main one, so it carries the surface color behind
|
|
* it — without that the two sets of strokes cross and neither reads.
|
|
*/
|
|
.wrolpi-icon-stack-corner {
|
|
position: absolute;
|
|
right: -3px;
|
|
bottom: -3px;
|
|
display: flex;
|
|
background: var(--bg);
|
|
border-radius: 50%;
|
|
padding: 1px;
|
|
}
|
|
|
|
/* ---------------------------------------------------------------- Buttons */
|
|
/*
|
|
* Dashed borders mean "destructive or failed", and nothing else may use them.
|
|
* In night mode a destructive button drops its fill entirely: 1.5px keeps the
|
|
* dashes legible at low brightness, where a 1px hairline dash disappears.
|
|
*/
|
|
|
|
/*
|
|
* In light, dark, and amber the button stays filled, which `color="red"` already
|
|
* does. Night is the exception, and it cannot be expressed by overriding
|
|
* Mantine's `--button-*` variables: Mantine writes those as inline styles on the
|
|
* element, which no stylesheet rule can outrank. Setting the real properties
|
|
* works instead — this selector (0,2,0 plus an element) outranks the Mantine
|
|
* class rule that reads the variables.
|
|
*/
|
|
html[data-theme="night"] .wrolpi-button-danger {
|
|
background: transparent;
|
|
color: var(--red);
|
|
border: 1.5px dashed var(--red);
|
|
}
|
|
|
|
html[data-theme="night"] .wrolpi-button-danger:hover:not(:disabled) {
|
|
background: var(--panel);
|
|
}
|
|
|
|
/* The icon inside inherits currentColor, so it follows without another rule. */
|
|
|
|
/* ----------------------------------------------------------- Theme picker */
|
|
|
|
.wrolpi-theme-picker {
|
|
display: grid;
|
|
grid-template-columns: repeat(auto-fit, minmax(230px, 1fr));
|
|
gap: 10px;
|
|
}
|
|
|
|
.wrolpi-theme-option {
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: 4px;
|
|
align-items: flex-start;
|
|
text-align: left;
|
|
padding: 10px 12px;
|
|
background: var(--panel);
|
|
color: var(--text);
|
|
border: 1px solid var(--border);
|
|
font: inherit;
|
|
cursor: pointer;
|
|
}
|
|
|
|
.wrolpi-theme-option:hover {
|
|
background: var(--head);
|
|
}
|
|
|
|
/* The selected theme is marked by a heavier border as well as color, so the
|
|
selection survives in monochrome themes. */
|
|
.wrolpi-theme-option-active {
|
|
border: 2px solid var(--blue);
|
|
padding: 9px 11px;
|
|
}
|
|
|
|
html[data-theme="night"] .wrolpi-theme-option-active,
|
|
html[data-theme="amber"] .wrolpi-theme-option-active {
|
|
border-color: var(--text);
|
|
}
|
|
|
|
.wrolpi-theme-option:focus-visible {
|
|
outline: 2px solid var(--blue);
|
|
outline-offset: 1px;
|
|
}
|
|
|
|
.wrolpi-theme-option-name {
|
|
display: inline-flex;
|
|
align-items: center;
|
|
gap: 7px;
|
|
font-weight: 600;
|
|
font-size: 13px;
|
|
}
|
|
|
|
.wrolpi-theme-option-description {
|
|
font-size: 12px;
|
|
color: var(--muted);
|
|
line-height: 1.4;
|
|
}
|
|
|
|
/* Separated from the theme grid: it configures the chosen theme, it is not a choice. */
|
|
.wrolpi-media-filter-setting {
|
|
margin-top: 14px;
|
|
padding-top: 12px;
|
|
border-top: 1px solid var(--border);
|
|
}
|
|
|
|
/* ------------------------------------------------------------ Pagination */
|
|
|
|
.wrolpi-pagination button {
|
|
background: var(--panel);
|
|
color: var(--text);
|
|
border: 1px solid var(--border);
|
|
}
|
|
|
|
.wrolpi-pagination button[data-active] {
|
|
background: var(--blue);
|
|
color: var(--btn-text);
|
|
border-color: var(--blue);
|
|
}
|
|
|
|
/* Night and amber have no second hue to spend, so the current page is marked by a
|
|
heavier border and the panel surface instead of a filled block of color. */
|
|
html[data-theme="night"] .wrolpi-pagination button[data-active],
|
|
html[data-theme="amber"] .wrolpi-pagination button[data-active] {
|
|
background: var(--panel);
|
|
color: var(--text);
|
|
border: 2px solid var(--text);
|
|
}
|
|
|
|
.wrolpi-pagination button:disabled {
|
|
color: var(--muted);
|
|
background: var(--bg);
|
|
}
|
|
|
|
/* ---------------------------------------------------------------- Tab bar */
|
|
|
|
.wrolpi-tabs {
|
|
display: flex;
|
|
align-items: flex-end;
|
|
justify-content: space-between;
|
|
gap: 12px;
|
|
border-bottom: 1px solid var(--border);
|
|
margin-bottom: 14px;
|
|
}
|
|
|
|
.wrolpi-tabs-links {
|
|
display: flex;
|
|
flex-wrap: wrap;
|
|
}
|
|
|
|
.wrolpi-tabs-right {
|
|
padding-bottom: 6px;
|
|
}
|
|
|
|
.wrolpi-tab {
|
|
display: inline-block;
|
|
padding: 8px 14px;
|
|
color: var(--muted);
|
|
text-decoration: none;
|
|
font-size: 13px;
|
|
border: 1px solid transparent;
|
|
/* Sits on the bar's own rule, so the active tab can erase it and read as
|
|
connected to the panel below. */
|
|
margin-bottom: -1px;
|
|
}
|
|
|
|
.wrolpi-tab:hover {
|
|
color: var(--text);
|
|
}
|
|
|
|
.wrolpi-tab-active {
|
|
color: var(--text);
|
|
background: var(--panel);
|
|
border-color: var(--border);
|
|
border-bottom-color: var(--panel);
|
|
font-weight: 600;
|
|
}
|
|
|
|
.wrolpi-tab:focus-visible {
|
|
outline: 2px solid var(--blue);
|
|
outline-offset: -2px;
|
|
}
|
|
|
|
/* ------------------------------------------------------------- Search box */
|
|
|
|
.wrolpi-searchbox {
|
|
position: relative;
|
|
width: 100%;
|
|
}
|
|
|
|
.wrolpi-searchbox-control {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 8px;
|
|
background: var(--panel);
|
|
border: 1px solid var(--border);
|
|
padding: 0 8px;
|
|
}
|
|
|
|
.wrolpi-searchbox-control:focus-within {
|
|
border-color: var(--blue);
|
|
}
|
|
|
|
html[data-theme="night"] .wrolpi-searchbox-control:focus-within,
|
|
html[data-theme="amber"] .wrolpi-searchbox-control:focus-within {
|
|
border-color: var(--text);
|
|
}
|
|
|
|
.wrolpi-searchbox-icon {
|
|
display: flex;
|
|
color: var(--muted);
|
|
}
|
|
|
|
.wrolpi-searchbox-input {
|
|
flex: 1;
|
|
min-width: 0;
|
|
padding: 8px 0;
|
|
background: none;
|
|
border: none;
|
|
outline: none;
|
|
color: var(--text);
|
|
font: inherit;
|
|
font-size: 13px;
|
|
}
|
|
|
|
.wrolpi-searchbox-input::placeholder {
|
|
color: var(--muted);
|
|
}
|
|
|
|
.wrolpi-searchbox-loading,
|
|
.wrolpi-searchbox-clear {
|
|
flex: none;
|
|
}
|
|
|
|
/*
|
|
* The suggestion list overlays the page rather than pushing it down — a search
|
|
* that reflows the results underneath it is unusable while typing.
|
|
*/
|
|
.wrolpi-searchbox-results {
|
|
position: absolute;
|
|
z-index: 20;
|
|
top: 100%;
|
|
left: 0;
|
|
right: 0;
|
|
/*
|
|
* Tall enough for a full set of grouped suggestions -- directories, channels and
|
|
* domains together run past 320px, and the last group was being clipped -- but
|
|
* still capped so a long list cannot cover the page.
|
|
*/
|
|
max-height: min(60vh, 480px);
|
|
overflow-y: auto;
|
|
background: var(--panel);
|
|
border: 1px solid var(--border);
|
|
border-top: none;
|
|
}
|
|
|
|
.wrolpi-searchbox-group-name {
|
|
padding: 6px 10px;
|
|
font-size: 11px;
|
|
font-weight: 600;
|
|
letter-spacing: 0.08em;
|
|
text-transform: uppercase;
|
|
color: var(--muted);
|
|
background: var(--head);
|
|
}
|
|
|
|
.wrolpi-searchbox-result {
|
|
padding: 7px 10px;
|
|
cursor: pointer;
|
|
border-top: 1px solid var(--border);
|
|
}
|
|
|
|
.wrolpi-searchbox-result-active {
|
|
background: var(--head);
|
|
}
|
|
|
|
.wrolpi-searchbox-result-title {
|
|
font-size: 13px;
|
|
color: var(--text);
|
|
overflow-wrap: anywhere;
|
|
}
|
|
|
|
.wrolpi-searchbox-result-description {
|
|
font-size: 11px;
|
|
color: var(--muted);
|
|
}
|
|
|
|
.wrolpi-searchbox-empty {
|
|
padding: 10px;
|
|
font-size: 12px;
|
|
color: var(--muted);
|
|
}
|
|
|
|
/* ---------------------------------------------------------------- Modals */
|
|
/*
|
|
* Actions sit below the body, separated by a hairline rather than a raised bar.
|
|
* Right-aligned, so the confirming action lands where the eye finishes reading.
|
|
*/
|
|
|
|
.wrolpi-modal-actions {
|
|
display: flex;
|
|
justify-content: flex-end;
|
|
flex-wrap: wrap;
|
|
gap: 10px;
|
|
/* Bleed back out through the modal body's own padding so the rule spans the
|
|
full width; -1 * whatever Mantine padded the body with. */
|
|
margin: 18px calc(var(--mantine-spacing-md) * -1) calc(var(--mantine-spacing-md) * -1);
|
|
padding: 12px var(--mantine-spacing-md);
|
|
border-top: 1px solid var(--border);
|
|
}
|
|
|
|
/* A stack of form rows, replacing Semantic's `ui stackable grid` in edit forms. */
|
|
.wrolpi-form-rows {
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: 14px;
|
|
}
|
|
|
|
/* ------------------------------------------------------------- Form fields */
|
|
/*
|
|
* `hooks/useForm.js` renders its own <label> and native <input> — it always did, with
|
|
* Semantic's Form.Input only wrapping them for field styling. These rules supply that
|
|
* styling from tokens instead, so a native input matches the library's own controls.
|
|
*/
|
|
|
|
.wrolpi-form-field input {
|
|
width: 100%;
|
|
padding: 7px 9px;
|
|
background: var(--panel);
|
|
color: var(--text);
|
|
border: 1px solid var(--border);
|
|
font: inherit;
|
|
font-size: 13px;
|
|
}
|
|
|
|
.wrolpi-form-field input::placeholder {
|
|
color: var(--muted);
|
|
}
|
|
|
|
.wrolpi-form-field input:focus {
|
|
outline: none;
|
|
border-color: var(--blue);
|
|
}
|
|
|
|
html[data-theme="night"] .wrolpi-form-field input:focus,
|
|
html[data-theme="amber"] .wrolpi-form-field input:focus {
|
|
border-color: var(--text);
|
|
}
|
|
|
|
/* An invalid field is dashed, matching every other failure state in the interface. */
|
|
.wrolpi-form-field[data-error] input {
|
|
border: 1.5px dashed var(--red);
|
|
}
|
|
|
|
.wrolpi-form-field input:disabled {
|
|
color: var(--muted);
|
|
background: var(--bg);
|
|
}
|
|
|
|
/* --------------------------------------------------------------- Path input */
|
|
/*
|
|
* Prefix and input are siblings in a flex row sharing one outline, so the prefix cannot
|
|
* print over the value -- the failure that Mantine's absolutely-positioned `leftSection`
|
|
* allowed when its reserved width was wrong. The prefix takes its natural width, so any
|
|
* media directory fits without a number being maintained anywhere.
|
|
*/
|
|
|
|
.wrolpi-path-input-label {
|
|
display: block;
|
|
font-weight: 600;
|
|
font-size: 13px;
|
|
margin-bottom: 4px;
|
|
}
|
|
|
|
.wrolpi-path-input-control {
|
|
display: flex;
|
|
align-items: stretch;
|
|
background: var(--panel);
|
|
border: 1px solid var(--border);
|
|
}
|
|
|
|
.wrolpi-path-input-control:focus-within {
|
|
border-color: var(--blue);
|
|
}
|
|
|
|
html[data-theme="night"] .wrolpi-path-input-control:focus-within,
|
|
html[data-theme="amber"] .wrolpi-path-input-control:focus-within {
|
|
border-color: var(--text);
|
|
}
|
|
|
|
.wrolpi-path-input-control[data-error] {
|
|
border: 1.5px dashed var(--red);
|
|
}
|
|
|
|
.wrolpi-path-input-control[data-disabled] {
|
|
background: var(--bg);
|
|
}
|
|
|
|
/*
|
|
* Muted and on the header surface: it reads as context, not as something to edit.
|
|
*
|
|
* It shrinks and ellipsises rather than holding its full width. The prefix is whatever the
|
|
* API reports as the media directory -- `/media/wrolpi` on a Pi, but a development box or an
|
|
* unusual Docker mount can report a much longer path -- and at `flex: none` a long one took
|
|
* the whole row and left the input 18px wide, a field nobody could type into. Capping it at
|
|
* half the control keeps the editable part editable; the full path stays available through
|
|
* `title` and is read out in full by the accessible description either way.
|
|
*/
|
|
.wrolpi-path-input-prefix {
|
|
display: flex;
|
|
align-items: center;
|
|
flex: 0 1 auto;
|
|
min-width: 3ch;
|
|
max-width: 50%;
|
|
overflow: hidden;
|
|
text-overflow: ellipsis;
|
|
padding: 0 6px 0 9px;
|
|
background: var(--head);
|
|
border-right: 1px solid var(--border);
|
|
color: var(--muted);
|
|
font-size: 12px;
|
|
white-space: nowrap;
|
|
user-select: none;
|
|
}
|
|
|
|
.wrolpi-path-input-control input {
|
|
flex: 1;
|
|
min-width: 0;
|
|
padding: 7px 9px;
|
|
background: none;
|
|
border: none;
|
|
outline: none;
|
|
color: var(--text);
|
|
font: inherit;
|
|
font-size: 13px;
|
|
}
|
|
|
|
.wrolpi-path-input-control input::placeholder {
|
|
color: var(--muted);
|
|
}
|
|
|
|
.wrolpi-path-input-control input:disabled {
|
|
color: var(--muted);
|
|
}
|
|
|
|
.wrolpi-path-input-description {
|
|
font-size: 12px;
|
|
color: var(--muted);
|
|
margin-top: 3px;
|
|
}
|
|
|
|
.wrolpi-path-input-error {
|
|
font-size: 12px;
|
|
color: var(--red);
|
|
margin-top: 3px;
|
|
}
|
|
|
|
/* ---------------------------------------------------------- Action inputs */
|
|
/*
|
|
* Input and button share one outline: the input gives up its right border and
|
|
* the button's left border becomes the seam between them.
|
|
*/
|
|
|
|
.wrolpi-action-input {
|
|
display: flex;
|
|
align-items: stretch;
|
|
width: 100%;
|
|
}
|
|
|
|
.wrolpi-action-input > .mantine-TextInput-root {
|
|
flex: 1;
|
|
min-width: 0;
|
|
}
|
|
|
|
.wrolpi-action-input .mantine-TextInput-input {
|
|
border-right-width: 0;
|
|
}
|
|
|
|
.wrolpi-action-input > :not(.mantine-TextInput-root) {
|
|
flex: none;
|
|
}
|
|
|
|
/* --------------------------------------------------------------- Switches */
|
|
/*
|
|
* Mantine's unchecked track is a gray from its own palette, which would put a
|
|
* non-red pixel on screen in night mode. Scoped to `:not(:checked)` so the
|
|
* checked track keeps coming from the `color` prop.
|
|
*/
|
|
/*
|
|
* Semantic's inverted stylesheet colors *every* descendant of `.ui.inverted.segment`
|
|
* (rgba(255,255,255,.9), and #dddddd on labels). Our components are mounted inside
|
|
* unmigrated pages that still use those wrappers, so an inherited color puts white
|
|
* pixels on screen in night mode. Restating from tokens is the fix; the rule can go
|
|
* when the last Semantic wrapper does.
|
|
*/
|
|
.wrolpi-switch .mantine-Switch-label {
|
|
color: var(--text);
|
|
}
|
|
|
|
.wrolpi-switch .mantine-Switch-description {
|
|
color: var(--muted);
|
|
}
|
|
|
|
.wrolpi-switch input:disabled + * .mantine-Switch-label,
|
|
.wrolpi-switch input:disabled ~ * .mantine-Switch-label {
|
|
color: var(--muted);
|
|
}
|
|
|
|
.wrolpi-switch input:not(:checked) + .mantine-Switch-track {
|
|
--switch-bg: var(--border);
|
|
}
|
|
|
|
/*
|
|
* The disabled thumb has the same problem: Mantine sets it from its gray palette,
|
|
* on the thumb element itself, which is a near-white pixel in night mode.
|
|
*/
|
|
.wrolpi-switch input:disabled + * > .mantine-Switch-thumb,
|
|
.wrolpi-switch input[data-disabled] + * > .mantine-Switch-thumb {
|
|
--switch-thumb-bg-disabled: var(--muted);
|
|
}
|
|
|
|
/* ----------------------------------------------------------------- Labels */
|
|
|
|
.wrolpi-label {
|
|
display: inline-flex;
|
|
align-items: center;
|
|
gap: 4px;
|
|
padding: 3px 9px;
|
|
font-size: 11.5px;
|
|
font-weight: 500;
|
|
line-height: 1.3;
|
|
background: var(--label-color);
|
|
color: var(--btn-text);
|
|
border: 1px solid transparent;
|
|
}
|
|
|
|
/* White and black labels would vanish into the surface they sit on. */
|
|
.wrolpi-label-white {
|
|
color: var(--black);
|
|
border-color: var(--border);
|
|
}
|
|
|
|
.wrolpi-label-black {
|
|
color: var(--white);
|
|
border-color: var(--border);
|
|
}
|
|
|
|
/*
|
|
* Night mode: a filled label is a bright patch, and a wall of tags would be a
|
|
* wall of light. Every label becomes an outline instead.
|
|
*/
|
|
html[data-theme="night"] .wrolpi-label {
|
|
background: transparent;
|
|
color: var(--label-color);
|
|
border-color: var(--label-color);
|
|
}
|
|
|
|
/* ---------------------------------------------------------------- Headers */
|
|
|
|
.wrolpi-header {
|
|
margin: 0 0 10px;
|
|
}
|
|
|
|
.wrolpi-header-dividing {
|
|
border-bottom: 1px solid var(--border);
|
|
padding-bottom: 6px;
|
|
}
|
|
|
|
.wrolpi-header-text {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 8px;
|
|
margin: 0;
|
|
/*
|
|
* Stated explicitly because semantic.min.css sets `h1,h2,h3,h4,h5 { font-family:
|
|
* Lato }` and loads after Mantine's element rule, so headings would ignore the
|
|
* theme's body face — visible in amber, where the body is mono and headings were
|
|
* not. A class outranks the element selector. Can go once Semantic is gone.
|
|
*/
|
|
font-family: var(--font-body);
|
|
font-weight: 600;
|
|
line-height: 1.25;
|
|
color: var(--text);
|
|
text-wrap: balance;
|
|
}
|
|
|
|
/* The scale lives here so no call site invents a size of its own. */
|
|
.wrolpi-header-h1 {
|
|
font-size: 26px;
|
|
}
|
|
|
|
.wrolpi-header-h2 {
|
|
font-size: 21px;
|
|
}
|
|
|
|
.wrolpi-header-h3 {
|
|
font-size: 17px;
|
|
}
|
|
|
|
.wrolpi-header-h4 {
|
|
font-size: 14px;
|
|
}
|
|
|
|
.wrolpi-header-h5 {
|
|
font-size: 13px;
|
|
letter-spacing: 0.02em;
|
|
}
|
|
|
|
.wrolpi-header-sub {
|
|
font-size: 12px;
|
|
color: var(--muted);
|
|
margin-top: 3px;
|
|
}
|
|
|
|
/*
|
|
* Night and amber are single-hue by definition, and a call site may legitimately want
|
|
* many distinguishable colours — the Flasher's chip-family badges set `--label-color`
|
|
* inline from a twelve-colour palette that no four tokens can express.
|
|
*
|
|
* In those themes the palette is overridden back to the theme's own text colour, so a
|
|
* label cannot put a green or blue pixel on a night-adapted screen. `!important` is
|
|
* required because the palette arrives as an inline custom property, which a normal
|
|
* rule cannot outrank. The labels stop being distinguishable by colour, which is the
|
|
* correct trade: the theme's whole purpose is that no other hue reaches the eye.
|
|
*/
|
|
html[data-theme="night"] .wrolpi-label,
|
|
html[data-theme="amber"] .wrolpi-label {
|
|
--label-color: var(--text) !important;
|
|
}
|
|
|
|
/* --------------------------------------------------------------- Messages */
|
|
|
|
.wrolpi-message {
|
|
display: flex;
|
|
align-items: flex-start;
|
|
gap: 10px;
|
|
border: 1px solid var(--border);
|
|
border-left: 3px solid var(--message-color, var(--border));
|
|
background: var(--panel);
|
|
padding: 10px 14px;
|
|
font-size: 13px;
|
|
}
|
|
|
|
.wrolpi-message-content {
|
|
flex: 1;
|
|
min-width: 0;
|
|
}
|
|
|
|
/* The icon repeats what the border color already says, so it takes that color. */
|
|
.wrolpi-message-icon {
|
|
color: var(--message-color, var(--text));
|
|
display: flex;
|
|
padding-top: 1px;
|
|
}
|
|
|
|
.wrolpi-message-dismiss {
|
|
background: none;
|
|
border: none;
|
|
padding: 0;
|
|
color: var(--muted);
|
|
cursor: pointer;
|
|
display: flex;
|
|
/* Widen the tap target past the glyph without moving it. */
|
|
margin: -6px -6px -6px 0;
|
|
padding: 6px;
|
|
}
|
|
|
|
.wrolpi-message-dismiss:hover {
|
|
color: var(--text);
|
|
}
|
|
|
|
.wrolpi-message-dismiss:focus-visible {
|
|
outline: 2px solid var(--blue);
|
|
outline-offset: -2px;
|
|
}
|
|
|
|
.wrolpi-message-title {
|
|
font-weight: 600;
|
|
margin-bottom: 2px;
|
|
}
|
|
|
|
.wrolpi-message-body {
|
|
color: var(--muted);
|
|
}
|
|
|
|
/* Errors take the danger treatment in every theme, so "dashed" stays one rule. */
|
|
.wrolpi-message-error {
|
|
border: 1.5px dashed var(--red);
|
|
}
|
|
|
|
/* --------------------------------------------------------------- Progress */
|
|
|
|
.wrolpi-progress {
|
|
position: relative;
|
|
height: 16px;
|
|
min-width: 120px;
|
|
background: var(--bg);
|
|
border: 1px solid var(--border);
|
|
}
|
|
|
|
.wrolpi-progress-fill {
|
|
height: 100%;
|
|
background: var(--progress-color, var(--blue));
|
|
transition: width 0.2s linear;
|
|
}
|
|
|
|
@media (prefers-reduced-motion: reduce) {
|
|
.wrolpi-progress-fill {
|
|
transition: none;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Indeterminate: a sliding band, for work whose size is not known yet. Semantic called
|
|
* this `indicating`; without it an upload that has not reported its size sits at 0% and
|
|
* reads as stalled.
|
|
*/
|
|
.wrolpi-progress-indeterminate .wrolpi-progress-fill {
|
|
width: 35%;
|
|
animation: wrolpi-progress-slide 1.4s ease-in-out infinite;
|
|
}
|
|
|
|
@keyframes wrolpi-progress-slide {
|
|
from {
|
|
margin-left: -35%;
|
|
}
|
|
to {
|
|
margin-left: 100%;
|
|
}
|
|
}
|
|
|
|
/* Motion is the only signal here, so when it is suppressed the bar fills instead. */
|
|
@media (prefers-reduced-motion: reduce) {
|
|
.wrolpi-progress-indeterminate .wrolpi-progress-fill {
|
|
animation: none;
|
|
width: 100%;
|
|
opacity: 0.5;
|
|
}
|
|
}
|
|
|
|
.wrolpi-progress-text {
|
|
position: absolute;
|
|
inset: 0;
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
font-size: 11px;
|
|
font-weight: 700;
|
|
color: var(--text);
|
|
font-variant-numeric: tabular-nums;
|
|
}
|
|
|
|
/*
|
|
* The percent text sits on both the filled and empty halves of the bar, so the
|
|
* fill has to stay light enough in light mode for dark text to read over it.
|
|
*/
|
|
html[data-theme="light"] .wrolpi-progress {
|
|
background: var(--panel);
|
|
}
|
|
|
|
html[data-theme="light"] .wrolpi-progress-fill {
|
|
filter: brightness(1.75) saturate(0.55);
|
|
}
|
|
|
|
/* Night: a mostly-full bar must not become a large bright surface. */
|
|
html[data-theme="night"] .wrolpi-progress-fill,
|
|
html[data-theme="amber"] .wrolpi-progress-fill {
|
|
background: var(--muted);
|
|
}
|
|
|
|
/* ----------------------------------------------------------------- Panels */
|
|
|
|
.wrolpi-panel {
|
|
background: var(--panel);
|
|
border: 1px solid var(--border);
|
|
padding: 14px 16px;
|
|
}
|
|
|
|
/* Danger zones are dashed in every theme; it doubles as a colorblind-safe cue. */
|
|
.wrolpi-panel-danger {
|
|
border: 1.5px dashed var(--red);
|
|
}
|
|
|
|
/* ----------------------------------------------------------- Card posters */
|
|
/*
|
|
* The icon shown when a file has no poster, and the corner mark for a tagged
|
|
* file. Both live here rather than in Common.js so the card surface and the
|
|
* poster area agree in every theme.
|
|
*/
|
|
|
|
.wrolpi-card-icon {
|
|
position: relative;
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
min-height: 120px;
|
|
background: var(--head);
|
|
}
|
|
|
|
.wrolpi-card-tag {
|
|
position: absolute;
|
|
top: 0;
|
|
left: 0;
|
|
display: flex;
|
|
padding: 3px 4px;
|
|
background: var(--green);
|
|
color: var(--btn-text);
|
|
}
|
|
|
|
/* Night and amber have no green to spend; the mark reads as an outline instead. */
|
|
html[data-theme="night"] .wrolpi-card-tag,
|
|
html[data-theme="amber"] .wrolpi-card-tag {
|
|
background: var(--panel);
|
|
color: var(--text);
|
|
border-right: 1px solid var(--border);
|
|
border-bottom: 1px solid var(--border);
|
|
}
|
|
|
|
/* ------------------------------------------------------------- Statistics */
|
|
/*
|
|
* A statistic carries its own spacing rather than relying on a parent to supply
|
|
* it: unmigrated pages still drop these inside Semantic's `Statistic.Group`,
|
|
* whose CSS only spaces elements carrying Semantic's own classes, and without
|
|
* this the labels run together into one unreadable line.
|
|
*/
|
|
|
|
.wrolpi-statistic {
|
|
display: inline-block;
|
|
vertical-align: top;
|
|
margin: 0 26px 10px 0;
|
|
}
|
|
|
|
/* Inside our own group the grid does the spacing, so the intrinsic margin goes. */
|
|
.wrolpi-statistic-cell .wrolpi-statistic {
|
|
display: block;
|
|
margin: 0;
|
|
}
|
|
|
|
.wrolpi-statistic-value {
|
|
font-size: 24px;
|
|
font-weight: 600;
|
|
font-variant-numeric: tabular-nums;
|
|
line-height: 1.15;
|
|
}
|
|
|
|
.wrolpi-statistic-label {
|
|
font-size: 11px;
|
|
letter-spacing: 0.1em;
|
|
text-transform: uppercase;
|
|
color: var(--muted);
|
|
margin-top: 2px;
|
|
}
|
|
|
|
/* ---------------------------------------------------------------- Status */
|
|
/*
|
|
* Status is encoded by hue in light/dark. In night mode hue is unavailable, so
|
|
* brightness carries it instead: dim = done, mid = active, brightest + bold =
|
|
* failed. Callers use these classes rather than naming a color.
|
|
*/
|
|
|
|
.wrolpi-status {
|
|
font-size: 12.5px;
|
|
font-weight: 500;
|
|
display: inline-flex;
|
|
align-items: center;
|
|
gap: 5px;
|
|
}
|
|
|
|
.wrolpi-status-complete {
|
|
color: var(--green);
|
|
}
|
|
|
|
.wrolpi-status-active {
|
|
color: var(--blue);
|
|
}
|
|
|
|
.wrolpi-status-pending {
|
|
color: var(--muted);
|
|
}
|
|
|
|
.wrolpi-status-failed {
|
|
color: var(--red);
|
|
}
|
|
|
|
html[data-theme="night"] .wrolpi-status-complete {
|
|
color: var(--muted);
|
|
}
|
|
|
|
html[data-theme="night"] .wrolpi-status-active {
|
|
color: var(--text);
|
|
}
|
|
|
|
html[data-theme="night"] .wrolpi-status-failed {
|
|
color: var(--red);
|
|
font-weight: 500;
|
|
}
|
|
|
|
/* ----------------------------------------------------------------- Tables */
|
|
|
|
/* A sortable header: a real button, so the column is reachable by keyboard. */
|
|
.wrolpi-th-sort {
|
|
display: inline-flex;
|
|
align-items: center;
|
|
gap: 5px;
|
|
background: none;
|
|
border: none;
|
|
padding: 0;
|
|
margin: 0;
|
|
font: inherit;
|
|
color: inherit;
|
|
cursor: pointer;
|
|
white-space: nowrap;
|
|
}
|
|
|
|
/* The unsorted columns show a faint arrow: the affordance is visible, but it does
|
|
not compete with the column actually doing the sorting. */
|
|
.wrolpi-th-sort-idle {
|
|
opacity: 0.35;
|
|
}
|
|
|
|
.wrolpi-th-sort:hover .wrolpi-th-sort-idle {
|
|
opacity: 0.7;
|
|
}
|
|
|
|
.wrolpi-th-sort:focus-visible {
|
|
outline: 2px solid var(--blue);
|
|
outline-offset: 2px;
|
|
}
|
|
|
|
html[data-theme="night"] .wrolpi-th-sort:focus-visible,
|
|
html[data-theme="amber"] .wrolpi-th-sort:focus-visible {
|
|
outline-color: var(--text);
|
|
}
|
|
|
|
/* A failed row carries the danger border, matching failed messages. */
|
|
.wrolpi-row-failed > td {
|
|
border-top: 1.5px dashed var(--red);
|
|
border-bottom: 1.5px dashed var(--red);
|
|
}
|
|
|
|
.wrolpi-row-failed > td:first-child {
|
|
border-left: 1.5px dashed var(--red);
|
|
}
|
|
|
|
.wrolpi-row-failed > td:last-child {
|
|
border-right: 1.5px dashed var(--red);
|
|
}
|
|
|
|
/* Wide tables scroll inside their own container; the page never scrolls sideways. */
|
|
.wrolpi-table-scroll {
|
|
overflow-x: auto;
|
|
}
|