mirror of
https://github.com/lrnselfreliance/wrolpi.git
synced 2026-10-26 19:01:39 -04:00
The group had a frame, a surface and a hairline between every cell. It now has none of those: a plain responsive grid, transparent, so the statistics sit on whatever they were dropped onto and take its colour -- the page on the gallery, a Panel on Apps and Status. That also retires the problem the previous commit went two rounds on. Ruling the cells needs a technique that survives wrapping, because `auto-fit` settles the number of rows at paint time: a border 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 to draw, neither case exists. The gap is wider across than down, since two statistics side by side have only air between them while stacked ones already have the label's line. The margin above the group is conditional on having a sibling before it. A plain `margin-top` reads correctly under the Header that precedes most groups, but Apps opens each of its Panels with one, and there it pushed the values down off the panel's top padding while the bottom stayed put, leaving every panel lopsided. Five browser tests described hairlines that no longer exist. They are replaced by the claims that now hold -- real space between wrapped rows, a wider column gap than row gap, transparency and no frame anywhere, and the conditional margin -- each verified red first: against flush rows for the spacing, against a background-carried hairline for the transparency, and against an unconditional margin for the panel case. Per-theme, the pair to check is no longer the hairline and the surface but the value and the label: with nothing framing a statistic, the contrast between those two is the only thing giving it structure.
1030 lines
26 KiB
CSS
1030 lines
26 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, 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 */
|
|
/*
|
|
* 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;
|
|
}
|