@danieldeusing/design 0.21.0 → 0.22.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +3 -1
- package/dist/danieldeusing-design.css +125 -1
- package/dist/danieldeusing-design.min.css +2 -2
- package/package.json +1 -1
- package/runtime/index.js +1 -0
- package/runtime/pagination.js +341 -0
- package/src/base.css +14 -0
- package/src/components.css +110 -0
- package/templates/documentation.html +5 -5
package/README.md
CHANGED
|
@@ -111,16 +111,18 @@ Four dependency-free ES modules, tree-shakeable from `@danieldeusing/design/runt
|
|
|
111
111
|
| `initTerminal()` | The `$ command` typing animation. No-ops under reduced motion / `html.anim-off`. |
|
|
112
112
|
| `initDropdowns()` | `<details class="dropdown">` behaviour: one-open, click-away, Escape. |
|
|
113
113
|
| `initSelects()` | Replaces the OS dropdown on every `<select>` with the themed listbox — the option list is painted outside the page, so CSS alone can never reach it. Markup contract: none. The `<select>` keeps the value and still fires `input`/`change`, and selects rendered later are enhanced on their own. |
|
|
114
|
+
| `initTablePagination()` | Page every `<table data-table-id>` to 20 rows, with a 5/10/20/50/100/200 picker remembered per table. It has no sort and no filter — it hides all but one window of the rows a page has **already** filtered and sorted, so the order is always filter → sort → slice over the full set. A table without a `data-table-id` is left alone. |
|
|
114
115
|
| `initAnimToggle()` | Wire `[data-anim-toggle]` buttons (`.anim-toggle`) to flip `html.anim-off` + persist it. |
|
|
115
116
|
| `initResolutionZoom(1920)` | Scale the whole layout up on screens wider than the reference width. |
|
|
116
117
|
|
|
117
118
|
```js
|
|
118
|
-
import { applyStoredTheme, initThemeSwitcher, initDropdowns, initSelects, initTerminal, initAnimToggle, initResolutionZoom } from "@danieldeusing/design/runtime";
|
|
119
|
+
import { applyStoredTheme, initThemeSwitcher, initDropdowns, initSelects, initTablePagination, initTerminal, initAnimToggle, initResolutionZoom } from "@danieldeusing/design/runtime";
|
|
119
120
|
applyStoredTheme(); // ideally inline, pre-paint
|
|
120
121
|
initResolutionZoom(); // ideally inline, pre-paint (no flash)
|
|
121
122
|
initThemeSwitcher();
|
|
122
123
|
initDropdowns();
|
|
123
124
|
initSelects();
|
|
125
|
+
initTablePagination();
|
|
124
126
|
initTerminal();
|
|
125
127
|
initAnimToggle();
|
|
126
128
|
```
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
/*! danieldeusing-design v0.
|
|
1
|
+
/*! danieldeusing-design v0.22.0 | MIT | https://github.com/danieldeusing/danieldeusing-design */
|
|
2
2
|
/* ===== reset.css ===== */
|
|
3
3
|
/*
|
|
4
4
|
* danieldeusing-design — minimal reset.
|
|
@@ -402,6 +402,20 @@ tbody tr:last-child > * {
|
|
|
402
402
|
tr > *:first-child { padding-inline-start: 0; }
|
|
403
403
|
tr > *:last-child { padding-inline-end: 0; }
|
|
404
404
|
|
|
405
|
+
/* A HIDDEN ROW MUST ACTUALLY BE HIDDEN, and `!important` is what makes that true.
|
|
406
|
+
The UA's own `[hidden] { display: none }` is a one-attribute selector, so ANY rule that
|
|
407
|
+
sets `display` on a row outruns it — and the estate has those: a narrow-screen media
|
|
408
|
+
query that restacks a key/value table as `.ask table.kv tr { display: block }` scores
|
|
409
|
+
higher and would put every hidden row back on screen, on phones only. Worse than a
|
|
410
|
+
cosmetic slip, because `hidden` that loses to a stylesheet is still exposed to a screen
|
|
411
|
+
reader: the table would announce rows nobody can see it announcing.
|
|
412
|
+
|
|
413
|
+
This is the mechanism `initTablePagination()` turns a page with, and it is declared
|
|
414
|
+
here rather than beside the pager because it is a correction to the ELEMENT: a row a
|
|
415
|
+
consumer hides for its own reasons has exactly the same problem. Same `display: none
|
|
416
|
+
!important` the print stylesheet and the diagram overlay already use. */
|
|
417
|
+
tr[hidden] { display: none !important; }
|
|
418
|
+
|
|
405
419
|
/* Tailwind v4's reset drops the default button cursor — restore it for all
|
|
406
420
|
interactive controls (buttons, summaries, role=button). */
|
|
407
421
|
button:not(:disabled),
|
|
@@ -569,6 +583,28 @@ body::after {
|
|
|
569
583
|
transform: translateY(-2px);
|
|
570
584
|
}
|
|
571
585
|
|
|
586
|
+
/* THE BUTTON HAD NO DISABLED STATE — the select next to it did, and they disagreed.
|
|
587
|
+
`select:disabled` has been `opacity: 0.45; cursor: default` since 0.21.0, so a control row
|
|
588
|
+
holding a greyed dropdown beside a fully-lit button that does nothing when pressed was saying
|
|
589
|
+
two different things about the same condition. A pager's `prev` on page one is the case that
|
|
590
|
+
made it unavoidable: it is disabled far more often than not.
|
|
591
|
+
|
|
592
|
+
BELOW the hover rules on purpose. `:disabled` and `--ghost:hover` are both (0,2,0), so equal
|
|
593
|
+
specificity is settled by source order — declared above them, a disabled button would light up
|
|
594
|
+
under the pointer and reinstate exactly the lie this removes. Same trap that made every compact
|
|
595
|
+
button render at CTA size in 0.13.0.
|
|
596
|
+
|
|
597
|
+
Opacity and nothing else about the fill: a disabled PRIMARY button must stay filled and just go
|
|
598
|
+
quiet. Setting `background: transparent` here would strip it to `--primary-foreground` text on
|
|
599
|
+
the page background — near-invisible on all four themes — so the one property that reads as
|
|
600
|
+
"off" on both variants is the only one touched. */
|
|
601
|
+
.btn-terminal:disabled {
|
|
602
|
+
opacity: 0.45;
|
|
603
|
+
cursor: default;
|
|
604
|
+
box-shadow: none;
|
|
605
|
+
transform: none;
|
|
606
|
+
}
|
|
607
|
+
|
|
572
608
|
/* A ROW-SIZED BUTTON. The system's button is a landing-page CTA at 12px/24px; a table row that
|
|
573
609
|
offers `remove` needs the same button at the size of its text. This is the size ONLY — colour,
|
|
574
610
|
corners and the `> ` prefix still come from .btn-terminal / --ghost, so a compact button cannot
|
|
@@ -627,6 +663,49 @@ body::after {
|
|
|
627
663
|
.btn-terminal--destructive { min-width: 44px; min-height: 44px; }
|
|
628
664
|
}
|
|
629
665
|
|
|
666
|
+
/* THE EDIT ROW ACTION — the same icon button as the bin, in the ordinary colour.
|
|
667
|
+
|
|
668
|
+
`edit →` was a word and an arrow in a table cell wide enough to hold them, next to a bin that
|
|
669
|
+
is 22px square. Two controls doing the same job in one column, one of them four times the
|
|
670
|
+
width of the other, and at 375px the label broke mid-word into "edi / t →" — which is why
|
|
671
|
+
cockpit carried a `white-space: nowrap` rule to prop it up. An icon says it in the space the
|
|
672
|
+
bin already proved is enough.
|
|
673
|
+
|
|
674
|
+
IT IS NOT DESTRUCTIVE AND MUST NOT BORROW THAT COLOUR. Editing is an ordinary action; red is
|
|
675
|
+
reserved for the one press that cannot be taken back. So this class carries NO colour at all —
|
|
676
|
+
compose it with `--ghost` and it is `--primary` on a `--border` outline like every other
|
|
677
|
+
secondary control, and with nothing and it is the filled primary button. That is also why it is
|
|
678
|
+
not `--destructive` with the fill swapped: colour is the bin's whole warning, and the delta
|
|
679
|
+
here is only the glyph and the box that holds it.
|
|
680
|
+
|
|
681
|
+
THE GLYPH IS A MASK, for the reason the bin's comment gives: `currentColor` re-takes whatever
|
|
682
|
+
colour the composition landed on, on all four themes, with nothing declared per-theme. A pasted
|
|
683
|
+
SVG would be one `fill=` that is wrong on two of them, once per surface that adopts it.
|
|
684
|
+
|
|
685
|
+
AN ACCESSIBLE NAME IS MANDATORY — see the bin. Dropping the word "edit" leaves a button whose
|
|
686
|
+
entire content is a mask, and a mask has no text: a screen reader announces "button", and a
|
|
687
|
+
column of them announces "button" a dozen times. `aria-label` must name the TARGET
|
|
688
|
+
("edit poi/vu3"), not repeat the verb, or the column is a dozen identical announcements
|
|
689
|
+
instead. `bin/design-conformance` fails an icon button with no accessible name. */
|
|
690
|
+
.btn-terminal--edit {
|
|
691
|
+
display: inline-flex; align-items: center; justify-content: center;
|
|
692
|
+
padding: 0.3rem;
|
|
693
|
+
}
|
|
694
|
+
/* Kills the `> ` prefix, which would otherwise be the entire text content of an icon button.
|
|
695
|
+
--ghost already blanks it; repeated here so the class is correct on a filled button too. */
|
|
696
|
+
.btn-terminal--edit::before { content: ""; }
|
|
697
|
+
.btn-terminal--edit::after {
|
|
698
|
+
content: ""; display: block; width: 0.875rem; height: 0.875rem;
|
|
699
|
+
background: currentColor;
|
|
700
|
+
-webkit-mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath d='M3 17.25V21h3.75L17.81 9.94l-3.75-3.75L3 17.25zM20.71 7.04l-3.75-3.75-1.83 1.83 3.75 3.75 1.83-1.83z'/%3E%3C/svg%3E") center / contain no-repeat;
|
|
701
|
+
mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath d='M3 17.25V21h3.75L17.81 9.94l-3.75-3.75L3 17.25zM20.71 7.04l-3.75-3.75-1.83 1.83 3.75 3.75 1.83-1.83z'/%3E%3C/svg%3E") center / contain no-repeat;
|
|
702
|
+
}
|
|
703
|
+
/* 44px under a thumb, same as the bin: `min-*` grows the tap target without moving the glyph,
|
|
704
|
+
which the flexbox keeps centred. Never set a width on it. */
|
|
705
|
+
@media (pointer: coarse) {
|
|
706
|
+
.btn-terminal--edit { min-width: 44px; min-height: 44px; }
|
|
707
|
+
}
|
|
708
|
+
|
|
630
709
|
/* Quiet, underlined link that warms to the primary colour on hover. */
|
|
631
710
|
.link-quiet {
|
|
632
711
|
color: var(--muted-foreground);
|
|
@@ -999,6 +1078,51 @@ select:disabled,
|
|
|
999
1078
|
letter-spacing: 0.06em;
|
|
1000
1079
|
}
|
|
1001
1080
|
|
|
1081
|
+
/* ─────────────────────────────────────────────────────────────────────────
|
|
1082
|
+
TABLE PAGER — twenty rows at a time, and the reader can say otherwise.
|
|
1083
|
+
|
|
1084
|
+
Built and placed by runtime/pagination.js; a page writes no part of this. It
|
|
1085
|
+
INVENTS NOTHING: the two buttons are `.btn-terminal--ghost.btn-terminal--compact`
|
|
1086
|
+
(the same pair every refresh and cancel in the estate wears) and the size picker
|
|
1087
|
+
is a bare `<select>` that `initSelects()` enhances. So there is no new colour
|
|
1088
|
+
here, no new control, and nothing to keep in step at the next release — only a
|
|
1089
|
+
row that decides where those three things sit.
|
|
1090
|
+
|
|
1091
|
+
Ghost, not filled: a pager is not the primary action of any view it appears in,
|
|
1092
|
+
and two filled buttons side by side compete. The status text is
|
|
1093
|
+
`--muted-foreground` because it is a measurement of the table, not part of it —
|
|
1094
|
+
4.84:1 at worst (warm on --card), so it clears AA while staying quiet.
|
|
1095
|
+
|
|
1096
|
+
The status is left and the controls are right (`margin-inline-start: auto` on the
|
|
1097
|
+
picker pushes the pair over), which puts "where am I" at the start of the line and
|
|
1098
|
+
"take me elsewhere" at the end. It wraps rather than scrolls: on a phone the three
|
|
1099
|
+
parts stack, and a control that has gone off the side of a table is worse than a
|
|
1100
|
+
control on a second line.
|
|
1101
|
+
───────────────────────────────────────────────────────────────────────── */
|
|
1102
|
+
.table-pager {
|
|
1103
|
+
display: flex;
|
|
1104
|
+
flex-wrap: wrap;
|
|
1105
|
+
align-items: center;
|
|
1106
|
+
gap: 0.45rem 0.9rem;
|
|
1107
|
+
margin-block-start: 0.6rem;
|
|
1108
|
+
font-size: var(--fs-sm);
|
|
1109
|
+
}
|
|
1110
|
+
.table-pager-status {
|
|
1111
|
+
margin: 0;
|
|
1112
|
+
color: var(--muted-foreground);
|
|
1113
|
+
}
|
|
1114
|
+
.table-pager-size {
|
|
1115
|
+
display: inline-flex;
|
|
1116
|
+
align-items: center;
|
|
1117
|
+
gap: 0.4rem;
|
|
1118
|
+
margin-inline-start: auto;
|
|
1119
|
+
color: var(--muted-foreground);
|
|
1120
|
+
}
|
|
1121
|
+
.table-pager-nav {
|
|
1122
|
+
display: inline-flex;
|
|
1123
|
+
gap: 0.4rem;
|
|
1124
|
+
}
|
|
1125
|
+
|
|
1002
1126
|
/* ─────────────────────────────────────────────────────────────────────────
|
|
1003
1127
|
ELI5 — an "explain like I'm 5" callout box (first-time-term explainers).
|
|
1004
1128
|
───────────────────────────────────────────────────────────────────────── */
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
/*! danieldeusing-design v0.
|
|
2
|
-
*,*::before,*::after{box-sizing: border-box}*{margin: 0}html{-webkit-text-size-adjust: 100%;text-size-adjust: 100%;tab-size: 4}body{min-height: 100vh;min-height: 100dvh;line-height: 1.5}img,picture,video,canvas,svg{display: block;max-width: 100%}input,button,textarea,select{font: inherit;color: inherit}p,h1,h2,h3,h4,h5,h6{overflow-wrap: break-word}ul,ol{list-style: none;padding: 0}a{color: inherit;text-decoration: none}table{border-collapse: collapse}:root{--background: #f5efe2;--foreground: #43352a;--card: #efe7d4;--card-foreground: #43352a;--popover: #efe7d4;--popover-foreground: #43352a;--primary: #8a4516;--primary-foreground: #f8f3e8;--secondary: #ece3cf;--secondary-foreground: #43352a;--muted: #ece3cf;--muted-foreground: #71614e;--accent: #8a4516;--accent-foreground: #f8f3e8;--destructive: #a02c2c;--success: #1a6b2e;--warning: #845600;--info: #1c5f9e;--pending: #5f3dc4;--border: #d9cdb6;--input: #d9cdb6;--ring: #8a4516;--glow: rgba(160,82,45,0.1);--glow-soft: rgba(160,82,45,0.05);--scanline-opacity: 0.15;--radius: 0rem;--font-mono: "JetBrains Mono Variable","JetBrains Mono","Menlo",monospace}html[data-theme="green"]{--background: #020604;--foreground: #4fdd7d;--card: #04110a;--card-foreground: #4fdd7d;--popover: #04110a;--popover-foreground: #4fdd7d;--primary: #33ff66;--primary-foreground: #02160a;--secondary: #071509;--secondary-foreground: #4fdd7d;--muted: #071509;--muted-foreground: #3da45e;--accent: #33ff66;--accent-foreground: #02160a;--destructive: #ff5c5c;--success: #4ade80;--warning: #f5b32b;--info: #4dabf7;--pending: #b197fc;--border: #1c4a2c;--input: #1c4a2c;--ring: #33ff66;--glow: rgba(51,255,102,0.35);--glow-soft: rgba(51,255,102,0.12);--scanline-opacity: 0.5}html[data-theme="mono"]{--background: #050505;--foreground: #d4d4d4;--card: #0d0d0d;--card-foreground: #d4d4d4;--popover: #0d0d0d;--popover-foreground: #d4d4d4;--primary: #ffffff;--primary-foreground: #050505;--secondary: #111111;--secondary-foreground: #d4d4d4;--muted: #111111;--muted-foreground: #8a8a8a;--accent: #ffffff;--accent-foreground: #050505;--destructive: #ff5c5c;--success: #4ade80;--warning: #f5b32b;--info: #4dabf7;--pending: #b197fc;--border: #333333;--input: #333333;--ring: #ffffff;--glow: rgba(255,255,255,0.25);--glow-soft: rgba(255,255,255,0.08);--scanline-opacity: 0.45}html[data-theme="paper"]{--background: #fafafa;--foreground: #1f1f1f;--card: #f1f1f1;--card-foreground: #1f1f1f;--popover: #f1f1f1;--popover-foreground: #1f1f1f;--primary: #000000;--primary-foreground: #fafafa;--secondary: #efefef;--secondary-foreground: #1f1f1f;--muted: #efefef;--muted-foreground: #595959;--accent: #000000;--accent-foreground: #fafafa;--destructive: #b3261e;--success: #1a7031;--warning: #8f5d00;--info: #1c5f9e;--pending: #5f3dc4;--border: #d4d4d4;--input: #d4d4d4;--ring: #000000;--glow: rgba(0,0,0,0.06);--glow-soft: rgba(0,0,0,0.04);--scanline-opacity: 0.12}:root{--content-w: 78rem;--content-pad: 1.5rem;--fs-xs: 0.625rem;--fs-sm: 0.6875rem;--fs-base: 0.75rem;--fs-md: 0.8125rem;--fs-lg: 0.9375rem;--fs-xl: 1.125rem;--fs-2xl: 1.5rem;--lh-tight: 1.3;--lh-base: 1.5;--field-label-w: 8.5rem}html{scroll-behavior: smooth}*,*::before,*::after{border-color: var(--border)}:focus-visible{outline: 2px solid var(--ring);outline-offset: 2px}body{background: var(--background);color: var(--foreground);font-family: var(--font-mono);font-size: var(--fs-base);line-height: var(--lh-base);overflow-x: hidden;-webkit-font-smoothing: antialiased;-moz-osx-font-smoothing: grayscale}table{font-size: var(--fs-md);width: 100%}th,td{padding: 0.45rem 0.85rem;text-align: left;vertical-align: top;border-block-end: 1px solid color-mix(in srgb,var(--border) 55%,transparent)}thead th{border-block-end-color: var(--border);color: var(--muted-foreground);font-weight: 600}tbody tr:last-child>*{border-block-end: 0}tr>*:first-child{padding-inline-start: 0}tr>*:last-child{padding-inline-end: 0}button:not(:disabled),summary,[role="button"]{cursor: pointer}body::after{content: "";position: fixed;inset: 0;z-index: 40;pointer-events: none;background: repeating-linear-gradient( 0deg,rgba(0,0,0,0.25) 0px,rgba(0,0,0,0.25) 1px,transparent 1px,transparent 3px );opacity: var(--scanline-opacity)}::selection{background: var(--primary);color: var(--primary-foreground)}.glow{color: var(--primary);text-shadow: 0 0 8px var(--glow)}.glow-lg{color: var(--primary);text-shadow: 0 0 12px var(--glow)}.field-row{display: grid;grid-template-columns: var(--field-label-w) minmax(0,1fr);align-items: center;gap: 0.5rem 0.75rem;margin: 0.35rem 0}.field-row>.lbl{color: var(--muted-foreground);font-size: var(--fs-sm);text-align: left}.field-row>.field-val{display: flex;flex-wrap: wrap;align-items: center;gap: 0.4rem 0.9rem;min-width: 0}@media (max-width: 40rem){.field-row{grid-template-columns: 1fr;gap: 0.2rem}}.prompt{font-size: 0.75rem;font-weight: 700;color: var(--muted-foreground)}.prompt::before{content: "$ ";color: var(--primary)}.comment::before{content: "# ";color: var(--border)}.cursor-block{display: inline-block;width: 0.55em;height: 1em;margin-left: 8px;background: var(--primary);vertical-align: baseline;transform: translateY(0.12em);animation: ddd-blink 1.1s step-end infinite}@keyframes ddd-blink{50%{opacity: 0}}.btn-terminal{display: inline-block;background: var(--primary);color: var(--primary-foreground);font-weight: 700;padding: 12px 24px;box-shadow: 0 0 16px var(--glow);transition: box-shadow 0.15s ease,transform 0.15s ease}.btn-terminal::before{content: "> "}.btn-terminal--ghost{background: transparent;color: var(--primary);border: 1px solid var(--border);box-shadow: none;font-weight: 400}.btn-terminal--ghost::before{content: ""}.btn-terminal--ghost:hover{border-color: var(--primary);background: color-mix(in srgb,var(--primary) 10%,transparent);box-shadow: none}.btn-terminal:hover{box-shadow: 0 0 28px var(--glow);transform: translateY(-2px)}.btn-terminal--compact{padding: 0.28rem 0.7rem;font-size: var(--fs-sm)}.btn-terminal--destructive{display: inline-flex;align-items: center;justify-content: center;padding: 0.3rem;color: var(--destructive);border-color: color-mix(in srgb,var(--destructive) 45%,var(--border))}.btn-terminal--destructive::before{content: ""}.btn-terminal--destructive::after{content: "";display: block;width: 0.875rem;height: 0.875rem;background: currentColor;-webkit-mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath d='M9 3h6v3H9zM4 6h16v2H4zM6 9h12l-1 12H7z'/%3E%3C/svg%3E") center / contain no-repeat;mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath d='M9 3h6v3H9zM4 6h16v2H4zM6 9h12l-1 12H7z'/%3E%3C/svg%3E") center / contain no-repeat}.btn-terminal--destructive:hover{border-color: var(--destructive);background: color-mix(in srgb,var(--destructive) 12%,transparent);box-shadow: none}@media (pointer: coarse){.btn-terminal--destructive{min-width: 44px;min-height: 44px}}.link-quiet{color: var(--muted-foreground);text-decoration: underline;text-underline-offset: 4px;transition: color 0.15s ease}.link-quiet:hover{color: var(--primary)}.anim-toggle{display: inline-flex;align-items: center;gap: 6px;font: inherit;color: var(--muted-foreground);background: none;border: 0;cursor: pointer;transition: color 0.15s ease}.anim-toggle:hover{color: var(--primary)}.anim-toggle[aria-pressed="true"] [data-anim-box]{color: var(--primary)}.dd-dot{display: inline-block;width: 11px;height: 11px;flex: none;border-radius: 50%;background: currentColor}.dd-flag{display: inline-block;width: 15px;height: 10px;flex: none;border: 1px solid var(--border);background-size: cover;background-repeat: no-repeat}.dd-flag-de{background-image: url("data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 18 12'><rect width='18' height='4' fill='%23262626'/><rect y='4' width='18' height='4' fill='%23c83232'/><rect y='8' width='18' height='4' fill='%23e8be32'/></svg>")}.dd-flag-en{background-image: url("data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 18 12'><rect width='18' height='12' fill='%23b22234'/><rect y='4' width='18' height='4' fill='%23ececec'/><rect width='8' height='6' fill='%233c3b6e'/></svg>")}.dd-flag-pt{background-image: url("data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 18 12'><rect width='18' height='12' fill='%231c9148'/><polygon points='9,1.5 15.5,6 9,10.5 2.5,6' fill='%23e8c832'/><rect x='8' y='5' width='2' height='2' fill='%232c3f87'/></svg>")}.dd-flag-es{background-image: url("data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 18 12'><rect width='6' height='12' fill='%2315734c'/><rect x='6' width='6' height='12' fill='%23ececec'/><rect x='12' width='6' height='12' fill='%23bf2233'/><rect x='8' y='5' width='2' height='2' fill='%237a5c3a'/></svg>")}.ascii-rule{margin: 1rem 0;border: 0;color: var(--border);line-height: 1;white-space: nowrap;overflow: hidden;user-select: none}.ascii-rule::before{content: "────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────"}.card-terminal{background: var(--card);border: 1px solid var(--border);transition: border-color 0.15s ease,box-shadow 0.15s ease}.card-terminal:hover{border-color: var(--primary);box-shadow: 0 0 20px var(--glow-soft)}.dropdown{position: relative}.dropdown>summary{display: inline-flex;align-items: center;gap: 6px;cursor: pointer;user-select: none;list-style: none;color: var(--muted-foreground);transition: color 0.15s ease}.dropdown>summary::-webkit-details-marker{display: none}.dropdown>summary:hover,.dropdown[open]>summary{color: var(--primary)}.dropdown-panel{position: absolute;right: 0;bottom: calc(100% + 12px);z-index: 50;min-width: 128px;margin: 0;padding: 4px 0;list-style: none;background: var(--card);border: 1px solid var(--border);border-radius: var(--radius);box-shadow: 0 0 20px var(--glow-soft)}.dropdown-panel--down{top: calc(100% + 8px);bottom: auto;left: 0;right: auto}.dropdown-item{display: flex;align-items: center;gap: 8px;width: 100%;padding: 5px 14px;text-align: left;font: inherit;line-height: 1.5;background: none;border: 0;color: var(--muted-foreground);cursor: pointer;white-space: nowrap;transition: color 0.15s ease}.dropdown-item:hover{color: var(--primary);background: var(--muted)}.dropdown-item[aria-current="true"],html:not([data-theme]) .dropdown-item[data-theme-value="warm"],html[data-theme="green"] .dropdown-item[data-theme-value="green"],html[data-theme="mono"] .dropdown-item[data-theme-value="mono"],html[data-theme="paper"] .dropdown-item[data-theme-value="paper"],html[data-theme="warm"] .dropdown-item[data-theme-value="warm"]{color: var(--primary);text-shadow: 0 0 8px var(--glow)}select,.select-trigger{appearance: none;-webkit-appearance: none;max-width: 100%;padding: 0.28rem 1.45rem 0.28rem 0.5rem;font: inherit;font-size: var(--fs-sm);line-height: var(--lh-tight);text-align: left;color: var(--foreground);background-color: transparent;background-image: linear-gradient(45deg,transparent 50%,currentColor 50%),linear-gradient(135deg,currentColor 50%,transparent 50%);background-position: right 0.78rem center,right 0.5rem center;background-size: 0.28rem 0.28rem;background-repeat: no-repeat;border: 1px solid color-mix(in srgb,var(--foreground) 60%,transparent);cursor: pointer;transition: border-color 0.15s ease}select:hover,.select-trigger:hover,.select-trigger[aria-expanded="true"]{border-color: var(--primary)}select:disabled,.select-trigger:disabled{opacity: 0.45;cursor: default}.select-trigger[aria-expanded="true"]{background-image: linear-gradient(135deg,transparent 50%,currentColor 50%),linear-gradient(45deg,currentColor 50%,transparent 50%)}.select-field{position: relative;display: inline-flex;max-width: 100%;vertical-align: middle}.select-field>select{position: absolute;inset: 0;width: 100%;height: 100%;opacity: 0;pointer-events: none}.select-trigger{display: inline-flex;align-items: center;width: 100%}.select-value{min-width: 0;overflow: hidden;text-overflow: ellipsis;white-space: nowrap}.select-value::after{content: "\200b"}.select-panel{position: fixed;z-index: 60;margin: 0;padding: 0.15rem 0;overflow-y: auto;overscroll-behavior: contain;list-style: none;font-size: var(--fs-sm);line-height: var(--lh-tight);color: var(--popover-foreground);background: var(--popover);border: 1px solid color-mix(in srgb,var(--foreground) 60%,transparent);box-shadow: 0 0 20px var(--glow-soft)}.select-option{display: flex;align-items: center;gap: 0.4rem;padding: 0.28rem 0.9rem 0.28rem 0.55rem;border-inline-start: 2px solid transparent;white-space: nowrap;cursor: pointer}.select-option:hover,.select-option[data-active="true"]{background: var(--muted)}.select-option[aria-selected="true"]{color: var(--primary);font-weight: 700;border-inline-start-color: var(--primary)}.select-option[aria-disabled="true"]{color: color-mix(in srgb,var(--popover-foreground) 65%,var(--popover));cursor: default}.select-option[aria-disabled="true"]:hover{background: none}.select-group{padding: 0.35rem 0.9rem 0.15rem;color: var(--muted-foreground);font-size: var(--fs-xs);text-transform: uppercase;letter-spacing: 0.06em}.eli5{margin: 1.25em 0;padding: 0.7em 1em;color: var(--foreground);background: color-mix(in srgb,var(--primary) 7%,var(--card));border: 1px solid var(--border);border-left: 3px solid var(--primary);border-radius: var(--radius);font-size: 0.92em;line-height: 1.6}.eli5::before{content: "ELI5";display: inline-flex;align-items: center;justify-content: center;margin-right: 0.6em;vertical-align: 0.08em;font-family: var(--font-mono);font-size: 0.72em;font-weight: 700;line-height: 1;letter-spacing: 0.04em;padding: 0.34em 0.6em;border-radius: var(--radius);background: var(--destructive);color: var(--primary-foreground)}.eli5-term{font-weight: 700;color: var(--primary)}html.anim-off *,html.anim-off *::before,html.anim-off *::after{animation: none !important}html.anim-off .cursor-block{display: none}@media (prefers-reduced-motion: no-preference){html.term-anim [data-term] .prompt:not(.term-live),html.term-anim [data-term] .prompt:not(.term-live)::before{color: transparent}html.term-anim [data-term] [data-term-out]:not(.term-show){visibility: hidden}html.term-anim [data-term] [data-term-out].term-show{animation: ddd-term-output 0.2s steps(3,end) both}@keyframes ddd-term-output{from{opacity: 0}}html.term-anim [data-term] [data-term-out].term-show>*{animation: ddd-term-output 0.25s steps(3,end) both}html.term-anim [data-term] [data-term-out].term-show>*:nth-child(2){animation-delay: 0.09s}html.term-anim [data-term] [data-term-out].term-show>*:nth-child(3){animation-delay: 0.18s}html.term-anim [data-term] [data-term-out].term-show>*:nth-child(4){animation-delay: 0.27s}html.term-anim [data-term] [data-term-out].term-show>*:nth-child(5){animation-delay: 0.36s}html.term-anim [data-term] [data-term-out].term-show>*:nth-child(6){animation-delay: 0.45s}html.term-anim [data-term] [data-term-out].term-show>*:nth-child(7){animation-delay: 0.54s}html.term-anim [data-term] [data-term-out].term-show>*:nth-child(8){animation-delay: 0.63s}html.term-anim [data-term] [data-term-out].term-show>*:nth-child(9){animation-delay: 0.72s}html.term-anim [data-term] [data-term-out].term-show>*:nth-child(10){animation-delay: 0.81s}html.term-anim [data-term] [data-term-out].term-show>*:nth-child(n + 11){animation-delay: 0.9s}.term-caret{display: inline-block;width: 0.55em;height: 1em;margin-left: 2px;background: var(--primary);vertical-align: baseline;transform: translateY(0.12em)}}.tabs{display: flex;flex-wrap: wrap;gap: 0.15rem;margin: 1.35rem 0 0;border-bottom: 2px solid color-mix(in srgb,var(--border) 80%,transparent)}.tab{background: transparent;border: 0;margin-bottom: -2px;font: inherit;font-size: 0.86em;cursor: pointer;padding: 0.5rem 0.95rem;color: var(--muted-foreground);transition: background 0.12s ease,color 0.12s ease}.tab:hover{color: var(--primary);background: color-mix(in srgb,var(--primary) 10%,transparent)}.tab[aria-selected="true"]{background: var(--primary);color: var(--background);font-weight: 700}.tab:focus-visible{outline: 2px solid color-mix(in srgb,var(--primary) 55%,transparent);outline-offset: -2px}.tab--info{margin-inline-start: auto}@media (max-width: 40rem){.tab--info{margin-inline-start: 0}}@media (pointer: coarse),(max-width: 40rem){.tab{padding: 0.7rem 0.8rem;min-height: 44px}}section.doc.tab-panel{margin-top: 1.35rem}details.fold{border-top: 1px solid color-mix(in srgb,currentColor 15%,transparent)}details.fold>summary{display: flex;align-items: center;gap: 0.5rem;cursor: pointer;list-style: none;padding: 0.55rem 0;font-size: 0.85rem;color: var(--muted-foreground)}details.fold>summary::-webkit-details-marker{display: none}details.fold>summary::before{content: "\25B8";flex: none;opacity: 0.6}details.fold[open]>summary::before{content: "\25BE"}details.fold>summary:hover{color: var(--primary)}details.fold>summary:focus-visible{outline: 2px solid color-mix(in srgb,currentColor 45%,transparent);outline-offset: 2px}.fold-body{font-size: 0.9rem;padding: 0 0 0.7rem 1.1rem}.fold-body>* + *{margin-top: 0.6rem}.legend{list-style: none;padding: 0;margin: 0.7rem 0 0;display: grid;gap: 0.4rem 1.4rem;font-size: 0.86rem}@media (min-width: 52rem){.legend{grid-template-columns: repeat(2,minmax(0,1fr))}}.legend>li{line-height: 1.6}.legend>li>:first-child{margin-right: 0.45rem}.dgm-zoomable{position: relative;cursor: zoom-in}.dgm-zoomable::after{content: "⤢";position: absolute;top: 0.4rem;right: 0.5rem;font-size: 0.85rem;line-height: 1;opacity: 0.45;pointer-events: none;color: var(--muted-foreground)}.dgm-zoomable:hover::after,.dgm-zoomable:focus-visible::after{opacity: 1;color: var(--primary)}.dgm-zoomable:focus-visible{outline: 2px solid var(--primary);outline-offset: 3px}html.dgm-locked,html.dgm-locked body{overflow: hidden}.dgm-overlay{display: none;position: fixed;inset: 0;z-index: 90;background: color-mix(in srgb,var(--background) 96%,transparent);flex-direction: column}.dgm-overlay.open{display: flex}.dgm-bar{flex: none;display: flex;align-items: center;justify-content: flex-end;gap: 0.4rem;padding: 0.5rem 0.75rem;border-bottom: 1px solid var(--border);background: var(--card)}.dgm-btn{font: inherit;font-size: 0.82rem;cursor: pointer;padding: 0.3rem 0.7rem;min-height: 32px;background: transparent;border: 1px solid var(--border);color: var(--foreground)}.dgm-btn:hover{border-color: var(--primary);color: var(--primary)}.dgm-btn:focus-visible{outline: 2px solid var(--primary);outline-offset: 1px}.dgm-close{border-color: color-mix(in srgb,var(--destructive) 55%,transparent);color: var(--destructive)}.dgm-close:hover{border-color: var(--destructive);color: var(--destructive)}.dgm-stage{flex: 1;overflow: hidden;display: grid;place-items: center;cursor: grab;touch-action: none}.dgm-stage.is-grabbing{cursor: grabbing}.dgm-art{transform-origin: center center;will-change: transform}.dgm-art>svg,.dgm-art>img{display: block;max-width: none;max-height: none}@media print{.dgm-overlay,.dgm-zoomable::after{display: none !important}}.minimap{position: fixed;left: 0;top: var(--ls-nav-top,3rem);bottom: var(--ls-nav-bottom,2.2rem);z-index: 20;display: none;flex-direction: column;justify-content: center;gap: 0.5rem;width: 2rem;padding: 0.75rem 0 0.75rem 0.75rem;overflow-y: auto;overscroll-behavior: contain;scrollbar-width: none}.minimap::-webkit-scrollbar{display: none}@media (min-width: 64rem){.minimap{display: flex}}.minimap-bar{flex: 0 0 auto;width: var(--minimap-bar-w,100%);height: 2px;padding: 0;border: 0;border-radius: 1px;background: var(--border);cursor: pointer;transition: background 0.15s ease,transform 0.15s ease;transform-origin: left center}.minimap-bar:hover{background: var(--muted-foreground);transform: scaleY(2)}.minimap-bar[data-tip]{cursor: pointer}.minimap-bar:focus-visible{outline: 2px solid var(--ring);outline-offset: 3px}.minimap-bar.active{background: var(--primary);transform: scaleY(2)}@media print{.minimap{display: none !important}}.wrap{max-width: var(--content-w);margin-inline: auto;padding-inline: var(--content-pad)}.tablewrap{position: relative;overflow-x: auto;overflow-y: auto;max-block-size: var(--tablewrap-max-h,none)}.tablewrap::after{content: "";position: absolute;inset-block: 0;right: 0;width: 1.5rem;pointer-events: none;opacity: 0.9;background: linear-gradient(to right,transparent,var(--background,transparent))}.tablewrap thead th{position: sticky;inset-block-start: 0;z-index: 1;background: var(--background,#f5efe2)}.tickstrip:not(:empty){border: 1px solid var(--border);background: var(--card);margin: 1.6rem 0 1.5rem}.ticktable{width: 100%;border-collapse: collapse;font-size: 0.82rem}.ticktable td{padding: 0.5rem 0.75rem;vertical-align: baseline;border: 0;white-space: nowrap}.ticktable td.tick-stats{width: 100%;white-space: normal;text-align: right}.tick{border-bottom: 1px solid color-mix(in srgb,var(--border) 55%,transparent)}.tick:last-child{border-bottom: 0}.tick-dot{font-size: 0.7em;width: 1.5rem;padding-right: 0 !important;border-left: 3px solid transparent;padding-left: 0.6rem !important}.tick--ok .tick-dot{color: var(--success);border-left-color: color-mix(in srgb,var(--success) 45%,transparent)}.tick--stale .tick-dot{color: var(--destructive);border-left-color: var(--destructive)}.tick--never .tick-dot{color: var(--muted-foreground);border-left-color: color-mix(in srgb,var(--muted-foreground) 35%,transparent)}.tick-name{font-weight: 700;color: var(--foreground);letter-spacing: 0.01em}.tick-last,.tick-next{font-variant-numeric: tabular-nums;color: var(--muted-foreground)}.tick-next{opacity: 0.72}.tick--stale .tick-last{color: var(--destructive);font-weight: 700}.tick--never .tick-last{font-style: italic}.tick-stats{color: var(--muted-foreground)}.tick-stats b{font-weight: 700;color: var(--primary);font-variant-numeric: tabular-nums}.tick-sep{font-style: normal;opacity: 0.35;margin: 0 0.5rem}.tick--stale{background: color-mix(in srgb,var(--destructive) 8%,transparent)}@media (max-width: 40rem){.ticktable,.ticktable tbody,.tick,.ticktable td{display: block}.tick{padding: 0.4rem 0.5rem}.ticktable td{display: inline-block;padding: 0.1rem 0.5rem 0.1rem 0}.ticktable td.tick-stats{display: block;width: auto;text-align: left}}.skip-link{position: absolute;left: -999px;top: 0;z-index: 100;background: var(--card);border: 1px solid var(--primary);padding: 0.5rem 1rem;color: var(--primary)}.skip-link:focus{left: 0.5rem;top: 0.5rem}.visually-hidden{position: absolute;width: 1px;height: 1px;overflow: hidden;clip: rect(0 0 0 0);white-space: nowrap}header.bar{position: sticky;top: 0;z-index: 30;display: flex;align-items: center;justify-content: space-between;gap: 1rem;padding: 0.6rem 1.25rem;border-bottom: 1px solid var(--border);background: var(--card)}header.bar .brand{display: inline-flex;align-items: center;font-weight: 700;font-size: 1rem}header.bar .brand,header.bar .brand a{color: inherit;text-decoration: none}header.bar .dropdown summary{padding: 0.6rem 0.4rem}.bar-right{display: flex;align-items: center;gap: 1rem;flex-shrink: 0}footer.status{position: fixed;inset-inline: 0;bottom: 0;z-index: 50;min-height: 2rem;display: flex;align-items: center;justify-content: space-between;gap: 1rem;padding: 0.35rem 1.25rem;border-top: 1px solid var(--border);background: var(--card);font-size: 0.7rem}footer.status .dropdown summary,footer.status .anim-toggle{padding: 0.55rem 0.4rem}.status-left{color: var(--muted-foreground);min-width: 0;overflow: hidden;text-overflow: ellipsis;white-space: nowrap}.status-right{display: flex;align-items: center;gap: 1rem;flex-shrink: 0}.sep{width: 1px;height: 0.9rem;background: var(--border)}.doc-link{color: var(--muted-foreground);text-decoration: none;transition: color 0.15s ease}.doc-link:hover{color: var(--primary)}.doc-link--forward{color: var(--primary);white-space: nowrap}.doc-link--forward:hover{color: var(--primary);text-decoration: underline}.ls-group{display: block;padding: 0.55rem 0 0.15rem;font-size: var(--fs-xs);letter-spacing: 0.08em;color: var(--muted-foreground);opacity: 0.75}.ls-panel>li:first-child .ls-group{padding-top: 0}.ls-row{display: flex;align-items: baseline;gap: 0.9rem}.ls-perm{color: color-mix(in srgb,var(--muted-foreground) 75%,var(--card));font-size: 0.72rem;letter-spacing: 0}.ls-row--sub .ls-name{margin-left: 0.55rem;padding-left: 0.75rem;border-left: 1px solid color-mix(in srgb,var(--muted-foreground) 40%,transparent)}.ls-row--sub2 .ls-name{margin-left: 1.7rem}.ls-panel .ls-name{color: var(--muted-foreground)}.ls-panel .ls-row--dir .ls-name{color: var(--primary);font-weight: 700}.ls-row[aria-current="page"]{background: color-mix(in srgb,var(--primary) 12%,transparent);box-shadow: inset 3px 0 0 0 var(--primary)}.ls-row[aria-current="page"] .ls-name{color: var(--primary);font-weight: 700;text-shadow: 0 0 8px var(--glow)}.ls-row[aria-current="page"] .ls-perm{color: color-mix(in srgb,var(--primary) 65%,transparent)}.ls-row[aria-current="page"]::after{content: "←";margin-left: auto;color: var(--primary)}.dropdown-panel.ls-panel{min-width: 17rem;left: auto !important;right: 0}:root{--ls-nav-w: 17rem;--ls-nav-top: 3rem;--ls-nav-bottom: 2.2rem}@media (min-width: 48.0625rem){html{--ls-nav-inset: 0rem}html:has(.ls-nav):not([data-ls-nav="off"]){--ls-nav-inset: var(--ls-nav-w)}body{padding-right: var(--ls-nav-inset)}header.bar,.bleed-rail{margin-right: calc(-1 * var(--ls-nav-inset))}.ls-nav{position: fixed;top: var(--ls-nav-top);right: 0;bottom: var(--ls-nav-bottom);width: var(--ls-nav-w);z-index: 25;display: flex;flex-direction: column;overflow: hidden;background: var(--card);border-left: 1px solid var(--border)}html:not(.anim-off) .ls-nav{transition: transform 0.18s ease}html[data-ls-nav="off"] .ls-nav{transform: translateX(100%)}.ls-nav-head{display: inline-flex;align-items: center;gap: 0.5rem;margin-left: 0.75rem}.ls-nav-title{font-weight: 700;font-size: 0.9rem}.ls-nav .ls-panel{flex: 1;min-height: 0;overflow-y: auto;list-style: none;margin: 0;padding: 0.5rem 0 1rem}.ls-nav-toggle{background: none;border: 0;font: inherit;cursor: pointer;line-height: 1;color: var(--muted-foreground);padding: 0.4rem 0.55rem}.ls-nav-toggle::after{content: "\00BB"}html[data-ls-nav="off"] .ls-nav-toggle::after{content: "\00AB"}.ls-nav-toggle:hover{color: var(--primary)}.ls-nav-toggle:focus-visible{outline: 2px solid color-mix(in srgb,var(--primary) 55%,transparent);outline-offset: -2px}}.nav-burger{display: none;background: none;border: 0;color: var(--muted-foreground);cursor: pointer;padding: 0.55rem;margin: -0.3rem -0.3rem -0.3rem 0}.nav-burger:hover{color: var(--primary)}.mobile-nav{display: none;list-style: none;margin: 0;padding: 0}.mobile-footer{display: none}@media (max-width: 48rem){.nav-burger{display: inline-flex;align-items: center}.site-nav{display: none;position: absolute;top: 100%;left: 0;right: 0;z-index: 40;background: var(--card);border-bottom: 1px solid var(--border);padding: 0.4rem 1.25rem 0.9rem;max-height: calc(100vh - 6rem);overflow-y: auto}.site-nav.open{display: block}.site-nav>.ls-nav,.mobile-nav{display: flex;flex-direction: column}.site-nav:has(>.ls-nav) .mobile-nav{display: none}.site-nav>.ls-nav .ls-row{align-items: center;min-height: 44px;border-bottom: 1px solid var(--border)}.site-nav>.ls-nav .ls-row .ls-name{display: flex;align-items: center;flex: 1;min-height: 44px}.site-nav>.ls-nav .ls-row:last-child{border-bottom: 0}.mobile-nav .mobile-item{display: flex;align-items: baseline;gap: 0.9rem;padding: 0.65rem 0;text-decoration: none;color: var(--foreground);font-size: 0.9rem;border-bottom: 1px solid var(--border)}.mobile-nav li:last-child .mobile-item{border-bottom: 0}.mobile-nav .mobile-item:active .ls-name,.mobile-nav .mobile-item:hover .ls-name{color: var(--primary)}.ls-nav-toggle{display: none}footer.status{display: none}.mobile-footer{display: flex;flex-direction: column;gap: 0.55rem;margin-top: 0.8rem;border-top: 1px solid var(--border);padding-top: 0.8rem;font-size: 0.85rem}.mobile-footer .doc-link{padding: 0.2rem 0}.mobile-footer .mobile-theme summary{list-style: none;display: flex;align-items: center;gap: 0.5rem;color: var(--muted-foreground);cursor: pointer;padding: 0.2rem 0}.mobile-footer .mobile-theme summary::-webkit-details-marker{display: none}.mobile-footer .mf-chev{margin-left: auto;opacity: 0.7}.mobile-footer .mf-panel{display: flex;flex-direction: column;margin-top: 0.3rem}.mobile-footer .mf-panel .dropdown-item{text-align: left;padding: 0.4rem 0.6rem}.mobile-footer .anim-toggle{display: inline-flex;gap: 0.5rem;align-items: center;background: none;border: 0;padding: 0.2rem 0;color: var(--muted-foreground);font: inherit;cursor: pointer}}[data-tip]{cursor: help}span[data-tip],th[data-tip]{border-bottom: 1px dotted var(--muted-foreground)}#ddtip{position: fixed;z-index: 9999;display: none;max-width: 340px;background: var(--background);color: var(--foreground);border: 1px solid var(--muted-foreground);border-radius: var(--radius);padding: 7px 10px;font-size: 0.72rem;line-height: 1.45;font-weight: 400;text-align: left;white-space: normal;pointer-events: none;box-shadow: 0 4px 14px rgba(0,0,0,0.25)}@media print{@page{margin: 14mm 12mm}:root,html,html[data-theme]{--background: #ffffff;--foreground: #111111;--card: #ffffff;--card-foreground: #111111;--popover: #ffffff;--popover-foreground: #111111;--primary: #000000;--primary-foreground: #ffffff;--secondary: #ffffff;--secondary-foreground: #111111;--muted: #ffffff;--muted-foreground: #444444;--accent: #000000;--accent-foreground: #ffffff;--destructive: #7a1c1c;--border: #9a9a9a;--input: #9a9a9a;--ring: #000000;--glow: transparent;--glow-soft: transparent;--scanline-opacity: 0}html{zoom: 1 !important;font-size: 16px;scroll-behavior: auto}body{background: #ffffff;color: var(--foreground);font-size: 12px;line-height: 1.45;overflow: visible}body::after{display: none !important}*,*::before,*::after{animation: none !important;transition: none !important;text-shadow: none !important;box-shadow: none !important}html.term-anim [data-term] [data-term-out],html.term-anim [data-term] [data-term-out]:not(.term-show),html.term-anim [data-term] [data-term-out]>*{visibility: visible !important;opacity: 1 !important}html.term-anim [data-term] .prompt:not(.term-live){color: var(--muted-foreground) !important}html.term-anim [data-term] .prompt:not(.term-live)::before{color: var(--primary) !important}header.bar,footer.status,.site-nav,.nav-burger,.mobile-nav,.mobile-footer,.skip-link,.dropdown,.dropdown-panel,.select-panel,.anim-toggle,.cursor-block,.term-caret,.ascii-rule,.toc,#ddtip{display: none !important}body{padding-right: 0 !important}[data-tip]{cursor: auto}span[data-tip],th[data-tip]{border-bottom: 0}.wrap{max-width: none !important;margin: 0 !important;padding: 0 !important}.layout{display: block !important}.content{min-width: 0}h1,.title{font-size: 1.5rem !important}h2{font-size: 1.15rem !important}h3,.sub{font-size: 1rem !important}h4{font-size: 0.9rem !important}.prompt{font-size: 0.7rem}h1,h2,h3,h4,.prompt,.toc-label{break-after: avoid-page;break-inside: avoid}p,li,blockquote{orphans: 3;widows: 3}pre,figure,blockquote,tr,img,.eli5,.card-terminal{break-inside: avoid}svg{break-inside: auto}thead{display: table-header-group}.tablewrap,.table-scroll,pre,.diagram{overflow: visible !important;max-width: 100% !important}pre{white-space: pre-wrap !important;overflow-wrap: anywhere;font-size: 0.75rem !important;background: transparent !important}pre.mermaid[data-processed]{white-space: normal !important}code{background: transparent !important;overflow-wrap: anywhere}table{width: 100% !important;table-layout: auto;border-collapse: collapse;font-size: 0.75rem !important}th,td{overflow-wrap: break-word;white-space: normal !important}td code,th code,td a,th a{overflow-wrap: anywhere}a{color: inherit;text-decoration: underline;text-underline-offset: 2px}a.card-terminal,a[class*="card"]{text-decoration: none}.glow,.glow-lg{color: inherit}.btn-terminal{border: 1px solid var(--border);background: transparent;color: var(--foreground)}.mermaid svg{display: block;margin-inline: auto;width: auto !important;height: auto !important;max-width: 100% !important;max-height: 235mm !important}.mermaid svg .node rect,.mermaid svg .node polygon,.mermaid svg .node circle,.mermaid svg .node path,.mermaid svg .label-container,.mermaid svg .cluster rect,.mermaid svg .actor,.mermaid svg .note{fill: #ffffff !important;stroke: #333333 !important}.mermaid svg .edgePath path,.mermaid svg .flowchart-link,.mermaid svg line,.mermaid svg .messageLine0,.mermaid svg .messageLine1{stroke: #333333 !important}.mermaid svg .arrowheadPath,.mermaid svg marker path,.mermaid svg marker polygon{fill: #333333 !important;stroke: #333333 !important}.mermaid svg text,.mermaid svg .nodeLabel,.mermaid svg .edgeLabel,.mermaid svg .cluster-label,.mermaid svg .messageText,.mermaid svg foreignObject div,.mermaid svg foreignObject span,.mermaid svg p{fill: #111111 !important;color: #111111 !important;background: transparent !important}.mermaid svg .edgeLabel rect,.mermaid svg .edgeLabel foreignObject>div{fill: #ffffff !important;background: #ffffff !important}}
|
|
1
|
+
/*! danieldeusing-design v0.22.0 | MIT | https://github.com/danieldeusing/danieldeusing-design */
|
|
2
|
+
*,*::before,*::after{box-sizing: border-box}*{margin: 0}html{-webkit-text-size-adjust: 100%;text-size-adjust: 100%;tab-size: 4}body{min-height: 100vh;min-height: 100dvh;line-height: 1.5}img,picture,video,canvas,svg{display: block;max-width: 100%}input,button,textarea,select{font: inherit;color: inherit}p,h1,h2,h3,h4,h5,h6{overflow-wrap: break-word}ul,ol{list-style: none;padding: 0}a{color: inherit;text-decoration: none}table{border-collapse: collapse}:root{--background: #f5efe2;--foreground: #43352a;--card: #efe7d4;--card-foreground: #43352a;--popover: #efe7d4;--popover-foreground: #43352a;--primary: #8a4516;--primary-foreground: #f8f3e8;--secondary: #ece3cf;--secondary-foreground: #43352a;--muted: #ece3cf;--muted-foreground: #71614e;--accent: #8a4516;--accent-foreground: #f8f3e8;--destructive: #a02c2c;--success: #1a6b2e;--warning: #845600;--info: #1c5f9e;--pending: #5f3dc4;--border: #d9cdb6;--input: #d9cdb6;--ring: #8a4516;--glow: rgba(160,82,45,0.1);--glow-soft: rgba(160,82,45,0.05);--scanline-opacity: 0.15;--radius: 0rem;--font-mono: "JetBrains Mono Variable","JetBrains Mono","Menlo",monospace}html[data-theme="green"]{--background: #020604;--foreground: #4fdd7d;--card: #04110a;--card-foreground: #4fdd7d;--popover: #04110a;--popover-foreground: #4fdd7d;--primary: #33ff66;--primary-foreground: #02160a;--secondary: #071509;--secondary-foreground: #4fdd7d;--muted: #071509;--muted-foreground: #3da45e;--accent: #33ff66;--accent-foreground: #02160a;--destructive: #ff5c5c;--success: #4ade80;--warning: #f5b32b;--info: #4dabf7;--pending: #b197fc;--border: #1c4a2c;--input: #1c4a2c;--ring: #33ff66;--glow: rgba(51,255,102,0.35);--glow-soft: rgba(51,255,102,0.12);--scanline-opacity: 0.5}html[data-theme="mono"]{--background: #050505;--foreground: #d4d4d4;--card: #0d0d0d;--card-foreground: #d4d4d4;--popover: #0d0d0d;--popover-foreground: #d4d4d4;--primary: #ffffff;--primary-foreground: #050505;--secondary: #111111;--secondary-foreground: #d4d4d4;--muted: #111111;--muted-foreground: #8a8a8a;--accent: #ffffff;--accent-foreground: #050505;--destructive: #ff5c5c;--success: #4ade80;--warning: #f5b32b;--info: #4dabf7;--pending: #b197fc;--border: #333333;--input: #333333;--ring: #ffffff;--glow: rgba(255,255,255,0.25);--glow-soft: rgba(255,255,255,0.08);--scanline-opacity: 0.45}html[data-theme="paper"]{--background: #fafafa;--foreground: #1f1f1f;--card: #f1f1f1;--card-foreground: #1f1f1f;--popover: #f1f1f1;--popover-foreground: #1f1f1f;--primary: #000000;--primary-foreground: #fafafa;--secondary: #efefef;--secondary-foreground: #1f1f1f;--muted: #efefef;--muted-foreground: #595959;--accent: #000000;--accent-foreground: #fafafa;--destructive: #b3261e;--success: #1a7031;--warning: #8f5d00;--info: #1c5f9e;--pending: #5f3dc4;--border: #d4d4d4;--input: #d4d4d4;--ring: #000000;--glow: rgba(0,0,0,0.06);--glow-soft: rgba(0,0,0,0.04);--scanline-opacity: 0.12}:root{--content-w: 78rem;--content-pad: 1.5rem;--fs-xs: 0.625rem;--fs-sm: 0.6875rem;--fs-base: 0.75rem;--fs-md: 0.8125rem;--fs-lg: 0.9375rem;--fs-xl: 1.125rem;--fs-2xl: 1.5rem;--lh-tight: 1.3;--lh-base: 1.5;--field-label-w: 8.5rem}html{scroll-behavior: smooth}*,*::before,*::after{border-color: var(--border)}:focus-visible{outline: 2px solid var(--ring);outline-offset: 2px}body{background: var(--background);color: var(--foreground);font-family: var(--font-mono);font-size: var(--fs-base);line-height: var(--lh-base);overflow-x: hidden;-webkit-font-smoothing: antialiased;-moz-osx-font-smoothing: grayscale}table{font-size: var(--fs-md);width: 100%}th,td{padding: 0.45rem 0.85rem;text-align: left;vertical-align: top;border-block-end: 1px solid color-mix(in srgb,var(--border) 55%,transparent)}thead th{border-block-end-color: var(--border);color: var(--muted-foreground);font-weight: 600}tbody tr:last-child>*{border-block-end: 0}tr>*:first-child{padding-inline-start: 0}tr>*:last-child{padding-inline-end: 0}tr[hidden]{display: none !important}button:not(:disabled),summary,[role="button"]{cursor: pointer}body::after{content: "";position: fixed;inset: 0;z-index: 40;pointer-events: none;background: repeating-linear-gradient( 0deg,rgba(0,0,0,0.25) 0px,rgba(0,0,0,0.25) 1px,transparent 1px,transparent 3px );opacity: var(--scanline-opacity)}::selection{background: var(--primary);color: var(--primary-foreground)}.glow{color: var(--primary);text-shadow: 0 0 8px var(--glow)}.glow-lg{color: var(--primary);text-shadow: 0 0 12px var(--glow)}.field-row{display: grid;grid-template-columns: var(--field-label-w) minmax(0,1fr);align-items: center;gap: 0.5rem 0.75rem;margin: 0.35rem 0}.field-row>.lbl{color: var(--muted-foreground);font-size: var(--fs-sm);text-align: left}.field-row>.field-val{display: flex;flex-wrap: wrap;align-items: center;gap: 0.4rem 0.9rem;min-width: 0}@media (max-width: 40rem){.field-row{grid-template-columns: 1fr;gap: 0.2rem}}.prompt{font-size: 0.75rem;font-weight: 700;color: var(--muted-foreground)}.prompt::before{content: "$ ";color: var(--primary)}.comment::before{content: "# ";color: var(--border)}.cursor-block{display: inline-block;width: 0.55em;height: 1em;margin-left: 8px;background: var(--primary);vertical-align: baseline;transform: translateY(0.12em);animation: ddd-blink 1.1s step-end infinite}@keyframes ddd-blink{50%{opacity: 0}}.btn-terminal{display: inline-block;background: var(--primary);color: var(--primary-foreground);font-weight: 700;padding: 12px 24px;box-shadow: 0 0 16px var(--glow);transition: box-shadow 0.15s ease,transform 0.15s ease}.btn-terminal::before{content: "> "}.btn-terminal--ghost{background: transparent;color: var(--primary);border: 1px solid var(--border);box-shadow: none;font-weight: 400}.btn-terminal--ghost::before{content: ""}.btn-terminal--ghost:hover{border-color: var(--primary);background: color-mix(in srgb,var(--primary) 10%,transparent);box-shadow: none}.btn-terminal:hover{box-shadow: 0 0 28px var(--glow);transform: translateY(-2px)}.btn-terminal:disabled{opacity: 0.45;cursor: default;box-shadow: none;transform: none}.btn-terminal--compact{padding: 0.28rem 0.7rem;font-size: var(--fs-sm)}.btn-terminal--destructive{display: inline-flex;align-items: center;justify-content: center;padding: 0.3rem;color: var(--destructive);border-color: color-mix(in srgb,var(--destructive) 45%,var(--border))}.btn-terminal--destructive::before{content: ""}.btn-terminal--destructive::after{content: "";display: block;width: 0.875rem;height: 0.875rem;background: currentColor;-webkit-mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath d='M9 3h6v3H9zM4 6h16v2H4zM6 9h12l-1 12H7z'/%3E%3C/svg%3E") center / contain no-repeat;mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath d='M9 3h6v3H9zM4 6h16v2H4zM6 9h12l-1 12H7z'/%3E%3C/svg%3E") center / contain no-repeat}.btn-terminal--destructive:hover{border-color: var(--destructive);background: color-mix(in srgb,var(--destructive) 12%,transparent);box-shadow: none}@media (pointer: coarse){.btn-terminal--destructive{min-width: 44px;min-height: 44px}}.btn-terminal--edit{display: inline-flex;align-items: center;justify-content: center;padding: 0.3rem}.btn-terminal--edit::before{content: ""}.btn-terminal--edit::after{content: "";display: block;width: 0.875rem;height: 0.875rem;background: currentColor;-webkit-mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath d='M3 17.25V21h3.75L17.81 9.94l-3.75-3.75L3 17.25zM20.71 7.04l-3.75-3.75-1.83 1.83 3.75 3.75 1.83-1.83z'/%3E%3C/svg%3E") center / contain no-repeat;mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath d='M3 17.25V21h3.75L17.81 9.94l-3.75-3.75L3 17.25zM20.71 7.04l-3.75-3.75-1.83 1.83 3.75 3.75 1.83-1.83z'/%3E%3C/svg%3E") center / contain no-repeat}@media (pointer: coarse){.btn-terminal--edit{min-width: 44px;min-height: 44px}}.link-quiet{color: var(--muted-foreground);text-decoration: underline;text-underline-offset: 4px;transition: color 0.15s ease}.link-quiet:hover{color: var(--primary)}.anim-toggle{display: inline-flex;align-items: center;gap: 6px;font: inherit;color: var(--muted-foreground);background: none;border: 0;cursor: pointer;transition: color 0.15s ease}.anim-toggle:hover{color: var(--primary)}.anim-toggle[aria-pressed="true"] [data-anim-box]{color: var(--primary)}.dd-dot{display: inline-block;width: 11px;height: 11px;flex: none;border-radius: 50%;background: currentColor}.dd-flag{display: inline-block;width: 15px;height: 10px;flex: none;border: 1px solid var(--border);background-size: cover;background-repeat: no-repeat}.dd-flag-de{background-image: url("data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 18 12'><rect width='18' height='4' fill='%23262626'/><rect y='4' width='18' height='4' fill='%23c83232'/><rect y='8' width='18' height='4' fill='%23e8be32'/></svg>")}.dd-flag-en{background-image: url("data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 18 12'><rect width='18' height='12' fill='%23b22234'/><rect y='4' width='18' height='4' fill='%23ececec'/><rect width='8' height='6' fill='%233c3b6e'/></svg>")}.dd-flag-pt{background-image: url("data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 18 12'><rect width='18' height='12' fill='%231c9148'/><polygon points='9,1.5 15.5,6 9,10.5 2.5,6' fill='%23e8c832'/><rect x='8' y='5' width='2' height='2' fill='%232c3f87'/></svg>")}.dd-flag-es{background-image: url("data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 18 12'><rect width='6' height='12' fill='%2315734c'/><rect x='6' width='6' height='12' fill='%23ececec'/><rect x='12' width='6' height='12' fill='%23bf2233'/><rect x='8' y='5' width='2' height='2' fill='%237a5c3a'/></svg>")}.ascii-rule{margin: 1rem 0;border: 0;color: var(--border);line-height: 1;white-space: nowrap;overflow: hidden;user-select: none}.ascii-rule::before{content: "────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────"}.card-terminal{background: var(--card);border: 1px solid var(--border);transition: border-color 0.15s ease,box-shadow 0.15s ease}.card-terminal:hover{border-color: var(--primary);box-shadow: 0 0 20px var(--glow-soft)}.dropdown{position: relative}.dropdown>summary{display: inline-flex;align-items: center;gap: 6px;cursor: pointer;user-select: none;list-style: none;color: var(--muted-foreground);transition: color 0.15s ease}.dropdown>summary::-webkit-details-marker{display: none}.dropdown>summary:hover,.dropdown[open]>summary{color: var(--primary)}.dropdown-panel{position: absolute;right: 0;bottom: calc(100% + 12px);z-index: 50;min-width: 128px;margin: 0;padding: 4px 0;list-style: none;background: var(--card);border: 1px solid var(--border);border-radius: var(--radius);box-shadow: 0 0 20px var(--glow-soft)}.dropdown-panel--down{top: calc(100% + 8px);bottom: auto;left: 0;right: auto}.dropdown-item{display: flex;align-items: center;gap: 8px;width: 100%;padding: 5px 14px;text-align: left;font: inherit;line-height: 1.5;background: none;border: 0;color: var(--muted-foreground);cursor: pointer;white-space: nowrap;transition: color 0.15s ease}.dropdown-item:hover{color: var(--primary);background: var(--muted)}.dropdown-item[aria-current="true"],html:not([data-theme]) .dropdown-item[data-theme-value="warm"],html[data-theme="green"] .dropdown-item[data-theme-value="green"],html[data-theme="mono"] .dropdown-item[data-theme-value="mono"],html[data-theme="paper"] .dropdown-item[data-theme-value="paper"],html[data-theme="warm"] .dropdown-item[data-theme-value="warm"]{color: var(--primary);text-shadow: 0 0 8px var(--glow)}select,.select-trigger{appearance: none;-webkit-appearance: none;max-width: 100%;padding: 0.28rem 1.45rem 0.28rem 0.5rem;font: inherit;font-size: var(--fs-sm);line-height: var(--lh-tight);text-align: left;color: var(--foreground);background-color: transparent;background-image: linear-gradient(45deg,transparent 50%,currentColor 50%),linear-gradient(135deg,currentColor 50%,transparent 50%);background-position: right 0.78rem center,right 0.5rem center;background-size: 0.28rem 0.28rem;background-repeat: no-repeat;border: 1px solid color-mix(in srgb,var(--foreground) 60%,transparent);cursor: pointer;transition: border-color 0.15s ease}select:hover,.select-trigger:hover,.select-trigger[aria-expanded="true"]{border-color: var(--primary)}select:disabled,.select-trigger:disabled{opacity: 0.45;cursor: default}.select-trigger[aria-expanded="true"]{background-image: linear-gradient(135deg,transparent 50%,currentColor 50%),linear-gradient(45deg,currentColor 50%,transparent 50%)}.select-field{position: relative;display: inline-flex;max-width: 100%;vertical-align: middle}.select-field>select{position: absolute;inset: 0;width: 100%;height: 100%;opacity: 0;pointer-events: none}.select-trigger{display: inline-flex;align-items: center;width: 100%}.select-value{min-width: 0;overflow: hidden;text-overflow: ellipsis;white-space: nowrap}.select-value::after{content: "\200b"}.select-panel{position: fixed;z-index: 60;margin: 0;padding: 0.15rem 0;overflow-y: auto;overscroll-behavior: contain;list-style: none;font-size: var(--fs-sm);line-height: var(--lh-tight);color: var(--popover-foreground);background: var(--popover);border: 1px solid color-mix(in srgb,var(--foreground) 60%,transparent);box-shadow: 0 0 20px var(--glow-soft)}.select-option{display: flex;align-items: center;gap: 0.4rem;padding: 0.28rem 0.9rem 0.28rem 0.55rem;border-inline-start: 2px solid transparent;white-space: nowrap;cursor: pointer}.select-option:hover,.select-option[data-active="true"]{background: var(--muted)}.select-option[aria-selected="true"]{color: var(--primary);font-weight: 700;border-inline-start-color: var(--primary)}.select-option[aria-disabled="true"]{color: color-mix(in srgb,var(--popover-foreground) 65%,var(--popover));cursor: default}.select-option[aria-disabled="true"]:hover{background: none}.select-group{padding: 0.35rem 0.9rem 0.15rem;color: var(--muted-foreground);font-size: var(--fs-xs);text-transform: uppercase;letter-spacing: 0.06em}.table-pager{display: flex;flex-wrap: wrap;align-items: center;gap: 0.45rem 0.9rem;margin-block-start: 0.6rem;font-size: var(--fs-sm)}.table-pager-status{margin: 0;color: var(--muted-foreground)}.table-pager-size{display: inline-flex;align-items: center;gap: 0.4rem;margin-inline-start: auto;color: var(--muted-foreground)}.table-pager-nav{display: inline-flex;gap: 0.4rem}.eli5{margin: 1.25em 0;padding: 0.7em 1em;color: var(--foreground);background: color-mix(in srgb,var(--primary) 7%,var(--card));border: 1px solid var(--border);border-left: 3px solid var(--primary);border-radius: var(--radius);font-size: 0.92em;line-height: 1.6}.eli5::before{content: "ELI5";display: inline-flex;align-items: center;justify-content: center;margin-right: 0.6em;vertical-align: 0.08em;font-family: var(--font-mono);font-size: 0.72em;font-weight: 700;line-height: 1;letter-spacing: 0.04em;padding: 0.34em 0.6em;border-radius: var(--radius);background: var(--destructive);color: var(--primary-foreground)}.eli5-term{font-weight: 700;color: var(--primary)}html.anim-off *,html.anim-off *::before,html.anim-off *::after{animation: none !important}html.anim-off .cursor-block{display: none}@media (prefers-reduced-motion: no-preference){html.term-anim [data-term] .prompt:not(.term-live),html.term-anim [data-term] .prompt:not(.term-live)::before{color: transparent}html.term-anim [data-term] [data-term-out]:not(.term-show){visibility: hidden}html.term-anim [data-term] [data-term-out].term-show{animation: ddd-term-output 0.2s steps(3,end) both}@keyframes ddd-term-output{from{opacity: 0}}html.term-anim [data-term] [data-term-out].term-show>*{animation: ddd-term-output 0.25s steps(3,end) both}html.term-anim [data-term] [data-term-out].term-show>*:nth-child(2){animation-delay: 0.09s}html.term-anim [data-term] [data-term-out].term-show>*:nth-child(3){animation-delay: 0.18s}html.term-anim [data-term] [data-term-out].term-show>*:nth-child(4){animation-delay: 0.27s}html.term-anim [data-term] [data-term-out].term-show>*:nth-child(5){animation-delay: 0.36s}html.term-anim [data-term] [data-term-out].term-show>*:nth-child(6){animation-delay: 0.45s}html.term-anim [data-term] [data-term-out].term-show>*:nth-child(7){animation-delay: 0.54s}html.term-anim [data-term] [data-term-out].term-show>*:nth-child(8){animation-delay: 0.63s}html.term-anim [data-term] [data-term-out].term-show>*:nth-child(9){animation-delay: 0.72s}html.term-anim [data-term] [data-term-out].term-show>*:nth-child(10){animation-delay: 0.81s}html.term-anim [data-term] [data-term-out].term-show>*:nth-child(n + 11){animation-delay: 0.9s}.term-caret{display: inline-block;width: 0.55em;height: 1em;margin-left: 2px;background: var(--primary);vertical-align: baseline;transform: translateY(0.12em)}}.tabs{display: flex;flex-wrap: wrap;gap: 0.15rem;margin: 1.35rem 0 0;border-bottom: 2px solid color-mix(in srgb,var(--border) 80%,transparent)}.tab{background: transparent;border: 0;margin-bottom: -2px;font: inherit;font-size: 0.86em;cursor: pointer;padding: 0.5rem 0.95rem;color: var(--muted-foreground);transition: background 0.12s ease,color 0.12s ease}.tab:hover{color: var(--primary);background: color-mix(in srgb,var(--primary) 10%,transparent)}.tab[aria-selected="true"]{background: var(--primary);color: var(--background);font-weight: 700}.tab:focus-visible{outline: 2px solid color-mix(in srgb,var(--primary) 55%,transparent);outline-offset: -2px}.tab--info{margin-inline-start: auto}@media (max-width: 40rem){.tab--info{margin-inline-start: 0}}@media (pointer: coarse),(max-width: 40rem){.tab{padding: 0.7rem 0.8rem;min-height: 44px}}section.doc.tab-panel{margin-top: 1.35rem}details.fold{border-top: 1px solid color-mix(in srgb,currentColor 15%,transparent)}details.fold>summary{display: flex;align-items: center;gap: 0.5rem;cursor: pointer;list-style: none;padding: 0.55rem 0;font-size: 0.85rem;color: var(--muted-foreground)}details.fold>summary::-webkit-details-marker{display: none}details.fold>summary::before{content: "\25B8";flex: none;opacity: 0.6}details.fold[open]>summary::before{content: "\25BE"}details.fold>summary:hover{color: var(--primary)}details.fold>summary:focus-visible{outline: 2px solid color-mix(in srgb,currentColor 45%,transparent);outline-offset: 2px}.fold-body{font-size: 0.9rem;padding: 0 0 0.7rem 1.1rem}.fold-body>* + *{margin-top: 0.6rem}.legend{list-style: none;padding: 0;margin: 0.7rem 0 0;display: grid;gap: 0.4rem 1.4rem;font-size: 0.86rem}@media (min-width: 52rem){.legend{grid-template-columns: repeat(2,minmax(0,1fr))}}.legend>li{line-height: 1.6}.legend>li>:first-child{margin-right: 0.45rem}.dgm-zoomable{position: relative;cursor: zoom-in}.dgm-zoomable::after{content: "⤢";position: absolute;top: 0.4rem;right: 0.5rem;font-size: 0.85rem;line-height: 1;opacity: 0.45;pointer-events: none;color: var(--muted-foreground)}.dgm-zoomable:hover::after,.dgm-zoomable:focus-visible::after{opacity: 1;color: var(--primary)}.dgm-zoomable:focus-visible{outline: 2px solid var(--primary);outline-offset: 3px}html.dgm-locked,html.dgm-locked body{overflow: hidden}.dgm-overlay{display: none;position: fixed;inset: 0;z-index: 90;background: color-mix(in srgb,var(--background) 96%,transparent);flex-direction: column}.dgm-overlay.open{display: flex}.dgm-bar{flex: none;display: flex;align-items: center;justify-content: flex-end;gap: 0.4rem;padding: 0.5rem 0.75rem;border-bottom: 1px solid var(--border);background: var(--card)}.dgm-btn{font: inherit;font-size: 0.82rem;cursor: pointer;padding: 0.3rem 0.7rem;min-height: 32px;background: transparent;border: 1px solid var(--border);color: var(--foreground)}.dgm-btn:hover{border-color: var(--primary);color: var(--primary)}.dgm-btn:focus-visible{outline: 2px solid var(--primary);outline-offset: 1px}.dgm-close{border-color: color-mix(in srgb,var(--destructive) 55%,transparent);color: var(--destructive)}.dgm-close:hover{border-color: var(--destructive);color: var(--destructive)}.dgm-stage{flex: 1;overflow: hidden;display: grid;place-items: center;cursor: grab;touch-action: none}.dgm-stage.is-grabbing{cursor: grabbing}.dgm-art{transform-origin: center center;will-change: transform}.dgm-art>svg,.dgm-art>img{display: block;max-width: none;max-height: none}@media print{.dgm-overlay,.dgm-zoomable::after{display: none !important}}.minimap{position: fixed;left: 0;top: var(--ls-nav-top,3rem);bottom: var(--ls-nav-bottom,2.2rem);z-index: 20;display: none;flex-direction: column;justify-content: center;gap: 0.5rem;width: 2rem;padding: 0.75rem 0 0.75rem 0.75rem;overflow-y: auto;overscroll-behavior: contain;scrollbar-width: none}.minimap::-webkit-scrollbar{display: none}@media (min-width: 64rem){.minimap{display: flex}}.minimap-bar{flex: 0 0 auto;width: var(--minimap-bar-w,100%);height: 2px;padding: 0;border: 0;border-radius: 1px;background: var(--border);cursor: pointer;transition: background 0.15s ease,transform 0.15s ease;transform-origin: left center}.minimap-bar:hover{background: var(--muted-foreground);transform: scaleY(2)}.minimap-bar[data-tip]{cursor: pointer}.minimap-bar:focus-visible{outline: 2px solid var(--ring);outline-offset: 3px}.minimap-bar.active{background: var(--primary);transform: scaleY(2)}@media print{.minimap{display: none !important}}.wrap{max-width: var(--content-w);margin-inline: auto;padding-inline: var(--content-pad)}.tablewrap{position: relative;overflow-x: auto;overflow-y: auto;max-block-size: var(--tablewrap-max-h,none)}.tablewrap::after{content: "";position: absolute;inset-block: 0;right: 0;width: 1.5rem;pointer-events: none;opacity: 0.9;background: linear-gradient(to right,transparent,var(--background,transparent))}.tablewrap thead th{position: sticky;inset-block-start: 0;z-index: 1;background: var(--background,#f5efe2)}.tickstrip:not(:empty){border: 1px solid var(--border);background: var(--card);margin: 1.6rem 0 1.5rem}.ticktable{width: 100%;border-collapse: collapse;font-size: 0.82rem}.ticktable td{padding: 0.5rem 0.75rem;vertical-align: baseline;border: 0;white-space: nowrap}.ticktable td.tick-stats{width: 100%;white-space: normal;text-align: right}.tick{border-bottom: 1px solid color-mix(in srgb,var(--border) 55%,transparent)}.tick:last-child{border-bottom: 0}.tick-dot{font-size: 0.7em;width: 1.5rem;padding-right: 0 !important;border-left: 3px solid transparent;padding-left: 0.6rem !important}.tick--ok .tick-dot{color: var(--success);border-left-color: color-mix(in srgb,var(--success) 45%,transparent)}.tick--stale .tick-dot{color: var(--destructive);border-left-color: var(--destructive)}.tick--never .tick-dot{color: var(--muted-foreground);border-left-color: color-mix(in srgb,var(--muted-foreground) 35%,transparent)}.tick-name{font-weight: 700;color: var(--foreground);letter-spacing: 0.01em}.tick-last,.tick-next{font-variant-numeric: tabular-nums;color: var(--muted-foreground)}.tick-next{opacity: 0.72}.tick--stale .tick-last{color: var(--destructive);font-weight: 700}.tick--never .tick-last{font-style: italic}.tick-stats{color: var(--muted-foreground)}.tick-stats b{font-weight: 700;color: var(--primary);font-variant-numeric: tabular-nums}.tick-sep{font-style: normal;opacity: 0.35;margin: 0 0.5rem}.tick--stale{background: color-mix(in srgb,var(--destructive) 8%,transparent)}@media (max-width: 40rem){.ticktable,.ticktable tbody,.tick,.ticktable td{display: block}.tick{padding: 0.4rem 0.5rem}.ticktable td{display: inline-block;padding: 0.1rem 0.5rem 0.1rem 0}.ticktable td.tick-stats{display: block;width: auto;text-align: left}}.skip-link{position: absolute;left: -999px;top: 0;z-index: 100;background: var(--card);border: 1px solid var(--primary);padding: 0.5rem 1rem;color: var(--primary)}.skip-link:focus{left: 0.5rem;top: 0.5rem}.visually-hidden{position: absolute;width: 1px;height: 1px;overflow: hidden;clip: rect(0 0 0 0);white-space: nowrap}header.bar{position: sticky;top: 0;z-index: 30;display: flex;align-items: center;justify-content: space-between;gap: 1rem;padding: 0.6rem 1.25rem;border-bottom: 1px solid var(--border);background: var(--card)}header.bar .brand{display: inline-flex;align-items: center;font-weight: 700;font-size: 1rem}header.bar .brand,header.bar .brand a{color: inherit;text-decoration: none}header.bar .dropdown summary{padding: 0.6rem 0.4rem}.bar-right{display: flex;align-items: center;gap: 1rem;flex-shrink: 0}footer.status{position: fixed;inset-inline: 0;bottom: 0;z-index: 50;min-height: 2rem;display: flex;align-items: center;justify-content: space-between;gap: 1rem;padding: 0.35rem 1.25rem;border-top: 1px solid var(--border);background: var(--card);font-size: 0.7rem}footer.status .dropdown summary,footer.status .anim-toggle{padding: 0.55rem 0.4rem}.status-left{color: var(--muted-foreground);min-width: 0;overflow: hidden;text-overflow: ellipsis;white-space: nowrap}.status-right{display: flex;align-items: center;gap: 1rem;flex-shrink: 0}.sep{width: 1px;height: 0.9rem;background: var(--border)}.doc-link{color: var(--muted-foreground);text-decoration: none;transition: color 0.15s ease}.doc-link:hover{color: var(--primary)}.doc-link--forward{color: var(--primary);white-space: nowrap}.doc-link--forward:hover{color: var(--primary);text-decoration: underline}.ls-group{display: block;padding: 0.55rem 0 0.15rem;font-size: var(--fs-xs);letter-spacing: 0.08em;color: var(--muted-foreground);opacity: 0.75}.ls-panel>li:first-child .ls-group{padding-top: 0}.ls-row{display: flex;align-items: baseline;gap: 0.9rem}.ls-perm{color: color-mix(in srgb,var(--muted-foreground) 75%,var(--card));font-size: 0.72rem;letter-spacing: 0}.ls-row--sub .ls-name{margin-left: 0.55rem;padding-left: 0.75rem;border-left: 1px solid color-mix(in srgb,var(--muted-foreground) 40%,transparent)}.ls-row--sub2 .ls-name{margin-left: 1.7rem}.ls-panel .ls-name{color: var(--muted-foreground)}.ls-panel .ls-row--dir .ls-name{color: var(--primary);font-weight: 700}.ls-row[aria-current="page"]{background: color-mix(in srgb,var(--primary) 12%,transparent);box-shadow: inset 3px 0 0 0 var(--primary)}.ls-row[aria-current="page"] .ls-name{color: var(--primary);font-weight: 700;text-shadow: 0 0 8px var(--glow)}.ls-row[aria-current="page"] .ls-perm{color: color-mix(in srgb,var(--primary) 65%,transparent)}.ls-row[aria-current="page"]::after{content: "←";margin-left: auto;color: var(--primary)}.dropdown-panel.ls-panel{min-width: 17rem;left: auto !important;right: 0}:root{--ls-nav-w: 17rem;--ls-nav-top: 3rem;--ls-nav-bottom: 2.2rem}@media (min-width: 48.0625rem){html{--ls-nav-inset: 0rem}html:has(.ls-nav):not([data-ls-nav="off"]){--ls-nav-inset: var(--ls-nav-w)}body{padding-right: var(--ls-nav-inset)}header.bar,.bleed-rail{margin-right: calc(-1 * var(--ls-nav-inset))}.ls-nav{position: fixed;top: var(--ls-nav-top);right: 0;bottom: var(--ls-nav-bottom);width: var(--ls-nav-w);z-index: 25;display: flex;flex-direction: column;overflow: hidden;background: var(--card);border-left: 1px solid var(--border)}html:not(.anim-off) .ls-nav{transition: transform 0.18s ease}html[data-ls-nav="off"] .ls-nav{transform: translateX(100%)}.ls-nav-head{display: inline-flex;align-items: center;gap: 0.5rem;margin-left: 0.75rem}.ls-nav-title{font-weight: 700;font-size: 0.9rem}.ls-nav .ls-panel{flex: 1;min-height: 0;overflow-y: auto;list-style: none;margin: 0;padding: 0.5rem 0 1rem}.ls-nav-toggle{background: none;border: 0;font: inherit;cursor: pointer;line-height: 1;color: var(--muted-foreground);padding: 0.4rem 0.55rem}.ls-nav-toggle::after{content: "\00BB"}html[data-ls-nav="off"] .ls-nav-toggle::after{content: "\00AB"}.ls-nav-toggle:hover{color: var(--primary)}.ls-nav-toggle:focus-visible{outline: 2px solid color-mix(in srgb,var(--primary) 55%,transparent);outline-offset: -2px}}.nav-burger{display: none;background: none;border: 0;color: var(--muted-foreground);cursor: pointer;padding: 0.55rem;margin: -0.3rem -0.3rem -0.3rem 0}.nav-burger:hover{color: var(--primary)}.mobile-nav{display: none;list-style: none;margin: 0;padding: 0}.mobile-footer{display: none}@media (max-width: 48rem){.nav-burger{display: inline-flex;align-items: center}.site-nav{display: none;position: absolute;top: 100%;left: 0;right: 0;z-index: 40;background: var(--card);border-bottom: 1px solid var(--border);padding: 0.4rem 1.25rem 0.9rem;max-height: calc(100vh - 6rem);overflow-y: auto}.site-nav.open{display: block}.site-nav>.ls-nav,.mobile-nav{display: flex;flex-direction: column}.site-nav:has(>.ls-nav) .mobile-nav{display: none}.site-nav>.ls-nav .ls-row{align-items: center;min-height: 44px;border-bottom: 1px solid var(--border)}.site-nav>.ls-nav .ls-row .ls-name{display: flex;align-items: center;flex: 1;min-height: 44px}.site-nav>.ls-nav .ls-row:last-child{border-bottom: 0}.mobile-nav .mobile-item{display: flex;align-items: baseline;gap: 0.9rem;padding: 0.65rem 0;text-decoration: none;color: var(--foreground);font-size: 0.9rem;border-bottom: 1px solid var(--border)}.mobile-nav li:last-child .mobile-item{border-bottom: 0}.mobile-nav .mobile-item:active .ls-name,.mobile-nav .mobile-item:hover .ls-name{color: var(--primary)}.ls-nav-toggle{display: none}footer.status{display: none}.mobile-footer{display: flex;flex-direction: column;gap: 0.55rem;margin-top: 0.8rem;border-top: 1px solid var(--border);padding-top: 0.8rem;font-size: 0.85rem}.mobile-footer .doc-link{padding: 0.2rem 0}.mobile-footer .mobile-theme summary{list-style: none;display: flex;align-items: center;gap: 0.5rem;color: var(--muted-foreground);cursor: pointer;padding: 0.2rem 0}.mobile-footer .mobile-theme summary::-webkit-details-marker{display: none}.mobile-footer .mf-chev{margin-left: auto;opacity: 0.7}.mobile-footer .mf-panel{display: flex;flex-direction: column;margin-top: 0.3rem}.mobile-footer .mf-panel .dropdown-item{text-align: left;padding: 0.4rem 0.6rem}.mobile-footer .anim-toggle{display: inline-flex;gap: 0.5rem;align-items: center;background: none;border: 0;padding: 0.2rem 0;color: var(--muted-foreground);font: inherit;cursor: pointer}}[data-tip]{cursor: help}span[data-tip],th[data-tip]{border-bottom: 1px dotted var(--muted-foreground)}#ddtip{position: fixed;z-index: 9999;display: none;max-width: 340px;background: var(--background);color: var(--foreground);border: 1px solid var(--muted-foreground);border-radius: var(--radius);padding: 7px 10px;font-size: 0.72rem;line-height: 1.45;font-weight: 400;text-align: left;white-space: normal;pointer-events: none;box-shadow: 0 4px 14px rgba(0,0,0,0.25)}@media print{@page{margin: 14mm 12mm}:root,html,html[data-theme]{--background: #ffffff;--foreground: #111111;--card: #ffffff;--card-foreground: #111111;--popover: #ffffff;--popover-foreground: #111111;--primary: #000000;--primary-foreground: #ffffff;--secondary: #ffffff;--secondary-foreground: #111111;--muted: #ffffff;--muted-foreground: #444444;--accent: #000000;--accent-foreground: #ffffff;--destructive: #7a1c1c;--border: #9a9a9a;--input: #9a9a9a;--ring: #000000;--glow: transparent;--glow-soft: transparent;--scanline-opacity: 0}html{zoom: 1 !important;font-size: 16px;scroll-behavior: auto}body{background: #ffffff;color: var(--foreground);font-size: 12px;line-height: 1.45;overflow: visible}body::after{display: none !important}*,*::before,*::after{animation: none !important;transition: none !important;text-shadow: none !important;box-shadow: none !important}html.term-anim [data-term] [data-term-out],html.term-anim [data-term] [data-term-out]:not(.term-show),html.term-anim [data-term] [data-term-out]>*{visibility: visible !important;opacity: 1 !important}html.term-anim [data-term] .prompt:not(.term-live){color: var(--muted-foreground) !important}html.term-anim [data-term] .prompt:not(.term-live)::before{color: var(--primary) !important}header.bar,footer.status,.site-nav,.nav-burger,.mobile-nav,.mobile-footer,.skip-link,.dropdown,.dropdown-panel,.select-panel,.anim-toggle,.cursor-block,.term-caret,.ascii-rule,.toc,#ddtip{display: none !important}body{padding-right: 0 !important}[data-tip]{cursor: auto}span[data-tip],th[data-tip]{border-bottom: 0}.wrap{max-width: none !important;margin: 0 !important;padding: 0 !important}.layout{display: block !important}.content{min-width: 0}h1,.title{font-size: 1.5rem !important}h2{font-size: 1.15rem !important}h3,.sub{font-size: 1rem !important}h4{font-size: 0.9rem !important}.prompt{font-size: 0.7rem}h1,h2,h3,h4,.prompt,.toc-label{break-after: avoid-page;break-inside: avoid}p,li,blockquote{orphans: 3;widows: 3}pre,figure,blockquote,tr,img,.eli5,.card-terminal{break-inside: avoid}svg{break-inside: auto}thead{display: table-header-group}.tablewrap,.table-scroll,pre,.diagram{overflow: visible !important;max-width: 100% !important}pre{white-space: pre-wrap !important;overflow-wrap: anywhere;font-size: 0.75rem !important;background: transparent !important}pre.mermaid[data-processed]{white-space: normal !important}code{background: transparent !important;overflow-wrap: anywhere}table{width: 100% !important;table-layout: auto;border-collapse: collapse;font-size: 0.75rem !important}th,td{overflow-wrap: break-word;white-space: normal !important}td code,th code,td a,th a{overflow-wrap: anywhere}a{color: inherit;text-decoration: underline;text-underline-offset: 2px}a.card-terminal,a[class*="card"]{text-decoration: none}.glow,.glow-lg{color: inherit}.btn-terminal{border: 1px solid var(--border);background: transparent;color: var(--foreground)}.mermaid svg{display: block;margin-inline: auto;width: auto !important;height: auto !important;max-width: 100% !important;max-height: 235mm !important}.mermaid svg .node rect,.mermaid svg .node polygon,.mermaid svg .node circle,.mermaid svg .node path,.mermaid svg .label-container,.mermaid svg .cluster rect,.mermaid svg .actor,.mermaid svg .note{fill: #ffffff !important;stroke: #333333 !important}.mermaid svg .edgePath path,.mermaid svg .flowchart-link,.mermaid svg line,.mermaid svg .messageLine0,.mermaid svg .messageLine1{stroke: #333333 !important}.mermaid svg .arrowheadPath,.mermaid svg marker path,.mermaid svg marker polygon{fill: #333333 !important;stroke: #333333 !important}.mermaid svg text,.mermaid svg .nodeLabel,.mermaid svg .edgeLabel,.mermaid svg .cluster-label,.mermaid svg .messageText,.mermaid svg foreignObject div,.mermaid svg foreignObject span,.mermaid svg p{fill: #111111 !important;color: #111111 !important;background: transparent !important}.mermaid svg .edgeLabel rect,.mermaid svg .edgeLabel foreignObject>div{fill: #ffffff !important;background: #ffffff !important}}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@danieldeusing/design",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.22.0",
|
|
4
4
|
"description": "The danieldeusing terminal design system — framework-agnostic CSS tokens, components, and a vanilla-JS runtime for the CRT/JetBrains-Mono look used across danieldeusing.de, seedr, and briefs.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
package/runtime/index.js
CHANGED
|
@@ -0,0 +1,341 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* pagination.js — a long table shows 20 rows at a time, and the rest are still THERE.
|
|
3
|
+
*
|
|
4
|
+
* Markup contract:
|
|
5
|
+
* <table data-table-id="docs-scopes">
|
|
6
|
+
*
|
|
7
|
+
* One attribute — unlike `initTableScroll()` and `initSelects()`, whose contract is
|
|
8
|
+
* nothing at all. The difference is deliberate and it is explained under "IDENTITY"
|
|
9
|
+
* below: a remembered page size has to be remembered AGAINST something, and every
|
|
10
|
+
* scheme for guessing that something is wrong the first time a table moves.
|
|
11
|
+
*
|
|
12
|
+
* ── THE ORDER IS FILTER, THEN SORT, THEN SLICE, AND THAT IS THE WHOLE FEATURE ──────
|
|
13
|
+
*
|
|
14
|
+
* The natural wrong implementation of paging is to cut the data to twenty rows and
|
|
15
|
+
* then wire the sort and the filter to what is on screen. It looks right on page 1
|
|
16
|
+
* and it is a table that lies: sorting by "newest" reorders twenty arbitrary rows
|
|
17
|
+
* while the actual newest row sits on page 3, and filtering finds nothing because the
|
|
18
|
+
* match was never in the slice being searched.
|
|
19
|
+
*
|
|
20
|
+
* THIS COMPONENT CANNOT MAKE THAT MISTAKE, because it cannot sort and it cannot
|
|
21
|
+
* filter. It has no idea what a row means. It reads the `<tbody>` that is already in
|
|
22
|
+
* the document — whatever produced those rows has already filtered and already sorted
|
|
23
|
+
* the FULL dataset, because that is the only way rows get into a tbody — and hides
|
|
24
|
+
* all but one window of them. Slicing last is not a rule anyone here has to remember;
|
|
25
|
+
* it is the only thing this code is able to do. A page keeps its own sort and filter
|
|
26
|
+
* and needs no edit to gain paging (cockpit's `cockpitTable` engine is the worked
|
|
27
|
+
* example: `visibleRows()` filters and sorts `rows`, the full array, exactly as it did
|
|
28
|
+
* before this existed).
|
|
29
|
+
*
|
|
30
|
+
* ── HIDING, NOT REMOVING ──────────────────────────────────────────────────────────
|
|
31
|
+
*
|
|
32
|
+
* Turning the page sets the `hidden` attribute on the rows that are off-window and
|
|
33
|
+
* clears it on the twenty that are not. No markup is rebuilt, nothing is re-parsed and
|
|
34
|
+
* no `innerHTML` is written — which matters here beyond speed: cockpit patches its
|
|
35
|
+
* tables in place (`cockpit/pages/dom-patch.js`) precisely so a background refresh does
|
|
36
|
+
* not destroy half-typed input, focus, or an opened <details>. A pager that re-rendered
|
|
37
|
+
* the table on every page change would hand all of that back.
|
|
38
|
+
*
|
|
39
|
+
* ── IDENTITY: `data-table-id`, OR PAGINATION DOES NOT ENGAGE ──────────────────────
|
|
40
|
+
*
|
|
41
|
+
* The size the reader picks is remembered per table in localStorage, so the key has to
|
|
42
|
+
* name a table. Deriving one from the page path plus the table's index on the page is
|
|
43
|
+
* the obvious move and it is a bug with a delay on it: add a table above another one
|
|
44
|
+
* and every reader's "100 per page" silently becomes some other table's setting, with
|
|
45
|
+
* no error and nothing to notice. So the id is REQUIRED and never guessed.
|
|
46
|
+
*
|
|
47
|
+
* A table without one is left alone entirely — it keeps every row, which is what it did
|
|
48
|
+
* before this ran, so a reader is never shown a broken or half-paged table. The warning
|
|
49
|
+
* is aimed at the one person who can fix it and fires only when the omission actually
|
|
50
|
+
* costs something: a table long enough to have been paged. Short reference tables (the
|
|
51
|
+
* estate has around forty of them — four machines, three agents) are silent, because
|
|
52
|
+
* "you forgot an id" on a table that would never have paged anyway is noise that
|
|
53
|
+
* teaches people to ignore the console.
|
|
54
|
+
*/
|
|
55
|
+
|
|
56
|
+
/** The sizes offered in the picker. 20 is the default; the rest are the reader's call. */
|
|
57
|
+
export const PAGE_SIZES = [5, 10, 20, 50, 100, 200];
|
|
58
|
+
export const DEFAULT_PAGE_SIZE = 20;
|
|
59
|
+
|
|
60
|
+
const STORE_PREFIX = "table-rows:";
|
|
61
|
+
const enhanced = new WeakMap();
|
|
62
|
+
let documentObserver = null;
|
|
63
|
+
|
|
64
|
+
/* ── the pure core ────────────────────────────────────────────────────────────────
|
|
65
|
+
* Three functions with no DOM in them, because they hold every decision worth being
|
|
66
|
+
* wrong about — which window of rows, what to do with a stored value that is junk, and
|
|
67
|
+
* which rows that window actually hides. `scripts/check-pagination.mjs` tests these
|
|
68
|
+
* directly, so the assertions are about the shipped logic rather than about a copy of
|
|
69
|
+
* it written into a harness.
|
|
70
|
+
*/
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* The window of row indices a page covers.
|
|
74
|
+
*
|
|
75
|
+
* `page` is CLAMPED rather than rejected, and that is the behaviour a live table needs:
|
|
76
|
+
* rows arrive and leave under a reader who is on page 4 (a filter narrows the set, a 30s
|
|
77
|
+
* poll returns fewer rows), and the answer to "page 4 of 2" is page 2, not an error and
|
|
78
|
+
* not an empty screen. Clamping rather than resetting to 1 is equally deliberate — a
|
|
79
|
+
* background refresh must not yank the reader back to the top of a table they are part
|
|
80
|
+
* way through.
|
|
81
|
+
*
|
|
82
|
+
* @param {number} total Rows in the full, already-filtered, already-sorted set.
|
|
83
|
+
* @param {number} size Rows per page.
|
|
84
|
+
* @param {number} page 1-based page number, possibly out of range.
|
|
85
|
+
* @returns {{page:number,pageCount:number,from:number,to:number}} `from`/`to` are a
|
|
86
|
+
* half-open index range into the full set.
|
|
87
|
+
*/
|
|
88
|
+
export function pageWindow(total, size, page) {
|
|
89
|
+
const pageCount = Math.max(1, Math.ceil(total / size));
|
|
90
|
+
const current = Math.min(Math.max(1, Math.floor(page) || 1), pageCount);
|
|
91
|
+
const from = (current - 1) * size;
|
|
92
|
+
return { page: current, pageCount, from, to: Math.min(from + size, total) };
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* A stored page size, or the default.
|
|
97
|
+
*
|
|
98
|
+
* Everything that is not one of the offered sizes becomes 20: `null` (never set),
|
|
99
|
+
* `"abc"` (junk), `999` (a size this build no longer offers), `""`, an object. The
|
|
100
|
+
* store is shared with every other tab, every older build of the page and anyone with a
|
|
101
|
+
* devtools console, so "it can only contain what we wrote" is not true of it.
|
|
102
|
+
*
|
|
103
|
+
* @param {unknown} raw
|
|
104
|
+
* @returns {number}
|
|
105
|
+
*/
|
|
106
|
+
export function normalizePageSize(raw) {
|
|
107
|
+
const size = Number(raw);
|
|
108
|
+
return PAGE_SIZES.includes(size) ? size : DEFAULT_PAGE_SIZE;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* Show the rows inside `[from, to)` and hide the rest.
|
|
113
|
+
*
|
|
114
|
+
* Writes are checked first so an unchanged row is not touched at all. That keeps the
|
|
115
|
+
* function idempotent at the DOM level — no mutation record, so the observer watching
|
|
116
|
+
* these very rows cannot be woken by this function's own work and cannot loop.
|
|
117
|
+
*
|
|
118
|
+
* @param {ArrayLike<{hidden:boolean}>} rows The full set, in display order.
|
|
119
|
+
*/
|
|
120
|
+
export function applyPageWindow(rows, from, to) {
|
|
121
|
+
for (let i = 0; i < rows.length; i += 1) {
|
|
122
|
+
const off = i < from || i >= to;
|
|
123
|
+
if (rows[i].hidden !== off) rows[i].hidden = off;
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/* ── storage ──────────────────────────────────────────────────────────────────────
|
|
128
|
+
* Both wrapped: Safari in private mode THROWS on localStorage access rather than
|
|
129
|
+
* returning null, and a table that will not render because a preference could not be
|
|
130
|
+
* read is a worse table than one that always starts at 20. Same shape as the theme
|
|
131
|
+
* read every page in the estate does pre-paint.
|
|
132
|
+
*/
|
|
133
|
+
const storedSize = (id) => {
|
|
134
|
+
try {
|
|
135
|
+
return normalizePageSize(localStorage.getItem(STORE_PREFIX + id));
|
|
136
|
+
} catch {
|
|
137
|
+
return DEFAULT_PAGE_SIZE;
|
|
138
|
+
}
|
|
139
|
+
};
|
|
140
|
+
const storeSize = (id, size) => {
|
|
141
|
+
try {
|
|
142
|
+
localStorage.setItem(STORE_PREFIX + id, String(size));
|
|
143
|
+
} catch {
|
|
144
|
+
/* nothing to do and nothing to say — the size still applies for this visit */
|
|
145
|
+
}
|
|
146
|
+
};
|
|
147
|
+
|
|
148
|
+
/* ── the component ────────────────────────────────────────────────────────────────*/
|
|
149
|
+
|
|
150
|
+
/*
|
|
151
|
+
* The rows this pager owns: the first tbody's direct rows, minus any the renderer
|
|
152
|
+
* marked `data-table-placeholder`. That opt-out exists because an engine's
|
|
153
|
+
* "Nothing matches these filters." row is a full-width message wearing a <tr>, and
|
|
154
|
+
* counting it would report "1 of 1" for an empty table and give it a page.
|
|
155
|
+
*/
|
|
156
|
+
function dataRows(table) {
|
|
157
|
+
const body = table.tBodies[0];
|
|
158
|
+
if (!body) return [];
|
|
159
|
+
const rows = [];
|
|
160
|
+
for (const row of body.rows) if (!row.hasAttribute("data-table-placeholder")) rows.push(row);
|
|
161
|
+
return rows;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
/*
|
|
165
|
+
* Built ONCE per table and then only updated — the status text and the two disabled
|
|
166
|
+
* flags. Rebuilding it per page change would destroy and re-create the <select>, which
|
|
167
|
+
* initSelects() has enhanced: the reader would lose the open list mid-click and the
|
|
168
|
+
* focus with it.
|
|
169
|
+
*
|
|
170
|
+
* The picker is a plain <select> with no class and no wrapper, which is the entire
|
|
171
|
+
* markup contract of the estate's dropdown — initSelects() finds it here exactly as it
|
|
172
|
+
* finds one written by hand, including the ones this creates long after page load.
|
|
173
|
+
* Hand-rolling a second picker beside the system's is how five copies of `.cfg-sel`
|
|
174
|
+
* happened.
|
|
175
|
+
*
|
|
176
|
+
* The label WRAPS the select and carries no aria-label, on purpose: that is the branch
|
|
177
|
+
* of initSelects()'s naming that makes the trigger announce the label AND the current
|
|
178
|
+
* value ("rows, 20"), the way a native select does. An aria-label here would replace
|
|
179
|
+
* the whole name and drop the value from it.
|
|
180
|
+
*/
|
|
181
|
+
function buildBar(instance) {
|
|
182
|
+
const bar = document.createElement("div");
|
|
183
|
+
bar.className = "table-pager";
|
|
184
|
+
|
|
185
|
+
const status = document.createElement("p");
|
|
186
|
+
status.className = "table-pager-status";
|
|
187
|
+
// role="status" is aria-live="polite" with a sensible default: turning the page
|
|
188
|
+
// changes nothing a screen reader would otherwise be told about, because the rows
|
|
189
|
+
// themselves are not focused.
|
|
190
|
+
status.setAttribute("role", "status");
|
|
191
|
+
|
|
192
|
+
const label = document.createElement("label");
|
|
193
|
+
label.className = "table-pager-size";
|
|
194
|
+
label.append("rows ");
|
|
195
|
+
const select = document.createElement("select");
|
|
196
|
+
for (const size of PAGE_SIZES) {
|
|
197
|
+
const option = document.createElement("option");
|
|
198
|
+
option.value = String(size);
|
|
199
|
+
option.textContent = String(size);
|
|
200
|
+
if (size === instance.size) option.selected = true;
|
|
201
|
+
select.appendChild(option);
|
|
202
|
+
}
|
|
203
|
+
label.appendChild(select);
|
|
204
|
+
|
|
205
|
+
const nav = document.createElement("div");
|
|
206
|
+
nav.className = "table-pager-nav";
|
|
207
|
+
const button = (text) => {
|
|
208
|
+
const element = document.createElement("button");
|
|
209
|
+
element.type = "button";
|
|
210
|
+
element.className = "btn-terminal btn-terminal--ghost btn-terminal--compact";
|
|
211
|
+
element.textContent = text;
|
|
212
|
+
return element;
|
|
213
|
+
};
|
|
214
|
+
const previous = button("← prev");
|
|
215
|
+
const next = button("next →");
|
|
216
|
+
nav.append(previous, next);
|
|
217
|
+
|
|
218
|
+
bar.append(status, label, nav);
|
|
219
|
+
|
|
220
|
+
previous.addEventListener("click", () => {
|
|
221
|
+
instance.page -= 1;
|
|
222
|
+
render(instance);
|
|
223
|
+
});
|
|
224
|
+
next.addEventListener("click", () => {
|
|
225
|
+
instance.page += 1;
|
|
226
|
+
render(instance);
|
|
227
|
+
});
|
|
228
|
+
select.addEventListener("change", () => {
|
|
229
|
+
instance.size = normalizePageSize(select.value);
|
|
230
|
+
storeSize(instance.id, instance.size);
|
|
231
|
+
// Back to the top: after "show me 200" the reader is asking to see the set, and
|
|
232
|
+
// landing on page 4 of a re-cut table is disorienting in a way clamping is not.
|
|
233
|
+
instance.page = 1;
|
|
234
|
+
render(instance);
|
|
235
|
+
});
|
|
236
|
+
|
|
237
|
+
Object.assign(instance, { bar, status, select, previous, next });
|
|
238
|
+
return bar;
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
function render(instance) {
|
|
242
|
+
const rows = dataRows(instance.table);
|
|
243
|
+
const total = rows.length;
|
|
244
|
+
const window_ = pageWindow(total, instance.size, instance.page);
|
|
245
|
+
instance.page = window_.page;
|
|
246
|
+
applyPageWindow(rows, window_.from, window_.to);
|
|
247
|
+
|
|
248
|
+
/*
|
|
249
|
+
* WHEN THE BAR IS THERE AT ALL. A control that can never do anything is noise, and
|
|
250
|
+
* most of the estate's tables are short reference tables — four machines, three
|
|
251
|
+
* agents. So: only when there is more than one page of rows to look at.
|
|
252
|
+
*
|
|
253
|
+
* The second clause is what stops that rule from being a trap. Pick 100 on a 30-row
|
|
254
|
+
* table and the first clause alone would remove the very control that was just used,
|
|
255
|
+
* with no way back to 20. While a non-default size is in force the bar stays.
|
|
256
|
+
*/
|
|
257
|
+
const useful = total > instance.size || instance.size !== DEFAULT_PAGE_SIZE;
|
|
258
|
+
instance.bar.hidden = !useful;
|
|
259
|
+
if (!useful) return;
|
|
260
|
+
|
|
261
|
+
instance.status.textContent = total ? `${window_.from + 1}–${window_.to} of ${total}` : "no rows";
|
|
262
|
+
instance.previous.disabled = window_.page <= 1;
|
|
263
|
+
instance.next.disabled = window_.page >= window_.pageCount;
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
function enhance(table) {
|
|
267
|
+
if (enhanced.has(table)) return;
|
|
268
|
+
|
|
269
|
+
const id = table.dataset.tableId;
|
|
270
|
+
if (!id) {
|
|
271
|
+
// Only worth saying when the omission cost something — see IDENTITY above.
|
|
272
|
+
if (dataRows(table).length > DEFAULT_PAGE_SIZE) {
|
|
273
|
+
console.warn(
|
|
274
|
+
"[design] this table is long enough to paginate but has no data-table-id, so it is showing every row. " +
|
|
275
|
+
"Add a stable data-table-id to page it and remember the reader's rows-per-page.",
|
|
276
|
+
table,
|
|
277
|
+
);
|
|
278
|
+
}
|
|
279
|
+
return;
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
const instance = { table, id, size: storedSize(id), page: 1 };
|
|
283
|
+
enhanced.set(table, instance);
|
|
284
|
+
buildBar(instance);
|
|
285
|
+
|
|
286
|
+
// After the scroll wrapper, never inside it: `.tablewrap` scrolls horizontally, and a
|
|
287
|
+
// pager parked in there slides out of reach on exactly the wide tables that need it.
|
|
288
|
+
// `closest` because initTableScroll may not have run yet — and if it runs later it
|
|
289
|
+
// wraps the <table> alone, so the bar stays put either way.
|
|
290
|
+
const anchor = table.closest(".tablewrap") || table;
|
|
291
|
+
anchor.after(instance.bar);
|
|
292
|
+
|
|
293
|
+
/*
|
|
294
|
+
* Rows change under us constantly: a filter keystroke, a 30s poll, a sort. childList
|
|
295
|
+
* catches a re-render; the `hidden` attributeFilter catches an in-place patcher that
|
|
296
|
+
* has stripped our own attribute off a row it kept (cockpit's dom-patch.js removes
|
|
297
|
+
* any attribute the incoming markup lacks — correct in general, and this is the
|
|
298
|
+
* exception, so it also exempts `hidden` on a <tr>). applyPageWindow() writes only
|
|
299
|
+
* real changes, so this observer cannot be woken by its own output.
|
|
300
|
+
*
|
|
301
|
+
* WATCHING THE TABLE, NOT THE TBODY, and the difference is not caution. A renderer is
|
|
302
|
+
* entitled to REPLACE the tbody rather than fill it — `tbody.outerHTML = …` is how
|
|
303
|
+
* cockpit's docs page still repaints, and it swaps in a new node. An observer bound to
|
|
304
|
+
* the old tbody would be left watching a detached element: the first paint would be
|
|
305
|
+
* paged and every one after it silently unpaged. The table element survives that, and
|
|
306
|
+
* `dataRows()` re-reads `tBodies[0]` on every render, so a swapped body is simply the
|
|
307
|
+
* body now.
|
|
308
|
+
*/
|
|
309
|
+
instance.observer = new MutationObserver(() => render(instance));
|
|
310
|
+
instance.observer.observe(table, { childList: true, attributes: true, attributeFilter: ["hidden"], subtree: true });
|
|
311
|
+
render(instance);
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
/**
|
|
315
|
+
* Page every `<table data-table-id>` to 20 rows, and keep doing so for tables rendered
|
|
316
|
+
* later.
|
|
317
|
+
*
|
|
318
|
+
* Filtering and sorting are none of this function's business and stay with whatever
|
|
319
|
+
* already owns them — it slices the rows it finds, which are by definition the whole
|
|
320
|
+
* set after that work has happened.
|
|
321
|
+
*
|
|
322
|
+
* @param {ParentNode} [root=document] Where to look for the initial pass.
|
|
323
|
+
*/
|
|
324
|
+
export function initTablePagination(root = document) {
|
|
325
|
+
for (const table of root.querySelectorAll("table")) enhance(table);
|
|
326
|
+
|
|
327
|
+
if (documentObserver) return;
|
|
328
|
+
// Same reasoning as initSelects(): cockpit rebuilds whole panels out of innerHTML on
|
|
329
|
+
// a poll, so a pager that only enhanced what existed at load would work until the
|
|
330
|
+
// first refresh and then quietly stop.
|
|
331
|
+
documentObserver = new MutationObserver((records) => {
|
|
332
|
+
for (const record of records) {
|
|
333
|
+
for (const node of record.addedNodes) {
|
|
334
|
+
if (node.nodeType !== 1) continue;
|
|
335
|
+
if (node.tagName === "TABLE") enhance(node);
|
|
336
|
+
else for (const table of node.querySelectorAll("table")) enhance(table);
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
});
|
|
340
|
+
documentObserver.observe(document.documentElement, { childList: true, subtree: true });
|
|
341
|
+
}
|
package/src/base.css
CHANGED
|
@@ -87,6 +87,20 @@ tbody tr:last-child > * {
|
|
|
87
87
|
tr > *:first-child { padding-inline-start: 0; }
|
|
88
88
|
tr > *:last-child { padding-inline-end: 0; }
|
|
89
89
|
|
|
90
|
+
/* A HIDDEN ROW MUST ACTUALLY BE HIDDEN, and `!important` is what makes that true.
|
|
91
|
+
The UA's own `[hidden] { display: none }` is a one-attribute selector, so ANY rule that
|
|
92
|
+
sets `display` on a row outruns it — and the estate has those: a narrow-screen media
|
|
93
|
+
query that restacks a key/value table as `.ask table.kv tr { display: block }` scores
|
|
94
|
+
higher and would put every hidden row back on screen, on phones only. Worse than a
|
|
95
|
+
cosmetic slip, because `hidden` that loses to a stylesheet is still exposed to a screen
|
|
96
|
+
reader: the table would announce rows nobody can see it announcing.
|
|
97
|
+
|
|
98
|
+
This is the mechanism `initTablePagination()` turns a page with, and it is declared
|
|
99
|
+
here rather than beside the pager because it is a correction to the ELEMENT: a row a
|
|
100
|
+
consumer hides for its own reasons has exactly the same problem. Same `display: none
|
|
101
|
+
!important` the print stylesheet and the diagram overlay already use. */
|
|
102
|
+
tr[hidden] { display: none !important; }
|
|
103
|
+
|
|
90
104
|
/* Tailwind v4's reset drops the default button cursor — restore it for all
|
|
91
105
|
interactive controls (buttons, summaries, role=button). */
|
|
92
106
|
button:not(:disabled),
|
package/src/components.css
CHANGED
|
@@ -133,6 +133,28 @@
|
|
|
133
133
|
transform: translateY(-2px);
|
|
134
134
|
}
|
|
135
135
|
|
|
136
|
+
/* THE BUTTON HAD NO DISABLED STATE — the select next to it did, and they disagreed.
|
|
137
|
+
`select:disabled` has been `opacity: 0.45; cursor: default` since 0.21.0, so a control row
|
|
138
|
+
holding a greyed dropdown beside a fully-lit button that does nothing when pressed was saying
|
|
139
|
+
two different things about the same condition. A pager's `prev` on page one is the case that
|
|
140
|
+
made it unavoidable: it is disabled far more often than not.
|
|
141
|
+
|
|
142
|
+
BELOW the hover rules on purpose. `:disabled` and `--ghost:hover` are both (0,2,0), so equal
|
|
143
|
+
specificity is settled by source order — declared above them, a disabled button would light up
|
|
144
|
+
under the pointer and reinstate exactly the lie this removes. Same trap that made every compact
|
|
145
|
+
button render at CTA size in 0.13.0.
|
|
146
|
+
|
|
147
|
+
Opacity and nothing else about the fill: a disabled PRIMARY button must stay filled and just go
|
|
148
|
+
quiet. Setting `background: transparent` here would strip it to `--primary-foreground` text on
|
|
149
|
+
the page background — near-invisible on all four themes — so the one property that reads as
|
|
150
|
+
"off" on both variants is the only one touched. */
|
|
151
|
+
.btn-terminal:disabled {
|
|
152
|
+
opacity: 0.45;
|
|
153
|
+
cursor: default;
|
|
154
|
+
box-shadow: none;
|
|
155
|
+
transform: none;
|
|
156
|
+
}
|
|
157
|
+
|
|
136
158
|
/* A ROW-SIZED BUTTON. The system's button is a landing-page CTA at 12px/24px; a table row that
|
|
137
159
|
offers `remove` needs the same button at the size of its text. This is the size ONLY — colour,
|
|
138
160
|
corners and the `> ` prefix still come from .btn-terminal / --ghost, so a compact button cannot
|
|
@@ -191,6 +213,49 @@
|
|
|
191
213
|
.btn-terminal--destructive { min-width: 44px; min-height: 44px; }
|
|
192
214
|
}
|
|
193
215
|
|
|
216
|
+
/* THE EDIT ROW ACTION — the same icon button as the bin, in the ordinary colour.
|
|
217
|
+
|
|
218
|
+
`edit →` was a word and an arrow in a table cell wide enough to hold them, next to a bin that
|
|
219
|
+
is 22px square. Two controls doing the same job in one column, one of them four times the
|
|
220
|
+
width of the other, and at 375px the label broke mid-word into "edi / t →" — which is why
|
|
221
|
+
cockpit carried a `white-space: nowrap` rule to prop it up. An icon says it in the space the
|
|
222
|
+
bin already proved is enough.
|
|
223
|
+
|
|
224
|
+
IT IS NOT DESTRUCTIVE AND MUST NOT BORROW THAT COLOUR. Editing is an ordinary action; red is
|
|
225
|
+
reserved for the one press that cannot be taken back. So this class carries NO colour at all —
|
|
226
|
+
compose it with `--ghost` and it is `--primary` on a `--border` outline like every other
|
|
227
|
+
secondary control, and with nothing and it is the filled primary button. That is also why it is
|
|
228
|
+
not `--destructive` with the fill swapped: colour is the bin's whole warning, and the delta
|
|
229
|
+
here is only the glyph and the box that holds it.
|
|
230
|
+
|
|
231
|
+
THE GLYPH IS A MASK, for the reason the bin's comment gives: `currentColor` re-takes whatever
|
|
232
|
+
colour the composition landed on, on all four themes, with nothing declared per-theme. A pasted
|
|
233
|
+
SVG would be one `fill=` that is wrong on two of them, once per surface that adopts it.
|
|
234
|
+
|
|
235
|
+
AN ACCESSIBLE NAME IS MANDATORY — see the bin. Dropping the word "edit" leaves a button whose
|
|
236
|
+
entire content is a mask, and a mask has no text: a screen reader announces "button", and a
|
|
237
|
+
column of them announces "button" a dozen times. `aria-label` must name the TARGET
|
|
238
|
+
("edit poi/vu3"), not repeat the verb, or the column is a dozen identical announcements
|
|
239
|
+
instead. `bin/design-conformance` fails an icon button with no accessible name. */
|
|
240
|
+
.btn-terminal--edit {
|
|
241
|
+
display: inline-flex; align-items: center; justify-content: center;
|
|
242
|
+
padding: 0.3rem;
|
|
243
|
+
}
|
|
244
|
+
/* Kills the `> ` prefix, which would otherwise be the entire text content of an icon button.
|
|
245
|
+
--ghost already blanks it; repeated here so the class is correct on a filled button too. */
|
|
246
|
+
.btn-terminal--edit::before { content: ""; }
|
|
247
|
+
.btn-terminal--edit::after {
|
|
248
|
+
content: ""; display: block; width: 0.875rem; height: 0.875rem;
|
|
249
|
+
background: currentColor;
|
|
250
|
+
-webkit-mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath d='M3 17.25V21h3.75L17.81 9.94l-3.75-3.75L3 17.25zM20.71 7.04l-3.75-3.75-1.83 1.83 3.75 3.75 1.83-1.83z'/%3E%3C/svg%3E") center / contain no-repeat;
|
|
251
|
+
mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath d='M3 17.25V21h3.75L17.81 9.94l-3.75-3.75L3 17.25zM20.71 7.04l-3.75-3.75-1.83 1.83 3.75 3.75 1.83-1.83z'/%3E%3C/svg%3E") center / contain no-repeat;
|
|
252
|
+
}
|
|
253
|
+
/* 44px under a thumb, same as the bin: `min-*` grows the tap target without moving the glyph,
|
|
254
|
+
which the flexbox keeps centred. Never set a width on it. */
|
|
255
|
+
@media (pointer: coarse) {
|
|
256
|
+
.btn-terminal--edit { min-width: 44px; min-height: 44px; }
|
|
257
|
+
}
|
|
258
|
+
|
|
194
259
|
/* Quiet, underlined link that warms to the primary colour on hover. */
|
|
195
260
|
.link-quiet {
|
|
196
261
|
color: var(--muted-foreground);
|
|
@@ -563,6 +628,51 @@ select:disabled,
|
|
|
563
628
|
letter-spacing: 0.06em;
|
|
564
629
|
}
|
|
565
630
|
|
|
631
|
+
/* ─────────────────────────────────────────────────────────────────────────
|
|
632
|
+
TABLE PAGER — twenty rows at a time, and the reader can say otherwise.
|
|
633
|
+
|
|
634
|
+
Built and placed by runtime/pagination.js; a page writes no part of this. It
|
|
635
|
+
INVENTS NOTHING: the two buttons are `.btn-terminal--ghost.btn-terminal--compact`
|
|
636
|
+
(the same pair every refresh and cancel in the estate wears) and the size picker
|
|
637
|
+
is a bare `<select>` that `initSelects()` enhances. So there is no new colour
|
|
638
|
+
here, no new control, and nothing to keep in step at the next release — only a
|
|
639
|
+
row that decides where those three things sit.
|
|
640
|
+
|
|
641
|
+
Ghost, not filled: a pager is not the primary action of any view it appears in,
|
|
642
|
+
and two filled buttons side by side compete. The status text is
|
|
643
|
+
`--muted-foreground` because it is a measurement of the table, not part of it —
|
|
644
|
+
4.84:1 at worst (warm on --card), so it clears AA while staying quiet.
|
|
645
|
+
|
|
646
|
+
The status is left and the controls are right (`margin-inline-start: auto` on the
|
|
647
|
+
picker pushes the pair over), which puts "where am I" at the start of the line and
|
|
648
|
+
"take me elsewhere" at the end. It wraps rather than scrolls: on a phone the three
|
|
649
|
+
parts stack, and a control that has gone off the side of a table is worse than a
|
|
650
|
+
control on a second line.
|
|
651
|
+
───────────────────────────────────────────────────────────────────────── */
|
|
652
|
+
.table-pager {
|
|
653
|
+
display: flex;
|
|
654
|
+
flex-wrap: wrap;
|
|
655
|
+
align-items: center;
|
|
656
|
+
gap: 0.45rem 0.9rem;
|
|
657
|
+
margin-block-start: 0.6rem;
|
|
658
|
+
font-size: var(--fs-sm);
|
|
659
|
+
}
|
|
660
|
+
.table-pager-status {
|
|
661
|
+
margin: 0;
|
|
662
|
+
color: var(--muted-foreground);
|
|
663
|
+
}
|
|
664
|
+
.table-pager-size {
|
|
665
|
+
display: inline-flex;
|
|
666
|
+
align-items: center;
|
|
667
|
+
gap: 0.4rem;
|
|
668
|
+
margin-inline-start: auto;
|
|
669
|
+
color: var(--muted-foreground);
|
|
670
|
+
}
|
|
671
|
+
.table-pager-nav {
|
|
672
|
+
display: inline-flex;
|
|
673
|
+
gap: 0.4rem;
|
|
674
|
+
}
|
|
675
|
+
|
|
566
676
|
/* ─────────────────────────────────────────────────────────────────────────
|
|
567
677
|
ELI5 — an "explain like I'm 5" callout box (first-time-term explainers).
|
|
568
678
|
───────────────────────────────────────────────────────────────────────── */
|
|
@@ -98,13 +98,13 @@
|
|
|
98
98
|
on docs.danieldeusing.de, NOT for a doc opened from disk over file://. -->
|
|
99
99
|
<link
|
|
100
100
|
rel="stylesheet"
|
|
101
|
-
href="https://cdn.jsdelivr.net/npm/@danieldeusing/design@0.
|
|
102
|
-
onerror="this.onerror=null;this.href='/_design/danieldeusing-design-0.
|
|
101
|
+
href="https://cdn.jsdelivr.net/npm/@danieldeusing/design@0.22.0/dist/danieldeusing-design.min.css"
|
|
102
|
+
onerror="this.onerror=null;this.href='/_design/danieldeusing-design-0.22.0.min.css'"
|
|
103
103
|
/>
|
|
104
104
|
<link
|
|
105
105
|
rel="stylesheet"
|
|
106
|
-
href="https://cdn.jsdelivr.net/npm/@danieldeusing/design@0.
|
|
107
|
-
onerror="this.onerror=null;this.href='/_design/danieldeusing-design-0.
|
|
106
|
+
href="https://cdn.jsdelivr.net/npm/@danieldeusing/design@0.22.0/src/fonts.css"
|
|
107
|
+
onerror="this.onerror=null;this.href='/_design/danieldeusing-design-0.22.0.fonts.css'"
|
|
108
108
|
/>
|
|
109
109
|
|
|
110
110
|
<style>
|
|
@@ -341,7 +341,7 @@ flowchart TD
|
|
|
341
341
|
|
|
342
342
|
<script type="module">
|
|
343
343
|
import { applyStoredTheme, initThemeSwitcher, initDropdowns, initSelects, initTerminal, initAnimToggle, initBurgerNav, initLsNav, initTableScroll, initDiagramZoom, initMinimap, initTooltips } from
|
|
344
|
-
"https://cdn.jsdelivr.net/npm/@danieldeusing/design@0.
|
|
344
|
+
"https://cdn.jsdelivr.net/npm/@danieldeusing/design@0.22.0/runtime/index.js";
|
|
345
345
|
applyStoredTheme();
|
|
346
346
|
initThemeSwitcher();
|
|
347
347
|
initDropdowns();
|