mirror of
https://github.com/caiwx86/small-packages.git
synced 2026-11-02 18:53:56 +08:00
update 2026-10-09 04:58:55
This commit is contained in:
1 parent
9c17634d78
commit
d6b7af2a48
636 files changed
+163
-121913
No files matched your search
@@ -1,97 +0,0 @@
|
||||
@layer base {
|
||||
/* Element reset (Meyer, trimmed to what a LuCI view can emit). */
|
||||
|
||||
/* scroll-margin-top: anchor jump must clear the sticky bar (WCAG 2.2 SC 2.4.11), token not a
|
||||
* literal — bar is 46px. */
|
||||
*, *::before, *::after {
|
||||
scroll-margin-top: var(--fs-bar-h);
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
border: 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
abbr[title], acronym[title] {
|
||||
border-bottom: 1px dotted;
|
||||
font-weight: inherit;
|
||||
cursor: help;
|
||||
}
|
||||
|
||||
table {
|
||||
border-collapse: collapse;
|
||||
border-spacing: 0;
|
||||
}
|
||||
|
||||
/* Outranks every layer's `display`, id rules included: docs/css.md "Reset: [hidden] must
|
||||
* outrank every layer, not patch one mistake". */
|
||||
[hidden]:not([hidden="until-found"]) { display: none !important; }
|
||||
|
||||
html {
|
||||
font-size: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
/* :focus-visible: UA heuristic keeps the ring off a mouse click. */
|
||||
a:focus-visible {
|
||||
outline: 2px solid var(--fs-accent);
|
||||
outline-offset: 2px;
|
||||
}
|
||||
|
||||
sub, sup {
|
||||
font-size: 75%;
|
||||
line-height: 0;
|
||||
position: relative;
|
||||
vertical-align: baseline;
|
||||
}
|
||||
|
||||
sup {
|
||||
top: -.5em;
|
||||
}
|
||||
|
||||
sub {
|
||||
bottom: -.25em;
|
||||
}
|
||||
|
||||
/* font-size: theme/60-inputs.css (control-typography rule + option) — absorbed */
|
||||
button,
|
||||
input,
|
||||
select,
|
||||
option,
|
||||
textarea {
|
||||
margin: 0;
|
||||
box-sizing: border-box;
|
||||
vertical-align: baseline;
|
||||
}
|
||||
|
||||
button,
|
||||
input[type="button"],
|
||||
input[type="reset"],
|
||||
input[type="submit"] {
|
||||
cursor: pointer;
|
||||
appearance: button;
|
||||
word-break: break-all;
|
||||
}
|
||||
|
||||
/* disabled-opacity fade: 30-forms.css, same elements + pointer-events/cursor */
|
||||
|
||||
input[type="search"] {
|
||||
appearance: textfield; /* not content-box: contradicts border-box above, ~22px too wide */
|
||||
}
|
||||
|
||||
input[type="search"]::-webkit-search-decoration {
|
||||
appearance: none;
|
||||
}
|
||||
|
||||
textarea {
|
||||
overflow: auto;
|
||||
vertical-align: top;
|
||||
font-family: var(--fs-font-mono);
|
||||
}
|
||||
|
||||
/* Unjoined (issue #7): bootstrap's joins square the trailing button's corner; this theme's
|
||||
* buttons keep full radius. Password reveal stays joined (theme/60-inputs.css). */
|
||||
.control-group {
|
||||
display: inline-flex;
|
||||
gap: var(--fs-space-2);
|
||||
}
|
||||
}
|
||||
@@ -1,153 +0,0 @@
|
||||
@layer base {
|
||||
/* body/link box + the type scale a view inherits. background/padding/font-family:
|
||||
* theme/20-shell.css — absorbed. */
|
||||
body {
|
||||
font-size: var(--fs-type);
|
||||
font-weight: var(--fs-weight-normal);
|
||||
line-height: var(--fs-leading); /* unitless: inherits as a FACTOR, not a clipping flat length */
|
||||
color: var(--fs-text);
|
||||
min-height: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
scrollbar-gutter: stable; /* no-reflow guarantee without a dead scrollbar on a short page */
|
||||
}
|
||||
|
||||
a {
|
||||
color: var(--fs-accent);
|
||||
text-decoration: none;
|
||||
line-height: inherit;
|
||||
font-weight: inherit;
|
||||
}
|
||||
|
||||
a:hover {
|
||||
color: var(--fs-accent);
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
.nowrap:not(span) {
|
||||
/* :not(span) — a badge must be free to wrap on a narrow screen, and nowrap
|
||||
defeats every other width constraint on it */
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
p,
|
||||
.cbi-map-descr,
|
||||
.cbi-section-descr,
|
||||
.table .tr.cbi-section-table-descr .th {
|
||||
font-size: var(--fs-type);
|
||||
font-weight: var(--fs-weight-normal);
|
||||
margin-bottom: var(--fs-space-2);
|
||||
/* prose, so no orphans: a single trailing word on its own line reads as a rendering fault.
|
||||
* `pretty`, not `balance` — balance is capped at a few lines and re-flows the whole block,
|
||||
* which is for headings. An unsupporting browser ignores it. */
|
||||
text-wrap: pretty;
|
||||
}
|
||||
|
||||
p small {
|
||||
font-size: var(--fs-type-xs);
|
||||
color: var(--fs-dim);
|
||||
}
|
||||
|
||||
/* The heading ramp lives in theme/45-misc.css — absorbed whole (weights, sizes, ink);
|
||||
* theme/30-tables.css owns `legend`. Only the `<h_ small>` treatment remains here. */
|
||||
|
||||
/* one rule for every `<h_ small>`, relative so it tracks whatever step its heading sits on */
|
||||
h1 small,
|
||||
h2 small,
|
||||
h3 small,
|
||||
h4 small,
|
||||
h5 small,
|
||||
h6 small {
|
||||
font-size: .7em;
|
||||
font-weight: var(--fs-weight-normal);
|
||||
color: var(--fs-dim);
|
||||
}
|
||||
|
||||
ul, ol {
|
||||
margin: 0 0 var(--fs-space-4) var(--fs-space-6);
|
||||
}
|
||||
|
||||
ul ul,
|
||||
ul ol,
|
||||
ol ol,
|
||||
ol ul {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
ul {
|
||||
list-style: disc;
|
||||
}
|
||||
|
||||
ol {
|
||||
list-style: decimal;
|
||||
}
|
||||
|
||||
li {
|
||||
color: var(--fs-dim);
|
||||
}
|
||||
|
||||
dl {
|
||||
margin-bottom: var(--fs-space-4);
|
||||
}
|
||||
|
||||
dl dt {
|
||||
font-weight: var(--fs-weight-bold);
|
||||
}
|
||||
|
||||
dl dd {
|
||||
margin-inline-start: var(--fs-space-2);
|
||||
}
|
||||
|
||||
hr {
|
||||
margin: var(--fs-space-5) 0;
|
||||
border: 0;
|
||||
border-bottom: var(--fs-hairline);
|
||||
}
|
||||
|
||||
strong {
|
||||
font-style: inherit;
|
||||
font-weight: var(--fs-weight-bold);
|
||||
}
|
||||
|
||||
em {
|
||||
font-style: italic;
|
||||
font-weight: inherit;
|
||||
line-height: inherit;
|
||||
}
|
||||
|
||||
small { font-size: var(--fs-type) }
|
||||
|
||||
address {
|
||||
display: block;
|
||||
margin-bottom: var(--fs-space-4);
|
||||
}
|
||||
|
||||
/* no shared padding: both members set their own below (code a chip's inset, pre a block's) */
|
||||
code, pre {
|
||||
font-family: var(--fs-font-mono);
|
||||
font-size: var(--fs-type);
|
||||
border-radius: var(--fs-radius-sm);
|
||||
}
|
||||
|
||||
code {
|
||||
/* panel2 (6.45:1): --fs-text on a border-level surface is 4.40:1 in dark, under AA */
|
||||
background-color: var(--fs-panel2);
|
||||
color: var(--fs-text);
|
||||
padding: 1px var(--fs-space-1);
|
||||
/* docs/css.md "Typography and widgets: an opaque token needs overflow-wrap: anywhere, a
|
||||
* tooltip needs pre-wrap" — a WireGuard key measured 64px past the column at 320px. */
|
||||
overflow-wrap: anywhere;
|
||||
}
|
||||
|
||||
/* border-radius: the shared `code, pre` rule above already sets it — a second copy of the
|
||||
* same value here restates, not overrides. */
|
||||
pre {
|
||||
background-color: var(--fs-panel2);
|
||||
display: block;
|
||||
padding: var(--fs-space-2);
|
||||
margin: 0 0 var(--fs-space-4);
|
||||
border: 1px solid color-mix(in srgb, var(--fs-border) 15%, transparent);
|
||||
white-space: pre-wrap;
|
||||
overflow-wrap: break-word;
|
||||
}
|
||||
}
|
||||
@@ -1,220 +0,0 @@
|
||||
@layer base {
|
||||
/* Forms: input types, .cbi-value row layout, focus/disabled/readonly/invalid states. */
|
||||
fieldset {
|
||||
margin-bottom: var(--fs-space-2);
|
||||
padding-top: var(--fs-space-2);
|
||||
}
|
||||
|
||||
/* size: theme/30-tables.css (legend) — absorbed */
|
||||
fieldset legend {
|
||||
display: block;
|
||||
line-height: 1;
|
||||
color: var(--fs-text);
|
||||
padding-top: var(--fs-space-5);
|
||||
}
|
||||
|
||||
/* Stays in BASE: docs/css.md "Forms: control typography stays in base, on purpose". select is
|
||||
* excluded — face/size/leading live in theme/60-inputs.css. */
|
||||
label,
|
||||
input,
|
||||
button {
|
||||
font-family: var(--fs-font-sans);
|
||||
font-size: var(--fs-type);
|
||||
font-weight: var(--fs-weight-normal);
|
||||
line-height: normal;
|
||||
}
|
||||
|
||||
.cbi-value {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
margin-bottom: var(--fs-space-4);
|
||||
align-items: baseline;
|
||||
}
|
||||
|
||||
.cbi-value-field {
|
||||
margin-inline-start: var(--fs-space-5); /* logical: LuCI ships four RTL languages */
|
||||
flex: 1;
|
||||
min-width: 0; /* else a nested table or one long value pushes the row out of the card */
|
||||
}
|
||||
|
||||
/* size + ink: theme/60-inputs.css (.cbi-value-title) — absorbed. Left: label column geometry. */
|
||||
.cbi-value label.cbi-value-title {
|
||||
padding-top: var(--fs-space-1); /* lands the label's first baseline on the field's */
|
||||
flex: 0 0 180px; /* column WIDTH (longest option label), not on the 4px step scale */
|
||||
text-align: end; /* towards the field — not base/95-luci.css's physical .left/.right/.center */
|
||||
/* docs/css.md "Forms: the label column's min-width: 0 survives the responsive layer" —
|
||||
* stated here, not in theme/90-responsive.css. */
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.cbi-value > .cbi-section,
|
||||
.cbi-value > .cbi-tblsection {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
label > input[type="checkbox"],
|
||||
label > input[type="radio"] {
|
||||
vertical-align: text-top;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
label[for] {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
/* Shared field box (210px x control-height, #localtime widening, per-widget escapes):
|
||||
* theme/60-inputs.css — absorbed whole, in-layer, or it loses to a theme rule by layer. */
|
||||
|
||||
.cbi-dynlist {
|
||||
height: auto;
|
||||
display: inline-flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
/* item paint: theme/60-inputs.css — absorbed. Left: drag-and-drop machinery (UIDynamicList). */
|
||||
.cbi-dynlist > .item {
|
||||
margin-bottom: var(--fs-space-1);
|
||||
padding: var(--fs-space-1) 2em var(--fs-space-1) var(--fs-space-2);
|
||||
position: relative;
|
||||
pointer-events: auto;
|
||||
overflow: hidden;
|
||||
word-break: break-all;
|
||||
cursor: move;
|
||||
user-select: text;
|
||||
}
|
||||
|
||||
/* plate paint: theme/60-inputs.css — absorbed. Left: pins the × to the chip. */
|
||||
.cbi-dynlist > .item::after {
|
||||
content: "×";
|
||||
position: absolute;
|
||||
align-items: center;
|
||||
top: -1px;
|
||||
inset-inline-end: -1px;
|
||||
bottom: -1px;
|
||||
font-weight: var(--fs-weight-bold);
|
||||
pointer-events: auto;
|
||||
}
|
||||
|
||||
.cbi-dynlist > .item.drag-over {
|
||||
border-top: 1px solid var(--fs-text);
|
||||
}
|
||||
|
||||
.cbi-dynlist > .item.dragging {
|
||||
opacity: .5;
|
||||
}
|
||||
|
||||
.cbi-dynlist > .item > span {
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.cbi-dynlist > .add-item {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.cbi-dynlist > .add-item > .cbi-input-text.drag-over {
|
||||
border-top: 1px solid var(--fs-text);
|
||||
}
|
||||
|
||||
.cbi-dynlist > .add-item > .cbi-button-add.drag-over {
|
||||
border-top: 1px solid var(--fs-text);
|
||||
}
|
||||
|
||||
.cbi-dynlist > .add-item > input,
|
||||
.cbi-dynlist > .add-item > button {
|
||||
/* flex: theme/60-inputs.css — absorbed */
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.cbi-value-field > .cbi-checkbox,
|
||||
.cbi-value-field > div > .cbi-radio {
|
||||
height: var(--fs-control-h); /* lines a toggle up with the field above it */
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.cbi-radio {
|
||||
cursor: pointer;
|
||||
gap: var(--fs-space-1);
|
||||
}
|
||||
|
||||
/* .cbi-select and the checkbox/radio drawing: theme/60-inputs.css — absorbed whole. Do not
|
||||
* re-add an option rule here either: layer beats specificity, so theme's bare `option`
|
||||
* outranks even `.cbi-select select option:checked`. */
|
||||
|
||||
/* --fs-placeholder, not --fs-dim: a placeholder carries the option's DEFAULT and must not read
|
||||
* as a value the reader typed (03-palettes.css has the numbers). */
|
||||
::placeholder {
|
||||
color: var(--fs-placeholder);
|
||||
}
|
||||
|
||||
/* Transitions and the focus/hover ring: the theme's one ring token
|
||||
* (--fs-focus-ring/--fs-focus-ring-invalid). No generic glow here. */
|
||||
|
||||
input[disabled],
|
||||
button[disabled],
|
||||
select[disabled],
|
||||
textarea[disabled],
|
||||
.cbi-dropdown[disabled]:not(.btn):not(.cbi-button),
|
||||
input[type="checkbox"][disabled]::before,
|
||||
input[type="checkbox"][disabled]::after,
|
||||
input[type="radio"][disabled]::before,
|
||||
input[type="radio"][disabled]::after {
|
||||
opacity: var(--fs-disabled-opacity);
|
||||
pointer-events: none;
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
/* readonly border fade: theme/60-inputs.css — absorbed with the generic box */
|
||||
|
||||
.cbi-optionals,
|
||||
.cbi-section-create {
|
||||
padding: 0 0 var(--fs-space-3) var(--fs-space-3);
|
||||
}
|
||||
|
||||
.cbi-section-create {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: var(--fs-space-2); /* not a negative-margin pair — that trick predates flex gap, leaks */
|
||||
}
|
||||
|
||||
.cbi-section-create > * {
|
||||
flex: 1 1 auto;
|
||||
}
|
||||
|
||||
/* the input's cell-filling width: theme/60-inputs.css — absorbed with the generic box */
|
||||
|
||||
/* .cbi-page-actions paint: theme/55-buttons.css — absorbed. .actions has no theme override, so
|
||||
* it stays: some luci-app-* still emits it. */
|
||||
.actions {
|
||||
background: var(--fs-panel2);
|
||||
margin-bottom: var(--fs-space-4);
|
||||
padding: var(--fs-card-pad); /* a plate, so it takes the card's own gutter on every side */
|
||||
border-top: var(--fs-hairline);
|
||||
border-radius: 0 0 var(--fs-radius-lg) var(--fs-radius-lg);
|
||||
text-align: end;
|
||||
}
|
||||
|
||||
/* Physical, hand-mirrored: docs/css.md "Widgets: .actions .secondary-action still floats, and
|
||||
* needs a hand RTL mirror". */
|
||||
.actions .secondary-action {
|
||||
float: right;
|
||||
}
|
||||
:root[dir="rtl"] .actions .secondary-action { float: left; }
|
||||
|
||||
.actions .secondary-action a,
|
||||
.cbi-page-actions .secondary-action a {
|
||||
line-height: var(--fs-control-h);
|
||||
}
|
||||
|
||||
.actions .secondary-action a:hover,
|
||||
.cbi-page-actions .secondary-action a:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
.cbi-page-actions > form {
|
||||
display: inline;
|
||||
margin: 0;
|
||||
}
|
||||
}
|
||||
@@ -1,61 +0,0 @@
|
||||
@layer base {
|
||||
/* LuCI tables are div soup (.table/.tr/.th/.td), display roles assigned by hand. */
|
||||
.tr { display: table-row; }
|
||||
.table[width="33%"], .th[width="33%"], .td[width="33%"] { width: 33%; }
|
||||
.table[width="100%"], .th[width="100%"], .td[width="100%"] { width: 100%; }
|
||||
|
||||
.table {
|
||||
display: table;
|
||||
width: 100%;
|
||||
margin-bottom: var(--fs-space-4);
|
||||
padding: 0;
|
||||
font-size: var(--fs-type);
|
||||
border-collapse: collapse;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
/* Separator on the ROW (theme/30-tables.css), not the cell — else doubled and squares off
|
||||
* the frame's rounded corner (issue #7). */
|
||||
.table .th, .table .td {
|
||||
display: table-cell;
|
||||
vertical-align: middle;
|
||||
padding: var(--fs-space-2) var(--fs-space-3);
|
||||
text-align: start;
|
||||
}
|
||||
|
||||
/* Containment default, not the data-table floor: docs/css.md "Tables: overflow-wrap follows
|
||||
* the tier, not the file". First column is a label too; exempt once carded (.fs-stacked .td,
|
||||
* theme/30-tables.css). */
|
||||
.table .td { overflow-wrap: anywhere; }
|
||||
.table .th { overflow-wrap: break-word; }
|
||||
.table .tr > .td:first-child { overflow-wrap: break-word; }
|
||||
|
||||
.table .tr:first-child .th {
|
||||
padding-top: var(--fs-space-2);
|
||||
font-weight: var(--fs-weight-bold);
|
||||
vertical-align: top;
|
||||
}
|
||||
|
||||
/* Out-of-flow, static position (never `bottom: 0`): docs/css.md "Tables: the empty-table
|
||||
* placeholder must not pin to the table's bottom" (issue #36). */
|
||||
.tr.placeholder {
|
||||
height: calc(3em + var(--fs-space-4));
|
||||
}
|
||||
|
||||
.tr.placeholder > .td {
|
||||
position: absolute;
|
||||
inset-inline: 0;
|
||||
text-align: center;
|
||||
line-height: 3em;
|
||||
padding-block: var(--fs-space-2); /* two --fs-space-2 = the row height above, one number */
|
||||
}
|
||||
|
||||
/* Physical: border-block-start/-end need a css-floor.mjs support decision first. */
|
||||
.tr.drag-over-above {
|
||||
border-top: 2px solid var(--fs-accent);
|
||||
}
|
||||
|
||||
.tr.drag-over-below {
|
||||
border-bottom: 2px solid var(--fs-accent);
|
||||
}
|
||||
}
|
||||
@@ -1,126 +0,0 @@
|
||||
@layer base {
|
||||
/* Page chrome: the sticky <header> band, dropdown anchoring, the tab strip, breadcrumbs and the
|
||||
* footer. styles/theme/ repaints these; what stays here is the geometry a view can rely on. */
|
||||
header {
|
||||
position: sticky;
|
||||
top: 0;
|
||||
z-index: var(--fs-z-header);
|
||||
overflow: visible;
|
||||
color: var(--fs-dim);
|
||||
display: flex;
|
||||
}
|
||||
|
||||
li.menu, .dropdown {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.dropdown:hover ul.dropdown-menu {
|
||||
inset-inline-start: 0;
|
||||
}
|
||||
|
||||
/* Tab-strip visuals (margins, gradients, per-state colours): theme/40-tabs.css —
|
||||
* absorbed. Left: strip geometry. */
|
||||
.tabs > li, .cbi-tabmenu > li {
|
||||
flex: 0 1 auto;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.tabs > li > a, .cbi-tabmenu > li > a {
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
text-decoration: none;
|
||||
outline: none;
|
||||
}
|
||||
|
||||
/* the tab focus ring lives in theme/40-tabs.css: an accent outline is invisible on the ACTIVE
|
||||
* pill, whose fill is that same accent. The theme repaints the rule that stood here in full
|
||||
* (same selectors), so a copy left behind would be a dead declaration tools/audit.py --strict
|
||||
* reports. */
|
||||
|
||||
.cbi-tab-disabled[data-errors]::after {
|
||||
content: attr(data-errors);
|
||||
/* badge fill: theme/40-tabs.css — absorbed */
|
||||
color: var(--fs-on-danger);
|
||||
height: var(--fs-space-4);
|
||||
min-width: var(--fs-space-4);
|
||||
/* a pill, so it says so, rather than half the height above restated by hand */
|
||||
border-radius: var(--fs-radius-pill);
|
||||
text-align: center;
|
||||
margin-inline-end: var(--fs-space-1);
|
||||
padding: 0 var(--fs-space-1);
|
||||
display: inline-flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
font-size: var(--fs-type);
|
||||
}
|
||||
|
||||
.cbi-tab-descr {
|
||||
/* the negative top pulls it under the tab strip it describes */
|
||||
margin: calc(var(--fs-space-2) * -1) 0 var(--fs-space-4) 0;
|
||||
}
|
||||
|
||||
/* hangs directly under the tab strip: 100% IS the strip's height by definition, where a literal
|
||||
* would be a hidden pair with whatever sizes the tab */
|
||||
.tabs .menu-dropdown, .tabs .dropdown-menu {
|
||||
top: 100%;
|
||||
border-width: 1px;
|
||||
/* square top-left only: the panel hangs off the tab above it and the two share that
|
||||
* corner */
|
||||
border-radius: 0 var(--fs-radius) var(--fs-radius) var(--fs-radius);
|
||||
}
|
||||
|
||||
/* the caret beside a tab's label, centred on the text box rather than nudged down it */
|
||||
.tabs a.menu:after, .tabs .dropdown-toggle:after {
|
||||
border-top-color: var(--fs-dim);
|
||||
align-self: center;
|
||||
margin-inline-start: var(--fs-space-1);
|
||||
}
|
||||
|
||||
.tabs li.open.menu .menu, .tabs .open.dropdown .dropdown-toggle {
|
||||
border-color: var(--fs-border);
|
||||
}
|
||||
|
||||
.tabs li.open a.menu:after, .tabs .dropdown.open .dropdown-toggle:after {
|
||||
border-top-color: var(--fs-text);
|
||||
}
|
||||
|
||||
.breadcrumb {
|
||||
padding: var(--fs-space-2) var(--fs-space-3);
|
||||
margin: 0 0 var(--fs-space-4);
|
||||
background: linear-gradient(to bottom, var(--fs-panel), var(--fs-panel2));
|
||||
border: var(--fs-hairline);
|
||||
border-radius: var(--fs-radius);
|
||||
display: flex;
|
||||
flex: 0;
|
||||
}
|
||||
|
||||
.breadcrumb li {
|
||||
list-style: none;
|
||||
}
|
||||
|
||||
.breadcrumb li:not(:last-child)::after {
|
||||
content: "|";
|
||||
padding: 0 var(--fs-space-1);
|
||||
}
|
||||
|
||||
.breadcrumb .active a {
|
||||
color: var(--fs-dim);
|
||||
}
|
||||
|
||||
/* Footer paint: theme/20-shell.css — absorbed. Left: the flex skeleton, where margin-top:auto
|
||||
* pins the footer to the bottom of the flex-column body. Do not re-add
|
||||
* `justify-content: space-between` or a span margin — they came from a two-column footer this
|
||||
* theme does not have. */
|
||||
footer {
|
||||
margin-top: auto;
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
align-items: baseline;
|
||||
}
|
||||
|
||||
footer ul.breadcrumb {
|
||||
margin-inline-start: auto;
|
||||
}
|
||||
}
|
||||
@@ -1,62 +0,0 @@
|
||||
@layer base {
|
||||
/* the modal overlay ui.js shows for config diffs, uploads and login */
|
||||
#modal_overlay {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
background: var(--fs-scrim);
|
||||
z-index: var(--fs-z-overlay);
|
||||
overflow: auto;
|
||||
transition: opacity var(--fs-dur);
|
||||
opacity: 0;
|
||||
visibility: hidden;
|
||||
}
|
||||
|
||||
.modal {
|
||||
width: 90%;
|
||||
margin: var(--fs-space-6) auto; /* clear of the sticky bar, and centred */
|
||||
min-height: var(--fs-control-h);
|
||||
max-width: 600px;
|
||||
border-radius: var(--fs-radius-lg); /* a modal is a card — the large radius, like every panel */
|
||||
background: var(--fs-panel);
|
||||
border: var(--fs-hairline);
|
||||
padding: var(--fs-card-pad);
|
||||
min-width: 270px;
|
||||
}
|
||||
|
||||
/* .modal > * / pre / textarea: theme/70-modal.css — absorbed with the heading ramp and the
|
||||
* generic field box, both of which would out-layer a restatement here. */
|
||||
|
||||
.modal > .button-row {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.modal > .button-row > button:not(:last-of-type) {
|
||||
margin-inline-end: var(--fs-space-2);
|
||||
}
|
||||
|
||||
.modal > .button-row > button:first-of-type {
|
||||
margin-inline-end: auto;
|
||||
}
|
||||
|
||||
body.modal-overlay-active {
|
||||
overflow: hidden;
|
||||
height: 100svh; /* not vh (crops under URL bar) or dvh (re-lays out each frame it slides) */
|
||||
}
|
||||
|
||||
body.modal-overlay-active #modal_overlay {
|
||||
inset-inline: 0;
|
||||
opacity: 1;
|
||||
visibility: visible;
|
||||
}
|
||||
|
||||
.btn .close, .alert-message .close {
|
||||
font-family: var(--fs-font-sans);
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
/* Colour-word block absorbed, do not re-add: .btn.<word> theme/55-buttons.css,
|
||||
* .alert-message/.cbi-tooltip.<word> theme/35-alerts.css. */
|
||||
}
|
||||
@@ -1,72 +0,0 @@
|
||||
@layer base {
|
||||
/* .btn/.cbi-button are interchangeable in LuCI markup. Paint (box, fill, radius) is
|
||||
* theme/55-buttons.css's; --on-color stays here because base/95-luci.css masks the spinner
|
||||
* glyph with it, so it must track the variant regardless of layer. */
|
||||
.btn,
|
||||
.cbi-button {
|
||||
--on-color: var(--fs-text);
|
||||
|
||||
cursor: pointer;
|
||||
font-size: var(--fs-type);
|
||||
white-space: pre;
|
||||
}
|
||||
|
||||
/* border-color: theme/60-inputs.css — absorbed. */
|
||||
.cbi-input-invalid,
|
||||
.cbi-input-invalid.cbi-dropdown:not(.btn):not(.cbi-button),
|
||||
.cbi-input-invalid.cbi-dropdown:not([open]) > ul > li,
|
||||
.cbi-value-error input {
|
||||
color: var(--fs-danger);
|
||||
}
|
||||
|
||||
/* --on-color only: docs/css.md "Buttons: --on-color survives the absorption into theme". */
|
||||
.cbi-button-positive,
|
||||
.cbi-button-fieldadd,
|
||||
.cbi-button-add,
|
||||
.cbi-button-save {
|
||||
--on-color: var(--fs-good);
|
||||
}
|
||||
|
||||
.btn.primary,
|
||||
.cbi-button-action,
|
||||
.cbi-button-apply,
|
||||
.cbi-button-reload,
|
||||
.cbi-button-edit {
|
||||
--on-color: var(--fs-accent);
|
||||
}
|
||||
|
||||
.cbi-button-negative,
|
||||
.cbi-section-remove .cbi-button,
|
||||
.cbi-button-reset,
|
||||
.cbi-button-remove {
|
||||
--on-color: var(--fs-danger);
|
||||
}
|
||||
|
||||
/* Not a clearfix — flex container, nothing floats: docs/css.md "Buttons: the flex clearfix,
|
||||
* and why float is dead on .cbi-page-actions". */
|
||||
.cbi-page-actions::after {
|
||||
display: table;
|
||||
content: "";
|
||||
clear: both;
|
||||
}
|
||||
|
||||
.cbi-page-actions > :not([method="post"]):not(.cbi-button-apply):not(.cbi-button-negative):not(.cbi-button-save):not(.cbi-button-reset) {
|
||||
margin-inline-end: var(--fs-space-1);
|
||||
}
|
||||
|
||||
/* fills/borders absorbed (theme/55-buttons.css); Save/Apply's --on-color is re-set there too
|
||||
* regardless of layer, so no base copy is needed for that pair. */
|
||||
.btn.primary,
|
||||
.cbi-button-action.important,
|
||||
.cbi-section-actions .cbi-button-edit {
|
||||
--on-color: var(--fs-on-accent);
|
||||
}
|
||||
|
||||
.cbi-button-positive.important {
|
||||
--on-color: var(--fs-on-good);
|
||||
}
|
||||
|
||||
.cbi-button-negative.important {
|
||||
--on-color: var(--fs-on-danger);
|
||||
}
|
||||
}
|
||||
@@ -1,119 +0,0 @@
|
||||
@layer base {
|
||||
/* small widgets any luci-app-* can drop anywhere: tooltip, progress bar, zone badge, close
|
||||
* cross, alert box, .label pill. Arrow: content, not paint — colour is theme/45-misc.css. */
|
||||
.cbi-title-ref::after {
|
||||
content: "➙";
|
||||
}
|
||||
|
||||
.cbi-tooltip-container {
|
||||
cursor: help;
|
||||
}
|
||||
|
||||
.cbi-tooltip {
|
||||
position: absolute;
|
||||
z-index: var(--fs-z-tooltip);
|
||||
/* docs/css.md "Widgets: the tooltip parks past the unreachable edge, and the close cross
|
||||
* needs the same mirror" — a physical `left` grows a 10000px RTL scrollbar. */
|
||||
inset-inline-start: -10000px;
|
||||
border-radius: var(--fs-radius-sm);
|
||||
background: var(--fs-panel);
|
||||
/* pre-wrap, never pre: docs/css.md "Typography and widgets: an opaque token needs
|
||||
* overflow-wrap: anywhere, a tooltip needs pre-wrap". */
|
||||
white-space: pre-wrap;
|
||||
padding: var(--fs-space-0-5) var(--fs-space-1);
|
||||
opacity: 0;
|
||||
transition: opacity var(--fs-dur-fade);
|
||||
}
|
||||
|
||||
.cbi-tooltip-container:hover .cbi-tooltip:not(:empty) {
|
||||
inset-inline-start: auto;
|
||||
opacity: 1;
|
||||
transition: opacity var(--fs-dur-fade);
|
||||
}
|
||||
|
||||
/* .cbi-progressbar paint: theme/25-progressbar.css — absorbed. The fill's starting
|
||||
* width stays: the poll JS animates the inline width up from it. */
|
||||
.cbi-progressbar > div {
|
||||
width: 0%;
|
||||
}
|
||||
|
||||
.zonebadge .cbi-tooltip {
|
||||
padding: 1px;
|
||||
background: inherit;
|
||||
margin: -1.6em 0 0 -5px;
|
||||
border-radius: var(--fs-radius-sm);
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.zonebadge .cbi-tooltip > * {
|
||||
margin: 1px;
|
||||
z-index: var(--fs-z-raise);
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.zone-forwards {
|
||||
display: flex;
|
||||
flex-wrap: nowrap;
|
||||
}
|
||||
|
||||
.zone-forwards > * {
|
||||
flex: 1 1 40%;
|
||||
padding: 1px;
|
||||
text-wrap: auto;
|
||||
}
|
||||
|
||||
.zone-forwards > span {
|
||||
flex-basis: 10%;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.zone-forwards .zone-src,
|
||||
.zone-forwards .zone-dest {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.btn.disabled,
|
||||
.btn[disabled] {
|
||||
cursor: default;
|
||||
opacity: var(--fs-disabled-opacity);
|
||||
}
|
||||
|
||||
/* .btn.large/.btn.small, close cross colour/shadow/opacities: theme/55-buttons.css and
|
||||
* theme/70-modal.css — absorbed. Left here: geometry only. */
|
||||
/* Physical, hand-mirrored: docs/css.md "Widgets: the tooltip parks past the unreachable edge,
|
||||
* and the close cross needs the same mirror" — appears in every modal and .alert-message. */
|
||||
.close {
|
||||
float: right;
|
||||
font-size: var(--fs-type-xl);
|
||||
font-weight: var(--fs-weight-bold);
|
||||
line-height: 1;
|
||||
}
|
||||
:root[dir="rtl"] .close { float: left; }
|
||||
|
||||
.close:hover {
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
/* alert paint: theme/35-alerts.css — absorbed. Left: geometry and the white-space reset. */
|
||||
.alert-message {
|
||||
position: relative;
|
||||
margin-bottom: var(--fs-space-2);
|
||||
white-space: unset;
|
||||
}
|
||||
|
||||
.alert-message .close {
|
||||
margin-top: 1px;
|
||||
}
|
||||
|
||||
.alert-message button {
|
||||
margin: var(--fs-space-1) 0;
|
||||
}
|
||||
|
||||
/* .label pill and the indicator pill: theme/35-alerts.css / theme/20-shell.css — absorbed. */
|
||||
header [data-indicator] {
|
||||
white-space: nowrap;
|
||||
text-shadow: none;
|
||||
margin: var(--fs-space-1) 0 var(--fs-space-1) var(--fs-space-1);
|
||||
}
|
||||
}
|
||||
@@ -1,630 +0,0 @@
|
||||
@layer base {
|
||||
/* Widgets with no generic equivalent: CBI section tables, interface/zone badges,
|
||||
* the uci change dialog, spinners, the file browser, and a few rules luci-mod-*
|
||||
* depends on. */
|
||||
/* .hidden: theme/60-inputs.css (with the .cbi-* variants LuCI emits) */
|
||||
form.inline { display: inline; margin-bottom: 0; }
|
||||
|
||||
#modemenu li:last-child span.divider { display: none }
|
||||
|
||||
/* #syslog: absorbed into pages/70-syslog.css — in base it LOST to the theme layer's generic
|
||||
* form-field box (a layer beats specificity), so the log rendered as a 210px column. */
|
||||
|
||||
/* :not(.placeholder): the empty-table row gets no hover cue (theme/30-tables.css spares it),
|
||||
* but this rule paints the CELL and a placeholder row carrying no `.cbi-section-table-row`
|
||||
* class escapes that guard — measured on UPnP (issue #15). */
|
||||
.cbi-section-table .tr:not(.placeholder):hover .td,
|
||||
.cbi-section-table .tr:not(.placeholder):hover .th,
|
||||
.cbi-section-table .tr:not(.placeholder):hover::before {
|
||||
background-color: var(--fs-panel2);
|
||||
}
|
||||
|
||||
.cbi-section-table .tr.cbi-section-table-descr .th {
|
||||
font-weight: var(--fs-weight-normal);
|
||||
}
|
||||
|
||||
.cbi-section-table-titles {
|
||||
font-weight: var(--fs-weight-bold);
|
||||
}
|
||||
|
||||
/* GridSection name column, discriminated by markup not release: docs/css.md "base/95-luci.css:
|
||||
* the GridSection name column, read from the markup, not the release" (issue #9). Column
|
||||
* geometry lives in theme/30-tables.css, next to the real cells it lines up with. */
|
||||
.cbi-section-table-titles.named[data-title]::before,
|
||||
.cbi-section-table-titles.named[data-title] ~ .cbi-section-table-descr.named::before {
|
||||
content: attr(data-title) " ";
|
||||
display: table-cell;
|
||||
vertical-align: middle;
|
||||
}
|
||||
.cbi-section-table-row[data-title]:not(:has(> .cbi-section-table-titles))::before {
|
||||
content: attr(data-title) " ";
|
||||
display: table-cell;
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
.left { text-align: left !important; }
|
||||
.right { text-align: right !important; }
|
||||
.center { text-align: center !important; }
|
||||
.top { vertical-align: top !important; }
|
||||
.middle { vertical-align: middle !important; }
|
||||
.bottom { vertical-align: bottom !important; }
|
||||
|
||||
table table td,
|
||||
.cbi-value-field table td {
|
||||
border: none;
|
||||
}
|
||||
|
||||
/* cell-filling field widths (+ numeric-cell refinement): theme/30-tables.css —
|
||||
* absorbed with the generic field box, which would out-layer them here */
|
||||
|
||||
.table.cbi-section-table .td.cbi-section-table-cell {
|
||||
white-space: nowrap;
|
||||
text-align: end;
|
||||
}
|
||||
|
||||
.td.cbi-section-actions {
|
||||
text-align: end;
|
||||
vertical-align: middle;
|
||||
width: 15%;
|
||||
}
|
||||
|
||||
.td.cbi-section-actions > * {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.td.cbi-section-actions > :not(.cbi-dropdown) > *,
|
||||
.td.cbi-section-actions > * > form > * {
|
||||
flex: 1 1 4em;
|
||||
margin: 0 var(--fs-space-1); /* WCAG 2.2 SC 2.5.8: 1px apart, targets sat inside each other */
|
||||
}
|
||||
|
||||
.td.cbi-section-actions > * > form {
|
||||
display: inline-flex;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.table.valign-middle .td {
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
.cbi-rowstyle-2,
|
||||
.tr.table-titles,
|
||||
.tr.cbi-section-table-titles {
|
||||
background: var(--fs-panel2);
|
||||
}
|
||||
|
||||
th[data-sortable-row] { cursor: pointer; }
|
||||
th[data-sort-direction="asc"]::after { content: "\a0\25b2"; }
|
||||
th[data-sort-direction="desc"]::after { content: "\a0\25bc"; }
|
||||
|
||||
/* Indent and the ::before glyph's own `left` are ONE relationship (both 1.25em, stated twice
|
||||
* before) — now one local. */
|
||||
.cbi-value-description {
|
||||
--help-indent: 1.25em;
|
||||
|
||||
margin: var(--fs-space-1) 0 0 var(--help-indent);
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.cbi-value-description:not(:empty)::before {
|
||||
/* OURS, drawn as a stroke like --spinner-icon below and --fs-icon-refresh in 02-tokens.css
|
||||
* (fill:none, round caps, 24 grid) — not the solid filled path luci-theme-bootstrap used. */
|
||||
--help-icon: url("data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='2.2' stroke-linecap='round' stroke-linejoin='round'><circle cx='12' cy='12' r='9.4'/><path d='M8.9 9.4a3.2 3.2 0 1 1 3.9 3.6v1.4'/><path d='M12.8 17.6h-.02'/></svg>");
|
||||
|
||||
position: absolute;
|
||||
inset-inline-start: calc(var(--help-indent) * -1);
|
||||
content: "\a0";
|
||||
display: inline-block;
|
||||
width: 1em;
|
||||
height: 1em;
|
||||
margin-inline-end: var(--fs-space-1);
|
||||
background: var(--fs-accent);
|
||||
mask-image: var(--help-icon);
|
||||
mask-size: cover;
|
||||
-webkit-mask-image: var(--help-icon);
|
||||
-webkit-mask-size: cover;
|
||||
}
|
||||
|
||||
/* tint (6%-not-12% reasoning): theme/30-tables.css — absorbed */
|
||||
.cbi-section-error {
|
||||
border: 1px solid var(--fs-danger);
|
||||
border-radius: var(--fs-radius);
|
||||
padding: var(--fs-space-2);
|
||||
margin-bottom: var(--fs-space-4);
|
||||
}
|
||||
|
||||
.cbi-section-error ul { margin: 0 0 0 var(--fs-space-5); }
|
||||
|
||||
.cbi-section-error ul li {
|
||||
font-weight: var(--fs-weight-bold); /* colour: theme/35-alerts.css — absorbed */
|
||||
}
|
||||
|
||||
.ifacebox {
|
||||
border: var(--fs-hairline);
|
||||
margin: 0 var(--fs-space-3);
|
||||
text-align: center;
|
||||
white-space: nowrap;
|
||||
border-radius: var(--fs-radius-sm);
|
||||
display: inline-flex;
|
||||
flex-direction: column;
|
||||
line-height: var(--fs-leading-tight); /* a plate of single-line labels, not prose */
|
||||
min-width: 100px;
|
||||
}
|
||||
|
||||
.ifacebox .ifacebox-head {
|
||||
border-bottom: var(--fs-hairline);
|
||||
/* inside the box's 1px border, so radius = the box's less that border */
|
||||
border-radius: calc(var(--fs-radius-sm) - 1px) calc(var(--fs-radius-sm) - 1px) 0 0;
|
||||
padding: var(--fs-space-0-5);
|
||||
}
|
||||
|
||||
/* !important fights luci-mod-network's inline zone-colour style=: docs/css.md "base/95-luci.css:
|
||||
* the dark-mode zone tint, and why it has no grey fallback". */
|
||||
[data-darkmode="true"] .zonebadge[style],
|
||||
[data-darkmode="true"] .ifacebox-head[style] {
|
||||
background: linear-gradient(rgba(var(--zone-color-rgb), .4), rgba(var(--zone-color-rgb), .3)) !important;
|
||||
}
|
||||
|
||||
/* generalised once: docs/css.md "base/95-luci.css: the overflow-wrap fix generalised once, not
|
||||
* restated per page". */
|
||||
.ifacebox .ifacebox-body {
|
||||
padding: var(--fs-space-1);
|
||||
white-space: normal;
|
||||
overflow-wrap: anywhere;
|
||||
}
|
||||
|
||||
#cbi-network-interface .ifacebox img {
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
}
|
||||
|
||||
.ifacebadge {
|
||||
display: inline-block;
|
||||
flex-direction: row;
|
||||
border: var(--fs-hairline);
|
||||
padding: var(--fs-space-0-5);
|
||||
color: var(--fs-text);
|
||||
border-radius: var(--fs-radius-sm);
|
||||
cursor: default;
|
||||
line-height: var(--fs-leading-tight);
|
||||
}
|
||||
|
||||
.ifacebadge img {
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
.ifacebadge-active {
|
||||
font-weight: var(--fs-weight-bold);
|
||||
}
|
||||
|
||||
.network-status-table {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.network-status-table .ifacebox {
|
||||
margin: var(--fs-space-2);
|
||||
flex-grow: 1;
|
||||
}
|
||||
|
||||
.network-status-table .ifacebox-body {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: 100%;
|
||||
text-align: start;
|
||||
}
|
||||
|
||||
/* same fix as .ifacebox-head above, same doc section (RU caption "Подключение IPv4
|
||||
* (внешняя сеть)" measured 41.3px past #view at 320px/Large, 0px in English). */
|
||||
.network-status-table .ifacebox-head {
|
||||
white-space: normal;
|
||||
overflow-wrap: anywhere;
|
||||
}
|
||||
|
||||
.network-status-table .ifacebox-body > * {
|
||||
margin: var(--fs-space-1);
|
||||
}
|
||||
|
||||
.network-status-table .ifacebox-body > span {
|
||||
flex: 10 10 auto;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.network-status-table .ifacebox-body > div {
|
||||
margin: -.125em;
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
#dsl_status_table .ifacebox-body span > strong {
|
||||
display: inline-block;
|
||||
min-width: 35%;
|
||||
}
|
||||
|
||||
.ifacebadge.large,
|
||||
.network-status-table .ifacebox-body .ifacebadge {
|
||||
flex: 1;
|
||||
padding: var(--fs-space-1);
|
||||
min-width: 220px;
|
||||
margin: var(--fs-space-1);
|
||||
}
|
||||
|
||||
.network-status-table .ifacebox-body .ifacebadge {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.ifacebadge.large {
|
||||
display: inline-flex;
|
||||
}
|
||||
|
||||
.network-status-table .ifacebox-body .ifacebadge > span {
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.ifacebadge > *,
|
||||
.ifacebadge.large > * {
|
||||
margin: 0 var(--fs-space-1);
|
||||
}
|
||||
|
||||
.zonebadge {
|
||||
padding: var(--fs-space-0-5);
|
||||
border-radius: var(--fs-radius-sm);
|
||||
display: inline-block;
|
||||
white-space: nowrap;
|
||||
color: var(--fs-text);
|
||||
}
|
||||
|
||||
.zonebadge > em,
|
||||
.zonebadge > strong {
|
||||
margin: 0 var(--fs-space-1);
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
/* .zonebadge input width: theme/30-tables.css — absorbed with the generic box */
|
||||
|
||||
.zonebadge > .ifacebadge {
|
||||
margin-inline-start: var(--fs-space-0-5);
|
||||
}
|
||||
|
||||
.zonebadge-empty {
|
||||
border: 1px dashed var(--fs-border); /* width/style only — colour + ink: theme/45-misc.css */
|
||||
font-style: italic;
|
||||
font-size: smaller;
|
||||
}
|
||||
|
||||
div.cbi-value var,
|
||||
.td.cbi-value-field var {
|
||||
font-style: italic; /* colour: theme/60-inputs.css — absorbed */
|
||||
}
|
||||
|
||||
div.cbi-value var[data-tooltip],
|
||||
.td.cbi-value-field var[data-tooltip],
|
||||
div.cbi-value var.cbi-tooltip-container,
|
||||
.td.cbi-value-field var.cbi-tooltip-container {
|
||||
cursor: help;
|
||||
border-bottom: 1px dotted var(--fs-accent);
|
||||
}
|
||||
|
||||
div.cbi-value var.cbi-tooltip-container,
|
||||
.td.cbi-value-field var.cbi-tooltip-container .cbi-tooltip {
|
||||
font-style: normal;
|
||||
white-space: normal;
|
||||
color: var(--fs-text);
|
||||
}
|
||||
|
||||
#modal_overlay > .modal.uci-dialog,
|
||||
#modal_overlay > .modal.cbi-modal {
|
||||
max-width: 900px;
|
||||
}
|
||||
|
||||
#modal_overlay > .modal.login > button.important {
|
||||
/* Coverage vocabulary, not a live control: no luci-base call site passes ui.showModal a
|
||||
* `login` class, and this theme's own login is a server-rendered form. Inherited from
|
||||
* luci-theme-bootstrap so a foreign app emitting it still gets a themed result. */
|
||||
font-size: var(--fs-type-lg);
|
||||
margin-top: var(--fs-space-2);
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.uci-change-list {
|
||||
line-height: var(--fs-leading); /* prose leading for a list of changed settings, not a hand 170% */
|
||||
white-space: pre;
|
||||
}
|
||||
|
||||
.uci-dialog div > del,
|
||||
.uci-dialog div > ins,
|
||||
.uci-dialog div > var {
|
||||
margin-bottom: var(--fs-space-0-5);
|
||||
border: var(--fs-hairline);
|
||||
line-height: var(--fs-leading-tight); /* one row of single-line code takes the tight step */
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
padding: var(--fs-space-0-5);
|
||||
position: relative;
|
||||
background-color: var(--fs-panel2);
|
||||
}
|
||||
|
||||
.uci-dialog div > ins {
|
||||
background-color: var(--fs-good-fill);
|
||||
border-color: var(--fs-good-line);
|
||||
}
|
||||
|
||||
.uci-dialog div > del {
|
||||
background-color: var(--fs-danger-fill);
|
||||
border-color: var(--fs-danger-line);
|
||||
}
|
||||
|
||||
.uci-dialog var > ins {
|
||||
background-color: var(--fs-good-fill);
|
||||
}
|
||||
|
||||
.uci-dialog var > del {
|
||||
background-color: var(--fs-danger-fill);
|
||||
}
|
||||
|
||||
.uci-change-list > var > * {
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.uci-dialog del,
|
||||
.uci-dialog ins,
|
||||
.uci-dialog var {
|
||||
text-decoration: none;
|
||||
font-family: var(--fs-font-mono);
|
||||
font-style: normal;
|
||||
color: var(--fs-text);
|
||||
display: block;
|
||||
}
|
||||
|
||||
.uci-change-legend {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.uci-change-legend-label {
|
||||
flex-basis: 150px;
|
||||
margin: var(--fs-space-0-5);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.uci-change-legend-label > ins,
|
||||
.uci-change-legend-label > del,
|
||||
.uci-change-legend-label > var {
|
||||
margin-inline-end: var(--fs-space-1);
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.uci-change-legend-label > * > * {
|
||||
flex-basis: 100%;
|
||||
padding: 1px;
|
||||
}
|
||||
|
||||
#applyreboot-section {
|
||||
line-height: 300%;
|
||||
}
|
||||
|
||||
@keyframes flash {
|
||||
0% { opacity: 1; }
|
||||
50% { opacity: .5; }
|
||||
100% { opacity: 1; }
|
||||
}
|
||||
|
||||
.flash {
|
||||
animation: flash var(--fs-dur-fade);
|
||||
}
|
||||
|
||||
#view > div.spinning:first-child {
|
||||
display: table;
|
||||
margin: 15vh auto;
|
||||
}
|
||||
|
||||
/* Rotate in CSS, never a SMIL <animateTransform> inside the data-URI: that runs in the SVG's
|
||||
* own document, out of reach of the page's `prefers-reduced-motion`. In-flow glyph, not
|
||||
* absolutely positioned: docs/css.md "base/95-luci.css: the spinner glyph sits in the flow, on
|
||||
* purpose" (issues #15, #22). */
|
||||
.spinning::before {
|
||||
--spinner-icon: url("data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 20 20' stroke='%23000' stroke-width='3' fill='none'><circle cx='10' cy='10' r='8' stroke-opacity='.5'/><path d='M10 2c4 0 8 4 8 8'/></svg>");
|
||||
|
||||
display: inline-block;
|
||||
flex: 0 0 auto;
|
||||
vertical-align: middle;
|
||||
margin-inline-end: var(--fs-space-1);
|
||||
width: var(--fs-space-5); /* one reader, so the size lives here rather than behind a token */
|
||||
height: var(--fs-space-5);
|
||||
content: " ";
|
||||
background: var(--on-color, #000);
|
||||
mask: var(--spinner-icon) center/cover no-repeat;
|
||||
-webkit-mask: var(--spinner-icon) center/cover no-repeat;
|
||||
/* two deliberate exceptions to the motion scale (02-tokens.css): a LOOP, not a state change
|
||||
* — 1s is a spinner period not one of the four durations, and `linear` avoids the stutter
|
||||
* `ease` gives an infinite rotation at each turn's seam. */
|
||||
animation: fs-spin 1s linear infinite;
|
||||
}
|
||||
|
||||
/* One user (.spinning::before above) — the rail's poll glyph is deliberately STATIC
|
||||
* (theme/20-shell.css). Unscoped: keyframe names resolve document-wide, a luci-app-* may ride it. */
|
||||
@keyframes fs-spin { to { transform: rotate(360deg); } }
|
||||
|
||||
[data-darkmode="true"] .spinning::before {
|
||||
background: var(--on-color, #fff);
|
||||
}
|
||||
|
||||
[data-tab-title] {
|
||||
height: 0;
|
||||
opacity: 0;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
/* the revealed state of both collapsibles — tab pane and file browser — in one rule */
|
||||
[data-tab-active="true"],
|
||||
.cbi-filebrowser.open {
|
||||
opacity: 1;
|
||||
height: auto;
|
||||
overflow: visible;
|
||||
transition: opacity var(--fs-dur-fade);
|
||||
}
|
||||
|
||||
/* min(210px, 100%) floor: docs/css.md "base/95-luci.css: the file browser's floor, and the row
|
||||
* that is not text". */
|
||||
.cbi-filebrowser {
|
||||
min-width: min(210px, 100%);
|
||||
max-width: 100%;
|
||||
border: var(--fs-hairline); /* width/style only; radius: theme/60-inputs.css — absorbed */
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
opacity: 0;
|
||||
height: 0;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
/* padding: theme/60-inputs.css — absorbed. Zero side padding here let a 36px themed button
|
||||
* touch this element's own clip box and lose its corners. */
|
||||
.cbi-filebrowser > * {
|
||||
max-width: 100%;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
margin: .25em .25em 0 .25em;
|
||||
white-space: nowrap;
|
||||
border-bottom: var(--fs-hairline);
|
||||
}
|
||||
|
||||
.cbi-filebrowser .cbi-button-positive {
|
||||
margin-inline-end: .25em;
|
||||
}
|
||||
|
||||
/* .right is not one line of text (upload strip + Create/Cancel), so it opts out of the
|
||||
* nowrap+ellipsis assumption above: docs/css.md "base/95-luci.css: the file browser's floor,
|
||||
* and the row that is not text". */
|
||||
.cbi-filebrowser > .right {
|
||||
white-space: normal;
|
||||
overflow: visible;
|
||||
}
|
||||
|
||||
.cbi-filebrowser > div {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
.cbi-filebrowser > ul > li {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
}
|
||||
|
||||
/* row hover surface + flex sizing: theme/60-inputs.css — absorbed (this file's old
|
||||
* name:10/actions:3 split fitted base's ~20px buttons, not the theme's 36px ones). */
|
||||
.cbi-filebrowser > ul > li > div:first-child {
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.cbi-filebrowser > ul > li > div:last-child {
|
||||
text-align: end;
|
||||
}
|
||||
|
||||
.cbi-filebrowser > ul > li > div:last-child > button {
|
||||
padding: .125em .25em;
|
||||
}
|
||||
|
||||
.cbi-filebrowser .upload {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
flex-wrap: wrap;
|
||||
margin: 0 -.125em .25em -.125em;
|
||||
padding: 0 0 .125em 0;
|
||||
border-bottom: var(--fs-hairline);
|
||||
}
|
||||
|
||||
.cbi-filebrowser .upload > * {
|
||||
margin: .125em;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.cbi-filebrowser .upload > .btn {
|
||||
flex-basis: 60px;
|
||||
}
|
||||
|
||||
.cbi-filebrowser .upload > div {
|
||||
flex: 10;
|
||||
min-width: 150px;
|
||||
}
|
||||
|
||||
/* the upload input's width: theme/60-inputs.css — absorbed with the generic box */
|
||||
|
||||
@keyframes fade-in {
|
||||
0% { opacity: 0; }
|
||||
100% { opacity: 1; }
|
||||
}
|
||||
|
||||
@keyframes fade-out {
|
||||
0% { opacity: 1; }
|
||||
100% { opacity: 0; }
|
||||
}
|
||||
|
||||
/* `fade-in` stays although nothing here rides it: an upstream animation NAME a luci-app-* may
|
||||
* ride (coverage contract, docs/conventions.md). The theme's own `.fade-in` CLASS rule is gone
|
||||
* on purpose — luci-mod-status re-marks it on every poll tick, replaying anything hung there.
|
||||
* `fs-fade` is one-shot instead, played once by the rail flyout and the search overlay. */
|
||||
@keyframes fs-fade {
|
||||
from { opacity: 0; transform: translateY(4px); }
|
||||
to { opacity: 1; transform: none; }
|
||||
}
|
||||
|
||||
.fade-out {
|
||||
animation: fade-out var(--fs-dur-fade);
|
||||
}
|
||||
|
||||
.assoclist .ifacebadge {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
white-space: normal;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.assoclist .ifacebadge > img {
|
||||
margin: var(--fs-space-1);
|
||||
}
|
||||
|
||||
.assoclist .td:nth-of-type(3),
|
||||
.assoclist .td:nth-of-type(5) {
|
||||
width: 25%;
|
||||
}
|
||||
|
||||
.assoclist .td:nth-of-type(6) button {
|
||||
word-break: normal;
|
||||
}
|
||||
|
||||
/* PNGs on a white ground, inverted for dark mode: docs/css.md "base/95-luci.css: Statistics
|
||||
* graphs and Realtime SVGs are art drawn for a white ground" — 180deg is exact for the three
|
||||
* primaries, not tuned further. */
|
||||
[data-darkmode="true"] [data-page="admin-statistics-graphs"] [data-plugin] img {
|
||||
filter: invert(100%) hue-rotate(180deg);
|
||||
}
|
||||
|
||||
/* Realtime SVGs carry inline style= for a black ground; only an author !important beats it:
|
||||
* same doc section. */
|
||||
#view div[style] > svg {
|
||||
background-color: var(--fs-panel);
|
||||
}
|
||||
|
||||
#view div[style] > svg line[style] {
|
||||
stroke: var(--fs-text) !important;
|
||||
}
|
||||
|
||||
/* child combinator, not descendant: reaches only the axis labels, not Channel Analysis's
|
||||
* per-BSSID curve labels nested inside a <g> — same doc section. */
|
||||
#view div[style] > svg > text[style] {
|
||||
fill: var(--fs-text) !important;
|
||||
text-shadow: none !important;
|
||||
font-weight: var(--fs-weight-bold);
|
||||
}
|
||||
|
||||
/* Overview Hide/Show toggle pills: theme/35-alerts.css (.cbi-title .label) +
|
||||
* pages/20-overview.css (chevron variant) — absorbed. */
|
||||
}
|
||||
Reference in new issue
Block a user