@danieldeusing/design 0.3.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 +106 -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
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
/*! danieldeusing-design v0.3.
|
|
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.
|
|
@@ -740,19 +740,30 @@ details.fold > summary:focus-visible {
|
|
|
740
740
|
would have added five more. */
|
|
741
741
|
.tickstrip:not(:empty) { border: 1px solid var(--border); background: var(--card); margin: 0 0 1.25rem; }
|
|
742
742
|
.ticktable { width: 100%; border-collapse: collapse; font-size: 0.82rem; }
|
|
743
|
-
.ticktable td { padding: 0.
|
|
743
|
+
.ticktable td { padding: 0.5rem 0.75rem; vertical-align: baseline; border: 0; white-space: nowrap; }
|
|
744
744
|
/* Only the last column may grow, so the four fixed columns keep the same width on every row. */
|
|
745
745
|
.ticktable td.tick-stats { width: 100%; white-space: normal; }
|
|
746
746
|
.tick { border-bottom: 1px solid color-mix(in srgb, var(--border) 55%, transparent); }
|
|
747
747
|
.tick:last-child { border-bottom: 0; }
|
|
748
|
-
/*
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
.tick
|
|
752
|
-
.
|
|
753
|
-
|
|
754
|
-
|
|
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; }
|
|
755
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; }
|
|
756
767
|
.tick--stale .tick-last { color: var(--destructive); font-weight: 700; }
|
|
757
768
|
.tick--never .tick-last { font-style: italic; }
|
|
758
769
|
/* The NUMBER is what gets scanned; its label is context. Weight and colour on the value alone
|
|
@@ -770,6 +781,51 @@ details.fold > summary:focus-visible {
|
|
|
770
781
|
.ticktable td.tick-stats { display: block; width: auto; }
|
|
771
782
|
}
|
|
772
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
|
+
|
|
773
829
|
/* ===== chrome.css ===== */
|
|
774
830
|
/*
|
|
775
831
|
* chrome.css — the app chrome kit: sticky header bar, fixed status footer,
|
|
@@ -874,8 +930,20 @@ footer.status .dropdown summary, footer.status .anim-toggle { padding: 0.55rem 0
|
|
|
874
930
|
leaves a small labelled tab on the edge rather than nothing, so a hidden rail
|
|
875
931
|
is still discoverable by someone who did not hide it themselves.
|
|
876
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
|
+
|
|
877
945
|
Width is one token so a consumer can widen it for longer trees. */
|
|
878
|
-
:root { --ls-nav-w: 17rem; }
|
|
946
|
+
:root { --ls-nav-w: 17rem; --ls-nav-top: 3rem; --ls-nav-bottom: 2.2rem; }
|
|
879
947
|
.ls-nav-show { display: none; }
|
|
880
948
|
|
|
881
949
|
@media (min-width: 48.0625rem) {
|
|
@@ -890,19 +958,30 @@ footer.status .dropdown summary, footer.status .anim-toggle { padding: 0.55rem 0
|
|
|
890
958
|
html { --ls-nav-inset: 0rem; }
|
|
891
959
|
html:has(.ls-nav):not([data-ls-nav="off"]) { --ls-nav-inset: var(--ls-nav-w); }
|
|
892
960
|
|
|
893
|
-
/*
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
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
|
|
897
969
|
document every frame, and the pages this ships to render tables of hundreds of
|
|
898
970
|
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. */
|
|
971
|
+
slides while the content reflows once. */
|
|
901
972
|
body { padding-right: var(--ls-nav-inset); }
|
|
902
|
-
|
|
903
|
-
|
|
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. */
|
|
904
982
|
.ls-nav {
|
|
905
|
-
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;
|
|
906
985
|
display: flex; flex-direction: column;
|
|
907
986
|
background: var(--card); border-left: 1px solid var(--border);
|
|
908
987
|
}
|
|
@@ -932,13 +1011,17 @@ footer.status .dropdown summary, footer.status .anim-toggle { padding: 0.55rem 0
|
|
|
932
1011
|
outline: 2px solid color-mix(in srgb, var(--primary) 55%, transparent); outline-offset: -2px;
|
|
933
1012
|
}
|
|
934
1013
|
|
|
935
|
-
/* The tab that survives hiding
|
|
936
|
-
|
|
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. */
|
|
937
1020
|
html[data-ls-nav="off"] .ls-nav-show {
|
|
938
1021
|
display: inline-flex; align-items: center; gap: 0.4rem;
|
|
939
|
-
position: fixed; top:
|
|
1022
|
+
position: fixed; top: var(--ls-nav-top); right: 0; z-index: 25;
|
|
940
1023
|
background: var(--card); border: 1px solid var(--border); border-right: 0;
|
|
941
|
-
padding: 0.6rem 0.
|
|
1024
|
+
border-top: 0; padding: 0.6rem 0.85rem; font-size: 0.9rem; font-weight: 700;
|
|
942
1025
|
}
|
|
943
1026
|
}
|
|
944
1027
|
|
|
@@ -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.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.
|
|
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; } }
|