Files
small-packages/luci-theme-footstrap/styles/theme/60-inputs.css
T
2026-10-10 11:56:38 +08:00

521 lines
27 KiB
CSS
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
@layer theme {
/* baseline, not center: a taller field (help sentence under the control) would push a
* centered label off the control's first line */
.cbi-value { justify-content: space-between; gap: var(--fs-space-4); padding: var(--fs-space-2) 0; margin: 0; border-bottom: var(--fs-hairline); }
/* stacked controls need a gap, a raw <br> collapses it; password-reveal excluded, must stay seamless */
.cbi-value-field > .control-group { margin-top: var(--fs-space-2); }
.cbi-value-field > .control-group:not(:has(.cbi-input-password)) {
display: inline-flex; flex-wrap: wrap; gap: var(--fs-space-2); align-items: center;
}
/* hairline separates a row from the NEXT one, so it drops whenever none follows, not only at the section's end */
.cbi-value:not(:has(+ .cbi-value)) { border-bottom: 0; }
/* .cbi-checkbox has no text baseline (synthesised at its bottom edge, floats ~5px above the label) — centred, except with a help sentence under it (see the baseline note above) */
.cbi-value:not(:has(.cbi-value-description)):has(> .cbi-value-field > .cbi-checkbox, > .cbi-value-field > div > .cbi-radio) {
align-items: center;
}
/* absorbed from base; ONLY the transition — typography stays in base (theme/45-misc.css). border-COLOR not the shorthand, which also animates width */
input, button {
transition: var(--fs-field-transition);
}
.cbi-value-title {
color: var(--fs-dim); font-size: var(--fs-type); font-weight: var(--fs-weight);
/* stated not inherited: this is TEXT, the control rule hands labels line-height: normal (measured 18px -> normal without this) */
line-height: var(--fs-leading);
}
/* mono is for VALUES: buttons, the help sentence and `<strong>` LABELS excluded — a label
* fed to mono also goes bold, 227 elements/20 KB of face for nothing. theme/45-misc.css's
* label rule must ASSIGN sans; excluding `<strong>` here alone still inherits mono */
.cbi-value-field,
.cbi-value-field *:where(:not(.btn, .cbi-button, .cbi-value-description)) { font-family: var(--fs-font-mono); }
.cbi-value-description { font-family: var(--fs-font-sans); text-wrap: pretty; }
/* not base's fixed 210px: mono + padded --fs-space-3, 210px clipped a URL mid-domain (issue #5); elastic, capped to the dynlist's ceiling */
.cbi-value-field input[type="text"],
.cbi-value-field input[type="url"],
.cbi-value-field input[type="search"] { width: 100%; max-width: var(--fs-field-max); }
/* base assumes ONE title+field pair; stack full-width on a SECOND title (not a second field — a runtime-appended field would re-stack under the user's finger, measured: a button moved 31px) */
.cbi-value:has(.cbi-value-title ~ .cbi-value-title) > .cbi-value-title,
.cbi-value:has(.cbi-value-title ~ .cbi-value-title) > .cbi-value-field {
flex: 1 1 100%;
margin-inline-start: 0;
text-align: start;
}
.td > input[type="text"],
.td > input[type="password"],
.td > select,
.td > .cbi-dropdown:not(.btn):not(.cbi-button),
.cbi-dynlist > .add-item > .cbi-dropdown { width: 100%; }
.cbi-section-create > * > input { width: 100%; }
/* --- live components that bypassed the theme --- */
/* a CBI row is <div class="cbi-value"> on a form, <td class="cbi-value-field"> in a table — same <var>, theme had claimed only the table half */
div.cbi-value var, .td.cbi-value-field var { color: var(--fs-accent); }
.td.cbi-value-field var.cbi-tooltip-container { border-bottom-color: var(--fs-accent); }
.cbi-filebrowser { border-color: var(--fs-border); background: var(--fs-panel); border-radius: var(--fs-radius-lg); }
.cbi-filebrowser > ul > li:hover { background: var(--fs-panel2); }
.cbi-filebrowser .upload { border-top-color: var(--fs-border); }
/* base sizes a row by proportion for its own ~20px buttons; this theme's 36px button (3 per
* row) does not fit at 23% and `overflow: hidden` clips the corners — size actions to content,
* let the name ellipsize, wrap when that is not enough */
.cbi-filebrowser > * { border-bottom-color: var(--fs-border); padding: var(--fs-space-1-5) var(--fs-space-2); }
.cbi-filebrowser > ul { display: flex; flex-direction: column; gap: var(--fs-space-1); }
.cbi-filebrowser > ul > li { align-items: center; flex-wrap: wrap; gap: var(--fs-space-2); }
.cbi-filebrowser > ul > li > div:first-child { flex: 1 1 auto; min-width: 0; }
/* `0 1 auto`: may wrap but not shrink below min-content, or it sticks out (measured 23px past the box at 320px before min-width: 0) */
.cbi-filebrowser > ul > li > div:last-child {
flex: 0 1 auto; min-width: 0;
display: flex; flex-wrap: wrap; gap: var(--fs-space-2); justify-content: flex-end;
}
.cbi-filebrowser > ul > li > div:last-child > button { margin: 0; }
.cbi-filebrowser .upload > div > input { width: 100%; }
/* Form widgets — toggles, file input. Applies to every CBI form. */
/* base zeroes checkbox margin; inside a .btn label only a literal space separated ✓ from text
* (issue #5.1). toggle switch below unaffected, its own margin:0 wins on a different ancestor */
.btn > input[type="checkbox"], .btn > input[type="radio"] { margin-inline-end: .5em; }
/* checkbox -> toggle switch, drawn on the INPUT — docs/css.md "Inputs: the toggle switch,
* drawn on the input" has the forum regression this replaced. density-scaled local tokens. */
/* overflow-wrap: anywhere for the one caption word too long even at full width (pseudo-loc: a
* 14-letter Russian compound grown to 20 pushed the row 24.6px past the field at 320px/Large) */
.cbi-checkbox { display: inline-flex; align-items: center; gap: var(--fs-space-2); overflow-wrap: anywhere; }
/* label[for] stays in the DOM but draws nothing: out of flow, out of the flex item count, so gap opens only on the switch input + an optional caption */
.cbi-checkbox > label[for] { position: absolute; opacity: 0; width: 0; height: 0; margin: 0; }
.cbi-checkbox > input[type="checkbox"] {
--sw-w: calc(40px * var(--fs-density-box));
--sw-h: calc(22px * var(--fs-density-box));
--sw-knob: calc(16px * var(--fs-density-box));
--sw-pad: calc(2px * var(--fs-density-box));
/* appearance/--bd-color/--fg-color come from the shared input[type=checkbox] rule below;
* flex-shrink: 0 keeps the pill at its width beside a caption (task 0145 has the numbers) */
display: inline-block; position: relative; margin: 0; flex-shrink: 0;
width: var(--sw-w); height: var(--sw-h); border-radius: var(--fs-radius-pill);
background: var(--fs-track); border: var(--fs-hairline);
cursor: pointer; transition: background var(--fs-dur), border-color var(--fs-dur); vertical-align: middle;
}
/* hit-target overlay to 44x30 (WCAG 2.5.8 floor 24px, pill is 22px); inset stays literal, not
* density-scaled — a floor that shrinks with Density is not one */
.cbi-checkbox > input[type="checkbox"]::before { inset: -4px -2px; }
.cbi-checkbox > input[type="checkbox"]::after {
top: var(--sw-pad); inset-inline-start: var(--sw-pad);
width: var(--sw-knob); height: var(--sw-knob); border-radius: 50%;
background: var(--fs-dim);
/* transform not `left`: left re-lays out the row every frame, translateX is composited */
transform: translateX(0);
transition: transform var(--fs-dur-move), background var(--fs-dur);
}
.cbi-checkbox > input[type="checkbox"]:checked { background: var(--fs-accent); border-color: transparent; }
/* derived so it cannot drift from the sizes above; -2px is the pill's two 1px borders (the knob sits against the padding box, so it has 2px less room — travel is 18px not 20) */
.cbi-checkbox > input[type="checkbox"]:checked::after {
transform: translateX(calc(var(--sw-w) - var(--sw-knob) - var(--sw-pad) * 2 - 2px));
background: var(--fs-on-accent);
}
/* the input IS the switch, ring goes here; -solo: checked switch already spends its border channel on accent, only the tinted shadow shows */
.cbi-checkbox > input[type="checkbox"]:focus-visible {
border-color: var(--fs-accent);
box-shadow: var(--fs-focus-ring-solo);
}
.cbi-checkbox > input[type="checkbox"]:disabled { opacity: var(--fs-disabled-opacity); cursor: not-allowed; }
/* raw checkbox/radio, absorbed from base: undoes the generic input box below. 16px box on the
* grid, mark inset FROM it. `.cbi-checkbox > input` also matches the switch's own input
* (unscoped, it wants this rule's appearance:none) — selectors that WOULD paint the switch
* exclude it individually below instead. */
input[type="checkbox"],
input[type="radio"] {
--bd-color: var(--fs-border);
--fg-color: var(--fs-text);
/* box tier not space tier: --fs-space-4 gave 16px at Normal but 10.4px at Compact (smaller than any control) and never grew at Large */
--box: calc(16px * var(--fs-density-box));
--mark-inset: 3px;
/* WCAG 2.5.8's 24px spacing floor holds only while the theme owns the layout; ELEMENT is
* 24px (drawing stays 16px, centred), max() so it never shrinks under --box at Large */
--hit: max(24px, var(--box));
appearance: none;
width: var(--hit);
height: var(--hit);
/* negative margin pulls the extra hit area back out of flow (without it, rows measured 42px -> 51px) */
margin: calc((var(--box) - var(--hit)) / 2);
color: var(--fg-color);
position: relative;
display: inline-block;
cursor: pointer;
background: none;
border: none;
/* padding/border-radius not reset: land on an appearance-none box whose pseudo-elements are absolute, invisible either way */
}
input[type="checkbox"]::before,
input[type="checkbox"]::after,
input[type="radio"]::before,
input[type="radio"]::after {
position: absolute;
content: "";
}
input[type="checkbox"]:not(:where(.cbi-checkbox > input))::before,
input[type="radio"]::before {
/* inset:0; margin:auto centres a fixed box the same in RTL as LTR; a translate pair would not */
inset: 0;
margin: auto;
width: var(--box);
height: var(--box);
background: linear-gradient(var(--fs-panel), var(--fs-panel2));
border: 1px solid var(--bd-color);
transition: var(--fs-field-transition);
}
input[type="checkbox"]::before { border-radius: 2px; }
input[type="radio"],
input[type="radio"]::before {
border-radius: 50%;
}
/* keyboard/click focus ring on the DRAWN box, the theme's one ring token */
input[type="checkbox"]:not(:where(.cbi-checkbox > input))::after,
input[type="radio"]::after {
/* mark inset from the DRAWING, which is centred in the target, not from the target's own edge */
inset: 0;
margin: auto;
width: calc(var(--box) - var(--mark-inset) * 2);
height: calc(var(--box) - var(--mark-inset) * 2);
}
input[type="checkbox"]:not(:where(.cbi-checkbox > input)):checked::after,
input[type="radio"]:checked::after {
/* OURS: stroked path (this theme's icon language), replacing a fill byte-identical to luci-theme-bootstrap's; mask keys on alpha so a stroke masks like a fill did */
--checkmark-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='3.2' stroke-linecap='round' stroke-linejoin='round'><path d='M4.5 12.5 9.5 17.5 19.5 6.5'/></svg>");
-webkit-mask: var(--checkmark-icon) center/cover no-repeat;
mask: var(--checkmark-icon) center/cover no-repeat;
background: var(--fg-color);
}
input[type="radio"]:checked:after {
/* radio dot filled, not ringed — a ring reads as "not selected" */
--checkmark-icon: url("data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'><circle cx='12' cy='12' r='6.5' fill='%23000'/></svg>");
}
input[type="checkbox"].cbi-input-invalid,
input[type="radio"].cbi-input-invalid {
--bd-color: var(--fs-danger);
--fg-color: var(--fs-danger);
}
/* generic field box, absorbed from base; bare input keeps the historical 210px fallback for types the list below does not name */
input {
display: inline-block;
width: 210px;
height: var(--fs-control-h);
padding: var(--fs-space-1) var(--fs-space-2);
background: var(--fs-panel);
color: var(--fs-text);
border: var(--fs-hairline);
border-radius: var(--fs-radius);
}
/* base's old #localtime{width:300px} media rule not moved here: the elastic field width above already out-layers it (measured 440px, not 300) */
input[type="file"] {
color: var(--fs-dim); font-size: var(--fs-type);
padding: initial;
border: initial;
line-height: initial;
box-shadow: none;
/* no !important: width:auto out-specifies the generic input{width:210px} alone */
width: auto;
}
/* native <select> chevron + theme; the data-URI lives in 03-palettes.css per palette/mode block since a URI cannot read var() */
select, select.cbi-input-select, .cbi-value-field select {
/* unprefixed only, floor clears it everywhere — docs/css.md "The browser floor" */
appearance: none;
background-color: var(--fs-panel2);
background-image: var(--fs-select-chevron);
/* right inset is the scale step, not a literal 12 — the .cbi-select shell below repeats the same one spelling */
background-repeat: no-repeat; background-size: 6px 6px;
background-position: right calc(var(--fs-space-3) - 1px) center, right calc(var(--fs-space-3) + 5px) center;
padding: var(--fs-space-2) var(--fs-space-8) var(--fs-space-2) var(--fs-space-3);
border: var(--fs-hairline); border-radius: var(--fs-radius);
color: var(--fs-text); font-family: var(--fs-font-sans); font-size: var(--fs-type);
line-height: 1.3; min-height: var(--fs-ctl-h); cursor: pointer;
width: 210px;
display: inline-block;
transition: var(--fs-field-transition);
}
/* height:inherit so the multiple/listbox variant does not take the closed control's one-line height */
select[multiple] { background-color: var(--fs-panel2); color: var(--fs-text); height: inherit; }
option { font-size: 100%; line-height: 2em; }
select option { background: var(--fs-panel); color: var(--fs-text); }
/* the OS renders the native list, so `select option` above is mostly ignored; Chromium's
* base-select renders it in the DOM instead, themeable like .cbi-dropdown; @supports-gated,
* scoped to .cbi-value-field so a third-party app's raw <select> stays native */
@supports (appearance: base-select) {
.cbi-value-field select { appearance: base-select; }
.cbi-value-field select::picker-icon { display: none; }
::picker(select) {
appearance: base-select;
background: var(--fs-panel); border: var(--fs-hairline);
border-radius: var(--fs-radius-lg); box-shadow: var(--fs-shadow-pop);
padding: var(--fs-space-1-5); margin-top: var(--fs-space-1-5);
}
.cbi-value-field select option {
padding: var(--fs-space-2) var(--fs-space-3); border-radius: var(--fs-radius-sm); font-size: var(--fs-type);
/* background:transparent undoes `select option`'s --fs-panel so the row's own hover shows */
background: transparent; min-height: 0;
}
.cbi-value-field select option::checkmark { display: none; }
.cbi-value-field select option:hover, .cbi-value-field select option:focus-visible { background: var(--fs-panel2); }
/* same shape as the open .cbi-dropdown's selected row (theme/65-dropdown.css): accent on --fs-accent-soft measures 4.21:1 in dark, under AA */
.cbi-value-field select option:checked {
/* @mirror selected-row/paint */
background: var(--fs-panel2); color: var(--fs-accent); box-shadow: var(--fs-row-rail);
/* @endmirror */
}
}
/* .cbi-select — legacy luci-compat shell around a native <select>, matching the themed select
* property for property. :not(select): the class also appears on a bare <select>, which cannot draw the ::before chevron */
.cbi-select:not(select) {
display: inline-block;
width: 210px;
/* height not min-height: the child's height:100% falls back to content height against an auto-height parent (measured collapse 30px -> 15px in a 38px shell) */
height: var(--fs-ctl-h);
color: var(--fs-text);
font-size: var(--fs-type);
border: var(--fs-hairline);
border-radius: var(--fs-radius);
padding: 0;
position: relative;
background-color: var(--fs-panel2);
background-image: var(--fs-select-chevron);
background-repeat: no-repeat;
background-size: 6px 6px;
background-position: right calc(var(--fs-space-3) - 1px) center, right calc(var(--fs-space-3) + 5px) center;
transition: var(--fs-field-transition);
}
/* base/30-forms.css only dims the old ▾ plate; with the plate gone the disabled state had no visual, so the shell carries it now */
.cbi-select[disabled]:not(select) {
opacity: var(--fs-disabled-opacity);
pointer-events: none;
}
/* invisible, filling the shell, clearing the plate by the same --fs-space-8 inset the themed select uses */
.cbi-select select,
.cbi-select select:focus {
appearance: none;
outline: none;
border: none;
background: transparent;
box-shadow: none;
height: 100%;
min-height: 0;
width: 100%;
padding: 0 var(--fs-space-8) 0 var(--fs-space-3);
font-size: var(--fs-type);
line-height: normal;
cursor: pointer;
}
.spinning, .cbi-value em, em.spinning { color: var(--fs-dim); }
/* type list must be EXHAUSTIVE: an omission falls back to BASE's stock field beside a themed one, not to "unstyled" */
.cbi-input-text, .cbi-input-password, .cbi-input-textarea,
.cbi-section-create-name, .cbi-value-field input.create-item-input,
input[type="text"], input[type="password"], input[type="number"],
input[type="email"], input[type="url"], input[type="search"],
input[type="tel"], input[type="date"], input[type="time"],
input[type="datetime-local"], input[type="month"], input[type="week"],
input[type="color"],
textarea {
background: var(--fs-panel2);
border: var(--fs-hairline);
border-radius: var(--fs-radius);
color: var(--fs-text);
padding: var(--fs-space-2) var(--fs-space-3);
min-height: var(--fs-ctl-h);
box-shadow: none;
transition: var(--fs-field-transition);
}
/* max-width:100% beside the fixed 210px: a textarea's intrinsic width comes from `cols` (default 20), which put two textareas 19px outside their card at 300px before this */
textarea { min-height: 84px; line-height: var(--fs-leading); width: 210px; max-width: 100%; font-size: var(--fs-type); }
/* a square native scrollbar is not clipped by a rounded corner; self-rounded thumb + transparent track fixes it. no scrollbar-width/-color: either switches Chromium to the square standard bar */
textarea::-webkit-scrollbar { width: 16px; height: 16px; }
textarea::-webkit-scrollbar-track { background: transparent; margin: var(--fs-space-4) 0 var(--fs-space-2-5); }
textarea::-webkit-scrollbar-thumb {
background: var(--fs-dim); border-radius: var(--fs-radius-pill);
border: 5px solid transparent; background-clip: padding-box;
}
textarea::-webkit-scrollbar-thumb:hover { background: var(--fs-text); }
textarea::-webkit-scrollbar-corner { background: transparent; }
/* accent arc traces the frame's own radius (two borders shrunk to 16px - radius); mirrored by
* hand below, ::-webkit-resizer ignores logical properties */
textarea::-webkit-resizer {
background: transparent;
border-right: 2px solid var(--fs-accent);
border-bottom: 2px solid var(--fs-accent);
border-bottom-right-radius: var(--fs-radius);
margin: calc(16px - var(--fs-radius)) 0 0 calc(16px - var(--fs-radius));
}
:root[dir="rtl"] textarea::-webkit-resizer {
border-right: 0; border-left: 2px solid var(--fs-accent);
border-bottom-right-radius: 0; border-bottom-left-radius: var(--fs-radius);
margin: calc(16px - var(--fs-radius)) calc(16px - var(--fs-radius)) 0 0;
}
.cbi-input-text:hover, .cbi-input-password:hover, .cbi-input-textarea:hover,
input[type="text"]:hover, input[type="password"]:hover, input[type="number"]:hover,
input[type="email"]:hover, input[type="url"]:hover, input[type="search"]:hover,
textarea:hover { border-color: var(--fs-dim); }
/* fallback ring for untyped inputs; excludes the toggle switch, whose :focus-visible-only ring must not double up */
.cbi-input-text:focus, .cbi-input-password:focus, .cbi-input-textarea:focus,
.cbi-section-create-name:focus,
input:focus:not(:where(.cbi-checkbox > input)),
input[type="text"]:focus, input[type="password"]:focus, input[type="number"]:focus,
input[type="email"]:focus, input[type="url"]:focus, input[type="search"]:focus,
input[type="tel"]:focus, input[type="date"]:focus, input[type="time"]:focus,
textarea:focus,
select:focus,
.cbi-dynlist > .item:focus {
border-color: var(--fs-accent);
box-shadow: var(--fs-focus-ring);
outline: none;
}
/* same ring, shared by 4 selectors that cannot join the list above; :is() around :has() keeps the list forgiving — unwrapped, a :has() compound sharing a list fails the WHOLE rule below the floor */
input[type="checkbox"]:not(:where(.cbi-checkbox > input)):focus::before, input[type="radio"]:focus::before,
.cbi-select.focus,
.cbi-dynlist > .add-item:focus-within,
.control-group:is(:has(> .cbi-input-password)):focus-within {
border-color: var(--fs-accent);
box-shadow: var(--fs-focus-ring);
}
/* .cbi-value-error input (Lua-CBI) must live HERE: base's red border-color is a longhand and
* this rule's border shorthand wipes it by layer regardless of specificity */
input[type="checkbox"].cbi-input-invalid:focus::before,
input[type="radio"].cbi-input-invalid:focus::before,
.cbi-select.cbi-input-invalid.focus,
.cbi-input-invalid, input.cbi-input-invalid, textarea.cbi-input-invalid,
.cbi-value-error input, .cbi-value-error textarea,
.cbi-input-invalid:focus {
border-color: var(--fs-danger);
box-shadow: var(--fs-focus-ring-invalid);
}
/* same rejected value shown by a ui.Dropdown: base carried the widget and its closed <li>, theme only the input half */
.cbi-input-invalid.cbi-dropdown:not(.btn):not(.cbi-button),
.cbi-input-invalid.cbi-dropdown:not([open]) > ul > li { border-color: var(--fs-danger); }
/* doubled [readonly] states intent at (0,2,1): at (0,1,1) it only tied the typed input rules above and silently lost */
:is(input, select, textarea)[readonly][readonly] {
border-color: color-mix(in srgb, var(--fs-border) calc(var(--fs-disabled-opacity) * 100%), transparent);
pointer-events: auto;
cursor: auto;
}
/* keep base's layout (inline-flex chips + add-item row); only colours, rounding, a compact + button, narrow cap so a chip's × sits near its text */
.cbi-dynlist {
gap: var(--fs-space-1-5);
background: transparent; border: 0; padding: 0;
/* min(240px,100%) not a bare 240: a floor that exceeds the room is an overflow (measured 29px outside a 210px field at 320px) */
min-height: 0; min-width: min(240px, 100%); max-width: var(--fs-field-max); width: 100%;
vertical-align: top;
}
.cbi-dynlist > .item {
background: var(--fs-panel);
border: var(--fs-hairline);
border-radius: var(--fs-radius-sm);
color: var(--fs-text);
box-shadow: none;
margin: 0;
transition: var(--fs-field-transition);
}
.item::after {
display: inline-block;
cursor: pointer;
background: linear-gradient(var(--fs-panel), var(--fs-panel) 25%, var(--fs-panel2));
padding: 0 var(--fs-space-3);
color: var(--fs-text);
line-height: 2em;
border: var(--fs-hairline);
border-radius: var(--fs-radius);
transition: var(--fs-field-transition);
}
.item:hover::after { filter: var(--fs-hover-lift); }
.cbi-dynlist > .item::after {
display: inline-flex;
padding: 0 var(--fs-space-2);
color: var(--fs-danger);
border-radius: 0 var(--fs-radius-sm) var(--fs-radius-sm) 0;
}
.cbi-dynlist > .add-item {
align-items: stretch;
background: var(--fs-panel2); border: var(--fs-hairline);
border-radius: var(--fs-radius-sm); overflow: hidden;
transition: var(--fs-field-transition);
}
.cbi-dynlist > .add-item > input,
/* width:auto keeps the flex basis in charge — this input sits inside .cbi-value-field, the elastic width above would otherwise hand it 100% on top of flex:1 1 auto */
.cbi-dynlist > .add-item > .cbi-input-text {
flex: 1 1 auto; min-width: 0; width: auto;
border: 0; background: transparent;
box-shadow: none; min-height: var(--fs-ctl-h-sm); border-radius: 0;
}
/* input + button as ONE seamless group; gap:0 is load-bearing — base gives every .control-group a gap, this is the one whose members must touch */
.control-group:has(> .cbi-input-password) {
display: inline-flex; align-items: stretch; gap: 0;
background: var(--fs-panel2);
border: var(--fs-hairline);
border-radius: var(--fs-radius);
overflow: hidden;
transition: var(--fs-field-transition);
}
.control-group:has(> .cbi-input-password) > .cbi-input-password {
border: 0; border-radius: 0;
background: transparent; box-shadow: none;
min-height: var(--fs-ctl-h);
}
.cbi-section-create, .cbi-section-create.cbi-tblsection-create { margin-top: var(--fs-space-3); }
/* `.hidden.hidden` outranks every component rule without !important (which would invert layer
* order later) and covers the Enter-key submit legacy Lua pages carry (issue #12).
* :not([class*=":"]) excludes a Tailwind/UnoCSS class fs-sheets.js re-hosts into this layer —
* docs/css.md "Inputs: `.hidden.hidden`, and the Tailwind/UnoCSS exception". */
.hidden.hidden:not([class*=":"]) { display: none; }
/* the WCAG 2.5.8 target (24px min) split from the 6px track — docs/css.md "Inputs: the range
* slider's hit target and its line-box" has the two-part fix and its numbers */
input[type="range"] {
appearance: none;
width: 100%; height: 24px; min-height: 0; padding: 0;
margin-block: -9px;
background: transparent; border: 0; border-radius: 0;
outline: none; box-shadow: none; cursor: pointer;
}
input[type="range"]::-webkit-slider-runnable-track {
width: 100%; height: 6px;
background: var(--fs-track); border: 0; border-radius: var(--fs-radius-pill);
}
input[type="range"]::-moz-range-track {
width: 100%; height: 6px;
background: var(--fs-track); border: 0; border-radius: var(--fs-radius-pill);
}
input[type="range"]::-webkit-slider-thumb {
appearance: none;
width: 16px; height: 16px; border-radius: 50%;
background: var(--fs-accent); border: 2px solid var(--fs-panel);
cursor: pointer;
/* WebKit centres on the INPUT's box not the track: (6-16)/2 = -5px */
margin-top: -5px;
}
input[type="range"]::-moz-range-thumb {
width: 16px; height: 16px; border-radius: 50%;
background: var(--fs-accent); border: 2px solid var(--fs-panel); cursor: pointer;
}
input[type="range"]:focus::-webkit-slider-runnable-track { box-shadow: var(--fs-focus-ring-solo); }
input[type="range"]:focus::-moz-range-track { box-shadow: var(--fs-focus-ring-solo); }
/* same ceiling as the elastic text inputs; vertical-align:middle decouples the line-box from the input's own edge, which moved 6px -> 24px above */
.cbi-range-slider { display: inline-flex; align-items: center; vertical-align: middle; gap: var(--fs-space-3); width: 100%; max-width: var(--fs-field-max); }
.cbi-range-slider-value {
background: var(--fs-panel2); border: var(--fs-hairline); border-radius: var(--fs-radius-sm);
padding: var(--fs-space-1) var(--fs-space-2); color: var(--fs-accent); font-family: var(--fs-font-mono);
/* not 600: no bold mono face bundled, drags in 20 KB or fakes it with synthetic emboldening */
font-weight: var(--fs-weight-normal); font-size: var(--fs-type); white-space: nowrap;
}
.cbi-range-slider-calc, .cbi-range-slider-calc-units { color: var(--fs-dim); font-size: var(--fs-type); }
}