@danieldeusing/design 0.2.0 → 0.3.1
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 +134 -23
- package/dist/danieldeusing-design.min.css +2 -2
- package/package.json +1 -1
- package/runtime/diagramzoom.js +156 -0
- package/runtime/index.js +1 -0
- package/runtime/lsnav.js +26 -0
- package/src/chrome.css +41 -14
- package/src/components.css +64 -8
- 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.1 | 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;
|
|
@@ -712,19 +740,30 @@ details.fold > summary:focus-visible {
|
|
|
712
740
|
would have added five more. */
|
|
713
741
|
.tickstrip:not(:empty) { border: 1px solid var(--border); background: var(--card); margin: 0 0 1.25rem; }
|
|
714
742
|
.ticktable { width: 100%; border-collapse: collapse; font-size: 0.82rem; }
|
|
715
|
-
.ticktable td { padding: 0.
|
|
743
|
+
.ticktable td { padding: 0.5rem 0.75rem; vertical-align: baseline; border: 0; white-space: nowrap; }
|
|
716
744
|
/* Only the last column may grow, so the four fixed columns keep the same width on every row. */
|
|
717
745
|
.ticktable td.tick-stats { width: 100%; white-space: normal; }
|
|
718
746
|
.tick { border-bottom: 1px solid color-mix(in srgb, var(--border) 55%, transparent); }
|
|
719
747
|
.tick:last-child { border-bottom: 0; }
|
|
720
|
-
/*
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
.tick
|
|
724
|
-
.
|
|
725
|
-
|
|
726
|
-
|
|
748
|
+
/* State reads THREE ways, so it survives every context: the GLYPH (two themes are near-black and
|
|
749
|
+
a printout has no colour at all), the COLOUR, and a left edge-marker that makes a bad row
|
|
750
|
+
findable by shape alone when the strip is scanned rather than read. */
|
|
751
|
+
.tick-dot {
|
|
752
|
+
font-size: 0.7em; width: 1.5rem; padding-right: 0 !important;
|
|
753
|
+
border-left: 3px solid transparent; padding-left: 0.6rem !important;
|
|
754
|
+
}
|
|
755
|
+
/* --success is per-theme and AA-measured (0.3.0). `ok` used to borrow --primary — the BRAND
|
|
756
|
+
colour — so a healthy poller and the page heading were the same ink, and "everything is fine"
|
|
757
|
+
had to be read rather than seen. */
|
|
758
|
+
.tick--ok .tick-dot { color: var(--success); border-left-color: color-mix(in srgb, var(--success) 45%, transparent); }
|
|
759
|
+
.tick--stale .tick-dot { color: var(--destructive); border-left-color: var(--destructive); }
|
|
760
|
+
.tick--never .tick-dot { color: var(--muted-foreground); border-left-color: color-mix(in srgb, var(--muted-foreground) 35%, transparent); }
|
|
761
|
+
/* The NAME is an identifier, not an accent. Making it --primary put brand colour on every row of
|
|
762
|
+
a strip whose entire job is that the exceptions stand out. */
|
|
763
|
+
.tick-name { font-weight: 700; color: var(--foreground); letter-spacing: 0.01em; }
|
|
727
764
|
.tick-last, .tick-next { font-variant-numeric: tabular-nums; color: var(--muted-foreground); }
|
|
765
|
+
/* "next 57s" is a prediction; the age beside it is an observation. It should not compete. */
|
|
766
|
+
.tick-next { opacity: 0.72; }
|
|
728
767
|
.tick--stale .tick-last { color: var(--destructive); font-weight: 700; }
|
|
729
768
|
.tick--never .tick-last { font-style: italic; }
|
|
730
769
|
/* The NUMBER is what gets scanned; its label is context. Weight and colour on the value alone
|
|
@@ -742,6 +781,51 @@ details.fold > summary:focus-visible {
|
|
|
742
781
|
.ticktable td.tick-stats { display: block; width: auto; }
|
|
743
782
|
}
|
|
744
783
|
|
|
784
|
+
/* ── zoomable diagrams ────────────────────────────────────────────────────────────────────────
|
|
785
|
+
An architecture diagram authored for a text column is unreadable at exactly the moment someone
|
|
786
|
+
needs it. Pair with `initDiagramZoom()`: every `.diagram` becomes a real button that opens
|
|
787
|
+
full-screen, fitted to the viewport, then zoomable and pannable.
|
|
788
|
+
|
|
789
|
+
The affordance has to be visible without hovering — this portal is read from a phone — so the
|
|
790
|
+
corner hint is always drawn, not revealed on :hover. */
|
|
791
|
+
.dgm-zoomable { position: relative; cursor: zoom-in; }
|
|
792
|
+
.dgm-zoomable::after {
|
|
793
|
+
content: "⤢"; position: absolute; top: 0.4rem; right: 0.5rem;
|
|
794
|
+
font-size: 0.85rem; line-height: 1; opacity: 0.45; pointer-events: none;
|
|
795
|
+
color: var(--muted-foreground);
|
|
796
|
+
}
|
|
797
|
+
.dgm-zoomable:hover::after, .dgm-zoomable:focus-visible::after { opacity: 1; color: var(--primary); }
|
|
798
|
+
.dgm-zoomable:focus-visible { outline: 2px solid var(--primary); outline-offset: 3px; }
|
|
799
|
+
|
|
800
|
+
html.dgm-locked, html.dgm-locked body { overflow: hidden; }
|
|
801
|
+
|
|
802
|
+
.dgm-overlay {
|
|
803
|
+
display: none; position: fixed; inset: 0; z-index: 90;
|
|
804
|
+
background: color-mix(in srgb, var(--background) 96%, transparent);
|
|
805
|
+
flex-direction: column;
|
|
806
|
+
}
|
|
807
|
+
.dgm-overlay.open { display: flex; }
|
|
808
|
+
.dgm-bar {
|
|
809
|
+
flex: none; display: flex; align-items: center; justify-content: flex-end; gap: 0.4rem;
|
|
810
|
+
padding: 0.5rem 0.75rem; border-bottom: 1px solid var(--border); background: var(--card);
|
|
811
|
+
}
|
|
812
|
+
.dgm-btn {
|
|
813
|
+
font: inherit; font-size: 0.82rem; cursor: pointer; padding: 0.3rem 0.7rem; min-height: 32px;
|
|
814
|
+
background: transparent; border: 1px solid var(--border); color: var(--foreground);
|
|
815
|
+
}
|
|
816
|
+
.dgm-btn:hover { border-color: var(--primary); color: var(--primary); }
|
|
817
|
+
.dgm-btn:focus-visible { outline: 2px solid var(--primary); outline-offset: 1px; }
|
|
818
|
+
.dgm-close { border-color: color-mix(in srgb, var(--destructive) 55%, transparent); color: var(--destructive); }
|
|
819
|
+
.dgm-close:hover { border-color: var(--destructive); color: var(--destructive); }
|
|
820
|
+
/* The stage clips; the artwork inside it is what moves. Transforming a wrapper rather than the
|
|
821
|
+
svg keeps mermaid's own geometry untouched. */
|
|
822
|
+
.dgm-stage { flex: 1; overflow: hidden; display: grid; place-items: center; cursor: grab; touch-action: none; }
|
|
823
|
+
.dgm-stage.is-grabbing { cursor: grabbing; }
|
|
824
|
+
.dgm-art { transform-origin: center center; will-change: transform; }
|
|
825
|
+
.dgm-art > svg, .dgm-art > img { display: block; max-width: none; max-height: none; }
|
|
826
|
+
|
|
827
|
+
@media print { .dgm-overlay, .dgm-zoomable::after { display: none !important; } }
|
|
828
|
+
|
|
745
829
|
/* ===== chrome.css ===== */
|
|
746
830
|
/*
|
|
747
831
|
* chrome.css — the app chrome kit: sticky header bar, fixed status footer,
|
|
@@ -846,8 +930,20 @@ footer.status .dropdown summary, footer.status .anim-toggle { padding: 0.55rem 0
|
|
|
846
930
|
leaves a small labelled tab on the edge rather than nothing, so a hidden rail
|
|
847
931
|
is still discoverable by someone who did not hide it themselves.
|
|
848
932
|
|
|
933
|
+
THE HEADER AND FOOTER WIN. The rail runs BETWEEN them — it never covers the bar
|
|
934
|
+
or the status line, and both keep their full width. Daniel: "the right sidebar
|
|
935
|
+
then should not overlay footer and header. Footer and header wins."
|
|
936
|
+
That also buys the better toggle behaviour: with the rail starting below the
|
|
937
|
+
bar, `ls -l` sits at ONE fixed spot whether the rail is open or shut, so the
|
|
938
|
+
only thing that changes is which way the arrow points. The reader never has to
|
|
939
|
+
go looking for a control that moved.
|
|
940
|
+
|
|
941
|
+
--ls-nav-top/--ls-nav-bottom are measured off the real chrome by initLsNav()
|
|
942
|
+
and written onto <html>; the values here are fallbacks that keep the layout
|
|
943
|
+
sane if the runtime never arrives.
|
|
944
|
+
|
|
849
945
|
Width is one token so a consumer can widen it for longer trees. */
|
|
850
|
-
:root { --ls-nav-w: 17rem; }
|
|
946
|
+
:root { --ls-nav-w: 17rem; --ls-nav-top: 3rem; --ls-nav-bottom: 2.2rem; }
|
|
851
947
|
.ls-nav-show { display: none; }
|
|
852
948
|
|
|
853
949
|
@media (min-width: 48.0625rem) {
|
|
@@ -862,19 +958,30 @@ footer.status .dropdown summary, footer.status .anim-toggle { padding: 0.55rem 0
|
|
|
862
958
|
html { --ls-nav-inset: 0rem; }
|
|
863
959
|
html:has(.ls-nav):not([data-ls-nav="off"]) { --ls-nav-inset: var(--ls-nav-w); }
|
|
864
960
|
|
|
865
|
-
/*
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
961
|
+
/* Content shifts left to sit against the rail's edge — but the CHROME does not.
|
|
962
|
+
The header bar and the status footer pull back out to full width with a
|
|
963
|
+
negative margin, so the rail begins below one and ends above the other rather
|
|
964
|
+
than covering either. Applied to the body rather than to a `main` selector
|
|
965
|
+
because the design system cannot know a consumer's content wrapper: every
|
|
966
|
+
block child shifts, and the two that must not are named explicitly.
|
|
967
|
+
|
|
968
|
+
Deliberately NOT transitioned. Animating padding on <body> reflows the whole
|
|
869
969
|
document every frame, and the pages this ships to render tables of hundreds of
|
|
870
970
|
rows — the rail's own transform is composited and costs nothing, so the panel
|
|
871
|
-
slides while the content reflows once.
|
|
872
|
-
made the containers dashboard stutter on toggle. */
|
|
971
|
+
slides while the content reflows once. */
|
|
873
972
|
body { padding-right: var(--ls-nav-inset); }
|
|
874
|
-
|
|
875
|
-
|
|
973
|
+
/* Only the HEADER needs pulling back out. It is `position: sticky`, i.e. still
|
|
974
|
+
in flow, so the body's padding narrows it. The footer is `position: fixed`
|
|
975
|
+
with `inset-inline: 0`, which ignores body padding and is already full width
|
|
976
|
+
— giving it the same negative margin pushed it 272px PAST the viewport edge
|
|
977
|
+
(measured 1099px against an 842px viewport). Fixed and in-flow siblings look
|
|
978
|
+
alike in the markup and do not behave alike here. */
|
|
979
|
+
header.bar { margin-right: calc(-1 * var(--ls-nav-inset)); }
|
|
980
|
+
|
|
981
|
+
/* Between the chrome, never over it. */
|
|
876
982
|
.ls-nav {
|
|
877
|
-
position: fixed; top:
|
|
983
|
+
position: fixed; top: var(--ls-nav-top); right: 0; bottom: var(--ls-nav-bottom);
|
|
984
|
+
width: var(--ls-nav-w); z-index: 25;
|
|
878
985
|
display: flex; flex-direction: column;
|
|
879
986
|
background: var(--card); border-left: 1px solid var(--border);
|
|
880
987
|
}
|
|
@@ -904,13 +1011,17 @@ footer.status .dropdown summary, footer.status .anim-toggle { padding: 0.55rem 0
|
|
|
904
1011
|
outline: 2px solid color-mix(in srgb, var(--primary) 55%, transparent); outline-offset: -2px;
|
|
905
1012
|
}
|
|
906
1013
|
|
|
907
|
-
/* The tab that survives hiding
|
|
908
|
-
|
|
1014
|
+
/* The tab that survives hiding — TOP RIGHT, in the header band, which is where
|
|
1015
|
+
`ls -l` has always lived (it was a header dropdown before it was a rail) and
|
|
1016
|
+
where the rail's own title sits when it is open. An earlier version centred it
|
|
1017
|
+
vertically on the right edge: correct as a drawer handle, wrong here, because
|
|
1018
|
+
it moved the one control the reader is looking for to a place navigation has
|
|
1019
|
+
never been. Same corner open or closed. */
|
|
909
1020
|
html[data-ls-nav="off"] .ls-nav-show {
|
|
910
1021
|
display: inline-flex; align-items: center; gap: 0.4rem;
|
|
911
|
-
position: fixed; top:
|
|
1022
|
+
position: fixed; top: var(--ls-nav-top); right: 0; z-index: 25;
|
|
912
1023
|
background: var(--card); border: 1px solid var(--border); border-right: 0;
|
|
913
|
-
padding: 0.6rem 0.
|
|
1024
|
+
border-top: 0; padding: 0.6rem 0.85rem; font-size: 0.9rem; font-weight: 700;
|
|
914
1025
|
}
|
|
915
1026
|
}
|
|
916
1027
|
|
|
@@ -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)}}.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}}
|
|
1
|
+
/*! danieldeusing-design v0.3.1 | 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.5rem 0.75rem;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: 1.5rem;padding-right: 0 !important;border-left: 3px solid transparent;padding-left: 0.6rem !important}.tick--ok .tick-dot{color: var(--success);border-left-color: color-mix(in srgb,var(--success) 45%,transparent)}.tick--stale .tick-dot{color: var(--destructive);border-left-color: var(--destructive)}.tick--never .tick-dot{color: var(--muted-foreground);border-left-color: color-mix(in srgb,var(--muted-foreground) 35%,transparent)}.tick-name{font-weight: 700;color: var(--foreground);letter-spacing: 0.01em}.tick-last,.tick-next{font-variant-numeric: tabular-nums;color: var(--muted-foreground)}.tick-next{opacity: 0.72}.tick--stale .tick-last{color: var(--destructive);font-weight: 700}.tick--never .tick-last{font-style: italic}.tick-stats{color: var(--muted-foreground)}.tick-stats b{font-weight: 700;color: var(--primary);font-variant-numeric: tabular-nums}.tick-sep{font-style: normal;opacity: 0.35;margin: 0 0.5rem}.tick--stale{background: color-mix(in srgb,var(--destructive) 8%,transparent)}@media (max-width: 40rem){.ticktable,.ticktable tbody,.tick,.ticktable td{display: block}.tick{padding: 0.4rem 0.5rem}.ticktable td{display: inline-block;padding: 0.1rem 0.5rem 0.1rem 0}.ticktable td.tick-stats{display: block;width: auto}}.dgm-zoomable{position: relative;cursor: zoom-in}.dgm-zoomable::after{content: "⤢";position: absolute;top: 0.4rem;right: 0.5rem;font-size: 0.85rem;line-height: 1;opacity: 0.45;pointer-events: none;color: var(--muted-foreground)}.dgm-zoomable:hover::after,.dgm-zoomable:focus-visible::after{opacity: 1;color: var(--primary)}.dgm-zoomable:focus-visible{outline: 2px solid var(--primary);outline-offset: 3px}html.dgm-locked,html.dgm-locked body{overflow: hidden}.dgm-overlay{display: none;position: fixed;inset: 0;z-index: 90;background: color-mix(in srgb,var(--background) 96%,transparent);flex-direction: column}.dgm-overlay.open{display: flex}.dgm-bar{flex: none;display: flex;align-items: center;justify-content: flex-end;gap: 0.4rem;padding: 0.5rem 0.75rem;border-bottom: 1px solid var(--border);background: var(--card)}.dgm-btn{font: inherit;font-size: 0.82rem;cursor: pointer;padding: 0.3rem 0.7rem;min-height: 32px;background: transparent;border: 1px solid var(--border);color: var(--foreground)}.dgm-btn:hover{border-color: var(--primary);color: var(--primary)}.dgm-btn:focus-visible{outline: 2px solid var(--primary);outline-offset: 1px}.dgm-close{border-color: color-mix(in srgb,var(--destructive) 55%,transparent);color: var(--destructive)}.dgm-close:hover{border-color: var(--destructive);color: var(--destructive)}.dgm-stage{flex: 1;overflow: hidden;display: grid;place-items: center;cursor: grab;touch-action: none}.dgm-stage.is-grabbing{cursor: grabbing}.dgm-art{transform-origin: center center;will-change: transform}.dgm-art>svg,.dgm-art>img{display: block;max-width: none;max-height: none}@media print{.dgm-overlay,.dgm-zoomable::after{display: none !important}}.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-top: 3rem;--ls-nav-bottom: 2.2rem}.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)}header.bar{margin-right: calc(-1 * var(--ls-nav-inset))}.ls-nav{position: fixed;top: var(--ls-nav-top);right: 0;bottom: var(--ls-nav-bottom);width: var(--ls-nav-w);z-index: 25;display: flex;flex-direction: column;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: var(--ls-nav-top);right: 0;z-index: 25;background: var(--card);border: 1px solid var(--border);border-right: 0;border-top: 0;padding: 0.6rem 0.85rem;font-size: 0.9rem;font-weight: 700}}.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,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@danieldeusing/design",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.1",
|
|
4
4
|
"description": "The danieldeusing terminal design system — framework-agnostic CSS tokens, components, and a vanilla-JS runtime for the CRT/JetBrains-Mono look used across danieldeusing.de, seedr, and briefs.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* diagramzoom.js — open any `.diagram` full-screen, then zoom and pan it.
|
|
3
|
+
*
|
|
4
|
+
* Markup contract: whatever `.diagram` wraps (a mermaid <svg>, an <img>). No
|
|
5
|
+
* per-diagram attributes — a diagram is zoomable by virtue of being a diagram,
|
|
6
|
+
* which is the point: an architecture diagram that has to fit a text column is
|
|
7
|
+
* unreadable at exactly the moment someone needs it.
|
|
8
|
+
*
|
|
9
|
+
* Behaviour: click (or Enter/Space — the wrapper becomes a real button, so this
|
|
10
|
+
* works from the keyboard and is announced) opens an overlay with the diagram
|
|
11
|
+
* scaled to FIT the viewport. Wheel zooms about the pointer, drag pans, +/-/0
|
|
12
|
+
* and the on-screen controls do the same. Escape or a click on the backdrop
|
|
13
|
+
* closes and returns focus to the diagram that was opened.
|
|
14
|
+
*
|
|
15
|
+
* The SVG is CLONED into the overlay rather than moved: mermaid holds references
|
|
16
|
+
* to the nodes it rendered and re-runs against them (a folded or tabbed diagram
|
|
17
|
+
* is redrawn when it becomes visible), so moving the original out of the document
|
|
18
|
+
* and back is how you get a diagram that silently stops updating.
|
|
19
|
+
*/
|
|
20
|
+
const FIT_MARGIN = 0.92; // leave a little air around a fitted diagram
|
|
21
|
+
const MIN_SCALE = 0.2;
|
|
22
|
+
const MAX_SCALE = 12;
|
|
23
|
+
|
|
24
|
+
export function initDiagramZoom(selector = ".diagram") {
|
|
25
|
+
const diagrams = Array.from(document.querySelectorAll(selector));
|
|
26
|
+
if (!diagrams.length) return;
|
|
27
|
+
|
|
28
|
+
let overlay, stage, art, closeBtn, opener;
|
|
29
|
+
let scale = 1, tx = 0, ty = 0, dragging = false, lastX = 0, lastY = 0;
|
|
30
|
+
|
|
31
|
+
const apply = () => { art.style.transform = `translate(${tx}px, ${ty}px) scale(${scale})`; };
|
|
32
|
+
const zoomTo = (next, cx, cy) => {
|
|
33
|
+
const clamped = Math.min(MAX_SCALE, Math.max(MIN_SCALE, next));
|
|
34
|
+
if (clamped === scale) return;
|
|
35
|
+
// keep the point under the cursor stationary
|
|
36
|
+
const rect = stage.getBoundingClientRect();
|
|
37
|
+
const px = (cx ?? rect.left + rect.width / 2) - rect.left - rect.width / 2;
|
|
38
|
+
const py = (cy ?? rect.top + rect.height / 2) - rect.top - rect.height / 2;
|
|
39
|
+
const ratio = clamped / scale;
|
|
40
|
+
tx = px - (px - tx) * ratio;
|
|
41
|
+
ty = py - (py - ty) * ratio;
|
|
42
|
+
scale = clamped;
|
|
43
|
+
apply();
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
const build = () => {
|
|
47
|
+
overlay = document.createElement("div");
|
|
48
|
+
overlay.className = "dgm-overlay";
|
|
49
|
+
overlay.setAttribute("role", "dialog");
|
|
50
|
+
overlay.setAttribute("aria-modal", "true");
|
|
51
|
+
overlay.setAttribute("aria-label", "diagram, zoomable");
|
|
52
|
+
overlay.innerHTML =
|
|
53
|
+
'<div class="dgm-bar">' +
|
|
54
|
+
'<button type="button" class="dgm-btn" data-dgm="out" aria-label="zoom out">−</button>' +
|
|
55
|
+
'<button type="button" class="dgm-btn" data-dgm="reset" aria-label="fit to screen">fit</button>' +
|
|
56
|
+
'<button type="button" class="dgm-btn" data-dgm="in" aria-label="zoom in">+</button>' +
|
|
57
|
+
'<button type="button" class="dgm-btn dgm-close" data-dgm="close" aria-label="close">close ×</button>' +
|
|
58
|
+
"</div><div class=\"dgm-stage\"><div class=\"dgm-art\"></div></div>";
|
|
59
|
+
document.body.appendChild(overlay);
|
|
60
|
+
stage = overlay.querySelector(".dgm-stage");
|
|
61
|
+
art = overlay.querySelector(".dgm-art");
|
|
62
|
+
closeBtn = overlay.querySelector(".dgm-close");
|
|
63
|
+
|
|
64
|
+
overlay.addEventListener("click", (e) => {
|
|
65
|
+
const act = e.target.closest("[data-dgm]")?.dataset.dgm;
|
|
66
|
+
if (act === "close" || e.target === stage || e.target === overlay) return close();
|
|
67
|
+
if (act === "in") return zoomTo(scale * 1.3);
|
|
68
|
+
if (act === "out") return zoomTo(scale / 1.3);
|
|
69
|
+
if (act === "reset") return fit();
|
|
70
|
+
});
|
|
71
|
+
stage.addEventListener("wheel", (e) => {
|
|
72
|
+
e.preventDefault();
|
|
73
|
+
zoomTo(scale * (e.deltaY < 0 ? 1.12 : 1 / 1.12), e.clientX, e.clientY);
|
|
74
|
+
}, { passive: false });
|
|
75
|
+
stage.addEventListener("pointerdown", (e) => {
|
|
76
|
+
dragging = true; lastX = e.clientX; lastY = e.clientY;
|
|
77
|
+
stage.setPointerCapture(e.pointerId); stage.classList.add("is-grabbing");
|
|
78
|
+
});
|
|
79
|
+
stage.addEventListener("pointermove", (e) => {
|
|
80
|
+
if (!dragging) return;
|
|
81
|
+
tx += e.clientX - lastX; ty += e.clientY - lastY;
|
|
82
|
+
lastX = e.clientX; lastY = e.clientY; apply();
|
|
83
|
+
});
|
|
84
|
+
const endDrag = () => { dragging = false; stage.classList.remove("is-grabbing"); };
|
|
85
|
+
stage.addEventListener("pointerup", endDrag);
|
|
86
|
+
stage.addEventListener("pointercancel", endDrag);
|
|
87
|
+
|
|
88
|
+
document.addEventListener("keydown", (e) => {
|
|
89
|
+
if (!overlay.classList.contains("open")) return;
|
|
90
|
+
if (e.key === "Escape") { e.preventDefault(); close(); }
|
|
91
|
+
else if (e.key === "+" || e.key === "=") zoomTo(scale * 1.3);
|
|
92
|
+
else if (e.key === "-") zoomTo(scale / 1.3);
|
|
93
|
+
else if (e.key === "0") fit();
|
|
94
|
+
});
|
|
95
|
+
};
|
|
96
|
+
|
|
97
|
+
const fit = () => {
|
|
98
|
+
const node = art.firstElementChild;
|
|
99
|
+
if (!node) return;
|
|
100
|
+
// Measure the artwork at 1x, not through the current transform.
|
|
101
|
+
const prev = art.style.transform;
|
|
102
|
+
art.style.transform = "none";
|
|
103
|
+
const box = node.getBoundingClientRect();
|
|
104
|
+
art.style.transform = prev;
|
|
105
|
+
const s = stage.getBoundingClientRect();
|
|
106
|
+
scale = box.width && box.height
|
|
107
|
+
? Math.min((s.width * FIT_MARGIN) / box.width, (s.height * FIT_MARGIN) / box.height)
|
|
108
|
+
: 1;
|
|
109
|
+
tx = ty = 0;
|
|
110
|
+
apply();
|
|
111
|
+
};
|
|
112
|
+
|
|
113
|
+
const close = () => {
|
|
114
|
+
overlay.classList.remove("open");
|
|
115
|
+
document.documentElement.classList.remove("dgm-locked");
|
|
116
|
+
art.replaceChildren();
|
|
117
|
+
opener?.focus();
|
|
118
|
+
};
|
|
119
|
+
|
|
120
|
+
const open = (source) => {
|
|
121
|
+
if (!overlay) build();
|
|
122
|
+
const node = source.querySelector("svg, img, canvas");
|
|
123
|
+
if (!node) return;
|
|
124
|
+
opener = source;
|
|
125
|
+
// Measure the ORIGINAL while it is still laid out. The clone needs a definite
|
|
126
|
+
// pixel size: a mermaid svg is sized by `width="100%"` plus an inline
|
|
127
|
+
// max-width, and both resolve against a parent — dropping them to "let it
|
|
128
|
+
// fill" gives an svg with only a viewBox, which collapses to 0x0 in an
|
|
129
|
+
// auto-sized box and makes fit() scale nothing (measured exactly that).
|
|
130
|
+
const box = node.getBoundingClientRect();
|
|
131
|
+
const copy = node.cloneNode(true);
|
|
132
|
+
copy.style.maxWidth = copy.style.maxHeight = "none";
|
|
133
|
+
if (box.width && box.height) {
|
|
134
|
+
copy.setAttribute("width", box.width);
|
|
135
|
+
copy.setAttribute("height", box.height);
|
|
136
|
+
copy.style.width = `${box.width}px`;
|
|
137
|
+
copy.style.height = `${box.height}px`;
|
|
138
|
+
}
|
|
139
|
+
art.replaceChildren(copy);
|
|
140
|
+
overlay.classList.add("open");
|
|
141
|
+
document.documentElement.classList.add("dgm-locked");
|
|
142
|
+
requestAnimationFrame(fit);
|
|
143
|
+
closeBtn.focus();
|
|
144
|
+
};
|
|
145
|
+
|
|
146
|
+
for (const d of diagrams) {
|
|
147
|
+
d.classList.add("dgm-zoomable");
|
|
148
|
+
d.setAttribute("role", "button");
|
|
149
|
+
d.setAttribute("tabindex", "0");
|
|
150
|
+
d.setAttribute("aria-label", "open diagram, zoomable");
|
|
151
|
+
d.addEventListener("click", () => open(d));
|
|
152
|
+
d.addEventListener("keydown", (e) => {
|
|
153
|
+
if (e.key === "Enter" || e.key === " ") { e.preventDefault(); open(d); }
|
|
154
|
+
});
|
|
155
|
+
}
|
|
156
|
+
}
|
package/runtime/index.js
CHANGED
package/runtime/lsnav.js
CHANGED
|
@@ -28,11 +28,37 @@
|
|
|
28
28
|
*/
|
|
29
29
|
const KEY = "ls-nav";
|
|
30
30
|
|
|
31
|
+
/*
|
|
32
|
+
* The rail runs BETWEEN the chrome — it must not cover the header bar or the
|
|
33
|
+
* status footer. Their heights are a consumer's business and change with the
|
|
34
|
+
* viewport, so they are measured here and published as --ls-nav-top /
|
|
35
|
+
* --ls-nav-bottom rather than guessed at in CSS. chrome.css carries fallbacks,
|
|
36
|
+
* so a page that never runs this still lays out sensibly.
|
|
37
|
+
*
|
|
38
|
+
* Keeping `ls -l` at one fixed spot is the point: the rail's head and the tab
|
|
39
|
+
* that replaces it share --ls-nav-top, so opening and closing changes the arrow
|
|
40
|
+
* and nothing else moves.
|
|
41
|
+
*/
|
|
42
|
+
function measureChrome() {
|
|
43
|
+
const root = document.documentElement;
|
|
44
|
+
const bar = document.querySelector("header.bar");
|
|
45
|
+
const status = document.querySelector("footer.status");
|
|
46
|
+
// A hidden footer (mobile folds it into the burger) contributes nothing.
|
|
47
|
+
const visible = (el) => el && el.getBoundingClientRect().height > 0;
|
|
48
|
+
if (visible(bar)) root.style.setProperty("--ls-nav-top", `${bar.getBoundingClientRect().height}px`);
|
|
49
|
+
root.style.setProperty("--ls-nav-bottom", visible(status) ? `${status.getBoundingClientRect().height}px` : "0px");
|
|
50
|
+
}
|
|
51
|
+
|
|
31
52
|
export function initLsNav() {
|
|
32
53
|
const root = document.documentElement;
|
|
33
54
|
const toggles = document.querySelectorAll("[data-ls-nav-toggle]");
|
|
34
55
|
if (!toggles.length) return;
|
|
35
56
|
|
|
57
|
+
measureChrome();
|
|
58
|
+
addEventListener("resize", measureChrome);
|
|
59
|
+
// The bar reflows when webfonts land, which changes its height after first paint.
|
|
60
|
+
if (document.fonts?.ready) document.fonts.ready.then(measureChrome).catch(() => {});
|
|
61
|
+
|
|
36
62
|
const shown = () => root.dataset.lsNav !== "off";
|
|
37
63
|
const sync = () => {
|
|
38
64
|
for (const button of toggles) button.setAttribute("aria-expanded", String(shown()));
|
package/src/chrome.css
CHANGED
|
@@ -101,8 +101,20 @@ footer.status .dropdown summary, footer.status .anim-toggle { padding: 0.55rem 0
|
|
|
101
101
|
leaves a small labelled tab on the edge rather than nothing, so a hidden rail
|
|
102
102
|
is still discoverable by someone who did not hide it themselves.
|
|
103
103
|
|
|
104
|
+
THE HEADER AND FOOTER WIN. The rail runs BETWEEN them — it never covers the bar
|
|
105
|
+
or the status line, and both keep their full width. Daniel: "the right sidebar
|
|
106
|
+
then should not overlay footer and header. Footer and header wins."
|
|
107
|
+
That also buys the better toggle behaviour: with the rail starting below the
|
|
108
|
+
bar, `ls -l` sits at ONE fixed spot whether the rail is open or shut, so the
|
|
109
|
+
only thing that changes is which way the arrow points. The reader never has to
|
|
110
|
+
go looking for a control that moved.
|
|
111
|
+
|
|
112
|
+
--ls-nav-top/--ls-nav-bottom are measured off the real chrome by initLsNav()
|
|
113
|
+
and written onto <html>; the values here are fallbacks that keep the layout
|
|
114
|
+
sane if the runtime never arrives.
|
|
115
|
+
|
|
104
116
|
Width is one token so a consumer can widen it for longer trees. */
|
|
105
|
-
:root { --ls-nav-w: 17rem; }
|
|
117
|
+
:root { --ls-nav-w: 17rem; --ls-nav-top: 3rem; --ls-nav-bottom: 2.2rem; }
|
|
106
118
|
.ls-nav-show { display: none; }
|
|
107
119
|
|
|
108
120
|
@media (min-width: 48.0625rem) {
|
|
@@ -117,19 +129,30 @@ footer.status .dropdown summary, footer.status .anim-toggle { padding: 0.55rem 0
|
|
|
117
129
|
html { --ls-nav-inset: 0rem; }
|
|
118
130
|
html:has(.ls-nav):not([data-ls-nav="off"]) { --ls-nav-inset: var(--ls-nav-w); }
|
|
119
131
|
|
|
120
|
-
/*
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
132
|
+
/* Content shifts left to sit against the rail's edge — but the CHROME does not.
|
|
133
|
+
The header bar and the status footer pull back out to full width with a
|
|
134
|
+
negative margin, so the rail begins below one and ends above the other rather
|
|
135
|
+
than covering either. Applied to the body rather than to a `main` selector
|
|
136
|
+
because the design system cannot know a consumer's content wrapper: every
|
|
137
|
+
block child shifts, and the two that must not are named explicitly.
|
|
138
|
+
|
|
139
|
+
Deliberately NOT transitioned. Animating padding on <body> reflows the whole
|
|
124
140
|
document every frame, and the pages this ships to render tables of hundreds of
|
|
125
141
|
rows — the rail's own transform is composited and costs nothing, so the panel
|
|
126
|
-
slides while the content reflows once.
|
|
127
|
-
made the containers dashboard stutter on toggle. */
|
|
142
|
+
slides while the content reflows once. */
|
|
128
143
|
body { padding-right: var(--ls-nav-inset); }
|
|
129
|
-
|
|
130
|
-
|
|
144
|
+
/* Only the HEADER needs pulling back out. It is `position: sticky`, i.e. still
|
|
145
|
+
in flow, so the body's padding narrows it. The footer is `position: fixed`
|
|
146
|
+
with `inset-inline: 0`, which ignores body padding and is already full width
|
|
147
|
+
— giving it the same negative margin pushed it 272px PAST the viewport edge
|
|
148
|
+
(measured 1099px against an 842px viewport). Fixed and in-flow siblings look
|
|
149
|
+
alike in the markup and do not behave alike here. */
|
|
150
|
+
header.bar { margin-right: calc(-1 * var(--ls-nav-inset)); }
|
|
151
|
+
|
|
152
|
+
/* Between the chrome, never over it. */
|
|
131
153
|
.ls-nav {
|
|
132
|
-
position: fixed; top:
|
|
154
|
+
position: fixed; top: var(--ls-nav-top); right: 0; bottom: var(--ls-nav-bottom);
|
|
155
|
+
width: var(--ls-nav-w); z-index: 25;
|
|
133
156
|
display: flex; flex-direction: column;
|
|
134
157
|
background: var(--card); border-left: 1px solid var(--border);
|
|
135
158
|
}
|
|
@@ -159,13 +182,17 @@ footer.status .dropdown summary, footer.status .anim-toggle { padding: 0.55rem 0
|
|
|
159
182
|
outline: 2px solid color-mix(in srgb, var(--primary) 55%, transparent); outline-offset: -2px;
|
|
160
183
|
}
|
|
161
184
|
|
|
162
|
-
/* The tab that survives hiding
|
|
163
|
-
|
|
185
|
+
/* The tab that survives hiding — TOP RIGHT, in the header band, which is where
|
|
186
|
+
`ls -l` has always lived (it was a header dropdown before it was a rail) and
|
|
187
|
+
where the rail's own title sits when it is open. An earlier version centred it
|
|
188
|
+
vertically on the right edge: correct as a drawer handle, wrong here, because
|
|
189
|
+
it moved the one control the reader is looking for to a place navigation has
|
|
190
|
+
never been. Same corner open or closed. */
|
|
164
191
|
html[data-ls-nav="off"] .ls-nav-show {
|
|
165
192
|
display: inline-flex; align-items: center; gap: 0.4rem;
|
|
166
|
-
position: fixed; top:
|
|
193
|
+
position: fixed; top: var(--ls-nav-top); right: 0; z-index: 25;
|
|
167
194
|
background: var(--card); border: 1px solid var(--border); border-right: 0;
|
|
168
|
-
padding: 0.6rem 0.
|
|
195
|
+
border-top: 0; padding: 0.6rem 0.85rem; font-size: 0.9rem; font-weight: 700;
|
|
169
196
|
}
|
|
170
197
|
}
|
|
171
198
|
|
package/src/components.css
CHANGED
|
@@ -446,19 +446,30 @@ details.fold > summary:focus-visible {
|
|
|
446
446
|
would have added five more. */
|
|
447
447
|
.tickstrip:not(:empty) { border: 1px solid var(--border); background: var(--card); margin: 0 0 1.25rem; }
|
|
448
448
|
.ticktable { width: 100%; border-collapse: collapse; font-size: 0.82rem; }
|
|
449
|
-
.ticktable td { padding: 0.
|
|
449
|
+
.ticktable td { padding: 0.5rem 0.75rem; vertical-align: baseline; border: 0; white-space: nowrap; }
|
|
450
450
|
/* Only the last column may grow, so the four fixed columns keep the same width on every row. */
|
|
451
451
|
.ticktable td.tick-stats { width: 100%; white-space: normal; }
|
|
452
452
|
.tick { border-bottom: 1px solid color-mix(in srgb, var(--border) 55%, transparent); }
|
|
453
453
|
.tick:last-child { border-bottom: 0; }
|
|
454
|
-
/*
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
.tick
|
|
458
|
-
.
|
|
459
|
-
|
|
460
|
-
|
|
454
|
+
/* State reads THREE ways, so it survives every context: the GLYPH (two themes are near-black and
|
|
455
|
+
a printout has no colour at all), the COLOUR, and a left edge-marker that makes a bad row
|
|
456
|
+
findable by shape alone when the strip is scanned rather than read. */
|
|
457
|
+
.tick-dot {
|
|
458
|
+
font-size: 0.7em; width: 1.5rem; padding-right: 0 !important;
|
|
459
|
+
border-left: 3px solid transparent; padding-left: 0.6rem !important;
|
|
460
|
+
}
|
|
461
|
+
/* --success is per-theme and AA-measured (0.3.0). `ok` used to borrow --primary — the BRAND
|
|
462
|
+
colour — so a healthy poller and the page heading were the same ink, and "everything is fine"
|
|
463
|
+
had to be read rather than seen. */
|
|
464
|
+
.tick--ok .tick-dot { color: var(--success); border-left-color: color-mix(in srgb, var(--success) 45%, transparent); }
|
|
465
|
+
.tick--stale .tick-dot { color: var(--destructive); border-left-color: var(--destructive); }
|
|
466
|
+
.tick--never .tick-dot { color: var(--muted-foreground); border-left-color: color-mix(in srgb, var(--muted-foreground) 35%, transparent); }
|
|
467
|
+
/* The NAME is an identifier, not an accent. Making it --primary put brand colour on every row of
|
|
468
|
+
a strip whose entire job is that the exceptions stand out. */
|
|
469
|
+
.tick-name { font-weight: 700; color: var(--foreground); letter-spacing: 0.01em; }
|
|
461
470
|
.tick-last, .tick-next { font-variant-numeric: tabular-nums; color: var(--muted-foreground); }
|
|
471
|
+
/* "next 57s" is a prediction; the age beside it is an observation. It should not compete. */
|
|
472
|
+
.tick-next { opacity: 0.72; }
|
|
462
473
|
.tick--stale .tick-last { color: var(--destructive); font-weight: 700; }
|
|
463
474
|
.tick--never .tick-last { font-style: italic; }
|
|
464
475
|
/* The NUMBER is what gets scanned; its label is context. Weight and colour on the value alone
|
|
@@ -475,3 +486,48 @@ details.fold > summary:focus-visible {
|
|
|
475
486
|
.ticktable td { display: inline-block; padding: 0.1rem 0.5rem 0.1rem 0; }
|
|
476
487
|
.ticktable td.tick-stats { display: block; width: auto; }
|
|
477
488
|
}
|
|
489
|
+
|
|
490
|
+
/* ── zoomable diagrams ────────────────────────────────────────────────────────────────────────
|
|
491
|
+
An architecture diagram authored for a text column is unreadable at exactly the moment someone
|
|
492
|
+
needs it. Pair with `initDiagramZoom()`: every `.diagram` becomes a real button that opens
|
|
493
|
+
full-screen, fitted to the viewport, then zoomable and pannable.
|
|
494
|
+
|
|
495
|
+
The affordance has to be visible without hovering — this portal is read from a phone — so the
|
|
496
|
+
corner hint is always drawn, not revealed on :hover. */
|
|
497
|
+
.dgm-zoomable { position: relative; cursor: zoom-in; }
|
|
498
|
+
.dgm-zoomable::after {
|
|
499
|
+
content: "⤢"; position: absolute; top: 0.4rem; right: 0.5rem;
|
|
500
|
+
font-size: 0.85rem; line-height: 1; opacity: 0.45; pointer-events: none;
|
|
501
|
+
color: var(--muted-foreground);
|
|
502
|
+
}
|
|
503
|
+
.dgm-zoomable:hover::after, .dgm-zoomable:focus-visible::after { opacity: 1; color: var(--primary); }
|
|
504
|
+
.dgm-zoomable:focus-visible { outline: 2px solid var(--primary); outline-offset: 3px; }
|
|
505
|
+
|
|
506
|
+
html.dgm-locked, html.dgm-locked body { overflow: hidden; }
|
|
507
|
+
|
|
508
|
+
.dgm-overlay {
|
|
509
|
+
display: none; position: fixed; inset: 0; z-index: 90;
|
|
510
|
+
background: color-mix(in srgb, var(--background) 96%, transparent);
|
|
511
|
+
flex-direction: column;
|
|
512
|
+
}
|
|
513
|
+
.dgm-overlay.open { display: flex; }
|
|
514
|
+
.dgm-bar {
|
|
515
|
+
flex: none; display: flex; align-items: center; justify-content: flex-end; gap: 0.4rem;
|
|
516
|
+
padding: 0.5rem 0.75rem; border-bottom: 1px solid var(--border); background: var(--card);
|
|
517
|
+
}
|
|
518
|
+
.dgm-btn {
|
|
519
|
+
font: inherit; font-size: 0.82rem; cursor: pointer; padding: 0.3rem 0.7rem; min-height: 32px;
|
|
520
|
+
background: transparent; border: 1px solid var(--border); color: var(--foreground);
|
|
521
|
+
}
|
|
522
|
+
.dgm-btn:hover { border-color: var(--primary); color: var(--primary); }
|
|
523
|
+
.dgm-btn:focus-visible { outline: 2px solid var(--primary); outline-offset: 1px; }
|
|
524
|
+
.dgm-close { border-color: color-mix(in srgb, var(--destructive) 55%, transparent); color: var(--destructive); }
|
|
525
|
+
.dgm-close:hover { border-color: var(--destructive); color: var(--destructive); }
|
|
526
|
+
/* The stage clips; the artwork inside it is what moves. Transforming a wrapper rather than the
|
|
527
|
+
svg keeps mermaid's own geometry untouched. */
|
|
528
|
+
.dgm-stage { flex: 1; overflow: hidden; display: grid; place-items: center; cursor: grab; touch-action: none; }
|
|
529
|
+
.dgm-stage.is-grabbing { cursor: grabbing; }
|
|
530
|
+
.dgm-art { transform-origin: center center; will-change: transform; }
|
|
531
|
+
.dgm-art > svg, .dgm-art > img { display: block; max-width: none; max-height: none; }
|
|
532
|
+
|
|
533
|
+
@media print { .dgm-overlay, .dgm-zoomable::after { display: none !important; } }
|
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",
|