@digilogiclabs/saas-factory-ui 1.30.0 → 1.30.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/index.d.mts +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +134 -24
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +134 -24
- package/dist/index.mjs.map +1 -1
- package/dist/web/index.d.mts +1 -1
- package/dist/web/index.d.ts +1 -1
- package/dist/web/index.js +134 -24
- package/dist/web/index.js.map +1 -1
- package/dist/web/index.mjs +134 -24
- package/dist/web/index.mjs.map +1 -1
- package/package.json +1 -1
package/dist/index.d.mts
CHANGED
|
@@ -5293,7 +5293,7 @@ declare function FlowTree({ data, title, eyebrow, tagline, renderIcon: renderIco
|
|
|
5293
5293
|
* delta: it keeps connector strokes crisp under the pan/zoom transform's
|
|
5294
5294
|
* `scale()` (otherwise sub-1 fit-zoom renders them sub-pixel and invisible).
|
|
5295
5295
|
*/
|
|
5296
|
-
declare const FLOW_TREE_CSS = "\n@import url(\"https://fonts.googleapis.com/css2?family=Instrument+Serif:ital@0;1&family=Inter:wght@400;500;600&family=JetBrains+Mono:wght@400;500&display=swap\");\n\n.ft-shell {\n --ft-bg: oklch(0.165 0.008 80);\n --ft-panel: oklch(0.21 0.008 80);\n --ft-panel-2: oklch(0.25 0.01 80);\n --ft-fg: oklch(0.96 0.005 85);\n --ft-muted-fg: oklch(0.68 0.008 80);\n --ft-dim-fg: oklch(0.5 0.008 80);\n --ft-line: oklch(0.28 0.008 80);\n --ft-line-strong: oklch(0.4 0.008 80);\n --ft-grid-strong: oklch(0.24 0.008 80);\n --ft-tier-orange: #f16c25;\n --ft-tier-teal: #5fb7b8;\n --ft-tier-blue: oklch(0.72 0.12 240);\n --ft-tier-amber: oklch(0.8 0.14 75);\n --ft-tier-green: oklch(0.74 0.14 155);\n --ft-accent: var(--ft-tier-orange);\n --ft-serif: \"Instrument Serif\", Georgia, serif;\n --ft-sans: \"Inter\", system-ui, sans-serif;\n --ft-mono: \"JetBrains Mono\", ui-monospace, Menlo, monospace;\n\n /* Defensive shell sizing \u2014 fills flex parents (flex: 1) and block\n parents with definite dimensions (width/height: 100%). */\n flex: 1 1 auto;\n width: 100%;\n height: 100%;\n min-height: 420px;\n display: flex;\n flex-direction: column;\n background: var(--ft-bg);\n border: 1px solid var(--ft-line);\n border-radius: 2px;\n overflow: hidden;\n color: var(--ft-fg);\n font-family: var(--ft-sans);\n font-size: 14px;\n line-height: 1.5;\n -webkit-font-smoothing: antialiased;\n box-shadow: 0 30px 60px -30px oklch(0 0 0 / 0.5);\n}\n.ft-shell * { box-sizing: border-box; }\n.ft-shell .ft-mono {\n font-family: var(--ft-mono);\n font-size: 11px;\n letter-spacing: 0.02em;\n}\n\n.ft-header {\n padding: 22px 28px 20px;\n border-bottom: 1px solid var(--ft-line);\n background: linear-gradient(180deg, oklch(0.21 0.01 60) 0%, oklch(0.19 0.008 70) 100%);\n display: flex;\n flex-direction: column;\n gap: 6px;\n}\n.ft-header__eyebrow {\n font-family: var(--ft-mono);\n font-size: 10.5px;\n letter-spacing: 0.22em;\n text-transform: uppercase;\n color: var(--ft-tier-orange);\n}\n.ft-header__title {\n margin: 0;\n font-family: var(--ft-serif);\n font-weight: 400;\n font-size: 44px;\n line-height: 1;\n letter-spacing: -0.015em;\n color: var(--ft-fg);\n}\n.ft-header__tagline {\n margin: 6px 0 0;\n max-width: 76ch;\n color: var(--ft-muted-fg);\n font-size: 13px;\n}\n\n.ft-stage {\n position: relative;\n flex: 1;\n min-height: 0;\n overflow: hidden;\n background:\n radial-gradient(1200px 700px at 15% -10%, oklch(0.22 0.012 60), transparent 60%),\n radial-gradient(900px 600px at 110% 110%, oklch(0.2 0.02 200 / 0.5), transparent 65%);\n}\n\n.ft-svg {\n display: block;\n width: 100%;\n height: 100%;\n background: transparent;\n cursor: grab;\n touch-action: none;\n user-select: none;\n}\n.ft-svg.is-dragging { cursor: grabbing; }\n\n.ft-controls {\n position: absolute;\n top: 14px;\n right: 14px;\n z-index: 5;\n display: flex;\n align-items: center;\n gap: 6px;\n background: oklch(0.2 0.008 80 / 0.94);\n border: 1px solid var(--ft-line-strong);\n padding: 5px 8px;\n backdrop-filter: blur(8px);\n border-radius: 2px;\n}\n.ft-controls button {\n font-family: var(--ft-mono);\n font-size: 12px;\n width: 26px;\n height: 26px;\n background: transparent;\n border: 1px solid var(--ft-line);\n color: var(--ft-fg);\n cursor: pointer;\n padding: 0;\n}\n.ft-controls button:hover { border-color: var(--ft-tier-orange); }\n.ft-controls__reset {\n width: auto !important;\n padding: 0 10px !important;\n font-size: 9.5px !important;\n letter-spacing: 0.1em;\n text-transform: uppercase;\n}\n.ft-controls__hint {\n font-family: var(--ft-mono);\n font-size: 9.5px;\n color: var(--ft-muted-fg);\n letter-spacing: 0.04em;\n padding-left: 8px;\n margin-left: 2px;\n border-left: 1px solid var(--ft-line);\n}\n\n.ft-legend {\n position: absolute;\n left: 18px;\n bottom: 14px;\n z-index: 5;\n display: flex;\n flex-wrap: wrap;\n align-items: center;\n gap: 6px;\n font-family: var(--ft-mono);\n font-size: 10px;\n color: var(--ft-muted-fg);\n background: oklch(0.2 0.008 80 / 0.88);\n border: 1px solid var(--ft-line);\n padding: 6px 10px;\n backdrop-filter: blur(6px);\n max-width: calc(100% - 220px);\n}\n.ft-legend__chip {\n display: inline-flex;\n align-items: center;\n gap: 6px;\n padding: 2px 6px;\n margin: -2px 0;\n background: transparent;\n border: none;\n color: inherit;\n font: inherit;\n letter-spacing: inherit;\n cursor: pointer;\n border-radius: 2px;\n transition: background 120ms, color 120ms;\n}\n.ft-legend__chip:hover {\n background: oklch(0.24 0.01 80);\n color: var(--ft-fg);\n}\n.ft-legend__chip:disabled {\n cursor: default;\n color: var(--ft-fg);\n}\n.ft-legend__chip:disabled:hover { background: transparent; }\n.ft-legend__dot {\n width: 5px;\n height: 5px;\n border-radius: 50%;\n background: var(--ft-tier-orange);\n flex-shrink: 0;\n}\n.ft-legend__sep {\n color: var(--ft-line-strong);\n margin: 0 2px;\n pointer-events: none;\n user-select: none;\n}\n\n/* edges */\n.ft-edge {\n fill: none;\n /* Keep stroke widths crisp under the SVG <g scale(z)> pan/zoom transform.\n Without this, fit-to-viewport (z \u2248 0.6-0.9) renders 1.6px strokes as\n sub-pixel and they visually disappear. */\n vector-effect: non-scaling-stroke;\n transition: stroke 260ms, opacity 260ms;\n}\n.ft-edge--off { stroke: var(--ft-line); stroke-width: 1; opacity: 0.55; }\n.ft-edge--on {\n stroke: var(--ft-tier-orange);\n stroke-width: 2.4;\n stroke-linecap: round;\n /* 0.1px \"dot\" + 9px gap \u2192 renders as discrete round beads. Animating\n stroke-dashoffset on this pattern produces the unmistakable\n \"flowing dots\" effect (vs the previous dashed look). */\n stroke-dasharray: 0.1 9;\n opacity: 1;\n filter: drop-shadow(0 0 4px oklch(0.7 0.18 50 / 0.45));\n animation: ftDashFlow 1.6s linear infinite;\n}\n.ft-edge--on.ft-edge--solid {\n /* Fallback for any consumer that explicitly opts out of the bead\n pattern \u2014 keeps the legacy solid orange line. */\n stroke-dasharray: none;\n animation: none;\n}\n@keyframes ftDashFlow {\n /* -18 == 2 \u00D7 (0.1 + 9) so each cycle advances the bead exactly two\n gap-units, which reads as smooth perpetual flow at 1.6s. */\n to { stroke-dashoffset: -18; }\n}\n@media (prefers-reduced-motion: reduce) {\n .ft-edge--on { animation: none; }\n}\n\n/* card */\n.ft-card {\n --tier-local: var(--tier, var(--ft-line-strong));\n width: 100%;\n height: 100%;\n display: grid;\n grid-template-columns: 1fr auto;\n gap: 0;\n align-items: stretch;\n background: oklch(0.21 0.008 80);\n border: 1px solid var(--ft-line);\n border-left: 3px solid var(--tier-local);\n border-radius: 2px;\n transition:\n background 260ms cubic-bezier(.2,.8,.2,1),\n border-color 260ms, box-shadow 260ms,\n transform 260ms cubic-bezier(.2,.8,.2,1),\n filter 260ms, opacity 260ms, color 260ms;\n overflow: visible;\n animation: ftCardIn 320ms cubic-bezier(.2,.8,.2,1) backwards;\n}\n@keyframes ftCardIn {\n from { opacity: 0; transform: translateX(-6px); }\n to { opacity: 1; transform: translateX(0); }\n}\n.ft-card__main {\n display: grid;\n grid-template-columns: auto 1fr auto;\n gap: 10px;\n align-items: center;\n padding: 0 10px 0 12px;\n background: transparent;\n border: none;\n color: inherit;\n font-family: var(--ft-sans);\n font-size: 13px;\n text-align: left;\n cursor: pointer;\n min-width: 0;\n}\n.ft-card__main:hover { background: oklch(0.23 0.01 80 / 0.6); }\n.ft-card__icon {\n width: 24px;\n height: 24px;\n display: inline-flex;\n align-items: center;\n justify-content: center;\n color: var(--tier-local);\n border: 1px solid var(--ft-line-strong);\n background: oklch(0.17 0.008 80);\n flex-shrink: 0;\n}\n.ft-card__body { display: flex; flex-direction: column; min-width: 0; gap: 1px; }\n.ft-card__label {\n font-family: var(--ft-serif);\n font-size: 15px;\n line-height: 1.15;\n color: var(--ft-fg);\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n display: flex;\n align-items: center;\n gap: 5px;\n}\n.ft-card__sub {\n font-family: var(--ft-mono);\n font-size: 9.5px;\n letter-spacing: 0.02em;\n color: var(--ft-muted-fg);\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n}\n.ft-card__lock { color: var(--ft-muted-fg); display: inline-flex; }\n.ft-card__caret {\n width: 18px;\n height: 18px;\n display: inline-flex;\n align-items: center;\n justify-content: center;\n font-family: var(--ft-mono);\n font-size: 12px;\n color: var(--ft-muted-fg);\n border: 1px solid var(--ft-line);\n}\n.ft-card--active .ft-card__caret {\n color: var(--tier-local);\n border-color: var(--tier-local);\n}\n\n.ft-card__rail {\n display: flex;\n flex-direction: column;\n border-left: 1px solid var(--ft-line);\n background: oklch(0.19 0.008 80);\n}\n.ft-card__mini {\n flex: 1;\n display: inline-flex;\n align-items: center;\n justify-content: center;\n width: 28px;\n min-height: 20px;\n background: transparent;\n border: none;\n color: var(--ft-muted-fg);\n cursor: pointer;\n transition: background 150ms, color 150ms;\n border-bottom: 1px solid var(--ft-line);\n padding: 0;\n text-decoration: none;\n}\n.ft-card__mini:last-child { border-bottom: none; }\n.ft-card__mini:hover { background: oklch(0.23 0.01 80); color: var(--tier-local); }\n.ft-card__mini.is-on { color: var(--tier-local); background: oklch(0.23 0.01 80); }\n\n/* states */\n.ft-card--active {\n background: oklch(0.25 0.015 60);\n box-shadow: 0 0 0 1px var(--tier-local), 0 14px 34px -16px oklch(0.5 0.18 50 / 0.55);\n transform: scale(1.02);\n}\n.ft-card--dim {\n filter: grayscale(0.85) brightness(0.82);\n opacity: 0.7;\n transform: scale(0.88);\n transform-origin: center left;\n}\n.ft-card--dim .ft-card__label { color: var(--ft-dim-fg); }\n.ft-card--dim:hover {\n filter: grayscale(0.3) brightness(0.96);\n opacity: 1;\n transform: scale(0.94);\n}\n.ft-card--cta {\n border-left-color: var(--ft-tier-orange);\n background: linear-gradient(180deg, oklch(0.23 0.02 55) 0%, oklch(0.2 0.015 60) 100%);\n}\n.ft-card--root {\n border-left-color: var(--ft-tier-orange);\n background: oklch(0.26 0.015 55);\n}\n.ft-card--root .ft-card__icon {\n color: var(--ft-tier-orange);\n border-color: var(--ft-tier-orange);\n}\n\n/* detail pop */\n.ft-detail {\n position: relative;\n margin-top: 0;\n padding: 16px 18px 14px;\n background: oklch(0.16 0.008 80);\n border: 1px solid var(--ft-line-strong);\n border-left: 2px solid var(--ft-tier-orange);\n border-radius: 3px;\n animation: ftDetailIn 220ms cubic-bezier(.2,.8,.2,1) backwards;\n color: var(--ft-fg);\n width: 320px;\n box-shadow: 0 18px 40px -8px rgba(0,0,0,0.6), 0 2px 8px rgba(0,0,0,0.4);\n}\n.ft-detail__head {\n display: flex;\n align-items: center;\n /* Clear room for the two stacked rail buttons in the top-right\n (expand + close). Bumped from 28px \u2192 58px to fit both. */\n margin: -2px 58px 10px 0;\n}\n.ft-detail__title {\n font-family: var(--ft-mono);\n font-size: 10px;\n letter-spacing: 0.14em;\n text-transform: uppercase;\n color: var(--ft-muted-fg);\n}\n.ft-detail__close,\n.ft-detail__expand {\n position: absolute;\n top: 8px;\n width: 22px;\n height: 22px;\n display: inline-flex;\n align-items: center;\n justify-content: center;\n background: transparent;\n border: 1px solid var(--ft-line);\n border-radius: 2px;\n color: var(--ft-muted-fg);\n cursor: pointer;\n padding: 0;\n transition: color 120ms, border-color 120ms, background 120ms;\n}\n.ft-detail__close { right: 8px; }\n/* Expand button sits immediately to the left of close. */\n.ft-detail__expand { right: 36px; }\n.ft-detail__close:hover,\n.ft-detail__expand:hover {\n color: var(--ft-fg);\n border-color: var(--ft-tier-orange);\n background: oklch(0.22 0.008 80);\n}\n@keyframes ftDetailIn {\n from { opacity: 0; transform: translateY(-4px); }\n to { opacity: 1; transform: translateY(0); }\n}\n.ft-detail__lead {\n /* Inter (sans) at slightly larger size + medium weight reads\n dramatically better than serif italic on dark stages, while still\n looking distinct from the body paragraph. The orange accent\n border on the left preserves the \"pull-quote\" rhythm. */\n font-family: var(--ft-sans);\n font-size: 14.5px;\n line-height: 1.45;\n font-style: normal;\n font-weight: 500;\n color: var(--ft-fg);\n padding-left: 10px;\n border-left: 2px solid var(--ft-tier-orange);\n margin-bottom: 12px;\n}\n.ft-detail__body {\n margin: 0 0 10px;\n font-size: 12px;\n color: var(--ft-muted-fg);\n line-height: 1.5;\n}\n.ft-detail__bullets {\n margin: 0 0 10px;\n padding-left: 16px;\n font-size: 12px;\n color: var(--ft-fg);\n}\n.ft-detail__bullets li { margin-bottom: 3px; }\n.ft-detail__meta {\n margin: 0 0 10px;\n padding: 0;\n display: flex;\n flex-direction: column;\n gap: 4px;\n}\n.ft-detail__meta-row {\n display: grid;\n grid-template-columns: 96px 1fr;\n gap: 8px;\n font-size: 11px;\n}\n.ft-detail__meta-row dt {\n font-family: var(--ft-mono);\n font-size: 9.5px;\n letter-spacing: 0.1em;\n text-transform: uppercase;\n color: var(--ft-muted-fg);\n padding-top: 1px;\n}\n.ft-detail__meta-row dd { margin: 0; color: var(--ft-fg); }\n.ft-detail__actions {\n display: flex;\n gap: 6px;\n flex-wrap: wrap;\n margin-top: 8px;\n}\n\n.ft-btn {\n font-family: var(--ft-mono);\n font-size: 10px;\n letter-spacing: 0.1em;\n text-transform: uppercase;\n padding: 7px 10px;\n background: transparent;\n color: var(--ft-fg);\n border: 1px solid var(--ft-line-strong);\n text-decoration: none;\n cursor: pointer;\n display: inline-flex;\n gap: 5px;\n align-items: center;\n transition: border-color 150ms, background 150ms;\n}\n.ft-btn:hover { border-color: var(--ft-tier-orange); }\n.ft-btn--primary {\n background: var(--ft-tier-orange);\n color: oklch(0.18 0.01 50);\n border-color: var(--ft-tier-orange);\n}\n\n@media (max-width: 760px) {\n .ft-header { padding: 16px 18px 14px; }\n .ft-header__title { font-size: 32px; }\n .ft-controls__hint { display: none; }\n .ft-legend { max-width: calc(100% - 24px); left: 10px; bottom: 10px; }\n}\n\n/* \u2500\u2500\u2500 Fullscreen detail overlay \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n Triggered by the expand button in any inline DetailPop. Renders\n inside .ft-stage as an absolutely-positioned scrim + scrollable\n panel. The inline pop stays mounted (cheaper React tree) but the\n user's focus and the pointer-events both transfer to the overlay\n while it's visible.\n*/\n.ft-detail-overlay {\n position: absolute;\n inset: 0;\n z-index: 20;\n display: flex;\n align-items: stretch;\n justify-content: center;\n /* Translucent backdrop blurs the flow tree behind so the focused\n content reads cleanly without losing the user's \"where am I\"\n spatial anchor. */\n background: oklch(0.12 0.008 80 / 0.78);\n backdrop-filter: blur(6px);\n animation: ftOverlayIn 200ms cubic-bezier(.2,.8,.2,1);\n}\n@keyframes ftOverlayIn {\n from { opacity: 0; }\n to { opacity: 1; }\n}\n.ft-detail-overlay__panel {\n position: relative;\n margin: 36px;\n flex: 1 1 auto;\n max-width: 760px;\n background: oklch(0.18 0.008 80);\n border: 1px solid var(--ft-line-strong);\n border-left: 3px solid var(--ft-tier-orange);\n border-radius: 4px;\n overflow: auto;\n padding: 26px 32px 28px;\n box-shadow: 0 30px 80px -10px oklch(0 0 0 / 0.7);\n animation: ftOverlayPanelIn 240ms cubic-bezier(.2,.8,.2,1);\n}\n@keyframes ftOverlayPanelIn {\n from { opacity: 0; transform: translateY(8px); }\n to { opacity: 1; transform: translateY(0); }\n}\n.ft-detail-overlay__panel .ft-detail__title {\n font-size: 11px;\n}\n.ft-detail-overlay__panel .ft-detail__lead {\n font-size: 18px;\n line-height: 1.5;\n padding-left: 14px;\n}\n.ft-detail-overlay__panel .ft-detail__body {\n font-size: 14px;\n color: var(--ft-fg);\n line-height: 1.65;\n}\n.ft-detail-overlay__panel .ft-detail__bullets {\n font-size: 14px;\n line-height: 1.55;\n padding-left: 20px;\n}\n.ft-detail-overlay__panel .ft-detail__bullets li { margin-bottom: 6px; }\n.ft-detail-overlay__panel .ft-detail__meta-row {\n font-size: 13px;\n grid-template-columns: 140px 1fr;\n}\n.ft-detail-overlay__panel .ft-detail__meta-row dt { font-size: 10.5px; }\n.ft-detail-overlay__panel .ft-btn { font-size: 11px; padding: 9px 14px; }\n.ft-detail-overlay__head {\n display: flex;\n align-items: center;\n margin: 0 0 14px;\n padding-right: 70px;\n}\n.ft-detail-overlay__title {\n font-family: var(--ft-mono);\n font-size: 11px;\n letter-spacing: 0.18em;\n text-transform: uppercase;\n color: var(--ft-tier-orange);\n}\n.ft-detail-overlay__close,\n.ft-detail-overlay__collapse {\n position: absolute;\n top: 16px;\n width: 28px;\n height: 28px;\n display: inline-flex;\n align-items: center;\n justify-content: center;\n background: transparent;\n border: 1px solid var(--ft-line-strong);\n border-radius: 2px;\n color: var(--ft-fg);\n cursor: pointer;\n padding: 0;\n transition: color 120ms, border-color 120ms, background 120ms;\n}\n.ft-detail-overlay__close { right: 16px; }\n.ft-detail-overlay__collapse { right: 52px; }\n.ft-detail-overlay__close:hover,\n.ft-detail-overlay__collapse:hover {\n color: var(--ft-tier-orange);\n border-color: var(--ft-tier-orange);\n background: oklch(0.22 0.008 80);\n}\n\n@media (max-width: 760px) {\n .ft-detail-overlay__panel {\n margin: 12px;\n padding: 18px 18px 22px;\n }\n .ft-detail-overlay__panel .ft-detail__lead { font-size: 16px; }\n}\n";
|
|
5296
|
+
declare const FLOW_TREE_CSS = "\n@import url(\"https://fonts.googleapis.com/css2?family=Instrument+Serif:ital@0;1&family=Inter:wght@400;500;600&family=JetBrains+Mono:wght@400;500&display=swap\");\n\n.ft-shell {\n --ft-bg: oklch(0.165 0.008 80);\n --ft-panel: oklch(0.21 0.008 80);\n --ft-panel-2: oklch(0.25 0.01 80);\n --ft-fg: oklch(0.96 0.005 85);\n --ft-muted-fg: oklch(0.68 0.008 80);\n --ft-dim-fg: oklch(0.5 0.008 80);\n --ft-line: oklch(0.28 0.008 80);\n --ft-line-strong: oklch(0.4 0.008 80);\n --ft-grid-strong: oklch(0.24 0.008 80);\n --ft-tier-orange: #f16c25;\n --ft-tier-teal: #5fb7b8;\n --ft-tier-blue: oklch(0.72 0.12 240);\n --ft-tier-amber: oklch(0.8 0.14 75);\n --ft-tier-green: oklch(0.74 0.14 155);\n --ft-accent: var(--ft-tier-orange);\n --ft-serif: \"Instrument Serif\", Georgia, serif;\n --ft-sans: \"Inter\", system-ui, sans-serif;\n --ft-mono: \"JetBrains Mono\", ui-monospace, Menlo, monospace;\n\n /* Defensive shell sizing \u2014 fills flex parents (flex: 1) and block\n parents with definite dimensions (width/height: 100%). */\n flex: 1 1 auto;\n width: 100%;\n height: 100%;\n min-height: 420px;\n display: flex;\n flex-direction: column;\n background: var(--ft-bg);\n border: 1px solid var(--ft-line);\n border-radius: 2px;\n overflow: hidden;\n color: var(--ft-fg);\n font-family: var(--ft-sans);\n font-size: 14px;\n line-height: 1.5;\n -webkit-font-smoothing: antialiased;\n box-shadow: 0 30px 60px -30px oklch(0 0 0 / 0.5);\n}\n.ft-shell * { box-sizing: border-box; }\n.ft-shell .ft-mono {\n font-family: var(--ft-mono);\n font-size: 11px;\n letter-spacing: 0.02em;\n}\n\n.ft-header {\n padding: 22px 28px 20px;\n border-bottom: 1px solid var(--ft-line);\n background: linear-gradient(180deg, oklch(0.21 0.01 60) 0%, oklch(0.19 0.008 70) 100%);\n display: flex;\n flex-direction: column;\n gap: 6px;\n}\n.ft-header__eyebrow {\n font-family: var(--ft-mono);\n font-size: 10.5px;\n letter-spacing: 0.22em;\n text-transform: uppercase;\n color: var(--ft-tier-orange);\n}\n.ft-header__title {\n margin: 0;\n font-family: var(--ft-serif);\n font-weight: 400;\n font-size: 44px;\n line-height: 1;\n letter-spacing: -0.015em;\n color: var(--ft-fg);\n}\n.ft-header__tagline {\n margin: 6px 0 0;\n max-width: 76ch;\n color: var(--ft-muted-fg);\n font-size: 13px;\n}\n\n.ft-stage {\n position: relative;\n flex: 1;\n min-height: 0;\n overflow: hidden;\n background:\n radial-gradient(1200px 700px at 15% -10%, oklch(0.22 0.012 60), transparent 60%),\n radial-gradient(900px 600px at 110% 110%, oklch(0.2 0.02 200 / 0.5), transparent 65%);\n}\n\n.ft-svg {\n display: block;\n width: 100%;\n height: 100%;\n background: transparent;\n cursor: grab;\n touch-action: none;\n user-select: none;\n}\n.ft-svg.is-dragging { cursor: grabbing; }\n\n.ft-controls {\n position: absolute;\n top: 14px;\n right: 14px;\n z-index: 5;\n display: flex;\n align-items: center;\n gap: 6px;\n background: oklch(0.2 0.008 80 / 0.94);\n border: 1px solid var(--ft-line-strong);\n padding: 5px 8px;\n backdrop-filter: blur(8px);\n border-radius: 2px;\n}\n.ft-controls button {\n font-family: var(--ft-mono);\n font-size: 12px;\n width: 26px;\n height: 26px;\n background: transparent;\n border: 1px solid var(--ft-line);\n color: var(--ft-fg);\n cursor: pointer;\n padding: 0;\n}\n.ft-controls button:hover { border-color: var(--ft-tier-orange); }\n.ft-controls__reset {\n width: auto !important;\n padding: 0 10px !important;\n font-size: 9.5px !important;\n letter-spacing: 0.1em;\n text-transform: uppercase;\n}\n.ft-controls__hint {\n font-family: var(--ft-mono);\n font-size: 9.5px;\n color: var(--ft-muted-fg);\n letter-spacing: 0.04em;\n padding-left: 8px;\n margin-left: 2px;\n border-left: 1px solid var(--ft-line);\n}\n\n.ft-legend {\n position: absolute;\n left: 18px;\n bottom: 14px;\n z-index: 5;\n display: flex;\n flex-wrap: wrap;\n align-items: center;\n gap: 6px;\n font-family: var(--ft-mono);\n font-size: 10px;\n color: var(--ft-muted-fg);\n background: oklch(0.2 0.008 80 / 0.88);\n border: 1px solid var(--ft-line);\n padding: 6px 10px;\n backdrop-filter: blur(6px);\n max-width: calc(100% - 220px);\n}\n.ft-legend__chip {\n display: inline-flex;\n align-items: center;\n gap: 6px;\n padding: 2px 6px;\n margin: -2px 0;\n background: transparent;\n border: none;\n color: inherit;\n font: inherit;\n letter-spacing: inherit;\n cursor: pointer;\n border-radius: 2px;\n transition: background 120ms, color 120ms;\n}\n.ft-legend__chip:hover {\n background: oklch(0.24 0.01 80);\n color: var(--ft-fg);\n}\n.ft-legend__chip:disabled {\n cursor: default;\n color: var(--ft-fg);\n}\n.ft-legend__chip:disabled:hover { background: transparent; }\n.ft-legend__dot {\n width: 5px;\n height: 5px;\n border-radius: 50%;\n background: var(--ft-tier-orange);\n flex-shrink: 0;\n}\n.ft-legend__sep {\n color: var(--ft-line-strong);\n margin: 0 2px;\n pointer-events: none;\n user-select: none;\n}\n\n/* edges */\n.ft-edge {\n fill: none;\n /* Keep stroke widths crisp under the SVG <g scale(z)> pan/zoom transform.\n Without this, fit-to-viewport (z \u2248 0.6-0.9) renders 1.6px strokes as\n sub-pixel and they visually disappear. */\n vector-effect: non-scaling-stroke;\n transition: stroke 260ms, opacity 260ms;\n}\n.ft-edge--off { stroke: var(--ft-line); stroke-width: 1; opacity: 0.55; }\n.ft-edge--on {\n stroke: var(--ft-tier-orange);\n /* Wider stroke than off-path so the dot pattern reads clearly on\n dense paths and on high-DPI displays where 1.6px antialiased to\n \"barely visible\". Cross-browser tested floor; renders a solid 3px\n dot (1px hard center + 1px round cap each side) every 13px. */\n stroke-width: 3;\n stroke-linecap: round;\n /* 1px hard dash + 12px gap. With stroke-linecap:round the cap\n extends each dash by half-stroke-width on each end, producing\n visible 4px round beads spaced 13px apart. The \"0.1 9\" pattern in\n 1.30.0 was below subpixel-antialiasing threshold in some\n renderers (particularly Chromium under fit-to-viewport zoom) and\n the dashes disappeared entirely \u2014 leaving the line visually\n \"plain\". 1px is the minimum value every browser renders. */\n stroke-dasharray: 1 12;\n opacity: 1;\n /* Drop-shadow drops a tier-tinted glow under the line. Cheap on\n desktop, slightly more expensive on mobile GPUs but well within\n budget for the handful of paths we render at once. */\n filter: drop-shadow(0 0 5px oklch(0.7 0.18 50 / 0.55));\n /* Animate stroke-dashoffset so beads visibly travel along the\n path. Speed tuned to ~30px/s on a typical card connector \u2014 fast\n enough to read as motion without becoming distracting. */\n animation: ftDashFlow 1.4s linear infinite;\n /* Keep the bead pattern crisp under fit-to-viewport scale()\n transforms \u2014 without this, sub-1 zoom turns 3px strokes into\n fractional pixels and the beads merge into a hairline. */\n vector-effect: non-scaling-stroke;\n}\n.ft-edge--on.ft-edge--solid {\n /* Fallback for any consumer that explicitly opts out of the bead\n pattern \u2014 keeps the legacy solid orange line. */\n stroke-dasharray: none;\n animation: none;\n}\n@keyframes ftDashFlow {\n /* -26 == 2 \u00D7 (1 + 12) so each cycle advances the bead exactly two\n full units, which reads as smooth perpetual flow at 1.4s. */\n to { stroke-dashoffset: -26; }\n}\n@media (prefers-reduced-motion: reduce) {\n .ft-edge--on {\n animation: none;\n /* When motion is suppressed, drop the dot pattern too so users\n see a clean solid line instead of a static frozen dash. */\n stroke-dasharray: none;\n }\n}\n\n/* card */\n.ft-card {\n --tier-local: var(--tier, var(--ft-line-strong));\n width: 100%;\n height: 100%;\n display: grid;\n grid-template-columns: 1fr auto;\n gap: 0;\n align-items: stretch;\n background: oklch(0.21 0.008 80);\n border: 1px solid var(--ft-line);\n border-left: 3px solid var(--tier-local);\n border-radius: 2px;\n transition:\n background 260ms cubic-bezier(.2,.8,.2,1),\n border-color 260ms, box-shadow 260ms,\n transform 260ms cubic-bezier(.2,.8,.2,1),\n filter 260ms, opacity 260ms, color 260ms;\n overflow: visible;\n animation: ftCardIn 320ms cubic-bezier(.2,.8,.2,1) backwards;\n}\n@keyframes ftCardIn {\n from { opacity: 0; transform: translateX(-6px); }\n to { opacity: 1; transform: translateX(0); }\n}\n.ft-card__main {\n display: grid;\n grid-template-columns: auto 1fr auto;\n gap: 10px;\n align-items: center;\n padding: 0 10px 0 12px;\n background: transparent;\n border: none;\n color: inherit;\n font-family: var(--ft-sans);\n font-size: 13px;\n text-align: left;\n cursor: pointer;\n min-width: 0;\n}\n.ft-card__main:hover { background: oklch(0.23 0.01 80 / 0.6); }\n.ft-card__icon {\n width: 24px;\n height: 24px;\n display: inline-flex;\n align-items: center;\n justify-content: center;\n color: var(--tier-local);\n border: 1px solid var(--ft-line-strong);\n background: oklch(0.17 0.008 80);\n flex-shrink: 0;\n}\n.ft-card__body { display: flex; flex-direction: column; min-width: 0; gap: 1px; }\n.ft-card__label {\n font-family: var(--ft-serif);\n font-size: 15px;\n line-height: 1.15;\n color: var(--ft-fg);\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n display: flex;\n align-items: center;\n gap: 5px;\n}\n.ft-card__sub {\n font-family: var(--ft-mono);\n font-size: 9.5px;\n letter-spacing: 0.02em;\n color: var(--ft-muted-fg);\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n}\n.ft-card__lock { color: var(--ft-muted-fg); display: inline-flex; }\n.ft-card__caret {\n width: 18px;\n height: 18px;\n display: inline-flex;\n align-items: center;\n justify-content: center;\n font-family: var(--ft-mono);\n font-size: 12px;\n color: var(--ft-muted-fg);\n border: 1px solid var(--ft-line);\n}\n.ft-card--active .ft-card__caret {\n color: var(--tier-local);\n border-color: var(--tier-local);\n}\n\n.ft-card__rail {\n display: flex;\n flex-direction: column;\n border-left: 1px solid var(--ft-line);\n background: oklch(0.19 0.008 80);\n}\n.ft-card__mini {\n flex: 1;\n display: inline-flex;\n align-items: center;\n justify-content: center;\n width: 28px;\n min-height: 20px;\n background: transparent;\n border: none;\n color: var(--ft-muted-fg);\n cursor: pointer;\n transition: background 150ms, color 150ms;\n border-bottom: 1px solid var(--ft-line);\n padding: 0;\n text-decoration: none;\n}\n.ft-card__mini:last-child { border-bottom: none; }\n.ft-card__mini:hover { background: oklch(0.23 0.01 80); color: var(--tier-local); }\n.ft-card__mini.is-on { color: var(--tier-local); background: oklch(0.23 0.01 80); }\n\n/* states */\n.ft-card--active {\n background: oklch(0.25 0.015 60);\n box-shadow: 0 0 0 1px var(--tier-local), 0 14px 34px -16px oklch(0.5 0.18 50 / 0.55);\n transform: scale(1.02);\n}\n.ft-card--dim {\n filter: grayscale(0.85) brightness(0.82);\n opacity: 0.7;\n transform: scale(0.88);\n transform-origin: center left;\n}\n.ft-card--dim .ft-card__label { color: var(--ft-dim-fg); }\n.ft-card--dim:hover {\n filter: grayscale(0.3) brightness(0.96);\n opacity: 1;\n transform: scale(0.94);\n}\n.ft-card--cta {\n border-left-color: var(--ft-tier-orange);\n background: linear-gradient(180deg, oklch(0.23 0.02 55) 0%, oklch(0.2 0.015 60) 100%);\n}\n.ft-card--root {\n border-left-color: var(--ft-tier-orange);\n background: oklch(0.26 0.015 55);\n}\n.ft-card--root .ft-card__icon {\n color: var(--ft-tier-orange);\n border-color: var(--ft-tier-orange);\n}\n\n/* detail pop */\n.ft-detail {\n position: relative;\n margin-top: 0;\n padding: 16px 18px 14px;\n background: oklch(0.16 0.008 80);\n border: 1px solid var(--ft-line-strong);\n border-left: 2px solid var(--ft-tier-orange);\n border-radius: 3px;\n animation: ftDetailIn 220ms cubic-bezier(.2,.8,.2,1) backwards;\n color: var(--ft-fg);\n width: 320px;\n box-shadow: 0 18px 40px -8px rgba(0,0,0,0.6), 0 2px 8px rgba(0,0,0,0.4);\n}\n.ft-detail__head {\n display: flex;\n align-items: center;\n /* Clear room for the two stacked rail buttons in the top-right\n (expand + close). Bumped from 28px \u2192 58px to fit both. */\n margin: -2px 58px 10px 0;\n}\n.ft-detail__title {\n font-family: var(--ft-mono);\n font-size: 10px;\n letter-spacing: 0.14em;\n text-transform: uppercase;\n color: var(--ft-muted-fg);\n}\n.ft-detail__close,\n.ft-detail__expand {\n position: absolute;\n top: 8px;\n width: 22px;\n height: 22px;\n display: inline-flex;\n align-items: center;\n justify-content: center;\n background: transparent;\n border: 1px solid var(--ft-line);\n border-radius: 2px;\n color: var(--ft-muted-fg);\n cursor: pointer;\n padding: 0;\n transition: color 120ms, border-color 120ms, background 120ms;\n}\n.ft-detail__close { right: 8px; }\n/* Expand button sits immediately to the left of close. */\n.ft-detail__expand { right: 36px; }\n.ft-detail__close:hover,\n.ft-detail__expand:hover {\n color: var(--ft-fg);\n border-color: var(--ft-tier-orange);\n background: oklch(0.22 0.008 80);\n}\n@keyframes ftDetailIn {\n from { opacity: 0; transform: translateY(-4px); }\n to { opacity: 1; transform: translateY(0); }\n}\n.ft-detail__lead {\n /* Inter (sans) at slightly larger size + medium weight reads\n dramatically better than serif italic on dark stages, while still\n looking distinct from the body paragraph. The orange accent\n border on the left preserves the \"pull-quote\" rhythm. */\n font-family: var(--ft-sans);\n font-size: 14.5px;\n line-height: 1.45;\n font-style: normal;\n font-weight: 500;\n color: var(--ft-fg);\n padding-left: 10px;\n border-left: 2px solid var(--ft-tier-orange);\n margin-bottom: 12px;\n}\n.ft-detail__body {\n margin: 0 0 10px;\n font-size: 12px;\n color: var(--ft-muted-fg);\n line-height: 1.5;\n}\n.ft-detail__bullets {\n margin: 0 0 10px;\n padding-left: 16px;\n font-size: 12px;\n color: var(--ft-fg);\n}\n.ft-detail__bullets li { margin-bottom: 3px; }\n.ft-detail__meta {\n margin: 0 0 10px;\n padding: 0;\n display: flex;\n flex-direction: column;\n gap: 4px;\n}\n.ft-detail__meta-row {\n display: grid;\n grid-template-columns: 96px 1fr;\n gap: 8px;\n font-size: 11px;\n}\n.ft-detail__meta-row dt {\n font-family: var(--ft-mono);\n font-size: 9.5px;\n letter-spacing: 0.1em;\n text-transform: uppercase;\n color: var(--ft-muted-fg);\n padding-top: 1px;\n}\n.ft-detail__meta-row dd { margin: 0; color: var(--ft-fg); }\n.ft-detail__actions {\n display: flex;\n gap: 6px;\n flex-wrap: wrap;\n margin-top: 8px;\n}\n\n.ft-btn {\n font-family: var(--ft-mono);\n font-size: 10px;\n letter-spacing: 0.1em;\n text-transform: uppercase;\n padding: 7px 10px;\n background: transparent;\n color: var(--ft-fg);\n border: 1px solid var(--ft-line-strong);\n text-decoration: none;\n cursor: pointer;\n display: inline-flex;\n gap: 5px;\n align-items: center;\n transition: border-color 150ms, background 150ms;\n}\n.ft-btn:hover { border-color: var(--ft-tier-orange); }\n.ft-btn--primary {\n background: var(--ft-tier-orange);\n color: oklch(0.18 0.01 50);\n border-color: var(--ft-tier-orange);\n}\n\n@media (max-width: 760px) {\n .ft-header { padding: 16px 18px 14px; }\n .ft-header__title { font-size: 32px; }\n .ft-controls__hint { display: none; }\n .ft-legend { max-width: calc(100% - 24px); left: 10px; bottom: 10px; }\n /* Larger touch targets on phones \u2014 bumps zoom controls from 26px\n to 36px to comfortably clear the 44px Apple HIG / Material 48dp\n minimum once you account for visual padding. */\n .ft-controls { padding: 6px 8px; }\n .ft-controls button { width: 36px; height: 36px; font-size: 16px; }\n .ft-controls__reset { padding: 0 14px !important; height: 36px; }\n /* Inline detail popup buttons are intentionally compact in the\n inline footprint \u2014 but on touch the user can promote to the\n fullscreen overlay which has 28px buttons by default. */\n .ft-detail__close, .ft-detail__expand { width: 28px; height: 28px; }\n .ft-detail__expand { right: 42px; }\n .ft-detail__head { margin-right: 70px; }\n}\n@media (max-width: 480px) {\n /* Phone-sized: shrink card width so 5+ depth levels still fit\n reasonably without forcing the user to scroll horizontally to\n read the labels. Cards still ellipsis-truncate when needed. */\n .ft-card { font-size: 12px; }\n .ft-card__label { font-size: 14px; }\n .ft-card__sub { font-size: 9px; }\n .ft-controls__hint { display: none; }\n}\n\n/* \u2500\u2500\u2500 Fullscreen detail overlay \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n Triggered by the expand button in any inline DetailPop. Renders\n inside .ft-stage as an absolutely-positioned scrim + scrollable\n panel. The inline pop stays mounted (cheaper React tree) but the\n user's focus and the pointer-events both transfer to the overlay\n while it's visible.\n*/\n.ft-detail-overlay {\n position: absolute;\n inset: 0;\n z-index: 20;\n display: flex;\n align-items: stretch;\n justify-content: center;\n /* Translucent backdrop blurs the flow tree behind so the focused\n content reads cleanly without losing the user's \"where am I\"\n spatial anchor. */\n background: oklch(0.12 0.008 80 / 0.78);\n backdrop-filter: blur(6px);\n animation: ftOverlayIn 200ms cubic-bezier(.2,.8,.2,1);\n}\n@keyframes ftOverlayIn {\n from { opacity: 0; }\n to { opacity: 1; }\n}\n.ft-detail-overlay__panel {\n position: relative;\n margin: 36px;\n flex: 1 1 auto;\n max-width: 760px;\n background: oklch(0.18 0.008 80);\n border: 1px solid var(--ft-line-strong);\n border-left: 3px solid var(--ft-tier-orange);\n border-radius: 4px;\n overflow: auto;\n padding: 26px 32px 28px;\n box-shadow: 0 30px 80px -10px oklch(0 0 0 / 0.7);\n animation: ftOverlayPanelIn 240ms cubic-bezier(.2,.8,.2,1);\n}\n@keyframes ftOverlayPanelIn {\n from { opacity: 0; transform: translateY(8px); }\n to { opacity: 1; transform: translateY(0); }\n}\n.ft-detail-overlay__panel .ft-detail__title {\n font-size: 11px;\n}\n.ft-detail-overlay__panel .ft-detail__lead {\n font-size: 18px;\n line-height: 1.5;\n padding-left: 14px;\n}\n.ft-detail-overlay__panel .ft-detail__body {\n font-size: 14px;\n color: var(--ft-fg);\n line-height: 1.65;\n}\n.ft-detail-overlay__panel .ft-detail__bullets {\n font-size: 14px;\n line-height: 1.55;\n padding-left: 20px;\n}\n.ft-detail-overlay__panel .ft-detail__bullets li { margin-bottom: 6px; }\n.ft-detail-overlay__panel .ft-detail__meta-row {\n font-size: 13px;\n grid-template-columns: 140px 1fr;\n}\n.ft-detail-overlay__panel .ft-detail__meta-row dt { font-size: 10.5px; }\n.ft-detail-overlay__panel .ft-btn { font-size: 11px; padding: 9px 14px; }\n.ft-detail-overlay__head {\n display: flex;\n align-items: center;\n margin: 0 0 14px;\n padding-right: 70px;\n}\n.ft-detail-overlay__title {\n font-family: var(--ft-mono);\n font-size: 11px;\n letter-spacing: 0.18em;\n text-transform: uppercase;\n color: var(--ft-tier-orange);\n}\n.ft-detail-overlay__close,\n.ft-detail-overlay__collapse {\n position: absolute;\n top: 16px;\n width: 28px;\n height: 28px;\n display: inline-flex;\n align-items: center;\n justify-content: center;\n background: transparent;\n border: 1px solid var(--ft-line-strong);\n border-radius: 2px;\n color: var(--ft-fg);\n cursor: pointer;\n padding: 0;\n transition: color 120ms, border-color 120ms, background 120ms;\n}\n.ft-detail-overlay__close { right: 16px; }\n.ft-detail-overlay__collapse { right: 52px; }\n.ft-detail-overlay__close:hover,\n.ft-detail-overlay__collapse:hover {\n color: var(--ft-tier-orange);\n border-color: var(--ft-tier-orange);\n background: oklch(0.22 0.008 80);\n}\n\n@media (max-width: 760px) {\n .ft-detail-overlay__panel {\n margin: 12px;\n padding: 18px 18px 22px;\n }\n .ft-detail-overlay__panel .ft-detail__lead { font-size: 16px; }\n}\n";
|
|
5297
5297
|
|
|
5298
5298
|
type GameTimerMode = "countdown" | "countup";
|
|
5299
5299
|
type GameTimerThemePreset = "green" | "red" | "blue" | "amber" | "purple" | "pink" | "teal";
|
package/dist/index.d.ts
CHANGED
|
@@ -5293,7 +5293,7 @@ declare function FlowTree({ data, title, eyebrow, tagline, renderIcon: renderIco
|
|
|
5293
5293
|
* delta: it keeps connector strokes crisp under the pan/zoom transform's
|
|
5294
5294
|
* `scale()` (otherwise sub-1 fit-zoom renders them sub-pixel and invisible).
|
|
5295
5295
|
*/
|
|
5296
|
-
declare const FLOW_TREE_CSS = "\n@import url(\"https://fonts.googleapis.com/css2?family=Instrument+Serif:ital@0;1&family=Inter:wght@400;500;600&family=JetBrains+Mono:wght@400;500&display=swap\");\n\n.ft-shell {\n --ft-bg: oklch(0.165 0.008 80);\n --ft-panel: oklch(0.21 0.008 80);\n --ft-panel-2: oklch(0.25 0.01 80);\n --ft-fg: oklch(0.96 0.005 85);\n --ft-muted-fg: oklch(0.68 0.008 80);\n --ft-dim-fg: oklch(0.5 0.008 80);\n --ft-line: oklch(0.28 0.008 80);\n --ft-line-strong: oklch(0.4 0.008 80);\n --ft-grid-strong: oklch(0.24 0.008 80);\n --ft-tier-orange: #f16c25;\n --ft-tier-teal: #5fb7b8;\n --ft-tier-blue: oklch(0.72 0.12 240);\n --ft-tier-amber: oklch(0.8 0.14 75);\n --ft-tier-green: oklch(0.74 0.14 155);\n --ft-accent: var(--ft-tier-orange);\n --ft-serif: \"Instrument Serif\", Georgia, serif;\n --ft-sans: \"Inter\", system-ui, sans-serif;\n --ft-mono: \"JetBrains Mono\", ui-monospace, Menlo, monospace;\n\n /* Defensive shell sizing \u2014 fills flex parents (flex: 1) and block\n parents with definite dimensions (width/height: 100%). */\n flex: 1 1 auto;\n width: 100%;\n height: 100%;\n min-height: 420px;\n display: flex;\n flex-direction: column;\n background: var(--ft-bg);\n border: 1px solid var(--ft-line);\n border-radius: 2px;\n overflow: hidden;\n color: var(--ft-fg);\n font-family: var(--ft-sans);\n font-size: 14px;\n line-height: 1.5;\n -webkit-font-smoothing: antialiased;\n box-shadow: 0 30px 60px -30px oklch(0 0 0 / 0.5);\n}\n.ft-shell * { box-sizing: border-box; }\n.ft-shell .ft-mono {\n font-family: var(--ft-mono);\n font-size: 11px;\n letter-spacing: 0.02em;\n}\n\n.ft-header {\n padding: 22px 28px 20px;\n border-bottom: 1px solid var(--ft-line);\n background: linear-gradient(180deg, oklch(0.21 0.01 60) 0%, oklch(0.19 0.008 70) 100%);\n display: flex;\n flex-direction: column;\n gap: 6px;\n}\n.ft-header__eyebrow {\n font-family: var(--ft-mono);\n font-size: 10.5px;\n letter-spacing: 0.22em;\n text-transform: uppercase;\n color: var(--ft-tier-orange);\n}\n.ft-header__title {\n margin: 0;\n font-family: var(--ft-serif);\n font-weight: 400;\n font-size: 44px;\n line-height: 1;\n letter-spacing: -0.015em;\n color: var(--ft-fg);\n}\n.ft-header__tagline {\n margin: 6px 0 0;\n max-width: 76ch;\n color: var(--ft-muted-fg);\n font-size: 13px;\n}\n\n.ft-stage {\n position: relative;\n flex: 1;\n min-height: 0;\n overflow: hidden;\n background:\n radial-gradient(1200px 700px at 15% -10%, oklch(0.22 0.012 60), transparent 60%),\n radial-gradient(900px 600px at 110% 110%, oklch(0.2 0.02 200 / 0.5), transparent 65%);\n}\n\n.ft-svg {\n display: block;\n width: 100%;\n height: 100%;\n background: transparent;\n cursor: grab;\n touch-action: none;\n user-select: none;\n}\n.ft-svg.is-dragging { cursor: grabbing; }\n\n.ft-controls {\n position: absolute;\n top: 14px;\n right: 14px;\n z-index: 5;\n display: flex;\n align-items: center;\n gap: 6px;\n background: oklch(0.2 0.008 80 / 0.94);\n border: 1px solid var(--ft-line-strong);\n padding: 5px 8px;\n backdrop-filter: blur(8px);\n border-radius: 2px;\n}\n.ft-controls button {\n font-family: var(--ft-mono);\n font-size: 12px;\n width: 26px;\n height: 26px;\n background: transparent;\n border: 1px solid var(--ft-line);\n color: var(--ft-fg);\n cursor: pointer;\n padding: 0;\n}\n.ft-controls button:hover { border-color: var(--ft-tier-orange); }\n.ft-controls__reset {\n width: auto !important;\n padding: 0 10px !important;\n font-size: 9.5px !important;\n letter-spacing: 0.1em;\n text-transform: uppercase;\n}\n.ft-controls__hint {\n font-family: var(--ft-mono);\n font-size: 9.5px;\n color: var(--ft-muted-fg);\n letter-spacing: 0.04em;\n padding-left: 8px;\n margin-left: 2px;\n border-left: 1px solid var(--ft-line);\n}\n\n.ft-legend {\n position: absolute;\n left: 18px;\n bottom: 14px;\n z-index: 5;\n display: flex;\n flex-wrap: wrap;\n align-items: center;\n gap: 6px;\n font-family: var(--ft-mono);\n font-size: 10px;\n color: var(--ft-muted-fg);\n background: oklch(0.2 0.008 80 / 0.88);\n border: 1px solid var(--ft-line);\n padding: 6px 10px;\n backdrop-filter: blur(6px);\n max-width: calc(100% - 220px);\n}\n.ft-legend__chip {\n display: inline-flex;\n align-items: center;\n gap: 6px;\n padding: 2px 6px;\n margin: -2px 0;\n background: transparent;\n border: none;\n color: inherit;\n font: inherit;\n letter-spacing: inherit;\n cursor: pointer;\n border-radius: 2px;\n transition: background 120ms, color 120ms;\n}\n.ft-legend__chip:hover {\n background: oklch(0.24 0.01 80);\n color: var(--ft-fg);\n}\n.ft-legend__chip:disabled {\n cursor: default;\n color: var(--ft-fg);\n}\n.ft-legend__chip:disabled:hover { background: transparent; }\n.ft-legend__dot {\n width: 5px;\n height: 5px;\n border-radius: 50%;\n background: var(--ft-tier-orange);\n flex-shrink: 0;\n}\n.ft-legend__sep {\n color: var(--ft-line-strong);\n margin: 0 2px;\n pointer-events: none;\n user-select: none;\n}\n\n/* edges */\n.ft-edge {\n fill: none;\n /* Keep stroke widths crisp under the SVG <g scale(z)> pan/zoom transform.\n Without this, fit-to-viewport (z \u2248 0.6-0.9) renders 1.6px strokes as\n sub-pixel and they visually disappear. */\n vector-effect: non-scaling-stroke;\n transition: stroke 260ms, opacity 260ms;\n}\n.ft-edge--off { stroke: var(--ft-line); stroke-width: 1; opacity: 0.55; }\n.ft-edge--on {\n stroke: var(--ft-tier-orange);\n stroke-width: 2.4;\n stroke-linecap: round;\n /* 0.1px \"dot\" + 9px gap \u2192 renders as discrete round beads. Animating\n stroke-dashoffset on this pattern produces the unmistakable\n \"flowing dots\" effect (vs the previous dashed look). */\n stroke-dasharray: 0.1 9;\n opacity: 1;\n filter: drop-shadow(0 0 4px oklch(0.7 0.18 50 / 0.45));\n animation: ftDashFlow 1.6s linear infinite;\n}\n.ft-edge--on.ft-edge--solid {\n /* Fallback for any consumer that explicitly opts out of the bead\n pattern \u2014 keeps the legacy solid orange line. */\n stroke-dasharray: none;\n animation: none;\n}\n@keyframes ftDashFlow {\n /* -18 == 2 \u00D7 (0.1 + 9) so each cycle advances the bead exactly two\n gap-units, which reads as smooth perpetual flow at 1.6s. */\n to { stroke-dashoffset: -18; }\n}\n@media (prefers-reduced-motion: reduce) {\n .ft-edge--on { animation: none; }\n}\n\n/* card */\n.ft-card {\n --tier-local: var(--tier, var(--ft-line-strong));\n width: 100%;\n height: 100%;\n display: grid;\n grid-template-columns: 1fr auto;\n gap: 0;\n align-items: stretch;\n background: oklch(0.21 0.008 80);\n border: 1px solid var(--ft-line);\n border-left: 3px solid var(--tier-local);\n border-radius: 2px;\n transition:\n background 260ms cubic-bezier(.2,.8,.2,1),\n border-color 260ms, box-shadow 260ms,\n transform 260ms cubic-bezier(.2,.8,.2,1),\n filter 260ms, opacity 260ms, color 260ms;\n overflow: visible;\n animation: ftCardIn 320ms cubic-bezier(.2,.8,.2,1) backwards;\n}\n@keyframes ftCardIn {\n from { opacity: 0; transform: translateX(-6px); }\n to { opacity: 1; transform: translateX(0); }\n}\n.ft-card__main {\n display: grid;\n grid-template-columns: auto 1fr auto;\n gap: 10px;\n align-items: center;\n padding: 0 10px 0 12px;\n background: transparent;\n border: none;\n color: inherit;\n font-family: var(--ft-sans);\n font-size: 13px;\n text-align: left;\n cursor: pointer;\n min-width: 0;\n}\n.ft-card__main:hover { background: oklch(0.23 0.01 80 / 0.6); }\n.ft-card__icon {\n width: 24px;\n height: 24px;\n display: inline-flex;\n align-items: center;\n justify-content: center;\n color: var(--tier-local);\n border: 1px solid var(--ft-line-strong);\n background: oklch(0.17 0.008 80);\n flex-shrink: 0;\n}\n.ft-card__body { display: flex; flex-direction: column; min-width: 0; gap: 1px; }\n.ft-card__label {\n font-family: var(--ft-serif);\n font-size: 15px;\n line-height: 1.15;\n color: var(--ft-fg);\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n display: flex;\n align-items: center;\n gap: 5px;\n}\n.ft-card__sub {\n font-family: var(--ft-mono);\n font-size: 9.5px;\n letter-spacing: 0.02em;\n color: var(--ft-muted-fg);\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n}\n.ft-card__lock { color: var(--ft-muted-fg); display: inline-flex; }\n.ft-card__caret {\n width: 18px;\n height: 18px;\n display: inline-flex;\n align-items: center;\n justify-content: center;\n font-family: var(--ft-mono);\n font-size: 12px;\n color: var(--ft-muted-fg);\n border: 1px solid var(--ft-line);\n}\n.ft-card--active .ft-card__caret {\n color: var(--tier-local);\n border-color: var(--tier-local);\n}\n\n.ft-card__rail {\n display: flex;\n flex-direction: column;\n border-left: 1px solid var(--ft-line);\n background: oklch(0.19 0.008 80);\n}\n.ft-card__mini {\n flex: 1;\n display: inline-flex;\n align-items: center;\n justify-content: center;\n width: 28px;\n min-height: 20px;\n background: transparent;\n border: none;\n color: var(--ft-muted-fg);\n cursor: pointer;\n transition: background 150ms, color 150ms;\n border-bottom: 1px solid var(--ft-line);\n padding: 0;\n text-decoration: none;\n}\n.ft-card__mini:last-child { border-bottom: none; }\n.ft-card__mini:hover { background: oklch(0.23 0.01 80); color: var(--tier-local); }\n.ft-card__mini.is-on { color: var(--tier-local); background: oklch(0.23 0.01 80); }\n\n/* states */\n.ft-card--active {\n background: oklch(0.25 0.015 60);\n box-shadow: 0 0 0 1px var(--tier-local), 0 14px 34px -16px oklch(0.5 0.18 50 / 0.55);\n transform: scale(1.02);\n}\n.ft-card--dim {\n filter: grayscale(0.85) brightness(0.82);\n opacity: 0.7;\n transform: scale(0.88);\n transform-origin: center left;\n}\n.ft-card--dim .ft-card__label { color: var(--ft-dim-fg); }\n.ft-card--dim:hover {\n filter: grayscale(0.3) brightness(0.96);\n opacity: 1;\n transform: scale(0.94);\n}\n.ft-card--cta {\n border-left-color: var(--ft-tier-orange);\n background: linear-gradient(180deg, oklch(0.23 0.02 55) 0%, oklch(0.2 0.015 60) 100%);\n}\n.ft-card--root {\n border-left-color: var(--ft-tier-orange);\n background: oklch(0.26 0.015 55);\n}\n.ft-card--root .ft-card__icon {\n color: var(--ft-tier-orange);\n border-color: var(--ft-tier-orange);\n}\n\n/* detail pop */\n.ft-detail {\n position: relative;\n margin-top: 0;\n padding: 16px 18px 14px;\n background: oklch(0.16 0.008 80);\n border: 1px solid var(--ft-line-strong);\n border-left: 2px solid var(--ft-tier-orange);\n border-radius: 3px;\n animation: ftDetailIn 220ms cubic-bezier(.2,.8,.2,1) backwards;\n color: var(--ft-fg);\n width: 320px;\n box-shadow: 0 18px 40px -8px rgba(0,0,0,0.6), 0 2px 8px rgba(0,0,0,0.4);\n}\n.ft-detail__head {\n display: flex;\n align-items: center;\n /* Clear room for the two stacked rail buttons in the top-right\n (expand + close). Bumped from 28px \u2192 58px to fit both. */\n margin: -2px 58px 10px 0;\n}\n.ft-detail__title {\n font-family: var(--ft-mono);\n font-size: 10px;\n letter-spacing: 0.14em;\n text-transform: uppercase;\n color: var(--ft-muted-fg);\n}\n.ft-detail__close,\n.ft-detail__expand {\n position: absolute;\n top: 8px;\n width: 22px;\n height: 22px;\n display: inline-flex;\n align-items: center;\n justify-content: center;\n background: transparent;\n border: 1px solid var(--ft-line);\n border-radius: 2px;\n color: var(--ft-muted-fg);\n cursor: pointer;\n padding: 0;\n transition: color 120ms, border-color 120ms, background 120ms;\n}\n.ft-detail__close { right: 8px; }\n/* Expand button sits immediately to the left of close. */\n.ft-detail__expand { right: 36px; }\n.ft-detail__close:hover,\n.ft-detail__expand:hover {\n color: var(--ft-fg);\n border-color: var(--ft-tier-orange);\n background: oklch(0.22 0.008 80);\n}\n@keyframes ftDetailIn {\n from { opacity: 0; transform: translateY(-4px); }\n to { opacity: 1; transform: translateY(0); }\n}\n.ft-detail__lead {\n /* Inter (sans) at slightly larger size + medium weight reads\n dramatically better than serif italic on dark stages, while still\n looking distinct from the body paragraph. The orange accent\n border on the left preserves the \"pull-quote\" rhythm. */\n font-family: var(--ft-sans);\n font-size: 14.5px;\n line-height: 1.45;\n font-style: normal;\n font-weight: 500;\n color: var(--ft-fg);\n padding-left: 10px;\n border-left: 2px solid var(--ft-tier-orange);\n margin-bottom: 12px;\n}\n.ft-detail__body {\n margin: 0 0 10px;\n font-size: 12px;\n color: var(--ft-muted-fg);\n line-height: 1.5;\n}\n.ft-detail__bullets {\n margin: 0 0 10px;\n padding-left: 16px;\n font-size: 12px;\n color: var(--ft-fg);\n}\n.ft-detail__bullets li { margin-bottom: 3px; }\n.ft-detail__meta {\n margin: 0 0 10px;\n padding: 0;\n display: flex;\n flex-direction: column;\n gap: 4px;\n}\n.ft-detail__meta-row {\n display: grid;\n grid-template-columns: 96px 1fr;\n gap: 8px;\n font-size: 11px;\n}\n.ft-detail__meta-row dt {\n font-family: var(--ft-mono);\n font-size: 9.5px;\n letter-spacing: 0.1em;\n text-transform: uppercase;\n color: var(--ft-muted-fg);\n padding-top: 1px;\n}\n.ft-detail__meta-row dd { margin: 0; color: var(--ft-fg); }\n.ft-detail__actions {\n display: flex;\n gap: 6px;\n flex-wrap: wrap;\n margin-top: 8px;\n}\n\n.ft-btn {\n font-family: var(--ft-mono);\n font-size: 10px;\n letter-spacing: 0.1em;\n text-transform: uppercase;\n padding: 7px 10px;\n background: transparent;\n color: var(--ft-fg);\n border: 1px solid var(--ft-line-strong);\n text-decoration: none;\n cursor: pointer;\n display: inline-flex;\n gap: 5px;\n align-items: center;\n transition: border-color 150ms, background 150ms;\n}\n.ft-btn:hover { border-color: var(--ft-tier-orange); }\n.ft-btn--primary {\n background: var(--ft-tier-orange);\n color: oklch(0.18 0.01 50);\n border-color: var(--ft-tier-orange);\n}\n\n@media (max-width: 760px) {\n .ft-header { padding: 16px 18px 14px; }\n .ft-header__title { font-size: 32px; }\n .ft-controls__hint { display: none; }\n .ft-legend { max-width: calc(100% - 24px); left: 10px; bottom: 10px; }\n}\n\n/* \u2500\u2500\u2500 Fullscreen detail overlay \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n Triggered by the expand button in any inline DetailPop. Renders\n inside .ft-stage as an absolutely-positioned scrim + scrollable\n panel. The inline pop stays mounted (cheaper React tree) but the\n user's focus and the pointer-events both transfer to the overlay\n while it's visible.\n*/\n.ft-detail-overlay {\n position: absolute;\n inset: 0;\n z-index: 20;\n display: flex;\n align-items: stretch;\n justify-content: center;\n /* Translucent backdrop blurs the flow tree behind so the focused\n content reads cleanly without losing the user's \"where am I\"\n spatial anchor. */\n background: oklch(0.12 0.008 80 / 0.78);\n backdrop-filter: blur(6px);\n animation: ftOverlayIn 200ms cubic-bezier(.2,.8,.2,1);\n}\n@keyframes ftOverlayIn {\n from { opacity: 0; }\n to { opacity: 1; }\n}\n.ft-detail-overlay__panel {\n position: relative;\n margin: 36px;\n flex: 1 1 auto;\n max-width: 760px;\n background: oklch(0.18 0.008 80);\n border: 1px solid var(--ft-line-strong);\n border-left: 3px solid var(--ft-tier-orange);\n border-radius: 4px;\n overflow: auto;\n padding: 26px 32px 28px;\n box-shadow: 0 30px 80px -10px oklch(0 0 0 / 0.7);\n animation: ftOverlayPanelIn 240ms cubic-bezier(.2,.8,.2,1);\n}\n@keyframes ftOverlayPanelIn {\n from { opacity: 0; transform: translateY(8px); }\n to { opacity: 1; transform: translateY(0); }\n}\n.ft-detail-overlay__panel .ft-detail__title {\n font-size: 11px;\n}\n.ft-detail-overlay__panel .ft-detail__lead {\n font-size: 18px;\n line-height: 1.5;\n padding-left: 14px;\n}\n.ft-detail-overlay__panel .ft-detail__body {\n font-size: 14px;\n color: var(--ft-fg);\n line-height: 1.65;\n}\n.ft-detail-overlay__panel .ft-detail__bullets {\n font-size: 14px;\n line-height: 1.55;\n padding-left: 20px;\n}\n.ft-detail-overlay__panel .ft-detail__bullets li { margin-bottom: 6px; }\n.ft-detail-overlay__panel .ft-detail__meta-row {\n font-size: 13px;\n grid-template-columns: 140px 1fr;\n}\n.ft-detail-overlay__panel .ft-detail__meta-row dt { font-size: 10.5px; }\n.ft-detail-overlay__panel .ft-btn { font-size: 11px; padding: 9px 14px; }\n.ft-detail-overlay__head {\n display: flex;\n align-items: center;\n margin: 0 0 14px;\n padding-right: 70px;\n}\n.ft-detail-overlay__title {\n font-family: var(--ft-mono);\n font-size: 11px;\n letter-spacing: 0.18em;\n text-transform: uppercase;\n color: var(--ft-tier-orange);\n}\n.ft-detail-overlay__close,\n.ft-detail-overlay__collapse {\n position: absolute;\n top: 16px;\n width: 28px;\n height: 28px;\n display: inline-flex;\n align-items: center;\n justify-content: center;\n background: transparent;\n border: 1px solid var(--ft-line-strong);\n border-radius: 2px;\n color: var(--ft-fg);\n cursor: pointer;\n padding: 0;\n transition: color 120ms, border-color 120ms, background 120ms;\n}\n.ft-detail-overlay__close { right: 16px; }\n.ft-detail-overlay__collapse { right: 52px; }\n.ft-detail-overlay__close:hover,\n.ft-detail-overlay__collapse:hover {\n color: var(--ft-tier-orange);\n border-color: var(--ft-tier-orange);\n background: oklch(0.22 0.008 80);\n}\n\n@media (max-width: 760px) {\n .ft-detail-overlay__panel {\n margin: 12px;\n padding: 18px 18px 22px;\n }\n .ft-detail-overlay__panel .ft-detail__lead { font-size: 16px; }\n}\n";
|
|
5296
|
+
declare const FLOW_TREE_CSS = "\n@import url(\"https://fonts.googleapis.com/css2?family=Instrument+Serif:ital@0;1&family=Inter:wght@400;500;600&family=JetBrains+Mono:wght@400;500&display=swap\");\n\n.ft-shell {\n --ft-bg: oklch(0.165 0.008 80);\n --ft-panel: oklch(0.21 0.008 80);\n --ft-panel-2: oklch(0.25 0.01 80);\n --ft-fg: oklch(0.96 0.005 85);\n --ft-muted-fg: oklch(0.68 0.008 80);\n --ft-dim-fg: oklch(0.5 0.008 80);\n --ft-line: oklch(0.28 0.008 80);\n --ft-line-strong: oklch(0.4 0.008 80);\n --ft-grid-strong: oklch(0.24 0.008 80);\n --ft-tier-orange: #f16c25;\n --ft-tier-teal: #5fb7b8;\n --ft-tier-blue: oklch(0.72 0.12 240);\n --ft-tier-amber: oklch(0.8 0.14 75);\n --ft-tier-green: oklch(0.74 0.14 155);\n --ft-accent: var(--ft-tier-orange);\n --ft-serif: \"Instrument Serif\", Georgia, serif;\n --ft-sans: \"Inter\", system-ui, sans-serif;\n --ft-mono: \"JetBrains Mono\", ui-monospace, Menlo, monospace;\n\n /* Defensive shell sizing \u2014 fills flex parents (flex: 1) and block\n parents with definite dimensions (width/height: 100%). */\n flex: 1 1 auto;\n width: 100%;\n height: 100%;\n min-height: 420px;\n display: flex;\n flex-direction: column;\n background: var(--ft-bg);\n border: 1px solid var(--ft-line);\n border-radius: 2px;\n overflow: hidden;\n color: var(--ft-fg);\n font-family: var(--ft-sans);\n font-size: 14px;\n line-height: 1.5;\n -webkit-font-smoothing: antialiased;\n box-shadow: 0 30px 60px -30px oklch(0 0 0 / 0.5);\n}\n.ft-shell * { box-sizing: border-box; }\n.ft-shell .ft-mono {\n font-family: var(--ft-mono);\n font-size: 11px;\n letter-spacing: 0.02em;\n}\n\n.ft-header {\n padding: 22px 28px 20px;\n border-bottom: 1px solid var(--ft-line);\n background: linear-gradient(180deg, oklch(0.21 0.01 60) 0%, oklch(0.19 0.008 70) 100%);\n display: flex;\n flex-direction: column;\n gap: 6px;\n}\n.ft-header__eyebrow {\n font-family: var(--ft-mono);\n font-size: 10.5px;\n letter-spacing: 0.22em;\n text-transform: uppercase;\n color: var(--ft-tier-orange);\n}\n.ft-header__title {\n margin: 0;\n font-family: var(--ft-serif);\n font-weight: 400;\n font-size: 44px;\n line-height: 1;\n letter-spacing: -0.015em;\n color: var(--ft-fg);\n}\n.ft-header__tagline {\n margin: 6px 0 0;\n max-width: 76ch;\n color: var(--ft-muted-fg);\n font-size: 13px;\n}\n\n.ft-stage {\n position: relative;\n flex: 1;\n min-height: 0;\n overflow: hidden;\n background:\n radial-gradient(1200px 700px at 15% -10%, oklch(0.22 0.012 60), transparent 60%),\n radial-gradient(900px 600px at 110% 110%, oklch(0.2 0.02 200 / 0.5), transparent 65%);\n}\n\n.ft-svg {\n display: block;\n width: 100%;\n height: 100%;\n background: transparent;\n cursor: grab;\n touch-action: none;\n user-select: none;\n}\n.ft-svg.is-dragging { cursor: grabbing; }\n\n.ft-controls {\n position: absolute;\n top: 14px;\n right: 14px;\n z-index: 5;\n display: flex;\n align-items: center;\n gap: 6px;\n background: oklch(0.2 0.008 80 / 0.94);\n border: 1px solid var(--ft-line-strong);\n padding: 5px 8px;\n backdrop-filter: blur(8px);\n border-radius: 2px;\n}\n.ft-controls button {\n font-family: var(--ft-mono);\n font-size: 12px;\n width: 26px;\n height: 26px;\n background: transparent;\n border: 1px solid var(--ft-line);\n color: var(--ft-fg);\n cursor: pointer;\n padding: 0;\n}\n.ft-controls button:hover { border-color: var(--ft-tier-orange); }\n.ft-controls__reset {\n width: auto !important;\n padding: 0 10px !important;\n font-size: 9.5px !important;\n letter-spacing: 0.1em;\n text-transform: uppercase;\n}\n.ft-controls__hint {\n font-family: var(--ft-mono);\n font-size: 9.5px;\n color: var(--ft-muted-fg);\n letter-spacing: 0.04em;\n padding-left: 8px;\n margin-left: 2px;\n border-left: 1px solid var(--ft-line);\n}\n\n.ft-legend {\n position: absolute;\n left: 18px;\n bottom: 14px;\n z-index: 5;\n display: flex;\n flex-wrap: wrap;\n align-items: center;\n gap: 6px;\n font-family: var(--ft-mono);\n font-size: 10px;\n color: var(--ft-muted-fg);\n background: oklch(0.2 0.008 80 / 0.88);\n border: 1px solid var(--ft-line);\n padding: 6px 10px;\n backdrop-filter: blur(6px);\n max-width: calc(100% - 220px);\n}\n.ft-legend__chip {\n display: inline-flex;\n align-items: center;\n gap: 6px;\n padding: 2px 6px;\n margin: -2px 0;\n background: transparent;\n border: none;\n color: inherit;\n font: inherit;\n letter-spacing: inherit;\n cursor: pointer;\n border-radius: 2px;\n transition: background 120ms, color 120ms;\n}\n.ft-legend__chip:hover {\n background: oklch(0.24 0.01 80);\n color: var(--ft-fg);\n}\n.ft-legend__chip:disabled {\n cursor: default;\n color: var(--ft-fg);\n}\n.ft-legend__chip:disabled:hover { background: transparent; }\n.ft-legend__dot {\n width: 5px;\n height: 5px;\n border-radius: 50%;\n background: var(--ft-tier-orange);\n flex-shrink: 0;\n}\n.ft-legend__sep {\n color: var(--ft-line-strong);\n margin: 0 2px;\n pointer-events: none;\n user-select: none;\n}\n\n/* edges */\n.ft-edge {\n fill: none;\n /* Keep stroke widths crisp under the SVG <g scale(z)> pan/zoom transform.\n Without this, fit-to-viewport (z \u2248 0.6-0.9) renders 1.6px strokes as\n sub-pixel and they visually disappear. */\n vector-effect: non-scaling-stroke;\n transition: stroke 260ms, opacity 260ms;\n}\n.ft-edge--off { stroke: var(--ft-line); stroke-width: 1; opacity: 0.55; }\n.ft-edge--on {\n stroke: var(--ft-tier-orange);\n /* Wider stroke than off-path so the dot pattern reads clearly on\n dense paths and on high-DPI displays where 1.6px antialiased to\n \"barely visible\". Cross-browser tested floor; renders a solid 3px\n dot (1px hard center + 1px round cap each side) every 13px. */\n stroke-width: 3;\n stroke-linecap: round;\n /* 1px hard dash + 12px gap. With stroke-linecap:round the cap\n extends each dash by half-stroke-width on each end, producing\n visible 4px round beads spaced 13px apart. The \"0.1 9\" pattern in\n 1.30.0 was below subpixel-antialiasing threshold in some\n renderers (particularly Chromium under fit-to-viewport zoom) and\n the dashes disappeared entirely \u2014 leaving the line visually\n \"plain\". 1px is the minimum value every browser renders. */\n stroke-dasharray: 1 12;\n opacity: 1;\n /* Drop-shadow drops a tier-tinted glow under the line. Cheap on\n desktop, slightly more expensive on mobile GPUs but well within\n budget for the handful of paths we render at once. */\n filter: drop-shadow(0 0 5px oklch(0.7 0.18 50 / 0.55));\n /* Animate stroke-dashoffset so beads visibly travel along the\n path. Speed tuned to ~30px/s on a typical card connector \u2014 fast\n enough to read as motion without becoming distracting. */\n animation: ftDashFlow 1.4s linear infinite;\n /* Keep the bead pattern crisp under fit-to-viewport scale()\n transforms \u2014 without this, sub-1 zoom turns 3px strokes into\n fractional pixels and the beads merge into a hairline. */\n vector-effect: non-scaling-stroke;\n}\n.ft-edge--on.ft-edge--solid {\n /* Fallback for any consumer that explicitly opts out of the bead\n pattern \u2014 keeps the legacy solid orange line. */\n stroke-dasharray: none;\n animation: none;\n}\n@keyframes ftDashFlow {\n /* -26 == 2 \u00D7 (1 + 12) so each cycle advances the bead exactly two\n full units, which reads as smooth perpetual flow at 1.4s. */\n to { stroke-dashoffset: -26; }\n}\n@media (prefers-reduced-motion: reduce) {\n .ft-edge--on {\n animation: none;\n /* When motion is suppressed, drop the dot pattern too so users\n see a clean solid line instead of a static frozen dash. */\n stroke-dasharray: none;\n }\n}\n\n/* card */\n.ft-card {\n --tier-local: var(--tier, var(--ft-line-strong));\n width: 100%;\n height: 100%;\n display: grid;\n grid-template-columns: 1fr auto;\n gap: 0;\n align-items: stretch;\n background: oklch(0.21 0.008 80);\n border: 1px solid var(--ft-line);\n border-left: 3px solid var(--tier-local);\n border-radius: 2px;\n transition:\n background 260ms cubic-bezier(.2,.8,.2,1),\n border-color 260ms, box-shadow 260ms,\n transform 260ms cubic-bezier(.2,.8,.2,1),\n filter 260ms, opacity 260ms, color 260ms;\n overflow: visible;\n animation: ftCardIn 320ms cubic-bezier(.2,.8,.2,1) backwards;\n}\n@keyframes ftCardIn {\n from { opacity: 0; transform: translateX(-6px); }\n to { opacity: 1; transform: translateX(0); }\n}\n.ft-card__main {\n display: grid;\n grid-template-columns: auto 1fr auto;\n gap: 10px;\n align-items: center;\n padding: 0 10px 0 12px;\n background: transparent;\n border: none;\n color: inherit;\n font-family: var(--ft-sans);\n font-size: 13px;\n text-align: left;\n cursor: pointer;\n min-width: 0;\n}\n.ft-card__main:hover { background: oklch(0.23 0.01 80 / 0.6); }\n.ft-card__icon {\n width: 24px;\n height: 24px;\n display: inline-flex;\n align-items: center;\n justify-content: center;\n color: var(--tier-local);\n border: 1px solid var(--ft-line-strong);\n background: oklch(0.17 0.008 80);\n flex-shrink: 0;\n}\n.ft-card__body { display: flex; flex-direction: column; min-width: 0; gap: 1px; }\n.ft-card__label {\n font-family: var(--ft-serif);\n font-size: 15px;\n line-height: 1.15;\n color: var(--ft-fg);\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n display: flex;\n align-items: center;\n gap: 5px;\n}\n.ft-card__sub {\n font-family: var(--ft-mono);\n font-size: 9.5px;\n letter-spacing: 0.02em;\n color: var(--ft-muted-fg);\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n}\n.ft-card__lock { color: var(--ft-muted-fg); display: inline-flex; }\n.ft-card__caret {\n width: 18px;\n height: 18px;\n display: inline-flex;\n align-items: center;\n justify-content: center;\n font-family: var(--ft-mono);\n font-size: 12px;\n color: var(--ft-muted-fg);\n border: 1px solid var(--ft-line);\n}\n.ft-card--active .ft-card__caret {\n color: var(--tier-local);\n border-color: var(--tier-local);\n}\n\n.ft-card__rail {\n display: flex;\n flex-direction: column;\n border-left: 1px solid var(--ft-line);\n background: oklch(0.19 0.008 80);\n}\n.ft-card__mini {\n flex: 1;\n display: inline-flex;\n align-items: center;\n justify-content: center;\n width: 28px;\n min-height: 20px;\n background: transparent;\n border: none;\n color: var(--ft-muted-fg);\n cursor: pointer;\n transition: background 150ms, color 150ms;\n border-bottom: 1px solid var(--ft-line);\n padding: 0;\n text-decoration: none;\n}\n.ft-card__mini:last-child { border-bottom: none; }\n.ft-card__mini:hover { background: oklch(0.23 0.01 80); color: var(--tier-local); }\n.ft-card__mini.is-on { color: var(--tier-local); background: oklch(0.23 0.01 80); }\n\n/* states */\n.ft-card--active {\n background: oklch(0.25 0.015 60);\n box-shadow: 0 0 0 1px var(--tier-local), 0 14px 34px -16px oklch(0.5 0.18 50 / 0.55);\n transform: scale(1.02);\n}\n.ft-card--dim {\n filter: grayscale(0.85) brightness(0.82);\n opacity: 0.7;\n transform: scale(0.88);\n transform-origin: center left;\n}\n.ft-card--dim .ft-card__label { color: var(--ft-dim-fg); }\n.ft-card--dim:hover {\n filter: grayscale(0.3) brightness(0.96);\n opacity: 1;\n transform: scale(0.94);\n}\n.ft-card--cta {\n border-left-color: var(--ft-tier-orange);\n background: linear-gradient(180deg, oklch(0.23 0.02 55) 0%, oklch(0.2 0.015 60) 100%);\n}\n.ft-card--root {\n border-left-color: var(--ft-tier-orange);\n background: oklch(0.26 0.015 55);\n}\n.ft-card--root .ft-card__icon {\n color: var(--ft-tier-orange);\n border-color: var(--ft-tier-orange);\n}\n\n/* detail pop */\n.ft-detail {\n position: relative;\n margin-top: 0;\n padding: 16px 18px 14px;\n background: oklch(0.16 0.008 80);\n border: 1px solid var(--ft-line-strong);\n border-left: 2px solid var(--ft-tier-orange);\n border-radius: 3px;\n animation: ftDetailIn 220ms cubic-bezier(.2,.8,.2,1) backwards;\n color: var(--ft-fg);\n width: 320px;\n box-shadow: 0 18px 40px -8px rgba(0,0,0,0.6), 0 2px 8px rgba(0,0,0,0.4);\n}\n.ft-detail__head {\n display: flex;\n align-items: center;\n /* Clear room for the two stacked rail buttons in the top-right\n (expand + close). Bumped from 28px \u2192 58px to fit both. */\n margin: -2px 58px 10px 0;\n}\n.ft-detail__title {\n font-family: var(--ft-mono);\n font-size: 10px;\n letter-spacing: 0.14em;\n text-transform: uppercase;\n color: var(--ft-muted-fg);\n}\n.ft-detail__close,\n.ft-detail__expand {\n position: absolute;\n top: 8px;\n width: 22px;\n height: 22px;\n display: inline-flex;\n align-items: center;\n justify-content: center;\n background: transparent;\n border: 1px solid var(--ft-line);\n border-radius: 2px;\n color: var(--ft-muted-fg);\n cursor: pointer;\n padding: 0;\n transition: color 120ms, border-color 120ms, background 120ms;\n}\n.ft-detail__close { right: 8px; }\n/* Expand button sits immediately to the left of close. */\n.ft-detail__expand { right: 36px; }\n.ft-detail__close:hover,\n.ft-detail__expand:hover {\n color: var(--ft-fg);\n border-color: var(--ft-tier-orange);\n background: oklch(0.22 0.008 80);\n}\n@keyframes ftDetailIn {\n from { opacity: 0; transform: translateY(-4px); }\n to { opacity: 1; transform: translateY(0); }\n}\n.ft-detail__lead {\n /* Inter (sans) at slightly larger size + medium weight reads\n dramatically better than serif italic on dark stages, while still\n looking distinct from the body paragraph. The orange accent\n border on the left preserves the \"pull-quote\" rhythm. */\n font-family: var(--ft-sans);\n font-size: 14.5px;\n line-height: 1.45;\n font-style: normal;\n font-weight: 500;\n color: var(--ft-fg);\n padding-left: 10px;\n border-left: 2px solid var(--ft-tier-orange);\n margin-bottom: 12px;\n}\n.ft-detail__body {\n margin: 0 0 10px;\n font-size: 12px;\n color: var(--ft-muted-fg);\n line-height: 1.5;\n}\n.ft-detail__bullets {\n margin: 0 0 10px;\n padding-left: 16px;\n font-size: 12px;\n color: var(--ft-fg);\n}\n.ft-detail__bullets li { margin-bottom: 3px; }\n.ft-detail__meta {\n margin: 0 0 10px;\n padding: 0;\n display: flex;\n flex-direction: column;\n gap: 4px;\n}\n.ft-detail__meta-row {\n display: grid;\n grid-template-columns: 96px 1fr;\n gap: 8px;\n font-size: 11px;\n}\n.ft-detail__meta-row dt {\n font-family: var(--ft-mono);\n font-size: 9.5px;\n letter-spacing: 0.1em;\n text-transform: uppercase;\n color: var(--ft-muted-fg);\n padding-top: 1px;\n}\n.ft-detail__meta-row dd { margin: 0; color: var(--ft-fg); }\n.ft-detail__actions {\n display: flex;\n gap: 6px;\n flex-wrap: wrap;\n margin-top: 8px;\n}\n\n.ft-btn {\n font-family: var(--ft-mono);\n font-size: 10px;\n letter-spacing: 0.1em;\n text-transform: uppercase;\n padding: 7px 10px;\n background: transparent;\n color: var(--ft-fg);\n border: 1px solid var(--ft-line-strong);\n text-decoration: none;\n cursor: pointer;\n display: inline-flex;\n gap: 5px;\n align-items: center;\n transition: border-color 150ms, background 150ms;\n}\n.ft-btn:hover { border-color: var(--ft-tier-orange); }\n.ft-btn--primary {\n background: var(--ft-tier-orange);\n color: oklch(0.18 0.01 50);\n border-color: var(--ft-tier-orange);\n}\n\n@media (max-width: 760px) {\n .ft-header { padding: 16px 18px 14px; }\n .ft-header__title { font-size: 32px; }\n .ft-controls__hint { display: none; }\n .ft-legend { max-width: calc(100% - 24px); left: 10px; bottom: 10px; }\n /* Larger touch targets on phones \u2014 bumps zoom controls from 26px\n to 36px to comfortably clear the 44px Apple HIG / Material 48dp\n minimum once you account for visual padding. */\n .ft-controls { padding: 6px 8px; }\n .ft-controls button { width: 36px; height: 36px; font-size: 16px; }\n .ft-controls__reset { padding: 0 14px !important; height: 36px; }\n /* Inline detail popup buttons are intentionally compact in the\n inline footprint \u2014 but on touch the user can promote to the\n fullscreen overlay which has 28px buttons by default. */\n .ft-detail__close, .ft-detail__expand { width: 28px; height: 28px; }\n .ft-detail__expand { right: 42px; }\n .ft-detail__head { margin-right: 70px; }\n}\n@media (max-width: 480px) {\n /* Phone-sized: shrink card width so 5+ depth levels still fit\n reasonably without forcing the user to scroll horizontally to\n read the labels. Cards still ellipsis-truncate when needed. */\n .ft-card { font-size: 12px; }\n .ft-card__label { font-size: 14px; }\n .ft-card__sub { font-size: 9px; }\n .ft-controls__hint { display: none; }\n}\n\n/* \u2500\u2500\u2500 Fullscreen detail overlay \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n Triggered by the expand button in any inline DetailPop. Renders\n inside .ft-stage as an absolutely-positioned scrim + scrollable\n panel. The inline pop stays mounted (cheaper React tree) but the\n user's focus and the pointer-events both transfer to the overlay\n while it's visible.\n*/\n.ft-detail-overlay {\n position: absolute;\n inset: 0;\n z-index: 20;\n display: flex;\n align-items: stretch;\n justify-content: center;\n /* Translucent backdrop blurs the flow tree behind so the focused\n content reads cleanly without losing the user's \"where am I\"\n spatial anchor. */\n background: oklch(0.12 0.008 80 / 0.78);\n backdrop-filter: blur(6px);\n animation: ftOverlayIn 200ms cubic-bezier(.2,.8,.2,1);\n}\n@keyframes ftOverlayIn {\n from { opacity: 0; }\n to { opacity: 1; }\n}\n.ft-detail-overlay__panel {\n position: relative;\n margin: 36px;\n flex: 1 1 auto;\n max-width: 760px;\n background: oklch(0.18 0.008 80);\n border: 1px solid var(--ft-line-strong);\n border-left: 3px solid var(--ft-tier-orange);\n border-radius: 4px;\n overflow: auto;\n padding: 26px 32px 28px;\n box-shadow: 0 30px 80px -10px oklch(0 0 0 / 0.7);\n animation: ftOverlayPanelIn 240ms cubic-bezier(.2,.8,.2,1);\n}\n@keyframes ftOverlayPanelIn {\n from { opacity: 0; transform: translateY(8px); }\n to { opacity: 1; transform: translateY(0); }\n}\n.ft-detail-overlay__panel .ft-detail__title {\n font-size: 11px;\n}\n.ft-detail-overlay__panel .ft-detail__lead {\n font-size: 18px;\n line-height: 1.5;\n padding-left: 14px;\n}\n.ft-detail-overlay__panel .ft-detail__body {\n font-size: 14px;\n color: var(--ft-fg);\n line-height: 1.65;\n}\n.ft-detail-overlay__panel .ft-detail__bullets {\n font-size: 14px;\n line-height: 1.55;\n padding-left: 20px;\n}\n.ft-detail-overlay__panel .ft-detail__bullets li { margin-bottom: 6px; }\n.ft-detail-overlay__panel .ft-detail__meta-row {\n font-size: 13px;\n grid-template-columns: 140px 1fr;\n}\n.ft-detail-overlay__panel .ft-detail__meta-row dt { font-size: 10.5px; }\n.ft-detail-overlay__panel .ft-btn { font-size: 11px; padding: 9px 14px; }\n.ft-detail-overlay__head {\n display: flex;\n align-items: center;\n margin: 0 0 14px;\n padding-right: 70px;\n}\n.ft-detail-overlay__title {\n font-family: var(--ft-mono);\n font-size: 11px;\n letter-spacing: 0.18em;\n text-transform: uppercase;\n color: var(--ft-tier-orange);\n}\n.ft-detail-overlay__close,\n.ft-detail-overlay__collapse {\n position: absolute;\n top: 16px;\n width: 28px;\n height: 28px;\n display: inline-flex;\n align-items: center;\n justify-content: center;\n background: transparent;\n border: 1px solid var(--ft-line-strong);\n border-radius: 2px;\n color: var(--ft-fg);\n cursor: pointer;\n padding: 0;\n transition: color 120ms, border-color 120ms, background 120ms;\n}\n.ft-detail-overlay__close { right: 16px; }\n.ft-detail-overlay__collapse { right: 52px; }\n.ft-detail-overlay__close:hover,\n.ft-detail-overlay__collapse:hover {\n color: var(--ft-tier-orange);\n border-color: var(--ft-tier-orange);\n background: oklch(0.22 0.008 80);\n}\n\n@media (max-width: 760px) {\n .ft-detail-overlay__panel {\n margin: 12px;\n padding: 18px 18px 22px;\n }\n .ft-detail-overlay__panel .ft-detail__lead { font-size: 16px; }\n}\n";
|
|
5297
5297
|
|
|
5298
5298
|
type GameTimerMode = "countdown" | "countup";
|
|
5299
5299
|
type GameTimerThemePreset = "green" | "red" | "blue" | "amber" | "purple" | "pink" | "teal";
|
package/dist/index.js
CHANGED
|
@@ -36124,15 +36124,33 @@ var FLOW_TREE_CSS = `
|
|
|
36124
36124
|
.ft-edge--off { stroke: var(--ft-line); stroke-width: 1; opacity: 0.55; }
|
|
36125
36125
|
.ft-edge--on {
|
|
36126
36126
|
stroke: var(--ft-tier-orange);
|
|
36127
|
-
stroke-
|
|
36127
|
+
/* Wider stroke than off-path so the dot pattern reads clearly on
|
|
36128
|
+
dense paths and on high-DPI displays where 1.6px antialiased to
|
|
36129
|
+
"barely visible". Cross-browser tested floor; renders a solid 3px
|
|
36130
|
+
dot (1px hard center + 1px round cap each side) every 13px. */
|
|
36131
|
+
stroke-width: 3;
|
|
36128
36132
|
stroke-linecap: round;
|
|
36129
|
-
/*
|
|
36130
|
-
stroke-
|
|
36131
|
-
|
|
36132
|
-
|
|
36133
|
+
/* 1px hard dash + 12px gap. With stroke-linecap:round the cap
|
|
36134
|
+
extends each dash by half-stroke-width on each end, producing
|
|
36135
|
+
visible 4px round beads spaced 13px apart. The "0.1 9" pattern in
|
|
36136
|
+
1.30.0 was below subpixel-antialiasing threshold in some
|
|
36137
|
+
renderers (particularly Chromium under fit-to-viewport zoom) and
|
|
36138
|
+
the dashes disappeared entirely \u2014 leaving the line visually
|
|
36139
|
+
"plain". 1px is the minimum value every browser renders. */
|
|
36140
|
+
stroke-dasharray: 1 12;
|
|
36133
36141
|
opacity: 1;
|
|
36134
|
-
|
|
36135
|
-
|
|
36142
|
+
/* Drop-shadow drops a tier-tinted glow under the line. Cheap on
|
|
36143
|
+
desktop, slightly more expensive on mobile GPUs but well within
|
|
36144
|
+
budget for the handful of paths we render at once. */
|
|
36145
|
+
filter: drop-shadow(0 0 5px oklch(0.7 0.18 50 / 0.55));
|
|
36146
|
+
/* Animate stroke-dashoffset so beads visibly travel along the
|
|
36147
|
+
path. Speed tuned to ~30px/s on a typical card connector \u2014 fast
|
|
36148
|
+
enough to read as motion without becoming distracting. */
|
|
36149
|
+
animation: ftDashFlow 1.4s linear infinite;
|
|
36150
|
+
/* Keep the bead pattern crisp under fit-to-viewport scale()
|
|
36151
|
+
transforms \u2014 without this, sub-1 zoom turns 3px strokes into
|
|
36152
|
+
fractional pixels and the beads merge into a hairline. */
|
|
36153
|
+
vector-effect: non-scaling-stroke;
|
|
36136
36154
|
}
|
|
36137
36155
|
.ft-edge--on.ft-edge--solid {
|
|
36138
36156
|
/* Fallback for any consumer that explicitly opts out of the bead
|
|
@@ -36141,12 +36159,17 @@ var FLOW_TREE_CSS = `
|
|
|
36141
36159
|
animation: none;
|
|
36142
36160
|
}
|
|
36143
36161
|
@keyframes ftDashFlow {
|
|
36144
|
-
/* -
|
|
36145
|
-
|
|
36146
|
-
to { stroke-dashoffset: -
|
|
36162
|
+
/* -26 == 2 \xD7 (1 + 12) so each cycle advances the bead exactly two
|
|
36163
|
+
full units, which reads as smooth perpetual flow at 1.4s. */
|
|
36164
|
+
to { stroke-dashoffset: -26; }
|
|
36147
36165
|
}
|
|
36148
36166
|
@media (prefers-reduced-motion: reduce) {
|
|
36149
|
-
.ft-edge--on {
|
|
36167
|
+
.ft-edge--on {
|
|
36168
|
+
animation: none;
|
|
36169
|
+
/* When motion is suppressed, drop the dot pattern too so users
|
|
36170
|
+
see a clean solid line instead of a static frozen dash. */
|
|
36171
|
+
stroke-dasharray: none;
|
|
36172
|
+
}
|
|
36150
36173
|
}
|
|
36151
36174
|
|
|
36152
36175
|
/* card */
|
|
@@ -36440,6 +36463,27 @@ var FLOW_TREE_CSS = `
|
|
|
36440
36463
|
.ft-header__title { font-size: 32px; }
|
|
36441
36464
|
.ft-controls__hint { display: none; }
|
|
36442
36465
|
.ft-legend { max-width: calc(100% - 24px); left: 10px; bottom: 10px; }
|
|
36466
|
+
/* Larger touch targets on phones \u2014 bumps zoom controls from 26px
|
|
36467
|
+
to 36px to comfortably clear the 44px Apple HIG / Material 48dp
|
|
36468
|
+
minimum once you account for visual padding. */
|
|
36469
|
+
.ft-controls { padding: 6px 8px; }
|
|
36470
|
+
.ft-controls button { width: 36px; height: 36px; font-size: 16px; }
|
|
36471
|
+
.ft-controls__reset { padding: 0 14px !important; height: 36px; }
|
|
36472
|
+
/* Inline detail popup buttons are intentionally compact in the
|
|
36473
|
+
inline footprint \u2014 but on touch the user can promote to the
|
|
36474
|
+
fullscreen overlay which has 28px buttons by default. */
|
|
36475
|
+
.ft-detail__close, .ft-detail__expand { width: 28px; height: 28px; }
|
|
36476
|
+
.ft-detail__expand { right: 42px; }
|
|
36477
|
+
.ft-detail__head { margin-right: 70px; }
|
|
36478
|
+
}
|
|
36479
|
+
@media (max-width: 480px) {
|
|
36480
|
+
/* Phone-sized: shrink card width so 5+ depth levels still fit
|
|
36481
|
+
reasonably without forcing the user to scroll horizontally to
|
|
36482
|
+
read the labels. Cards still ellipsis-truncate when needed. */
|
|
36483
|
+
.ft-card { font-size: 12px; }
|
|
36484
|
+
.ft-card__label { font-size: 14px; }
|
|
36485
|
+
.ft-card__sub { font-size: 9px; }
|
|
36486
|
+
.ft-controls__hint { display: none; }
|
|
36443
36487
|
}
|
|
36444
36488
|
|
|
36445
36489
|
/* \u2500\u2500\u2500 Fullscreen detail overlay \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
|
|
@@ -37241,15 +37285,16 @@ function FlowTree({
|
|
|
37241
37285
|
},
|
|
37242
37286
|
[zoom]
|
|
37243
37287
|
);
|
|
37244
|
-
|
|
37245
|
-
|
|
37288
|
+
function pickInitialBoost(viewW) {
|
|
37289
|
+
if (viewW <= 480) return { boost: 1.15, cap: 1.2 };
|
|
37290
|
+
if (viewW <= 900) return { boost: 1.3, cap: 1.35 };
|
|
37291
|
+
return { boost: 1.5, cap: 1.5 };
|
|
37292
|
+
}
|
|
37246
37293
|
const recenterOnFocused = React87.useCallback(() => {
|
|
37247
37294
|
const fit = computeFitZoom();
|
|
37248
37295
|
if (!fit) return;
|
|
37249
|
-
const
|
|
37250
|
-
|
|
37251
|
-
Math.min(MAX_INITIAL_ZOOM, fit.baseZ * INITIAL_ZOOM_BOOST)
|
|
37252
|
-
);
|
|
37296
|
+
const { boost, cap } = pickInitialBoost(fit.viewW);
|
|
37297
|
+
const z = Math.max(0.5, Math.min(cap, fit.baseZ * boost));
|
|
37253
37298
|
setZoom(z);
|
|
37254
37299
|
if (focusedTailNode) {
|
|
37255
37300
|
setPan({
|
|
@@ -37306,15 +37351,68 @@ function FlowTree({
|
|
|
37306
37351
|
ro.observe(containerRef.current);
|
|
37307
37352
|
return () => ro.disconnect();
|
|
37308
37353
|
}, [recenterOnFocused]);
|
|
37354
|
+
const ZOOM_MIN = 0.35;
|
|
37355
|
+
const ZOOM_MAX = 2.5;
|
|
37356
|
+
const pointersRef = React87.useRef(
|
|
37357
|
+
/* @__PURE__ */ new Map()
|
|
37358
|
+
);
|
|
37359
|
+
const pinchRef = React87.useRef(null);
|
|
37309
37360
|
const onPointerDown = (e) => {
|
|
37310
37361
|
if (e.target.closest("button, a")) return;
|
|
37311
37362
|
userInteractedRef.current = true;
|
|
37312
37363
|
markInteracted();
|
|
37313
|
-
|
|
37364
|
+
pointersRef.current.set(e.pointerId, { x: e.clientX, y: e.clientY });
|
|
37314
37365
|
e.currentTarget.setPointerCapture?.(e.pointerId);
|
|
37315
|
-
|
|
37366
|
+
if (pointersRef.current.size === 2) {
|
|
37367
|
+
const [a, b] = Array.from(pointersRef.current.values());
|
|
37368
|
+
const dist = Math.hypot(a.x - b.x, a.y - b.y) || 1;
|
|
37369
|
+
const host = e.currentTarget.getBoundingClientRect();
|
|
37370
|
+
pinchRef.current = {
|
|
37371
|
+
initialDist: dist,
|
|
37372
|
+
initialZoom: zoom,
|
|
37373
|
+
initialPan: { x: pan.x, y: pan.y },
|
|
37374
|
+
mid: {
|
|
37375
|
+
x: (a.x + b.x) / 2 - host.left,
|
|
37376
|
+
y: (a.y + b.y) / 2 - host.top
|
|
37377
|
+
}
|
|
37378
|
+
};
|
|
37379
|
+
dragRef.current = null;
|
|
37380
|
+
e.currentTarget.classList.remove("is-dragging");
|
|
37381
|
+
} else if (pointersRef.current.size === 1) {
|
|
37382
|
+
dragRef.current = {
|
|
37383
|
+
sx: e.clientX,
|
|
37384
|
+
sy: e.clientY,
|
|
37385
|
+
px: pan.x,
|
|
37386
|
+
py: pan.y
|
|
37387
|
+
};
|
|
37388
|
+
e.currentTarget.classList.add("is-dragging");
|
|
37389
|
+
}
|
|
37316
37390
|
};
|
|
37317
37391
|
const onPointerMove = (e) => {
|
|
37392
|
+
const tracked = pointersRef.current.get(e.pointerId);
|
|
37393
|
+
if (tracked) {
|
|
37394
|
+
tracked.x = e.clientX;
|
|
37395
|
+
tracked.y = e.clientY;
|
|
37396
|
+
}
|
|
37397
|
+
if (pinchRef.current && pointersRef.current.size >= 2) {
|
|
37398
|
+
const [a, b] = Array.from(pointersRef.current.values()).slice(0, 2);
|
|
37399
|
+
const dist = Math.hypot(a.x - b.x, a.y - b.y) || 1;
|
|
37400
|
+
const ratio = dist / pinchRef.current.initialDist;
|
|
37401
|
+
const newZ = Math.max(
|
|
37402
|
+
ZOOM_MIN,
|
|
37403
|
+
Math.min(ZOOM_MAX, pinchRef.current.initialZoom * ratio)
|
|
37404
|
+
);
|
|
37405
|
+
const z = pinchRef.current.initialZoom;
|
|
37406
|
+
const m = pinchRef.current.mid;
|
|
37407
|
+
const p0 = pinchRef.current.initialPan;
|
|
37408
|
+
const ratioZ = newZ / z;
|
|
37409
|
+
setPan({
|
|
37410
|
+
x: m.x - (m.x - p0.x) * ratioZ,
|
|
37411
|
+
y: m.y - (m.y - p0.y) * ratioZ
|
|
37412
|
+
});
|
|
37413
|
+
setZoom(newZ);
|
|
37414
|
+
return;
|
|
37415
|
+
}
|
|
37318
37416
|
if (!dragRef.current) return;
|
|
37319
37417
|
setPan({
|
|
37320
37418
|
x: dragRef.current.px + (e.clientX - dragRef.current.sx),
|
|
@@ -37322,11 +37420,23 @@ function FlowTree({
|
|
|
37322
37420
|
});
|
|
37323
37421
|
};
|
|
37324
37422
|
const onPointerUp = (e) => {
|
|
37325
|
-
|
|
37326
|
-
|
|
37423
|
+
pointersRef.current.delete(e.pointerId);
|
|
37424
|
+
if (pointersRef.current.size < 2) {
|
|
37425
|
+
pinchRef.current = null;
|
|
37426
|
+
}
|
|
37427
|
+
if (pointersRef.current.size === 0) {
|
|
37428
|
+
dragRef.current = null;
|
|
37429
|
+
e.currentTarget.classList.remove("is-dragging");
|
|
37430
|
+
} else if (pointersRef.current.size === 1) {
|
|
37431
|
+
const [remaining] = Array.from(pointersRef.current.values());
|
|
37432
|
+
dragRef.current = {
|
|
37433
|
+
sx: remaining.x,
|
|
37434
|
+
sy: remaining.y,
|
|
37435
|
+
px: pan.x,
|
|
37436
|
+
py: pan.y
|
|
37437
|
+
};
|
|
37438
|
+
}
|
|
37327
37439
|
};
|
|
37328
|
-
const ZOOM_MIN = 0.35;
|
|
37329
|
-
const ZOOM_MAX = 2.5;
|
|
37330
37440
|
const onWheel = (e) => {
|
|
37331
37441
|
if (!e.ctrlKey && !e.metaKey) return;
|
|
37332
37442
|
e.preventDefault();
|
|
@@ -37450,7 +37560,7 @@ function FlowTree({
|
|
|
37450
37560
|
children: "Fit"
|
|
37451
37561
|
}
|
|
37452
37562
|
),
|
|
37453
|
-
/* @__PURE__ */ (0, import_jsx_runtime120.jsx)("span", { className: "ft-controls__hint", children: "drag
|
|
37563
|
+
/* @__PURE__ */ (0, import_jsx_runtime120.jsx)("span", { className: "ft-controls__hint", children: "drag \xB7 double-click \xB7 pinch \xB7 \u2318/ctrl + scroll" })
|
|
37454
37564
|
] }) : null,
|
|
37455
37565
|
/* @__PURE__ */ (0, import_jsx_runtime120.jsxs)(
|
|
37456
37566
|
"svg",
|