/* * 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; max-height: 320px; 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); } /* ---------------------------------------------------------- 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; } /* --------------------------------------------------------------- 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; } } .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; }