@ferax564/noma-cli 0.11.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.
Files changed (83) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +199 -0
  3. package/bin/noma.mjs +8 -0
  4. package/dist/ast.d.ts +111 -0
  5. package/dist/ast.js +23 -0
  6. package/dist/ast.js.map +1 -0
  7. package/dist/book.d.ts +56 -0
  8. package/dist/book.js +120 -0
  9. package/dist/book.js.map +1 -0
  10. package/dist/cli.d.ts +2 -0
  11. package/dist/cli.js +573 -0
  12. package/dist/cli.js.map +1 -0
  13. package/dist/diff.d.ts +29 -0
  14. package/dist/diff.js +77 -0
  15. package/dist/diff.js.map +1 -0
  16. package/dist/fmt.d.ts +1 -0
  17. package/dist/fmt.js +105 -0
  18. package/dist/fmt.js.map +1 -0
  19. package/dist/ids.d.ts +15 -0
  20. package/dist/ids.js +27 -0
  21. package/dist/ids.js.map +1 -0
  22. package/dist/index.d.ts +20 -0
  23. package/dist/index.js +12 -0
  24. package/dist/index.js.map +1 -0
  25. package/dist/inline.d.ts +14 -0
  26. package/dist/inline.js +83 -0
  27. package/dist/inline.js.map +1 -0
  28. package/dist/loader.d.ts +12 -0
  29. package/dist/loader.js +59 -0
  30. package/dist/loader.js.map +1 -0
  31. package/dist/parser.d.ts +7 -0
  32. package/dist/parser.js +434 -0
  33. package/dist/parser.js.map +1 -0
  34. package/dist/patch.d.ts +61 -0
  35. package/dist/patch.js +530 -0
  36. package/dist/patch.js.map +1 -0
  37. package/dist/renderer-html.d.ts +44 -0
  38. package/dist/renderer-html.js +929 -0
  39. package/dist/renderer-html.js.map +1 -0
  40. package/dist/renderer-json.d.ts +5 -0
  41. package/dist/renderer-json.js +4 -0
  42. package/dist/renderer-json.js.map +1 -0
  43. package/dist/renderer-llm.d.ts +29 -0
  44. package/dist/renderer-llm.js +275 -0
  45. package/dist/renderer-llm.js.map +1 -0
  46. package/dist/renderer-noma.d.ts +10 -0
  47. package/dist/renderer-noma.js +179 -0
  48. package/dist/renderer-noma.js.map +1 -0
  49. package/dist/renderer-site.d.ts +11 -0
  50. package/dist/renderer-site.js +175 -0
  51. package/dist/renderer-site.js.map +1 -0
  52. package/dist/validator.d.ts +24 -0
  53. package/dist/validator.js +699 -0
  54. package/dist/validator.js.map +1 -0
  55. package/dist/verify.d.ts +10 -0
  56. package/dist/verify.js +141 -0
  57. package/dist/verify.js.map +1 -0
  58. package/package.json +83 -0
  59. package/schemas/ast.schema.json +187 -0
  60. package/schemas/capability.schema.json +70 -0
  61. package/schemas/patch-op.schema.json +92 -0
  62. package/schemas/patch-transaction.schema.json +28 -0
  63. package/schemas/transcript.schema.json +95 -0
  64. package/src/ast.ts +152 -0
  65. package/src/book.ts +162 -0
  66. package/src/cli.ts +595 -0
  67. package/src/diff.ts +108 -0
  68. package/src/fmt.ts +126 -0
  69. package/src/ids.ts +42 -0
  70. package/src/index.ts +20 -0
  71. package/src/inline.ts +92 -0
  72. package/src/loader.ts +55 -0
  73. package/src/parser.ts +501 -0
  74. package/src/patch.ts +646 -0
  75. package/src/renderer-html.ts +1047 -0
  76. package/src/renderer-json.ts +9 -0
  77. package/src/renderer-llm.ts +320 -0
  78. package/src/renderer-noma.ts +220 -0
  79. package/src/renderer-site.ts +245 -0
  80. package/src/validator.ts +733 -0
  81. package/src/verify.ts +157 -0
  82. package/themes/dark.css +382 -0
  83. package/themes/default.css +537 -0
package/src/verify.ts ADDED
@@ -0,0 +1,157 @@
1
+ import { readFileSync, readdirSync, statSync, existsSync } from "node:fs";
2
+ import { join, relative } from "node:path";
3
+ import { parse } from "./parser.js";
4
+ import { renderNoma } from "./renderer-noma.js";
5
+ import { validate } from "./validator.js";
6
+ import { walk } from "./ast.js";
7
+ import { patchSource, type PatchOp } from "./patch.js";
8
+
9
+ export interface FixtureReport {
10
+ name: string;
11
+ status: "pass" | "fail" | "skip";
12
+ error?: string;
13
+ }
14
+
15
+ export interface VerifyReport {
16
+ ok: boolean;
17
+ fixtures: FixtureReport[];
18
+ }
19
+
20
+ function listFixtures(root: string): string[] {
21
+ const out: string[] = [];
22
+ const walkDir = (dir: string) => {
23
+ for (const entry of readdirSync(dir)) {
24
+ const p = join(dir, entry);
25
+ const s = statSync(p);
26
+ if (s.isDirectory()) {
27
+ if (existsSync(join(p, "input.noma"))) {
28
+ out.push(p);
29
+ } else {
30
+ walkDir(p);
31
+ }
32
+ }
33
+ }
34
+ };
35
+ walkDir(root);
36
+ return out.sort();
37
+ }
38
+
39
+ function checkIds(doc: ReturnType<typeof parse>, expectedPath: string): string | null {
40
+ const expected = JSON.parse(readFileSync(expectedPath, "utf8")) as {
41
+ canonical: string[];
42
+ aliases: Record<string, string[]>;
43
+ };
44
+ const canonical: string[] = [];
45
+ const aliases: Record<string, string[]> = {};
46
+ for (const node of walk(doc)) {
47
+ if ("id" in node && node.id) {
48
+ canonical.push(node.id);
49
+ if (node.aliases?.length) aliases[node.id] = [...node.aliases];
50
+ }
51
+ }
52
+ if (JSON.stringify(canonical.sort()) !== JSON.stringify([...expected.canonical].sort())) {
53
+ return `ids mismatch: got ${JSON.stringify(canonical)}, expected ${JSON.stringify(expected.canonical)}`;
54
+ }
55
+ for (const k of Object.keys(expected.aliases)) {
56
+ if (JSON.stringify((aliases[k] ?? []).sort()) !== JSON.stringify([...expected.aliases[k]!].sort())) {
57
+ return `aliases mismatch for ${k}`;
58
+ }
59
+ }
60
+ return null;
61
+ }
62
+
63
+ function checkDiagnostics(doc: ReturnType<typeof parse>, expectedPath: string): string | null {
64
+ const expected = JSON.parse(readFileSync(expectedPath, "utf8")) as { code: string; severity: string }[];
65
+ const got = validate(doc).map((d) => ({ code: d.code, severity: d.severity }));
66
+ const norm = (xs: { code: string; severity: string }[]) =>
67
+ xs.map((x) => `${x.severity}:${x.code}`).sort();
68
+ if (JSON.stringify(norm(got)) !== JSON.stringify(norm(expected))) {
69
+ return `diagnostics mismatch: got ${JSON.stringify(got)}, expected ${JSON.stringify(expected)}`;
70
+ }
71
+ return null;
72
+ }
73
+
74
+ function checkRoundtrip(source: string, expectedPath: string): string | null {
75
+ const doc = parse(source);
76
+ const rendered = renderNoma(doc);
77
+ const expected = readFileSync(expectedPath, "utf8");
78
+ if (rendered !== expected) {
79
+ return `roundtrip mismatch: render-noma output differs from expected.roundtrip.noma`;
80
+ }
81
+ const reparsed = parse(rendered);
82
+ if (JSON.stringify(reparsed) !== JSON.stringify(doc)) {
83
+ return `roundtrip property failed: parse(render-noma(parse(x))) !== parse(x)`;
84
+ }
85
+ return null;
86
+ }
87
+
88
+ function checkSpans(doc: ReturnType<typeof parse>, expectedPath: string): string | null {
89
+ const expected = JSON.parse(readFileSync(expectedPath, "utf8")) as Record<
90
+ string,
91
+ { startLine: number; endLine: number }
92
+ >;
93
+ for (const node of walk(doc)) {
94
+ if (!("id" in node) || !node.id) continue;
95
+ const want = expected[node.id];
96
+ if (!want) continue;
97
+ const gotStart = node.pos?.line;
98
+ const gotEnd = node.endLine;
99
+ if (gotStart !== want.startLine || gotEnd !== want.endLine) {
100
+ return `span mismatch for "${node.id}": got [${gotStart}, ${gotEnd}], expected [${want.startLine}, ${want.endLine}]`;
101
+ }
102
+ }
103
+ return null;
104
+ }
105
+
106
+ function checkPatch(source: string, fixturePath: string): string | null {
107
+ const patchPath = join(fixturePath, "patch.json");
108
+ const postPath = join(fixturePath, "expected.post.noma");
109
+ if (!existsSync(patchPath) || !existsSync(postPath)) return null;
110
+ const raw = JSON.parse(readFileSync(patchPath, "utf8")) as PatchOp | PatchOp[];
111
+ const ops = Array.isArray(raw) ? raw : [raw];
112
+ let cur = source;
113
+ for (const op of ops) {
114
+ cur = patchSource(cur, op);
115
+ }
116
+ const expected = readFileSync(postPath, "utf8");
117
+ if (cur !== expected) {
118
+ return `patch output mismatch: got\n${cur}\n--- expected ---\n${expected}`;
119
+ }
120
+ return null;
121
+ }
122
+
123
+ function checkOne(fixturePath: string): FixtureReport {
124
+ const name = relative(process.cwd(), fixturePath);
125
+ const inputPath = join(fixturePath, "input.noma");
126
+ const source = readFileSync(inputPath, "utf8");
127
+ const doc = parse(source);
128
+
129
+ const idsPath = join(fixturePath, "expected.ids.json");
130
+ if (existsSync(idsPath)) {
131
+ const err = checkIds(doc, idsPath);
132
+ if (err) return { name, status: "fail", error: err };
133
+ }
134
+ const diagsPath = join(fixturePath, "expected.diagnostics.json");
135
+ if (existsSync(diagsPath)) {
136
+ const err = checkDiagnostics(doc, diagsPath);
137
+ if (err) return { name, status: "fail", error: err };
138
+ }
139
+ const rtPath = join(fixturePath, "expected.roundtrip.noma");
140
+ if (existsSync(rtPath)) {
141
+ const err = checkRoundtrip(source, rtPath);
142
+ if (err) return { name, status: "fail", error: err };
143
+ }
144
+ const spansPath = join(fixturePath, "expected.spans.json");
145
+ if (existsSync(spansPath)) {
146
+ const err = checkSpans(doc, spansPath);
147
+ if (err) return { name, status: "fail", error: err };
148
+ }
149
+ const err = checkPatch(source, fixturePath);
150
+ if (err) return { name, status: "fail", error: err };
151
+ return { name, status: "pass" };
152
+ }
153
+
154
+ export function verifyFixtureDir(root: string): VerifyReport {
155
+ const fixtures = listFixtures(root).map(checkOne);
156
+ return { ok: fixtures.every((f) => f.status === "pass"), fixtures };
157
+ }
@@ -0,0 +1,382 @@
1
+ :root {
2
+ --noma-bg: #14151a;
3
+ --noma-fg: #e8e6e1;
4
+ --noma-muted: #8b8a85;
5
+ --noma-rule: #2b2d33;
6
+ --noma-accent: #ffa874;
7
+ --noma-accent-soft: #3a261c;
8
+ --noma-claim: #7eb8e8;
9
+ --noma-claim-soft: #1c2a3a;
10
+ --noma-evidence: #82d39c;
11
+ --noma-evidence-soft: #1c2e22;
12
+ --noma-risk: #f0857c;
13
+ --noma-risk-soft: #341d1c;
14
+ --noma-code-bg: #1c1d22;
15
+ --noma-card-bg: #1a1b20;
16
+ --noma-shadow: 0 1px 0 rgba(0, 0, 0, 0.3), 0 8px 24px -16px rgba(0, 0, 0, 0.6);
17
+ --noma-radius: 10px;
18
+ --noma-cols: 2;
19
+ --noma-font-serif: "Iowan Old Style", "Charter", Georgia, serif;
20
+ --noma-font-sans: -apple-system, BlinkMacSystemFont, "Inter", "Segoe UI", system-ui, sans-serif;
21
+ --noma-font-mono: "JetBrains Mono", "SF Mono", Menlo, Consolas, monospace;
22
+ }
23
+
24
+ * { box-sizing: border-box; }
25
+
26
+ html, body {
27
+ margin: 0;
28
+ padding: 0;
29
+ background: var(--noma-bg);
30
+ color: var(--noma-fg);
31
+ font-family: var(--noma-font-serif);
32
+ font-size: 17px;
33
+ line-height: 1.65;
34
+ -webkit-font-smoothing: antialiased;
35
+ text-rendering: optimizeLegibility;
36
+ }
37
+
38
+ main.noma-doc {
39
+ max-width: 760px;
40
+ margin: 4rem auto;
41
+ padding: 0 1.5rem 6rem;
42
+ }
43
+
44
+ main.noma-doc > section.noma-hero ~ section,
45
+ main.noma-doc > .noma-grid {
46
+ max-width: 100%;
47
+ }
48
+
49
+ h1, h2, h3, h4, h5, h6 {
50
+ font-family: var(--noma-font-sans);
51
+ font-weight: 700;
52
+ line-height: 1.2;
53
+ letter-spacing: -0.01em;
54
+ margin: 2.5em 0 0.6em;
55
+ color: var(--noma-fg);
56
+ }
57
+ h1 { font-size: 2.4rem; margin-top: 0; letter-spacing: -0.02em; }
58
+ h2 { font-size: 1.7rem; border-bottom: 1px solid var(--noma-rule); padding-bottom: 0.3em; }
59
+ h3 { font-size: 1.25rem; }
60
+ h4 { font-size: 1.05rem; color: var(--noma-muted); text-transform: uppercase; letter-spacing: 0.06em; }
61
+
62
+ p { margin: 0 0 1.05em; }
63
+ a { color: var(--noma-accent); text-decoration: underline; text-underline-offset: 2px; text-decoration-thickness: 1px; }
64
+ a:hover { text-decoration-thickness: 2px; }
65
+
66
+ code {
67
+ font-family: var(--noma-font-mono);
68
+ font-size: 0.9em;
69
+ background: var(--noma-code-bg);
70
+ padding: 0.1em 0.35em;
71
+ border-radius: 4px;
72
+ color: var(--noma-fg);
73
+ }
74
+ pre {
75
+ background: var(--noma-code-bg);
76
+ padding: 1em 1.2em;
77
+ border-radius: var(--noma-radius);
78
+ overflow-x: auto;
79
+ font-size: 0.9rem;
80
+ line-height: 1.5;
81
+ color: var(--noma-fg);
82
+ border: 1px solid var(--noma-rule);
83
+ }
84
+ pre code { background: none; padding: 0; }
85
+
86
+ blockquote {
87
+ border-left: 3px solid var(--noma-accent);
88
+ margin: 1.5em 0;
89
+ padding: 0.2em 1.2em;
90
+ color: var(--noma-muted);
91
+ font-style: italic;
92
+ }
93
+
94
+ hr { border: 0; border-top: 1px solid var(--noma-rule); margin: 3em 0; }
95
+
96
+ ul, ol { padding-left: 1.4em; }
97
+ li { margin: 0.25em 0; }
98
+
99
+ aside.noma-callout {
100
+ margin: 1.6em 0;
101
+ padding: 1em 1.2em;
102
+ border-radius: var(--noma-radius);
103
+ background: var(--noma-accent-soft);
104
+ border-left: 3px solid var(--noma-accent);
105
+ }
106
+ aside.noma-callout-warning { background: var(--noma-risk-soft); border-color: var(--noma-risk); }
107
+ aside.noma-callout-tip { background: var(--noma-evidence-soft); border-color: var(--noma-evidence); }
108
+ aside.noma-callout-note { background: #232530; border-color: #5a6071; }
109
+
110
+ aside.noma-research {
111
+ margin: 1.6em 0;
112
+ padding: 1em 1.2em;
113
+ border-radius: var(--noma-radius);
114
+ background: var(--noma-card-bg);
115
+ border: 1px solid var(--noma-rule);
116
+ box-shadow: var(--noma-shadow);
117
+ }
118
+ aside.noma-research .noma-research-head {
119
+ display: flex;
120
+ align-items: center;
121
+ gap: 0.8em;
122
+ margin-bottom: 0.5em;
123
+ }
124
+ aside.noma-research .noma-tag {
125
+ display: inline-block;
126
+ font-family: var(--noma-font-sans);
127
+ font-size: 0.7rem;
128
+ font-weight: 700;
129
+ letter-spacing: 0.08em;
130
+ text-transform: uppercase;
131
+ color: var(--noma-muted);
132
+ padding: 0.2em 0.6em;
133
+ border-radius: 999px;
134
+ background: var(--noma-code-bg);
135
+ }
136
+ aside.noma-claim { border-left: 3px solid var(--noma-claim); }
137
+ aside.noma-claim .noma-tag { color: var(--noma-claim); background: var(--noma-claim-soft); }
138
+ aside.noma-evidence { border-left: 3px solid var(--noma-evidence); }
139
+ aside.noma-evidence .noma-tag { color: var(--noma-evidence); background: var(--noma-evidence-soft); }
140
+ aside.noma-counterevidence { border-left: 3px solid var(--noma-risk); }
141
+ aside.noma-counterevidence .noma-tag { color: var(--noma-risk); background: var(--noma-risk-soft); }
142
+ aside.noma-risk { border-left: 3px solid var(--noma-risk); }
143
+ aside.noma-risk .noma-tag { color: var(--noma-risk); background: var(--noma-risk-soft); }
144
+ aside.noma-decision, aside.noma-adr { border-left: 3px solid var(--noma-accent); }
145
+ aside.noma-decision .noma-tag, aside.noma-adr .noma-tag { color: var(--noma-accent); background: var(--noma-accent-soft); }
146
+ aside.noma-open_question { border-left: 3px solid #d4b066; }
147
+ aside.noma-open_question .noma-tag { color: #d4b066; background: #2e2616; }
148
+ aside.noma-assumption { border-left: 3px solid #8a93ad; }
149
+ aside.noma-assumption .noma-tag { color: #8a93ad; background: #232530; }
150
+
151
+ .noma-export-button {
152
+ display: inline-block;
153
+ background: var(--noma-fg);
154
+ color: var(--noma-bg);
155
+ border: 0;
156
+ padding: 0.55em 1.1em;
157
+ margin: 0.3em 0.4em 0.3em 0;
158
+ border-radius: 999px;
159
+ font-family: var(--noma-font-sans);
160
+ font-weight: 600;
161
+ font-size: 0.85rem;
162
+ cursor: pointer;
163
+ transition: background 120ms ease;
164
+ }
165
+ .noma-export-button:hover { background: var(--noma-accent); color: var(--noma-bg); }
166
+ .noma-export-button[data-format="prompt"] { background: var(--noma-claim); color: var(--noma-bg); }
167
+ .noma-export-button[data-format="markdown"] { background: var(--noma-evidence); color: var(--noma-bg); }
168
+ .noma-export-button[data-format="json"] { background: var(--noma-muted); color: var(--noma-bg); }
169
+
170
+ .noma-control {
171
+ margin: 1em 0;
172
+ padding: 0.8em 1em;
173
+ background: var(--noma-card-bg);
174
+ border: 1px solid var(--noma-rule);
175
+ border-radius: var(--noma-radius);
176
+ font-family: var(--noma-font-sans);
177
+ font-size: 0.9rem;
178
+ color: var(--noma-fg);
179
+ }
180
+ .noma-control input { margin-left: 0.6em; background: var(--noma-bg); color: var(--noma-fg); border: 1px solid var(--noma-rule); border-radius: 4px; padding: 0.2em 0.4em; }
181
+
182
+ .noma-confidence {
183
+ flex: 1;
184
+ height: 6px;
185
+ border-radius: 999px;
186
+ background: var(--noma-rule);
187
+ overflow: hidden;
188
+ max-width: 140px;
189
+ }
190
+ .noma-confidence-bar {
191
+ height: 100%;
192
+ background: linear-gradient(90deg, var(--noma-accent), var(--noma-claim));
193
+ }
194
+
195
+ .noma-meta {
196
+ margin-top: 0.6em;
197
+ font-size: 0.85rem;
198
+ color: var(--noma-muted);
199
+ font-family: var(--noma-font-sans);
200
+ }
201
+ .noma-meta-key {
202
+ font-weight: 600;
203
+ color: var(--noma-fg);
204
+ }
205
+
206
+ .noma-grid {
207
+ display: grid;
208
+ grid-template-columns: repeat(var(--noma-cols), minmax(0, 1fr));
209
+ gap: 1.2rem;
210
+ margin: 2em 0;
211
+ }
212
+ @media (max-width: 720px) {
213
+ .noma-grid { grid-template-columns: 1fr; }
214
+ }
215
+
216
+ article.noma-card {
217
+ background: var(--noma-card-bg);
218
+ border: 1px solid var(--noma-rule);
219
+ border-radius: var(--noma-radius);
220
+ padding: 1.2em 1.4em;
221
+ box-shadow: var(--noma-shadow);
222
+ }
223
+ article.noma-card .noma-card-head {
224
+ display: flex;
225
+ align-items: center;
226
+ gap: 0.6em;
227
+ margin-bottom: 0.4em;
228
+ }
229
+ article.noma-card h3 {
230
+ margin: 0;
231
+ font-size: 1.05rem;
232
+ font-family: var(--noma-font-sans);
233
+ }
234
+ article.noma-card .noma-icon {
235
+ color: var(--noma-accent);
236
+ font-size: 0.9em;
237
+ }
238
+ article.noma-card p:last-child { margin-bottom: 0; }
239
+
240
+ section.noma-hero {
241
+ background: linear-gradient(180deg, var(--noma-accent-soft), transparent);
242
+ padding: 4rem 2.5rem 3rem;
243
+ border-radius: var(--noma-radius);
244
+ margin: 0 0 3rem;
245
+ text-align: center;
246
+ }
247
+ section.noma-hero h1 { font-size: 2.8rem; margin-top: 0; }
248
+
249
+ a.noma-button {
250
+ display: inline-block;
251
+ background: var(--noma-fg);
252
+ color: var(--noma-bg);
253
+ padding: 0.7em 1.4em;
254
+ border-radius: 999px;
255
+ text-decoration: none;
256
+ font-family: var(--noma-font-sans);
257
+ font-weight: 600;
258
+ font-size: 0.95rem;
259
+ margin-top: 0.5em;
260
+ }
261
+ a.noma-button:hover { background: var(--noma-accent); color: var(--noma-bg); }
262
+
263
+ figure.noma-plot {
264
+ margin: 1.8em 0;
265
+ padding: 1em 1.2em;
266
+ background: var(--noma-card-bg);
267
+ border: 1px solid var(--noma-rule);
268
+ border-radius: var(--noma-radius);
269
+ }
270
+ figure.noma-plot .noma-plot-canvas {
271
+ color: var(--noma-claim);
272
+ background: linear-gradient(180deg, transparent, var(--noma-claim-soft));
273
+ border-radius: 6px;
274
+ padding: 0.6em;
275
+ }
276
+ figure.noma-plot svg { width: 100%; height: auto; display: block; }
277
+ figure.noma-plot figcaption {
278
+ margin-top: 0.6em;
279
+ font-size: 0.85rem;
280
+ color: var(--noma-muted);
281
+ font-family: var(--noma-font-sans);
282
+ }
283
+
284
+ details.noma-dataset {
285
+ margin: 1.4em 0;
286
+ padding: 0.6em 1em;
287
+ background: var(--noma-code-bg);
288
+ border-radius: var(--noma-radius);
289
+ font-family: var(--noma-font-sans);
290
+ font-size: 0.9rem;
291
+ color: var(--noma-fg);
292
+ }
293
+ details.noma-dataset pre {
294
+ background: transparent;
295
+ padding: 0.6em 0 0;
296
+ border: 0;
297
+ }
298
+
299
+ .noma-agent-task {
300
+ margin: 1.4em 0;
301
+ padding: 1em 1.2em;
302
+ background: var(--noma-claim-soft);
303
+ border-left: 3px solid var(--noma-claim);
304
+ border-radius: var(--noma-radius);
305
+ }
306
+ .noma-agent-task label {
307
+ display: flex;
308
+ align-items: center;
309
+ gap: 0.6em;
310
+ font-family: var(--noma-font-sans);
311
+ font-weight: 600;
312
+ font-size: 0.9rem;
313
+ margin-bottom: 0.4em;
314
+ }
315
+
316
+ table.noma-table {
317
+ width: 100%;
318
+ border-collapse: collapse;
319
+ margin: 1.6em 0;
320
+ font-family: var(--noma-font-sans);
321
+ font-size: 0.92rem;
322
+ background: var(--noma-card-bg);
323
+ border: 1px solid var(--noma-rule);
324
+ border-radius: var(--noma-radius);
325
+ overflow: hidden;
326
+ box-shadow: var(--noma-shadow);
327
+ }
328
+ table.noma-table thead {
329
+ background: var(--noma-code-bg);
330
+ }
331
+ table.noma-table th {
332
+ text-align: left;
333
+ font-weight: 700;
334
+ letter-spacing: 0.02em;
335
+ padding: 0.7em 1em;
336
+ border-bottom: 1px solid var(--noma-rule);
337
+ color: var(--noma-fg);
338
+ }
339
+ table.noma-table td {
340
+ padding: 0.6em 1em;
341
+ border-bottom: 1px solid var(--noma-rule);
342
+ vertical-align: top;
343
+ color: var(--noma-fg);
344
+ }
345
+ table.noma-table tbody tr:last-child td { border-bottom: 0; }
346
+ table.noma-table tbody tr:hover { background: rgba(255, 168, 116, 0.06); }
347
+
348
+ [data-variant="important"] { border-width: 5px !important; box-shadow: 0 0 0 1px var(--noma-accent) inset, var(--noma-shadow); }
349
+ [data-variant="subtle"] { opacity: 0.7; box-shadow: none; }
350
+ [data-variant="success"] { border-left: 3px solid var(--noma-evidence); background: var(--noma-evidence-soft); }
351
+ [data-variant="danger"] { border-left: 3px solid var(--noma-risk); background: var(--noma-risk-soft); }
352
+ [data-variant="info"] { border-left: 3px solid var(--noma-claim); background: var(--noma-claim-soft); }
353
+
354
+ @media print {
355
+ html, body { background: white; color: black; font-size: 11pt; }
356
+ main.noma-doc { margin: 0 auto; padding: 0; max-width: 100%; }
357
+ section.noma-hero { background: none; border: 1px solid #ddd; }
358
+ a { color: black; }
359
+ pre, article.noma-card, figure.noma-plot, aside.noma-research, details.noma-dataset {
360
+ box-shadow: none;
361
+ page-break-inside: avoid;
362
+ }
363
+ h1, h2, h3 { page-break-after: avoid; }
364
+ }
365
+
366
+ /* v0.4 — site nav + math (mirrors default.css; vars from :root cascade) */
367
+ a.noma-alias { display: block; position: relative; top: -1.2em; visibility: hidden; height: 0; }
368
+ nav.noma-site-nav { max-width: 880px; margin: 1.5rem auto 0; padding: 0 2rem; display: flex; align-items: baseline; gap: 1.5rem; flex-wrap: wrap; font-size: 0.85rem; color: var(--noma-muted); }
369
+ nav.noma-site-nav a.noma-site-home { font-weight: 600; color: var(--noma-fg); text-decoration: none; border-right: 1px solid var(--noma-rule); padding-right: 1.25rem; }
370
+ nav.noma-site-nav ol { list-style: none; padding: 0; margin: 0; display: flex; gap: 1.25rem; flex-wrap: wrap; }
371
+ nav.noma-site-nav a { color: var(--noma-muted); text-decoration: none; }
372
+ nav.noma-site-nav a:hover { color: var(--noma-accent); }
373
+ nav.noma-site-nav .noma-nav-current span { color: var(--noma-fg); font-weight: 500; }
374
+ a.noma-ref.noma-xchapter::after { content: " ↗"; font-size: 0.85em; color: var(--noma-muted); }
375
+ ol.noma-site-toc { list-style: none; padding: 0; margin: 2rem 0; display: grid; gap: 0.6rem; }
376
+ a.noma-site-chapter { display: block; padding: 1.1rem 1.4rem; background: var(--noma-card-bg); border: 1px solid var(--noma-rule); border-radius: var(--noma-radius); text-decoration: none; color: inherit; }
377
+ a.noma-site-chapter:hover { border-color: var(--noma-accent); }
378
+ .noma-site-chapter-title { display: block; font-weight: 600; font-size: 1.1rem; }
379
+ .noma-site-chapter-summary { display: block; color: var(--noma-muted); margin-top: 0.4rem; font-size: 0.93rem; }
380
+ .noma-math-display { margin: 1.4em auto; text-align: center; overflow-x: auto; }
381
+ .noma-math-inline { display: inline; }
382
+