@danieldeusing/design 0.1.5 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/danieldeusing-design.css +599 -11
- package/dist/danieldeusing-design.min.css +2 -2
- package/package.json +2 -2
- package/runtime/index.js +1 -0
- package/runtime/lsnav.js +56 -0
- package/runtime/terminal.js +22 -0
- package/src/chrome.css +114 -10
- package/src/components.css +123 -0
- package/src/index.css +4 -2
- package/src/print.css +359 -0
- package/src/tailwind.css +1 -0
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
/*! danieldeusing-design v0.
|
|
1
|
+
/*! danieldeusing-design v0.2.0 | MIT | https://github.com/danieldeusing/danieldeusing-design */
|
|
2
2
|
/* ===== reset.css ===== */
|
|
3
3
|
/*
|
|
4
4
|
* danieldeusing-design — minimal reset.
|
|
@@ -619,12 +619,135 @@ html.anim-off .cursor-block {
|
|
|
619
619
|
}
|
|
620
620
|
}
|
|
621
621
|
|
|
622
|
+
/* ═══════════════════════════════════════════════════════════════════════════════════════════════
|
|
623
|
+
Shared page vocabulary, promoted from the cockpit portal (2026-08-04).
|
|
624
|
+
|
|
625
|
+
Each of these was built once in cockpit/pages/portal.css and then wanted by every other
|
|
626
|
+
surface. They live here so there is ONE definition — the tabs had already drifted into FOUR
|
|
627
|
+
page style blocks before being consolidated, which is exactly the failure this move prevents.
|
|
628
|
+
═════════════════════════════════════════════════════════════════════════════════════════════ */
|
|
629
|
+
|
|
630
|
+
/* ── tabs ──────────────────────────────────────────────────────────────────────────────────────
|
|
631
|
+
Daniel: "I am always searching the tabs. They are not falling into my eyes directly."
|
|
632
|
+
|
|
633
|
+
They were low-contrast pills at 0.7 opacity, and the SELECTED one differed from the rest only
|
|
634
|
+
by a slightly darker border and 8% background — so the row read as six equal chips and the eye
|
|
635
|
+
had to stop and compare them. This inverts it: the inactive tabs recede to plain muted text
|
|
636
|
+
with no chrome at all, and the active one is the only thing on the row carrying weight, colour
|
|
637
|
+
and a fill. Nothing to compare — one thing is loud. */
|
|
638
|
+
.tabs {
|
|
639
|
+
display: flex; flex-wrap: wrap; gap: 0.15rem; margin: 1.35rem 0 0;
|
|
640
|
+
border-bottom: 2px solid color-mix(in srgb, var(--border) 80%, transparent);
|
|
641
|
+
}
|
|
642
|
+
.tab {
|
|
643
|
+
background: transparent; border: 0; margin-bottom: -2px;
|
|
644
|
+
font: inherit; font-size: 0.86em; cursor: pointer; padding: 0.5rem 0.95rem;
|
|
645
|
+
color: var(--muted-foreground); transition: background 0.12s ease, color 0.12s ease;
|
|
646
|
+
}
|
|
647
|
+
.tab:hover { color: var(--primary); background: color-mix(in srgb, var(--primary) 10%, transparent); }
|
|
648
|
+
/* FILLED, not underlined. An underline plus a colour shift was still one thin rule and a hue
|
|
649
|
+
change on a row of same-weight words — Daniel was still hunting for the row. A solid block of
|
|
650
|
+
--primary with the page background as its text is the highest-contrast pair the theme owns
|
|
651
|
+
(5.6:1 on warm), and it is the only filled thing on the page, so it is found without reading. */
|
|
652
|
+
.tab[aria-selected="true"] { background: var(--primary); color: var(--background); font-weight: 700; }
|
|
653
|
+
.tab:focus-visible { outline: 2px solid color-mix(in srgb, var(--primary) 55%, transparent); outline-offset: -2px; }
|
|
654
|
+
/* 44px on a touch screen — tabs are the primary navigation of these pages, so they are the last
|
|
655
|
+
control that should be hard to hit. */
|
|
656
|
+
@media (pointer: coarse), (max-width: 40rem) {
|
|
657
|
+
.tab { padding: 0.7rem 0.8rem; min-height: 44px; }
|
|
658
|
+
}
|
|
659
|
+
section.doc.tab-panel { margin-top: 1.35rem; }
|
|
660
|
+
|
|
661
|
+
/* ── fold: progressive disclosure whose affordance is always visible ───────────────────────────
|
|
662
|
+
The detail behind a visual has to open by TAP — these pages are read from a phone, where hover
|
|
663
|
+
does not exist — and the control has to be findable WITHOUT hovering, or the detail is simply
|
|
664
|
+
text that was deleted. Hence a permanent ▸ marker and a full-width summary row rather than
|
|
665
|
+
anything that appears on hover.
|
|
666
|
+
|
|
667
|
+
Pair with a `beforeprint` handler that opens every <details>, or a printout silently loses
|
|
668
|
+
whatever the closed ones were holding. */
|
|
669
|
+
details.fold { border-top: 1px solid color-mix(in srgb, currentColor 15%, transparent); }
|
|
670
|
+
details.fold > summary {
|
|
671
|
+
display: flex; align-items: center; gap: 0.5rem; cursor: pointer; list-style: none;
|
|
672
|
+
padding: 0.55rem 0; font-size: 0.85rem; color: var(--muted-foreground);
|
|
673
|
+
}
|
|
674
|
+
details.fold > summary::-webkit-details-marker { display: none; }
|
|
675
|
+
details.fold > summary::before { content: "\25B8"; flex: none; opacity: 0.6; }
|
|
676
|
+
details.fold[open] > summary::before { content: "\25BE"; }
|
|
677
|
+
details.fold > summary:hover { color: var(--primary); }
|
|
678
|
+
details.fold > summary:focus-visible {
|
|
679
|
+
outline: 2px solid color-mix(in srgb, currentColor 45%, transparent); outline-offset: 2px;
|
|
680
|
+
}
|
|
681
|
+
.fold-body { font-size: 0.9rem; padding: 0 0 0.7rem 1.1rem; }
|
|
682
|
+
.fold-body > * + * { margin-top: 0.6rem; }
|
|
683
|
+
|
|
684
|
+
/* ── legend: one rendering of each state, beside the thing it keys ─────────────────────────────
|
|
685
|
+
An item is `display: block`, NOT flex: a gloss contains <strong>/<code>/<em>, and every one of
|
|
686
|
+
those would become its own flex item — which is how a single sentence first rendered as four
|
|
687
|
+
separately-wrapped columns. */
|
|
688
|
+
.legend {
|
|
689
|
+
list-style: none; padding: 0; margin: 0.7rem 0 0;
|
|
690
|
+
display: grid; gap: 0.4rem 1.4rem; font-size: 0.86rem;
|
|
691
|
+
}
|
|
692
|
+
@media (min-width: 52rem) { .legend { grid-template-columns: repeat(2, minmax(0, 1fr)); } }
|
|
693
|
+
.legend > li { line-height: 1.6; }
|
|
694
|
+
.legend > li > :first-child { margin-right: 0.45rem; }
|
|
695
|
+
|
|
696
|
+
/* ── ticker strip: one standard header for every poller ────────────────────────────────────────
|
|
697
|
+
Daniel: "For all pollers / tickers we should make a standard layout ... on top of the pages,
|
|
698
|
+
compact and like dashboard style."
|
|
699
|
+
|
|
700
|
+
Every poller answers the same four questions — what ran, when, when next, what did it count —
|
|
701
|
+
and each page was answering them in its own prose box, so "is anything stalled?" took four
|
|
702
|
+
paragraphs instead of one glance.
|
|
703
|
+
|
|
704
|
+
A real TABLE, not a grid per row. The first version made each row its own `display: grid`,
|
|
705
|
+
which cannot align anything across rows: every row sized its columns to its own content, so
|
|
706
|
+
`next` sat wherever that row's name happened to end and the layout moved when a label changed.
|
|
707
|
+
One table means one set of column widths for the whole strip, and scanning a column is the only
|
|
708
|
+
thing the strip is for. Borderless by design — rules between cells draw a grid nobody reads.
|
|
709
|
+
|
|
710
|
+
Colour comes only from --primary and --destructive, both per-theme. Hardcoded accents miss AA
|
|
711
|
+
on at least one theme (measured 2026-08-04), so a strip inventing its own greens and ambers
|
|
712
|
+
would have added five more. */
|
|
713
|
+
.tickstrip:not(:empty) { border: 1px solid var(--border); background: var(--card); margin: 0 0 1.25rem; }
|
|
714
|
+
.ticktable { width: 100%; border-collapse: collapse; font-size: 0.82rem; }
|
|
715
|
+
.ticktable td { padding: 0.45rem 0.55rem; vertical-align: baseline; border: 0; white-space: nowrap; }
|
|
716
|
+
/* Only the last column may grow, so the four fixed columns keep the same width on every row. */
|
|
717
|
+
.ticktable td.tick-stats { width: 100%; white-space: normal; }
|
|
718
|
+
.tick { border-bottom: 1px solid color-mix(in srgb, var(--border) 55%, transparent); }
|
|
719
|
+
.tick:last-child { border-bottom: 0; }
|
|
720
|
+
/* The glyph carries the state WITHOUT colour as well as with it — two of the four themes are
|
|
721
|
+
near-black and a printout has none, so ● vs ✕ vs ○ has to survive on its own. */
|
|
722
|
+
.tick-dot { font-size: 0.7em; width: 1rem; padding-right: 0 !important; }
|
|
723
|
+
.tick--ok .tick-dot { color: var(--primary); }
|
|
724
|
+
.tick--stale .tick-dot { color: var(--destructive); }
|
|
725
|
+
.tick--never .tick-dot { color: var(--muted-foreground); }
|
|
726
|
+
.tick-name { font-weight: 700; color: var(--primary); }
|
|
727
|
+
.tick-last, .tick-next { font-variant-numeric: tabular-nums; color: var(--muted-foreground); }
|
|
728
|
+
.tick--stale .tick-last { color: var(--destructive); font-weight: 700; }
|
|
729
|
+
.tick--never .tick-last { font-style: italic; }
|
|
730
|
+
/* The NUMBER is what gets scanned; its label is context. Weight and colour on the value alone
|
|
731
|
+
means a row reads as its numbers first and its wording second. */
|
|
732
|
+
.tick-stats { color: var(--muted-foreground); }
|
|
733
|
+
.tick-stats b { font-weight: 700; color: var(--primary); font-variant-numeric: tabular-nums; }
|
|
734
|
+
.tick-sep { font-style: normal; opacity: 0.35; margin: 0 0.5rem; }
|
|
735
|
+
/* A stale row earns the only background tint in the strip — an all-tinted strip is one the eye
|
|
736
|
+
stops reading. */
|
|
737
|
+
.tick--stale { background: color-mix(in srgb, var(--destructive) 8%, transparent); }
|
|
738
|
+
@media (max-width: 40rem) {
|
|
739
|
+
.ticktable, .ticktable tbody, .tick, .ticktable td { display: block; }
|
|
740
|
+
.tick { padding: 0.4rem 0.5rem; }
|
|
741
|
+
.ticktable td { display: inline-block; padding: 0.1rem 0.5rem 0.1rem 0; }
|
|
742
|
+
.ticktable td.tick-stats { display: block; width: auto; }
|
|
743
|
+
}
|
|
744
|
+
|
|
622
745
|
/* ===== chrome.css ===== */
|
|
623
746
|
/*
|
|
624
747
|
* chrome.css — the app chrome kit: sticky header bar, fixed status footer,
|
|
625
|
-
* and the responsive site navigation (desktop `ls -l`
|
|
626
|
-
*
|
|
627
|
-
*
|
|
748
|
+
* and the responsive site navigation (desktop `ls -l` RAIL, mobile burger with
|
|
749
|
+
* the footer folded in). Extracted from danieldeusing.de and the cockpit portal
|
|
750
|
+
* so every app ships the same chrome without rebuilding it.
|
|
628
751
|
*
|
|
629
752
|
* Markup contract (see templates/documentation.html for the full example):
|
|
630
753
|
*
|
|
@@ -633,7 +756,14 @@ html.anim-off .cursor-block {
|
|
|
633
756
|
* <a href="/" class="brand"><span class="glow">name</span><span class="cursor-block"></span></a>
|
|
634
757
|
* <button class="nav-burger" data-nav-toggle aria-controls="site-nav" aria-expanded="false">…☰ svg…</button>
|
|
635
758
|
* <nav aria-label="site" id="site-nav" class="site-nav">
|
|
636
|
-
* <
|
|
759
|
+
* <div class="ls-nav" id="nav">
|
|
760
|
+
* <div class="ls-nav-head">
|
|
761
|
+
* <span class="ls-nav-title">ls -l</span>
|
|
762
|
+
* <button class="ls-nav-hide" data-ls-nav-toggle aria-controls="nav" aria-expanded="true">»</button>
|
|
763
|
+
* </div>
|
|
764
|
+
* <ul class="ls-panel">….ls-row items…</ul>
|
|
765
|
+
* </div>
|
|
766
|
+
* <button class="ls-nav-show" data-ls-nav-toggle aria-controls="nav">« ls -l</button>
|
|
637
767
|
* <ul class="mobile-nav">…same links as .mobile-item ls-rows…</ul>
|
|
638
768
|
* <div class="mobile-footer">…footer controls folded in…</div>
|
|
639
769
|
* </nav>
|
|
@@ -641,8 +771,15 @@ html.anim-off .cursor-block {
|
|
|
641
771
|
* …
|
|
642
772
|
* <footer class="status">…status-left + status-right…</footer>
|
|
643
773
|
*
|
|
644
|
-
* Behaviour: pair with `initBurgerNav()`
|
|
645
|
-
*
|
|
774
|
+
* Behaviour: pair with `initBurgerNav()` (mobile burger) and `initLsNav()`
|
|
775
|
+
* (rail show/hide) from the runtime. The rail's SHOWN/HIDDEN state must also be
|
|
776
|
+
* applied before first paint by an inline <head> script — see the rail section
|
|
777
|
+
* below for why the runtime cannot be the one to do it:
|
|
778
|
+
*
|
|
779
|
+
* try { if (localStorage.getItem("ls-nav") === "off")
|
|
780
|
+
* document.documentElement.dataset.lsNav = "off"; } catch {}
|
|
781
|
+
*
|
|
782
|
+
* Breakpoint: 48rem.
|
|
646
783
|
*/
|
|
647
784
|
|
|
648
785
|
/* ── a11y helpers ── */
|
|
@@ -682,12 +819,100 @@ footer.status .dropdown summary, footer.status .anim-toggle { padding: 0.55rem 0
|
|
|
682
819
|
.doc-link { color: var(--muted-foreground); text-decoration: none; transition: color 0.15s ease; }
|
|
683
820
|
.doc-link:hover { color: var(--primary); }
|
|
684
821
|
|
|
685
|
-
/* ── ls-listing rows (shared by the desktop
|
|
822
|
+
/* ── ls-listing rows (shared by the desktop rail + mobile menu) ── */
|
|
686
823
|
.ls-row { display: flex; align-items: baseline; gap: 0.9rem; }
|
|
687
824
|
.ls-perm { color: var(--muted-foreground); font-size: 0.72rem; letter-spacing: 0; }
|
|
688
825
|
.ls-row--sub .ls-name { padding-left: 1rem; }
|
|
689
|
-
|
|
690
|
-
|
|
826
|
+
/* The pre-rail dropdown form. Kept, and scoped to the dropdown panel, because the
|
|
827
|
+
other surfaces still ship that markup — when they unpin they get this file, and
|
|
828
|
+
their nav should stay right-aligned rather than quietly jumping to the left. The
|
|
829
|
+
rail's own <ul> carries `ls-panel` WITHOUT `dropdown-panel`, so it is unaffected. */
|
|
830
|
+
.dropdown-panel.ls-panel { min-width: 17rem; left: auto !important; right: 0; }
|
|
831
|
+
|
|
832
|
+
/* ── `ls -l` site rail (desktop ≥48rem): sticky, full-height, right-hand panel ──
|
|
833
|
+
Was a dropdown that had to be re-opened on every page. Daniel: "Always make it
|
|
834
|
+
sticky (default shown) right side, keep `ls -l`, but not down arrow, but
|
|
835
|
+
something which tells the user he can hide / minimize."
|
|
836
|
+
|
|
837
|
+
SHOWN BY DEFAULT, and the state lives in localStorage under "ls-nav". It is
|
|
838
|
+
applied by an inline <head> script as `html[data-ls-nav="off"]` BEFORE first
|
|
839
|
+
paint — not by the runtime — because the runtime is a module at the end of
|
|
840
|
+
<body>: a reader who hid the rail would otherwise watch it paint and then jump
|
|
841
|
+
away on every single page load. `initLsNav()` only handles the clicks.
|
|
842
|
+
|
|
843
|
+
The affordance is deliberately NOT a caret. A caret means "a menu drops from
|
|
844
|
+
here", which is the thing this stopped being. Guillemets point the way the
|
|
845
|
+
panel travels — » pushes it off to the right, « pulls it back — and hiding it
|
|
846
|
+
leaves a small labelled tab on the edge rather than nothing, so a hidden rail
|
|
847
|
+
is still discoverable by someone who did not hide it themselves.
|
|
848
|
+
|
|
849
|
+
Width is one token so a consumer can widen it for longer trees. */
|
|
850
|
+
:root { --ls-nav-w: 17rem; }
|
|
851
|
+
.ls-nav-show { display: none; }
|
|
852
|
+
|
|
853
|
+
@media (min-width: 48.0625rem) {
|
|
854
|
+
/* Room is reserved ONLY when a rail is actually in the page, and the default is
|
|
855
|
+
zero. This is what makes the stylesheet safe to publish ahead of a consumer's
|
|
856
|
+
markup: every surface that still ships the old dropdown nav (or no nav at all)
|
|
857
|
+
keeps its full width instead of growing a 17rem gutter with nothing in it —
|
|
858
|
+
which is exactly what they would have got the moment they unpinned.
|
|
859
|
+
It also fails in the right direction: if :has() is unavailable the selector is
|
|
860
|
+
dropped, the inset stays 0, and the rail overlays content rather than the page
|
|
861
|
+
reserving space for a rail that never arrives. */
|
|
862
|
+
html { --ls-nav-inset: 0rem; }
|
|
863
|
+
html:has(.ls-nav):not([data-ls-nav="off"]) { --ls-nav-inset: var(--ls-nav-w); }
|
|
864
|
+
|
|
865
|
+
/* Everything in normal flow shifts left, so content sits against the rail's
|
|
866
|
+
left edge instead of running under it. The fixed footer has to be told
|
|
867
|
+
separately: `inset-inline: 0` ignores the body's padding. */
|
|
868
|
+
/* Deliberately NOT transitioned. Animating padding on <body> reflows the whole
|
|
869
|
+
document every frame, and the pages this ships to render tables of hundreds of
|
|
870
|
+
rows — the rail's own transform is composited and costs nothing, so the panel
|
|
871
|
+
slides while the content reflows once. Animating both looked no better and
|
|
872
|
+
made the containers dashboard stutter on toggle. */
|
|
873
|
+
body { padding-right: var(--ls-nav-inset); }
|
|
874
|
+
footer.status { right: var(--ls-nav-inset); }
|
|
875
|
+
|
|
876
|
+
.ls-nav {
|
|
877
|
+
position: fixed; top: 0; right: 0; bottom: 0; width: var(--ls-nav-w); z-index: 45;
|
|
878
|
+
display: flex; flex-direction: column;
|
|
879
|
+
background: var(--card); border-left: 1px solid var(--border);
|
|
880
|
+
}
|
|
881
|
+
html:not(.anim-off) .ls-nav,
|
|
882
|
+
html:not(.anim-off) .ls-nav-show { transition: transform 0.18s ease; }
|
|
883
|
+
html[data-ls-nav="off"] .ls-nav { transform: translateX(100%); }
|
|
884
|
+
|
|
885
|
+
.ls-nav-head {
|
|
886
|
+
flex: none; display: flex; align-items: center; justify-content: space-between; gap: 0.5rem;
|
|
887
|
+
padding: 0.6rem 0.5rem 0.6rem 0.85rem; border-bottom: 1px solid var(--border);
|
|
888
|
+
}
|
|
889
|
+
.ls-nav-title { font-weight: 700; font-size: 0.9rem; }
|
|
890
|
+
|
|
891
|
+
/* The list is the only thing that scrolls — the head stays put, so the control
|
|
892
|
+
that hides the rail cannot be scrolled out of reach. */
|
|
893
|
+
.ls-nav .ls-panel {
|
|
894
|
+
flex: 1; min-height: 0; overflow-y: auto;
|
|
895
|
+
list-style: none; margin: 0; padding: 0.5rem 0 1rem;
|
|
896
|
+
}
|
|
897
|
+
|
|
898
|
+
.ls-nav-hide, .ls-nav-show {
|
|
899
|
+
background: none; border: 0; font: inherit; cursor: pointer; line-height: 1;
|
|
900
|
+
color: var(--muted-foreground); padding: 0.4rem 0.55rem;
|
|
901
|
+
}
|
|
902
|
+
.ls-nav-hide:hover, .ls-nav-show:hover { color: var(--primary); }
|
|
903
|
+
.ls-nav-hide:focus-visible, .ls-nav-show:focus-visible {
|
|
904
|
+
outline: 2px solid color-mix(in srgb, var(--primary) 55%, transparent); outline-offset: -2px;
|
|
905
|
+
}
|
|
906
|
+
|
|
907
|
+
/* The tab that survives hiding. Sits on the right edge, mid-height, and only
|
|
908
|
+
exists while the rail is away. */
|
|
909
|
+
html[data-ls-nav="off"] .ls-nav-show {
|
|
910
|
+
display: inline-flex; align-items: center; gap: 0.4rem;
|
|
911
|
+
position: fixed; top: 50%; right: 0; z-index: 45; transform: translateY(-50%);
|
|
912
|
+
background: var(--card); border: 1px solid var(--border); border-right: 0;
|
|
913
|
+
padding: 0.6rem 0.5rem; font-size: 0.8rem;
|
|
914
|
+
}
|
|
915
|
+
}
|
|
691
916
|
|
|
692
917
|
/* ── burger + mobile menu ── */
|
|
693
918
|
.nav-burger {
|
|
@@ -708,7 +933,9 @@ footer.status .dropdown summary, footer.status .anim-toggle { padding: 0.55rem 0
|
|
|
708
933
|
padding: 0.4rem 1.25rem 0.9rem; max-height: calc(100vh - 6rem); overflow-y: auto;
|
|
709
934
|
}
|
|
710
935
|
.site-nav.open { display: block; }
|
|
711
|
-
|
|
936
|
+
/* The desktop rail is a desktop idea: a fixed 17rem column would eat a phone.
|
|
937
|
+
Below the breakpoint the burger owns navigation and the flat list replaces it. */
|
|
938
|
+
.site-nav > .ls-nav, .ls-nav-show { display: none; }
|
|
712
939
|
|
|
713
940
|
.mobile-nav { display: flex; flex-direction: column; }
|
|
714
941
|
.mobile-nav .mobile-item {
|
|
@@ -760,3 +987,364 @@ span[data-tip], th[data-tip] { border-bottom: 1px dotted var(--muted-foreground)
|
|
|
760
987
|
text-align: left; white-space: normal; pointer-events: none;
|
|
761
988
|
box-shadow: 0 4px 14px rgba(0, 0, 0, 0.25);
|
|
762
989
|
}
|
|
990
|
+
|
|
991
|
+
/* ===== print.css ===== */
|
|
992
|
+
/*
|
|
993
|
+
* danieldeusing-design — print layer.
|
|
994
|
+
*
|
|
995
|
+
* Paper is a different device, not a narrow screen. This layer strips the parts
|
|
996
|
+
* of the terminal look that only exist to sell "a CRT is running" — scanlines,
|
|
997
|
+
* phosphor glow, sticky chrome, the reveal animation — and lays the content out
|
|
998
|
+
* as a document: black on white, 12px body text, sane page breaks.
|
|
999
|
+
*
|
|
1000
|
+
* Load LAST (index.css imports it after every other layer) so its token
|
|
1001
|
+
* overrides win on source order without needing !important on each one.
|
|
1002
|
+
*
|
|
1003
|
+
* Three things here are load-bearing and easy to lose in a refactor:
|
|
1004
|
+
*
|
|
1005
|
+
* 1. The terminal reveal is forced open. runtime/terminal.js only reveals a
|
|
1006
|
+
* [data-term-out] once its section scrolls into view, so a long page that
|
|
1007
|
+
* is printed straight after load has most of its content sitting at
|
|
1008
|
+
* visibility:hidden. Printing it would silently produce blank pages — the
|
|
1009
|
+
* reader only finds out on paper. Everything is forced visible here
|
|
1010
|
+
* regardless of animation state.
|
|
1011
|
+
* 2. `zoom` is reset. Pages that use the resolution-independent zoom trick
|
|
1012
|
+
* (html.style.zoom = innerWidth / 1920) carry that multiplier into the
|
|
1013
|
+
* print box, which is what makes a printout come out oversized.
|
|
1014
|
+
* 3. Mermaid bakes the ACTIVE theme's colours into the SVG at render time, so
|
|
1015
|
+
* a diagram rendered under `green`/`mono` is dark-on-dark on paper no
|
|
1016
|
+
* matter what the page tokens say. The SVG parts are repainted below.
|
|
1017
|
+
*/
|
|
1018
|
+
|
|
1019
|
+
@media print {
|
|
1020
|
+
@page {
|
|
1021
|
+
margin: 14mm 12mm;
|
|
1022
|
+
}
|
|
1023
|
+
|
|
1024
|
+
/* ── ink-frugal palette, whatever theme is on screen ────────────────────
|
|
1025
|
+
`html[data-theme]` matches the specificity of the themed token blocks in
|
|
1026
|
+
tokens.css and comes later in the bundle, so it wins for every theme. */
|
|
1027
|
+
:root,
|
|
1028
|
+
html,
|
|
1029
|
+
html[data-theme] {
|
|
1030
|
+
--background: #ffffff;
|
|
1031
|
+
--foreground: #111111;
|
|
1032
|
+
--card: #ffffff;
|
|
1033
|
+
--card-foreground: #111111;
|
|
1034
|
+
--popover: #ffffff;
|
|
1035
|
+
--popover-foreground: #111111;
|
|
1036
|
+
--primary: #000000;
|
|
1037
|
+
--primary-foreground: #ffffff;
|
|
1038
|
+
--secondary: #ffffff;
|
|
1039
|
+
--secondary-foreground: #111111;
|
|
1040
|
+
--muted: #ffffff;
|
|
1041
|
+
--muted-foreground: #444444;
|
|
1042
|
+
--accent: #000000;
|
|
1043
|
+
--accent-foreground: #ffffff;
|
|
1044
|
+
--destructive: #7a1c1c;
|
|
1045
|
+
--border: #9a9a9a;
|
|
1046
|
+
--input: #9a9a9a;
|
|
1047
|
+
--ring: #000000;
|
|
1048
|
+
--glow: transparent;
|
|
1049
|
+
--glow-soft: transparent;
|
|
1050
|
+
--scanline-opacity: 0;
|
|
1051
|
+
}
|
|
1052
|
+
|
|
1053
|
+
/* ── document metrics ──────────────────────────────────────────────────
|
|
1054
|
+
The rem baseline is pinned so the design's rem scale lands where it was
|
|
1055
|
+
drawn, and the body size is stated in px so it is 12px on paper whatever
|
|
1056
|
+
the browser's default font size is. `zoom` is a screen-only device. */
|
|
1057
|
+
html {
|
|
1058
|
+
zoom: 1 !important;
|
|
1059
|
+
font-size: 16px;
|
|
1060
|
+
scroll-behavior: auto;
|
|
1061
|
+
}
|
|
1062
|
+
|
|
1063
|
+
body {
|
|
1064
|
+
background: #ffffff;
|
|
1065
|
+
color: var(--foreground);
|
|
1066
|
+
font-size: 12px;
|
|
1067
|
+
line-height: 1.45;
|
|
1068
|
+
overflow: visible;
|
|
1069
|
+
}
|
|
1070
|
+
|
|
1071
|
+
/* CRT scanline overlay: a fixed, full-viewport gradient — on paper it is a
|
|
1072
|
+
grey wash over the page. */
|
|
1073
|
+
body::after {
|
|
1074
|
+
display: none !important;
|
|
1075
|
+
}
|
|
1076
|
+
|
|
1077
|
+
/* ── nothing animates on paper ─────────────────────────────────────────
|
|
1078
|
+
Also the mechanism that un-hides the terminal reveal: the keyframes use
|
|
1079
|
+
`both`, so killing the animation restores the element's own opacity. */
|
|
1080
|
+
*,
|
|
1081
|
+
*::before,
|
|
1082
|
+
*::after {
|
|
1083
|
+
animation: none !important;
|
|
1084
|
+
transition: none !important;
|
|
1085
|
+
text-shadow: none !important;
|
|
1086
|
+
box-shadow: none !important;
|
|
1087
|
+
}
|
|
1088
|
+
|
|
1089
|
+
/* ── the reveal must never decide what lands on paper ──────────────────
|
|
1090
|
+
See note 1 at the top of this file. */
|
|
1091
|
+
html.term-anim [data-term] [data-term-out],
|
|
1092
|
+
html.term-anim [data-term] [data-term-out]:not(.term-show),
|
|
1093
|
+
html.term-anim [data-term] [data-term-out] > * {
|
|
1094
|
+
visibility: visible !important;
|
|
1095
|
+
opacity: 1 !important;
|
|
1096
|
+
}
|
|
1097
|
+
html.term-anim [data-term] .prompt:not(.term-live) {
|
|
1098
|
+
color: var(--muted-foreground) !important;
|
|
1099
|
+
}
|
|
1100
|
+
html.term-anim [data-term] .prompt:not(.term-live)::before {
|
|
1101
|
+
color: var(--primary) !important;
|
|
1102
|
+
}
|
|
1103
|
+
|
|
1104
|
+
/* ── screen-only chrome ────────────────────────────────────────────────
|
|
1105
|
+
.toc is the "On this page" navigator: a screen affordance with no meaning
|
|
1106
|
+
on paper, and the first thing that makes a printout look broken. */
|
|
1107
|
+
header.bar,
|
|
1108
|
+
footer.status,
|
|
1109
|
+
.site-nav,
|
|
1110
|
+
.nav-burger,
|
|
1111
|
+
.mobile-nav,
|
|
1112
|
+
.mobile-footer,
|
|
1113
|
+
.skip-link,
|
|
1114
|
+
.dropdown,
|
|
1115
|
+
.dropdown-panel,
|
|
1116
|
+
.anim-toggle,
|
|
1117
|
+
.cursor-block,
|
|
1118
|
+
.term-caret,
|
|
1119
|
+
.ascii-rule,
|
|
1120
|
+
.toc,
|
|
1121
|
+
#ddtip {
|
|
1122
|
+
display: none !important;
|
|
1123
|
+
}
|
|
1124
|
+
|
|
1125
|
+
/* The `ls -l` rail sits inside .site-nav, so the rule above already hides it —
|
|
1126
|
+
but the room it reserved is padding on the BODY, and that would print as a
|
|
1127
|
+
17rem gutter down the right of every page. Hiding a fixed element does not
|
|
1128
|
+
reclaim the space something else was told to leave for it. */
|
|
1129
|
+
body {
|
|
1130
|
+
padding-right: 0 !important;
|
|
1131
|
+
}
|
|
1132
|
+
|
|
1133
|
+
[data-tip] {
|
|
1134
|
+
cursor: auto;
|
|
1135
|
+
}
|
|
1136
|
+
span[data-tip],
|
|
1137
|
+
th[data-tip] {
|
|
1138
|
+
border-bottom: 0;
|
|
1139
|
+
}
|
|
1140
|
+
|
|
1141
|
+
/* ── layout: the page is the column ────────────────────────────────────
|
|
1142
|
+
.wrap/.layout/.content are the documentation-template vocabulary shipped
|
|
1143
|
+
in templates/documentation.html. */
|
|
1144
|
+
.wrap {
|
|
1145
|
+
max-width: none !important;
|
|
1146
|
+
margin: 0 !important;
|
|
1147
|
+
padding: 0 !important;
|
|
1148
|
+
}
|
|
1149
|
+
.layout {
|
|
1150
|
+
display: block !important;
|
|
1151
|
+
}
|
|
1152
|
+
.content {
|
|
1153
|
+
min-width: 0;
|
|
1154
|
+
}
|
|
1155
|
+
|
|
1156
|
+
/* ── heading hierarchy against a 12px body ─────────────────────────────
|
|
1157
|
+
24 / 18 / 16 / 14 px. !important because page-level rules give these
|
|
1158
|
+
classes a higher specificity than a bare element selector. */
|
|
1159
|
+
h1,
|
|
1160
|
+
.title {
|
|
1161
|
+
font-size: 1.5rem !important;
|
|
1162
|
+
}
|
|
1163
|
+
h2 {
|
|
1164
|
+
font-size: 1.15rem !important;
|
|
1165
|
+
}
|
|
1166
|
+
h3,
|
|
1167
|
+
.sub {
|
|
1168
|
+
font-size: 1rem !important;
|
|
1169
|
+
}
|
|
1170
|
+
h4 {
|
|
1171
|
+
font-size: 0.9rem !important;
|
|
1172
|
+
}
|
|
1173
|
+
|
|
1174
|
+
.prompt {
|
|
1175
|
+
font-size: 0.7rem;
|
|
1176
|
+
}
|
|
1177
|
+
|
|
1178
|
+
/* ── page breaks ───────────────────────────────────────────────────────
|
|
1179
|
+
A heading (or a `$ command` section header) must never be the last thing
|
|
1180
|
+
on a page. Tables break between rows and repeat their header; everything
|
|
1181
|
+
that reads as one unit stays together. */
|
|
1182
|
+
h1,
|
|
1183
|
+
h2,
|
|
1184
|
+
h3,
|
|
1185
|
+
h4,
|
|
1186
|
+
.prompt,
|
|
1187
|
+
.toc-label {
|
|
1188
|
+
break-after: avoid-page;
|
|
1189
|
+
break-inside: avoid;
|
|
1190
|
+
}
|
|
1191
|
+
p,
|
|
1192
|
+
li,
|
|
1193
|
+
blockquote {
|
|
1194
|
+
orphans: 3;
|
|
1195
|
+
widows: 3;
|
|
1196
|
+
}
|
|
1197
|
+
pre,
|
|
1198
|
+
figure,
|
|
1199
|
+
blockquote,
|
|
1200
|
+
tr,
|
|
1201
|
+
img,
|
|
1202
|
+
.eli5,
|
|
1203
|
+
.card-terminal {
|
|
1204
|
+
break-inside: avoid;
|
|
1205
|
+
}
|
|
1206
|
+
/* NOT svg. A mermaid flowchart is routinely taller than a printed page;
|
|
1207
|
+
`break-inside: avoid` on the SVG pushes it past the page boundary while its
|
|
1208
|
+
wrapper stays behind, which prints an entire page as an empty bordered box.
|
|
1209
|
+
Let the SVG break where the wrapper breaks. */
|
|
1210
|
+
svg {
|
|
1211
|
+
break-inside: auto;
|
|
1212
|
+
}
|
|
1213
|
+
thead {
|
|
1214
|
+
display: table-header-group;
|
|
1215
|
+
}
|
|
1216
|
+
|
|
1217
|
+
/* ── nothing scrolls on paper ──────────────────────────────────────────
|
|
1218
|
+
An overflow-x container has no scrollbar in print: whatever sits past the
|
|
1219
|
+
right edge is simply gone. Let it wrap instead. */
|
|
1220
|
+
.table-scroll,
|
|
1221
|
+
pre,
|
|
1222
|
+
.diagram {
|
|
1223
|
+
overflow: visible !important;
|
|
1224
|
+
max-width: 100% !important;
|
|
1225
|
+
}
|
|
1226
|
+
pre {
|
|
1227
|
+
white-space: pre-wrap !important;
|
|
1228
|
+
overflow-wrap: anywhere;
|
|
1229
|
+
font-size: 0.75rem !important;
|
|
1230
|
+
background: transparent !important;
|
|
1231
|
+
}
|
|
1232
|
+
pre.mermaid[data-processed] {
|
|
1233
|
+
white-space: normal !important;
|
|
1234
|
+
}
|
|
1235
|
+
code {
|
|
1236
|
+
background: transparent !important;
|
|
1237
|
+
overflow-wrap: anywhere;
|
|
1238
|
+
}
|
|
1239
|
+
|
|
1240
|
+
table {
|
|
1241
|
+
width: 100% !important;
|
|
1242
|
+
table-layout: auto;
|
|
1243
|
+
border-collapse: collapse;
|
|
1244
|
+
font-size: 0.75rem !important;
|
|
1245
|
+
}
|
|
1246
|
+
/* `overflow-wrap: break-word` and NOT `anywhere`: `anywhere` also shrinks a
|
|
1247
|
+
cell's min-content width to one character, and the auto table layout then
|
|
1248
|
+
collapses a short column to nothing ("Orchestrator" set as "Orche/strat/or"
|
|
1249
|
+
down a 6-character column). Only the elements that actually hold
|
|
1250
|
+
unbreakable strings — paths, URLs, identifiers — get to break anywhere. */
|
|
1251
|
+
th,
|
|
1252
|
+
td {
|
|
1253
|
+
overflow-wrap: break-word;
|
|
1254
|
+
white-space: normal !important;
|
|
1255
|
+
}
|
|
1256
|
+
td code,
|
|
1257
|
+
th code,
|
|
1258
|
+
td a,
|
|
1259
|
+
th a {
|
|
1260
|
+
overflow-wrap: anywhere;
|
|
1261
|
+
}
|
|
1262
|
+
|
|
1263
|
+
/* ── links read as text ────────────────────────────────────────────────
|
|
1264
|
+
The URL is deliberately NOT printed after the link. These are internal
|
|
1265
|
+
documents whose links are mostly in-page anchors and hosts already named
|
|
1266
|
+
in the prose; appending every href would add noise and pages without
|
|
1267
|
+
adding information. To turn it on, add:
|
|
1268
|
+
a[href^="http"]::after { content: " (" attr(href) ")"; } */
|
|
1269
|
+
a {
|
|
1270
|
+
color: inherit;
|
|
1271
|
+
text-decoration: underline;
|
|
1272
|
+
text-underline-offset: 2px;
|
|
1273
|
+
}
|
|
1274
|
+
a.card-terminal,
|
|
1275
|
+
a[class*="card"] {
|
|
1276
|
+
text-decoration: none;
|
|
1277
|
+
}
|
|
1278
|
+
|
|
1279
|
+
.glow,
|
|
1280
|
+
.glow-lg {
|
|
1281
|
+
color: inherit;
|
|
1282
|
+
}
|
|
1283
|
+
.btn-terminal {
|
|
1284
|
+
border: 1px solid var(--border);
|
|
1285
|
+
background: transparent;
|
|
1286
|
+
color: var(--foreground);
|
|
1287
|
+
}
|
|
1288
|
+
|
|
1289
|
+
/* ── mermaid diagrams ──────────────────────────────────────────────────
|
|
1290
|
+
See note 3 at the top of this file: the palette is baked into the SVG at
|
|
1291
|
+
render time, so a diagram rendered under a dark theme prints as dark
|
|
1292
|
+
boxes with invisible labels. Repaint the standard mermaid parts. */
|
|
1293
|
+
/* Every diagram is scaled to fit ONE page. Not cosmetic: Chromium will not
|
|
1294
|
+
start an over-tall diagram in the space left on the current page, so an
|
|
1295
|
+
uncapped flowchart (they run to 2500px in a narrow column) prints a blank
|
|
1296
|
+
page, then an empty bordered box, then the diagram — six such pages in the
|
|
1297
|
+
executor design doc alone. `width/height: auto` with both max-* set is what
|
|
1298
|
+
makes the browser scale it proportionally instead of squashing it.
|
|
1299
|
+
A diagram that ends up too small to read is a diagram that is too tall for
|
|
1300
|
+
paper: split it, or lay it out left-to-right. */
|
|
1301
|
+
.mermaid svg {
|
|
1302
|
+
display: block;
|
|
1303
|
+
margin-inline: auto;
|
|
1304
|
+
width: auto !important;
|
|
1305
|
+
height: auto !important;
|
|
1306
|
+
max-width: 100% !important;
|
|
1307
|
+
max-height: 235mm !important;
|
|
1308
|
+
}
|
|
1309
|
+
.mermaid svg .node rect,
|
|
1310
|
+
.mermaid svg .node polygon,
|
|
1311
|
+
.mermaid svg .node circle,
|
|
1312
|
+
.mermaid svg .node path,
|
|
1313
|
+
.mermaid svg .label-container,
|
|
1314
|
+
.mermaid svg .cluster rect,
|
|
1315
|
+
.mermaid svg .actor,
|
|
1316
|
+
.mermaid svg .note {
|
|
1317
|
+
fill: #ffffff !important;
|
|
1318
|
+
stroke: #333333 !important;
|
|
1319
|
+
}
|
|
1320
|
+
.mermaid svg .edgePath path,
|
|
1321
|
+
.mermaid svg .flowchart-link,
|
|
1322
|
+
.mermaid svg line,
|
|
1323
|
+
.mermaid svg .messageLine0,
|
|
1324
|
+
.mermaid svg .messageLine1 {
|
|
1325
|
+
stroke: #333333 !important;
|
|
1326
|
+
}
|
|
1327
|
+
.mermaid svg .arrowheadPath,
|
|
1328
|
+
.mermaid svg marker path,
|
|
1329
|
+
.mermaid svg marker polygon {
|
|
1330
|
+
fill: #333333 !important;
|
|
1331
|
+
stroke: #333333 !important;
|
|
1332
|
+
}
|
|
1333
|
+
.mermaid svg text,
|
|
1334
|
+
.mermaid svg .nodeLabel,
|
|
1335
|
+
.mermaid svg .edgeLabel,
|
|
1336
|
+
.mermaid svg .cluster-label,
|
|
1337
|
+
.mermaid svg .messageText,
|
|
1338
|
+
.mermaid svg foreignObject div,
|
|
1339
|
+
.mermaid svg foreignObject span,
|
|
1340
|
+
.mermaid svg p {
|
|
1341
|
+
fill: #111111 !important;
|
|
1342
|
+
color: #111111 !important;
|
|
1343
|
+
background: transparent !important;
|
|
1344
|
+
}
|
|
1345
|
+
.mermaid svg .edgeLabel rect,
|
|
1346
|
+
.mermaid svg .edgeLabel foreignObject > div {
|
|
1347
|
+
fill: #ffffff !important;
|
|
1348
|
+
background: #ffffff !important;
|
|
1349
|
+
}
|
|
1350
|
+
}
|