concept-atlas-dense-explain 0.9.0 → 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/template/guides/scroll-guide.mdx +32 -0
- package/template/index.html +1 -1
- package/template/src/components/MDXComponents.jsx +92 -22
- package/template/src/styles/core.css +129 -38
- package/template/src/styles/packs/elastic.css +57 -0
- package/template/src/styles/packs/manuscript.css +38 -0
- package/template/src/styles/packs/shadcn.css +54 -0
- package/template/src/styles/tokens.css +1 -1
package/package.json
CHANGED
|
@@ -100,6 +100,38 @@ npm run dev`}</CodeBlock>
|
|
|
100
100
|
<RelationMap title="关系速览" items={[{from:'MDX',type:'produces',to:'Graph',note:'生成关系图'},{from:'Node',type:'uses',to:'Mermaid',note:'展示局部流程'}]} />
|
|
101
101
|
</ScrollSection>
|
|
102
102
|
|
|
103
|
+
<ScrollSection title="附录:大图的框内适配与放大阅读">
|
|
104
|
+
<ScrollProse>较大的流程图会被等比收进框内,而不是画出边框。点击图面或标题栏的「放大阅读」可以打开全屏视图,用滚轮缩放、拖拽平移、Esc 关闭。</ScrollProse>
|
|
105
|
+
<Mermaid title="图谱生成管线" width="100%" height="360px" chart={`flowchart TD
|
|
106
|
+
S[MDX 源文件] --> P[解析与校验]
|
|
107
|
+
P --> G[概念图谱]
|
|
108
|
+
P --> F[图表与公式]
|
|
109
|
+
G --> D[节点探索]
|
|
110
|
+
G --> R[关系图谱]
|
|
111
|
+
D --> M[Mermaid 局部流程图]
|
|
112
|
+
R --> M
|
|
113
|
+
F --> M
|
|
114
|
+
M --> Z[框内自适应缩放]
|
|
115
|
+
Z --> O[点击放大阅读]
|
|
116
|
+
O --> W[拖拽平移]
|
|
117
|
+
O --> K[滚轮缩放]
|
|
118
|
+
O --> E[Esc 关闭]
|
|
119
|
+
style S fill:#172554,stroke:#38bdf8,color:#e0f2fe
|
|
120
|
+
style Z fill:#14532d,stroke:#34d399,color:#ecfdf5
|
|
121
|
+
style O fill:#78350f,stroke:#fbbf24,color:#fffbeb`} />
|
|
122
|
+
<ScrollGrid columns="2">
|
|
123
|
+
<Mermaid title="窄栏中的纵向长图" width="100%" height="220px" chart={`flowchart TD
|
|
124
|
+
A[读取] --> B[解析]
|
|
125
|
+
B --> C[建模]
|
|
126
|
+
C --> D[渲染]
|
|
127
|
+
D --> E[校验]
|
|
128
|
+
E --> F[发布]
|
|
129
|
+
F --> G[归档]
|
|
130
|
+
G --> H[复盘]`} />
|
|
131
|
+
<RelationPath title="阅读路径" steps={[{level:'L0',node:'总览',note:'建立直觉',tone:'info'},{level:'L1',node:'机制',note:'理解过程',tone:'success'},{level:'L2',node:'边界',note:'检查反例',tone:'warn'}]} />
|
|
132
|
+
</ScrollGrid>
|
|
133
|
+
</ScrollSection>
|
|
134
|
+
|
|
103
135
|
<ScrollSection title="参考文献">
|
|
104
136
|
<References title="本页引用" items={[{id:'tufte1983',authors:'Tufte, E. R.',year:'1983',title:'The Visual Display of Quantitative Information',source:'Graphics Press',note:'关于以图形压缩与呈现证据的经典论述。'}]} />
|
|
105
137
|
</ScrollSection>
|
package/template/index.html
CHANGED
|
@@ -33,7 +33,7 @@
|
|
|
33
33
|
<!-- Google Fonts for Academic / Editorial Knowledge style -->
|
|
34
34
|
<link rel="preconnect" href="https://fonts.googleapis.com">
|
|
35
35
|
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
|
36
|
-
<link href="https://fonts.googleapis.com/css2?family=
|
|
36
|
+
<link href="https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@400;500;700&family=Lora:ital,wght@0,500;0,600;0,700;1,400&family=Plus+Jakarta+Sans:wght@400;500;600;700;800&family=Noto+Serif+SC:wght@500;600;700;900&display=swap" rel="stylesheet">
|
|
37
37
|
</head>
|
|
38
38
|
<body>
|
|
39
39
|
<div id="root"></div>
|
|
@@ -173,8 +173,13 @@ Mechanism.displayName = 'Mechanism';
|
|
|
173
173
|
export function Implementation({ language = 'text', title = '实现代码', children }) {
|
|
174
174
|
return (
|
|
175
175
|
<div className="semantic-implementation" data-language={language}>
|
|
176
|
-
{title &&
|
|
177
|
-
|
|
176
|
+
{title && (
|
|
177
|
+
<div className="semantic-code-head impl-header">
|
|
178
|
+
<span className="semantic-code-title">{title}</span>
|
|
179
|
+
{language && <span className="lang-badge">{language}</span>}
|
|
180
|
+
</div>
|
|
181
|
+
)}
|
|
182
|
+
<pre className="code-block"><code>{typeof children === 'string' ? children.trim() : children}</code></pre>
|
|
178
183
|
</div>
|
|
179
184
|
);
|
|
180
185
|
}
|
|
@@ -721,13 +726,48 @@ export function Tabs({ items = [], children }) {
|
|
|
721
726
|
}
|
|
722
727
|
Tabs.displayName = 'Tabs';
|
|
723
728
|
|
|
729
|
+
/**
|
|
730
|
+
* Enlarged reader for a diagram. It re-renders the chart under a fresh id so
|
|
731
|
+
* the full-size copy never collides with the inline SVG, then hands the result
|
|
732
|
+
* to the shared zoom overlay (wheel zoom, drag to pan, Esc to close).
|
|
733
|
+
*/
|
|
734
|
+
function MermaidZoom({ chart, title, onClose }) {
|
|
735
|
+
const ref = React.useRef(null);
|
|
736
|
+
const appearanceKey = useAppearanceKey();
|
|
737
|
+
const [error, setError] = React.useState('');
|
|
738
|
+
|
|
739
|
+
React.useEffect(() => {
|
|
740
|
+
let cancelled = false;
|
|
741
|
+
async function renderZoom() {
|
|
742
|
+
if (!ref.current || !chart.trim()) return;
|
|
743
|
+
try {
|
|
744
|
+
configureMermaid();
|
|
745
|
+
const renderId = `mermaid-zoom-${Math.random().toString(36).slice(2, 10)}`;
|
|
746
|
+
const { svg } = await mermaid.render(renderId, chart.trim());
|
|
747
|
+
if (!cancelled && ref.current) ref.current.innerHTML = svg;
|
|
748
|
+
} catch (err) {
|
|
749
|
+
if (!cancelled) setError(err.message || 'Mermaid 图表语法错误');
|
|
750
|
+
}
|
|
751
|
+
}
|
|
752
|
+
renderZoom();
|
|
753
|
+
return () => { cancelled = true; };
|
|
754
|
+
}, [chart, appearanceKey]);
|
|
755
|
+
|
|
756
|
+
return (
|
|
757
|
+
<ZoomOverlay title={title || '关系草图'} caption={title} onClose={onClose}>
|
|
758
|
+
{error
|
|
759
|
+
? <pre className="mermaid-error">{error}</pre>
|
|
760
|
+
: <div className="mermaid-zoom-canvas" ref={ref} />}
|
|
761
|
+
</ZoomOverlay>
|
|
762
|
+
);
|
|
763
|
+
}
|
|
764
|
+
|
|
724
765
|
export function Mermaid({ chart = '', title = '关系草图', width = 'auto', height = 'auto', x = 0, y = 0, position = 'flow' }) {
|
|
725
766
|
const ref = React.useRef(null);
|
|
726
767
|
const id = React.useId().replace(/:/g, '');
|
|
727
768
|
const appearanceKey = useAppearanceKey();
|
|
728
769
|
const [error, setError] = React.useState('');
|
|
729
|
-
const [
|
|
730
|
-
const [pan, setPan] = React.useState({ x: 0, y: 0 });
|
|
770
|
+
const [zoomed, setZoomed] = React.useState(false);
|
|
731
771
|
|
|
732
772
|
React.useEffect(() => {
|
|
733
773
|
let cancelled = false;
|
|
@@ -749,15 +789,41 @@ export function Mermaid({ chart = '', title = '关系草图', width = 'auto', he
|
|
|
749
789
|
return () => { cancelled = true; };
|
|
750
790
|
}, [chart, id, appearanceKey]);
|
|
751
791
|
|
|
752
|
-
const
|
|
753
|
-
const
|
|
792
|
+
const canZoom = Boolean(chart.trim()) && !error;
|
|
793
|
+
const openZoom = () => { if (canZoom) setZoomed(true); };
|
|
794
|
+
const onKeyDown = event => {
|
|
795
|
+
if (event.key === 'Enter' || event.key === ' ') {
|
|
796
|
+
event.preventDefault();
|
|
797
|
+
openZoom();
|
|
798
|
+
}
|
|
799
|
+
};
|
|
800
|
+
|
|
754
801
|
return (
|
|
755
802
|
<div className={`semantic-mermaid ${widgetClass(position)}`} style={widgetStyle({ width, height, x, y, position })}>
|
|
756
803
|
<div className="semantic-widget-head">
|
|
757
804
|
<span>{title}</span>
|
|
758
|
-
<div className="mermaid-tools"
|
|
805
|
+
<div className="mermaid-tools">
|
|
806
|
+
<code>MERMAID</code>
|
|
807
|
+
<button type="button" onClick={openZoom} disabled={!canZoom} aria-label={`放大阅读:${title}`}>
|
|
808
|
+
<ZoomIn size={12} />放大阅读
|
|
809
|
+
</button>
|
|
810
|
+
</div>
|
|
759
811
|
</div>
|
|
760
|
-
{error ?
|
|
812
|
+
{error ? (
|
|
813
|
+
<pre className="mermaid-error">{error}</pre>
|
|
814
|
+
) : (
|
|
815
|
+
<div
|
|
816
|
+
className="mermaid-canvas"
|
|
817
|
+
role="button"
|
|
818
|
+
tabIndex={0}
|
|
819
|
+
aria-label={`放大阅读:${title}`}
|
|
820
|
+
onClick={openZoom}
|
|
821
|
+
onKeyDown={onKeyDown}
|
|
822
|
+
>
|
|
823
|
+
<div className="mermaid-canvas-inner" ref={ref} />
|
|
824
|
+
</div>
|
|
825
|
+
)}
|
|
826
|
+
{zoomed && <MermaidZoom chart={chart} title={title} onClose={() => setZoomed(false)} />}
|
|
761
827
|
</div>
|
|
762
828
|
);
|
|
763
829
|
}
|
|
@@ -1042,12 +1108,12 @@ const ZOOM_MIN = 0.5;
|
|
|
1042
1108
|
const ZOOM_MAX = 6;
|
|
1043
1109
|
|
|
1044
1110
|
/**
|
|
1045
|
-
* Full-viewport
|
|
1046
|
-
* transformed/rotated ancestor surfaces (`position:
|
|
1047
|
-
* contained by them). Supports wheel zoom, drag to
|
|
1048
|
-
* 1x/2x, and Escape to close.
|
|
1111
|
+
* Full-viewport zoom viewer shared by images and diagrams. Rendered through a
|
|
1112
|
+
* portal so it escapes the transformed/rotated ancestor surfaces (`position:
|
|
1113
|
+
* fixed` would otherwise be contained by them). Supports wheel zoom, drag to
|
|
1114
|
+
* pan, double-click to toggle 1x/2x, and Escape to close.
|
|
1049
1115
|
*/
|
|
1050
|
-
function
|
|
1116
|
+
function ZoomOverlay({ title, caption, label, onClose, children }) {
|
|
1051
1117
|
const [scale, setScale] = React.useState(1);
|
|
1052
1118
|
const [offset, setOffset] = React.useState({ x: 0, y: 0 });
|
|
1053
1119
|
const [dragging, setDragging] = React.useState(false);
|
|
@@ -1121,8 +1187,6 @@ function ImageZoom({ src, alt, caption, label, onClose }) {
|
|
|
1121
1187
|
}
|
|
1122
1188
|
};
|
|
1123
1189
|
|
|
1124
|
-
const title = alt || caption || '图片';
|
|
1125
|
-
|
|
1126
1190
|
return createPortal(
|
|
1127
1191
|
<div
|
|
1128
1192
|
className="image-zoom-overlay"
|
|
@@ -1141,14 +1205,11 @@ function ImageZoom({ src, alt, caption, label, onClose }) {
|
|
|
1141
1205
|
onDoubleClick={() => { const next = scaleRef.current > 1 ? 1 : 2; setScale(next); settle(next); }}
|
|
1142
1206
|
onClick={event => { if (movedRef.current) { event.stopPropagation(); movedRef.current = false; } }}
|
|
1143
1207
|
>
|
|
1144
|
-
<
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
-
draggable="false"
|
|
1148
|
-
style={{ transform: `translate(${offset.x}px, ${offset.y}px) scale(${scale})` }}
|
|
1149
|
-
/>
|
|
1208
|
+
<div className="image-zoom-target" style={{ transform: `translate(${offset.x}px, ${offset.y}px) scale(${scale})` }}>
|
|
1209
|
+
{children}
|
|
1210
|
+
</div>
|
|
1150
1211
|
</div>
|
|
1151
|
-
{title && <p className="image-zoom-caption">{label && <b>{label}</b>}{caption
|
|
1212
|
+
{title && <p className="image-zoom-caption">{label && <b>{label}</b>}{caption}</p>}
|
|
1152
1213
|
<div className="image-zoom-toolbar">
|
|
1153
1214
|
<button type="button" onClick={() => zoomBy(-0.25)} aria-label="缩小" title="缩小(−)">−</button>
|
|
1154
1215
|
<span aria-live="polite">{Math.round(scale * 100)}%</span>
|
|
@@ -1161,6 +1222,15 @@ function ImageZoom({ src, alt, caption, label, onClose }) {
|
|
|
1161
1222
|
);
|
|
1162
1223
|
}
|
|
1163
1224
|
|
|
1225
|
+
function ImageZoom({ src, alt, caption, label, onClose }) {
|
|
1226
|
+
const title = alt || caption || '图片';
|
|
1227
|
+
return (
|
|
1228
|
+
<ZoomOverlay title={title} caption={caption || alt} label={label} onClose={onClose}>
|
|
1229
|
+
<img src={src} alt={alt || ''} draggable="false" />
|
|
1230
|
+
</ZoomOverlay>
|
|
1231
|
+
);
|
|
1232
|
+
}
|
|
1233
|
+
|
|
1164
1234
|
export function Figure({ src, alt = '', caption, label, width = 'auto', height = 'auto', x = 0, y = 0, position = 'flow' }) {
|
|
1165
1235
|
const [zoomed, setZoomed] = React.useState(false);
|
|
1166
1236
|
const title = alt || caption || '图片';
|
|
@@ -94,11 +94,45 @@ button:focus-visible, a:focus-visible, [role="button"]:focus-visible {
|
|
|
94
94
|
.gap-sm { gap: 8px; } .gap-md { gap: 16px; } .gap-lg { gap: 28px; }
|
|
95
95
|
.semantic-tabs details { border-bottom: 1px solid var(--border-subtle); padding: 8px 0; }
|
|
96
96
|
.semantic-tabs summary { cursor: pointer; font-weight: 700; color: var(--text-primary); }
|
|
97
|
-
.mermaid-tools { display: inline-flex; align-items: center; gap:
|
|
98
|
-
.mermaid-tools button {
|
|
97
|
+
.mermaid-tools { display: inline-flex; align-items: center; gap: 6px; }
|
|
98
|
+
.mermaid-tools button {
|
|
99
|
+
display: inline-flex;
|
|
100
|
+
align-items: center;
|
|
101
|
+
gap: 4px;
|
|
102
|
+
height: 22px;
|
|
103
|
+
padding: 0 8px;
|
|
104
|
+
border: 1px solid var(--border-medium);
|
|
105
|
+
border-radius: var(--radius-1);
|
|
106
|
+
background: var(--bg-subtle);
|
|
107
|
+
color: var(--text-secondary);
|
|
108
|
+
font-size: 11px;
|
|
109
|
+
font-weight: 700;
|
|
110
|
+
}
|
|
99
111
|
.mermaid-tools button:hover { color: var(--primary); border-color: var(--primary); }
|
|
100
|
-
.mermaid-
|
|
101
|
-
|
|
112
|
+
.mermaid-tools button:disabled { opacity: .5; cursor: not-allowed; }
|
|
113
|
+
|
|
114
|
+
/* The diagram frame is a bounded viewport, not a loose box: the head keeps its
|
|
115
|
+
own height and the canvas takes the rest, so a fixed `height` prop contains
|
|
116
|
+
the drawing instead of letting it paint past the border. The SVG is scaled to
|
|
117
|
+
fit with its own preserveAspectRatio; clicking opens the full-size viewer. */
|
|
118
|
+
.mermaid-canvas {
|
|
119
|
+
position: relative;
|
|
120
|
+
flex: 1 1 auto;
|
|
121
|
+
min-height: 0;
|
|
122
|
+
min-width: 0;
|
|
123
|
+
max-width: 100%;
|
|
124
|
+
overflow: hidden;
|
|
125
|
+
padding: 4px;
|
|
126
|
+
cursor: zoom-in;
|
|
127
|
+
}
|
|
128
|
+
.mermaid-canvas-inner {
|
|
129
|
+
display: grid;
|
|
130
|
+
place-items: center;
|
|
131
|
+
width: 100%;
|
|
132
|
+
height: 100%;
|
|
133
|
+
transform-origin: center center;
|
|
134
|
+
}
|
|
135
|
+
.mermaid-canvas svg { display: block; width: 100%; height: 100%; max-width: 100%; max-height: 100%; }
|
|
102
136
|
|
|
103
137
|
/* Scrollbar beautification */
|
|
104
138
|
::-webkit-scrollbar {
|
|
@@ -1026,26 +1060,37 @@ button.flow-box {
|
|
|
1026
1060
|
border-radius: 10px;
|
|
1027
1061
|
overflow-x: auto;
|
|
1028
1062
|
font-family: var(--font-data);
|
|
1029
|
-
|
|
1030
|
-
|
|
1063
|
+
/* A comfortable, taller setting for a mono face; ligatures stay off so
|
|
1064
|
+
operators render exactly as they were typed. */
|
|
1065
|
+
font-size: 14px;
|
|
1066
|
+
line-height: 1.7;
|
|
1067
|
+
font-variant-ligatures: none;
|
|
1068
|
+
font-feature-settings: 'liga' 0, 'calt' 0;
|
|
1031
1069
|
color: var(--code-text);
|
|
1032
1070
|
border: 1px solid var(--code-border);
|
|
1033
1071
|
box-shadow: var(--shadow-inset);
|
|
1034
1072
|
}
|
|
1035
1073
|
|
|
1036
|
-
/*
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
1074
|
+
/* -- Code family: `.code-block` is the one shared dark inset used by the
|
|
1075
|
+
explorer, `Implementation` and `CodeBlock`. Both components wrap it with
|
|
1076
|
+
an optional head, caption and numbered lines, and they share the head +
|
|
1077
|
+
surface classes so every style pack only has to re-voice one grammar. -- */
|
|
1078
|
+
.semantic-code,
|
|
1079
|
+
.semantic-implementation {
|
|
1041
1080
|
display: flex;
|
|
1042
1081
|
flex-direction: column;
|
|
1043
1082
|
gap: 8px;
|
|
1083
|
+
margin: 0;
|
|
1084
|
+
}
|
|
1085
|
+
|
|
1086
|
+
.semantic-code > pre,
|
|
1087
|
+
.semantic-implementation > pre {
|
|
1088
|
+
margin: 0;
|
|
1044
1089
|
}
|
|
1045
1090
|
|
|
1046
1091
|
.semantic-code-head {
|
|
1047
1092
|
display: flex;
|
|
1048
|
-
align-items:
|
|
1093
|
+
align-items: baseline;
|
|
1049
1094
|
justify-content: space-between;
|
|
1050
1095
|
gap: 10px;
|
|
1051
1096
|
}
|
|
@@ -1057,6 +1102,17 @@ button.flow-box {
|
|
|
1057
1102
|
color: var(--text-secondary);
|
|
1058
1103
|
}
|
|
1059
1104
|
|
|
1105
|
+
/* The language tag rides every code head; a pack may dress it as a badge. */
|
|
1106
|
+
.lang-badge {
|
|
1107
|
+
font-family: var(--font-data);
|
|
1108
|
+
font-size: 10px;
|
|
1109
|
+
font-weight: 700;
|
|
1110
|
+
letter-spacing: 0.08em;
|
|
1111
|
+
text-transform: uppercase;
|
|
1112
|
+
color: var(--text-muted);
|
|
1113
|
+
white-space: nowrap;
|
|
1114
|
+
}
|
|
1115
|
+
|
|
1060
1116
|
.semantic-code-caption {
|
|
1061
1117
|
font-size: 12px;
|
|
1062
1118
|
color: var(--text-muted);
|
|
@@ -1078,18 +1134,22 @@ button.flow-box {
|
|
|
1078
1134
|
.code-block-numbered .code-line {
|
|
1079
1135
|
position: relative;
|
|
1080
1136
|
display: block;
|
|
1081
|
-
min-height: 1.
|
|
1082
|
-
padding-left:
|
|
1137
|
+
min-height: 1.7em;
|
|
1138
|
+
padding-left: 3.6em;
|
|
1083
1139
|
counter-increment: code-line;
|
|
1084
1140
|
}
|
|
1085
1141
|
|
|
1086
1142
|
.code-block-numbered .code-line::before {
|
|
1087
1143
|
content: counter(code-line);
|
|
1088
1144
|
position: absolute;
|
|
1145
|
+
top: 0;
|
|
1146
|
+
bottom: 0;
|
|
1089
1147
|
left: 0;
|
|
1090
|
-
width:
|
|
1148
|
+
width: 2.5em;
|
|
1149
|
+
padding-right: 0.85em;
|
|
1091
1150
|
text-align: right;
|
|
1092
|
-
color:
|
|
1151
|
+
color: color-mix(in srgb, var(--code-text) 50%, transparent);
|
|
1152
|
+
border-right: 1px solid color-mix(in srgb, var(--code-text) 18%, transparent);
|
|
1093
1153
|
user-select: none;
|
|
1094
1154
|
}
|
|
1095
1155
|
|
|
@@ -2044,6 +2104,16 @@ button.flow-box {
|
|
|
2044
2104
|
min-width: 0;
|
|
2045
2105
|
}
|
|
2046
2106
|
|
|
2107
|
+
/* The diagram frame clips and hands its remaining height to the canvas, so a
|
|
2108
|
+
fixed `height` prop can never let the drawing escape the border. */
|
|
2109
|
+
.semantic-mermaid {
|
|
2110
|
+
display: flex;
|
|
2111
|
+
flex-direction: column;
|
|
2112
|
+
overflow: hidden;
|
|
2113
|
+
}
|
|
2114
|
+
|
|
2115
|
+
.semantic-mermaid > .semantic-widget-head { flex: 0 0 auto; }
|
|
2116
|
+
|
|
2047
2117
|
.draft-board .custom-section {
|
|
2048
2118
|
padding: 12px 14px;
|
|
2049
2119
|
}
|
|
@@ -2093,10 +2163,10 @@ button.flow-box {
|
|
|
2093
2163
|
.relation-path-node.tone-warning { border-top-color: var(--accent-amber); }
|
|
2094
2164
|
.relation-path-node.tone-danger { border-top-color: var(--accent-rose); }
|
|
2095
2165
|
.relation-path-kicker, .relation-path-node strong, .relation-path-node small { display: block; }
|
|
2096
|
-
.relation-path-kicker { color: var(--text-dim); font: 700 10px
|
|
2166
|
+
.relation-path-kicker { color: var(--text-dim); font: 700 10px var(--font-data); margin-bottom: 4px; }
|
|
2097
2167
|
.relation-path-node strong { color: var(--text-primary); font-size: 13px; }
|
|
2098
2168
|
.relation-path-node small { margin-top: 4px; color: var(--text-muted); font-size: 11px; }
|
|
2099
|
-
.relation-path-edge { flex: 0 0 72px; display: grid; place-items: center; position: relative; color: var(--text-accent); font: 700 10px
|
|
2169
|
+
.relation-path-edge { flex: 0 0 72px; display: grid; place-items: center; position: relative; color: var(--text-accent); font: 700 10px var(--font-data); }
|
|
2100
2170
|
.relation-path-edge::before { content: ''; position: absolute; left: 4px; right: 4px; top: 50%; border-top: 1px dashed var(--border-strong); }
|
|
2101
2171
|
.relation-path-edge span { z-index: 1; padding: 2px 4px; background: var(--bg-surface); }
|
|
2102
2172
|
|
|
@@ -2104,9 +2174,10 @@ button.flow-box {
|
|
|
2104
2174
|
.draft-board .semantic-relation-map {
|
|
2105
2175
|
width: fit-content;
|
|
2106
2176
|
max-width: 100%;
|
|
2107
|
-
overflow: visible;
|
|
2108
2177
|
}
|
|
2109
2178
|
|
|
2179
|
+
.draft-board .semantic-relation-map { overflow: visible; }
|
|
2180
|
+
|
|
2110
2181
|
.draft-board .semantic-insight,
|
|
2111
2182
|
.draft-board .semantic-note-grid {
|
|
2112
2183
|
width: fit-content;
|
|
@@ -2130,22 +2201,18 @@ button.flow-box {
|
|
|
2130
2201
|
font-size: 9px;
|
|
2131
2202
|
}
|
|
2132
2203
|
|
|
2133
|
-
|
|
2134
|
-
|
|
2135
|
-
|
|
2136
|
-
|
|
2137
|
-
|
|
2138
|
-
|
|
2139
|
-
}
|
|
2140
|
-
|
|
2141
|
-
.mermaid-canvas svg {
|
|
2142
|
-
max-width: none;
|
|
2143
|
-
min-width: 100%;
|
|
2144
|
-
height: auto;
|
|
2204
|
+
/* The inline diagram is a preview: clicking it, or the head button, opens the
|
|
2205
|
+
full-size viewer. The cursor is the only affordance on the drawing itself, so
|
|
2206
|
+
nothing competes with the diagram for attention. */
|
|
2207
|
+
.mermaid-canvas:focus-visible {
|
|
2208
|
+
outline: 2px solid var(--accent-cyan);
|
|
2209
|
+
outline-offset: 2px;
|
|
2145
2210
|
}
|
|
2146
2211
|
|
|
2147
2212
|
.mermaid-error {
|
|
2148
2213
|
margin: 0;
|
|
2214
|
+
min-height: 0;
|
|
2215
|
+
overflow: auto;
|
|
2149
2216
|
color: var(--accent-rose);
|
|
2150
2217
|
font-size: 11px;
|
|
2151
2218
|
white-space: pre-wrap;
|
|
@@ -3439,7 +3506,8 @@ html[data-carrier='scroll'] #root {
|
|
|
3439
3506
|
.continuous-document .model-matrix-cell span,
|
|
3440
3507
|
.continuous-document .model-pyramid-level span,
|
|
3441
3508
|
.continuous-document .model-funnel-step span,
|
|
3442
|
-
.continuous-document .tradeoff-option span
|
|
3509
|
+
.continuous-document .tradeoff-option span,
|
|
3510
|
+
.continuous-document .code-block {
|
|
3443
3511
|
font-size: calc(14px * var(--reading-ui-scale));
|
|
3444
3512
|
}
|
|
3445
3513
|
|
|
@@ -3733,6 +3801,10 @@ html[data-carrier='scroll'] #root {
|
|
|
3733
3801
|
place-items: center;
|
|
3734
3802
|
padding: 28px;
|
|
3735
3803
|
background: var(--overlay-scrim);
|
|
3804
|
+
/* Clip at the viewport, not at the media box: the zoom transform does not
|
|
3805
|
+
change layout size, so a stage-sized clip would only ever reveal what the
|
|
3806
|
+
un-zoomed element already covered. */
|
|
3807
|
+
overflow: hidden;
|
|
3736
3808
|
}
|
|
3737
3809
|
|
|
3738
3810
|
.image-zoom-stage {
|
|
@@ -3740,13 +3812,22 @@ html[data-carrier='scroll'] #root {
|
|
|
3740
3812
|
place-items: center;
|
|
3741
3813
|
max-width: 100%;
|
|
3742
3814
|
max-height: 100%;
|
|
3743
|
-
overflow:
|
|
3815
|
+
overflow: visible;
|
|
3744
3816
|
cursor: grab;
|
|
3745
3817
|
touch-action: none;
|
|
3746
3818
|
}
|
|
3747
3819
|
|
|
3748
3820
|
.image-zoom-stage.is-dragging { cursor: grabbing; }
|
|
3749
3821
|
|
|
3822
|
+
/* The pan/zoom transform rides a wrapper so the viewer can hold an <img> or a
|
|
3823
|
+
rendered diagram without duplicating the gesture logic. */
|
|
3824
|
+
.image-zoom-target {
|
|
3825
|
+
display: block;
|
|
3826
|
+
transition: transform 0.12s ease-out;
|
|
3827
|
+
will-change: transform;
|
|
3828
|
+
user-select: none;
|
|
3829
|
+
}
|
|
3830
|
+
|
|
3750
3831
|
.image-zoom-stage img {
|
|
3751
3832
|
display: block;
|
|
3752
3833
|
width: auto;
|
|
@@ -3756,12 +3837,22 @@ html[data-carrier='scroll'] #root {
|
|
|
3756
3837
|
border-radius: var(--radius-2);
|
|
3757
3838
|
background: var(--bg-surface);
|
|
3758
3839
|
box-shadow: var(--shadow-modal);
|
|
3759
|
-
transition: transform 0.12s ease-out;
|
|
3760
|
-
will-change: transform;
|
|
3761
3840
|
user-select: none;
|
|
3762
3841
|
}
|
|
3763
3842
|
|
|
3764
|
-
.image-zoom-stage.is-dragging
|
|
3843
|
+
.image-zoom-stage.is-dragging .image-zoom-target { transition: none; }
|
|
3844
|
+
|
|
3845
|
+
/* A diagram keeps its natural aspect inside the viewer and paints on the
|
|
3846
|
+
Mermaid canvas tone, so labels stay legible over the dark scrim. */
|
|
3847
|
+
.mermaid-zoom-canvas svg {
|
|
3848
|
+
display: block;
|
|
3849
|
+
width: min(94vw, 1800px);
|
|
3850
|
+
height: auto;
|
|
3851
|
+
max-height: 84vh;
|
|
3852
|
+
border-radius: var(--radius-3);
|
|
3853
|
+
background: var(--mermaid-canvas);
|
|
3854
|
+
box-shadow: var(--shadow-modal);
|
|
3855
|
+
}
|
|
3765
3856
|
|
|
3766
3857
|
.image-zoom-caption {
|
|
3767
3858
|
position: absolute;
|
|
@@ -3820,14 +3911,14 @@ html[data-carrier='scroll'] #root {
|
|
|
3820
3911
|
}
|
|
3821
3912
|
|
|
3822
3913
|
@media (prefers-reduced-motion: reduce) {
|
|
3823
|
-
.image-zoom-
|
|
3914
|
+
.image-zoom-target { transition: none; }
|
|
3824
3915
|
}
|
|
3825
3916
|
|
|
3826
3917
|
@media (max-width: 760px) {
|
|
3827
3918
|
.image-zoom-overlay { padding: 12px; }
|
|
3828
3919
|
.image-zoom-stage img { max-height: 74vh; }
|
|
3920
|
+
.mermaid-zoom-canvas svg { max-height: 74vh; }
|
|
3829
3921
|
.image-zoom-caption { top: 10px; font-size: 12px; }
|
|
3830
|
-
.image-zoom-hint { opacity: 0.9; }
|
|
3831
3922
|
}
|
|
3832
3923
|
|
|
3833
3924
|
.semantic-figure figcaption {
|
|
@@ -515,3 +515,60 @@
|
|
|
515
515
|
[data-style='elastic'] .semantic-compare-table tbody tr {
|
|
516
516
|
transition: background-color var(--elastic-motion);
|
|
517
517
|
}
|
|
518
|
+
|
|
519
|
+
/* -- Code as a panel: a 6px bordered surface under a subdued header strip,
|
|
520
|
+
the well set flush inside and inset like a terminal. The whole panel
|
|
521
|
+
answers on the pack's 250ms curve with the same 1px lift as every other
|
|
522
|
+
elastic surface, and the caption closes as a dim mono footer. -- */
|
|
523
|
+
[data-style='elastic'] .semantic-code,
|
|
524
|
+
[data-style='elastic'] .semantic-implementation {
|
|
525
|
+
gap: 0;
|
|
526
|
+
border: 1px solid var(--border-medium);
|
|
527
|
+
border-radius: var(--radius-2);
|
|
528
|
+
background: var(--bg-surface);
|
|
529
|
+
box-shadow: var(--shadow-card);
|
|
530
|
+
overflow: hidden;
|
|
531
|
+
transition: border-color var(--elastic-motion), transform var(--elastic-motion);
|
|
532
|
+
}
|
|
533
|
+
|
|
534
|
+
[data-style='elastic'] .semantic-code:hover,
|
|
535
|
+
[data-style='elastic'] .semantic-implementation:hover {
|
|
536
|
+
border-color: var(--border-strong);
|
|
537
|
+
transform: translateY(-1px);
|
|
538
|
+
}
|
|
539
|
+
|
|
540
|
+
[data-style='elastic'] .semantic-code-head {
|
|
541
|
+
padding: 9px 12px;
|
|
542
|
+
background: var(--bg-subtle);
|
|
543
|
+
border-bottom: 1px solid var(--border-subtle);
|
|
544
|
+
}
|
|
545
|
+
|
|
546
|
+
[data-style='elastic'] .semantic-code-title {
|
|
547
|
+
font-family: var(--font-sans);
|
|
548
|
+
font-size: 13px;
|
|
549
|
+
font-weight: 600;
|
|
550
|
+
letter-spacing: -0.01em;
|
|
551
|
+
color: var(--text-primary);
|
|
552
|
+
}
|
|
553
|
+
|
|
554
|
+
[data-style='elastic'] .lang-badge {
|
|
555
|
+
padding: 2px 6px;
|
|
556
|
+
border: 1px solid var(--border-medium);
|
|
557
|
+
border-radius: var(--radius-1);
|
|
558
|
+
background: var(--bg-surface);
|
|
559
|
+
color: var(--text-secondary);
|
|
560
|
+
letter-spacing: 0.03em;
|
|
561
|
+
}
|
|
562
|
+
|
|
563
|
+
[data-style='elastic'] .semantic-code > .code-block,
|
|
564
|
+
[data-style='elastic'] .semantic-implementation > .code-block {
|
|
565
|
+
border: 0;
|
|
566
|
+
border-radius: 0;
|
|
567
|
+
box-shadow: var(--shadow-inset);
|
|
568
|
+
}
|
|
569
|
+
|
|
570
|
+
[data-style='elastic'] .semantic-code-caption {
|
|
571
|
+
padding: 8px 12px 10px;
|
|
572
|
+
border-top: 1px solid var(--border-subtle);
|
|
573
|
+
font-family: var(--font-sans);
|
|
574
|
+
}
|
|
@@ -424,3 +424,41 @@
|
|
|
424
424
|
border-color: var(--primary);
|
|
425
425
|
color: var(--primary);
|
|
426
426
|
}
|
|
427
|
+
|
|
428
|
+
/* -- Listings: the code well keeps its ink, but the frame drops to a
|
|
429
|
+
near-square ruled inset under a heavier top rule. The head reads as a
|
|
430
|
+
printed listing caption in the heading face, the language tag as a quiet
|
|
431
|
+
mono folio, and the caption closes as an italic printer's note. Paper
|
|
432
|
+
answers the pointer through rule weight only — no nudging. -- */
|
|
433
|
+
[data-style='manuscript'] .code-block {
|
|
434
|
+
padding: 16px 18px;
|
|
435
|
+
border-radius: 2px;
|
|
436
|
+
border-top: 2px solid color-mix(in srgb, var(--code-text) 42%, transparent);
|
|
437
|
+
box-shadow: none;
|
|
438
|
+
transition: border-top-color var(--motion-base) var(--ease-standard);
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
[data-style='manuscript'] .code-block:hover {
|
|
442
|
+
border-top-color: color-mix(in srgb, var(--code-text) 62%, transparent);
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
[data-style='manuscript'] .semantic-code-title {
|
|
446
|
+
font-family: var(--font-heading);
|
|
447
|
+
font-size: 14px;
|
|
448
|
+
font-weight: 600;
|
|
449
|
+
color: var(--text-primary);
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
[data-style='manuscript'] .lang-badge {
|
|
453
|
+
color: var(--text-dim);
|
|
454
|
+
letter-spacing: 0.12em;
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
[data-style='manuscript'] .semantic-code-caption {
|
|
458
|
+
font-style: italic;
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
[data-style='manuscript'] .code-block-numbered .code-line::before {
|
|
462
|
+
color: color-mix(in srgb, var(--code-text) 40%, transparent);
|
|
463
|
+
border-right-color: color-mix(in srgb, var(--code-text) 16%, transparent);
|
|
464
|
+
}
|
|
@@ -515,3 +515,57 @@
|
|
|
515
515
|
[data-style='shadcn'] .semantic-compare-table tbody tr {
|
|
516
516
|
transition: background-color var(--shadcn-motion);
|
|
517
517
|
}
|
|
518
|
+
|
|
519
|
+
/* -- Code as a card: hairline chassis, a muted divided header and the well set
|
|
520
|
+
flush inside it, closed by a caption footer. The language tag is an
|
|
521
|
+
outline badge, and the surface only answers on the shared 150ms curve. -- */
|
|
522
|
+
[data-style='shadcn'] .semantic-code,
|
|
523
|
+
[data-style='shadcn'] .semantic-implementation {
|
|
524
|
+
gap: 0;
|
|
525
|
+
border: 1px solid var(--border-subtle);
|
|
526
|
+
border-radius: var(--radius-3);
|
|
527
|
+
background: var(--bg-surface);
|
|
528
|
+
box-shadow: var(--shadow-sm);
|
|
529
|
+
overflow: hidden;
|
|
530
|
+
transition: border-color var(--shadcn-motion);
|
|
531
|
+
}
|
|
532
|
+
|
|
533
|
+
[data-style='shadcn'] .semantic-code:hover,
|
|
534
|
+
[data-style='shadcn'] .semantic-implementation:hover {
|
|
535
|
+
border-color: var(--border-medium);
|
|
536
|
+
}
|
|
537
|
+
|
|
538
|
+
[data-style='shadcn'] .semantic-code-head {
|
|
539
|
+
padding: 10px 14px;
|
|
540
|
+
background: var(--bg-subtle);
|
|
541
|
+
border-bottom: 1px solid var(--border-subtle);
|
|
542
|
+
}
|
|
543
|
+
|
|
544
|
+
[data-style='shadcn'] .semantic-code-title {
|
|
545
|
+
font-family: var(--font-sans);
|
|
546
|
+
font-size: 13px;
|
|
547
|
+
font-weight: 600;
|
|
548
|
+
letter-spacing: -0.01em;
|
|
549
|
+
color: var(--text-primary);
|
|
550
|
+
}
|
|
551
|
+
|
|
552
|
+
[data-style='shadcn'] .lang-badge {
|
|
553
|
+
padding: 2px 7px;
|
|
554
|
+
border: 1px solid var(--border-subtle);
|
|
555
|
+
border-radius: var(--radius-1);
|
|
556
|
+
background: var(--bg-surface);
|
|
557
|
+
color: var(--text-muted);
|
|
558
|
+
letter-spacing: 0.04em;
|
|
559
|
+
}
|
|
560
|
+
|
|
561
|
+
[data-style='shadcn'] .semantic-code > .code-block,
|
|
562
|
+
[data-style='shadcn'] .semantic-implementation > .code-block {
|
|
563
|
+
border: 0;
|
|
564
|
+
border-radius: 0;
|
|
565
|
+
box-shadow: none;
|
|
566
|
+
}
|
|
567
|
+
|
|
568
|
+
[data-style='shadcn'] .semantic-code-caption {
|
|
569
|
+
padding: 8px 14px 10px;
|
|
570
|
+
border-top: 1px solid var(--border-subtle);
|
|
571
|
+
}
|
|
@@ -262,7 +262,7 @@
|
|
|
262
262
|
/* Family stacks */
|
|
263
263
|
--font-sans: 'Plus Jakarta Sans', -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Noto Sans SC", sans-serif;
|
|
264
264
|
--font-serif: 'Lora', 'Noto Serif SC', Georgia, serif;
|
|
265
|
-
--font-mono: 'Fira Code', ui-monospace, SFMono-Regular, Menlo, Consolas, "Liberation Mono", monospace;
|
|
265
|
+
--font-mono: 'JetBrains Mono', 'Fira Code', ui-monospace, SFMono-Regular, Menlo, Consolas, "Liberation Mono", monospace;
|
|
266
266
|
|
|
267
267
|
/* Roles — the only families component styles should consume */
|
|
268
268
|
--font-body: var(--font-sans);
|