mirror of
https://github.com/lrnselfreliance/wrolpi.git
synced 2026-10-26 19:01:39 -04:00
Six real fixes and one honest retraction.
The control is now a SIBLING of the heading inside a flex row, not a child of
it. Inside the `<h3>` it joined the heading's accessible name, so screen reader
heading navigation announced "Root CA Certificate Help". The row is what keeps
them on one line, and it is only rendered when there is something to sit beside,
so the markup for the other 46 call sites is unchanged.
`style` now lands on the wrapper. It used to reach the outer div; going through
Header put it on the inner heading, where a margin sets space INSIDE the wrapper
and only worked at all through margin collapse -- which stops the moment the
wrapper gains padding or a border. Status's "Drive Bandwidth" is the live case:
verified 16px on the block, 0 on the h2. Text properties still arrive, because
colour and text-align inherit; the `color` PROP stays on the heading.
A `<label>` only when `for_` names a field. Several call sites pass an
InfoHeader as the `label` prop of InputForm/ToggleForm, which wraps it in a
label of its own -- an unconditional one nested labels, which is invalid and
makes clicking the text ambiguous. Verified: zero `label label` on
/videos/settings, where "Video Resolutions" keeps its label and "Video File
Format" no longer has one.
The help controls carried `iconStyle: {margin: '0.5em'}`, which separated a
free-floating sibling and, inside the row, added to the gap on every side.
Zeroed; the row's gap is the spacing.
The browser test mounted a plain Button in an `after` slot rather than a real
HelpHeader, so the default icon chrome above was exactly what it could not see.
It mounts the real wrapper now. Two of my assertions were also too weak to
catch what they claimed: the gap was measured to `.wrolpi-header-after`, a flex
container whose own rect does not move when a child gains a margin, so it had to
measure the button; and the squeeze case used a title of ordinary words, which
simply wraps and never puts the row under pressure.
Renamed the suites -- "keeps the icon on its own line" read as the opposite of
the fix.
The retraction: `flex-shrink: 0` is declared but has NO test. Three attempts
could not find a width where removing it changes what is painted -- the row's
only other item is a heading, and its automatic minimum size absorbs the
pressure; the help control is an ActionIcon with a min-width it will not go
below either. The declaration stays as a statement of intent, with a comment
saying it is unproven, rather than a green test that does not discriminate.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
1307 lines
39 KiB
CSS
1307 lines
39 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.
|
|
*
|
|
* Which surface that is depends on where the stack was placed, and only the call
|
|
* site knows: the file browser puts one inside a filled blue button, where a disc
|
|
* of the page background is a hole punched in the button. `--icon-stack-bg`
|
|
* defaults to the page, and a call site anywhere else sets it.
|
|
*/
|
|
.wrolpi-icon-stack-corner {
|
|
position: absolute;
|
|
right: -3px;
|
|
bottom: -3px;
|
|
display: flex;
|
|
background: var(--icon-stack-bg, var(--bg));
|
|
border-radius: 50%;
|
|
padding: 1px;
|
|
}
|
|
|
|
/*
|
|
* A stack inside a button is the one case the call site should not have to describe,
|
|
* because the surface changes underneath it: Mantine paints a filled button from
|
|
* `--button-bg` but a disabled one from `--mantine-color-disabled`, whatever its variant.
|
|
* The file browser's New Folder button is disabled in WROL mode, so a disc pinned to the
|
|
* enabled fill would be a blue chip on a grey button -- the original hole, inverted.
|
|
*
|
|
* Only `filled` gets the first rule: an outline or subtle button sets `--button-bg` to
|
|
* `transparent`, and there the surface behind the button is still the right answer.
|
|
* `:not([data-variant])` is that same case -- Mantine omits the attribute entirely when a
|
|
* call site names a colour but no variant, which is how every filled button here is
|
|
* written, and keying on the attribute alone matched none of them. `--button-bg` itself
|
|
* is absent when a button names neither, hence the fallback chain.
|
|
*/
|
|
.mantine-Button-root:not([data-variant]) .wrolpi-icon-stack-corner,
|
|
.mantine-Button-root[data-variant="filled"] .wrolpi-icon-stack-corner {
|
|
background: var(--button-bg, var(--icon-stack-bg, var(--bg)));
|
|
}
|
|
|
|
.mantine-Button-root:disabled .wrolpi-icon-stack-corner,
|
|
.mantine-Button-root[data-disabled] .wrolpi-icon-stack-corner {
|
|
background: var(--mantine-color-disabled);
|
|
}
|
|
|
|
/* ---------------------------------------------------------------- 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(--danger);
|
|
border: 1.5px dashed var(--danger);
|
|
}
|
|
|
|
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;
|
|
/*
|
|
* A tab is a NavLink in the app's nav bars but a <button> on Inventory and in the
|
|
* gallery, and a button carries the UA's own `buttonface` background -- rgb(239,239,239).
|
|
* Only the active tab set a background, so every inactive one was a near-white block in
|
|
* dark, night and amber. The bar's own surface has to show through instead.
|
|
*/
|
|
background: transparent;
|
|
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 */
|
|
/*
|
|
* A Pill is what MultiSelect renders for each chosen option, and it is the one place
|
|
* Mantine does not honour the radius theme. MultiSelect leaves `radius` undefined, so
|
|
* Pill emits no `--pill-radius` at all and its stylesheet falls back to
|
|
* `border-radius: var(--pill-radius, 1000rem)` -- a literal in Mantine's own CSS, which
|
|
* zeroing `--mantine-radius-xl` never touches. Defining the variable is what stops the
|
|
* fallback from ever applying. Everything else in the library has hard corners.
|
|
*/
|
|
.mantine-Pill-root {
|
|
--pill-radius: 0;
|
|
border-radius: 0;
|
|
}
|
|
|
|
/*
|
|
* `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(--danger);
|
|
}
|
|
|
|
.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(--danger);
|
|
}
|
|
|
|
.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(--danger);
|
|
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 */
|
|
|
|
/*
|
|
* Sized to Semantic's label (12px text, ~7px of vertical padding), which these replaced.
|
|
* The first pass came out at 11.5px with 3px of padding and read as noticeably smaller than
|
|
* what users had before -- a tag is a hit target as well as a word.
|
|
*/
|
|
.wrolpi-label {
|
|
display: inline-flex;
|
|
align-items: center;
|
|
gap: 4px;
|
|
padding: 5px 10px;
|
|
font-size: 12.5px;
|
|
font-weight: 500;
|
|
line-height: 1.3;
|
|
background: var(--label-color);
|
|
/*
|
|
* `--label-text` is set per-tag by Tags.js, which calculates black or light text against
|
|
* the user's chosen fill. The fallback covers every label whose fill is a theme token.
|
|
* It is a variable rather than an inline `color` so that a theme which discards the fill
|
|
* can also discard the text colour calculated for it.
|
|
*/
|
|
color: var(--label-text, var(--btn-text));
|
|
border: 1px solid transparent;
|
|
}
|
|
|
|
/* ------------------------------------------------------------------- Tags */
|
|
/*
|
|
* A tag is a label with a physical shape: a pointed left edge and an eyelet, like a tag
|
|
* tied to a garment. The point is a square rotated 45 degrees behind the left edge -- its
|
|
* right half sits under the body in the same colour and merges, leaving the left half
|
|
* showing as the point. At 20px a side its diagonal is 28px, which is the body's height,
|
|
* so the two edges of the point meet the body exactly at its corners.
|
|
*
|
|
* The eyelet is drawn in `currentColor`, so it reads as a hole punched through the tag on
|
|
* any surface, filled or outlined, without needing to know what is behind it.
|
|
*/
|
|
|
|
.wrolpi-tag {
|
|
position: relative;
|
|
/*
|
|
* Room for the point, which is drawn outside the body: half its diagonal (14.1px) plus
|
|
* the 1px it is nudged left, rounded up.
|
|
*/
|
|
margin-left: 16px;
|
|
/* Tags wrap, and a wrapped row was sitting on the row above it. */
|
|
margin-bottom: 5px;
|
|
/*
|
|
* The point is centred on the left edge, so its inner half covers the body's first 10px
|
|
* -- and a positioned pseudo-element paints above the parent's text, not behind it, so
|
|
* without this the first letter is hidden under the point. Semantic's tag label carried
|
|
* the same asymmetry (`padding-left: 1.5em`) for the same reason.
|
|
*/
|
|
padding-left: 13px;
|
|
}
|
|
|
|
.wrolpi-tag::before {
|
|
content: '';
|
|
position: absolute;
|
|
top: 50%;
|
|
right: 100%;
|
|
width: 20px;
|
|
height: 20px;
|
|
/*
|
|
* Nudged 1px left of where `right: 100%` puts it. That property positions against the
|
|
* containing block's *padding* box, and the body carries a 1px border, so a square centred
|
|
* there sits a pixel inside the visible left edge -- and the point read as set slightly
|
|
* into the tag rather than growing out of it. This is exactly that border: 2px opened a
|
|
* visible gap on the inner side, because the point's edges then meet the body's left edge
|
|
* short of its corners and leave the difference showing.
|
|
*/
|
|
transform: translate(calc(50% - 1px), -50%) rotate(45deg);
|
|
background: var(--label-color);
|
|
}
|
|
|
|
/*
|
|
* The eyelet sits in the point, where a real tag's hole is -- inside the body it reads as a
|
|
* bullet in front of the word. It is outside the element's own box, over the ::before, and
|
|
* paints on top of it by being the later pseudo-element.
|
|
*/
|
|
.wrolpi-tag::after {
|
|
content: '';
|
|
position: absolute;
|
|
top: 50%;
|
|
left: -7px;
|
|
width: 5px;
|
|
height: 5px;
|
|
margin-top: -2.5px;
|
|
border-radius: 50%;
|
|
background: currentColor;
|
|
/* A hole, not a dot: it should read as absence rather than as a bullet. */
|
|
opacity: 0.45;
|
|
}
|
|
|
|
/*
|
|
* White and black labels would vanish into the surface they sit on. They set the text
|
|
* colour through `--label-text` rather than `color` for the same reason the per-tag colour
|
|
* does: setting `color` here outranked the base rule, so amber -- which replaces every fill
|
|
* with its own and has no `color` rule of its own -- was left painting `--white` on `--text`,
|
|
* light orange on orange.
|
|
*/
|
|
.wrolpi-label-white {
|
|
--label-text: var(--black);
|
|
border-color: var(--border);
|
|
}
|
|
|
|
.wrolpi-label-black {
|
|
--label-text: 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);
|
|
}
|
|
|
|
/*
|
|
* Night draws the tag as an outline, so the point has to be two strokes rather than a solid
|
|
* diamond, and the body's own left border has to go -- otherwise a line closes across the
|
|
* base of the point and it reads as a chip with a triangle stuck to it.
|
|
*/
|
|
html[data-theme="night"] .wrolpi-tag {
|
|
border-left-color: transparent;
|
|
}
|
|
|
|
html[data-theme="night"] .wrolpi-tag::before {
|
|
background: transparent;
|
|
border-left: 1px solid var(--label-color);
|
|
border-bottom: 1px solid var(--label-color);
|
|
}
|
|
|
|
/* ---------------------------------------------------------------- Headers */
|
|
|
|
.wrolpi-header {
|
|
margin: 0 0 10px;
|
|
}
|
|
|
|
/*
|
|
* A heading opens a section, so it needs more air above it than below — it belongs to
|
|
* what follows, not to what came before. With no top margin at all a heading sat flush
|
|
* against the panel above it, which read as part of that panel rather than as the start
|
|
* of the next thing (visible on /more/statistics, where "Tags" touched the panel above).
|
|
*
|
|
* `* +` rather than a blanket margin: a heading that opens a Panel or a page is already
|
|
* where it should be, and pushing it down would just dent the top of its own container.
|
|
*/
|
|
* + .wrolpi-header {
|
|
margin-top: 22px;
|
|
}
|
|
|
|
/* The heading and its trailing control share one row. */
|
|
.wrolpi-header-row {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 8px;
|
|
}
|
|
|
|
/*
|
|
* Trailing content keeps the heading's row but not its size: a help button rendered at
|
|
* heading scale is a 26px glyph next to a 17px word.
|
|
*
|
|
* `flex-shrink: 0` states the intent -- a control is not the thing that should give way,
|
|
* a long title should wrap instead. Declared, but NOT covered by a test: with the row's
|
|
* only other item being a heading, the heading's automatic minimum size absorbs the
|
|
* pressure and no width could be found where removing this changes what is painted. It
|
|
* is here for the day the slot holds something beside a heading that can shrink, not
|
|
* because a case exists today.
|
|
*/
|
|
.wrolpi-header-after {
|
|
display: inline-flex;
|
|
align-items: center;
|
|
flex-shrink: 0;
|
|
font-size: 13px;
|
|
font-weight: 400;
|
|
}
|
|
|
|
.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;
|
|
/*
|
|
* The per-tag text colour goes with the fill it was calculated against. Tags.js works
|
|
* out black-or-light from the user's chosen hex, and once that hex is discarded the
|
|
* answer is not just useless but wrong: amber was painting `#dddddd` on `#ff9500`, about
|
|
* 1.9:1 and a neutral grey in a theme whose whole point is that nothing else is. Night,
|
|
* which is an outline over a near-black page, was painting that black text on nothing.
|
|
*
|
|
* There is no contrast to calculate here, which is why this is a token and not a second
|
|
* version of `contrastingColor`: the fill is the theme's own, so the answer is fixed.
|
|
*/
|
|
--label-text: var(--btn-text) !important;
|
|
}
|
|
|
|
/* ----------------------------------------------------------------- Toasts */
|
|
/*
|
|
* Notifications take their colours from our tokens rather than from Mantine's colour scheme.
|
|
*
|
|
* Mantine paints a dark-scheme notification title with `--mantine-color-white`, and the
|
|
* bridge aliases that to `--btn-text` -- deliberately, because it is the text drawn on a
|
|
* filled button, which is near-black in dark, night and amber. So the title came out at
|
|
* rgb(28, 31, 33) on a rgb(38, 41, 43) surface: about 1.1:1, present and invisible. Fixing
|
|
* the alias is not an option; it is correct for the buttons it exists for.
|
|
*
|
|
* The `html` prefix is only there to outrank Mantine's single-class rules. One set of values
|
|
* covers every theme, because the tokens are what differ.
|
|
*/
|
|
|
|
html .mantine-Notification-root {
|
|
background-color: var(--panel);
|
|
border-color: var(--border);
|
|
}
|
|
|
|
html .mantine-Notification-title {
|
|
color: var(--text);
|
|
}
|
|
|
|
html .mantine-Notification-description {
|
|
color: var(--muted);
|
|
}
|
|
|
|
/*
|
|
* The description is the message itself, so it has to be read, and `--muted` is too dim to
|
|
* read on a panel in the two monochrome themes -- 2.0:1 in night, 3.1:1 in amber, against
|
|
* 5.1:1 and 5.7:1 in dark and light. Those two drop the muting and let the title's weight
|
|
* carry the hierarchy instead, which is the same brightness-over-hue trade the rest of night
|
|
* makes.
|
|
*/
|
|
html[data-theme="night"] .mantine-Notification-description,
|
|
html[data-theme="amber"] .mantine-Notification-description {
|
|
color: var(--text);
|
|
}
|
|
|
|
html .mantine-Notification-closeButton {
|
|
color: var(--muted);
|
|
}
|
|
|
|
html .mantine-Notification-closeButton:hover {
|
|
background-color: var(--head);
|
|
color: var(--text);
|
|
}
|
|
|
|
/* --------------------------------------------------------------- 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. */
|
|
/*
|
|
* The dashed frame tracks the role the message was given, exactly as its accent and
|
|
* icon already do. Naming a hue here meant the frame and the accent could disagree --
|
|
* invisible in night, where they are nearly the same pixel, but still wrong.
|
|
*/
|
|
.wrolpi-message-error {
|
|
border: 1.5px dashed var(--message-color);
|
|
}
|
|
|
|
/* --------------------------------------------------------------- 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(--danger);
|
|
}
|
|
|
|
/* ----------------------------------------------------------- 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, so that one dropped on a page by itself still sits clear of its
|
|
* neighbours. Semantic's `Statistic.Group` only spaced elements carrying
|
|
* Semantic's own classes, so without this the labels ran together into one
|
|
* unreadable line.
|
|
*/
|
|
|
|
.wrolpi-statistic {
|
|
display: inline-block;
|
|
vertical-align: top;
|
|
margin: 0 26px 10px 0;
|
|
}
|
|
|
|
/*
|
|
* The group is spacing and nothing else: no frame, no rules between cells, and
|
|
* no surface of its own, so the statistics sit on whatever they are dropped onto
|
|
* -- the page in one place, a Panel in another -- and pick up its colour.
|
|
*
|
|
* That also settles a question this went round twice on. Ruling the cells needs
|
|
* a technique that survives wrapping, because `auto-fit` decides the number of
|
|
* rows at paint time: a `border-right` per cell rules columns but not rows, and
|
|
* a border-coloured background behind a 1px gap rules both but paints every
|
|
* track no cell landed in. With no rules at all, neither problem exists.
|
|
*
|
|
* The gap is wider across than down. Two statistics side by side have nothing
|
|
* between them but air, so they need enough of it not to read as one column of
|
|
* text; stacked ones are already separated by the label's line.
|
|
*/
|
|
.wrolpi-statistic-group {
|
|
display: grid;
|
|
grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
|
|
gap: 18px 30px;
|
|
}
|
|
|
|
/*
|
|
* With the frame gone there is nothing holding the group off whatever precedes it, and on
|
|
* most pages that is a Header -- the first row of values sat directly against the heading.
|
|
*
|
|
* Only when something does precede it, though. Apps opens each of its Panels with a group,
|
|
* and a blanket `margin-top` there pushed the values down off the panel's own top padding
|
|
* while leaving the bottom where it was, so the whole panel read as lopsided.
|
|
*/
|
|
* + .wrolpi-statistic-group {
|
|
margin-top: 18px;
|
|
}
|
|
|
|
/*
|
|
* A statistic that renders nothing -- Status omits the fan reading on a device
|
|
* with no fan connector -- must not hold a track open. The group cannot know in
|
|
* advance, since it would have to render the child to find out, so the cell is
|
|
* emitted either way and this takes the empty ones back out. It is also why the
|
|
* cells carry no inline style: there is nothing here for it to outrank.
|
|
*/
|
|
.wrolpi-statistic-cell:empty {
|
|
display: none;
|
|
}
|
|
|
|
/* 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 */
|
|
/*
|
|
* One rule for four states in four themes. The colour is the role the component
|
|
* chose, and the role already knows what it looks like here -- which is what
|
|
* replaced the three `html[data-theme="night"]` overrides that used to live below.
|
|
*/
|
|
.wrolpi-status {
|
|
font-size: 12.5px;
|
|
font-weight: 500;
|
|
display: inline-flex;
|
|
align-items: center;
|
|
gap: 5px;
|
|
color: var(--status-color);
|
|
}
|
|
|
|
/*
|
|
* Failure is not left to colour alone. Night and amber distinguish the roles by
|
|
* brightness, and brightness is the first thing lost on a dim screen or to a user
|
|
* who cannot separate these hues, so the loudest state also carries weight.
|
|
*/
|
|
.wrolpi-status-failed {
|
|
font-weight: 600;
|
|
}
|
|
|
|
/* ----------------------------------------------------------------- Tables */
|
|
|
|
/*
|
|
* The header is a surface, not just bold text.
|
|
*
|
|
* Mantine gives `thead` no background at all, so the header showed whatever was
|
|
* behind the table -- which is exactly what the unstriped body rows show. Bold
|
|
* type was the only thing separating the two, and against a striped body the
|
|
* header read as one more stripe. `--head` is the token for a recessed surface
|
|
* and is distinct from both the panel and the stripe in all four themes.
|
|
*/
|
|
.mantine-Table-thead .mantine-Table-th {
|
|
background: var(--head);
|
|
}
|
|
|
|
/*
|
|
* A full-strength rule under the header, because that is the one division in a
|
|
* table that carries meaning: everything above it names, everything below it is
|
|
* data. The rules between body rows are `--table-line` and much fainter -- see
|
|
* the note there. Applied to the last header row so a grouped header does not
|
|
* get a hard rule through the middle of itself.
|
|
*/
|
|
.mantine-Table-thead .mantine-Table-tr:last-child .mantine-Table-th {
|
|
border-bottom: 2px solid var(--border);
|
|
}
|
|
|
|
/*
|
|
* Row rules are quieter than any other border in the interface. They only have
|
|
* to let the eye track across a row; at `--border` strength, with a rule under
|
|
* every row, a long table turns into a stack of boxes.
|
|
*
|
|
* `html` for specificity, not decoration: Mantine declares this variable on its
|
|
* own hashed class, which ties with `.mantine-Table-table` and wins on source
|
|
* order because its stylesheet is imported after this one.
|
|
*/
|
|
html .mantine-Table-table {
|
|
--table-border-color: var(--table-line);
|
|
}
|
|
|
|
/*
|
|
* The outline around the whole table is not a row rule and keeps full strength;
|
|
* Mantine draws both from the same variable, so it has to be restated.
|
|
*/
|
|
html .mantine-Table-table[data-with-table-border] {
|
|
border-color: var(--border);
|
|
}
|
|
|
|
/* 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(--danger);
|
|
border-bottom: 1.5px dashed var(--danger);
|
|
}
|
|
|
|
.wrolpi-row-failed > td:first-child {
|
|
border-left: 1.5px dashed var(--danger);
|
|
}
|
|
|
|
.wrolpi-row-failed > td:last-child {
|
|
border-right: 1.5px dashed var(--danger);
|
|
}
|
|
|
|
/* Wide tables scroll inside their own container; the page never scrolls sideways. */
|
|
.wrolpi-table-scroll {
|
|
overflow-x: auto;
|
|
}
|