privateboard 0.1.7 → 0.1.8
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/cli.js +694 -16
- package/dist/cli.js.map +1 -1
- package/package.json +1 -1
- package/public/adjourn-overlay.css +194 -0
- package/public/app.js +426 -66
- package/public/bento.html +1396 -0
- package/public/home.html +389 -17
- package/public/index.html +22 -5
- package/public/magazine.html +1266 -0
- package/public/newspaper.html +1770 -0
- package/public/report.html +366 -52
- package/public/user-settings.css +94 -47
- package/public/user-settings.js +76 -42
package/public/report.html
CHANGED
|
@@ -588,6 +588,22 @@
|
|
|
588
588
|
border-top-width: 0;
|
|
589
589
|
}
|
|
590
590
|
|
|
591
|
+
/* ─── Hide mermaid's internal render scratchpad ──────────────
|
|
592
|
+
During `mermaid.render(id, src)`, mermaid 10 briefly appends a
|
|
593
|
+
`<div id="dmermaid-...">` to document.body to measure layout +
|
|
594
|
+
produce the SVG. The div is removed once render() resolves,
|
|
595
|
+
but in the meantime it can flash visibly. Force it offscreen
|
|
596
|
+
so the user never sees mermaid's pre-takeover render at all.
|
|
597
|
+
The element still has natural dimensions for mermaid's layout
|
|
598
|
+
measurement — only its position + visibility is constrained. */
|
|
599
|
+
body > [id^="dmermaid-"] {
|
|
600
|
+
position: absolute !important;
|
|
601
|
+
left: -99999px !important;
|
|
602
|
+
top: 0 !important;
|
|
603
|
+
visibility: hidden !important;
|
|
604
|
+
pointer-events: none !important;
|
|
605
|
+
}
|
|
606
|
+
|
|
591
607
|
/* ─── Hide mermaid SVG until spine palette is applied ─────────
|
|
592
608
|
Pattern from user reports: "first time wrong, refresh OK" /
|
|
593
609
|
strict alternation (1st wrong, 2nd right, 3rd wrong, 4th
|
|
@@ -611,6 +627,129 @@
|
|
|
611
627
|
visibility: visible;
|
|
612
628
|
}
|
|
613
629
|
|
|
630
|
+
/* ─── CSS-variable mermaid palette · timing-race-immune ──────
|
|
631
|
+
Final defense layer for the recurring "first time wrong,
|
|
632
|
+
refresh OK" bug. JS takeover fights timing with mermaid's
|
|
633
|
+
deferred render work; CSS doesn't have that problem — as
|
|
634
|
+
soon as the SVG is in the DOM, these rules apply. The
|
|
635
|
+
spine-specific colours are injected as CSS variables on
|
|
636
|
+
`:root` from JS in the swapSpine flow (see swapSpine /
|
|
637
|
+
mermaid.initialize block). Variables update when the spine
|
|
638
|
+
changes; CSS rules are static.
|
|
639
|
+
|
|
640
|
+
Specificity: each rule uses a 3-class+ chain plus
|
|
641
|
+
!important, beating mermaid's `.quadrant-fill { fill: ... }`
|
|
642
|
+
and any `fill="..."` presentation attribute. The element
|
|
643
|
+
selectors target every mermaid 10 chart-type's known
|
|
644
|
+
visual elements. */
|
|
645
|
+
|
|
646
|
+
/* Quadrant chart · 4 quadrant rects + data points */
|
|
647
|
+
.body pre.mermaid svg .quadrants .quadrant rect,
|
|
648
|
+
.body pre.mermaid svg g.quadrant > rect,
|
|
649
|
+
.body pre.mermaid svg [aria-roledescription="quadrantChart"] g.quadrant rect {
|
|
650
|
+
fill: var(--mm-q-fill, #1A1A18) !important;
|
|
651
|
+
}
|
|
652
|
+
.body pre.mermaid svg .data-points circle,
|
|
653
|
+
.body pre.mermaid svg circle.data-point,
|
|
654
|
+
.body pre.mermaid svg [aria-roledescription="quadrantChart"] .data-points circle {
|
|
655
|
+
fill: var(--mm-q-point, #C9A46B) !important;
|
|
656
|
+
stroke: var(--mm-q-bg, #1A1A18) !important;
|
|
657
|
+
}
|
|
658
|
+
.body pre.mermaid svg .quadrants line,
|
|
659
|
+
.body pre.mermaid svg .axis-line,
|
|
660
|
+
.body pre.mermaid svg .axisl-line,
|
|
661
|
+
.body pre.mermaid svg g.border line,
|
|
662
|
+
.body pre.mermaid svg [aria-roledescription="quadrantChart"] line,
|
|
663
|
+
.body pre.mermaid svg [aria-roledescription="quadrantChart"] g.border line {
|
|
664
|
+
stroke: var(--mm-q-border, #3A3934) !important;
|
|
665
|
+
}
|
|
666
|
+
.body pre.mermaid svg [aria-roledescription="quadrantChart"] text {
|
|
667
|
+
fill: var(--mm-q-text, #C8C5BE) !important;
|
|
668
|
+
}
|
|
669
|
+
.body pre.mermaid svg [aria-roledescription="quadrantChart"] .bottom-axis text,
|
|
670
|
+
.body pre.mermaid svg [aria-roledescription="quadrantChart"] .left-axis text,
|
|
671
|
+
.body pre.mermaid svg [aria-roledescription="quadrantChart"] .top-axis text,
|
|
672
|
+
.body pre.mermaid svg [aria-roledescription="quadrantChart"] .right-axis text {
|
|
673
|
+
fill: var(--mm-q-axis-text, #5C5A4D) !important;
|
|
674
|
+
}
|
|
675
|
+
.body pre.mermaid svg [aria-roledescription="quadrantChart"] .quadrant text {
|
|
676
|
+
fill: var(--mm-q-quad-text, #8E8B83) !important;
|
|
677
|
+
}
|
|
678
|
+
|
|
679
|
+
/* xychart-beta · bars cycle through palette via :nth-child */
|
|
680
|
+
.body pre.mermaid svg [aria-roledescription="xychart"] g[class*="bar-plot"] rect,
|
|
681
|
+
.body pre.mermaid svg [aria-roledescription="xychart"] g.plot rect {
|
|
682
|
+
fill: var(--mm-pal-0, #C9A46B) !important;
|
|
683
|
+
stroke: none !important;
|
|
684
|
+
}
|
|
685
|
+
.body pre.mermaid svg [aria-roledescription="xychart"] g[class*="bar-plot"] rect:nth-child(2),
|
|
686
|
+
.body pre.mermaid svg [aria-roledescription="xychart"] g.plot rect:nth-child(2) {
|
|
687
|
+
fill: var(--mm-pal-1, #B6B0A2) !important;
|
|
688
|
+
}
|
|
689
|
+
.body pre.mermaid svg [aria-roledescription="xychart"] g[class*="bar-plot"] rect:nth-child(3),
|
|
690
|
+
.body pre.mermaid svg [aria-roledescription="xychart"] g.plot rect:nth-child(3) {
|
|
691
|
+
fill: var(--mm-pal-2, #8E8B83) !important;
|
|
692
|
+
}
|
|
693
|
+
.body pre.mermaid svg [aria-roledescription="xychart"] g[class*="bar-plot"] rect:nth-child(n+4),
|
|
694
|
+
.body pre.mermaid svg [aria-roledescription="xychart"] g.plot rect:nth-child(n+4) {
|
|
695
|
+
fill: var(--mm-pal-3, #5C5A52) !important;
|
|
696
|
+
}
|
|
697
|
+
.body pre.mermaid svg [aria-roledescription="xychart"] text {
|
|
698
|
+
fill: var(--mm-axis-text, #5C5A4D) !important;
|
|
699
|
+
}
|
|
700
|
+
.body pre.mermaid svg [aria-roledescription="xychart"] .background,
|
|
701
|
+
.body pre.mermaid svg [aria-roledescription="xychart"] rect.background {
|
|
702
|
+
fill: transparent !important;
|
|
703
|
+
}
|
|
704
|
+
|
|
705
|
+
/* Pie · slice palette */
|
|
706
|
+
.body pre.mermaid svg path.pieCircle:nth-of-type(1) { fill: var(--mm-pal-0, #C9A46B) !important; stroke: var(--mm-q-bg, #1A1A18) !important; }
|
|
707
|
+
.body pre.mermaid svg path.pieCircle:nth-of-type(2) { fill: var(--mm-pal-1, #B6B0A2) !important; }
|
|
708
|
+
.body pre.mermaid svg path.pieCircle:nth-of-type(3) { fill: var(--mm-pal-2, #8E8B83) !important; }
|
|
709
|
+
.body pre.mermaid svg path.pieCircle:nth-of-type(n+4) { fill: var(--mm-pal-3, #5C5A52) !important; }
|
|
710
|
+
.body pre.mermaid svg circle.pieOuterCircle {
|
|
711
|
+
stroke: var(--mm-q-border, #3A3934) !important;
|
|
712
|
+
fill: none !important;
|
|
713
|
+
}
|
|
714
|
+
|
|
715
|
+
/* Universal text safety net inside any mermaid SVG · titleFill
|
|
716
|
+
for anything that escaped the per-type rules above. Lower
|
|
717
|
+
specificity than the type-specific rules so they still win. */
|
|
718
|
+
.body pre.mermaid svg text {
|
|
719
|
+
fill: var(--mm-text, #C8C5BE); /* no !important · type-specific rules above WIN */
|
|
720
|
+
}
|
|
721
|
+
|
|
722
|
+
/* ─── Chart display sizes · CSS-enforced ─────────────────────
|
|
723
|
+
Mermaid 10.9.5's chartWidth/chartHeight config keys for
|
|
724
|
+
quadrantChart and xychart are silently ignored by the
|
|
725
|
+
bundled CDN build (verified via DOM dump showing default
|
|
726
|
+
500×500 viewBox even when chartWidth: 460 is configured).
|
|
727
|
+
CSS-enforced sizes via `max-width` on the rendered SVG let
|
|
728
|
+
us get refined-compact dimensions regardless of what
|
|
729
|
+
mermaid's config validator does. The viewBox stays at
|
|
730
|
+
mermaid's internal coordinate space (500×500 etc.) but the
|
|
731
|
+
displayed SVG scales down to our target size. Centered with
|
|
732
|
+
margin: 0 auto so the chart sits flush in the article column
|
|
733
|
+
instead of stretching to full width. */
|
|
734
|
+
.body pre.mermaid svg[aria-roledescription="quadrantChart"] {
|
|
735
|
+
max-width: 460px !important;
|
|
736
|
+
height: auto !important;
|
|
737
|
+
margin: 0 auto !important;
|
|
738
|
+
display: block !important;
|
|
739
|
+
}
|
|
740
|
+
.body pre.mermaid svg[aria-roledescription="xychart"] {
|
|
741
|
+
max-width: 560px !important;
|
|
742
|
+
height: auto !important;
|
|
743
|
+
margin: 0 auto !important;
|
|
744
|
+
display: block !important;
|
|
745
|
+
}
|
|
746
|
+
.body pre.mermaid svg[aria-roledescription="pie"] {
|
|
747
|
+
max-width: 420px !important;
|
|
748
|
+
height: auto !important;
|
|
749
|
+
margin: 0 auto !important;
|
|
750
|
+
display: block !important;
|
|
751
|
+
}
|
|
752
|
+
|
|
614
753
|
/* ─── Metric-strip · spine-agnostic baseline ─────────────────────
|
|
615
754
|
Editorial / Swiss register: oversized thin numerals top-left, mute
|
|
616
755
|
mono label bottom-left, hairline grid (no card backgrounds, no
|
|
@@ -4806,6 +4945,31 @@
|
|
|
4806
4945
|
},
|
|
4807
4946
|
};
|
|
4808
4947
|
const t = themes[spineKey];
|
|
4948
|
+
|
|
4949
|
+
// Inject spine palette as CSS variables on :root so the
|
|
4950
|
+
// CSS rules in the global <style> block (mermaid spine
|
|
4951
|
+
// CSS) resolve to the right colours. This is the
|
|
4952
|
+
// PRIMARY mechanism — CSS applies on every paint, no JS
|
|
4953
|
+
// timing race possible. The JS takeover below stays as
|
|
4954
|
+
// belt-and-suspenders for chart elements not covered by
|
|
4955
|
+
// the CSS rules.
|
|
4956
|
+
try {
|
|
4957
|
+
const root = document.documentElement.style;
|
|
4958
|
+
root.setProperty('--mm-q-fill', t.vars.quadrantFill);
|
|
4959
|
+
root.setProperty('--mm-q-bg', t.vars.background);
|
|
4960
|
+
root.setProperty('--mm-q-point', t.vars.pointFill);
|
|
4961
|
+
root.setProperty('--mm-q-border', t.vars.border);
|
|
4962
|
+
root.setProperty('--mm-q-text', t.vars.titleFill);
|
|
4963
|
+
root.setProperty('--mm-q-axis-text', t.vars.axisText);
|
|
4964
|
+
root.setProperty('--mm-q-quad-text', t.vars.quadrantText);
|
|
4965
|
+
root.setProperty('--mm-text', t.vars.titleFill);
|
|
4966
|
+
root.setProperty('--mm-axis-text', t.vars.axisText);
|
|
4967
|
+
root.setProperty('--mm-pal-0', t.palette[0]);
|
|
4968
|
+
root.setProperty('--mm-pal-1', t.palette[1]);
|
|
4969
|
+
root.setProperty('--mm-pal-2', t.palette[2]);
|
|
4970
|
+
root.setProperty('--mm-pal-3', t.palette[3]);
|
|
4971
|
+
} catch (_) {}
|
|
4972
|
+
|
|
4809
4973
|
// Build pie / journey / state / git slice-color overrides
|
|
4810
4974
|
// by cycling through the spine's monochrome `palette`.
|
|
4811
4975
|
// Without these, mermaid 11+ paints pie1..pie12 +
|
|
@@ -5418,47 +5582,74 @@
|
|
|
5418
5582
|
useMaxWidth: true,
|
|
5419
5583
|
},
|
|
5420
5584
|
});
|
|
5421
|
-
|
|
5422
|
-
//
|
|
5423
|
-
//
|
|
5424
|
-
//
|
|
5425
|
-
//
|
|
5426
|
-
//
|
|
5427
|
-
// playing whack-a-mole with mermaid's internals, we let
|
|
5428
|
-
// mermaid render the structure, then:
|
|
5429
|
-
// 1. WIPE mermaid's embedded <style> block in each SVG
|
|
5430
|
-
// (eliminates the CSS-vs-attribute specificity gotcha
|
|
5431
|
-
// where <style> rules silently beat setAttribute calls)
|
|
5432
|
-
// 2. Force-set fill/stroke via inline `style` attribute on
|
|
5433
|
-
// every visual element by class — inline style has the
|
|
5434
|
-
// highest CSS specificity, so nothing in the cascade
|
|
5435
|
-
// can override our spine palette.
|
|
5436
|
-
// Result: every chart element renders in the spine's colors
|
|
5437
|
-
// regardless of mermaid version, theme preset, or which
|
|
5438
|
-
// themeVariables we pass.
|
|
5439
|
-
// Define the takeover as a re-runnable function so we can
|
|
5440
|
-
// schedule it multiple times to defeat first-load timing
|
|
5441
|
-
// races. Pattern observed: on cold loads (CDN uncached, CSS
|
|
5442
|
-
// uncached) mermaid does async post-render work via
|
|
5443
|
-
// requestAnimationFrame / setTimeout AFTER the run() promise
|
|
5444
|
-
// resolves — re-injecting <style>, recomputing layouts, etc.
|
|
5445
|
-
// A single sync takeover then loses the race because mermaid
|
|
5446
|
-
// overwrites our inline styles afterward. On refresh
|
|
5447
|
-
// everything's cached, mermaid finishes faster, and a single
|
|
5448
|
-
// takeover wins by accident.
|
|
5585
|
+
// ── Off-screen render + paint-then-insert ────────────────
|
|
5586
|
+
// DEFINITIVE fix for "first time wrong, refresh OK" /
|
|
5587
|
+
// strict alternation pattern. Earlier strategies all let
|
|
5588
|
+
// mermaid render in-place, then patched colors after —
|
|
5589
|
+
// which gave the user a visible window of mermaid-default
|
|
5590
|
+
// colors before our takeover landed.
|
|
5449
5591
|
//
|
|
5450
|
-
//
|
|
5451
|
-
//
|
|
5452
|
-
//
|
|
5453
|
-
//
|
|
5454
|
-
|
|
5455
|
-
|
|
5592
|
+
// The only race-free approach: render OFFSCREEN, paint our
|
|
5593
|
+
// spine palette onto the SVG while it's still detached,
|
|
5594
|
+
// THEN insert into the visible DOM. The user's first paint
|
|
5595
|
+
// sees the spine-colored SVG; there is no intermediate
|
|
5596
|
+
// mermaid-default state to perceive.
|
|
5597
|
+
//
|
|
5598
|
+
// mermaid 10's `render(id, src)` returns the SVG as a
|
|
5599
|
+
// string (not in-DOM) — perfect for this flow. We loop
|
|
5600
|
+
// through every `pre.mermaid`, render its source via
|
|
5601
|
+
// `mermaid.render`, parse the resulting string, apply the
|
|
5602
|
+
// takeover to the parsed (still-detached) SVG, then swap
|
|
5603
|
+
// the corrected SVG into the visible container.
|
|
5604
|
+
// Belt-and-suspenders fill setter · sets BOTH the inline
|
|
5605
|
+
// `style="fill: X !important"` AND the SVG `fill="X"`
|
|
5606
|
+
// presentation attribute. With mermaid's embedded <style>
|
|
5607
|
+
// already wiped, either layer is sufficient — but setting
|
|
5608
|
+
// both means even if one is somehow stripped/overridden
|
|
5609
|
+
// (browser quirk, late mermaid post-processing, CSS
|
|
5610
|
+
// cascade edge case), the chart still renders in the
|
|
5611
|
+
// spine palette.
|
|
5612
|
+
const setFill = (el, c) => {
|
|
5613
|
+
if (!c) return;
|
|
5614
|
+
try { el.style.setProperty('fill', c, 'important'); } catch (_) {}
|
|
5615
|
+
try { if (el.setAttribute) el.setAttribute('fill', c); } catch (_) {}
|
|
5616
|
+
};
|
|
5617
|
+
const setStroke = (el, c) => {
|
|
5618
|
+
if (c === undefined || c === null) return;
|
|
5619
|
+
try { el.style.setProperty('stroke', c, 'important'); } catch (_) {}
|
|
5620
|
+
try { if (el.setAttribute) el.setAttribute('stroke', c); } catch (_) {}
|
|
5621
|
+
};
|
|
5456
5622
|
const setColor = (el, c) => { if (c) el.style.setProperty('color', c, 'important'); };
|
|
5457
5623
|
const cyclePalette = (i) => t.palette[i % t.palette.length];
|
|
5458
5624
|
|
|
5459
|
-
|
|
5625
|
+
// Apply the spine palette to ONE SVG element (offscreen or
|
|
5626
|
+
// onscreen — works either way since every fill/stroke is
|
|
5627
|
+
// forced via inline `!important`). Pure function: no
|
|
5628
|
+
// global queries, only operates on the passed element.
|
|
5629
|
+
const applySpineToSvg = (svg) => {
|
|
5460
5630
|
try {
|
|
5461
|
-
|
|
5631
|
+
// ── 0. Pin the SVG max-width per chart type ──
|
|
5632
|
+
// Mermaid 10's chartWidth/chartHeight config is silently
|
|
5633
|
+
// ignored by the bundled CDN build, so the SVG ships at
|
|
5634
|
+
// its default size with `style="max-width: 500px"`
|
|
5635
|
+
// inline. CSS in <style> overrides it at paint time, but
|
|
5636
|
+
// there's a one-frame race on cold loads where the
|
|
5637
|
+
// user can perceive the unconstrained size before CSS
|
|
5638
|
+
// applies. Pin via inline style with !important here
|
|
5639
|
+
// for refined-compact dimensions on every first paint.
|
|
5640
|
+
const role = svg.getAttribute('aria-roledescription') || '';
|
|
5641
|
+
const chartMaxWidth = (
|
|
5642
|
+
role === 'quadrantChart' ? '460px' :
|
|
5643
|
+
role === 'xychart' ? '560px' :
|
|
5644
|
+
role === 'pie' ? '420px' :
|
|
5645
|
+
''
|
|
5646
|
+
);
|
|
5647
|
+
if (chartMaxWidth) {
|
|
5648
|
+
svg.style.setProperty('max-width', chartMaxWidth, 'important');
|
|
5649
|
+
svg.style.setProperty('height', 'auto', 'important');
|
|
5650
|
+
svg.style.setProperty('margin', '0 auto', 'important');
|
|
5651
|
+
svg.style.setProperty('display', 'block', 'important');
|
|
5652
|
+
}
|
|
5462
5653
|
// ── 1. Wipe mermaid's embedded <style> block ──
|
|
5463
5654
|
// Mermaid generates `<style>.quadrant-chart > .quadrant-
|
|
5464
5655
|
// point > circle { fill: #...; }` etc. inside each SVG;
|
|
@@ -5478,16 +5669,39 @@
|
|
|
5478
5669
|
|
|
5479
5670
|
// ── 3. Quadrant chart ──
|
|
5480
5671
|
// Mermaid 10's actual class names (verified against rendered DOM):
|
|
5481
|
-
//
|
|
5482
|
-
// `.quadrants` — parent group
|
|
5483
|
-
//
|
|
5672
|
+
// `<g class="quadrant">` — group; each contains a child <rect>
|
|
5673
|
+
// `.quadrants` — outer parent group
|
|
5674
|
+
// `<circle class="data-point">` — each data circle
|
|
5484
5675
|
// `.data-points` — parent group
|
|
5485
5676
|
// `.axis-line` / `.axisl-line` — axis lines
|
|
5486
|
-
// `.bottom-axis`, `.left-axis` — axis text
|
|
5487
|
-
//
|
|
5488
|
-
//
|
|
5489
|
-
|
|
5490
|
-
|
|
5677
|
+
// `.bottom-axis`, `.left-axis` — axis text containers
|
|
5678
|
+
// CRITICAL: the `.quadrant` class is on a <g>, but the
|
|
5679
|
+
// actual fillable rect is INSIDE the group. Selecting
|
|
5680
|
+
// `.quadrants > .quadrant` matches the <g> only;
|
|
5681
|
+
// setting style.fill on a <g> does NOT override the
|
|
5682
|
+
// child <rect>'s `fill="..."` attribute in SVG. We
|
|
5683
|
+
// must select the inner rect directly.
|
|
5684
|
+
//
|
|
5685
|
+
// Belt-and-suspenders: also match ALL <rect> inside
|
|
5686
|
+
// any quadrantChart SVG (excluding the canvas-bg rect
|
|
5687
|
+
// we already handled via dimensions). Catches any
|
|
5688
|
+
// mermaid version variation in class naming.
|
|
5689
|
+
svg.querySelectorAll('.quadrants .quadrant rect, g.quadrant > rect, rect.quadrant, rect.quadrant-fill, .quadrant-fill').forEach((el) => setFill(el, t.vars.quadrantFill));
|
|
5690
|
+
if (svg.getAttribute('aria-roledescription') === 'quadrantChart') {
|
|
5691
|
+
const qsw = parseFloat(svg.getAttribute('width') || '0');
|
|
5692
|
+
svg.querySelectorAll('rect').forEach((el) => {
|
|
5693
|
+
// Skip the canvas-bg rect (full-width).
|
|
5694
|
+
const w = parseFloat(el.getAttribute('width') || '0');
|
|
5695
|
+
if (qsw > 0 && w >= qsw * 0.95) return;
|
|
5696
|
+
setFill(el, t.vars.quadrantFill);
|
|
5697
|
+
});
|
|
5698
|
+
}
|
|
5699
|
+
// Quadrant border lines · mermaid 10 puts the outer
|
|
5700
|
+
// rectangle + cross-divider lines inside `<g class="border">`,
|
|
5701
|
+
// a sibling of `.quadrants` (NOT a descendant). Catch
|
|
5702
|
+
// both the inside-quadrants axis-line case AND the
|
|
5703
|
+
// border group case.
|
|
5704
|
+
svg.querySelectorAll('.axis-line, .axisl-line, .quadrants line, g.border line, .border line').forEach((el) => setStroke(el, t.vars.border));
|
|
5491
5705
|
svg.querySelectorAll('.quadrant-internal-border, line.quadrant-internal-border').forEach((el) => setStroke(el, t.vars.inner));
|
|
5492
5706
|
svg.querySelectorAll('.quadrant-external-border, rect.quadrant-external-border').forEach((el) => {
|
|
5493
5707
|
setStroke(el, t.vars.border);
|
|
@@ -5695,20 +5909,120 @@
|
|
|
5695
5909
|
|
|
5696
5910
|
// ── 16. Mark the parent pre.mermaid as painted ──
|
|
5697
5911
|
// CSS hides `.body pre.mermaid svg` until `.is-painted`
|
|
5698
|
-
// is added
|
|
5699
|
-
//
|
|
5700
|
-
//
|
|
5701
|
-
// (last step of each takeover pass) reveals the SVG only
|
|
5702
|
-
// after our spine fills are pinned.
|
|
5912
|
+
// is added on the parent. We add it here so that even
|
|
5913
|
+
// when the SVG is later inserted into the live DOM, the
|
|
5914
|
+
// CSS-based hide is already lifted (no flash).
|
|
5703
5915
|
const host = svg.closest('pre.mermaid');
|
|
5704
5916
|
if (host && !host.classList.contains('is-painted')) {
|
|
5705
5917
|
host.classList.add('is-painted');
|
|
5706
5918
|
}
|
|
5707
|
-
}); // forEach svg
|
|
5708
5919
|
} catch (forceColorErr) {
|
|
5709
|
-
console.warn('[mermaid]
|
|
5920
|
+
console.warn('[mermaid] palette takeover failed for svg:', forceColorErr);
|
|
5921
|
+
}
|
|
5922
|
+
}; // applySpineToSvg
|
|
5923
|
+
|
|
5924
|
+
// Wrapper · walks every onscreen SVG and applies the
|
|
5925
|
+
// takeover. Used by the safety polling + MutationObserver
|
|
5926
|
+
// (defense-in-depth) for any SVGs that were already
|
|
5927
|
+
// inserted before the offscreen-render loop took over.
|
|
5928
|
+
const applySpinePaletteOnce = () => {
|
|
5929
|
+
document.querySelectorAll('.mermaid svg').forEach(applySpineToSvg);
|
|
5930
|
+
};
|
|
5931
|
+
|
|
5932
|
+
// ── Offscreen render via mermaid.run + DOM relocation ─
|
|
5933
|
+
// DEFINITIVE fix for "first load wrong / strict
|
|
5934
|
+
// alternation." Keep mermaid.run (it handles lazy module
|
|
5935
|
+
// loading for diagram types correctly — mermaid.render's
|
|
5936
|
+
// first call races with that loading on cold cache),
|
|
5937
|
+
// but move every pre.mermaid INTO AN OFFSCREEN CONTAINER
|
|
5938
|
+
// before running. mermaid renders inside the offscreen
|
|
5939
|
+
// container (user can't see it), we apply the spine
|
|
5940
|
+
// takeover to every resulting SVG, then move each
|
|
5941
|
+
// pre.mermaid back to its original DOM position. The
|
|
5942
|
+
// user's first paint of each chart shows the
|
|
5943
|
+
// spine-coloured SVG; no race possible.
|
|
5944
|
+
//
|
|
5945
|
+
// Diagnostic logs (console.info) so the user can verify
|
|
5946
|
+
// which path was taken and at what timing — open DevTools
|
|
5947
|
+
// and refresh; the log narrates the flow.
|
|
5948
|
+
const _mLog = (msg, ...rest) => {
|
|
5949
|
+
try { console.info(`[mermaid-spine] ${msg}`, ...rest); } catch (_) {}
|
|
5950
|
+
};
|
|
5951
|
+
_mLog('flow start · spine =', spineKey);
|
|
5952
|
+
const charts = Array.from(document.querySelectorAll('pre.mermaid'));
|
|
5953
|
+
_mLog('charts found:', charts.length);
|
|
5954
|
+
if (charts.length > 0) {
|
|
5955
|
+
// Capture original DOM positions so we can restore each
|
|
5956
|
+
// pre.mermaid exactly where it was.
|
|
5957
|
+
const anchors = charts.map((el) => ({
|
|
5958
|
+
el,
|
|
5959
|
+
parent: el.parentNode,
|
|
5960
|
+
nextSibling: el.nextSibling,
|
|
5961
|
+
}));
|
|
5962
|
+
// Build the offscreen host. Width matches a typical
|
|
5963
|
+
// article column so mermaid sizes the SVG correctly
|
|
5964
|
+
// (useMaxWidth: true uses the offscreen container's
|
|
5965
|
+
// dimensions for layout). Height: 0 + overflow: visible
|
|
5966
|
+
// so mermaid can compute proper SVG sizes.
|
|
5967
|
+
const offscreen = document.createElement('div');
|
|
5968
|
+
offscreen.id = 'mermaid-offscreen-host';
|
|
5969
|
+
offscreen.style.cssText = [
|
|
5970
|
+
'position: absolute',
|
|
5971
|
+
'left: -99999px',
|
|
5972
|
+
'top: 0',
|
|
5973
|
+
'width: 880px',
|
|
5974
|
+
'visibility: hidden',
|
|
5975
|
+
'pointer-events: none',
|
|
5976
|
+
'z-index: -1',
|
|
5977
|
+
].join('; ');
|
|
5978
|
+
document.body.appendChild(offscreen);
|
|
5979
|
+
// Move each pre.mermaid into the offscreen host.
|
|
5980
|
+
anchors.forEach((a) => offscreen.appendChild(a.el));
|
|
5981
|
+
_mLog('moved offscreen, calling mermaid.run');
|
|
5982
|
+
// Run mermaid · scoped to the offscreen host so it only
|
|
5983
|
+
// touches elements we just moved. mermaid handles lazy
|
|
5984
|
+
// module registration internally.
|
|
5985
|
+
try {
|
|
5986
|
+
await mermaid.run({ querySelector: '#mermaid-offscreen-host pre.mermaid' });
|
|
5987
|
+
_mLog('mermaid.run resolved');
|
|
5988
|
+
} catch (runErr) {
|
|
5989
|
+
console.warn('[mermaid-spine] offscreen run failed:', runErr);
|
|
5710
5990
|
}
|
|
5711
|
-
|
|
5991
|
+
// Wait for mermaid's deferred post-render work to land.
|
|
5992
|
+
// Quadrant chart (specifically) does d3-based label
|
|
5993
|
+
// positioning in a setTimeout(0) AFTER mermaid.run
|
|
5994
|
+
// resolves — if we apply takeover before that fires,
|
|
5995
|
+
// mermaid overwrites our fills. Two rAF + setTimeout(0)
|
|
5996
|
+
// cycles guarantee deferred work has settled. Cheap
|
|
5997
|
+
// (~32ms) and only runs once per page load.
|
|
5998
|
+
await new Promise((r) => requestAnimationFrame(() => requestAnimationFrame(() => setTimeout(r, 0))));
|
|
5999
|
+
// Apply takeover to all SVGs while still offscreen.
|
|
6000
|
+
const svgsOffscreen = offscreen.querySelectorAll('svg');
|
|
6001
|
+
_mLog('SVGs rendered offscreen:', svgsOffscreen.length);
|
|
6002
|
+
svgsOffscreen.forEach(applySpineToSvg);
|
|
6003
|
+
_mLog('takeover applied offscreen');
|
|
6004
|
+
// Move each pre.mermaid back to its original position
|
|
6005
|
+
// and mark as painted (CSS reveal rule). Single DOM
|
|
6006
|
+
// mutation per chart → single paint frame with the
|
|
6007
|
+
// corrected SVG already in place.
|
|
6008
|
+
anchors.forEach((a) => {
|
|
6009
|
+
a.el.classList.add('is-painted');
|
|
6010
|
+
if (a.parent) {
|
|
6011
|
+
if (a.nextSibling && a.nextSibling.parentNode === a.parent) {
|
|
6012
|
+
a.parent.insertBefore(a.el, a.nextSibling);
|
|
6013
|
+
} else {
|
|
6014
|
+
a.parent.appendChild(a.el);
|
|
6015
|
+
}
|
|
6016
|
+
}
|
|
6017
|
+
// Re-apply takeover immediately after move · catches
|
|
6018
|
+
// any post-attach mermaid behaviour (ResizeObserver
|
|
6019
|
+
// re-renders, deferred rAF) that might restyle.
|
|
6020
|
+
a.el.querySelectorAll('svg').forEach(applySpineToSvg);
|
|
6021
|
+
});
|
|
6022
|
+
_mLog('moved back onscreen + reapplied takeover');
|
|
6023
|
+
// Cleanup offscreen host.
|
|
6024
|
+
offscreen.remove();
|
|
6025
|
+
}
|
|
5712
6026
|
|
|
5713
6027
|
// Safety net · if the takeover errors out for some reason
|
|
5714
6028
|
// (script bug, exotic chart type), make sure every chart
|
package/public/user-settings.css
CHANGED
|
@@ -261,50 +261,76 @@
|
|
|
261
261
|
margin-top: 5px;
|
|
262
262
|
}
|
|
263
263
|
|
|
264
|
-
/* ─── Toggle row ·
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
264
|
+
/* ─── Toggle row · proper switch + supporting deck text ───
|
|
265
|
+
Switch shape (track + sliding thumb) instead of the prior pill
|
|
266
|
+
button: the affordance reads "on/off state" at a glance, not
|
|
267
|
+
"press me to do something." Track is a 32×18 rounded rect, thumb
|
|
268
|
+
is a 14×14 disc that slides 14px between rest positions. Lime
|
|
269
|
+
fill when on; panel-3 fill + hairline border when off. */
|
|
268
270
|
.us-toggle-row {
|
|
269
271
|
display: flex;
|
|
270
272
|
align-items: center;
|
|
271
273
|
gap: 12px;
|
|
272
274
|
flex-wrap: wrap;
|
|
273
275
|
}
|
|
274
|
-
.us-
|
|
276
|
+
.us-switch {
|
|
275
277
|
appearance: none;
|
|
276
278
|
background: transparent;
|
|
277
|
-
border:
|
|
278
|
-
|
|
279
|
-
font-family: var(--mono);
|
|
280
|
-
font-size: 10.5px;
|
|
281
|
-
letter-spacing: 0.18em;
|
|
282
|
-
text-transform: uppercase;
|
|
283
|
-
font-weight: 700;
|
|
284
|
-
padding: 6px 14px;
|
|
279
|
+
border: none;
|
|
280
|
+
padding: 0;
|
|
285
281
|
display: inline-flex;
|
|
286
282
|
align-items: center;
|
|
287
|
-
gap:
|
|
283
|
+
gap: 10px;
|
|
288
284
|
cursor: pointer;
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
.us-toggle-pill:hover {
|
|
292
|
-
color: var(--text, #C8C5BE);
|
|
293
|
-
border-color: var(--text-faint, #3A382F);
|
|
285
|
+
font-family: var(--mono);
|
|
286
|
+
color: inherit;
|
|
294
287
|
}
|
|
295
|
-
.us-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
288
|
+
.us-switch-track {
|
|
289
|
+
position: relative;
|
|
290
|
+
display: inline-block;
|
|
291
|
+
width: 32px;
|
|
292
|
+
height: 18px;
|
|
293
|
+
background: var(--panel-3, #21211E);
|
|
294
|
+
border: 0.5px solid var(--line-bright, #2A2A26);
|
|
295
|
+
border-radius: 9px;
|
|
296
|
+
flex-shrink: 0;
|
|
297
|
+
transition: background 0.18s, border-color 0.18s;
|
|
299
298
|
}
|
|
300
|
-
.us-
|
|
301
|
-
|
|
302
|
-
|
|
299
|
+
.us-switch-thumb {
|
|
300
|
+
position: absolute;
|
|
301
|
+
top: 1px;
|
|
302
|
+
left: 1px;
|
|
303
|
+
width: 14px;
|
|
304
|
+
height: 14px;
|
|
303
305
|
border-radius: 50%;
|
|
304
306
|
background: var(--text-faint, #3A382F);
|
|
305
|
-
transition: background 0.
|
|
307
|
+
transition: transform 0.18s ease, background 0.18s;
|
|
308
|
+
}
|
|
309
|
+
.us-switch.on .us-switch-track {
|
|
310
|
+
background: var(--lime, #6FB572);
|
|
311
|
+
border-color: var(--lime, #6FB572);
|
|
306
312
|
}
|
|
307
|
-
.us-
|
|
313
|
+
.us-switch.on .us-switch-thumb {
|
|
314
|
+
transform: translateX(14px);
|
|
315
|
+
background: var(--bg, #0A0A0A);
|
|
316
|
+
}
|
|
317
|
+
.us-switch:hover .us-switch-track { border-color: var(--text-faint, #3A382F); }
|
|
318
|
+
.us-switch.on:hover .us-switch-track { border-color: var(--lime, #6FB572); }
|
|
319
|
+
.us-switch:focus-visible { outline: none; }
|
|
320
|
+
.us-switch:focus-visible .us-switch-track {
|
|
321
|
+
outline: 1.5px solid var(--lime, #6FB572);
|
|
322
|
+
outline-offset: 2px;
|
|
323
|
+
}
|
|
324
|
+
.us-switch-label {
|
|
325
|
+
font-size: 10.5px;
|
|
326
|
+
letter-spacing: 0.18em;
|
|
327
|
+
text-transform: uppercase;
|
|
328
|
+
font-weight: 700;
|
|
329
|
+
color: var(--text-soft, #8E8B83);
|
|
330
|
+
transition: color 0.12s;
|
|
331
|
+
}
|
|
332
|
+
.us-switch.on .us-switch-label { color: var(--lime, #6FB572); }
|
|
333
|
+
|
|
308
334
|
.us-toggle-deck {
|
|
309
335
|
font-size: 11px;
|
|
310
336
|
color: var(--text-faint, #3A382F);
|
|
@@ -895,9 +921,11 @@
|
|
|
895
921
|
display: flex;
|
|
896
922
|
align-items: flex-end;
|
|
897
923
|
gap: 4px;
|
|
898
|
-
height:
|
|
924
|
+
height: 180px;
|
|
899
925
|
/* The stack within each bar lives at the bottom (flex-end) so
|
|
900
|
-
bar height grows upward like a real chart axis.
|
|
926
|
+
bar height grows upward like a real chart axis. Bumped from
|
|
927
|
+
96px → 180px so the chart reads as a real exhibit instead of
|
|
928
|
+
a squashed sparkline. */
|
|
901
929
|
}
|
|
902
930
|
.us-chart-bar {
|
|
903
931
|
appearance: none;
|
|
@@ -915,27 +943,46 @@
|
|
|
915
943
|
position: relative;
|
|
916
944
|
}
|
|
917
945
|
.us-chart-bar:hover .us-chart-stack { filter: brightness(1.15); }
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
content: "";
|
|
946
|
+
/* Hover tooltip · two-line custom tip rendered via ::before from the
|
|
947
|
+
bar's data-tip-day / data-tip-num attributes. Floats above the
|
|
948
|
+
column so it doesn't overlap the stack or get clipped by the bar's
|
|
949
|
+
contents. Native browser title= is replaced by aria-label= on the
|
|
950
|
+
button to keep a11y without the 500ms native-tooltip delay.
|
|
951
|
+
Suppressed on `.empty` bars · zero-usage days show no tooltip
|
|
952
|
+
(the row is already empty; a tooltip saying "no usage" is noise). */
|
|
953
|
+
.us-chart-bar:not(.empty)::before {
|
|
954
|
+
content: attr(data-tip-day) "\A" attr(data-tip-num);
|
|
955
|
+
white-space: pre;
|
|
956
|
+
text-align: center;
|
|
927
957
|
position: absolute;
|
|
928
|
-
|
|
929
|
-
|
|
958
|
+
bottom: calc(100% + 6px);
|
|
959
|
+
left: 50%;
|
|
960
|
+
transform: translateX(-50%);
|
|
961
|
+
background: var(--bg, #0A0A0A);
|
|
962
|
+
border: 1px solid var(--line-bright, #2A2A26);
|
|
963
|
+
padding: 6px 10px;
|
|
964
|
+
font-family: var(--mono, "Inter", system-ui, sans-serif);
|
|
965
|
+
font-size: 10px;
|
|
966
|
+
line-height: 1.5;
|
|
967
|
+
letter-spacing: 0.04em;
|
|
968
|
+
color: var(--text, #C8C5BE);
|
|
969
|
+
opacity: 0;
|
|
930
970
|
pointer-events: none;
|
|
971
|
+
transition: opacity 0.12s ease;
|
|
972
|
+
z-index: 10;
|
|
931
973
|
}
|
|
932
|
-
.us-chart-bar.
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
border: 1px solid var(--lime, #6FB572);
|
|
937
|
-
pointer-events: none;
|
|
974
|
+
.us-chart-bar:not(.empty):hover::before { opacity: 1; }
|
|
975
|
+
.us-chart-bar.empty .us-chart-stack {
|
|
976
|
+
height: 1px !important;
|
|
977
|
+
background: var(--line-bright, #2A2A26);
|
|
938
978
|
}
|
|
979
|
+
/* Today / active markers carry only on the tick label below
|
|
980
|
+
(color shift) — no perimeter border around the bar slot.
|
|
981
|
+
Earlier the today/active states drew an outline ring via
|
|
982
|
+
`::before` which read as an ugly box around an otherwise
|
|
983
|
+
clean stacked-bar; the tick-colour change carries enough
|
|
984
|
+
signal. See `.us-chart-bar.today .us-chart-tick` and
|
|
985
|
+
`.us-chart-bar.active .us-chart-tick` below. */
|
|
939
986
|
.us-chart-stack {
|
|
940
987
|
display: flex;
|
|
941
988
|
flex-direction: column-reverse; /* first segment at the bottom */
|