mirror of
https://github.com/caiwx86/small-packages.git
synced 2026-10-26 20:42:53 +08:00
1195 lines
68 KiB
HTML
1195 lines
68 KiB
HTML
<!DOCTYPE html>
|
|
<!--
|
|
footstrap component gallery.
|
|
|
|
Every widget LuCI (or any luci-app-* package) can put on a page, rendered with
|
|
the real class names ui.js/cbi.js emit, so the theme can be checked without
|
|
hunting for a router page that happens to use the widget.
|
|
|
|
Serve it next to the stylesheet and open it without logging in:
|
|
|
|
scp docs/gallery.html router:/www/luci-static/footstrap/
|
|
open http://<router>/luci-static/footstrap/gallery.html
|
|
|
|
Toggle dark/light with the button; ?palette=rvht picks the wallpaper palette.
|
|
-->
|
|
<html lang="en" data-darkmode="true" data-palette="footstrap">
|
|
<head>
|
|
<meta charset="utf-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
<title>footstrap — component gallery</title>
|
|
<link rel="stylesheet" href="cascade.css">
|
|
<style>
|
|
/* gallery chrome only — never part of the theme */
|
|
body { background: var(--fs-bg); color: var(--fs-text); }
|
|
/* `min-width: 0` because the theme lays <body> out as a FLEX container, and a flex item's
|
|
automatic minimum size is its min-content — so one deliberately unbreakable fixture (the 130-
|
|
character token below) stretched this wrapper to its max-width and pushed the whole page
|
|
sideways at every viewport. Measured at 390px: body 390, wrapper 1180, document scroll 790.
|
|
`width: 100%` is the other half: the item is sized shrink-to-fit on the cross axis, so
|
|
`min-width: 0` alone permits shrinking that nothing then asks for. */
|
|
.g-wrap { width: 100%; max-width: 1180px; min-width: 0; margin: 0 auto; padding: 24px 28px 80px; }
|
|
.g-bar { position: sticky; top: 0; z-index: 900; display: flex; flex-wrap: wrap; gap: 10px; align-items: center;
|
|
padding: 12px 0; margin-bottom: 8px; background: var(--fs-bg); border-bottom: 1px solid var(--fs-border); }
|
|
.g-bar h1 { font-size: 18px; margin: 0; flex: 1 1 auto; }
|
|
.g-sec { margin: 34px 0 0; }
|
|
.g-sec > h2 { font-size: 13px; letter-spacing: .08em; text-transform: uppercase;
|
|
color: var(--fs-dim); margin: 0 0 12px; font-weight: 700; }
|
|
.g-row { display: flex; flex-wrap: wrap; gap: 10px; align-items: center; margin-bottom: 12px; }
|
|
.g-note { color: var(--fs-dim); font-size: 12px; margin: -4px 0 10px; }
|
|
/* The pathological fixtures are built to NOT fit — that is what they are for. On a live page the
|
|
fitter cards or scrolls them; the gallery has no LuCI JS, so they get a scroll frame here and
|
|
the page itself stays measurable. The frame is gallery chrome, never theme. */
|
|
.g-overwide { overflow-x: auto; overscroll-behavior-x: contain; }
|
|
/* the dialog is rendered INLINE here (position: static) so it can be looked at; out of its fixed
|
|
overlay it has no viewport to be centred in, so cap it to the column it now lives in */
|
|
/* the theme's own container context: on a page it is `#view` and `.cbi-map` that carry
|
|
`container-name: fs-content`, and the config table's card threshold is a @container query
|
|
against it. Without one the gallery renders that table in a context no page has. */
|
|
.g-wrap { container-type: inline-size; container-name: fs-view fs-content; }
|
|
#modal_overlay { max-width: 100%; }
|
|
#modal_overlay .modal { max-width: 100%; }
|
|
</style>
|
|
</head>
|
|
<body class="lang_en">
|
|
<div class="g-wrap">
|
|
|
|
<div class="g-bar">
|
|
<h1>footstrap — component gallery</h1>
|
|
<button class="cbi-button" onclick="var r=document.documentElement;r.setAttribute('data-darkmode',r.getAttribute('data-darkmode')==='true'?'false':'true')">Toggle dark</button>
|
|
<button class="cbi-button" onclick="var r=document.documentElement;r.setAttribute('data-palette',r.getAttribute('data-palette')==='rvht'?'footstrap':'rvht')">Toggle palette</button>
|
|
</div>
|
|
|
|
<div class="g-sec">
|
|
<h2>Typography & text elements</h2>
|
|
<div class="cbi-section">
|
|
<h1>Heading 1</h1><h2>Heading 2</h2><h3>Heading 3</h3>
|
|
<h4>Heading 4</h4><h5>Heading 5</h5><h6>Heading 6</h6>
|
|
<p>Body paragraph with a <a href="#">link</a>, <strong>strong</strong>, <em>emphasis</em>,
|
|
<code>inline code</code>, <var>a var</var>, <small>small print</small>,
|
|
H<sub>2</sub>O and x<sup>2</sup>, an <abbr title="abbreviation">abbr</abbr>,
|
|
<kbd>Ctrl</kbd>+<kbd>C</kbd>, <del>deleted</del> and <ins>inserted</ins> text.</p>
|
|
<blockquote><p>A blockquote, as an app might render a hint.</p></blockquote>
|
|
<pre>#!/bin/sh
|
|
uci set network.lan.ipaddr='192.168.1.1'
|
|
uci commit network</pre>
|
|
<hr>
|
|
<ul><li>Unordered item</li><li>Another item</li></ul>
|
|
<ol><li>Ordered item</li><li>Another item</li></ol>
|
|
<ul class="unstyled"><li>Unstyled list item</li></ul>
|
|
<dl><dt>Term</dt><dd>Definition</dd><dt>Другой термин</dt><dd>Определение</dd></dl>
|
|
<address>OpenWrt, somewhere on a LAN</address>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="g-sec">
|
|
<h2>Buttons — every cbi-button variant</h2>
|
|
<div class="g-row">
|
|
<button class="cbi-button">cbi-button</button>
|
|
<button class="cbi-button cbi-button-action">action</button>
|
|
<button class="cbi-button cbi-button-apply">apply</button>
|
|
<button class="cbi-button cbi-button-save">save</button>
|
|
<button class="cbi-button cbi-button-positive">positive</button>
|
|
<button class="cbi-button cbi-button-negative">negative</button>
|
|
<button class="cbi-button cbi-button-remove">remove</button>
|
|
<button class="cbi-button cbi-button-reset">reset</button>
|
|
<button class="cbi-button cbi-button-neutral">neutral</button>
|
|
<button class="cbi-button cbi-button-edit">edit</button>
|
|
<button class="cbi-button cbi-button-add">add</button>
|
|
<button class="cbi-button cbi-button-link">link</button>
|
|
<button class="cbi-button cbi-button-download">download</button>
|
|
<button class="cbi-button cbi-button-find">find</button>
|
|
<button class="cbi-button cbi-button-reload">reload</button>
|
|
<button class="cbi-button cbi-button-up">up</button>
|
|
<button class="cbi-button cbi-button-down">down</button>
|
|
<button class="cbi-button" disabled>disabled</button>
|
|
<button class="cbi-button spinning">spinning</button>
|
|
</div>
|
|
<div class="g-row">
|
|
<a class="btn" href="#">.btn</a>
|
|
<a class="btn primary" href="#">.btn.primary</a>
|
|
<!-- native input[type=button/submit/reset]: a REAL caption, not the markup that produces the
|
|
control — the old `value="input[type=button]"` was gallery documentation wearing the
|
|
control's own face, and pseudo-loc grew that 19-character syntax (not a real button's
|
|
caption) into a 28-character one, 47-64.5px past this row's flex parent at 320px/Large.
|
|
No real page ever emits this string as a button's label; a real one says "Submit". -->
|
|
<input type="button" value="Button">
|
|
<input type="submit" value="Submit">
|
|
<input type="reset" value="Reset">
|
|
</div>
|
|
<div class="g-row">
|
|
<a class="btn danger" href="#">.btn.danger</a>
|
|
<a class="btn error" href="#">.btn.error</a>
|
|
<a class="btn success" href="#">.btn.success</a>
|
|
<a class="btn info" href="#">.btn.info</a>
|
|
<a class="btn large" href="#">.btn.large</a>
|
|
<a class="btn small" href="#">.btn.small</a>
|
|
</div>
|
|
<p class="g-note">The colour words (<code>.danger/.error/.success/.info</code>) and size words
|
|
(<code>.large/.small</code>) come from <code>styles/base</code>, but the theme's plain
|
|
<code>.btn</code> rule sits in a LATER layer and overrides background/padding/radius —
|
|
so the theme has to re-state them (<code>theme/55-buttons.css</code>). They rendered as
|
|
neutral grey buttons, and <code>.btn.large</code> came out with small-button padding,
|
|
until it did. Nothing in stock LuCI emits them; a third-party <code>luci-app-*</code> can.</p>
|
|
<p class="g-note">Keyboard focus: <kbd>Tab</kbd> through this section. Every button must show a
|
|
ring — there was none at all before (base listed <code>button:hover</code>, not
|
|
<code>:focus</code>, and the theme's <code>box-shadow: none</code> erased the rest).</p>
|
|
<p class="g-note">Row-action size (inside a <code>.td</code>) is shown in the tables section.</p>
|
|
</div>
|
|
|
|
<div class="g-sec">
|
|
<h2>Page action bar</h2>
|
|
<!-- The Save & Apply split control, copied from what ui.js ACTUALLY emits (captured off a
|
|
live 25.12 router on admin/network/firewall). The classes are on the WRAPPER — it is a
|
|
ui.Dropdown wearing `btn cbi-button cbi-button-apply` — and the label is an <li> inside its
|
|
own <ul>, not a nested <button>. The shape here used to be `<div class="cbi-dropdown">` with
|
|
an inner button, which is not a shape LuCI produces: the wrapper then matched the theme's
|
|
`.cbi-dropdown:not(.btn):not(.cbi-button)` field rule and rendered as a grey box glued to a
|
|
blue button. Anyone reading this card diagnosed a bug the theme does not have. -->
|
|
<div class="cbi-page-actions">
|
|
<div class="cbi-dropdown btn cbi-button cbi-button-apply important" tabindex="0" data-idref="g-apply">
|
|
<ul tabindex="-1">
|
|
<li data-value="0" selected display="0">Save & Apply</li>
|
|
<li data-value="1">Apply unchecked</li>
|
|
</ul>
|
|
<span class="more" tabindex="-1">···</span>
|
|
<span class="open" tabindex="-1">▾</span>
|
|
<div><input type="hidden" value="0"></div>
|
|
</div>
|
|
<button class="cbi-button cbi-button-save">Save</button>
|
|
<button class="cbi-button cbi-button-reset">Reset</button>
|
|
</div>
|
|
<!-- The SAME control after the user picks the second mode. The class on the wrapper is the one
|
|
ui.js chose for the selected value (luci.js's ComboButton `classes` map: 0 -> cbi-button-apply,
|
|
1 -> cbi-button-negative), so this is not a variant of the card above but its other half —
|
|
and it is the half that was invisible: a blanket `.cbi-page-actions .cbi-dropdown` rule
|
|
erased the negative fill and left --fs-on-danger on the page behind it, 1.13:1 in dark. -->
|
|
<div class="cbi-page-actions">
|
|
<div class="cbi-dropdown btn cbi-button cbi-button-negative important" tabindex="0" data-idref="g-apply-unchecked">
|
|
<ul tabindex="-1">
|
|
<li data-value="0">Save & Apply</li>
|
|
<li data-value="1" selected display="0">Apply unchecked</li>
|
|
</ul>
|
|
<span class="more" tabindex="-1">···</span>
|
|
<span class="open" tabindex="-1">▾</span>
|
|
<div><input type="hidden" value="1"></div>
|
|
</div>
|
|
<button class="cbi-button cbi-button-save">Save</button>
|
|
<button class="cbi-button cbi-button-reset">Reset</button>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="g-sec">
|
|
<h2>Form fields (.cbi-value rows)</h2>
|
|
<div class="cbi-section">
|
|
<h3>Section title</h3>
|
|
<div class="cbi-section-descr">A section description, as CBI renders it.</div>
|
|
<div class="cbi-section-node">
|
|
<div class="cbi-value">
|
|
<label class="cbi-value-title" for="g-text">Text input</label>
|
|
<div class="cbi-value-field"><input id="g-text" type="text" class="cbi-input-text" value="OpenWrt"></div>
|
|
</div>
|
|
<!-- task 0147 (second RU sweep), finding 1: under 767px theme/90-responsive.css restates
|
|
`flex: 1 1 100%` on the label (stacked above the field), and a flex item's automatic
|
|
minimum size is content-based once that basis is not smaller than the content — a
|
|
label whose text has too few break points renders at its own min-content instead of
|
|
the row's width. Sweep-measured on ssclash's settings form at 320px: 320px wide in a
|
|
254-276px row, the section clipped 33-50px with no scroller. The run below has no
|
|
space for the middle third on purpose, the shape that broke it; a normal RU sentence
|
|
with ordinary word breaks does not need this fixture to pass. -->
|
|
<div class="cbi-value">
|
|
<label class="cbi-value-title" for="g-longlabel">Автоматическоепереподключениексерверу при обрыве</label>
|
|
<div class="cbi-value-field"><input id="g-longlabel" type="text" class="cbi-input-text" value=""></div>
|
|
</div>
|
|
<div class="cbi-value">
|
|
<label class="cbi-value-title" for="g-pass">Password</label>
|
|
<div class="cbi-value-field"><input id="g-pass" type="password" class="cbi-input-password" value="secret"></div>
|
|
</div>
|
|
<div class="cbi-value">
|
|
<label class="cbi-value-title" for="g-inv">Invalid input</label>
|
|
<div class="cbi-value-field">
|
|
<input id="g-inv" type="text" class="cbi-input-text cbi-input-invalid" value="not an ip">
|
|
<div class="cbi-value-description">Expecting: valid IP address</div>
|
|
</div>
|
|
</div>
|
|
<div class="cbi-value">
|
|
<label class="cbi-value-title" for="g-sel">Native select</label>
|
|
<div class="cbi-value-field">
|
|
<select id="g-sel" class="cbi-input-select"><option>alpha</option><option>beta</option></select>
|
|
</div>
|
|
</div>
|
|
<div class="cbi-value">
|
|
<label class="cbi-value-title" for="g-msel">Multiple select</label>
|
|
<div class="cbi-value-field">
|
|
<select id="g-msel" multiple size="3"><option selected>one</option><option>two</option><option>three</option></select>
|
|
</div>
|
|
</div>
|
|
<!-- task 0145: a caption-less `div.cbi-checkbox` never gives the pill a sibling flex item
|
|
to shrink against, so this fixture stayed green through the whole life of the
|
|
pill-shrink/knob-overhang bug — every gallery gate, computed-diff included, measured
|
|
a switch that could never fail this way. `label.cbi-checkbox > input + label[for] +
|
|
TEXT` is package-manager.js's and banip's real markup (a raw checkbox with an inline
|
|
caption, not the ui.Checkbox widget), and the caption is the RU string the bug was
|
|
measured against: 'Автоматически удалять неиспользуемые зависимости', long enough to
|
|
shrink the pill at default density on every phone width — EN only broke it at
|
|
Density=Large. -->
|
|
<div class="cbi-value">
|
|
<label class="cbi-value-title" for="cb1">Checkbox (toggle)</label>
|
|
<div class="cbi-value-field">
|
|
<label class="cbi-checkbox"><input id="cb1" type="checkbox" checked><label for="cb1"></label> Автоматически удалять неиспользуемые зависимости</label>
|
|
</div>
|
|
</div>
|
|
<!-- ui.Checkbox with `tooltip` set (form.Flag.tooltip, e.g. luci-app-firewall's zones.js
|
|
`masq` — "IPv4 Masquerading"), markup exactly as ui.js's Checkbox.render() emits it:
|
|
the switch label immediately after the input, THEN a second label.cbi-tooltip-container
|
|
holding the icon glyph and its .cbi-tooltip popup. Two states, since the bug this covers
|
|
(a dead second switch pill painted where the icon belongs) showed on both. -->
|
|
<div class="cbi-value">
|
|
<label class="cbi-value-title" for="cb-tt-on">Checkbox + tooltip (checked)</label>
|
|
<div class="cbi-value-field">
|
|
<div class="cbi-checkbox"><input id="cb-tt-on" type="checkbox" checked><label for="cb-tt-on"></label><label class="cbi-tooltip-container">⚠️<div class="cbi-tooltip">Limited masquerading enabled</div></label></div>
|
|
</div>
|
|
</div>
|
|
<div class="cbi-value">
|
|
<label class="cbi-value-title" for="cb-tt-off">Checkbox + tooltip (unchecked)</label>
|
|
<div class="cbi-value-field">
|
|
<div class="cbi-checkbox"><input id="cb-tt-off" type="checkbox"><label for="cb-tt-off"></label><label class="cbi-tooltip-container">⚠️<div class="cbi-tooltip">Limited masquerading enabled</div></label></div>
|
|
</div>
|
|
</div>
|
|
<!-- task cbi-checkbox: forum.openwrt.org/t/251930 post #141 — luci-app-modemdata injects an
|
|
unlayered `label[for]{display:none!important}` scoped to its own markup, expecting the
|
|
switch to live on the native input (as it does under bootstrap). Reproduced verbatim,
|
|
scoped to this fixture only, to prove the switch stays visible from the INPUT alone. -->
|
|
<style>
|
|
.fs-fixture-hidden-label label[for] { display: none !important; }
|
|
</style>
|
|
<div class="cbi-value fs-fixture-hidden-label">
|
|
<label class="cbi-value-title" for="cb-hl-on">Checkbox, label[for] hidden (checked)</label>
|
|
<div class="cbi-value-field">
|
|
<label class="cbi-checkbox"><input id="cb-hl-on" type="checkbox" checked><label for="cb-hl-on"></label> Hide data</label>
|
|
</div>
|
|
</div>
|
|
<div class="cbi-value fs-fixture-hidden-label">
|
|
<label class="cbi-value-title" for="cb-hl-off">Checkbox, label[for] hidden (unchecked)</label>
|
|
<div class="cbi-value-field">
|
|
<label class="cbi-checkbox"><input id="cb-hl-off" type="checkbox"><label for="cb-hl-off"></label> Hide data</label>
|
|
</div>
|
|
</div>
|
|
<div class="cbi-value">
|
|
<label class="cbi-value-title">Radio</label>
|
|
<div class="cbi-value-field">
|
|
<label><input type="radio" name="r" checked> yes</label>
|
|
<label><input type="radio" name="r"> no</label>
|
|
</div>
|
|
</div>
|
|
<div class="cbi-value">
|
|
<label class="cbi-value-title" for="g-ta">Textarea</label>
|
|
<div class="cbi-value-field"><textarea id="g-ta" class="cbi-input-textarea" rows="3">option one
|
|
option two</textarea></div>
|
|
</div>
|
|
<div class="cbi-value" style="margin-top:28px">
|
|
<label class="cbi-value-title" for="g-ta2">Textarea (scrolling — NFQWS_OPT-style config)</label>
|
|
<div class="cbi-value-field"><textarea id="g-ta2" class="cbi-input-textarea" rows="6" style="height:150px">#Yv11
|
|
--filter-tcp=443
|
|
--hostlist=/opt/zapret/ipset/zapret-hosts.txt
|
|
--dpi-desync=fake,multisplit
|
|
--dpi-desync-fake-tls=0x00000000
|
|
--dpi-desync-fake-tls=!
|
|
--dpi-desync-split-pos=1,sniext+1,host+1,midsld
|
|
--dpi-desync-ttl=0
|
|
--new
|
|
--filter-udp=443
|
|
--dpi-desync=fake
|
|
--dpi-desync-repeats=6</textarea></div>
|
|
</div>
|
|
<div class="cbi-value">
|
|
<label class="cbi-value-title" for="g-file">File upload (bare input)</label>
|
|
<div class="cbi-value-field"><input id="g-file" type="file"></div>
|
|
</div>
|
|
<!-- ui.FileUpload, CLOSED — what a form shows before you click it. The browser below is
|
|
the same widget with .open on it; LuCI only ever has one of the two visible. -->
|
|
<div class="cbi-value">
|
|
<label class="cbi-value-title">File upload (ui.FileUpload, closed)</label>
|
|
<div class="cbi-value-field">
|
|
<div>
|
|
<button class="btn open-file-browser">Select file…</button>
|
|
<div class="cbi-filebrowser"></div>
|
|
<input type="hidden" name="g-fb0" value="">
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<!-- ui.FileUpload, OPEN. Markup copied from ui.js (renderListing + renderUpload), classes
|
|
and nesting included: the listing rows are <li> with .name / .mtime.hide-xs / a button
|
|
div, and the upload strip is [Browse…] [filename input] [Upload file] in one .upload
|
|
flex row. Both are only reachable after two clicks on a live router, which is why the
|
|
row that could not hold its button went unseen. -->
|
|
<div class="cbi-value">
|
|
<label class="cbi-value-title">File upload (ui.FileUpload, browser open)</label>
|
|
<div class="cbi-value-field">
|
|
<div>
|
|
<button class="btn open-file-browser" style="display:none">Select file…</button>
|
|
<div class="cbi-filebrowser open">
|
|
<p><a href="#">/etc/luci-uploads</a> » <a href="#">certs</a></p>
|
|
<ul>
|
|
<li>
|
|
<div class="name"><img src="data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3E%3Cpath d='M1 3h5l1 2h8v9H1z' fill='%23888'/%3E%3C/svg%3E" width="16" class="middle" alt=""> <a href="#">subdir</a></div>
|
|
<div class="mtime hide-xs"> 2026-07-14 18:31:07 </div>
|
|
<div><button class="btn cbi-button-negative">Delete</button></div>
|
|
</li>
|
|
<li>
|
|
<div class="name"><img src="data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3E%3Cpath d='M3 1h7l3 3v11H3z' fill='%23888'/%3E%3C/svg%3E" width="16" class="middle" alt=""> <a href="#" style="font-weight:bold">fullchain.pem</a></div>
|
|
<div class="mtime hide-xs"> 2026-07-14 18:29:55 </div>
|
|
<div><button class="btn">Deselect</button><button class="btn">Download</button><button class="btn cbi-button-negative">Delete</button></div>
|
|
</li>
|
|
</ul>
|
|
<div class="right">
|
|
<div class="upload">
|
|
<input type="file" style="display:none">
|
|
<button class="btn">Browse…</button>
|
|
<div><input type="text" placeholder="Filename"></div>
|
|
<button class="btn cbi-button-save">Upload file</button>
|
|
</div>
|
|
<a href="#" class="btn cbi-button">Create</a>
|
|
<a href="#" class="btn">Cancel</a>
|
|
</div>
|
|
</div>
|
|
<input type="hidden" name="g-fb1" value="">
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="cbi-value">
|
|
<label class="cbi-value-title" for="g-range">Range slider</label>
|
|
<div class="cbi-value-field">
|
|
<div class="cbi-range-slider">
|
|
<input id="g-range" type="range" min="0" max="100" value="40">
|
|
<span class="cbi-range-slider-value">40</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="cbi-value">
|
|
<label class="cbi-value-title">Dynamic list</label>
|
|
<div class="cbi-value-field">
|
|
<div class="cbi-dynlist">
|
|
<div class="item" tabindex="0"><span>192.168.1.1</span></div>
|
|
<div class="item" tabindex="0"><span>1.1.1.1</span></div>
|
|
<div class="add-item"><input type="text" class="cbi-input-text" placeholder="add entry…"></div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="cbi-value">
|
|
<label class="cbi-value-title">Buttons in a field</label>
|
|
<div class="cbi-value-field">
|
|
<button class="cbi-button cbi-button-neutral">Start</button>
|
|
<button class="cbi-button cbi-button-neutral">Restart</button>
|
|
<button class="cbi-button cbi-button-negative">Stop</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="cbi-section-error"><ul><li>A section error, as CBI renders a validation failure.</li></ul></div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="g-sec">
|
|
<h2>cbi-dropdown widget (the JS select)</h2>
|
|
<div class="g-row">
|
|
<div class="cbi-dropdown" tabindex="0"><ul><li class="focus" display="0">Selected value</li></ul><span class="open">▾</span></div>
|
|
<div class="cbi-dropdown btn cbi-button cbi-button-action" tabindex="0"><ul><li>Button dropdown</li></ul><span class="open">▾</span></div>
|
|
<!-- The Save & Apply split control WHILE APPLYING: a .cbi-dropdown that is also a spinning
|
|
button. Its padding has to out-rank the dropdown box's own `padding: 0` — the one case
|
|
the spinner's !important used to exist for, and the one nothing rendered. -->
|
|
<div class="cbi-dropdown btn cbi-button cbi-button-apply spinning" tabindex="0"><ul><li>Applying…</li></ul><span class="open">▾</span></div>
|
|
<!-- Field dropdown (not .btn/.cbi-button) with a value longer than its 400px cap — the
|
|
https-dns-proxy/nlbw-config/interface-edit shape task 0149 measured cutting mid-glyph at
|
|
320px (RU: 132/225/280px past the box at Compact/Normal/Large). No `title`/`aria-label`:
|
|
the ellipsis is visual only, the full string stays in the DOM and the accessibility tree. -->
|
|
<div class="cbi-dropdown" tabindex="0"><ul><li class="focus" display="0">Автоматический выбор лучшего доступного вышестоящего DNS-сервера по задержке отклика</li></ul><span class="open">▾</span></div>
|
|
</div>
|
|
<p class="g-note">Closed controls only. The open list — and the create-item row that goes with it —
|
|
is in the Combobox section below.</p>
|
|
</div>
|
|
|
|
<div class="g-sec">
|
|
<h2>Combobox (cbi-dropdown + custom value)</h2>
|
|
<!--
|
|
ui.Combobox IS ui.Dropdown with `{ multiple: false, create: true, optional: true }` forced on
|
|
(ui.js), so its markup is the dropdown's plus one extra row: a bare `<li data-value="-">` whose
|
|
only child is `<input class="create-item-input">` — the field a user types a custom value into.
|
|
That row is the whole reason this section exists; it is the one dropdown state no shipped LuCI
|
|
page shows standing still, so nothing else in this gallery renders it.
|
|
|
|
Everything below is what ui.js leaves in the DOM, written out by hand because there is no JS
|
|
here to build it: bind() appends `<span class="more">` + `<span class="open">` + the trailing
|
|
`<div>` that holds the hidden inputs, prepends the `<li placeholder>` when the widget is
|
|
optional, and marks the shown item `selected` + `display="0"`.
|
|
-->
|
|
<div class="g-row">
|
|
<!-- closed, with a value chosen -->
|
|
<div class="cbi-dropdown" optional="" tabindex="0">
|
|
<ul tabindex="-1">
|
|
<li placeholder="">-- Please choose --</li>
|
|
<li data-value="wan">wan</li>
|
|
<li data-value="lan" selected="" display="0">lan</li>
|
|
<li data-value="wg0">wg0</li>
|
|
<li data-value="-"><input type="text" class="create-item-input" placeholder="-- custom --" inputmode="text" enterkeyhint="done"></li>
|
|
</ul>
|
|
<span class="more" tabindex="-1">···</span>
|
|
<span class="open" tabindex="-1">▾</span>
|
|
<div><input type="hidden" name="g-combo" value="lan"></div>
|
|
</div>
|
|
|
|
<!-- closed and unset: optional, so bind() puts `selected`/`display` on the placeholder li
|
|
(and saveValues writes no hidden input for it) -->
|
|
<div class="cbi-dropdown" optional="" tabindex="0">
|
|
<ul tabindex="-1">
|
|
<li placeholder="" selected="" display="0">-- Please choose --</li>
|
|
<li data-value="wan">wan</li>
|
|
<li data-value="lan">lan</li>
|
|
<li data-value="-"><input type="text" class="create-item-input" placeholder="-- custom --" inputmode="text" enterkeyhint="done"></li>
|
|
</ul>
|
|
<span class="more" tabindex="-1">···</span>
|
|
<span class="open" tabindex="-1">▾</span>
|
|
<div></div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- OPEN. openDropdown() sets [open], clones the <ul> into a `ul.preview` (which is what you
|
|
still see in the control's own box — every li but the `display`-marked one stays hidden, so
|
|
the clone's copy of the create-item input is present but not shown; that duplicate is real,
|
|
do not "fix" it), adds `.dropdown` to the original <ul>, hands each of ITS li a tabindex,
|
|
and writes top/max-height inline. The list is absolutely positioned, hence the bottom margin
|
|
on the row — gallery staging only, the same trick the Modal section uses. -->
|
|
<div class="g-row" style="margin-bottom:250px">
|
|
<div class="cbi-dropdown" optional="" open="" tabindex="0">
|
|
<ul class="dropdown" tabindex="-1" style="top:38px;max-height:280px">
|
|
<li placeholder="" tabindex="0">-- Please choose --</li>
|
|
<li data-value="wan" tabindex="0">wan</li>
|
|
<li data-value="lan" selected="" display="0" tabindex="0">lan</li>
|
|
<li data-value="wg0" tabindex="0">wg0</li>
|
|
<li data-value="-" tabindex="0"><input type="text" class="create-item-input" placeholder="-- custom --" inputmode="text" enterkeyhint="done"></li>
|
|
</ul>
|
|
<ul class="preview">
|
|
<li placeholder="">-- Please choose --</li>
|
|
<li data-value="wan">wan</li>
|
|
<li data-value="lan" selected="" display="0">lan</li>
|
|
<li data-value="wg0">wg0</li>
|
|
<li data-value="-"><input type="text" class="create-item-input" placeholder="-- custom --" inputmode="text" enterkeyhint="done"></li>
|
|
</ul>
|
|
<span class="more" tabindex="-1">···</span>
|
|
<span class="open" tabindex="-1">▾</span>
|
|
<div tabindex="0"><input type="hidden" name="g-combo-open" value="lan"></div>
|
|
</div>
|
|
</div>
|
|
<p class="g-note">What to look at: the create-item row must read as a text field, not as a choice —
|
|
it is the only row a user can type in. Base gives it <code>width: 100%</code> via
|
|
<code>li > input.create-item-input:first-child:last-child</code>, which only matches because
|
|
the input is that li's sole child; the theme paints it as a normal input
|
|
(<code>theme/65-dropdown.css</code>). Check its focus ring, and check that the row's own hover
|
|
pill does not fight the field inside it.</p>
|
|
<p class="g-note">The theme also carries a rule for <code>li.create-item</code>, a class stock
|
|
LuCI does not emit anywhere (grepped: <code>ui.js</code> gives the li no class at all, only the
|
|
input gets <code>.create-item-input</code>). It is kept under the coverage contract — a
|
|
third-party app may still emit it — but it is deliberately not rendered here, because this
|
|
gallery shows the markup LuCI really produces.</p>
|
|
</div>
|
|
|
|
<div class="g-sec">
|
|
<h2>cbi-dropdown, MULTIPLE and open</h2>
|
|
<!-- The other half of the widget, and it is not just the single-select list with checkboxes: a
|
|
multi-select wraps every row's text in `<form><label>`, which is a second element for any
|
|
descendant rule in a field to hit (it is how the menu lost its sans face to the mono rule
|
|
in theme/60-inputs.css). The checkbox carries no `checked` attribute — ui.js paints the
|
|
state off `li[selected]` — so copy the shape exactly, including the empty <form>.
|
|
Absolutely positioned list, hence the row's bottom margin: gallery staging only. -->
|
|
<!-- Inside a .cbi-value-field on purpose: that is where a form's dropdown actually lives, and it
|
|
is the container whose descendant rule hands out the mono face. Staged bare, the menu never
|
|
meets the rule that used to take its font. -->
|
|
<div class="cbi-value" style="margin-bottom:200px">
|
|
<label class="cbi-value-title">Network bands</label>
|
|
<div class="cbi-value-field">
|
|
<div class="cbi-dropdown" multiple="" optional="" open="" tabindex="0">
|
|
<ul class="dropdown" tabindex="-1" style="top:38px;max-height:280px">
|
|
<li data-value="lan" selected="" display="0" tabindex="0" class="focus">
|
|
<form><input type="checkbox" tabindex="-1"></form><label>lan</label>
|
|
</li>
|
|
<li data-value="wan" tabindex="0">
|
|
<form><input type="checkbox" tabindex="-1"></form><label>wan</label>
|
|
</li>
|
|
<li data-value="wg0" selected="" display="1" tabindex="0">
|
|
<form><input type="checkbox" tabindex="-1"></form><label>wg0</label>
|
|
</li>
|
|
</ul>
|
|
<ul class="preview">
|
|
<li data-value="lan" selected="" display="0"><form><input type="checkbox" tabindex="-1"></form><label>lan</label></li>
|
|
<li data-value="wan"><form><input type="checkbox" tabindex="-1"></form><label>wan</label></li>
|
|
<li data-value="wg0" selected="" display="1"><form><input type="checkbox" tabindex="-1"></form><label>wg0</label></li>
|
|
</ul>
|
|
<span class="more" tabindex="-1">···</span>
|
|
<span class="open" tabindex="-1">▾</span>
|
|
<div tabindex="0"><input type="hidden" name="g-multi-open" value="lan"></div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<p class="g-note">What to look at: every row is the menu's own face, not the mono a field hands
|
|
its descendants — the <code><label></code> is a LABEL, and mono makes a wordy option list
|
|
(<code>3G Band 1</code>) read stretched. A chosen row says so with its CHECKBOX, so it does not
|
|
also wear the accent rail the single-select row above needs: the rail is an inset shadow on a
|
|
rounded row, i.e. an arc, and a column of them reads as a broken frame. The focused row wears
|
|
the theme's ring, inset — the browser's own <code>outline: auto</code> is drawn outside the row
|
|
and the menu clips it.</p>
|
|
</div>
|
|
|
|
<div class="g-sec">
|
|
<h2>Tabs</h2>
|
|
<!-- TWO strips, TWO active markers, and the difference is not cosmetic: `ul.tabs` is the
|
|
THEME's own section strip (fs-chrome.js renderTabMenu) and marks the current page with
|
|
`.active` + `aria-current="page"`, on an `li.tabmenu-item-<name>`; `ul.cbi-tabmenu` is
|
|
ui.js's in-form strip and marks it with `.cbi-tab`. theme/40-tabs.css says so and styles
|
|
both. This card used to write `<li class="cbi-tab">` in BOTH, so the first strip rendered
|
|
with NO active tab at all (measured: all three li carried the muted ink) — a shape nothing
|
|
can emit, which is exactly what a card must not show. -->
|
|
<ul class="tabs">
|
|
<li class="tabmenu-item-general active"><a href="#" aria-current="page">General Settings</a></li>
|
|
<li class="tabmenu-item-advanced"><a href="#">Advanced</a></li>
|
|
<li class="tabmenu-item-errors cbi-tab-disabled" data-errors="1"><a href="#">With errors</a></li>
|
|
</ul>
|
|
<ul class="cbi-tabmenu">
|
|
<li class="cbi-tab"><a href="#">Mode A</a></li>
|
|
<li class="cbi-tab-disabled"><a href="#">Mode B</a></li>
|
|
<!-- adblock's Overview page, RU: a `.cbi-tabmenu > li` label that is a sentence, not a word.
|
|
Narrow the window and this is the one that used to escape its column (measured at
|
|
320px/Density Large: 293px in a 264px column, 28.6px past it, no scroller) — the `a`
|
|
already ellipsises (base/50-chrome.css) but the li's own `max-width: none` never let it
|
|
shrink that far. EN has no fixture this long because EN never broke. -->
|
|
<li class="cbi-tab"><a href="#">Настройки межсетевого экрана</a></li>
|
|
</ul>
|
|
</div>
|
|
|
|
<div class="g-sec">
|
|
<h2>Alerts & notices</h2>
|
|
<div class="alert-message">Plain alert-message.</div>
|
|
<div class="alert-message notice">notice — a poll or a hint.</div>
|
|
<div class="alert-message info">info</div>
|
|
<div class="alert-message success">success</div>
|
|
<div class="alert-message warning">warning — configuration not committed.</div>
|
|
<div class="alert-message danger">danger — the interface will go down.</div>
|
|
<div class="alert-message error">error</div>
|
|
<div class="alert-message warning">
|
|
<h4>With a heading and actions</h4>
|
|
<p>Some explanatory body text.</p>
|
|
<div><button class="cbi-button">Stop showing</button> <button class="cbi-button">Dismiss</button></div>
|
|
</div>
|
|
<!-- The rest of what base styles inside an alert: h5, a list, and a <pre>. Only h4 and <p> were
|
|
here, so the audit's absorption backlog for `.alert-message li/ul/pre/h5` could not be
|
|
settled either way — an un-rendered widget shows no diff, which reads as "already dead". -->
|
|
<div class="alert-message danger">
|
|
<h5>Heading 5 inside an alert</h5>
|
|
<p>Body.</p>
|
|
<ul><li>a list item</li><li>another one</li></ul>
|
|
<pre>a <pre> block inside an alert</pre>
|
|
</div>
|
|
<!-- A table INSIDE an alert: the firmware Current/Available readout (admin/system/flash). The
|
|
theme flattens it so it does not read as a card on a card, and that rule had never been
|
|
rendered by anything here — a padding change to it measured 0 diffs because the shape was
|
|
missing. -->
|
|
<div class="alert-message warning">
|
|
<h4>Firmware update available</h4>
|
|
<table class="table">
|
|
<tr class="tr table-titles"><th class="th">Field</th><th class="th">Current</th><th class="th">Available</th></tr>
|
|
<tr class="tr"><td class="td">Version</td><td class="td">25.12.0</td><td class="td">25.12.1</td></tr>
|
|
<tr class="tr"><td class="td">Size</td><td class="td">7.8 MB</td><td class="td">7.9 MB</td></tr>
|
|
</table>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="g-sec">
|
|
<h2>Tooltips (.cbi-tooltip — the colour words too)</h2>
|
|
<!-- ui.js pops these on a .cbi-tooltip-container; the colour variants are what a third-party
|
|
luci-app-* uses for a validation hint, and NOTHING else in this gallery renders them. -->
|
|
<div style="position:relative;height:190px">
|
|
<div class="cbi-tooltip" style="position:static;display:block;opacity:1;margin:0 0 6px">plain .cbi-tooltip</div>
|
|
<div class="cbi-tooltip info" style="position:static;display:block;opacity:1;margin:0 0 6px">.cbi-tooltip.info</div>
|
|
<div class="cbi-tooltip success" style="position:static;display:block;opacity:1;margin:0 0 6px">.cbi-tooltip.success</div>
|
|
<div class="cbi-tooltip error" style="position:static;display:block;opacity:1;margin:0 0 6px">.cbi-tooltip.error</div>
|
|
<div class="cbi-tooltip notice" style="position:static;display:block;opacity:1">.cbi-tooltip.notice</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="g-sec">
|
|
<h2>.cbi-select (the non-JS select fallback)</h2>
|
|
<!-- What LuCI renders when fs-select.js has not (yet) turned a <select> into a ui.Dropdown,
|
|
and what a third-party package emits directly. base styles it as a field; the theme names
|
|
every input TYPE but never this class, so it was invisible to every check. -->
|
|
<div class="cbi-value">
|
|
<label class="cbi-value-title" for="g-cbisel">.cbi-select</label>
|
|
<div class="cbi-value-field">
|
|
<select id="g-cbisel" class="cbi-select"><option>one</option><option>two</option></select>
|
|
</div>
|
|
</div>
|
|
<div class="cbi-value">
|
|
<label class="cbi-value-title" for="g-cbisel-bad">.cbi-select.cbi-input-invalid</label>
|
|
<div class="cbi-value-field">
|
|
<select id="g-cbisel-bad" class="cbi-select cbi-input-invalid"><option>rejected value</option></select>
|
|
</div>
|
|
</div>
|
|
<!-- The other shape, and the one every check was blind to: luci-compat wraps the native select
|
|
in a DIV that carries the class, so `.cbi-select:not(select)` is a real element with a real
|
|
`select` child. Only this shape gets the shell paint (a replaced element cannot), which is
|
|
why the theme has a `:not(select)` rule at all — and with no example here, nothing measured
|
|
it. `[disabled]` is separate on purpose: base dims only the shell's ::before, so the state
|
|
is easy to lose. -->
|
|
<div class="cbi-value">
|
|
<label class="cbi-value-title" for="g-cbisel-shell">.cbi-select (the DIV shell)</label>
|
|
<div class="cbi-value-field">
|
|
<div class="cbi-select"><select id="g-cbisel-shell"><option>one</option><option>two</option></select></div>
|
|
</div>
|
|
</div>
|
|
<div class="cbi-value">
|
|
<label class="cbi-value-title" for="g-cbisel-off">.cbi-select[disabled] (the DIV shell)</label>
|
|
<div class="cbi-value-field">
|
|
<div class="cbi-select" disabled><select id="g-cbisel-off" disabled><option>unavailable</option></select></div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="g-sec">
|
|
<h2>Progress bars</h2>
|
|
<div class="cbi-progressbar" title="303.37 MiB / 484.11 MiB (62%)"><div style="width:62%"></div></div>
|
|
<div class="cbi-progressbar" title="3.46 MiB / 242.05 MiB (1%)"><div style="width:1%"></div></div>
|
|
<div class="cbi-progressbar" title="97%"><div style="width:97%"></div></div>
|
|
<!-- The three below are what window.progressbar() itself writes (menu-footstrap-common.js), not
|
|
hand-authored markup like the three above: role, aria-value* and — the reason these exist at
|
|
all — data-fs-level, which is the ONLY thing 25-progressbar.css's warn/danger rules match.
|
|
Nothing above carried that attribute, so computed-diff, a11y and export-tier never rendered
|
|
those two rules at all; a clean run proved nothing about them. None of the three carries
|
|
aria-label: none sits in a .cbi-value row or a table cell, so the function finds no label and
|
|
writes no attribute at all — the accessible name then falls back to `title`, the same reading
|
|
the visible `::after` shows (menu-footstrap-common.js, findProgressbarLabel). -->
|
|
<div class="cbi-progressbar" role="progressbar" aria-valuemin="0" aria-valuemax="100"
|
|
aria-valuenow="42" aria-valuetext="203.37 MiB / 484.11 MiB (42%)"
|
|
title="203.37 MiB / 484.11 MiB (42%)"><div style="width:42%"></div></div>
|
|
<div class="cbi-progressbar" data-fs-level="warn" role="progressbar" aria-valuemin="0" aria-valuemax="100"
|
|
aria-valuenow="85" aria-valuetext="411.49 MiB / 484.11 MiB (85%)"
|
|
title="411.49 MiB / 484.11 MiB (85%)"><div style="width:85%"></div></div>
|
|
<div class="cbi-progressbar" data-fs-level="danger" role="progressbar" aria-valuemin="0" aria-valuemax="100"
|
|
aria-valuenow="96" aria-valuetext="464.75 MiB / 484.11 MiB (96%)"
|
|
title="464.75 MiB / 484.11 MiB (96%)"><div style="width:96%"></div></div>
|
|
<!-- The same meter as the FIELD of a labelled CBI row, which is what an app building a form row
|
|
with ui.js emits (luci-app-modeminfo's Antennas page: one of these per figure). The six
|
|
above own their line and take the value above the bar; here the name is already in the left
|
|
column, so the value goes beside the meter instead. -->
|
|
<div class="cbi-value">
|
|
<label class="cbi-value-title">RSSI</label>
|
|
<div class="cbi-value-field"><div class="cbi-progressbar" title="-55.9 dBm"><div style="width:72%"></div></div></div>
|
|
</div>
|
|
<div class="cbi-value">
|
|
<label class="cbi-value-title">RSRP</label>
|
|
<div class="cbi-value-field"><div class="cbi-progressbar" title="-140.0 dBm"><div style="width:2%"></div></div></div>
|
|
</div>
|
|
<p class="g-note">What to look at: the two shapes read as one row each. In the bare meters the
|
|
value sits above the bar's right edge, pairing with whatever names the bar on the line above;
|
|
in a <code>.cbi-value</code> row it sits after the bar, on the row's own line, and the values
|
|
line up in a column because the reserve is fixed rather than shrink-wrapped.</p>
|
|
</div>
|
|
|
|
<div class="g-sec">
|
|
<h2>Badges & status</h2>
|
|
<div class="g-row">
|
|
<span class="ifacebadge">eth0</span>
|
|
<span class="ifacebadge ifacebadge-active">br-lan</span>
|
|
<!-- The inline style is firewall.js's getZoneColorStyle() verbatim: it declares
|
|
--zone-color-rgb and then paints FROM it. Writing a plain `background:#a4d1a4` here
|
|
was wrong in a way only dark mode shows: base/95-luci.css re-mixes the zone colour at
|
|
.4/.3 for dark, reading that var with NO fallback (deliberately — see the comment
|
|
there), so the !important declaration went invalid at computed-value time and the
|
|
badge lost its colour entirely (measured: background rgba(0,0,0,0) in dark). -->
|
|
<span class="zonebadge" style="--zone-color-rgb:164, 209, 164; background-color:rgb(var(--zone-color-rgb))">lan</span>
|
|
<span class="zonebadge zonebadge-empty">empty</span>
|
|
<span class="label">label</span>
|
|
<span class="label notice">notice</span>
|
|
<span class="label warning">warning</span>
|
|
<span class="label danger">danger</span>
|
|
<span class="label success">success</span>
|
|
</div>
|
|
<div class="g-row">
|
|
<div class="ifacebox" style="min-width:70px;max-width:120px">
|
|
<div class="ifacebox-head center active">wan</div>
|
|
<div class="ifacebox-body left">1000 Mbit/s<br>▲ 1.2 GiB ▼ 8.3 GiB</div>
|
|
</div>
|
|
<div class="ifacebox" style="min-width:70px;max-width:120px">
|
|
<div class="ifacebox-head center">lan2</div>
|
|
<div class="ifacebox-body left">no link</div>
|
|
</div>
|
|
</div>
|
|
<!-- The Overview's upstream plates (30_network.js): the SAME .ifacebox classes as the port
|
|
tiles above, wrapped in .network-status-table, and the reason that wrapper is styled
|
|
separately — these lines are ADDRESSES. The IPv6 one is deliberately uncompressed and
|
|
full length, because that is the case that broke: `.ifacebox` is `white-space: nowrap`,
|
|
an address has no spaces to wrap at, and the plate is a flex item that shrinks — so the
|
|
address printed outside its own border (measured at a 390px viewport: 45px past it).
|
|
Narrow the window here and the text must stay inside the box.
|
|
|
|
The third plate is the RU HEAD, the sibling bug: the head inherits the same `nowrap` and
|
|
had no override at all, so a sentence-length caption (LuCI's RU string for "IPv4 Upstream")
|
|
ran straight out of the box: measured at 320px/Density Large, 301px in a 288px column,
|
|
41.3px past #view, with the .cbi-section clipped by 42px and no scroller. EN has no fixture
|
|
this long because EN never broke. -->
|
|
<div class="network-status-table">
|
|
<div class="ifacebox">
|
|
<div class="ifacebox-head center active"><strong>IPv4 Upstream</strong></div>
|
|
<div class="ifacebox-body left">
|
|
<span><strong>Protocol:</strong> Static address<br><strong>Address:</strong> 203.0.113.42/24<br><strong>Gateway:</strong> 203.0.113.1<br><strong>DNS:</strong> 9.9.9.9<br><strong>Connected:</strong> 8d 12h 0m 57s</span>
|
|
<div><span class="ifacebadge"><img src="/luci-static/resources/icons/ethernet.svg" width="16" alt=""><span><strong>Device:</strong> Ethernet Adapter: "wwan0"<br><strong>MAC address:</strong> C6:68:C6:D1:EF:25</span></span></div>
|
|
</div>
|
|
</div>
|
|
<div class="ifacebox">
|
|
<div class="ifacebox-head center active"><strong>IPv6 Upstream</strong></div>
|
|
<div class="ifacebox-body left">
|
|
<span><strong>Protocol:</strong> Static address<br><strong>Prefix Delegated:</strong> 2001:db8:1a2b:3c4e:5e6f:7a8b:9c0d::/64<br><strong>Address:</strong> 2001:db8:1a2b:3c4d:5e6f:7a8b:9c0d:1e2f/64<br><strong>Gateway:</strong> 2001:db8:1a2b:3c4d::1<br><strong>Connected:</strong> 8d 12h 0m 57s</span>
|
|
<div><span class="ifacebadge"><img src="/luci-static/resources/icons/ethernet.svg" width="16" alt=""><span><strong>Device:</strong> Ethernet Adapter: "wwan0"<br><strong>MAC address:</strong> C6:68:C6:D1:EF:25</span></span></div>
|
|
</div>
|
|
</div>
|
|
<div class="ifacebox">
|
|
<div class="ifacebox-head center active"><strong>Подключение IPv4 (внешняя сеть)</strong></div>
|
|
<div class="ifacebox-body left">
|
|
<span><strong>Протокол:</strong> Статический адрес<br><strong>Адрес:</strong> 203.0.113.42/24<br><strong>Шлюз:</strong> 203.0.113.1<br><strong>Соединение:</strong> 8d 12h 0m 57s</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="g-row">
|
|
<span class="cbi-tooltip-container">hover for tooltip<span class="cbi-tooltip">The tooltip body.</span></span>
|
|
<em class="spinning">Loading…</em>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="g-sec">
|
|
<h2>Key/value table (no id) — System, Memory</h2>
|
|
<div class="cbi-section">
|
|
<h3>System</h3>
|
|
<div class="table">
|
|
<div class="tr"><div class="td">Hostname</div><div class="td">OpenWrt</div></div>
|
|
<div class="tr"><div class="td">Model</div><div class="td">Netcore N60 Pro</div></div>
|
|
<div class="tr"><div class="td">Uptime</div><div class="td">8d 14h 11m 18s</div></div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="g-sec">
|
|
<h2>Data table (with id) — leases, processes</h2>
|
|
<p class="g-note">On a live page fs-select.js measures this one and cards it when it stops fitting.
|
|
The gallery has no LuCI JS, so it is shown in both states: the table in a scroll frame (gallery
|
|
chrome, so a narrow viewport does not drag the whole page sideways), and the card below it.</p>
|
|
<div class="cbi-section g-overwide" tabindex="0" role="group" aria-label="Active DHCPv4 Leases (scrolls)">
|
|
<h3>Active DHCPv4 Leases</h3>
|
|
<!-- `.fs-dt` is written by fs-select.js on a live page (a table with a header row that is not
|
|
a config table). The gallery has no LuCI JS, so the fixture carries the tag it would be
|
|
given — without it, every rule the measured tier owns (the floor, the sticky header, the
|
|
card) is unrepresented here and tools/table-contract.mjs has nothing to measure. -->
|
|
<table class="table fs-dt" id="status_leases">
|
|
<tr class="tr table-titles">
|
|
<th class="th">Hostname</th><th class="th">IPv4 address</th>
|
|
<th class="th">MAC</th><th class="th">Lease time</th><th class="th"> </th>
|
|
</tr>
|
|
<!-- `data-title` on every cell, because `ui.Table` writes it on a live page and the card
|
|
view prints it: a fixture without it renders a card of unlabelled values, which is
|
|
the exact defect the contract gate exists to catch. -->
|
|
<tr class="tr cbi-rowstyle-1">
|
|
<td class="td" data-title="Hostname">Watch</td><td class="td" data-title="IPv4 address">10.11.12.131</td>
|
|
<td class="td" data-title="MAC">E6:EF:BA:54:DE:32</td><td class="td" data-title="Lease time">9h 10m 51s</td>
|
|
<td class="td"><button class="cbi-button cbi-button-apply">Reserve IP</button></td>
|
|
</tr>
|
|
<tr class="tr cbi-rowstyle-2">
|
|
<td class="td" data-title="Hostname">iPhone</td><td class="td" data-title="IPv4 address">10.11.12.182</td>
|
|
<td class="td" data-title="MAC">F4:A3:10:96:E0:53</td><td class="td" data-title="Lease time">8h 56m 4s</td>
|
|
<td class="td"><button class="cbi-button cbi-button-apply">Reserve IP</button></td>
|
|
</tr>
|
|
<!-- …and the SAME button mid-click, which is the shape issue #22 was reported on:
|
|
40_dhcp.js adds `spinning` to the button it was clicked on, and this row is inside
|
|
a `.table[id]`, whose cell-button rule is (0,5,0) — the rule that used to out-rank
|
|
the spinner's padding ladder and draw the glyph on top of the label. Rendered here
|
|
so a regression is visible without a router (issue #15 was the same bug one rule
|
|
lower down, and neither was in the gallery). -->
|
|
<tr class="tr cbi-rowstyle-1">
|
|
<td class="td" data-title="Hostname">nb-ivan</td><td class="td" data-title="IPv4 address">10.11.12.144</td>
|
|
<td class="td" data-title="MAC">A2:9C:41:07:BB:10</td><td class="td" data-title="Lease time">2h 28m 11s</td>
|
|
<td class="td cbi-section-actions"><button class="cbi-button cbi-button-apply spinning" disabled>Reserve IP</button></td>
|
|
</tr>
|
|
</table>
|
|
</div>
|
|
<!-- the same table in the state the fitter puts it in when it stops fitting: `.fs-stacked` is the
|
|
card, and it is what a phone actually gets. Rendered here so the card's own rules — the
|
|
`data-title` eyebrow, the half-width cell pairs, the full-width actions row — are visible and
|
|
measurable without a router. -->
|
|
<div class="cbi-section">
|
|
<h3>…and the same table carded (.fs-stacked)</h3>
|
|
<table class="table fs-dt fs-stacked" id="status_leases_carded">
|
|
<tr class="tr table-titles">
|
|
<th class="th">Hostname</th><th class="th">IPv4 address</th>
|
|
<th class="th">MAC</th><th class="th">Lease time</th><th class="th"> </th>
|
|
</tr>
|
|
<tr class="tr cbi-rowstyle-1">
|
|
<td class="td" data-title="Hostname">Watch</td><td class="td" data-title="IPv4 address">10.11.12.131</td>
|
|
<td class="td" data-title="MAC">E6:EF:BA:54:DE:32</td><td class="td" data-title="Lease time">9h 10m 51s</td>
|
|
<td class="td cbi-section-actions"><button class="cbi-button cbi-button-apply">Reserve IP</button></td>
|
|
</tr>
|
|
</table>
|
|
</div>
|
|
|
|
<!-- Two more header shapes fs-select.js's adoptMarkup() produces from a real <thead> — neither
|
|
is `.tr.table-titles`, which is what the frame's corner-rounding rule (30-tables.css) used
|
|
to key on alone, so the top corners stayed square under either shape (forum post #141). -->
|
|
<div class="cbi-section g-overwide" tabindex="0" role="group" aria-label="Framed table, thead with bare th, no tr (scrolls)">
|
|
<h3>Header row: <thead> with <th> directly inside, no <tr> (issue #141)</h3>
|
|
<!-- include/20_lan.js (DHCP table) builds its header with E(), which appends the <th>s
|
|
straight to the <thead> — the parser's implied <tr> never happens, so the header cells
|
|
are the thead's own direct children. -->
|
|
<table class="table fs-dt" id="table_head_bare_thead">
|
|
<thead class="thead">
|
|
<th class="th">Host</th><th class="th">IPv4 address</th><th class="th">MAC</th>
|
|
</thead>
|
|
<tr class="tr cbi-rowstyle-1">
|
|
<td class="td" data-title="Host">router</td><td class="td" data-title="IPv4 address">10.0.0.1</td>
|
|
<td class="td" data-title="MAC">AA:BB:CC:DD:EE:FF</td>
|
|
</tr>
|
|
</table>
|
|
</div>
|
|
<div class="cbi-section g-overwide" tabindex="0" role="group" aria-label="Framed table, thead wrapping a real tr (scrolls)">
|
|
<h3>Header row: <thead> wrapping a real <tr>, no .table-titles (issue #141)</h3>
|
|
<!-- include/30_wifi.js (Wi-Fi table) nests a real <tr> inside <thead>; adoptMarkup() marks
|
|
the <thead> itself `.thead` but never adds `.tr`/`.table-titles` to the row nested
|
|
inside it, so a selector keyed on `.tr.table-titles` alone misses this shape too. -->
|
|
<table class="table fs-dt" id="table_head_thead_tr">
|
|
<thead class="thead">
|
|
<tr class="dashboard-bg">
|
|
<th class="th">SSID</th><th class="th">Channel</th><th class="th">Signal</th>
|
|
</tr>
|
|
</thead>
|
|
<tr class="tr cbi-rowstyle-1">
|
|
<td class="td" data-title="SSID">HomeNet</td><td class="td" data-title="Channel">36</td>
|
|
<td class="td" data-title="Signal">-52 dBm</td>
|
|
</tr>
|
|
</table>
|
|
</div>
|
|
|
|
<h2>The shapes that broke it — one fixture per report</h2>
|
|
<!-- These are not decoration: each is the exact value that produced a bug, kept so the contract
|
|
gate (tools/table-contract.mjs) has something to squeeze. A theme with no floor starves the
|
|
column holding one of these, and the browser then reports, truthfully, that the table fits. -->
|
|
<div class="cbi-section g-overwide" tabindex="0" role="group" aria-label="Data table with the reported values (scrolls)">
|
|
<h3>Data table with the reported values</h3>
|
|
<table class="table fs-dt" id="pathological">
|
|
<tr class="tr table-titles">
|
|
<th class="th">Client</th><th class="th">MAC</th><th class="th">Encryption</th>
|
|
<th class="th">Key</th><th class="th">Package</th>
|
|
</tr>
|
|
<tr class="tr cbi-rowstyle-1">
|
|
<!-- a hyphenated identifier: `break-word` may use the hyphen, which is why
|
|
pages/30-software.css still nowraps the package name -->
|
|
<td class="td">Yandex-Station-Midi-61QK</td>
|
|
<!-- a colon is NOT a break opportunity: this is one 17-character token (issue #5) -->
|
|
<td class="td">48:E1:5C:9F:26:20</td>
|
|
<!-- slash and hyphen ARE break opportunities: the canvas floor used to measure this
|
|
at 144px against a real 93 (which is why it carded tables that had room) -->
|
|
<td class="td">WPA2-PSK/CCMP</td>
|
|
<!-- 130 characters with nothing to break on: luci-app-3ginfo-lite's ICCID shape -->
|
|
<td class="td">qHMcYUJ3RJ5Zr1Qz8xN2vB7kL0pW6sT4dG9fH1jK3mP5nR7tV9xZ2bC4eF6gJ8lN0qS2uW4yA6cE8hK1mO3rT5vX7zB9dG1jL3nQ5sU7wY9aD2fH4kM6pR8tV0x</td>
|
|
<td class="td">luci-app-attendedsysupgrade</td>
|
|
</tr>
|
|
</table>
|
|
</div>
|
|
<div class="cbi-section g-overwide" tabindex="0" role="group" aria-label="Key/value with an unbreakable value (scrolls)">
|
|
<h3>Key/value with an unbreakable value (issue #36)</h3>
|
|
<!-- luci-app-dockerman prints JSON.stringify() of everything `docker info` returns. This tier
|
|
has no header row, so it can never card: `overflow-wrap: anywhere` is what keeps it inside
|
|
the card, and that is the half of the split base/40-tables.css owns. -->
|
|
<div class="table">
|
|
<div class="tr"><div class="td">ENTRYPOINT</div><div class="td">xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx</div></div>
|
|
<div class="tr"><div class="td">Driver</div><div class="td">overlay2</div></div>
|
|
</div>
|
|
</div>
|
|
<div class="cbi-section g-overwide" tabindex="0" role="group" aria-label="Log without a header row (scrolls)">
|
|
<h3>No header row — a log, which scrolls rather than cards</h3>
|
|
<!-- `.fs-xscroll` is written by fs-select.js when such a table is MEASURED to overflow and
|
|
holds no widget. Carried here so the pinned first column and the focus ring are visible
|
|
without a router. -->
|
|
<div class="table fs-xscroll" tabindex="0" role="group" aria-label="System log (sample)">
|
|
<div class="tr">
|
|
<div class="td nowrap">2026-08-14 20:43:11</div><div class="td nowrap">daemon.notice</div>
|
|
<div class="td nowrap">netifd</div>
|
|
<div class="td nowrap">Interface wan is now up, protocol dhcp, device eth0.2, metric 0</div>
|
|
</div>
|
|
<div class="tr">
|
|
<div class="td nowrap">2026-08-14 20:43:12</div><div class="td nowrap">daemon.warn</div>
|
|
<div class="td nowrap">dnsmasq</div>
|
|
<div class="td nowrap">reducing DNS packet size for nameserver 2001:db8:1a2b:3c4d:5e6f:7a8b:9c0d:1e2f to 1280</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<h2>The forcing utilities (.left/.right/.center + .top/.middle/.bottom)</h2>
|
|
<!-- LuCI writes these on a cell to OVERRIDE the table's own alignment — `<td class="td right">`
|
|
— which is why they carry an `!important`: a bare `.right` (0,1,0) loses to the theme's
|
|
`.cbi-section .table .td` (0,3,0) on specificity alone. Six of the eight flags in
|
|
styles/base are these. Nothing rendered them, so stripping the flags showed no diff and
|
|
read as "the flags do nothing". They do. (They are not the theme's ONLY flags outside the
|
|
reduced-motion block, which is what this comment used to claim: there are 22 of those, each
|
|
fighting an inline `style=` or an unlayered `<style>` — docs/conventions.md accounts for
|
|
every one.) -->
|
|
<div class="cbi-section g-overwide" tabindex="0" role="group" aria-label="Forcing utilities table (scrolls)">
|
|
<table class="table" id="forcing-utils">
|
|
<tr class="tr table-titles">
|
|
<th class="th left">left</th><th class="th center">center</th><th class="th right">right</th>
|
|
<th class="th top">top</th><th class="th middle">middle</th><th class="th bottom">bottom</th>
|
|
</tr>
|
|
<tr class="tr cbi-rowstyle-1">
|
|
<td class="td left">left</td>
|
|
<td class="td center">center</td>
|
|
<td class="td right">right</td>
|
|
<td class="td top">top<br>two lines</td>
|
|
<td class="td middle">middle<br>two lines</td>
|
|
<td class="td bottom">bottom<br>two lines</td>
|
|
</tr>
|
|
</table>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="g-sec">
|
|
<h2>Key/value table, narrow container (System -> System at 390px)</h2>
|
|
<p class="g-note">The @container fs-content query below only fires inside a container narrower than
|
|
560px. `.g-wrap` (this page's only other fs-content container) never gets that narrow, so
|
|
without a fixture of its own this rule is unrendered and computed-diff reads 0 for it. The
|
|
340px `.cbi-map` here stands in for the sidebar layout's cramped content column.</p>
|
|
<div class="cbi-map" style="max-width: 340px">
|
|
<div class="cbi-section">
|
|
<h3>System</h3>
|
|
<div class="table">
|
|
<div class="tr"><div class="td">Firmware Version</div><div class="td">OpenWrt 24.10.5 r28134-cb8087f118</div></div>
|
|
<div class="tr"><div class="td">Kernel Version</div><div class="td">6.6.104</div></div>
|
|
<div class="tr"><div class="td">Architecture</div><div class="td">ARMv8 Processor rev 4</div></div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="g-sec">
|
|
<h2>CBI section table — firewall zones, port forwards</h2>
|
|
<div class="cbi-section cbi-tblsection">
|
|
<h3>Zones</h3>
|
|
<table class="table cbi-section-table">
|
|
<tr class="tr cbi-section-table-titles">
|
|
<th class="th cbi-section-table-cell">Zone</th>
|
|
<th class="th cbi-section-table-cell">Input</th>
|
|
<th class="th cbi-section-table-cell cbi-section-actions"> </th>
|
|
</tr>
|
|
<tr class="tr cbi-section-table-row cbi-rowstyle-1">
|
|
<td class="td cbi-value-field cbi-section-table-cell">lan</td>
|
|
<td class="td cbi-value-field cbi-section-table-cell">accept</td>
|
|
<td class="td cbi-section-table-cell nowrap cbi-section-actions">
|
|
<button class="cbi-button drag-handle center">☰</button>
|
|
<button class="btn cbi-button cbi-button-edit">Edit</button>
|
|
<button class="btn cbi-button cbi-button-remove">Delete</button>
|
|
</td>
|
|
</tr>
|
|
<tr class="tr cbi-section-table-row cbi-rowstyle-2">
|
|
<td class="td cbi-value-field cbi-section-table-cell">wan</td>
|
|
<td class="td cbi-value-field cbi-section-table-cell">reject</td>
|
|
<td class="td cbi-section-table-cell nowrap cbi-section-actions">
|
|
<button class="cbi-button drag-handle center">☰</button>
|
|
<button class="btn cbi-button cbi-button-edit">Edit</button>
|
|
<button class="btn cbi-button cbi-button-remove">Delete</button>
|
|
</td>
|
|
</tr>
|
|
</table>
|
|
<div class="cbi-section-create"><button class="cbi-button cbi-button-add">Add</button></div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="g-sec">
|
|
<h2>CBI section table — header reflow at the card boundary (RU, task 0163)</h2>
|
|
<p class="g-note">`.cbi-map` pinned to 968px — firewall/zones' own measured content column at
|
|
1024px, live, RU. The card fires on THIS element (theme/45-misc.css), one level above
|
|
`.cbi-section`'s own `--fs-card-pad`, so 968px here only hands the table 936px. The six real
|
|
header strings below need ~1017px at the word floor (base/40-tables.css) — before the 992px
|
|
fix (theme/65-dropdown.css) this table stayed a table at 968px and clipped its own header row
|
|
with no scroller in reach; 968 now clears 992 and the table cards instead.</p>
|
|
<div class="cbi-map" style="max-width: 968px">
|
|
<div class="cbi-section cbi-tblsection">
|
|
<h3>Зоны</h3>
|
|
<table class="table cbi-section-table">
|
|
<tr class="tr cbi-section-table-titles">
|
|
<th class="th cbi-section-table-cell">Зона ⇒ Перенаправления</th>
|
|
<th class="th cbi-section-table-cell">Входящий трафик</th>
|
|
<th class="th cbi-section-table-cell">Исходящий трафик</th>
|
|
<th class="th cbi-section-table-cell">Внутризональная пересылка</th>
|
|
<th class="th cbi-section-table-cell">Маскарадинг IPv4</th>
|
|
<th class="th cbi-section-table-cell cbi-section-actions"> </th>
|
|
</tr>
|
|
<tr class="tr cbi-section-table-row cbi-rowstyle-1">
|
|
<td class="td cbi-value-field cbi-section-table-cell">lan</td>
|
|
<td class="td cbi-value-field cbi-section-table-cell">принимать</td>
|
|
<td class="td cbi-value-field cbi-section-table-cell">принимать</td>
|
|
<td class="td cbi-value-field cbi-section-table-cell">да</td>
|
|
<td class="td cbi-value-field cbi-section-table-cell">нет</td>
|
|
<td class="td cbi-section-table-cell nowrap cbi-section-actions">
|
|
<button class="btn cbi-button cbi-button-edit">Правка</button>
|
|
</td>
|
|
</tr>
|
|
</table>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- task 0147 (second RU sweep), finding 3: base/95-luci.css sizes each row-action button
|
|
`flex: 1 1 4em` inside `.td.cbi-section-actions`, a plain <table>/.td markup (Startup, not
|
|
form.js's .cbi-section-table above) with no per-row carding. A definite flex-basis caps the
|
|
automatic minimum at 4em once the row runs out of room, so a caption longer than that shrinks
|
|
the BOX below its own text instead of the text — theme/55-buttons.css's ellipsis is what stops
|
|
the overflow from painting onto the next button. Captions are the real Startup strings. -->
|
|
<div class="g-sec">
|
|
<h2>Row actions, plain table (Startup-style, no .cbi-section-table)</h2>
|
|
<div class="cbi-section">
|
|
<div class="table" style="max-width: 340px">
|
|
<div class="tr">
|
|
<div class="td">dnsmasq</div>
|
|
<div class="td cbi-section-actions">
|
|
<form>
|
|
<button class="cbi-button cbi-button-positive">Запустить</button>
|
|
<button class="cbi-button">Перезапустить</button>
|
|
<button class="cbi-button cbi-button-negative">Остановить</button>
|
|
<button class="cbi-button">Принудительно завершить</button>
|
|
</form>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="g-sec">
|
|
<h2>Pager</h2>
|
|
<div class="pager center">
|
|
<a class="btn prev" href="#">«</a>
|
|
<div class="text">Page 2 of 7</div>
|
|
<a class="btn next" href="#">»</a>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="g-sec">
|
|
<h2>The chrome bar (#indicators, .fs-themerow, .fs-logout)</h2>
|
|
<!-- The bar's own controls, in the shape the chrome really emits: the buttons come from
|
|
ucode/template/themes/footstrap/partials/search.ut and logout.ut, and #indicators is the
|
|
empty div header.ut leaves for LuCI (`<div id="indicators" class="fs-indicators">`), which
|
|
ui.js fills with a BARE `<span data-indicator=…>` — no class, which is exactly why a theme
|
|
rule written against `.label` painted nothing and no gate noticed. Nothing here needs the
|
|
chrome JS: fs-chrome.js only adds .fs-ind-compact/.fs-bar-actrow by measurement, and their
|
|
absence is the ordinary wide-bar case this fixture stands for. -->
|
|
<div class="fs-sidebar" style="position:static">
|
|
<div class="fs-indicators" id="indicators">
|
|
<span data-indicator="poll-status" data-style="active">Refreshing</span>
|
|
<span data-indicator="uci-changes" data-clickable="true">Unsaved: 3</span>
|
|
</div>
|
|
<button type="button" class="fs-themerow" aria-label="Search">
|
|
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" aria-hidden="true"><circle cx="11" cy="11" r="7"/><path d="M16.5 16.5 21 21"/></svg>
|
|
<span class="fs-label">Search</span>
|
|
</button>
|
|
<a class="fs-logout" href="#" aria-label="Log out">
|
|
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" aria-hidden="true"><path d="M10 4H6a2 2 0 0 0-2 2v12a2 2 0 0 0 2 2h4"/><path d="M14 12h7m0 0-3-3m3 3-3 3"/></svg>
|
|
<span class="fs-label">Log out</span>
|
|
</a>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="g-sec">
|
|
<h2>Modal</h2>
|
|
<!-- `opacity`/`visibility`, not just `position`/`display`: base/60-modal.css hides the overlay
|
|
until <body> carries `modal-overlay-active`, so this whole section rendered NOTHING — and
|
|
axe skips what is invisible, which meant the a11y gate had never once measured a dialog.
|
|
The body class is not usable here: it also sets `overflow: hidden; height: 100dvh`. -->
|
|
<div id="modal_overlay" style="position:static;display:block;background:transparent;padding:0;opacity:1;visibility:visible">
|
|
<div class="modal" style="margin:0">
|
|
<h4>Modal title</h4>
|
|
<p>Modal body — this is what a confirmation or an edit form looks like.</p>
|
|
<div class="right button-row">
|
|
<button class="cbi-button cbi-button-neutral">Dismiss</button>
|
|
<button class="cbi-button cbi-button-positive important">Confirm</button>
|
|
</div>
|
|
</div>
|
|
<!-- flash.js's "Flash image?" dialog: prose inside `label.btn`, and a checksum `<li>`
|
|
with no break opportunity. Both used to force the overlay to scroll sideways. -->
|
|
<div class="modal" style="margin:1em 0 0">
|
|
<h4>Flash image?</h4>
|
|
<p>The flash image was uploaded. Below is the checksum and file size listed, compare them with the original file to ensure data integrity.</p>
|
|
<ul>
|
|
<li>Size: 8.25 MB</li>
|
|
<li>SHA256: 5d27b68d801a4569696991c2435437508dff558b0c1f5d27b68d801a4569696b</li>
|
|
</ul>
|
|
<p><label class="btn"><input type="checkbox" checked> Keep settings and retain the current configuration</label></p>
|
|
<p><label class="btn"><input type="checkbox"> Include in backup a list of current installed packages at /etc/backup/installed_packages.txt</label></p>
|
|
<p class="alert-message danger"><label>Image check failed:<br><br>Invalid image type</label></p>
|
|
<p><label class="btn alert-message danger"><input type="checkbox"> Force upgrade<br><br>Select 'Force upgrade' to flash the image even if the image format check fails. Use only if you are sure that the firmware is correct and meant for your device!</label></p>
|
|
<div class="right button-row">
|
|
<button class="cbi-button cbi-button-neutral">Cancel</button>
|
|
<button class="cbi-button cbi-button-action important">Continue</button>
|
|
</div>
|
|
</div>
|
|
<!-- Reproduces luci-mod-dashboard's `.Dashboard … .devices-info .tr .td:nth-child(3) {
|
|
position:relative; top:-3px }` (issue #141, post #141): fs-sheets.js re-hosts an app's
|
|
<style> into `@layer theme`, so the app rule below meets 30-tables.css's `:is(#view,
|
|
#modal_overlay) .fs-dt .td { top: auto; bottom: auto }` in the SAME layer, and it is
|
|
specificity that decides — the mock selector carries the 7 classes the report measured,
|
|
none of them an id, so footstrap's rule (one id, two classes) wins with no
|
|
`!important`. Placed under `#modal_overlay` because that counter-rule, unlike the
|
|
corner-rounding one above, has no bare `table.fs-dt` fallback: every real table already
|
|
sits under `#view` or `#modal_overlay` (docs/css.md, "the card contract"), so the id is
|
|
load-bearing, not decorative, and a fixture outside both roots would not exercise it. -->
|
|
<style>
|
|
@layer theme {
|
|
.g-mock-a.g-mock-b.g-mock-c.g-mock-d.g-mock-e .td:nth-child(2) { position: relative; top: -3px; }
|
|
}
|
|
</style>
|
|
<div class="modal g-mock-a g-mock-b g-mock-c g-mock-d g-mock-e" style="margin:1em 0 0">
|
|
<h4>Row separator vs. an app's offset cell</h4>
|
|
<table class="table fs-dt" id="table_head_offset_td">
|
|
<tr class="tr table-titles">
|
|
<th class="th">Client</th><th class="th">RSSI</th><th class="th">Uptime</th>
|
|
</tr>
|
|
<tr class="tr cbi-rowstyle-1">
|
|
<td class="td" data-title="Client">iPhone</td>
|
|
<td class="td" data-title="RSSI">-52 dBm</td>
|
|
<td class="td" data-title="Uptime">2h 14m</td>
|
|
</tr>
|
|
</table>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="g-sec">
|
|
<h2>Overview card header — disclosure control (pages/20-overview.css)</h2>
|
|
<!-- fs-overview.js wireDisclosure() promotes the card heading to role="button", with the
|
|
keyboard-only pieces (tabindex, aria-expanded, aria-controls) plus a hand cursor and a
|
|
focus ring from the theme (pages/20-overview.css). The wrapper differs by release
|
|
(fs-overview.js headerEl()): 25.12 wraps the heading in `.cbi-title`, 24.10 leaves a bare
|
|
`<h3>` as the section's own first child, and the theme rule has to reach both — this is
|
|
the specimen that measures it, computed-diff otherwise reading 0 for this file. Appended
|
|
after Modal; nothing above is touched or reordered. -->
|
|
<div class="cbi-section">
|
|
<div class="cbi-title">
|
|
<h3 role="button" tabindex="0" aria-controls="g-ovl-panel-1" aria-expanded="true">System
|
|
<span class="label" data-indicator="poll-status" data-style="inactive" aria-hidden="true">Hide</span>
|
|
</h3>
|
|
</div>
|
|
<div id="g-ovl-panel-1">
|
|
<div class="table">
|
|
<div class="tr"><div class="td">Hostname</div><div class="td">OpenWrt</div></div>
|
|
<div class="tr"><div class="td">Model</div><div class="td">Generic</div></div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="cbi-section">
|
|
<h3 role="button" tabindex="0" aria-expanded="false">Memory
|
|
<span class="label" data-indicator="poll-status" data-style="active" aria-hidden="true">Show</span>
|
|
</h3>
|
|
</div>
|
|
</div>
|
|
|
|
</div>
|
|
</body>
|
|
</html>
|