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.
- package/LICENSE +21 -0
- package/README.md +61 -0
- package/app/components/CanvasMount.tsx +62 -0
- package/app/components/CodeWrapToggle.tsx +78 -0
- package/app/components/FindOnPage.tsx +224 -0
- package/app/components/MobileBottomBar.tsx +93 -0
- package/app/components/MobileProjectsPanel.tsx +113 -0
- package/app/components/PageFloatingMenu.tsx +224 -0
- package/app/components/ProjectSwitcher.tsx +124 -0
- package/app/components/Search.tsx +930 -0
- package/app/components/ShortcutsHelp.tsx +113 -0
- package/app/components/Sidebar.tsx +1049 -0
- package/app/components/TabBar.tsx +227 -0
- package/app/components/Toc.tsx +129 -0
- package/app/components/TopBar.tsx +74 -0
- package/app/components/theme-toggle.tsx +71 -0
- package/app/components/ui/button.tsx +56 -0
- package/app/components/ui/card.tsx +55 -0
- package/app/components/ui/dropdown-menu.tsx +156 -0
- package/app/components/ui/input.tsx +21 -0
- package/app/entry.client.tsx +12 -0
- package/app/entry.server.tsx +155 -0
- package/app/generated/site.ts +19 -0
- package/app/generated/slots.ts +10 -0
- package/app/generated/theme.generated.css +60 -0
- package/app/lib/config/config.server.ts +50 -0
- package/app/lib/config/defaults.ts +120 -0
- package/app/lib/config/load.ts +82 -0
- package/app/lib/config/schema.ts +131 -0
- package/app/lib/config/site.ts +43 -0
- package/app/lib/content.server.ts +105 -0
- package/app/lib/projects.ts +86 -0
- package/app/lib/sidebar.server.ts +113 -0
- package/app/lib/site.ts +27 -0
- package/app/lib/slots.tsx +33 -0
- package/app/lib/tabs.tsx +128 -0
- package/app/lib/useKeyboardShortcuts.ts +149 -0
- package/app/lib/utils.ts +17 -0
- package/app/root.tsx +171 -0
- package/app/routes/$.tsx +158 -0
- package/app/routes/_index.tsx +60 -0
- package/app/styles/app.css +461 -0
- package/app/styles/obsidian.css +83 -0
- package/app/styles/tailwind.css +227 -0
- package/cli.js +119 -0
- package/components.json +21 -0
- package/dist/config.mjs +87 -0
- package/dist/generate-content.mjs +1665 -0
- package/package.json +112 -0
- package/scripts/build-search-index.ts +129 -0
- package/scripts/canonical.ts +34 -0
- package/scripts/canvas-to-md.ts +73 -0
- package/scripts/compile.ts +242 -0
- package/scripts/emit-config.ts +163 -0
- package/scripts/generate-content.ts +197 -0
- package/scripts/obsidian/files.ts +222 -0
- package/scripts/obsidian/fs.ts +34 -0
- package/scripts/obsidian/generate.ts +36 -0
- package/scripts/obsidian/html.ts +17 -0
- package/scripts/obsidian/logger.ts +10 -0
- package/scripts/obsidian/markdown.ts +56 -0
- package/scripts/obsidian/obsidian.ts +229 -0
- package/scripts/obsidian/path.ts +60 -0
- package/scripts/obsidian/rehype.ts +60 -0
- package/scripts/obsidian/remark.ts +712 -0
- package/scripts/obsidian/types.ts +31 -0
- 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
|
+
}
|