cantip 0.1.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 (67) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +61 -0
  3. package/app/components/CanvasMount.tsx +62 -0
  4. package/app/components/CodeWrapToggle.tsx +78 -0
  5. package/app/components/FindOnPage.tsx +224 -0
  6. package/app/components/MobileBottomBar.tsx +93 -0
  7. package/app/components/MobileProjectsPanel.tsx +113 -0
  8. package/app/components/PageFloatingMenu.tsx +224 -0
  9. package/app/components/ProjectSwitcher.tsx +124 -0
  10. package/app/components/Search.tsx +930 -0
  11. package/app/components/ShortcutsHelp.tsx +113 -0
  12. package/app/components/Sidebar.tsx +1049 -0
  13. package/app/components/TabBar.tsx +227 -0
  14. package/app/components/Toc.tsx +129 -0
  15. package/app/components/TopBar.tsx +74 -0
  16. package/app/components/theme-toggle.tsx +71 -0
  17. package/app/components/ui/button.tsx +56 -0
  18. package/app/components/ui/card.tsx +55 -0
  19. package/app/components/ui/dropdown-menu.tsx +156 -0
  20. package/app/components/ui/input.tsx +21 -0
  21. package/app/entry.client.tsx +12 -0
  22. package/app/entry.server.tsx +155 -0
  23. package/app/generated/site.ts +19 -0
  24. package/app/generated/slots.ts +10 -0
  25. package/app/generated/theme.generated.css +60 -0
  26. package/app/lib/config/config.server.ts +50 -0
  27. package/app/lib/config/defaults.ts +120 -0
  28. package/app/lib/config/load.ts +82 -0
  29. package/app/lib/config/schema.ts +131 -0
  30. package/app/lib/config/site.ts +43 -0
  31. package/app/lib/content.server.ts +105 -0
  32. package/app/lib/projects.ts +86 -0
  33. package/app/lib/sidebar.server.ts +113 -0
  34. package/app/lib/site.ts +27 -0
  35. package/app/lib/slots.tsx +33 -0
  36. package/app/lib/tabs.tsx +128 -0
  37. package/app/lib/useKeyboardShortcuts.ts +149 -0
  38. package/app/lib/utils.ts +17 -0
  39. package/app/root.tsx +171 -0
  40. package/app/routes/$.tsx +158 -0
  41. package/app/routes/_index.tsx +60 -0
  42. package/app/styles/app.css +461 -0
  43. package/app/styles/obsidian.css +83 -0
  44. package/app/styles/tailwind.css +227 -0
  45. package/cli.js +119 -0
  46. package/components.json +21 -0
  47. package/dist/config.mjs +87 -0
  48. package/dist/generate-content.mjs +1665 -0
  49. package/package.json +112 -0
  50. package/scripts/build-search-index.ts +129 -0
  51. package/scripts/canonical.ts +34 -0
  52. package/scripts/canvas-to-md.ts +73 -0
  53. package/scripts/compile.ts +242 -0
  54. package/scripts/emit-config.ts +163 -0
  55. package/scripts/generate-content.ts +197 -0
  56. package/scripts/obsidian/files.ts +222 -0
  57. package/scripts/obsidian/fs.ts +34 -0
  58. package/scripts/obsidian/generate.ts +36 -0
  59. package/scripts/obsidian/html.ts +17 -0
  60. package/scripts/obsidian/logger.ts +10 -0
  61. package/scripts/obsidian/markdown.ts +56 -0
  62. package/scripts/obsidian/obsidian.ts +229 -0
  63. package/scripts/obsidian/path.ts +60 -0
  64. package/scripts/obsidian/rehype.ts +60 -0
  65. package/scripts/obsidian/remark.ts +712 -0
  66. package/scripts/obsidian/types.ts +31 -0
  67. package/vite.config.ts +62 -0
@@ -0,0 +1,461 @@
1
+ @import './obsidian.css';
2
+
3
+ /* Layout sizing tokens still consumed by the markdown/canvas styles below.
4
+ Theme colours now come from tailwind.css (shadcn) via the --sl-* bridge. */
5
+ :root {
6
+ --content-max: 720px;
7
+ --content-pad-x: 2.5rem;
8
+ --sl-text-xs: 0.75rem;
9
+ --sl-text-sm: 0.875rem;
10
+ --sl-text-base: 1rem;
11
+ --sl-text-lg: 1.125rem;
12
+ --sl-text-xl: 1.25rem;
13
+ --sl-text-2xl: 1.5rem;
14
+ --sl-text-3xl: 1.875rem;
15
+ --sl-text-4xl: 2.25rem;
16
+ /* Obsidian's default heading scale (its --h1..--h6-size vars): a smooth
17
+ ~1.125 ratio from H1 1.802rem down to H6 1rem. */
18
+ --sl-text-h1: 1.802rem;
19
+ --sl-text-h2: 1.602rem;
20
+ --sl-text-h3: 1.424rem;
21
+ --sl-text-h4: 1.266rem;
22
+ --sl-text-h5: 1.125rem;
23
+ --sl-text-h6: 1rem;
24
+ --sl-line-height: 1.7;
25
+ /* Obsidian renders headings in a soft off-white and the body in a muted
26
+ gray, rather than pure-white bold headings on bright text. Like Obsidian's
27
+ defaults, H1 is heavier (700) than H2–H6 (600). */
28
+ --obsidian-heading-color: oklch(0.93 0 0);
29
+ --obsidian-heading-weight: 600;
30
+ --obsidian-h1-weight: 700;
31
+ --obsidian-body-color: oklch(0.8 0 0);
32
+ /* Vertical space the sticky bars occupy, used as scroll-margin-top on
33
+ anchor targets so hash navigation lands below them instead of behind.
34
+ 0 on mobile: there are no sticky top bars there (the nav is a fixed
35
+ bottom bar). The desktop bars kick in at the md breakpoint below. */
36
+ --scroll-offset: 0px;
37
+ }
38
+
39
+ /* Light theme: invert the soft off-white/gray pair to soft near-black/gray so
40
+ the same Obsidian-style restraint holds when the dark/light toggle flips. */
41
+ :root:not(.dark) {
42
+ --obsidian-heading-color: oklch(0.22 0 0);
43
+ --obsidian-body-color: oklch(0.38 0 0);
44
+ }
45
+
46
+ @media (min-width: 48rem) {
47
+ /* Desktop: the TopBar (h-11 = 2.75rem) is always pinned at the top. */
48
+ :root {
49
+ --scroll-offset: 2.75rem;
50
+ }
51
+ /* The TabBar's sticky wrapper (top-11) renders its inner strip only when
52
+ tabs are open; otherwise it's empty. When present, the sticky stack is
53
+ one bar taller, so push anchor targets down by the TabBar's height too. */
54
+ body:has([data-tab-strip]) {
55
+ --scroll-offset: calc(2.75rem + 2.25rem);
56
+ }
57
+ }
58
+
59
+ img {
60
+ max-width: 100%;
61
+ height: auto;
62
+ }
63
+
64
+ /* ---- Document title ---- */
65
+ .content h1 {
66
+ margin-top: 0;
67
+ margin-bottom: 0.5rem;
68
+ font-size: var(--sl-text-h1);
69
+ line-height: 1.3;
70
+ font-weight: var(--obsidian-h1-weight);
71
+ color: var(--obsidian-heading-color);
72
+ }
73
+
74
+ /* ---- Title row: H1 + priority badge inline ---- */
75
+ .content h1.title-row {
76
+ display: flex;
77
+ align-items: baseline;
78
+ flex-wrap: wrap;
79
+ column-gap: 0.75rem;
80
+ row-gap: 0.5rem;
81
+ }
82
+
83
+ /* ---- Priority badge (MoSCoW) ---- */
84
+ .priority-badge {
85
+ display: inline-flex;
86
+ align-items: center;
87
+ align-self: center;
88
+ border: 1px solid var(--badge-border, var(--sl-color-gray-4));
89
+ border-radius: 9999px;
90
+ padding: 0.15em 0.6em;
91
+ background: var(--badge-bg, var(--sl-color-gray-6));
92
+ color: var(--badge-fg, var(--sl-color-gray-1));
93
+ font-size: var(--sl-text-xs);
94
+ font-weight: 600;
95
+ line-height: 1.4;
96
+ white-space: nowrap;
97
+ }
98
+
99
+ .priority-badge[data-priority='must-have'] {
100
+ --badge-border: var(--sl-color-red, #ef4444);
101
+ --badge-bg: var(--sl-color-red-low, #450a0a);
102
+ --badge-fg: var(--sl-color-red-high, #f87171);
103
+ }
104
+
105
+ .priority-badge[data-priority='should-have'] {
106
+ --badge-border: var(--sl-color-orange, #eab308);
107
+ --badge-bg: var(--sl-color-orange-low, #422006);
108
+ --badge-fg: var(--sl-color-orange-high, #fbbf24);
109
+ }
110
+
111
+ .priority-badge[data-priority='could-have'] {
112
+ --badge-border: var(--sl-color-blue, #3b82f6);
113
+ --badge-bg: var(--sl-color-blue-low, #172554);
114
+ --badge-fg: var(--sl-color-blue-high, #60a5fa);
115
+ }
116
+
117
+ .priority-badge[data-priority='wont-have'] {
118
+ --badge-border: var(--sl-color-gray-4);
119
+ --badge-bg: var(--sl-color-gray-6);
120
+ --badge-fg: var(--sl-color-gray-2);
121
+ }
122
+
123
+ /* ---- Frontmatter metadata (collapsible, deliberately understated) ---- */
124
+ .frontmatter {
125
+ margin: 0 0 2rem;
126
+ font-size: var(--sl-text-xs);
127
+ }
128
+
129
+ .frontmatter__summary {
130
+ display: inline-flex;
131
+ align-items: center;
132
+ gap: 0.35em;
133
+ color: var(--sl-color-gray-3);
134
+ font-size: var(--sl-text-xs);
135
+ cursor: pointer;
136
+ user-select: none;
137
+ opacity: 0.6;
138
+ transition: opacity 0.15s;
139
+ list-style: none;
140
+ }
141
+
142
+ /* Hide the native disclosure triangle so we can draw our own caret. */
143
+ .frontmatter__summary::-webkit-details-marker {
144
+ display: none;
145
+ }
146
+
147
+ /* Open chevron that marks the block as expandable; rotates down when open. */
148
+ .frontmatter__summary::before {
149
+ content: '';
150
+ width: 0.4em;
151
+ height: 0.4em;
152
+ border-style: solid;
153
+ border-color: currentColor;
154
+ border-width: 0.1em 0.1em 0 0;
155
+ transform: rotate(45deg);
156
+ transition: transform 0.15s;
157
+ }
158
+
159
+ .frontmatter[open] > .frontmatter__summary::before {
160
+ transform: rotate(135deg);
161
+ }
162
+
163
+ .frontmatter__summary:hover {
164
+ opacity: 1;
165
+ color: var(--sl-color-gray-2);
166
+ }
167
+
168
+ .frontmatter__list {
169
+ margin: 0.5rem 0 0;
170
+ }
171
+
172
+ .frontmatter__row {
173
+ display: grid;
174
+ grid-template-columns: minmax(7rem, max-content) 1fr;
175
+ gap: 1rem;
176
+ padding: 0.15rem 0;
177
+ }
178
+
179
+ .frontmatter__key {
180
+ margin: 0;
181
+ color: var(--sl-color-gray-3);
182
+ font-family: var(--sl-font-system-mono, ui-monospace, monospace);
183
+ }
184
+
185
+ .frontmatter__value {
186
+ margin: 0;
187
+ color: var(--sl-color-gray-2);
188
+ word-break: break-word;
189
+ }
190
+
191
+ /* ---- Canvas pages ---- */
192
+ .canvas-container {
193
+ position: relative;
194
+ width: 100%;
195
+ height: calc(100vh - 4rem);
196
+ min-height: 480px;
197
+ overflow: hidden;
198
+ background: var(--background);
199
+ }
200
+
201
+ /* ---- Prose: markdown body ---- */
202
+ .content .body {
203
+ font-size: var(--sl-text-base);
204
+ line-height: var(--sl-line-height);
205
+ color: var(--obsidian-body-color);
206
+ }
207
+ /* Strip browser-default block margins so the ONLY source of vertical rhythm is
208
+ the blank-line marker below — never a blanket adjacent-sibling rule.
209
+ `margin-top` is computed from two custom properties so the blank-line gap ADDS
210
+ to a block's own top margin instead of overriding it: `--block-mt` is the
211
+ block's intrinsic top margin (0 by default; element rules below set it), and
212
+ `--blank-gap` is the inter-block gap (0 unless `.has-blank-before` sets it).
213
+ Their sum is the final margin, so a block that sets `--block-mt: 0.25rem` and
214
+ also carries `.has-blank-before` ends up at 1.25rem, not a clobbered 1rem. */
215
+ .content .body :is(p, ul, ol, blockquote, pre, table, h1, h2, h3, h4, h5, h6) {
216
+ --block-mt: 0px;
217
+ --blank-gap: 0px;
218
+ margin-block: 0;
219
+ margin-top: calc(var(--block-mt) + var(--blank-gap));
220
+ }
221
+ /* A block earns a top gap ONLY when it was separated from its previous sibling
222
+ by a blank line in the source markdown. `scripts/compile.ts` tags those
223
+ blocks with `.has-blank-before`; blocks the author wrote on adjacent lines
224
+ (no blank line) get no class and sit flush. Setting `--blank-gap` (not
225
+ `margin-top` directly) lets the gap add to the block's own `--block-mt`. The
226
+ doubled `.body.body` raises specificity above the reset above so this wins. */
227
+ .content .body.body .has-blank-before {
228
+ --blank-gap: 1rem;
229
+ }
230
+ /* The first block in the body has nothing above it, so it gets no top margin —
231
+ neither the intrinsic `--block-mt` (e.g. a heading's) nor a blank-line gap.
232
+ Keeps content flush to the top of the column instead of starting with a gap. */
233
+ .content .body > :first-child {
234
+ --block-mt: 0px;
235
+ --blank-gap: 0px;
236
+ }
237
+ .content .body :is(h1, h2, h3, h4, h5, h6) {
238
+ color: var(--obsidian-heading-color);
239
+ font-weight: var(--obsidian-heading-weight);
240
+ line-height: 1.3;
241
+ /* Offset hash navigation (TOC clicks, sidebar links with #anchors) so the
242
+ heading lands below the sticky bars instead of hidden behind them. */
243
+ scroll-margin-top: var(--scroll-offset);
244
+ }
245
+ /* Headings carry an intrinsic TOP-only margin so they breathe above and sit
246
+ flush to the text below them (bottom stays 0 from the reset). The margin
247
+ feeds `--block-mt`, so when the source also left a blank line above, the
248
+ `.has-blank-before` gap (--blank-gap) ADDS on top — heading spacing scales by
249
+ level (larger for higher-level headings) the way Obsidian renders it. */
250
+ .content .body h1 {
251
+ font-size: var(--sl-text-h1);
252
+ font-weight: var(--obsidian-h1-weight);
253
+ --block-mt: 2rem;
254
+ }
255
+ /* Obsidian draws no rule under H2 — drop the hairline so headings match. */
256
+ .content .body h2 {
257
+ font-size: var(--sl-text-h2);
258
+ --block-mt: 1.75rem;
259
+ }
260
+ .content .body h3 {
261
+ font-size: var(--sl-text-h3);
262
+ --block-mt: 1.5rem;
263
+ }
264
+ .content .body h4 {
265
+ font-size: var(--sl-text-h4);
266
+ --block-mt: 1.25rem;
267
+ }
268
+ .content .body h5 {
269
+ font-size: var(--sl-text-h5);
270
+ --block-mt: 1rem;
271
+ }
272
+ .content .body h6 {
273
+ font-size: var(--sl-text-h6);
274
+ --block-mt: 1rem;
275
+ }
276
+ .content .body a {
277
+ color: var(--brand);
278
+ text-decoration: underline;
279
+ text-underline-offset: 0.15em;
280
+ text-decoration-color: var(--sl-color-gray-4);
281
+ }
282
+ .content .body a:hover {
283
+ text-decoration-color: currentColor;
284
+ }
285
+ /* Tailwind's preflight resets lists to `list-style: none`; restore real
286
+ markers for prose lists (task lists re-disable them below). */
287
+ .content .body ul {
288
+ padding-inline-start: 1.5rem;
289
+ list-style: disc;
290
+ }
291
+ .content .body ol {
292
+ padding-inline-start: 1.5rem;
293
+ list-style: decimal;
294
+ }
295
+ /* List items aren't in the reset's accumulator group, so give them the same
296
+ `--block-mt`/`--blank-gap` machinery: the 0.25rem inter-item gap goes into
297
+ `--block-mt`, and a loose-list item that earns `.has-blank-before` adds its
298
+ 1rem on top (→ 1.25rem) instead of replacing the 0.25rem. */
299
+ .content .body li {
300
+ --block-mt: 0px;
301
+ --blank-gap: 0px;
302
+ margin-top: calc(var(--block-mt) + var(--blank-gap));
303
+ }
304
+ .content .body li + li,
305
+ .content .body li > :is(ul, ol) {
306
+ --block-mt: 0.25rem;
307
+ }
308
+ .content .body li > p {
309
+ margin: 0;
310
+ }
311
+ .content .body :is(ul, ol).contains-task-list {
312
+ padding-inline-start: 0;
313
+ list-style: none;
314
+ }
315
+ .content .body .task-list-item {
316
+ display: flex;
317
+ align-items: flex-start;
318
+ gap: 0.5rem;
319
+ }
320
+ .content .body .task-list-item input {
321
+ margin-top: 0.4em;
322
+ }
323
+ .content .body code {
324
+ font-family: var(--sl-font-system-mono);
325
+ background: var(--sl-color-bg-inline-code, var(--sl-color-gray-6));
326
+ padding: 0.15em 0.35em;
327
+ border-radius: 0.25rem;
328
+ font-size: 0.875em;
329
+ }
330
+ .content .body pre {
331
+ font-family: var(--sl-font-system-mono);
332
+ background: var(--sl-color-gray-6);
333
+ border: 1px solid var(--sl-color-hairline);
334
+ padding: 0.75rem 1rem;
335
+ border-radius: 0.5rem;
336
+ line-height: 1.6;
337
+ font-size: var(--sl-text-sm);
338
+ /* No-wrap with horizontal scroll by default; the injected per-block toggle
339
+ adds `.pre-wrap` to wrap long lines instead. `overflow-wrap: anywhere`
340
+ (only when wrapping) forces breaks inside unbreakable tokens. */
341
+ overflow-x: auto;
342
+ }
343
+ .content .body pre.pre-wrap {
344
+ white-space: pre-wrap;
345
+ overflow-wrap: anywhere;
346
+ overflow-x: visible;
347
+ }
348
+ /* Wrapper added client-side around each <pre> (see CodeWrapToggle). It is the
349
+ positioning context for the toggle, so the button pins to the block's
350
+ top-right corner and does NOT scroll with the <pre>'s horizontal overflow. */
351
+ .content .body .pre-wrap-shell {
352
+ position: relative;
353
+ }
354
+ /* Reserve room on the right of the first line so the button never overlaps
355
+ long code. Applied to the wrapper so it affects layout without scrolling. */
356
+ .content .body .pre-wrap-shell > pre {
357
+ padding-right: 2.75rem;
358
+ }
359
+ .content .body .pre-wrap-toggle {
360
+ position: absolute;
361
+ top: 0.375rem;
362
+ right: 0.375rem;
363
+ z-index: 1;
364
+ display: inline-flex;
365
+ align-items: center;
366
+ justify-content: center;
367
+ width: 1.75rem;
368
+ height: 1.75rem;
369
+ padding: 0;
370
+ border: 1px solid var(--sl-color-hairline);
371
+ border-radius: 0.375rem;
372
+ background: var(--sl-color-gray-6);
373
+ color: var(--sl-color-gray-2);
374
+ cursor: pointer;
375
+ opacity: 0;
376
+ transition: opacity 0.15s, color 0.15s, background 0.15s;
377
+ }
378
+ .content .body .pre-wrap-shell:hover .pre-wrap-toggle,
379
+ .content .body .pre-wrap-shell:focus-within .pre-wrap-toggle,
380
+ .content .body .pre-wrap-toggle:focus-visible {
381
+ opacity: 1;
382
+ }
383
+ .content .body .pre-wrap-toggle:hover {
384
+ color: var(--sl-color-white);
385
+ background: var(--sl-color-gray-5);
386
+ }
387
+ .content .body .pre-wrap-toggle svg {
388
+ width: 1rem;
389
+ height: 1rem;
390
+ pointer-events: none;
391
+ }
392
+ .content .body pre code {
393
+ background: none;
394
+ padding: 0;
395
+ font-size: inherit;
396
+ border-radius: 0;
397
+ }
398
+ .content .body table {
399
+ border-collapse: collapse;
400
+ width: auto;
401
+ max-width: 100%;
402
+ display: block;
403
+ overflow-x: auto;
404
+ font-size: var(--sl-text-sm);
405
+ }
406
+ .content .body :is(th, td) {
407
+ border: 1px solid var(--sl-color-gray-5);
408
+ padding: 0.5rem 0.75rem;
409
+ text-align: start;
410
+ }
411
+ .content .body th {
412
+ background: var(--sl-color-gray-6);
413
+ color: var(--sl-color-white);
414
+ font-weight: 600;
415
+ }
416
+ .content .body blockquote {
417
+ border-inline-start: 3px solid var(--sl-color-accent);
418
+ margin: 0;
419
+ padding-inline-start: 1rem;
420
+ color: var(--sl-color-gray-2);
421
+ font-style: italic;
422
+ }
423
+ .content .body blockquote :is(p, ul, ol) {
424
+ margin: 0;
425
+ }
426
+ /* hr keeps its own 2rem rule but routes the top half through the accumulator so
427
+ a blank-line-separated `<hr>` adds the 1rem gap on top (→ 3rem) instead of the
428
+ class being a no-op. `--block-mt` carries the intrinsic 2rem; `--blank-gap`
429
+ (0, or 1rem via `.has-blank-before`) adds to it. Bottom margin stays a flat
430
+ 2rem since the blank-line marker only ever affects the top. */
431
+ .content .body hr {
432
+ --block-mt: 2rem;
433
+ --blank-gap: 0px;
434
+ border: none;
435
+ border-top: 1px solid var(--sl-color-hairline);
436
+ margin-block: 2rem;
437
+ margin-top: calc(var(--block-mt) + var(--blank-gap));
438
+ }
439
+ .content .body img {
440
+ border-radius: 0.5rem;
441
+ }
442
+
443
+ /* When the left sidebar is dragged wide enough that the content column would be
444
+ too narrow, the resize logic adds `.toc-collapsed` on <html> and zeroes the
445
+ --toc-width grid column. Hide the TOC element to match (it is xl:block). */
446
+ html.toc-collapsed #toc {
447
+ display: none;
448
+ }
449
+
450
+ /* In-page find (FindOnPage): painted over Range objects via the CSS Custom
451
+ Highlight API — no DOM mutation, so it sits cleanly over the injected article
452
+ HTML. `find-all` tints every match; `find-current` overrides the active one so
453
+ it stands out as you step through. Registered in app/components/FindOnPage.tsx. */
454
+ ::highlight(find-all) {
455
+ background-color: hsl(51, 100%, 50%);
456
+ color: black;
457
+ }
458
+ ::highlight(find-current) {
459
+ background-color: hsl(28, 100%, 53%);
460
+ color: black;
461
+ }
@@ -0,0 +1,83 @@
1
+ /* Obsidian highlight ==text== */
2
+ .obs-highlight {
3
+ background-color: hsl(51, 71%, 56%);
4
+ }
5
+
6
+ [data-theme='light'] .obs-highlight {
7
+ background-color: hsl(51, 80%, 56%);
8
+ }
9
+
10
+ /* Obsidian #tags */
11
+ .obs-tag {
12
+ background-color: var(--sl-color-accent-low);
13
+ border-radius: 0.25rem;
14
+ border: 1px solid var(--sl-color-accent);
15
+ color: #fff;
16
+ font-family: var(--sl-font-system-mono, ui-monospace, monospace);
17
+ font-size: 0.75rem;
18
+ padding: 0.25rem 0.375rem;
19
+ }
20
+
21
+ [data-theme='light'] .obs-tag {
22
+ background-color: var(--sl-color-accent-high, #4C3FB0);
23
+ }
24
+
25
+ /* Audio/video/PDF embeds */
26
+ .obs-embed-audio {
27
+ width: 100%;
28
+ }
29
+
30
+ .obs-embed-pdf {
31
+ border: none;
32
+ height: 600px;
33
+ width: 100%;
34
+ }
35
+
36
+ /* Callouts */
37
+ .callout {
38
+ border-left: 4px solid;
39
+ border-radius: 0.25rem;
40
+ padding: 0.75rem 1rem;
41
+ margin: 1rem 0;
42
+ }
43
+
44
+ .callout .callout-title {
45
+ font-weight: 600;
46
+ margin: 0 0 0.5rem;
47
+ }
48
+
49
+ .callout-note {
50
+ border-color: var(--sl-color-blue, #3b82f6);
51
+ background: var(--sl-color-blue-low, #172554);
52
+ }
53
+
54
+ .callout-note .callout-title {
55
+ color: var(--sl-color-blue-high, #60a5fa);
56
+ }
57
+
58
+ .callout-tip {
59
+ border-color: var(--sl-color-green, #22c55e);
60
+ background: var(--sl-color-green-low, #14532d);
61
+ }
62
+
63
+ .callout-tip .callout-title {
64
+ color: var(--sl-color-green-high, #4ade80);
65
+ }
66
+
67
+ .callout-caution {
68
+ border-color: var(--sl-color-orange, #eab308);
69
+ background: var(--sl-color-orange-low, #422006);
70
+ }
71
+
72
+ .callout-caution .callout-title {
73
+ color: var(--sl-color-orange-high, #fbbf24);
74
+ }
75
+
76
+ .callout-danger {
77
+ border-color: var(--sl-color-red, #ef4444);
78
+ background: var(--sl-color-red-low, #450a0a);
79
+ }
80
+
81
+ .callout-danger .callout-title {
82
+ color: var(--sl-color-red-high, #f87171);
83
+ }