@danieldeusing/design 0.1.6 → 0.3.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/dist/danieldeusing-design.css +274 -11
- package/dist/danieldeusing-design.min.css +2 -2
- package/package.json +2 -2
- package/runtime/index.js +1 -0
- package/runtime/lsnav.js +56 -0
- package/src/chrome.css +114 -10
- package/src/components.css +123 -0
- package/src/print.css +8 -0
- package/src/tokens.css +28 -0
- package/tokens/tokens.json +8 -0
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
/*! danieldeusing-design v0.
|
|
1
|
+
/*! danieldeusing-design v0.3.0 | MIT | https://github.com/danieldeusing/danieldeusing-design */
|
|
2
2
|
/* ===== reset.css ===== */
|
|
3
3
|
/*
|
|
4
4
|
* danieldeusing-design — minimal reset.
|
|
@@ -87,6 +87,26 @@ table {
|
|
|
87
87
|
* These are plain CSS custom properties — they work in any framework, any
|
|
88
88
|
* browser, with or without a build step. Load this file first; base.css and
|
|
89
89
|
* components.css resolve against the variables declared here.
|
|
90
|
+
*
|
|
91
|
+
* ── WHY EVERY ACCENT IS PER-THEME, AND WHY A LITERAL IS NEVER OK ──
|
|
92
|
+
* A status colour has to clear WCAG AA 4.5:1 against every surface it can land
|
|
93
|
+
* on — --background, --card AND --muted. Do that arithmetic across the four
|
|
94
|
+
* themes and the two admissible luminance bands DO NOT OVERLAP:
|
|
95
|
+
*
|
|
96
|
+
* light themes: darkest surface is warm's --muted #ece3cf -> accent L <= 0.1328
|
|
97
|
+
* dark themes: lightest surface is green's --muted #071509 -> accent L >= 0.2021
|
|
98
|
+
* (mono's #111111 wants accent L >= 0.2002)
|
|
99
|
+
*
|
|
100
|
+
* A gap of 0.0692 with nothing in it. So NO single hex can serve four themes:
|
|
101
|
+
* the two near-black themes need a light tint, the two light themes a dark one,
|
|
102
|
+
* and every compromise fails both. That is arithmetic, not taste — which is why
|
|
103
|
+
* --destructive, --success and --warning are each declared four times.
|
|
104
|
+
*
|
|
105
|
+
* Cockpit carried twelve hardcoded accents that predate these tokens; every one
|
|
106
|
+
* missed AA on at least one theme and four sat inside the gap, clearing neither
|
|
107
|
+
* side. Adding a status colour? Add a TOKEN here with its measured worst-case
|
|
108
|
+
* ratio in a trailing comment. Do not tune a literal at the call site — that
|
|
109
|
+
* forks the palette and fixes exactly one theme.
|
|
90
110
|
*/
|
|
91
111
|
|
|
92
112
|
:root {
|
|
@@ -105,6 +125,8 @@ table {
|
|
|
105
125
|
--accent: #8a4516;
|
|
106
126
|
--accent-foreground: #f8f3e8;
|
|
107
127
|
--destructive: #a02c2c;
|
|
128
|
+
--success: #1a6b2e; /* 5.16:1 worst surface */
|
|
129
|
+
--warning: #845600; /* 4.97:1 worst surface */
|
|
108
130
|
--border: #d9cdb6;
|
|
109
131
|
--input: #d9cdb6;
|
|
110
132
|
--ring: #8a4516;
|
|
@@ -135,6 +157,8 @@ html[data-theme="green"] {
|
|
|
135
157
|
--accent: #33ff66;
|
|
136
158
|
--accent-foreground: #02160a;
|
|
137
159
|
--destructive: #ff5c5c;
|
|
160
|
+
--success: #4ade80; /* 10.76:1 worst surface */
|
|
161
|
+
--warning: #f5b32b; /* 10.15:1 worst surface */
|
|
138
162
|
--border: #1c4a2c;
|
|
139
163
|
--input: #1c4a2c;
|
|
140
164
|
--ring: #33ff66;
|
|
@@ -160,6 +184,8 @@ html[data-theme="mono"] {
|
|
|
160
184
|
--accent: #ffffff;
|
|
161
185
|
--accent-foreground: #050505;
|
|
162
186
|
--destructive: #ff5c5c;
|
|
187
|
+
--success: #4ade80; /* 10.84:1 worst surface */
|
|
188
|
+
--warning: #f5b32b; /* 10.22:1 worst surface */
|
|
163
189
|
--border: #333333;
|
|
164
190
|
--input: #333333;
|
|
165
191
|
--ring: #ffffff;
|
|
@@ -185,6 +211,8 @@ html[data-theme="paper"] {
|
|
|
185
211
|
--accent: #000000;
|
|
186
212
|
--accent-foreground: #fafafa;
|
|
187
213
|
--destructive: #b3261e;
|
|
214
|
+
--success: #1a7031; /* 5.36:1 worst surface */
|
|
215
|
+
--warning: #8f5d00; /* 4.89:1 worst surface */
|
|
188
216
|
--border: #d4d4d4;
|
|
189
217
|
--input: #d4d4d4;
|
|
190
218
|
--ring: #000000;
|
|
@@ -619,12 +647,135 @@ html.anim-off .cursor-block {
|
|
|
619
647
|
}
|
|
620
648
|
}
|
|
621
649
|
|
|
650
|
+
/* ═══════════════════════════════════════════════════════════════════════════════════════════════
|
|
651
|
+
Shared page vocabulary, promoted from the cockpit portal (2026-08-04).
|
|
652
|
+
|
|
653
|
+
Each of these was built once in cockpit/pages/portal.css and then wanted by every other
|
|
654
|
+
surface. They live here so there is ONE definition — the tabs had already drifted into FOUR
|
|
655
|
+
page style blocks before being consolidated, which is exactly the failure this move prevents.
|
|
656
|
+
═════════════════════════════════════════════════════════════════════════════════════════════ */
|
|
657
|
+
|
|
658
|
+
/* ── tabs ──────────────────────────────────────────────────────────────────────────────────────
|
|
659
|
+
Daniel: "I am always searching the tabs. They are not falling into my eyes directly."
|
|
660
|
+
|
|
661
|
+
They were low-contrast pills at 0.7 opacity, and the SELECTED one differed from the rest only
|
|
662
|
+
by a slightly darker border and 8% background — so the row read as six equal chips and the eye
|
|
663
|
+
had to stop and compare them. This inverts it: the inactive tabs recede to plain muted text
|
|
664
|
+
with no chrome at all, and the active one is the only thing on the row carrying weight, colour
|
|
665
|
+
and a fill. Nothing to compare — one thing is loud. */
|
|
666
|
+
.tabs {
|
|
667
|
+
display: flex; flex-wrap: wrap; gap: 0.15rem; margin: 1.35rem 0 0;
|
|
668
|
+
border-bottom: 2px solid color-mix(in srgb, var(--border) 80%, transparent);
|
|
669
|
+
}
|
|
670
|
+
.tab {
|
|
671
|
+
background: transparent; border: 0; margin-bottom: -2px;
|
|
672
|
+
font: inherit; font-size: 0.86em; cursor: pointer; padding: 0.5rem 0.95rem;
|
|
673
|
+
color: var(--muted-foreground); transition: background 0.12s ease, color 0.12s ease;
|
|
674
|
+
}
|
|
675
|
+
.tab:hover { color: var(--primary); background: color-mix(in srgb, var(--primary) 10%, transparent); }
|
|
676
|
+
/* FILLED, not underlined. An underline plus a colour shift was still one thin rule and a hue
|
|
677
|
+
change on a row of same-weight words — Daniel was still hunting for the row. A solid block of
|
|
678
|
+
--primary with the page background as its text is the highest-contrast pair the theme owns
|
|
679
|
+
(5.6:1 on warm), and it is the only filled thing on the page, so it is found without reading. */
|
|
680
|
+
.tab[aria-selected="true"] { background: var(--primary); color: var(--background); font-weight: 700; }
|
|
681
|
+
.tab:focus-visible { outline: 2px solid color-mix(in srgb, var(--primary) 55%, transparent); outline-offset: -2px; }
|
|
682
|
+
/* 44px on a touch screen — tabs are the primary navigation of these pages, so they are the last
|
|
683
|
+
control that should be hard to hit. */
|
|
684
|
+
@media (pointer: coarse), (max-width: 40rem) {
|
|
685
|
+
.tab { padding: 0.7rem 0.8rem; min-height: 44px; }
|
|
686
|
+
}
|
|
687
|
+
section.doc.tab-panel { margin-top: 1.35rem; }
|
|
688
|
+
|
|
689
|
+
/* ── fold: progressive disclosure whose affordance is always visible ───────────────────────────
|
|
690
|
+
The detail behind a visual has to open by TAP — these pages are read from a phone, where hover
|
|
691
|
+
does not exist — and the control has to be findable WITHOUT hovering, or the detail is simply
|
|
692
|
+
text that was deleted. Hence a permanent ▸ marker and a full-width summary row rather than
|
|
693
|
+
anything that appears on hover.
|
|
694
|
+
|
|
695
|
+
Pair with a `beforeprint` handler that opens every <details>, or a printout silently loses
|
|
696
|
+
whatever the closed ones were holding. */
|
|
697
|
+
details.fold { border-top: 1px solid color-mix(in srgb, currentColor 15%, transparent); }
|
|
698
|
+
details.fold > summary {
|
|
699
|
+
display: flex; align-items: center; gap: 0.5rem; cursor: pointer; list-style: none;
|
|
700
|
+
padding: 0.55rem 0; font-size: 0.85rem; color: var(--muted-foreground);
|
|
701
|
+
}
|
|
702
|
+
details.fold > summary::-webkit-details-marker { display: none; }
|
|
703
|
+
details.fold > summary::before { content: "\25B8"; flex: none; opacity: 0.6; }
|
|
704
|
+
details.fold[open] > summary::before { content: "\25BE"; }
|
|
705
|
+
details.fold > summary:hover { color: var(--primary); }
|
|
706
|
+
details.fold > summary:focus-visible {
|
|
707
|
+
outline: 2px solid color-mix(in srgb, currentColor 45%, transparent); outline-offset: 2px;
|
|
708
|
+
}
|
|
709
|
+
.fold-body { font-size: 0.9rem; padding: 0 0 0.7rem 1.1rem; }
|
|
710
|
+
.fold-body > * + * { margin-top: 0.6rem; }
|
|
711
|
+
|
|
712
|
+
/* ── legend: one rendering of each state, beside the thing it keys ─────────────────────────────
|
|
713
|
+
An item is `display: block`, NOT flex: a gloss contains <strong>/<code>/<em>, and every one of
|
|
714
|
+
those would become its own flex item — which is how a single sentence first rendered as four
|
|
715
|
+
separately-wrapped columns. */
|
|
716
|
+
.legend {
|
|
717
|
+
list-style: none; padding: 0; margin: 0.7rem 0 0;
|
|
718
|
+
display: grid; gap: 0.4rem 1.4rem; font-size: 0.86rem;
|
|
719
|
+
}
|
|
720
|
+
@media (min-width: 52rem) { .legend { grid-template-columns: repeat(2, minmax(0, 1fr)); } }
|
|
721
|
+
.legend > li { line-height: 1.6; }
|
|
722
|
+
.legend > li > :first-child { margin-right: 0.45rem; }
|
|
723
|
+
|
|
724
|
+
/* ── ticker strip: one standard header for every poller ────────────────────────────────────────
|
|
725
|
+
Daniel: "For all pollers / tickers we should make a standard layout ... on top of the pages,
|
|
726
|
+
compact and like dashboard style."
|
|
727
|
+
|
|
728
|
+
Every poller answers the same four questions — what ran, when, when next, what did it count —
|
|
729
|
+
and each page was answering them in its own prose box, so "is anything stalled?" took four
|
|
730
|
+
paragraphs instead of one glance.
|
|
731
|
+
|
|
732
|
+
A real TABLE, not a grid per row. The first version made each row its own `display: grid`,
|
|
733
|
+
which cannot align anything across rows: every row sized its columns to its own content, so
|
|
734
|
+
`next` sat wherever that row's name happened to end and the layout moved when a label changed.
|
|
735
|
+
One table means one set of column widths for the whole strip, and scanning a column is the only
|
|
736
|
+
thing the strip is for. Borderless by design — rules between cells draw a grid nobody reads.
|
|
737
|
+
|
|
738
|
+
Colour comes only from --primary and --destructive, both per-theme. Hardcoded accents miss AA
|
|
739
|
+
on at least one theme (measured 2026-08-04), so a strip inventing its own greens and ambers
|
|
740
|
+
would have added five more. */
|
|
741
|
+
.tickstrip:not(:empty) { border: 1px solid var(--border); background: var(--card); margin: 0 0 1.25rem; }
|
|
742
|
+
.ticktable { width: 100%; border-collapse: collapse; font-size: 0.82rem; }
|
|
743
|
+
.ticktable td { padding: 0.45rem 0.55rem; vertical-align: baseline; border: 0; white-space: nowrap; }
|
|
744
|
+
/* Only the last column may grow, so the four fixed columns keep the same width on every row. */
|
|
745
|
+
.ticktable td.tick-stats { width: 100%; white-space: normal; }
|
|
746
|
+
.tick { border-bottom: 1px solid color-mix(in srgb, var(--border) 55%, transparent); }
|
|
747
|
+
.tick:last-child { border-bottom: 0; }
|
|
748
|
+
/* The glyph carries the state WITHOUT colour as well as with it — two of the four themes are
|
|
749
|
+
near-black and a printout has none, so ● vs ✕ vs ○ has to survive on its own. */
|
|
750
|
+
.tick-dot { font-size: 0.7em; width: 1rem; padding-right: 0 !important; }
|
|
751
|
+
.tick--ok .tick-dot { color: var(--primary); }
|
|
752
|
+
.tick--stale .tick-dot { color: var(--destructive); }
|
|
753
|
+
.tick--never .tick-dot { color: var(--muted-foreground); }
|
|
754
|
+
.tick-name { font-weight: 700; color: var(--primary); }
|
|
755
|
+
.tick-last, .tick-next { font-variant-numeric: tabular-nums; color: var(--muted-foreground); }
|
|
756
|
+
.tick--stale .tick-last { color: var(--destructive); font-weight: 700; }
|
|
757
|
+
.tick--never .tick-last { font-style: italic; }
|
|
758
|
+
/* The NUMBER is what gets scanned; its label is context. Weight and colour on the value alone
|
|
759
|
+
means a row reads as its numbers first and its wording second. */
|
|
760
|
+
.tick-stats { color: var(--muted-foreground); }
|
|
761
|
+
.tick-stats b { font-weight: 700; color: var(--primary); font-variant-numeric: tabular-nums; }
|
|
762
|
+
.tick-sep { font-style: normal; opacity: 0.35; margin: 0 0.5rem; }
|
|
763
|
+
/* A stale row earns the only background tint in the strip — an all-tinted strip is one the eye
|
|
764
|
+
stops reading. */
|
|
765
|
+
.tick--stale { background: color-mix(in srgb, var(--destructive) 8%, transparent); }
|
|
766
|
+
@media (max-width: 40rem) {
|
|
767
|
+
.ticktable, .ticktable tbody, .tick, .ticktable td { display: block; }
|
|
768
|
+
.tick { padding: 0.4rem 0.5rem; }
|
|
769
|
+
.ticktable td { display: inline-block; padding: 0.1rem 0.5rem 0.1rem 0; }
|
|
770
|
+
.ticktable td.tick-stats { display: block; width: auto; }
|
|
771
|
+
}
|
|
772
|
+
|
|
622
773
|
/* ===== chrome.css ===== */
|
|
623
774
|
/*
|
|
624
775
|
* chrome.css — the app chrome kit: sticky header bar, fixed status footer,
|
|
625
|
-
* and the responsive site navigation (desktop `ls -l`
|
|
626
|
-
*
|
|
627
|
-
*
|
|
776
|
+
* and the responsive site navigation (desktop `ls -l` RAIL, mobile burger with
|
|
777
|
+
* the footer folded in). Extracted from danieldeusing.de and the cockpit portal
|
|
778
|
+
* so every app ships the same chrome without rebuilding it.
|
|
628
779
|
*
|
|
629
780
|
* Markup contract (see templates/documentation.html for the full example):
|
|
630
781
|
*
|
|
@@ -633,7 +784,14 @@ html.anim-off .cursor-block {
|
|
|
633
784
|
* <a href="/" class="brand"><span class="glow">name</span><span class="cursor-block"></span></a>
|
|
634
785
|
* <button class="nav-burger" data-nav-toggle aria-controls="site-nav" aria-expanded="false">…☰ svg…</button>
|
|
635
786
|
* <nav aria-label="site" id="site-nav" class="site-nav">
|
|
636
|
-
* <
|
|
787
|
+
* <div class="ls-nav" id="nav">
|
|
788
|
+
* <div class="ls-nav-head">
|
|
789
|
+
* <span class="ls-nav-title">ls -l</span>
|
|
790
|
+
* <button class="ls-nav-hide" data-ls-nav-toggle aria-controls="nav" aria-expanded="true">»</button>
|
|
791
|
+
* </div>
|
|
792
|
+
* <ul class="ls-panel">….ls-row items…</ul>
|
|
793
|
+
* </div>
|
|
794
|
+
* <button class="ls-nav-show" data-ls-nav-toggle aria-controls="nav">« ls -l</button>
|
|
637
795
|
* <ul class="mobile-nav">…same links as .mobile-item ls-rows…</ul>
|
|
638
796
|
* <div class="mobile-footer">…footer controls folded in…</div>
|
|
639
797
|
* </nav>
|
|
@@ -641,8 +799,15 @@ html.anim-off .cursor-block {
|
|
|
641
799
|
* …
|
|
642
800
|
* <footer class="status">…status-left + status-right…</footer>
|
|
643
801
|
*
|
|
644
|
-
* Behaviour: pair with `initBurgerNav()`
|
|
645
|
-
*
|
|
802
|
+
* Behaviour: pair with `initBurgerNav()` (mobile burger) and `initLsNav()`
|
|
803
|
+
* (rail show/hide) from the runtime. The rail's SHOWN/HIDDEN state must also be
|
|
804
|
+
* applied before first paint by an inline <head> script — see the rail section
|
|
805
|
+
* below for why the runtime cannot be the one to do it:
|
|
806
|
+
*
|
|
807
|
+
* try { if (localStorage.getItem("ls-nav") === "off")
|
|
808
|
+
* document.documentElement.dataset.lsNav = "off"; } catch {}
|
|
809
|
+
*
|
|
810
|
+
* Breakpoint: 48rem.
|
|
646
811
|
*/
|
|
647
812
|
|
|
648
813
|
/* ── a11y helpers ── */
|
|
@@ -682,12 +847,100 @@ footer.status .dropdown summary, footer.status .anim-toggle { padding: 0.55rem 0
|
|
|
682
847
|
.doc-link { color: var(--muted-foreground); text-decoration: none; transition: color 0.15s ease; }
|
|
683
848
|
.doc-link:hover { color: var(--primary); }
|
|
684
849
|
|
|
685
|
-
/* ── ls-listing rows (shared by the desktop
|
|
850
|
+
/* ── ls-listing rows (shared by the desktop rail + mobile menu) ── */
|
|
686
851
|
.ls-row { display: flex; align-items: baseline; gap: 0.9rem; }
|
|
687
852
|
.ls-perm { color: var(--muted-foreground); font-size: 0.72rem; letter-spacing: 0; }
|
|
688
853
|
.ls-row--sub .ls-name { padding-left: 1rem; }
|
|
689
|
-
|
|
690
|
-
|
|
854
|
+
/* The pre-rail dropdown form. Kept, and scoped to the dropdown panel, because the
|
|
855
|
+
other surfaces still ship that markup — when they unpin they get this file, and
|
|
856
|
+
their nav should stay right-aligned rather than quietly jumping to the left. The
|
|
857
|
+
rail's own <ul> carries `ls-panel` WITHOUT `dropdown-panel`, so it is unaffected. */
|
|
858
|
+
.dropdown-panel.ls-panel { min-width: 17rem; left: auto !important; right: 0; }
|
|
859
|
+
|
|
860
|
+
/* ── `ls -l` site rail (desktop ≥48rem): sticky, full-height, right-hand panel ──
|
|
861
|
+
Was a dropdown that had to be re-opened on every page. Daniel: "Always make it
|
|
862
|
+
sticky (default shown) right side, keep `ls -l`, but not down arrow, but
|
|
863
|
+
something which tells the user he can hide / minimize."
|
|
864
|
+
|
|
865
|
+
SHOWN BY DEFAULT, and the state lives in localStorage under "ls-nav". It is
|
|
866
|
+
applied by an inline <head> script as `html[data-ls-nav="off"]` BEFORE first
|
|
867
|
+
paint — not by the runtime — because the runtime is a module at the end of
|
|
868
|
+
<body>: a reader who hid the rail would otherwise watch it paint and then jump
|
|
869
|
+
away on every single page load. `initLsNav()` only handles the clicks.
|
|
870
|
+
|
|
871
|
+
The affordance is deliberately NOT a caret. A caret means "a menu drops from
|
|
872
|
+
here", which is the thing this stopped being. Guillemets point the way the
|
|
873
|
+
panel travels — » pushes it off to the right, « pulls it back — and hiding it
|
|
874
|
+
leaves a small labelled tab on the edge rather than nothing, so a hidden rail
|
|
875
|
+
is still discoverable by someone who did not hide it themselves.
|
|
876
|
+
|
|
877
|
+
Width is one token so a consumer can widen it for longer trees. */
|
|
878
|
+
:root { --ls-nav-w: 17rem; }
|
|
879
|
+
.ls-nav-show { display: none; }
|
|
880
|
+
|
|
881
|
+
@media (min-width: 48.0625rem) {
|
|
882
|
+
/* Room is reserved ONLY when a rail is actually in the page, and the default is
|
|
883
|
+
zero. This is what makes the stylesheet safe to publish ahead of a consumer's
|
|
884
|
+
markup: every surface that still ships the old dropdown nav (or no nav at all)
|
|
885
|
+
keeps its full width instead of growing a 17rem gutter with nothing in it —
|
|
886
|
+
which is exactly what they would have got the moment they unpinned.
|
|
887
|
+
It also fails in the right direction: if :has() is unavailable the selector is
|
|
888
|
+
dropped, the inset stays 0, and the rail overlays content rather than the page
|
|
889
|
+
reserving space for a rail that never arrives. */
|
|
890
|
+
html { --ls-nav-inset: 0rem; }
|
|
891
|
+
html:has(.ls-nav):not([data-ls-nav="off"]) { --ls-nav-inset: var(--ls-nav-w); }
|
|
892
|
+
|
|
893
|
+
/* Everything in normal flow shifts left, so content sits against the rail's
|
|
894
|
+
left edge instead of running under it. The fixed footer has to be told
|
|
895
|
+
separately: `inset-inline: 0` ignores the body's padding. */
|
|
896
|
+
/* Deliberately NOT transitioned. Animating padding on <body> reflows the whole
|
|
897
|
+
document every frame, and the pages this ships to render tables of hundreds of
|
|
898
|
+
rows — the rail's own transform is composited and costs nothing, so the panel
|
|
899
|
+
slides while the content reflows once. Animating both looked no better and
|
|
900
|
+
made the containers dashboard stutter on toggle. */
|
|
901
|
+
body { padding-right: var(--ls-nav-inset); }
|
|
902
|
+
footer.status { right: var(--ls-nav-inset); }
|
|
903
|
+
|
|
904
|
+
.ls-nav {
|
|
905
|
+
position: fixed; top: 0; right: 0; bottom: 0; width: var(--ls-nav-w); z-index: 45;
|
|
906
|
+
display: flex; flex-direction: column;
|
|
907
|
+
background: var(--card); border-left: 1px solid var(--border);
|
|
908
|
+
}
|
|
909
|
+
html:not(.anim-off) .ls-nav,
|
|
910
|
+
html:not(.anim-off) .ls-nav-show { transition: transform 0.18s ease; }
|
|
911
|
+
html[data-ls-nav="off"] .ls-nav { transform: translateX(100%); }
|
|
912
|
+
|
|
913
|
+
.ls-nav-head {
|
|
914
|
+
flex: none; display: flex; align-items: center; justify-content: space-between; gap: 0.5rem;
|
|
915
|
+
padding: 0.6rem 0.5rem 0.6rem 0.85rem; border-bottom: 1px solid var(--border);
|
|
916
|
+
}
|
|
917
|
+
.ls-nav-title { font-weight: 700; font-size: 0.9rem; }
|
|
918
|
+
|
|
919
|
+
/* The list is the only thing that scrolls — the head stays put, so the control
|
|
920
|
+
that hides the rail cannot be scrolled out of reach. */
|
|
921
|
+
.ls-nav .ls-panel {
|
|
922
|
+
flex: 1; min-height: 0; overflow-y: auto;
|
|
923
|
+
list-style: none; margin: 0; padding: 0.5rem 0 1rem;
|
|
924
|
+
}
|
|
925
|
+
|
|
926
|
+
.ls-nav-hide, .ls-nav-show {
|
|
927
|
+
background: none; border: 0; font: inherit; cursor: pointer; line-height: 1;
|
|
928
|
+
color: var(--muted-foreground); padding: 0.4rem 0.55rem;
|
|
929
|
+
}
|
|
930
|
+
.ls-nav-hide:hover, .ls-nav-show:hover { color: var(--primary); }
|
|
931
|
+
.ls-nav-hide:focus-visible, .ls-nav-show:focus-visible {
|
|
932
|
+
outline: 2px solid color-mix(in srgb, var(--primary) 55%, transparent); outline-offset: -2px;
|
|
933
|
+
}
|
|
934
|
+
|
|
935
|
+
/* The tab that survives hiding. Sits on the right edge, mid-height, and only
|
|
936
|
+
exists while the rail is away. */
|
|
937
|
+
html[data-ls-nav="off"] .ls-nav-show {
|
|
938
|
+
display: inline-flex; align-items: center; gap: 0.4rem;
|
|
939
|
+
position: fixed; top: 50%; right: 0; z-index: 45; transform: translateY(-50%);
|
|
940
|
+
background: var(--card); border: 1px solid var(--border); border-right: 0;
|
|
941
|
+
padding: 0.6rem 0.5rem; font-size: 0.8rem;
|
|
942
|
+
}
|
|
943
|
+
}
|
|
691
944
|
|
|
692
945
|
/* ── burger + mobile menu ── */
|
|
693
946
|
.nav-burger {
|
|
@@ -708,7 +961,9 @@ footer.status .dropdown summary, footer.status .anim-toggle { padding: 0.55rem 0
|
|
|
708
961
|
padding: 0.4rem 1.25rem 0.9rem; max-height: calc(100vh - 6rem); overflow-y: auto;
|
|
709
962
|
}
|
|
710
963
|
.site-nav.open { display: block; }
|
|
711
|
-
|
|
964
|
+
/* The desktop rail is a desktop idea: a fixed 17rem column would eat a phone.
|
|
965
|
+
Below the breakpoint the burger owns navigation and the flat list replaces it. */
|
|
966
|
+
.site-nav > .ls-nav, .ls-nav-show { display: none; }
|
|
712
967
|
|
|
713
968
|
.mobile-nav { display: flex; flex-direction: column; }
|
|
714
969
|
.mobile-nav .mobile-item {
|
|
@@ -895,6 +1150,14 @@ span[data-tip], th[data-tip] { border-bottom: 1px dotted var(--muted-foreground)
|
|
|
895
1150
|
display: none !important;
|
|
896
1151
|
}
|
|
897
1152
|
|
|
1153
|
+
/* The `ls -l` rail sits inside .site-nav, so the rule above already hides it —
|
|
1154
|
+
but the room it reserved is padding on the BODY, and that would print as a
|
|
1155
|
+
17rem gutter down the right of every page. Hiding a fixed element does not
|
|
1156
|
+
reclaim the space something else was told to leave for it. */
|
|
1157
|
+
body {
|
|
1158
|
+
padding-right: 0 !important;
|
|
1159
|
+
}
|
|
1160
|
+
|
|
898
1161
|
[data-tip] {
|
|
899
1162
|
cursor: auto;
|
|
900
1163
|
}
|
|
@@ -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;--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;--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;--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;--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}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: 0.75rem;line-height: 1.5;overflow-x: hidden;-webkit-font-smoothing: antialiased;-moz-osx-font-smoothing: grayscale}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)}.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:hover{box-shadow: 0 0 28px var(--glow);transform: translateY(-2px)}.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: 0;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)}.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: 5px;background: #b42318;color: #fff}.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)}}.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)}.ls-row{display: flex;align-items: baseline;gap: 0.9rem}.ls-perm{color: var(--muted-foreground);font-size: 0.72rem;letter-spacing: 0}.ls-row--sub .ls-name{padding-left: 1rem}.ls-panel{min-width: 17rem;left: auto !important;right: 0}#nav{position: relative}.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>.dropdown{display: none}.mobile-nav{display: flex;flex-direction: column}.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)}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: 4px;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,.anim-toggle,.cursor-block,.term-caret,.ascii-rule,.toc,#ddtip{display: none !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}.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.3.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;--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;--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;--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;--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}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: 0.75rem;line-height: 1.5;overflow-x: hidden;-webkit-font-smoothing: antialiased;-moz-osx-font-smoothing: grayscale}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)}.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:hover{box-shadow: 0 0 28px var(--glow);transform: translateY(-2px)}.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: 0;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)}.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: 5px;background: #b42318;color: #fff}.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}@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}.tickstrip:not(:empty){border: 1px solid var(--border);background: var(--card);margin: 0 0 1.25rem}.ticktable{width: 100%;border-collapse: collapse;font-size: 0.82rem}.ticktable td{padding: 0.45rem 0.55rem;vertical-align: baseline;border: 0;white-space: nowrap}.ticktable td.tick-stats{width: 100%;white-space: normal}.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: 1rem;padding-right: 0 !important}.tick--ok .tick-dot{color: var(--primary)}.tick--stale .tick-dot{color: var(--destructive)}.tick--never .tick-dot{color: var(--muted-foreground)}.tick-name{font-weight: 700;color: var(--primary)}.tick-last,.tick-next{font-variant-numeric: tabular-nums;color: var(--muted-foreground)}.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}}.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)}.ls-row{display: flex;align-items: baseline;gap: 0.9rem}.ls-perm{color: var(--muted-foreground);font-size: 0.72rem;letter-spacing: 0}.ls-row--sub .ls-name{padding-left: 1rem}.dropdown-panel.ls-panel{min-width: 17rem;left: auto !important;right: 0}:root{--ls-nav-w: 17rem}.ls-nav-show{display: none}@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)}footer.status{right: var(--ls-nav-inset)}.ls-nav{position: fixed;top: 0;right: 0;bottom: 0;width: var(--ls-nav-w);z-index: 45;display: flex;flex-direction: column;background: var(--card);border-left: 1px solid var(--border)}html:not(.anim-off) .ls-nav,html:not(.anim-off) .ls-nav-show{transition: transform 0.18s ease}html[data-ls-nav="off"] .ls-nav{transform: translateX(100%)}.ls-nav-head{flex: none;display: flex;align-items: center;justify-content: space-between;gap: 0.5rem;padding: 0.6rem 0.5rem 0.6rem 0.85rem;border-bottom: 1px solid var(--border)}.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-hide,.ls-nav-show{background: none;border: 0;font: inherit;cursor: pointer;line-height: 1;color: var(--muted-foreground);padding: 0.4rem 0.55rem}.ls-nav-hide:hover,.ls-nav-show:hover{color: var(--primary)}.ls-nav-hide:focus-visible,.ls-nav-show:focus-visible{outline: 2px solid color-mix(in srgb,var(--primary) 55%,transparent);outline-offset: -2px}html[data-ls-nav="off"] .ls-nav-show{display: inline-flex;align-items: center;gap: 0.4rem;position: fixed;top: 50%;right: 0;z-index: 45;transform: translateY(-50%);background: var(--card);border: 1px solid var(--border);border-right: 0;padding: 0.6rem 0.5rem;font-size: 0.8rem}}.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,.ls-nav-show{display: none}.mobile-nav{display: flex;flex-direction: column}.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)}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: 4px;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,.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}.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,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@danieldeusing/design",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "The danieldeusing terminal design system
|
|
3
|
+
"version": "0.3.0",
|
|
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",
|
|
7
7
|
"author": "Daniel Deusing",
|
package/runtime/index.js
CHANGED
package/runtime/lsnav.js
ADDED
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* lsnav.js — show/hide for the `ls -l` site rail (src/chrome.css).
|
|
3
|
+
*
|
|
4
|
+
* Markup contract:
|
|
5
|
+
* <div class="ls-nav" id="nav">
|
|
6
|
+
* <div class="ls-nav-head">
|
|
7
|
+
* <span class="ls-nav-title">ls -l</span>
|
|
8
|
+
* <button class="ls-nav-hide" data-ls-nav-toggle aria-controls="nav" aria-expanded="true">»</button>
|
|
9
|
+
* </div>
|
|
10
|
+
* <ul class="ls-panel">…</ul>
|
|
11
|
+
* </div>
|
|
12
|
+
* <button class="ls-nav-show" data-ls-nav-toggle aria-controls="nav">« ls -l</button>
|
|
13
|
+
*
|
|
14
|
+
* THIS MODULE DOES NOT APPLY THE INITIAL STATE, and that is deliberate. It is a
|
|
15
|
+
* module at the end of <body>, so anything it does happens after first paint: a
|
|
16
|
+
* reader who hid the rail would watch it paint and then jump away on every page
|
|
17
|
+
* load. The state is applied by an inline <head> script instead —
|
|
18
|
+
*
|
|
19
|
+
* try { if (localStorage.getItem("ls-nav") === "off")
|
|
20
|
+
* document.documentElement.dataset.lsNav = "off"; } catch {}
|
|
21
|
+
*
|
|
22
|
+
* — and this module only reads that, wires the clicks, and writes back. Shown is
|
|
23
|
+
* the default, so an absent key and a failed read both land on "shown", which is
|
|
24
|
+
* the state that is correct when in doubt.
|
|
25
|
+
*
|
|
26
|
+
* Every [data-ls-nav-toggle] toggles, so the hide button inside the rail and the
|
|
27
|
+
* tab that replaces it share one handler and one aria-expanded truth.
|
|
28
|
+
*/
|
|
29
|
+
const KEY = "ls-nav";
|
|
30
|
+
|
|
31
|
+
export function initLsNav() {
|
|
32
|
+
const root = document.documentElement;
|
|
33
|
+
const toggles = document.querySelectorAll("[data-ls-nav-toggle]");
|
|
34
|
+
if (!toggles.length) return;
|
|
35
|
+
|
|
36
|
+
const shown = () => root.dataset.lsNav !== "off";
|
|
37
|
+
const sync = () => {
|
|
38
|
+
for (const button of toggles) button.setAttribute("aria-expanded", String(shown()));
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
sync(); // the inline script set the state; the buttons have not been told yet
|
|
42
|
+
|
|
43
|
+
for (const button of toggles) {
|
|
44
|
+
button.addEventListener("click", () => {
|
|
45
|
+
const next = !shown();
|
|
46
|
+
root.dataset.lsNav = next ? "on" : "off";
|
|
47
|
+
// A rail the reader hid should stay hidden on the next page. Wrapped
|
|
48
|
+
// because a locked-down browser throws on write and a nav that cannot
|
|
49
|
+
// remember is still a working nav.
|
|
50
|
+
try {
|
|
51
|
+
localStorage.setItem(KEY, next ? "on" : "off");
|
|
52
|
+
} catch {}
|
|
53
|
+
sync();
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
}
|
package/src/chrome.css
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
/*
|
|
2
2
|
* chrome.css — the app chrome kit: sticky header bar, fixed status footer,
|
|
3
|
-
* and the responsive site navigation (desktop `ls -l`
|
|
4
|
-
*
|
|
5
|
-
*
|
|
3
|
+
* and the responsive site navigation (desktop `ls -l` RAIL, mobile burger with
|
|
4
|
+
* the footer folded in). Extracted from danieldeusing.de and the cockpit portal
|
|
5
|
+
* so every app ships the same chrome without rebuilding it.
|
|
6
6
|
*
|
|
7
7
|
* Markup contract (see templates/documentation.html for the full example):
|
|
8
8
|
*
|
|
@@ -11,7 +11,14 @@
|
|
|
11
11
|
* <a href="/" class="brand"><span class="glow">name</span><span class="cursor-block"></span></a>
|
|
12
12
|
* <button class="nav-burger" data-nav-toggle aria-controls="site-nav" aria-expanded="false">…☰ svg…</button>
|
|
13
13
|
* <nav aria-label="site" id="site-nav" class="site-nav">
|
|
14
|
-
* <
|
|
14
|
+
* <div class="ls-nav" id="nav">
|
|
15
|
+
* <div class="ls-nav-head">
|
|
16
|
+
* <span class="ls-nav-title">ls -l</span>
|
|
17
|
+
* <button class="ls-nav-hide" data-ls-nav-toggle aria-controls="nav" aria-expanded="true">»</button>
|
|
18
|
+
* </div>
|
|
19
|
+
* <ul class="ls-panel">….ls-row items…</ul>
|
|
20
|
+
* </div>
|
|
21
|
+
* <button class="ls-nav-show" data-ls-nav-toggle aria-controls="nav">« ls -l</button>
|
|
15
22
|
* <ul class="mobile-nav">…same links as .mobile-item ls-rows…</ul>
|
|
16
23
|
* <div class="mobile-footer">…footer controls folded in…</div>
|
|
17
24
|
* </nav>
|
|
@@ -19,8 +26,15 @@
|
|
|
19
26
|
* …
|
|
20
27
|
* <footer class="status">…status-left + status-right…</footer>
|
|
21
28
|
*
|
|
22
|
-
* Behaviour: pair with `initBurgerNav()`
|
|
23
|
-
*
|
|
29
|
+
* Behaviour: pair with `initBurgerNav()` (mobile burger) and `initLsNav()`
|
|
30
|
+
* (rail show/hide) from the runtime. The rail's SHOWN/HIDDEN state must also be
|
|
31
|
+
* applied before first paint by an inline <head> script — see the rail section
|
|
32
|
+
* below for why the runtime cannot be the one to do it:
|
|
33
|
+
*
|
|
34
|
+
* try { if (localStorage.getItem("ls-nav") === "off")
|
|
35
|
+
* document.documentElement.dataset.lsNav = "off"; } catch {}
|
|
36
|
+
*
|
|
37
|
+
* Breakpoint: 48rem.
|
|
24
38
|
*/
|
|
25
39
|
|
|
26
40
|
/* ── a11y helpers ── */
|
|
@@ -60,12 +74,100 @@ footer.status .dropdown summary, footer.status .anim-toggle { padding: 0.55rem 0
|
|
|
60
74
|
.doc-link { color: var(--muted-foreground); text-decoration: none; transition: color 0.15s ease; }
|
|
61
75
|
.doc-link:hover { color: var(--primary); }
|
|
62
76
|
|
|
63
|
-
/* ── ls-listing rows (shared by the desktop
|
|
77
|
+
/* ── ls-listing rows (shared by the desktop rail + mobile menu) ── */
|
|
64
78
|
.ls-row { display: flex; align-items: baseline; gap: 0.9rem; }
|
|
65
79
|
.ls-perm { color: var(--muted-foreground); font-size: 0.72rem; letter-spacing: 0; }
|
|
66
80
|
.ls-row--sub .ls-name { padding-left: 1rem; }
|
|
67
|
-
|
|
68
|
-
|
|
81
|
+
/* The pre-rail dropdown form. Kept, and scoped to the dropdown panel, because the
|
|
82
|
+
other surfaces still ship that markup — when they unpin they get this file, and
|
|
83
|
+
their nav should stay right-aligned rather than quietly jumping to the left. The
|
|
84
|
+
rail's own <ul> carries `ls-panel` WITHOUT `dropdown-panel`, so it is unaffected. */
|
|
85
|
+
.dropdown-panel.ls-panel { min-width: 17rem; left: auto !important; right: 0; }
|
|
86
|
+
|
|
87
|
+
/* ── `ls -l` site rail (desktop ≥48rem): sticky, full-height, right-hand panel ──
|
|
88
|
+
Was a dropdown that had to be re-opened on every page. Daniel: "Always make it
|
|
89
|
+
sticky (default shown) right side, keep `ls -l`, but not down arrow, but
|
|
90
|
+
something which tells the user he can hide / minimize."
|
|
91
|
+
|
|
92
|
+
SHOWN BY DEFAULT, and the state lives in localStorage under "ls-nav". It is
|
|
93
|
+
applied by an inline <head> script as `html[data-ls-nav="off"]` BEFORE first
|
|
94
|
+
paint — not by the runtime — because the runtime is a module at the end of
|
|
95
|
+
<body>: a reader who hid the rail would otherwise watch it paint and then jump
|
|
96
|
+
away on every single page load. `initLsNav()` only handles the clicks.
|
|
97
|
+
|
|
98
|
+
The affordance is deliberately NOT a caret. A caret means "a menu drops from
|
|
99
|
+
here", which is the thing this stopped being. Guillemets point the way the
|
|
100
|
+
panel travels — » pushes it off to the right, « pulls it back — and hiding it
|
|
101
|
+
leaves a small labelled tab on the edge rather than nothing, so a hidden rail
|
|
102
|
+
is still discoverable by someone who did not hide it themselves.
|
|
103
|
+
|
|
104
|
+
Width is one token so a consumer can widen it for longer trees. */
|
|
105
|
+
:root { --ls-nav-w: 17rem; }
|
|
106
|
+
.ls-nav-show { display: none; }
|
|
107
|
+
|
|
108
|
+
@media (min-width: 48.0625rem) {
|
|
109
|
+
/* Room is reserved ONLY when a rail is actually in the page, and the default is
|
|
110
|
+
zero. This is what makes the stylesheet safe to publish ahead of a consumer's
|
|
111
|
+
markup: every surface that still ships the old dropdown nav (or no nav at all)
|
|
112
|
+
keeps its full width instead of growing a 17rem gutter with nothing in it —
|
|
113
|
+
which is exactly what they would have got the moment they unpinned.
|
|
114
|
+
It also fails in the right direction: if :has() is unavailable the selector is
|
|
115
|
+
dropped, the inset stays 0, and the rail overlays content rather than the page
|
|
116
|
+
reserving space for a rail that never arrives. */
|
|
117
|
+
html { --ls-nav-inset: 0rem; }
|
|
118
|
+
html:has(.ls-nav):not([data-ls-nav="off"]) { --ls-nav-inset: var(--ls-nav-w); }
|
|
119
|
+
|
|
120
|
+
/* Everything in normal flow shifts left, so content sits against the rail's
|
|
121
|
+
left edge instead of running under it. The fixed footer has to be told
|
|
122
|
+
separately: `inset-inline: 0` ignores the body's padding. */
|
|
123
|
+
/* Deliberately NOT transitioned. Animating padding on <body> reflows the whole
|
|
124
|
+
document every frame, and the pages this ships to render tables of hundreds of
|
|
125
|
+
rows — the rail's own transform is composited and costs nothing, so the panel
|
|
126
|
+
slides while the content reflows once. Animating both looked no better and
|
|
127
|
+
made the containers dashboard stutter on toggle. */
|
|
128
|
+
body { padding-right: var(--ls-nav-inset); }
|
|
129
|
+
footer.status { right: var(--ls-nav-inset); }
|
|
130
|
+
|
|
131
|
+
.ls-nav {
|
|
132
|
+
position: fixed; top: 0; right: 0; bottom: 0; width: var(--ls-nav-w); z-index: 45;
|
|
133
|
+
display: flex; flex-direction: column;
|
|
134
|
+
background: var(--card); border-left: 1px solid var(--border);
|
|
135
|
+
}
|
|
136
|
+
html:not(.anim-off) .ls-nav,
|
|
137
|
+
html:not(.anim-off) .ls-nav-show { transition: transform 0.18s ease; }
|
|
138
|
+
html[data-ls-nav="off"] .ls-nav { transform: translateX(100%); }
|
|
139
|
+
|
|
140
|
+
.ls-nav-head {
|
|
141
|
+
flex: none; display: flex; align-items: center; justify-content: space-between; gap: 0.5rem;
|
|
142
|
+
padding: 0.6rem 0.5rem 0.6rem 0.85rem; border-bottom: 1px solid var(--border);
|
|
143
|
+
}
|
|
144
|
+
.ls-nav-title { font-weight: 700; font-size: 0.9rem; }
|
|
145
|
+
|
|
146
|
+
/* The list is the only thing that scrolls — the head stays put, so the control
|
|
147
|
+
that hides the rail cannot be scrolled out of reach. */
|
|
148
|
+
.ls-nav .ls-panel {
|
|
149
|
+
flex: 1; min-height: 0; overflow-y: auto;
|
|
150
|
+
list-style: none; margin: 0; padding: 0.5rem 0 1rem;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
.ls-nav-hide, .ls-nav-show {
|
|
154
|
+
background: none; border: 0; font: inherit; cursor: pointer; line-height: 1;
|
|
155
|
+
color: var(--muted-foreground); padding: 0.4rem 0.55rem;
|
|
156
|
+
}
|
|
157
|
+
.ls-nav-hide:hover, .ls-nav-show:hover { color: var(--primary); }
|
|
158
|
+
.ls-nav-hide:focus-visible, .ls-nav-show:focus-visible {
|
|
159
|
+
outline: 2px solid color-mix(in srgb, var(--primary) 55%, transparent); outline-offset: -2px;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
/* The tab that survives hiding. Sits on the right edge, mid-height, and only
|
|
163
|
+
exists while the rail is away. */
|
|
164
|
+
html[data-ls-nav="off"] .ls-nav-show {
|
|
165
|
+
display: inline-flex; align-items: center; gap: 0.4rem;
|
|
166
|
+
position: fixed; top: 50%; right: 0; z-index: 45; transform: translateY(-50%);
|
|
167
|
+
background: var(--card); border: 1px solid var(--border); border-right: 0;
|
|
168
|
+
padding: 0.6rem 0.5rem; font-size: 0.8rem;
|
|
169
|
+
}
|
|
170
|
+
}
|
|
69
171
|
|
|
70
172
|
/* ── burger + mobile menu ── */
|
|
71
173
|
.nav-burger {
|
|
@@ -86,7 +188,9 @@ footer.status .dropdown summary, footer.status .anim-toggle { padding: 0.55rem 0
|
|
|
86
188
|
padding: 0.4rem 1.25rem 0.9rem; max-height: calc(100vh - 6rem); overflow-y: auto;
|
|
87
189
|
}
|
|
88
190
|
.site-nav.open { display: block; }
|
|
89
|
-
|
|
191
|
+
/* The desktop rail is a desktop idea: a fixed 17rem column would eat a phone.
|
|
192
|
+
Below the breakpoint the burger owns navigation and the flat list replaces it. */
|
|
193
|
+
.site-nav > .ls-nav, .ls-nav-show { display: none; }
|
|
90
194
|
|
|
91
195
|
.mobile-nav { display: flex; flex-direction: column; }
|
|
92
196
|
.mobile-nav .mobile-item {
|
package/src/components.css
CHANGED
|
@@ -352,3 +352,126 @@ html.anim-off .cursor-block {
|
|
|
352
352
|
transform: translateY(0.12em);
|
|
353
353
|
}
|
|
354
354
|
}
|
|
355
|
+
|
|
356
|
+
/* ═══════════════════════════════════════════════════════════════════════════════════════════════
|
|
357
|
+
Shared page vocabulary, promoted from the cockpit portal (2026-08-04).
|
|
358
|
+
|
|
359
|
+
Each of these was built once in cockpit/pages/portal.css and then wanted by every other
|
|
360
|
+
surface. They live here so there is ONE definition — the tabs had already drifted into FOUR
|
|
361
|
+
page style blocks before being consolidated, which is exactly the failure this move prevents.
|
|
362
|
+
═════════════════════════════════════════════════════════════════════════════════════════════ */
|
|
363
|
+
|
|
364
|
+
/* ── tabs ──────────────────────────────────────────────────────────────────────────────────────
|
|
365
|
+
Daniel: "I am always searching the tabs. They are not falling into my eyes directly."
|
|
366
|
+
|
|
367
|
+
They were low-contrast pills at 0.7 opacity, and the SELECTED one differed from the rest only
|
|
368
|
+
by a slightly darker border and 8% background — so the row read as six equal chips and the eye
|
|
369
|
+
had to stop and compare them. This inverts it: the inactive tabs recede to plain muted text
|
|
370
|
+
with no chrome at all, and the active one is the only thing on the row carrying weight, colour
|
|
371
|
+
and a fill. Nothing to compare — one thing is loud. */
|
|
372
|
+
.tabs {
|
|
373
|
+
display: flex; flex-wrap: wrap; gap: 0.15rem; margin: 1.35rem 0 0;
|
|
374
|
+
border-bottom: 2px solid color-mix(in srgb, var(--border) 80%, transparent);
|
|
375
|
+
}
|
|
376
|
+
.tab {
|
|
377
|
+
background: transparent; border: 0; margin-bottom: -2px;
|
|
378
|
+
font: inherit; font-size: 0.86em; cursor: pointer; padding: 0.5rem 0.95rem;
|
|
379
|
+
color: var(--muted-foreground); transition: background 0.12s ease, color 0.12s ease;
|
|
380
|
+
}
|
|
381
|
+
.tab:hover { color: var(--primary); background: color-mix(in srgb, var(--primary) 10%, transparent); }
|
|
382
|
+
/* FILLED, not underlined. An underline plus a colour shift was still one thin rule and a hue
|
|
383
|
+
change on a row of same-weight words — Daniel was still hunting for the row. A solid block of
|
|
384
|
+
--primary with the page background as its text is the highest-contrast pair the theme owns
|
|
385
|
+
(5.6:1 on warm), and it is the only filled thing on the page, so it is found without reading. */
|
|
386
|
+
.tab[aria-selected="true"] { background: var(--primary); color: var(--background); font-weight: 700; }
|
|
387
|
+
.tab:focus-visible { outline: 2px solid color-mix(in srgb, var(--primary) 55%, transparent); outline-offset: -2px; }
|
|
388
|
+
/* 44px on a touch screen — tabs are the primary navigation of these pages, so they are the last
|
|
389
|
+
control that should be hard to hit. */
|
|
390
|
+
@media (pointer: coarse), (max-width: 40rem) {
|
|
391
|
+
.tab { padding: 0.7rem 0.8rem; min-height: 44px; }
|
|
392
|
+
}
|
|
393
|
+
section.doc.tab-panel { margin-top: 1.35rem; }
|
|
394
|
+
|
|
395
|
+
/* ── fold: progressive disclosure whose affordance is always visible ───────────────────────────
|
|
396
|
+
The detail behind a visual has to open by TAP — these pages are read from a phone, where hover
|
|
397
|
+
does not exist — and the control has to be findable WITHOUT hovering, or the detail is simply
|
|
398
|
+
text that was deleted. Hence a permanent ▸ marker and a full-width summary row rather than
|
|
399
|
+
anything that appears on hover.
|
|
400
|
+
|
|
401
|
+
Pair with a `beforeprint` handler that opens every <details>, or a printout silently loses
|
|
402
|
+
whatever the closed ones were holding. */
|
|
403
|
+
details.fold { border-top: 1px solid color-mix(in srgb, currentColor 15%, transparent); }
|
|
404
|
+
details.fold > summary {
|
|
405
|
+
display: flex; align-items: center; gap: 0.5rem; cursor: pointer; list-style: none;
|
|
406
|
+
padding: 0.55rem 0; font-size: 0.85rem; color: var(--muted-foreground);
|
|
407
|
+
}
|
|
408
|
+
details.fold > summary::-webkit-details-marker { display: none; }
|
|
409
|
+
details.fold > summary::before { content: "\25B8"; flex: none; opacity: 0.6; }
|
|
410
|
+
details.fold[open] > summary::before { content: "\25BE"; }
|
|
411
|
+
details.fold > summary:hover { color: var(--primary); }
|
|
412
|
+
details.fold > summary:focus-visible {
|
|
413
|
+
outline: 2px solid color-mix(in srgb, currentColor 45%, transparent); outline-offset: 2px;
|
|
414
|
+
}
|
|
415
|
+
.fold-body { font-size: 0.9rem; padding: 0 0 0.7rem 1.1rem; }
|
|
416
|
+
.fold-body > * + * { margin-top: 0.6rem; }
|
|
417
|
+
|
|
418
|
+
/* ── legend: one rendering of each state, beside the thing it keys ─────────────────────────────
|
|
419
|
+
An item is `display: block`, NOT flex: a gloss contains <strong>/<code>/<em>, and every one of
|
|
420
|
+
those would become its own flex item — which is how a single sentence first rendered as four
|
|
421
|
+
separately-wrapped columns. */
|
|
422
|
+
.legend {
|
|
423
|
+
list-style: none; padding: 0; margin: 0.7rem 0 0;
|
|
424
|
+
display: grid; gap: 0.4rem 1.4rem; font-size: 0.86rem;
|
|
425
|
+
}
|
|
426
|
+
@media (min-width: 52rem) { .legend { grid-template-columns: repeat(2, minmax(0, 1fr)); } }
|
|
427
|
+
.legend > li { line-height: 1.6; }
|
|
428
|
+
.legend > li > :first-child { margin-right: 0.45rem; }
|
|
429
|
+
|
|
430
|
+
/* ── ticker strip: one standard header for every poller ────────────────────────────────────────
|
|
431
|
+
Daniel: "For all pollers / tickers we should make a standard layout ... on top of the pages,
|
|
432
|
+
compact and like dashboard style."
|
|
433
|
+
|
|
434
|
+
Every poller answers the same four questions — what ran, when, when next, what did it count —
|
|
435
|
+
and each page was answering them in its own prose box, so "is anything stalled?" took four
|
|
436
|
+
paragraphs instead of one glance.
|
|
437
|
+
|
|
438
|
+
A real TABLE, not a grid per row. The first version made each row its own `display: grid`,
|
|
439
|
+
which cannot align anything across rows: every row sized its columns to its own content, so
|
|
440
|
+
`next` sat wherever that row's name happened to end and the layout moved when a label changed.
|
|
441
|
+
One table means one set of column widths for the whole strip, and scanning a column is the only
|
|
442
|
+
thing the strip is for. Borderless by design — rules between cells draw a grid nobody reads.
|
|
443
|
+
|
|
444
|
+
Colour comes only from --primary and --destructive, both per-theme. Hardcoded accents miss AA
|
|
445
|
+
on at least one theme (measured 2026-08-04), so a strip inventing its own greens and ambers
|
|
446
|
+
would have added five more. */
|
|
447
|
+
.tickstrip:not(:empty) { border: 1px solid var(--border); background: var(--card); margin: 0 0 1.25rem; }
|
|
448
|
+
.ticktable { width: 100%; border-collapse: collapse; font-size: 0.82rem; }
|
|
449
|
+
.ticktable td { padding: 0.45rem 0.55rem; vertical-align: baseline; border: 0; white-space: nowrap; }
|
|
450
|
+
/* Only the last column may grow, so the four fixed columns keep the same width on every row. */
|
|
451
|
+
.ticktable td.tick-stats { width: 100%; white-space: normal; }
|
|
452
|
+
.tick { border-bottom: 1px solid color-mix(in srgb, var(--border) 55%, transparent); }
|
|
453
|
+
.tick:last-child { border-bottom: 0; }
|
|
454
|
+
/* The glyph carries the state WITHOUT colour as well as with it — two of the four themes are
|
|
455
|
+
near-black and a printout has none, so ● vs ✕ vs ○ has to survive on its own. */
|
|
456
|
+
.tick-dot { font-size: 0.7em; width: 1rem; padding-right: 0 !important; }
|
|
457
|
+
.tick--ok .tick-dot { color: var(--primary); }
|
|
458
|
+
.tick--stale .tick-dot { color: var(--destructive); }
|
|
459
|
+
.tick--never .tick-dot { color: var(--muted-foreground); }
|
|
460
|
+
.tick-name { font-weight: 700; color: var(--primary); }
|
|
461
|
+
.tick-last, .tick-next { font-variant-numeric: tabular-nums; color: var(--muted-foreground); }
|
|
462
|
+
.tick--stale .tick-last { color: var(--destructive); font-weight: 700; }
|
|
463
|
+
.tick--never .tick-last { font-style: italic; }
|
|
464
|
+
/* The NUMBER is what gets scanned; its label is context. Weight and colour on the value alone
|
|
465
|
+
means a row reads as its numbers first and its wording second. */
|
|
466
|
+
.tick-stats { color: var(--muted-foreground); }
|
|
467
|
+
.tick-stats b { font-weight: 700; color: var(--primary); font-variant-numeric: tabular-nums; }
|
|
468
|
+
.tick-sep { font-style: normal; opacity: 0.35; margin: 0 0.5rem; }
|
|
469
|
+
/* A stale row earns the only background tint in the strip — an all-tinted strip is one the eye
|
|
470
|
+
stops reading. */
|
|
471
|
+
.tick--stale { background: color-mix(in srgb, var(--destructive) 8%, transparent); }
|
|
472
|
+
@media (max-width: 40rem) {
|
|
473
|
+
.ticktable, .ticktable tbody, .tick, .ticktable td { display: block; }
|
|
474
|
+
.tick { padding: 0.4rem 0.5rem; }
|
|
475
|
+
.ticktable td { display: inline-block; padding: 0.1rem 0.5rem 0.1rem 0; }
|
|
476
|
+
.ticktable td.tick-stats { display: block; width: auto; }
|
|
477
|
+
}
|
package/src/print.css
CHANGED
|
@@ -131,6 +131,14 @@
|
|
|
131
131
|
display: none !important;
|
|
132
132
|
}
|
|
133
133
|
|
|
134
|
+
/* The `ls -l` rail sits inside .site-nav, so the rule above already hides it —
|
|
135
|
+
but the room it reserved is padding on the BODY, and that would print as a
|
|
136
|
+
17rem gutter down the right of every page. Hiding a fixed element does not
|
|
137
|
+
reclaim the space something else was told to leave for it. */
|
|
138
|
+
body {
|
|
139
|
+
padding-right: 0 !important;
|
|
140
|
+
}
|
|
141
|
+
|
|
134
142
|
[data-tip] {
|
|
135
143
|
cursor: auto;
|
|
136
144
|
}
|
package/src/tokens.css
CHANGED
|
@@ -9,6 +9,26 @@
|
|
|
9
9
|
* These are plain CSS custom properties — they work in any framework, any
|
|
10
10
|
* browser, with or without a build step. Load this file first; base.css and
|
|
11
11
|
* components.css resolve against the variables declared here.
|
|
12
|
+
*
|
|
13
|
+
* ── WHY EVERY ACCENT IS PER-THEME, AND WHY A LITERAL IS NEVER OK ──
|
|
14
|
+
* A status colour has to clear WCAG AA 4.5:1 against every surface it can land
|
|
15
|
+
* on — --background, --card AND --muted. Do that arithmetic across the four
|
|
16
|
+
* themes and the two admissible luminance bands DO NOT OVERLAP:
|
|
17
|
+
*
|
|
18
|
+
* light themes: darkest surface is warm's --muted #ece3cf -> accent L <= 0.1328
|
|
19
|
+
* dark themes: lightest surface is green's --muted #071509 -> accent L >= 0.2021
|
|
20
|
+
* (mono's #111111 wants accent L >= 0.2002)
|
|
21
|
+
*
|
|
22
|
+
* A gap of 0.0692 with nothing in it. So NO single hex can serve four themes:
|
|
23
|
+
* the two near-black themes need a light tint, the two light themes a dark one,
|
|
24
|
+
* and every compromise fails both. That is arithmetic, not taste — which is why
|
|
25
|
+
* --destructive, --success and --warning are each declared four times.
|
|
26
|
+
*
|
|
27
|
+
* Cockpit carried twelve hardcoded accents that predate these tokens; every one
|
|
28
|
+
* missed AA on at least one theme and four sat inside the gap, clearing neither
|
|
29
|
+
* side. Adding a status colour? Add a TOKEN here with its measured worst-case
|
|
30
|
+
* ratio in a trailing comment. Do not tune a literal at the call site — that
|
|
31
|
+
* forks the palette and fixes exactly one theme.
|
|
12
32
|
*/
|
|
13
33
|
|
|
14
34
|
:root {
|
|
@@ -27,6 +47,8 @@
|
|
|
27
47
|
--accent: #8a4516;
|
|
28
48
|
--accent-foreground: #f8f3e8;
|
|
29
49
|
--destructive: #a02c2c;
|
|
50
|
+
--success: #1a6b2e; /* 5.16:1 worst surface */
|
|
51
|
+
--warning: #845600; /* 4.97:1 worst surface */
|
|
30
52
|
--border: #d9cdb6;
|
|
31
53
|
--input: #d9cdb6;
|
|
32
54
|
--ring: #8a4516;
|
|
@@ -57,6 +79,8 @@ html[data-theme="green"] {
|
|
|
57
79
|
--accent: #33ff66;
|
|
58
80
|
--accent-foreground: #02160a;
|
|
59
81
|
--destructive: #ff5c5c;
|
|
82
|
+
--success: #4ade80; /* 10.76:1 worst surface */
|
|
83
|
+
--warning: #f5b32b; /* 10.15:1 worst surface */
|
|
60
84
|
--border: #1c4a2c;
|
|
61
85
|
--input: #1c4a2c;
|
|
62
86
|
--ring: #33ff66;
|
|
@@ -82,6 +106,8 @@ html[data-theme="mono"] {
|
|
|
82
106
|
--accent: #ffffff;
|
|
83
107
|
--accent-foreground: #050505;
|
|
84
108
|
--destructive: #ff5c5c;
|
|
109
|
+
--success: #4ade80; /* 10.84:1 worst surface */
|
|
110
|
+
--warning: #f5b32b; /* 10.22:1 worst surface */
|
|
85
111
|
--border: #333333;
|
|
86
112
|
--input: #333333;
|
|
87
113
|
--ring: #ffffff;
|
|
@@ -107,6 +133,8 @@ html[data-theme="paper"] {
|
|
|
107
133
|
--accent: #000000;
|
|
108
134
|
--accent-foreground: #fafafa;
|
|
109
135
|
--destructive: #b3261e;
|
|
136
|
+
--success: #1a7031; /* 5.36:1 worst surface */
|
|
137
|
+
--warning: #8f5d00; /* 4.89:1 worst surface */
|
|
110
138
|
--border: #d4d4d4;
|
|
111
139
|
--input: #d4d4d4;
|
|
112
140
|
--ring: #000000;
|
package/tokens/tokens.json
CHANGED
|
@@ -18,6 +18,8 @@
|
|
|
18
18
|
"accent": "#8a4516",
|
|
19
19
|
"accent-foreground": "#f8f3e8",
|
|
20
20
|
"destructive": "#a02c2c",
|
|
21
|
+
"success": "#1a6b2e",
|
|
22
|
+
"warning": "#845600",
|
|
21
23
|
"border": "#d9cdb6",
|
|
22
24
|
"input": "#d9cdb6",
|
|
23
25
|
"ring": "#8a4516",
|
|
@@ -43,6 +45,8 @@
|
|
|
43
45
|
"accent": "#33ff66",
|
|
44
46
|
"accent-foreground": "#02160a",
|
|
45
47
|
"destructive": "#ff5c5c",
|
|
48
|
+
"success": "#4ade80",
|
|
49
|
+
"warning": "#f5b32b",
|
|
46
50
|
"border": "#1c4a2c",
|
|
47
51
|
"input": "#1c4a2c",
|
|
48
52
|
"ring": "#33ff66",
|
|
@@ -66,6 +70,8 @@
|
|
|
66
70
|
"accent": "#ffffff",
|
|
67
71
|
"accent-foreground": "#050505",
|
|
68
72
|
"destructive": "#ff5c5c",
|
|
73
|
+
"success": "#4ade80",
|
|
74
|
+
"warning": "#f5b32b",
|
|
69
75
|
"border": "#333333",
|
|
70
76
|
"input": "#333333",
|
|
71
77
|
"ring": "#ffffff",
|
|
@@ -89,6 +95,8 @@
|
|
|
89
95
|
"accent": "#000000",
|
|
90
96
|
"accent-foreground": "#fafafa",
|
|
91
97
|
"destructive": "#b3261e",
|
|
98
|
+
"success": "#1a7031",
|
|
99
|
+
"warning": "#8f5d00",
|
|
92
100
|
"border": "#d4d4d4",
|
|
93
101
|
"input": "#d4d4d4",
|
|
94
102
|
"ring": "#000000",
|