@danieldeusing/design 0.3.0 → 0.3.2
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 +123 -44
- 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 +32 -0
- package/src/chrome.css +55 -34
- package/src/components.css +67 -9
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
/*! danieldeusing-design v0.3.
|
|
1
|
+
/*! danieldeusing-design v0.3.2 | MIT | https://github.com/danieldeusing/danieldeusing-design */
|
|
2
2
|
/* ===== reset.css ===== */
|
|
3
3
|
/*
|
|
4
4
|
* danieldeusing-design — minimal reset.
|
|
@@ -738,21 +738,34 @@ details.fold > summary:focus-visible {
|
|
|
738
738
|
Colour comes only from --primary and --destructive, both per-theme. Hardcoded accents miss AA
|
|
739
739
|
on at least one theme (measured 2026-08-04), so a strip inventing its own greens and ambers
|
|
740
740
|
would have added five more. */
|
|
741
|
-
|
|
741
|
+
/* Breathing room above: the strip follows the page's lede, and butted straight up against a
|
|
742
|
+
paragraph it read as part of the prose rather than as instrumentation. */
|
|
743
|
+
.tickstrip:not(:empty) { border: 1px solid var(--border); background: var(--card); margin: 1.6rem 0 1.5rem; }
|
|
742
744
|
.ticktable { width: 100%; border-collapse: collapse; font-size: 0.82rem; }
|
|
743
|
-
.ticktable td { padding: 0.
|
|
745
|
+
.ticktable td { padding: 0.5rem 0.75rem; vertical-align: baseline; border: 0; white-space: nowrap; }
|
|
744
746
|
/* Only the last column may grow, so the four fixed columns keep the same width on every row. */
|
|
745
747
|
.ticktable td.tick-stats { width: 100%; white-space: normal; }
|
|
746
748
|
.tick { border-bottom: 1px solid color-mix(in srgb, var(--border) 55%, transparent); }
|
|
747
749
|
.tick:last-child { border-bottom: 0; }
|
|
748
|
-
/*
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
.tick
|
|
752
|
-
.
|
|
753
|
-
|
|
754
|
-
|
|
750
|
+
/* State reads THREE ways, so it survives every context: the GLYPH (two themes are near-black and
|
|
751
|
+
a printout has no colour at all), the COLOUR, and a left edge-marker that makes a bad row
|
|
752
|
+
findable by shape alone when the strip is scanned rather than read. */
|
|
753
|
+
.tick-dot {
|
|
754
|
+
font-size: 0.7em; width: 1.5rem; padding-right: 0 !important;
|
|
755
|
+
border-left: 3px solid transparent; padding-left: 0.6rem !important;
|
|
756
|
+
}
|
|
757
|
+
/* --success is per-theme and AA-measured (0.3.0). `ok` used to borrow --primary — the BRAND
|
|
758
|
+
colour — so a healthy poller and the page heading were the same ink, and "everything is fine"
|
|
759
|
+
had to be read rather than seen. */
|
|
760
|
+
.tick--ok .tick-dot { color: var(--success); border-left-color: color-mix(in srgb, var(--success) 45%, transparent); }
|
|
761
|
+
.tick--stale .tick-dot { color: var(--destructive); border-left-color: var(--destructive); }
|
|
762
|
+
.tick--never .tick-dot { color: var(--muted-foreground); border-left-color: color-mix(in srgb, var(--muted-foreground) 35%, transparent); }
|
|
763
|
+
/* The NAME is an identifier, not an accent. Making it --primary put brand colour on every row of
|
|
764
|
+
a strip whose entire job is that the exceptions stand out. */
|
|
765
|
+
.tick-name { font-weight: 700; color: var(--foreground); letter-spacing: 0.01em; }
|
|
755
766
|
.tick-last, .tick-next { font-variant-numeric: tabular-nums; color: var(--muted-foreground); }
|
|
767
|
+
/* "next 57s" is a prediction; the age beside it is an observation. It should not compete. */
|
|
768
|
+
.tick-next { opacity: 0.72; }
|
|
756
769
|
.tick--stale .tick-last { color: var(--destructive); font-weight: 700; }
|
|
757
770
|
.tick--never .tick-last { font-style: italic; }
|
|
758
771
|
/* The NUMBER is what gets scanned; its label is context. Weight and colour on the value alone
|
|
@@ -770,6 +783,51 @@ details.fold > summary:focus-visible {
|
|
|
770
783
|
.ticktable td.tick-stats { display: block; width: auto; }
|
|
771
784
|
}
|
|
772
785
|
|
|
786
|
+
/* ── zoomable diagrams ────────────────────────────────────────────────────────────────────────
|
|
787
|
+
An architecture diagram authored for a text column is unreadable at exactly the moment someone
|
|
788
|
+
needs it. Pair with `initDiagramZoom()`: every `.diagram` becomes a real button that opens
|
|
789
|
+
full-screen, fitted to the viewport, then zoomable and pannable.
|
|
790
|
+
|
|
791
|
+
The affordance has to be visible without hovering — this portal is read from a phone — so the
|
|
792
|
+
corner hint is always drawn, not revealed on :hover. */
|
|
793
|
+
.dgm-zoomable { position: relative; cursor: zoom-in; }
|
|
794
|
+
.dgm-zoomable::after {
|
|
795
|
+
content: "⤢"; position: absolute; top: 0.4rem; right: 0.5rem;
|
|
796
|
+
font-size: 0.85rem; line-height: 1; opacity: 0.45; pointer-events: none;
|
|
797
|
+
color: var(--muted-foreground);
|
|
798
|
+
}
|
|
799
|
+
.dgm-zoomable:hover::after, .dgm-zoomable:focus-visible::after { opacity: 1; color: var(--primary); }
|
|
800
|
+
.dgm-zoomable:focus-visible { outline: 2px solid var(--primary); outline-offset: 3px; }
|
|
801
|
+
|
|
802
|
+
html.dgm-locked, html.dgm-locked body { overflow: hidden; }
|
|
803
|
+
|
|
804
|
+
.dgm-overlay {
|
|
805
|
+
display: none; position: fixed; inset: 0; z-index: 90;
|
|
806
|
+
background: color-mix(in srgb, var(--background) 96%, transparent);
|
|
807
|
+
flex-direction: column;
|
|
808
|
+
}
|
|
809
|
+
.dgm-overlay.open { display: flex; }
|
|
810
|
+
.dgm-bar {
|
|
811
|
+
flex: none; display: flex; align-items: center; justify-content: flex-end; gap: 0.4rem;
|
|
812
|
+
padding: 0.5rem 0.75rem; border-bottom: 1px solid var(--border); background: var(--card);
|
|
813
|
+
}
|
|
814
|
+
.dgm-btn {
|
|
815
|
+
font: inherit; font-size: 0.82rem; cursor: pointer; padding: 0.3rem 0.7rem; min-height: 32px;
|
|
816
|
+
background: transparent; border: 1px solid var(--border); color: var(--foreground);
|
|
817
|
+
}
|
|
818
|
+
.dgm-btn:hover { border-color: var(--primary); color: var(--primary); }
|
|
819
|
+
.dgm-btn:focus-visible { outline: 2px solid var(--primary); outline-offset: 1px; }
|
|
820
|
+
.dgm-close { border-color: color-mix(in srgb, var(--destructive) 55%, transparent); color: var(--destructive); }
|
|
821
|
+
.dgm-close:hover { border-color: var(--destructive); color: var(--destructive); }
|
|
822
|
+
/* The stage clips; the artwork inside it is what moves. Transforming a wrapper rather than the
|
|
823
|
+
svg keeps mermaid's own geometry untouched. */
|
|
824
|
+
.dgm-stage { flex: 1; overflow: hidden; display: grid; place-items: center; cursor: grab; touch-action: none; }
|
|
825
|
+
.dgm-stage.is-grabbing { cursor: grabbing; }
|
|
826
|
+
.dgm-art { transform-origin: center center; will-change: transform; }
|
|
827
|
+
.dgm-art > svg, .dgm-art > img { display: block; max-width: none; max-height: none; }
|
|
828
|
+
|
|
829
|
+
@media print { .dgm-overlay, .dgm-zoomable::after { display: none !important; } }
|
|
830
|
+
|
|
773
831
|
/* ===== chrome.css ===== */
|
|
774
832
|
/*
|
|
775
833
|
* chrome.css — the app chrome kit: sticky header bar, fixed status footer,
|
|
@@ -784,14 +842,14 @@ details.fold > summary:focus-visible {
|
|
|
784
842
|
* <a href="/" class="brand"><span class="glow">name</span><span class="cursor-block"></span></a>
|
|
785
843
|
* <button class="nav-burger" data-nav-toggle aria-controls="site-nav" aria-expanded="false">…☰ svg…</button>
|
|
786
844
|
* <nav aria-label="site" id="site-nav" class="site-nav">
|
|
845
|
+
* <!-- `ls -l` sits in the BAR; the arrow is generated by CSS from the state -->
|
|
846
|
+
* <div class="ls-nav-head">
|
|
847
|
+
* <span class="ls-nav-title">ls -l</span>
|
|
848
|
+
* <button class="ls-nav-toggle" data-ls-nav-toggle aria-controls="nav" aria-expanded="true"></button>
|
|
849
|
+
* </div>
|
|
787
850
|
* <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
851
|
* <ul class="ls-panel">….ls-row items…</ul>
|
|
793
852
|
* </div>
|
|
794
|
-
* <button class="ls-nav-show" data-ls-nav-toggle aria-controls="nav">« ls -l</button>
|
|
795
853
|
* <ul class="mobile-nav">…same links as .mobile-item ls-rows…</ul>
|
|
796
854
|
* <div class="mobile-footer">…footer controls folded in…</div>
|
|
797
855
|
* </nav>
|
|
@@ -874,9 +932,20 @@ footer.status .dropdown summary, footer.status .anim-toggle { padding: 0.55rem 0
|
|
|
874
932
|
leaves a small labelled tab on the edge rather than nothing, so a hidden rail
|
|
875
933
|
is still discoverable by someone who did not hide it themselves.
|
|
876
934
|
|
|
935
|
+
THE HEADER AND FOOTER WIN. The rail runs BETWEEN them — it never covers the bar
|
|
936
|
+
or the status line, and both keep their full width. Daniel: "the right sidebar
|
|
937
|
+
then should not overlay footer and header. Footer and header wins."
|
|
938
|
+
That also buys the better toggle behaviour: with the rail starting below the
|
|
939
|
+
bar, `ls -l` sits at ONE fixed spot whether the rail is open or shut, so the
|
|
940
|
+
only thing that changes is which way the arrow points. The reader never has to
|
|
941
|
+
go looking for a control that moved.
|
|
942
|
+
|
|
943
|
+
--ls-nav-top/--ls-nav-bottom are measured off the real chrome by initLsNav()
|
|
944
|
+
and written onto <html>; the values here are fallbacks that keep the layout
|
|
945
|
+
sane if the runtime never arrives.
|
|
946
|
+
|
|
877
947
|
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; }
|
|
948
|
+
:root { --ls-nav-w: 17rem; --ls-nav-top: 3rem; --ls-nav-bottom: 2.2rem; }
|
|
880
949
|
|
|
881
950
|
@media (min-width: 48.0625rem) {
|
|
882
951
|
/* Room is reserved ONLY when a rail is actually in the page, and the default is
|
|
@@ -890,29 +959,44 @@ footer.status .dropdown summary, footer.status .anim-toggle { padding: 0.55rem 0
|
|
|
890
959
|
html { --ls-nav-inset: 0rem; }
|
|
891
960
|
html:has(.ls-nav):not([data-ls-nav="off"]) { --ls-nav-inset: var(--ls-nav-w); }
|
|
892
961
|
|
|
893
|
-
/*
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
962
|
+
/* Content shifts left to sit against the rail's edge — but the CHROME does not.
|
|
963
|
+
The header bar and the status footer pull back out to full width with a
|
|
964
|
+
negative margin, so the rail begins below one and ends above the other rather
|
|
965
|
+
than covering either. Applied to the body rather than to a `main` selector
|
|
966
|
+
because the design system cannot know a consumer's content wrapper: every
|
|
967
|
+
block child shifts, and the two that must not are named explicitly.
|
|
968
|
+
|
|
969
|
+
Deliberately NOT transitioned. Animating padding on <body> reflows the whole
|
|
897
970
|
document every frame, and the pages this ships to render tables of hundreds of
|
|
898
971
|
rows — the rail's own transform is composited and costs nothing, so the panel
|
|
899
|
-
slides while the content reflows once.
|
|
900
|
-
made the containers dashboard stutter on toggle. */
|
|
972
|
+
slides while the content reflows once. */
|
|
901
973
|
body { padding-right: var(--ls-nav-inset); }
|
|
902
|
-
|
|
903
|
-
|
|
974
|
+
/* Only the HEADER needs pulling back out. It is `position: sticky`, i.e. still
|
|
975
|
+
in flow, so the body's padding narrows it. The footer is `position: fixed`
|
|
976
|
+
with `inset-inline: 0`, which ignores body padding and is already full width
|
|
977
|
+
— giving it the same negative margin pushed it 272px PAST the viewport edge
|
|
978
|
+
(measured 1099px against an 842px viewport). Fixed and in-flow siblings look
|
|
979
|
+
alike in the markup and do not behave alike here. */
|
|
980
|
+
header.bar { margin-right: calc(-1 * var(--ls-nav-inset)); }
|
|
981
|
+
|
|
982
|
+
/* Between the chrome, never over it. */
|
|
904
983
|
.ls-nav {
|
|
905
|
-
position: fixed; top:
|
|
906
|
-
|
|
984
|
+
position: fixed; top: var(--ls-nav-top); right: 0; bottom: var(--ls-nav-bottom);
|
|
985
|
+
width: var(--ls-nav-w); z-index: 25;
|
|
986
|
+
display: flex; flex-direction: column; overflow: hidden;
|
|
907
987
|
background: var(--card); border-left: 1px solid var(--border);
|
|
908
988
|
}
|
|
909
|
-
html:not(.anim-off) .ls-nav
|
|
910
|
-
html:not(.anim-off) .ls-nav-show { transition: transform 0.18s ease; }
|
|
989
|
+
html:not(.anim-off) .ls-nav { transition: transform 0.18s ease; }
|
|
911
990
|
html[data-ls-nav="off"] .ls-nav { transform: translateX(100%); }
|
|
912
991
|
|
|
992
|
+
/* `ls -l` LIVES IN THE HEADER BAR, not inside the rail. Daniel: "the ls-l should
|
|
993
|
+
be in the header". That is also where it was before any of this (the dropdown
|
|
994
|
+
summary sat in the bar), and it collapses the two toggles into ONE control:
|
|
995
|
+
the button never moves, the panel below it opens and closes, and the only
|
|
996
|
+
thing that changes is which way the arrow points. */
|
|
913
997
|
.ls-nav-head {
|
|
914
|
-
|
|
915
|
-
|
|
998
|
+
display: inline-flex; align-items: center; gap: 0.5rem;
|
|
999
|
+
margin-left: 0.75rem;
|
|
916
1000
|
}
|
|
917
1001
|
.ls-nav-title { font-weight: 700; font-size: 0.9rem; }
|
|
918
1002
|
|
|
@@ -923,23 +1007,19 @@ footer.status .dropdown summary, footer.status .anim-toggle { padding: 0.55rem 0
|
|
|
923
1007
|
list-style: none; margin: 0; padding: 0.5rem 0 1rem;
|
|
924
1008
|
}
|
|
925
1009
|
|
|
926
|
-
|
|
1010
|
+
/* ONE control, and it never moves. The arrow is generated, so open/closed is a
|
|
1011
|
+
pure CSS state change: » pushes the panel away, « pulls it back. */
|
|
1012
|
+
.ls-nav-toggle {
|
|
927
1013
|
background: none; border: 0; font: inherit; cursor: pointer; line-height: 1;
|
|
928
1014
|
color: var(--muted-foreground); padding: 0.4rem 0.55rem;
|
|
929
1015
|
}
|
|
930
|
-
.ls-nav-
|
|
931
|
-
|
|
1016
|
+
.ls-nav-toggle::after { content: "\00BB"; }
|
|
1017
|
+
html[data-ls-nav="off"] .ls-nav-toggle::after { content: "\00AB"; }
|
|
1018
|
+
.ls-nav-toggle:hover { color: var(--primary); }
|
|
1019
|
+
.ls-nav-toggle:focus-visible {
|
|
932
1020
|
outline: 2px solid color-mix(in srgb, var(--primary) 55%, transparent); outline-offset: -2px;
|
|
933
1021
|
}
|
|
934
1022
|
|
|
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
1023
|
}
|
|
944
1024
|
|
|
945
1025
|
/* ── burger + mobile menu ── */
|
|
@@ -963,8 +1043,7 @@ footer.status .dropdown summary, footer.status .anim-toggle { padding: 0.55rem 0
|
|
|
963
1043
|
.site-nav.open { display: block; }
|
|
964
1044
|
/* The desktop rail is a desktop idea: a fixed 17rem column would eat a phone.
|
|
965
1045
|
Below the breakpoint the burger owns navigation and the flat list replaces it. */
|
|
966
|
-
.site-nav > .ls-nav,
|
|
967
|
-
|
|
1046
|
+
.site-nav > .ls-nav,
|
|
968
1047
|
.mobile-nav { display: flex; flex-direction: column; }
|
|
969
1048
|
.mobile-nav .mobile-item {
|
|
970
1049
|
display: flex; align-items: baseline; gap: 0.9rem; padding: 0.65rem 0;
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
/*! danieldeusing-design v0.3.
|
|
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}}
|
|
1
|
+
/*! danieldeusing-design v0.3.2 | 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: 1.6rem 0 1.5rem}.ticktable{width: 100%;border-collapse: collapse;font-size: 0.82rem}.ticktable td{padding: 0.5rem 0.75rem;vertical-align: baseline;border: 0;white-space: nowrap}.ticktable td.tick-stats{width: 100%;white-space: normal}.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}@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;overflow: hidden;background: var(--card);border-left: 1px solid var(--border)}html:not(.anim-off) .ls-nav{transition: transform 0.18s ease}html[data-ls-nav="off"] .ls-nav{transform: translateX(100%)}.ls-nav-head{display: inline-flex;align-items: center;gap: 0.5rem;margin-left: 0.75rem}.ls-nav-title{font-weight: 700;font-size: 0.9rem}.ls-nav .ls-panel{flex: 1;min-height: 0;overflow-y: auto;list-style: none;margin: 0;padding: 0.5rem 0 1rem}.ls-nav-toggle{background: none;border: 0;font: inherit;cursor: pointer;line-height: 1;color: var(--muted-foreground);padding: 0.4rem 0.55rem}.ls-nav-toggle::after{content: "\00BB"}html[data-ls-nav="off"] .ls-nav-toggle::after{content: "\00AB"}.ls-nav-toggle:hover{color: var(--primary)}.ls-nav-toggle:focus-visible{outline: 2px solid color-mix(in srgb,var(--primary) 55%,transparent);outline-offset: -2px}}.nav-burger{display: none;background: none;border: 0;color: var(--muted-foreground);cursor: pointer;padding: 0.55rem;margin: -0.3rem -0.3rem -0.3rem 0}.nav-burger:hover{color: var(--primary)}.mobile-nav{display: none;list-style: none;margin: 0;padding: 0}.mobile-footer{display: none}@media (max-width: 48rem){.nav-burger{display: inline-flex;align-items: center}.site-nav{display: none;position: absolute;top: 100%;left: 0;right: 0;z-index: 40;background: var(--card);border-bottom: 1px solid var(--border);padding: 0.4rem 1.25rem 0.9rem;max-height: calc(100vh - 6rem);overflow-y: auto}.site-nav.open{display: block}.site-nav>.ls-nav,.mobile-nav{display: flex;flex-direction: column}.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.
|
|
3
|
+
"version": "0.3.2",
|
|
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,43 @@
|
|
|
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
|
+
// offsetHeight, NOT getBoundingClientRect(). Consumers may set `zoom` on <html>
|
|
47
|
+
// (cockpit scales the whole layout to the viewport), and getBoundingClientRect
|
|
48
|
+
// reports VISUAL pixels — already multiplied by the zoom. Writing that number
|
|
49
|
+
// back as a CSS length zooms it a second time, which left the rail sitting a
|
|
50
|
+
// few px below the bar with a visible seam. offsetHeight is layout px and is
|
|
51
|
+
// the same unit the CSS will be interpreted in.
|
|
52
|
+
const h = (el) => (el ? el.offsetHeight : 0);
|
|
53
|
+
if (h(bar)) root.style.setProperty("--ls-nav-top", `${h(bar)}px`);
|
|
54
|
+
// A hidden footer (mobile folds it into the burger) reserves nothing.
|
|
55
|
+
root.style.setProperty("--ls-nav-bottom", `${h(status)}px`);
|
|
56
|
+
}
|
|
57
|
+
|
|
31
58
|
export function initLsNav() {
|
|
32
59
|
const root = document.documentElement;
|
|
33
60
|
const toggles = document.querySelectorAll("[data-ls-nav-toggle]");
|
|
34
61
|
if (!toggles.length) return;
|
|
35
62
|
|
|
63
|
+
measureChrome();
|
|
64
|
+
addEventListener("resize", measureChrome);
|
|
65
|
+
// The bar reflows when webfonts land, which changes its height after first paint.
|
|
66
|
+
if (document.fonts?.ready) document.fonts.ready.then(measureChrome).catch(() => {});
|
|
67
|
+
|
|
36
68
|
const shown = () => root.dataset.lsNav !== "off";
|
|
37
69
|
const sync = () => {
|
|
38
70
|
for (const button of toggles) button.setAttribute("aria-expanded", String(shown()));
|
package/src/chrome.css
CHANGED
|
@@ -11,14 +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
|
+
* <!-- `ls -l` sits in the BAR; the arrow is generated by CSS from the state -->
|
|
15
|
+
* <div class="ls-nav-head">
|
|
16
|
+
* <span class="ls-nav-title">ls -l</span>
|
|
17
|
+
* <button class="ls-nav-toggle" data-ls-nav-toggle aria-controls="nav" aria-expanded="true"></button>
|
|
18
|
+
* </div>
|
|
14
19
|
* <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
20
|
* <ul class="ls-panel">….ls-row items…</ul>
|
|
20
21
|
* </div>
|
|
21
|
-
* <button class="ls-nav-show" data-ls-nav-toggle aria-controls="nav">« ls -l</button>
|
|
22
22
|
* <ul class="mobile-nav">…same links as .mobile-item ls-rows…</ul>
|
|
23
23
|
* <div class="mobile-footer">…footer controls folded in…</div>
|
|
24
24
|
* </nav>
|
|
@@ -101,9 +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; }
|
|
106
|
-
.ls-nav-show { display: none; }
|
|
117
|
+
:root { --ls-nav-w: 17rem; --ls-nav-top: 3rem; --ls-nav-bottom: 2.2rem; }
|
|
107
118
|
|
|
108
119
|
@media (min-width: 48.0625rem) {
|
|
109
120
|
/* Room is reserved ONLY when a rail is actually in the page, and the default is
|
|
@@ -117,29 +128,44 @@ footer.status .dropdown summary, footer.status .anim-toggle { padding: 0.55rem 0
|
|
|
117
128
|
html { --ls-nav-inset: 0rem; }
|
|
118
129
|
html:has(.ls-nav):not([data-ls-nav="off"]) { --ls-nav-inset: var(--ls-nav-w); }
|
|
119
130
|
|
|
120
|
-
/*
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
131
|
+
/* Content shifts left to sit against the rail's edge — but the CHROME does not.
|
|
132
|
+
The header bar and the status footer pull back out to full width with a
|
|
133
|
+
negative margin, so the rail begins below one and ends above the other rather
|
|
134
|
+
than covering either. Applied to the body rather than to a `main` selector
|
|
135
|
+
because the design system cannot know a consumer's content wrapper: every
|
|
136
|
+
block child shifts, and the two that must not are named explicitly.
|
|
137
|
+
|
|
138
|
+
Deliberately NOT transitioned. Animating padding on <body> reflows the whole
|
|
124
139
|
document every frame, and the pages this ships to render tables of hundreds of
|
|
125
140
|
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. */
|
|
141
|
+
slides while the content reflows once. */
|
|
128
142
|
body { padding-right: var(--ls-nav-inset); }
|
|
129
|
-
|
|
130
|
-
|
|
143
|
+
/* Only the HEADER needs pulling back out. It is `position: sticky`, i.e. still
|
|
144
|
+
in flow, so the body's padding narrows it. The footer is `position: fixed`
|
|
145
|
+
with `inset-inline: 0`, which ignores body padding and is already full width
|
|
146
|
+
— giving it the same negative margin pushed it 272px PAST the viewport edge
|
|
147
|
+
(measured 1099px against an 842px viewport). Fixed and in-flow siblings look
|
|
148
|
+
alike in the markup and do not behave alike here. */
|
|
149
|
+
header.bar { margin-right: calc(-1 * var(--ls-nav-inset)); }
|
|
150
|
+
|
|
151
|
+
/* Between the chrome, never over it. */
|
|
131
152
|
.ls-nav {
|
|
132
|
-
position: fixed; top:
|
|
133
|
-
|
|
153
|
+
position: fixed; top: var(--ls-nav-top); right: 0; bottom: var(--ls-nav-bottom);
|
|
154
|
+
width: var(--ls-nav-w); z-index: 25;
|
|
155
|
+
display: flex; flex-direction: column; overflow: hidden;
|
|
134
156
|
background: var(--card); border-left: 1px solid var(--border);
|
|
135
157
|
}
|
|
136
|
-
html:not(.anim-off) .ls-nav
|
|
137
|
-
html:not(.anim-off) .ls-nav-show { transition: transform 0.18s ease; }
|
|
158
|
+
html:not(.anim-off) .ls-nav { transition: transform 0.18s ease; }
|
|
138
159
|
html[data-ls-nav="off"] .ls-nav { transform: translateX(100%); }
|
|
139
160
|
|
|
161
|
+
/* `ls -l` LIVES IN THE HEADER BAR, not inside the rail. Daniel: "the ls-l should
|
|
162
|
+
be in the header". That is also where it was before any of this (the dropdown
|
|
163
|
+
summary sat in the bar), and it collapses the two toggles into ONE control:
|
|
164
|
+
the button never moves, the panel below it opens and closes, and the only
|
|
165
|
+
thing that changes is which way the arrow points. */
|
|
140
166
|
.ls-nav-head {
|
|
141
|
-
|
|
142
|
-
|
|
167
|
+
display: inline-flex; align-items: center; gap: 0.5rem;
|
|
168
|
+
margin-left: 0.75rem;
|
|
143
169
|
}
|
|
144
170
|
.ls-nav-title { font-weight: 700; font-size: 0.9rem; }
|
|
145
171
|
|
|
@@ -150,23 +176,19 @@ footer.status .dropdown summary, footer.status .anim-toggle { padding: 0.55rem 0
|
|
|
150
176
|
list-style: none; margin: 0; padding: 0.5rem 0 1rem;
|
|
151
177
|
}
|
|
152
178
|
|
|
153
|
-
|
|
179
|
+
/* ONE control, and it never moves. The arrow is generated, so open/closed is a
|
|
180
|
+
pure CSS state change: » pushes the panel away, « pulls it back. */
|
|
181
|
+
.ls-nav-toggle {
|
|
154
182
|
background: none; border: 0; font: inherit; cursor: pointer; line-height: 1;
|
|
155
183
|
color: var(--muted-foreground); padding: 0.4rem 0.55rem;
|
|
156
184
|
}
|
|
157
|
-
.ls-nav-
|
|
158
|
-
|
|
185
|
+
.ls-nav-toggle::after { content: "\00BB"; }
|
|
186
|
+
html[data-ls-nav="off"] .ls-nav-toggle::after { content: "\00AB"; }
|
|
187
|
+
.ls-nav-toggle:hover { color: var(--primary); }
|
|
188
|
+
.ls-nav-toggle:focus-visible {
|
|
159
189
|
outline: 2px solid color-mix(in srgb, var(--primary) 55%, transparent); outline-offset: -2px;
|
|
160
190
|
}
|
|
161
191
|
|
|
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
192
|
}
|
|
171
193
|
|
|
172
194
|
/* ── burger + mobile menu ── */
|
|
@@ -190,8 +212,7 @@ footer.status .dropdown summary, footer.status .anim-toggle { padding: 0.55rem 0
|
|
|
190
212
|
.site-nav.open { display: block; }
|
|
191
213
|
/* The desktop rail is a desktop idea: a fixed 17rem column would eat a phone.
|
|
192
214
|
Below the breakpoint the burger owns navigation and the flat list replaces it. */
|
|
193
|
-
.site-nav > .ls-nav,
|
|
194
|
-
|
|
215
|
+
.site-nav > .ls-nav,
|
|
195
216
|
.mobile-nav { display: flex; flex-direction: column; }
|
|
196
217
|
.mobile-nav .mobile-item {
|
|
197
218
|
display: flex; align-items: baseline; gap: 0.9rem; padding: 0.65rem 0;
|
package/src/components.css
CHANGED
|
@@ -444,21 +444,34 @@ details.fold > summary:focus-visible {
|
|
|
444
444
|
Colour comes only from --primary and --destructive, both per-theme. Hardcoded accents miss AA
|
|
445
445
|
on at least one theme (measured 2026-08-04), so a strip inventing its own greens and ambers
|
|
446
446
|
would have added five more. */
|
|
447
|
-
|
|
447
|
+
/* Breathing room above: the strip follows the page's lede, and butted straight up against a
|
|
448
|
+
paragraph it read as part of the prose rather than as instrumentation. */
|
|
449
|
+
.tickstrip:not(:empty) { border: 1px solid var(--border); background: var(--card); margin: 1.6rem 0 1.5rem; }
|
|
448
450
|
.ticktable { width: 100%; border-collapse: collapse; font-size: 0.82rem; }
|
|
449
|
-
.ticktable td { padding: 0.
|
|
451
|
+
.ticktable td { padding: 0.5rem 0.75rem; vertical-align: baseline; border: 0; white-space: nowrap; }
|
|
450
452
|
/* Only the last column may grow, so the four fixed columns keep the same width on every row. */
|
|
451
453
|
.ticktable td.tick-stats { width: 100%; white-space: normal; }
|
|
452
454
|
.tick { border-bottom: 1px solid color-mix(in srgb, var(--border) 55%, transparent); }
|
|
453
455
|
.tick:last-child { border-bottom: 0; }
|
|
454
|
-
/*
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
.tick
|
|
458
|
-
.
|
|
459
|
-
|
|
460
|
-
|
|
456
|
+
/* State reads THREE ways, so it survives every context: the GLYPH (two themes are near-black and
|
|
457
|
+
a printout has no colour at all), the COLOUR, and a left edge-marker that makes a bad row
|
|
458
|
+
findable by shape alone when the strip is scanned rather than read. */
|
|
459
|
+
.tick-dot {
|
|
460
|
+
font-size: 0.7em; width: 1.5rem; padding-right: 0 !important;
|
|
461
|
+
border-left: 3px solid transparent; padding-left: 0.6rem !important;
|
|
462
|
+
}
|
|
463
|
+
/* --success is per-theme and AA-measured (0.3.0). `ok` used to borrow --primary — the BRAND
|
|
464
|
+
colour — so a healthy poller and the page heading were the same ink, and "everything is fine"
|
|
465
|
+
had to be read rather than seen. */
|
|
466
|
+
.tick--ok .tick-dot { color: var(--success); border-left-color: color-mix(in srgb, var(--success) 45%, transparent); }
|
|
467
|
+
.tick--stale .tick-dot { color: var(--destructive); border-left-color: var(--destructive); }
|
|
468
|
+
.tick--never .tick-dot { color: var(--muted-foreground); border-left-color: color-mix(in srgb, var(--muted-foreground) 35%, transparent); }
|
|
469
|
+
/* The NAME is an identifier, not an accent. Making it --primary put brand colour on every row of
|
|
470
|
+
a strip whose entire job is that the exceptions stand out. */
|
|
471
|
+
.tick-name { font-weight: 700; color: var(--foreground); letter-spacing: 0.01em; }
|
|
461
472
|
.tick-last, .tick-next { font-variant-numeric: tabular-nums; color: var(--muted-foreground); }
|
|
473
|
+
/* "next 57s" is a prediction; the age beside it is an observation. It should not compete. */
|
|
474
|
+
.tick-next { opacity: 0.72; }
|
|
462
475
|
.tick--stale .tick-last { color: var(--destructive); font-weight: 700; }
|
|
463
476
|
.tick--never .tick-last { font-style: italic; }
|
|
464
477
|
/* The NUMBER is what gets scanned; its label is context. Weight and colour on the value alone
|
|
@@ -475,3 +488,48 @@ details.fold > summary:focus-visible {
|
|
|
475
488
|
.ticktable td { display: inline-block; padding: 0.1rem 0.5rem 0.1rem 0; }
|
|
476
489
|
.ticktable td.tick-stats { display: block; width: auto; }
|
|
477
490
|
}
|
|
491
|
+
|
|
492
|
+
/* ── zoomable diagrams ────────────────────────────────────────────────────────────────────────
|
|
493
|
+
An architecture diagram authored for a text column is unreadable at exactly the moment someone
|
|
494
|
+
needs it. Pair with `initDiagramZoom()`: every `.diagram` becomes a real button that opens
|
|
495
|
+
full-screen, fitted to the viewport, then zoomable and pannable.
|
|
496
|
+
|
|
497
|
+
The affordance has to be visible without hovering — this portal is read from a phone — so the
|
|
498
|
+
corner hint is always drawn, not revealed on :hover. */
|
|
499
|
+
.dgm-zoomable { position: relative; cursor: zoom-in; }
|
|
500
|
+
.dgm-zoomable::after {
|
|
501
|
+
content: "⤢"; position: absolute; top: 0.4rem; right: 0.5rem;
|
|
502
|
+
font-size: 0.85rem; line-height: 1; opacity: 0.45; pointer-events: none;
|
|
503
|
+
color: var(--muted-foreground);
|
|
504
|
+
}
|
|
505
|
+
.dgm-zoomable:hover::after, .dgm-zoomable:focus-visible::after { opacity: 1; color: var(--primary); }
|
|
506
|
+
.dgm-zoomable:focus-visible { outline: 2px solid var(--primary); outline-offset: 3px; }
|
|
507
|
+
|
|
508
|
+
html.dgm-locked, html.dgm-locked body { overflow: hidden; }
|
|
509
|
+
|
|
510
|
+
.dgm-overlay {
|
|
511
|
+
display: none; position: fixed; inset: 0; z-index: 90;
|
|
512
|
+
background: color-mix(in srgb, var(--background) 96%, transparent);
|
|
513
|
+
flex-direction: column;
|
|
514
|
+
}
|
|
515
|
+
.dgm-overlay.open { display: flex; }
|
|
516
|
+
.dgm-bar {
|
|
517
|
+
flex: none; display: flex; align-items: center; justify-content: flex-end; gap: 0.4rem;
|
|
518
|
+
padding: 0.5rem 0.75rem; border-bottom: 1px solid var(--border); background: var(--card);
|
|
519
|
+
}
|
|
520
|
+
.dgm-btn {
|
|
521
|
+
font: inherit; font-size: 0.82rem; cursor: pointer; padding: 0.3rem 0.7rem; min-height: 32px;
|
|
522
|
+
background: transparent; border: 1px solid var(--border); color: var(--foreground);
|
|
523
|
+
}
|
|
524
|
+
.dgm-btn:hover { border-color: var(--primary); color: var(--primary); }
|
|
525
|
+
.dgm-btn:focus-visible { outline: 2px solid var(--primary); outline-offset: 1px; }
|
|
526
|
+
.dgm-close { border-color: color-mix(in srgb, var(--destructive) 55%, transparent); color: var(--destructive); }
|
|
527
|
+
.dgm-close:hover { border-color: var(--destructive); color: var(--destructive); }
|
|
528
|
+
/* The stage clips; the artwork inside it is what moves. Transforming a wrapper rather than the
|
|
529
|
+
svg keeps mermaid's own geometry untouched. */
|
|
530
|
+
.dgm-stage { flex: 1; overflow: hidden; display: grid; place-items: center; cursor: grab; touch-action: none; }
|
|
531
|
+
.dgm-stage.is-grabbing { cursor: grabbing; }
|
|
532
|
+
.dgm-art { transform-origin: center center; will-change: transform; }
|
|
533
|
+
.dgm-art > svg, .dgm-art > img { display: block; max-width: none; max-height: none; }
|
|
534
|
+
|
|
535
|
+
@media print { .dgm-overlay, .dgm-zoomable::after { display: none !important; } }
|