css: .row style guide + conditional templates — fix 'log out' breaking + note overlap
The previous .row rule unconditionally pinned column 2 at 1fr, which stretched whichever child happened to land there. On the screenshot that meant the 'log out' button got stretched and wrapped its label across two lines, and the trailing note overlapped buttons it was supposed to describe. New rules (now also documented in CLAUDE.md as a style guide so future authoring is consistent): - default .row: grid-auto-columns: max-content (everything packs at its natural width, no stretch) - :has(> :first-child + input/select): template 'auto 1fr', input grows - :has(> input/select:first-child): template '1fr', input fills, rest pack - .row > .note: auto-drops to its own line under the buttons/inputs via grid-column: 1 / -1 - .row > label: white-space: normal, so long checkbox labels wrap Applied to zebra-spaces, chat, zebra-audio. CLAUDE.md "Web style guide — form-row patterns" table lists every supported shape so new rows reuse the primitive instead of inventing custom layouts.
This commit is contained in:
parent
a2d5071b71
commit
327cb63b00
4 changed files with 102 additions and 17 deletions
34
CLAUDE.md
34
CLAUDE.md
|
|
@ -165,6 +165,40 @@ If you need a one-off horizontal alignment of two short inline things
|
|||
(e.g. a label + a value), grid still works fine
|
||||
(`grid-template-columns: auto 1fr`). Don't reach for flex.
|
||||
|
||||
### Web style guide — form-row patterns
|
||||
|
||||
`.row` is the single primitive for every horizontal form strip in every
|
||||
zebra page. Don't invent new wrappers. CSS auto-detects the row shape via
|
||||
`:has()` and picks the right grid template.
|
||||
|
||||
Supported row shapes (DOM order matters):
|
||||
|
||||
| Shape | Template applied | Use when |
|
||||
|---|---|---|
|
||||
| `<button> <button> ...` (no input) | `grid-auto-columns: max-content` (default — pack) | "stop sharing" + "stop camera" + select-camera; vault buttons |
|
||||
| `<label> <input>` or `<label> <select>` | `auto 1fr` (input cell stretches) | handle row, mic-input row |
|
||||
| `<label> <input> <button>` | `auto 1fr` + extra cells packed | rare; same template as above + a trailing packed cell |
|
||||
| `<input> <button>` (input first) | `1fr` (input fills, button packs after) | rendezvous code + enter; password + export; share URL + copy |
|
||||
| anything + `<span class="note">` | the `.note` is auto-placed on its own row beneath via `grid-column: 1 / -1` | descriptive subtext after buttons |
|
||||
|
||||
Rules:
|
||||
|
||||
- Never put inline `style="flex:1"` on a row child. Use `.row` and trust
|
||||
the template selector.
|
||||
- Don't add a stretchy `<div>` to fake spacing. If you need a button
|
||||
group on the right, append the buttons as siblings — they'll pack
|
||||
right of the stretchy cell.
|
||||
- A `<span class="note">` inside `.row` *always* drops to its own line.
|
||||
If you want note text on the same line as a button, use a different
|
||||
class (e.g. inline `<span>`).
|
||||
- Long checkbox labels get `white-space: normal` automatically, so a
|
||||
music-mode-style "raw mic, no echo/noise cancellation (for playing
|
||||
audio through it)" wraps cleanly inside its column.
|
||||
|
||||
If a new row shape doesn't fit the patterns above, add the case to this
|
||||
table and add the matching `:has()` selector — don't reach for inline
|
||||
styles or flex.
|
||||
|
||||
### Web page integrity stamping
|
||||
|
||||
Each deployed page (`web/chat.html`, `web/zebra-audio.html`, `web/how-it-works.html`,
|
||||
|
|
|
|||
|
|
@ -54,11 +54,26 @@
|
|||
button.invert { background: #000; color: #fff; }
|
||||
button.invert:hover:not(:disabled) { background: #333; }
|
||||
.row {
|
||||
display: grid; grid-auto-flow: column;
|
||||
grid-template-columns: max-content minmax(0, 1fr);
|
||||
display: grid;
|
||||
grid-auto-flow: column;
|
||||
grid-auto-columns: max-content;
|
||||
align-items: center; gap: 0.75rem; margin-bottom: 0.75rem;
|
||||
}
|
||||
.row:has(> :first-child + input[type=text]),
|
||||
.row:has(> :first-child + input[type=password]),
|
||||
.row:has(> :first-child + select) {
|
||||
grid-template-columns: max-content minmax(0, 1fr);
|
||||
}
|
||||
.row:has(> input[type=text]:first-child),
|
||||
.row:has(> input[type=password]:first-child),
|
||||
.row:has(> select:first-child) {
|
||||
grid-template-columns: minmax(0, 1fr);
|
||||
}
|
||||
.row > .note {
|
||||
grid-column: 1 / -1;
|
||||
margin-top: 0.05rem; line-height: 1.4;
|
||||
}
|
||||
.row > label { white-space: normal; }
|
||||
.dot {
|
||||
width: 9px; height: 9px; border-radius: 50%;
|
||||
border: 1px solid #000; background: #fff;
|
||||
|
|
@ -1732,8 +1747,8 @@ logLine('sys', 'chat content lives in encrypted SRTP audio. no IP packets carry
|
|||
</script>
|
||||
<footer style="max-width:820px;margin:2.2rem auto 0;font-size:0.65rem;color:#999;line-height:1.7;word-break:break-all;font-family:monospace">
|
||||
<span style="color:#777">page integrity</span> · built <span class="stamp-date">2026-06-02</span><br>
|
||||
md5 <span class="stamp-md5">5ae64bcf96a54da7ea55a0df88548920</span><br>
|
||||
sha256 <span class="stamp-sha">06a3c011ec9670d85c6ab27b8decbfcc4f78e8a9be8186407670fdf4e4cc8dfd</span><br>
|
||||
md5 <span class="stamp-md5">6e1da66dbcde72c3d1d9956086fc78cc</span><br>
|
||||
sha256 <span class="stamp-sha">96ede5ac72e954e4c39c3c9e3888bd3feb4c20c7dc182f22a3468befa3140849</span><br>
|
||||
<span style="color:#bbb">hashes are of this page with these two fields zeroed — to verify, blank them and re-hash</span><br>
|
||||
<span style="color:#bbb">one self-contained file — <strong>save a copy</strong> and verify it against these hashes; point it at your own servers with ?signal= and ?turncred=, or <a href="host-your-own.html" style="color:#999">host your own community</a></span>
|
||||
</footer>
|
||||
|
|
|
|||
|
|
@ -40,11 +40,26 @@
|
|||
button.invert { background: #000; color: #fff; }
|
||||
button.invert:hover:not(:disabled) { background: #333; }
|
||||
.row {
|
||||
display: grid; grid-auto-flow: column;
|
||||
grid-template-columns: max-content minmax(0, 1fr);
|
||||
display: grid;
|
||||
grid-auto-flow: column;
|
||||
grid-auto-columns: max-content;
|
||||
align-items: center; gap: 0.75rem; margin-bottom: 0.75rem;
|
||||
}
|
||||
.row:has(> :first-child + input[type=text]),
|
||||
.row:has(> :first-child + input[type=password]),
|
||||
.row:has(> :first-child + select) {
|
||||
grid-template-columns: max-content minmax(0, 1fr);
|
||||
}
|
||||
.row:has(> input[type=text]:first-child),
|
||||
.row:has(> input[type=password]:first-child),
|
||||
.row:has(> select:first-child) {
|
||||
grid-template-columns: minmax(0, 1fr);
|
||||
}
|
||||
.row > .note {
|
||||
grid-column: 1 / -1;
|
||||
margin-top: 0.05rem; line-height: 1.4;
|
||||
}
|
||||
.row > label { white-space: normal; }
|
||||
input[type=text] {
|
||||
font-family: monospace; font-size: 0.9rem; border: 1px solid #000;
|
||||
padding: 0.45rem; background: #fff; color: #000; min-width: 0; width: 100%;
|
||||
|
|
@ -732,8 +747,8 @@ else wirePuppet();
|
|||
|
||||
<footer style="max-width:560px;margin:2.2rem auto 0;font-size:0.65rem;color:#999;line-height:1.7;word-break:break-all;font-family:monospace">
|
||||
<span id="pi-seal" style="color:#777;cursor:default;user-select:none" title="">page integrity</span> · built <span class="stamp-date">2026-06-02</span><br>
|
||||
md5 <span class="stamp-md5">47f8c9b6c4c4e3d2dc0544cf59da1efc</span><br>
|
||||
sha256 <span class="stamp-sha">a9532ca9dbe91f11d63ece9073f558e3beef5e5ead524b85972c8f0436a8aa41</span><br>
|
||||
md5 <span class="stamp-md5">cca00d784accd4d70abac005cdf3b6c7</span><br>
|
||||
sha256 <span class="stamp-sha">9a9e52d79a8fb6723048235562d6d81f2642fd8b7cdfe6dab9055bef8ae0eac9</span><br>
|
||||
<span style="color:#bbb">hashes are of this page with these two fields zeroed — to verify, blank them and re-hash</span><br>
|
||||
<span style="color:#bbb">one self-contained file — <strong>save a copy</strong> and verify it against these hashes; point it at your own servers with ?signal= and ?turncred=, or <a href="host-your-own.html" style="color:#999">host your own community</a></span>
|
||||
</footer>
|
||||
|
|
|
|||
|
|
@ -298,19 +298,40 @@
|
|||
button.invert { background: #000; color: #fff; }
|
||||
button.invert:hover:not(:disabled) { background: #333; }
|
||||
button.small { padding: 0.2rem 0.55rem; font-size: 0.75rem; }
|
||||
/* form rows: inline strip with a single grow track for the input/select.
|
||||
* grid-template-columns is generous enough for the common shapes:
|
||||
* label input (1) label-stretchy
|
||||
* label input button (2) label-stretchy-button
|
||||
* button button span (3) buttons-stretchy
|
||||
* Items past the third just flow into the row via grid-auto-flow. */
|
||||
/* form-row style guide — every form row is a grid.
|
||||
* default children pack at max-content width, no stretch
|
||||
* <something> <input/select> ... → template "auto 1fr ..." so the
|
||||
* input cell grows
|
||||
* <input/select> ... → template "1fr ..." so the field fills,
|
||||
* packed siblings follow
|
||||
* any <span.note> child → moved to its own row inside the grid so
|
||||
* long text never competes with the
|
||||
* buttons it explains
|
||||
* Items beyond the templated cells flow via grid-auto-columns:max-content. */
|
||||
.row {
|
||||
display: grid;
|
||||
grid-auto-flow: column;
|
||||
grid-template-columns: max-content minmax(0, 1fr);
|
||||
grid-auto-columns: max-content;
|
||||
align-items: center; gap: 0.6rem; margin-bottom: 0.6rem;
|
||||
}
|
||||
.row:has(> :first-child + input[type=text]),
|
||||
.row:has(> :first-child + input[type=password]),
|
||||
.row:has(> :first-child + select) {
|
||||
grid-template-columns: max-content minmax(0, 1fr);
|
||||
}
|
||||
.row:has(> input[type=text]:first-child),
|
||||
.row:has(> input[type=password]:first-child),
|
||||
.row:has(> select:first-child) {
|
||||
grid-template-columns: minmax(0, 1fr);
|
||||
}
|
||||
/* notes inside a row drop to their own line under the buttons/inputs
|
||||
* instead of fighting them for horizontal space */
|
||||
.row > .note {
|
||||
grid-column: 1 / -1;
|
||||
margin-top: 0.05rem; line-height: 1.4;
|
||||
}
|
||||
/* labels wrapping a checkbox + long text break naturally */
|
||||
.row > label { white-space: normal; }
|
||||
input[type=text], input[type=password] {
|
||||
font-family: monospace; font-size: 0.9rem; border: 1px solid #000;
|
||||
padding: 0.4rem; background: #fff; color: #000; min-width: 0; width: 100%;
|
||||
|
|
@ -2301,8 +2322,8 @@ logLine('', 'ready — pick a handle, type a rendezvous code, enter the space');
|
|||
|
||||
<footer style="margin:2.2rem auto 0;font-size:0.65rem;color:#999;line-height:1.7;word-break:break-all;font-family:monospace">
|
||||
<span id="pi-seal" style="color:#777;cursor:default;user-select:none" title="">page integrity</span> · built <span class="stamp-date">2026-06-02</span><br>
|
||||
md5 <span class="stamp-md5">ddf174a681416af767243cb652f4c946</span><br>
|
||||
sha256 <span class="stamp-sha">3c1db79787ea6e2c0cac7ea75e20bb59dc2d9c47f628b4964d65af58fa1f415f</span><br>
|
||||
md5 <span class="stamp-md5">d43eccee30cc6f4a999e3af6806138d2</span><br>
|
||||
sha256 <span class="stamp-sha">4b481a70ef4564ed9950982d9a61d2cabd7860d1ae79f6db6caa73f443bfef2d</span><br>
|
||||
<span style="color:#bbb">hashes are of this page with these two fields zeroed — to verify, blank them and re-hash</span><br>
|
||||
<span style="color:#bbb">one self-contained file — <strong>save a copy</strong> and verify against these hashes; point at your own servers with ?signal= and ?turncred=, or <a href="host-your-own.html" style="color:#999">host your own community</a></span>
|
||||
</footer>
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue