+6
-6
flake.lock
+6
-6
flake.lock
···
69
69
"nixpkgs": "nixpkgs_2"
70
70
},
71
71
"locked": {
72
-
"lastModified": 1758224598,
73
-
"narHash": "sha256-Ai+kyEpZVPTuk0IP34kE8ZaXxhI+Z97msUFPe82k0Ic=",
72
+
"lastModified": 1768109018,
73
+
"narHash": "sha256-ePHsZ62UURGy44rkLva16RILZKI7PWcnGzyrP5Qmqt8=",
74
74
"ref": "refs/heads/master",
75
-
"rev": "e67e553dc237e41adc9ceae4d834fc02d44e4eb4",
76
-
"revCount": 96,
75
+
"rev": "0368173f7a3672916d26ac7c3183dd9998d1a514",
76
+
"revCount": 98,
77
77
"type": "git",
78
-
"url": "https://tangled.sh/@anirudh.fi/vite"
78
+
"url": "https://tangled.org/oppi.li/vite"
79
79
},
80
80
"original": {
81
81
"type": "git",
82
-
"url": "https://tangled.sh/@anirudh.fi/vite"
82
+
"url": "https://tangled.org/oppi.li/vite"
83
83
}
84
84
}
85
85
},
+1
-1
flake.nix
+1
-1
flake.nix
+856
-27
input.css
+856
-27
input.css
···
66
66
font-display: swap;
67
67
}
68
68
69
-
h1 {
70
-
@apply text-2xl;
71
-
@apply text-black;
72
-
@apply font-bold;
73
-
}
74
-
75
69
::selection {
76
70
@apply bg-yellow-400 text-black bg-opacity-30 dark:bg-yellow-600 dark:bg-opacity-50 dark:text-white;
77
71
}
···
84
78
@supports (font-variation-settings: normal) {
85
79
html {
86
80
font-feature-settings:
87
-
"ss01" 1,
88
81
"kern" 1,
89
82
"liga" 1,
90
83
"cv05" 1,
···
92
85
}
93
86
}
94
87
95
-
a {
96
-
@apply no-underline text-black hover:underline hover:text-gray-800 dark:text-white dark:hover:text-gray-300;
97
-
}
98
-
99
88
img {
100
89
@apply border border-gray-200 rounded dark:border-gray-700;
101
90
}
···
104
93
@apply border-0 dark:brightness-100 dark:opacity-100;
105
94
}
106
95
96
+
a {
97
+
@apply no-underline text-black hover:underline hover:text-gray-800 dark:text-white dark:hover:text-gray-300;
98
+
}
99
+
107
100
label {
108
-
@apply block mb-2 text-gray-900 text-sm font-bold py-2 uppercase dark:text-gray-100;
101
+
@apply block text-gray-900 text-sm font-bold py-2 uppercase dark:text-gray-100;
109
102
}
110
103
input {
111
-
@apply bg-white border border-gray-400 rounded-sm focus:ring-black p-3 dark:bg-gray-800 dark:border-gray-600 dark:text-white dark:focus:ring-gray-400;
104
+
@apply border border-gray-400 block rounded bg-gray-50 focus:ring-black p-3 dark:bg-gray-800 dark:border-gray-600 dark:text-white dark:focus:ring-gray-400;
112
105
}
113
106
textarea {
114
-
@apply bg-white border border-gray-400 rounded-sm focus:ring-black p-3 dark:bg-gray-800 dark:border-gray-600 dark:text-white dark:focus:ring-gray-400;
107
+
@apply border border-gray-400 block rounded bg-gray-50 focus:ring-black p-3 dark:bg-gray-800 dark:border-gray-600 dark:text-white dark:focus:ring-gray-400;
115
108
}
116
109
details summary::-webkit-details-marker {
117
110
display: none;
118
111
}
112
+
113
+
code {
114
+
@apply font-mono rounded bg-gray-100 dark:bg-gray-700 text-black dark:text-white;
115
+
}
119
116
}
120
117
121
118
@layer components {
122
119
.btn {
123
-
@apply relative z-10 inline-flex min-h-[30px] cursor-pointer items-center
124
-
justify-center bg-transparent px-2 pb-[0.2rem] text-base
125
-
text-gray-900 before:absolute before:inset-0 before:-z-10
126
-
before:block before:rounded-sm before:border before:border-gray-200
127
-
before:bg-white before:drop-shadow-sm
128
-
before:content-[''] hover:before:border-gray-300
129
-
hover:before:bg-gray-50
130
-
hover:before:shadow-[0_2px_2px_0_rgba(20,20,96,0.1),inset_0_-2px_0_0_#f5f5f5]
131
-
focus:outline-none focus-visible:before:outline
132
-
focus-visible:before:outline-4 focus-visible:before:outline-gray-500
133
-
active:before:shadow-[inset_0_2px_2px_0_rgba(20,20,96,0.1)];
120
+
@apply relative z-10 inline-flex min-h-[30px] cursor-pointer items-center justify-center
121
+
bg-transparent px-2 pb-[0.2rem] text-sm text-gray-900
122
+
before:absolute before:inset-0 before:-z-10 before:block before:rounded
123
+
before:border before:border-gray-200 before:bg-white
124
+
before:shadow-[inset_0_-2px_0_0_rgba(0,0,0,0.1),0_1px_0_0_rgba(0,0,0,0.04)]
125
+
before:content-[''] before:transition-all before:duration-150 before:ease-in-out
126
+
hover:before:shadow-[inset_0_-2px_0_0_rgba(0,0,0,0.15),0_2px_1px_0_rgba(0,0,0,0.06)]
127
+
hover:before:bg-gray-50
128
+
dark:hover:before:bg-gray-700
129
+
active:before:shadow-[inset_0_2px_2px_0_rgba(0,0,0,0.1)]
130
+
focus:outline-none focus-visible:before:outline focus-visible:before:outline-2 focus-visible:before:outline-gray-400
131
+
disabled:cursor-not-allowed disabled:opacity-50
132
+
dark:text-gray-100 dark:before:bg-gray-800 dark:before:border-gray-700;
133
+
}
134
+
135
+
.btn-create {
136
+
@apply btn text-white
137
+
before:bg-green-600 hover:before:bg-green-700
138
+
dark:before:bg-green-700 dark:hover:before:bg-green-800
139
+
before:border before:border-green-700 hover:before:border-green-800
140
+
focus-visible:before:outline-green-500
141
+
disabled:before:bg-green-400 dark:disabled:before:bg-green-600;
142
+
}
143
+
144
+
.prose hr {
145
+
@apply my-2;
146
+
}
147
+
148
+
.prose li:has(input) {
149
+
@apply list-none;
150
+
}
151
+
152
+
.prose ul:has(input) {
153
+
@apply pl-2;
154
+
}
155
+
156
+
.prose .heading .anchor {
157
+
@apply no-underline mx-2 opacity-0;
158
+
}
159
+
160
+
.prose .heading:hover .anchor {
161
+
@apply opacity-70;
162
+
}
163
+
164
+
.prose .heading .anchor:hover {
165
+
@apply opacity-70;
166
+
}
167
+
168
+
.prose a.footnote-backref {
169
+
@apply no-underline;
170
+
}
171
+
172
+
.prose a.mention {
173
+
@apply no-underline hover:underline font-bold;
174
+
}
175
+
176
+
.prose li {
177
+
@apply my-0 py-0;
178
+
}
179
+
180
+
.prose ul,
181
+
.prose ol {
182
+
@apply my-1 py-0;
183
+
}
184
+
185
+
.prose img {
186
+
display: inline;
187
+
margin: 0;
188
+
vertical-align: middle;
189
+
}
190
+
191
+
.prose input {
192
+
@apply inline-block my-0 mb-1 mx-1;
193
+
}
194
+
195
+
.prose input[type="checkbox"] {
196
+
@apply disabled:accent-blue-500 checked:accent-blue-500 disabled:checked:accent-blue-500;
197
+
}
198
+
199
+
/* Base callout */
200
+
details[data-callout] {
201
+
@apply border-l-4 pl-3 py-2 text-gray-800 dark:text-gray-200 my-4;
202
+
}
203
+
204
+
details[data-callout] > summary {
205
+
@apply font-bold cursor-pointer mb-1;
206
+
}
207
+
208
+
details[data-callout] > .callout-content {
209
+
@apply text-sm leading-snug;
210
+
}
211
+
212
+
/* Note (blue) */
213
+
details[data-callout="note" i] {
214
+
@apply border-blue-400 dark:border-blue-500;
215
+
}
216
+
details[data-callout="note" i] > summary {
217
+
@apply text-blue-700 dark:text-blue-400;
218
+
}
219
+
220
+
/* Important (purple) */
221
+
details[data-callout="important" i] {
222
+
@apply border-purple-400 dark:border-purple-500;
223
+
}
224
+
details[data-callout="important" i] > summary {
225
+
@apply text-purple-700 dark:text-purple-400;
226
+
}
227
+
228
+
/* Warning (yellow) */
229
+
details[data-callout="warning" i] {
230
+
@apply border-yellow-400 dark:border-yellow-500;
231
+
}
232
+
details[data-callout="warning" i] > summary {
233
+
@apply text-yellow-700 dark:text-yellow-400;
234
+
}
235
+
236
+
/* Caution (red) */
237
+
details[data-callout="caution" i] {
238
+
@apply border-red-400 dark:border-red-500;
239
+
}
240
+
details[data-callout="caution" i] > summary {
241
+
@apply text-red-700 dark:text-red-400;
242
+
}
243
+
244
+
/* Tip (green) */
245
+
details[data-callout="tip" i] {
246
+
@apply border-green-400 dark:border-green-500;
247
+
}
248
+
details[data-callout="tip" i] > summary {
249
+
@apply text-green-700 dark:text-green-400;
134
250
}
251
+
252
+
/* Optional: hide the disclosure arrow like GitHub */
253
+
details[data-callout] > summary::-webkit-details-marker {
254
+
display: none;
255
+
}
256
+
135
257
}
136
258
@layer utilities {
137
259
.error {
138
-
@apply py-1 text-red-400;
260
+
@apply py-1 text-red-400 dark:text-red-300;
139
261
}
140
262
.success {
141
-
@apply py-1 text-black;
263
+
@apply py-1 text-gray-900 dark:text-gray-100;
142
264
}
143
265
}
266
+
267
+
}
268
+
269
+
/* Background */
270
+
.bg {
271
+
color: #4c4f69;
272
+
background-color: #eff1f5;
273
+
}
274
+
/* PreWrapper */
275
+
.chroma {
276
+
color: #4c4f69;
277
+
}
278
+
/* Error */
279
+
.chroma .err {
280
+
color: #d20f39;
281
+
}
282
+
/* LineLink */
283
+
.chroma .lnlinks {
284
+
outline: none;
285
+
text-decoration: none;
286
+
color: inherit;
287
+
}
288
+
/* LineTableTD */
289
+
.chroma .lntd {
290
+
vertical-align: top;
291
+
padding: 0;
292
+
margin: 0;
293
+
border: 0;
294
+
}
295
+
/* LineTable */
296
+
.chroma .lntable {
297
+
border-spacing: 0;
298
+
padding: 0;
299
+
margin: 0;
300
+
border: 0;
301
+
}
302
+
/* LineHighlight */
303
+
.chroma .hl {
304
+
@apply bg-amber-400/30 dark:bg-amber-500/20;
305
+
}
306
+
307
+
/* LineNumbersTable */
308
+
.chroma .lnt {
309
+
white-space: pre;
310
+
-webkit-user-select: none;
311
+
user-select: none;
312
+
margin-right: 0.4em;
313
+
padding: 0 0.4em 0 0.4em;
314
+
color: #8c8fa1;
315
+
}
316
+
/* LineNumbers */
317
+
.chroma .ln {
318
+
white-space: pre;
319
+
-webkit-user-select: none;
320
+
user-select: none;
321
+
margin-right: 0.4em;
322
+
padding: 0 0.4em 0 0.4em;
323
+
color: #8c8fa1;
324
+
}
325
+
/* Line */
326
+
.chroma .line {
327
+
display: flex;
328
+
}
329
+
/* Keyword */
330
+
.chroma .k {
331
+
color: #8839ef;
332
+
}
333
+
/* KeywordConstant */
334
+
.chroma .kc {
335
+
color: #fe640b;
336
+
}
337
+
/* KeywordDeclaration */
338
+
.chroma .kd {
339
+
color: #d20f39;
340
+
}
341
+
/* KeywordNamespace */
342
+
.chroma .kn {
343
+
color: #179299;
344
+
}
345
+
/* KeywordPseudo */
346
+
.chroma .kp {
347
+
color: #8839ef;
348
+
}
349
+
/* KeywordReserved */
350
+
.chroma .kr {
351
+
color: #8839ef;
352
+
}
353
+
/* KeywordType */
354
+
.chroma .kt {
355
+
color: #d20f39;
356
+
}
357
+
/* NameAttribute */
358
+
.chroma .na {
359
+
color: #1e66f5;
360
+
}
361
+
/* NameBuiltin */
362
+
.chroma .nb {
363
+
color: #04a5e5;
364
+
}
365
+
/* NameBuiltinPseudo */
366
+
.chroma .bp {
367
+
color: #04a5e5;
368
+
}
369
+
/* NameClass */
370
+
.chroma .nc {
371
+
color: #df8e1d;
372
+
}
373
+
/* NameConstant */
374
+
.chroma .no {
375
+
color: #df8e1d;
376
+
}
377
+
/* NameDecorator */
378
+
.chroma .nd {
379
+
color: #1e66f5;
380
+
font-weight: bold;
381
+
}
382
+
/* NameEntity */
383
+
.chroma .ni {
384
+
color: #179299;
385
+
}
386
+
/* NameException */
387
+
.chroma .ne {
388
+
color: #fe640b;
389
+
}
390
+
/* NameFunction */
391
+
.chroma .nf {
392
+
color: #1e66f5;
393
+
}
394
+
/* NameFunctionMagic */
395
+
.chroma .fm {
396
+
color: #1e66f5;
397
+
}
398
+
/* NameLabel */
399
+
.chroma .nl {
400
+
color: #04a5e5;
401
+
}
402
+
/* NameNamespace */
403
+
.chroma .nn {
404
+
color: #fe640b;
405
+
}
406
+
/* NameProperty */
407
+
.chroma .py {
408
+
color: #fe640b;
409
+
}
410
+
/* NameTag */
411
+
.chroma .nt {
412
+
color: #8839ef;
413
+
}
414
+
/* NameVariable */
415
+
.chroma .nv {
416
+
color: #dc8a78;
417
+
}
418
+
/* NameVariableClass */
419
+
.chroma .vc {
420
+
color: #dc8a78;
421
+
}
422
+
/* NameVariableGlobal */
423
+
.chroma .vg {
424
+
color: #dc8a78;
425
+
}
426
+
/* NameVariableInstance */
427
+
.chroma .vi {
428
+
color: #dc8a78;
429
+
}
430
+
/* NameVariableMagic */
431
+
.chroma .vm {
432
+
color: #dc8a78;
433
+
}
434
+
/* LiteralString */
435
+
.chroma .s {
436
+
color: #40a02b;
437
+
}
438
+
/* LiteralStringAffix */
439
+
.chroma .sa {
440
+
color: #d20f39;
441
+
}
442
+
/* LiteralStringBacktick */
443
+
.chroma .sb {
444
+
color: #40a02b;
445
+
}
446
+
/* LiteralStringChar */
447
+
.chroma .sc {
448
+
color: #40a02b;
449
+
}
450
+
/* LiteralStringDelimiter */
451
+
.chroma .dl {
452
+
color: #1e66f5;
453
+
}
454
+
/* LiteralStringDoc */
455
+
.chroma .sd {
456
+
color: #9ca0b0;
457
+
}
458
+
/* LiteralStringDouble */
459
+
.chroma .s2 {
460
+
color: #40a02b;
461
+
}
462
+
/* LiteralStringEscape */
463
+
.chroma .se {
464
+
color: #1e66f5;
465
+
}
466
+
/* LiteralStringHeredoc */
467
+
.chroma .sh {
468
+
color: #9ca0b0;
469
+
}
470
+
/* LiteralStringInterpol */
471
+
.chroma .si {
472
+
color: #40a02b;
473
+
}
474
+
/* LiteralStringOther */
475
+
.chroma .sx {
476
+
color: #40a02b;
477
+
}
478
+
/* LiteralStringRegex */
479
+
.chroma .sr {
480
+
color: #179299;
481
+
}
482
+
/* LiteralStringSingle */
483
+
.chroma .s1 {
484
+
color: #40a02b;
485
+
}
486
+
/* LiteralStringSymbol */
487
+
.chroma .ss {
488
+
color: #40a02b;
489
+
}
490
+
/* LiteralNumber */
491
+
.chroma .m {
492
+
color: #fe640b;
493
+
}
494
+
/* LiteralNumberBin */
495
+
.chroma .mb {
496
+
color: #fe640b;
497
+
}
498
+
/* LiteralNumberFloat */
499
+
.chroma .mf {
500
+
color: #fe640b;
501
+
}
502
+
/* LiteralNumberHex */
503
+
.chroma .mh {
504
+
color: #fe640b;
505
+
}
506
+
/* LiteralNumberInteger */
507
+
.chroma .mi {
508
+
color: #fe640b;
509
+
}
510
+
/* LiteralNumberIntegerLong */
511
+
.chroma .il {
512
+
color: #fe640b;
513
+
}
514
+
/* LiteralNumberOct */
515
+
.chroma .mo {
516
+
color: #fe640b;
517
+
}
518
+
/* Operator */
519
+
.chroma .o {
520
+
color: #04a5e5;
521
+
font-weight: bold;
522
+
}
523
+
/* OperatorWord */
524
+
.chroma .ow {
525
+
color: #04a5e5;
526
+
font-weight: bold;
527
+
}
528
+
/* Comment */
529
+
.chroma .c {
530
+
color: #9ca0b0;
531
+
font-style: italic;
532
+
}
533
+
/* CommentHashbang */
534
+
.chroma .ch {
535
+
color: #9ca0b0;
536
+
font-style: italic;
537
+
}
538
+
/* CommentMultiline */
539
+
.chroma .cm {
540
+
color: #9ca0b0;
541
+
font-style: italic;
542
+
}
543
+
/* CommentSingle */
544
+
.chroma .c1 {
545
+
color: #9ca0b0;
546
+
font-style: italic;
547
+
}
548
+
/* CommentSpecial */
549
+
.chroma .cs {
550
+
color: #9ca0b0;
551
+
font-style: italic;
552
+
}
553
+
/* CommentPreproc */
554
+
.chroma .cp {
555
+
color: #9ca0b0;
556
+
font-style: italic;
557
+
}
558
+
/* CommentPreprocFile */
559
+
.chroma .cpf {
560
+
color: #9ca0b0;
561
+
font-weight: bold;
562
+
font-style: italic;
563
+
}
564
+
/* GenericDeleted */
565
+
.chroma .gd {
566
+
color: #d20f39;
567
+
background-color: oklch(93.6% 0.032 17.717);
568
+
}
569
+
/* GenericEmph */
570
+
.chroma .ge {
571
+
font-style: italic;
572
+
}
573
+
/* GenericError */
574
+
.chroma .gr {
575
+
color: #d20f39;
576
+
}
577
+
/* GenericHeading */
578
+
.chroma .gh {
579
+
color: #fe640b;
580
+
font-weight: bold;
581
+
}
582
+
/* GenericInserted */
583
+
.chroma .gi {
584
+
color: #40a02b;
585
+
background-color: oklch(96.2% 0.044 156.743);
586
+
}
587
+
/* GenericStrong */
588
+
.chroma .gs {
589
+
font-weight: bold;
590
+
}
591
+
/* GenericSubheading */
592
+
.chroma .gu {
593
+
color: #fe640b;
594
+
font-weight: bold;
595
+
}
596
+
/* GenericTraceback */
597
+
.chroma .gt {
598
+
color: #d20f39;
599
+
}
600
+
/* GenericUnderline */
601
+
.chroma .gl {
602
+
text-decoration: underline;
603
+
}
604
+
605
+
@media (prefers-color-scheme: dark) {
606
+
/* Background */
607
+
.bg {
608
+
color: #cad3f5;
609
+
background-color: #24273a;
610
+
}
611
+
/* PreWrapper */
612
+
.chroma {
613
+
color: #cad3f5;
614
+
}
615
+
/* Error */
616
+
.chroma .err {
617
+
color: #ed8796;
618
+
}
619
+
/* LineLink */
620
+
.chroma .lnlinks {
621
+
outline: none;
622
+
text-decoration: none;
623
+
color: inherit;
624
+
}
625
+
/* LineTableTD */
626
+
.chroma .lntd {
627
+
vertical-align: top;
628
+
padding: 0;
629
+
margin: 0;
630
+
border: 0;
631
+
}
632
+
/* LineTable */
633
+
.chroma .lntable {
634
+
border-spacing: 0;
635
+
padding: 0;
636
+
margin: 0;
637
+
border: 0;
638
+
}
639
+
/* LineHighlight */
640
+
.chroma .hl {
641
+
background-color: #494d64;
642
+
}
643
+
/* LineNumbersTable */
644
+
.chroma .lnt {
645
+
white-space: pre;
646
+
-webkit-user-select: none;
647
+
user-select: none;
648
+
margin-right: 0.4em;
649
+
padding: 0 0.4em 0 0.4em;
650
+
color: #8087a2;
651
+
}
652
+
/* LineNumbers */
653
+
.chroma .ln {
654
+
white-space: pre;
655
+
-webkit-user-select: none;
656
+
user-select: none;
657
+
margin-right: 0.4em;
658
+
padding: 0 0.4em 0 0.4em;
659
+
color: #8087a2;
660
+
}
661
+
/* Line */
662
+
.chroma .line {
663
+
display: flex;
664
+
}
665
+
/* Keyword */
666
+
.chroma .k {
667
+
color: #c6a0f6;
668
+
}
669
+
/* KeywordConstant */
670
+
.chroma .kc {
671
+
color: #f5a97f;
672
+
}
673
+
/* KeywordDeclaration */
674
+
.chroma .kd {
675
+
color: #ed8796;
676
+
}
677
+
/* KeywordNamespace */
678
+
.chroma .kn {
679
+
color: #8bd5ca;
680
+
}
681
+
/* KeywordPseudo */
682
+
.chroma .kp {
683
+
color: #c6a0f6;
684
+
}
685
+
/* KeywordReserved */
686
+
.chroma .kr {
687
+
color: #c6a0f6;
688
+
}
689
+
/* KeywordType */
690
+
.chroma .kt {
691
+
color: #ed8796;
692
+
}
693
+
/* NameAttribute */
694
+
.chroma .na {
695
+
color: #8aadf4;
696
+
}
697
+
/* NameBuiltin */
698
+
.chroma .nb {
699
+
color: #91d7e3;
700
+
}
701
+
/* NameBuiltinPseudo */
702
+
.chroma .bp {
703
+
color: #91d7e3;
704
+
}
705
+
/* NameClass */
706
+
.chroma .nc {
707
+
color: #eed49f;
708
+
}
709
+
/* NameConstant */
710
+
.chroma .no {
711
+
color: #eed49f;
712
+
}
713
+
/* NameDecorator */
714
+
.chroma .nd {
715
+
color: #8aadf4;
716
+
font-weight: bold;
717
+
}
718
+
/* NameEntity */
719
+
.chroma .ni {
720
+
color: #8bd5ca;
721
+
}
722
+
/* NameException */
723
+
.chroma .ne {
724
+
color: #f5a97f;
725
+
}
726
+
/* NameFunction */
727
+
.chroma .nf {
728
+
color: #8aadf4;
729
+
}
730
+
/* NameFunctionMagic */
731
+
.chroma .fm {
732
+
color: #8aadf4;
733
+
}
734
+
/* NameLabel */
735
+
.chroma .nl {
736
+
color: #91d7e3;
737
+
}
738
+
/* NameNamespace */
739
+
.chroma .nn {
740
+
color: #f5a97f;
741
+
}
742
+
/* NameProperty */
743
+
.chroma .py {
744
+
color: #f5a97f;
745
+
}
746
+
/* NameTag */
747
+
.chroma .nt {
748
+
color: #c6a0f6;
749
+
}
750
+
/* NameVariable */
751
+
.chroma .nv {
752
+
color: #f4dbd6;
753
+
}
754
+
/* NameVariableClass */
755
+
.chroma .vc {
756
+
color: #f4dbd6;
757
+
}
758
+
/* NameVariableGlobal */
759
+
.chroma .vg {
760
+
color: #f4dbd6;
761
+
}
762
+
/* NameVariableInstance */
763
+
.chroma .vi {
764
+
color: #f4dbd6;
765
+
}
766
+
/* NameVariableMagic */
767
+
.chroma .vm {
768
+
color: #f4dbd6;
769
+
}
770
+
/* LiteralString */
771
+
.chroma .s {
772
+
color: #a6da95;
773
+
}
774
+
/* LiteralStringAffix */
775
+
.chroma .sa {
776
+
color: #ed8796;
777
+
}
778
+
/* LiteralStringBacktick */
779
+
.chroma .sb {
780
+
color: #a6da95;
781
+
}
782
+
/* LiteralStringChar */
783
+
.chroma .sc {
784
+
color: #a6da95;
785
+
}
786
+
/* LiteralStringDelimiter */
787
+
.chroma .dl {
788
+
color: #8aadf4;
789
+
}
790
+
/* LiteralStringDoc */
791
+
.chroma .sd {
792
+
color: #6e738d;
793
+
}
794
+
/* LiteralStringDouble */
795
+
.chroma .s2 {
796
+
color: #a6da95;
797
+
}
798
+
/* LiteralStringEscape */
799
+
.chroma .se {
800
+
color: #8aadf4;
801
+
}
802
+
/* LiteralStringHeredoc */
803
+
.chroma .sh {
804
+
color: #6e738d;
805
+
}
806
+
/* LiteralStringInterpol */
807
+
.chroma .si {
808
+
color: #a6da95;
809
+
}
810
+
/* LiteralStringOther */
811
+
.chroma .sx {
812
+
color: #a6da95;
813
+
}
814
+
/* LiteralStringRegex */
815
+
.chroma .sr {
816
+
color: #8bd5ca;
817
+
}
818
+
/* LiteralStringSingle */
819
+
.chroma .s1 {
820
+
color: #a6da95;
821
+
}
822
+
/* LiteralStringSymbol */
823
+
.chroma .ss {
824
+
color: #a6da95;
825
+
}
826
+
/* LiteralNumber */
827
+
.chroma .m {
828
+
color: #f5a97f;
829
+
}
830
+
/* LiteralNumberBin */
831
+
.chroma .mb {
832
+
color: #f5a97f;
833
+
}
834
+
/* LiteralNumberFloat */
835
+
.chroma .mf {
836
+
color: #f5a97f;
837
+
}
838
+
/* LiteralNumberHex */
839
+
.chroma .mh {
840
+
color: #f5a97f;
841
+
}
842
+
/* LiteralNumberInteger */
843
+
.chroma .mi {
844
+
color: #f5a97f;
845
+
}
846
+
/* LiteralNumberIntegerLong */
847
+
.chroma .il {
848
+
color: #f5a97f;
849
+
}
850
+
/* LiteralNumberOct */
851
+
.chroma .mo {
852
+
color: #f5a97f;
853
+
}
854
+
/* Operator */
855
+
.chroma .o {
856
+
color: #91d7e3;
857
+
font-weight: bold;
858
+
}
859
+
/* OperatorWord */
860
+
.chroma .ow {
861
+
color: #91d7e3;
862
+
font-weight: bold;
863
+
}
864
+
/* Comment */
865
+
.chroma .c {
866
+
color: #6e738d;
867
+
font-style: italic;
868
+
}
869
+
/* CommentHashbang */
870
+
.chroma .ch {
871
+
color: #6e738d;
872
+
font-style: italic;
873
+
}
874
+
/* CommentMultiline */
875
+
.chroma .cm {
876
+
color: #6e738d;
877
+
font-style: italic;
878
+
}
879
+
/* CommentSingle */
880
+
.chroma .c1 {
881
+
color: #6e738d;
882
+
font-style: italic;
883
+
}
884
+
/* CommentSpecial */
885
+
.chroma .cs {
886
+
color: #6e738d;
887
+
font-style: italic;
888
+
}
889
+
/* CommentPreproc */
890
+
.chroma .cp {
891
+
color: #6e738d;
892
+
font-style: italic;
893
+
}
894
+
/* CommentPreprocFile */
895
+
.chroma .cpf {
896
+
color: #6e738d;
897
+
font-weight: bold;
898
+
font-style: italic;
899
+
}
900
+
/* GenericDeleted */
901
+
.chroma .gd {
902
+
color: #ed8796;
903
+
background-color: oklch(44.4% 0.177 26.899 / 0.5);
904
+
}
905
+
/* GenericEmph */
906
+
.chroma .ge {
907
+
font-style: italic;
908
+
}
909
+
/* GenericError */
910
+
.chroma .gr {
911
+
color: #ed8796;
912
+
}
913
+
/* GenericHeading */
914
+
.chroma .gh {
915
+
color: #f5a97f;
916
+
font-weight: bold;
917
+
}
918
+
/* GenericInserted */
919
+
.chroma .gi {
920
+
color: #a6da95;
921
+
background-color: oklch(44.8% 0.119 151.328 / 0.5);
922
+
}
923
+
/* GenericStrong */
924
+
.chroma .gs {
925
+
font-weight: bold;
926
+
}
927
+
/* GenericSubheading */
928
+
.chroma .gu {
929
+
color: #f5a97f;
930
+
font-weight: bold;
931
+
}
932
+
/* GenericTraceback */
933
+
.chroma .gt {
934
+
color: #ed8796;
935
+
}
936
+
/* GenericUnderline */
937
+
.chroma .gl {
938
+
text-decoration: underline;
939
+
}
940
+
}
941
+
942
+
actor-typeahead {
943
+
--color-background: #ffffff;
944
+
--color-border: #d1d5db;
945
+
--color-shadow: #000000;
946
+
--color-hover: #f9fafb;
947
+
--color-avatar-fallback: #e5e7eb;
948
+
--radius: 0.0;
949
+
--padding-menu: 0.0rem;
950
+
z-index: 1000;
951
+
}
952
+
953
+
actor-typeahead::part(handle) {
954
+
color: #111827;
955
+
}
956
+
957
+
actor-typeahead::part(menu) {
958
+
box-shadow: 0 4px 6px -1px rgb(0 0 0 / 0.1), 0 2px 4px -2px rgb(0 0 0 / 0.1);
959
+
}
960
+
961
+
@media (prefers-color-scheme: dark) {
962
+
actor-typeahead {
963
+
--color-background: #1f2937;
964
+
--color-border: #4b5563;
965
+
--color-shadow: #000000;
966
+
--color-hover: #374151;
967
+
--color-avatar-fallback: #4b5563;
968
+
}
969
+
970
+
actor-typeahead::part(handle) {
971
+
color: #f9fafb;
972
+
}
144
973
}
+1
-1
pages/blog/ci.md
+1
-1
pages/blog/ci.md
+241
pages/blog/docs.md
+241
pages/blog/docs.md
···
1
+
---
2
+
atroot: true
3
+
template:
4
+
slug: docs
5
+
title: why we rolled our own documentation site
6
+
subtitle: you don't need mintlify
7
+
date: 2026-01-06
8
+
authors:
9
+
- name: Akshay
10
+
email: akshay@tangled.org
11
+
handle: oppi.li
12
+
draft: true
13
+
---
14
+
15
+
We recently organized our documentation and put it up on
16
+
https://docs.tangled.org, using just pandoc. For several
17
+
reasons, using pandoc to roll your own static sites is more
18
+
than sufficient for small projects.
19
+
20
+

21
+
22
+
## requirements
23
+
24
+
- Lives in [our
25
+
monorepo](https://tangled.org/tangled.org/core).
26
+
- No JS: a collection of pages containing just text
27
+
should not require JS to view!
28
+
- Searchability: in practice, documentation engines that
29
+
come bundled with a search-engine have always been lack
30
+
lustre. I tend to Ctrl+F or use an actual search engine in
31
+
most scenarios.
32
+
- Low complexity: building, testing, deploying should be
33
+
easy.
34
+
- Easy to style
35
+
36
+
## evaluating the ecosystem
37
+
38
+
I took the time to evaluate several documentation engine
39
+
solutions:
40
+
41
+
- [Mintlify](https://www.mintlify.com/): It is quite obvious
42
+
from their homepage that mintlify is performing an AI
43
+
pivot for the sake of doing so.
44
+
- [Docusaurus](https://docusaurus.io/): The generated
45
+
documentation site is quite nice, but the value of pages
46
+
being served as a full-blown React SPA is questionable.
47
+
- [MkDocs](https://www.mkdocs.org/): Works great with JS
48
+
disabled, however the table of contents needs to be
49
+
maintained via `mkdocs.yml`, which can be quite tedious.
50
+
- [MdBook](https://rust-lang.github.io/mdBook/index.html):
51
+
As above, you need a `SUMMARY.md` file to control the
52
+
table-of-contents.
53
+
54
+
MkDocs and MdBook are still on my radar however, in case we
55
+
need a bigger feature set.
56
+
57
+
## using pandoc
58
+
59
+
[pandoc](https://pandoc.org/) is a wonderfully customizable
60
+
markup converter. It provides a "chunkedhtml" output format,
61
+
which is perfect for generating documentation sites. Without
62
+
any customization,
63
+
[this](https://pandoc.org/demo/example33/) is the generated
64
+
output, for this [markdown file
65
+
input](https://pandoc.org/demo/MANUAL.txt).
66
+
67
+
- You get an autogenerated TOC based on the document layout
68
+
- Each section is turned into a page of its own
69
+
70
+
Massaging pandoc to work for us was quite straightforward:
71
+
72
+
- I first combined all our individual markdown files into
73
+
[one big
74
+
`DOCS.md`](https://tangled.org/tangled.org/core/blob/master/docs/DOCS.md)
75
+
file.
76
+
- Modified the [default
77
+
template](https://github.com/jgm/pandoc-templates/blob/master/default.chunkedhtml)
78
+
to put the TOC on every page, to form a "sidebar", see
79
+
[`docs/template.html`](https://tangled.org/tangled.org/core/blob/master/docs/template.html)
80
+
- Inserted tailwind `prose` classes where necessary, such
81
+
that markdown content is rendered the same way between
82
+
`tangled.org` and `docs.tangled.org`
83
+
84
+
Generating the docs is done with one pandoc command:
85
+
86
+
```bash
87
+
pandoc docs/DOCS.md \
88
+
-o out/ \
89
+
-t chunkedhtml \
90
+
--variable toc \
91
+
--toc-depth=2 \
92
+
--css=docs/stylesheet.css \
93
+
--chunk-template="%i.html" \
94
+
--highlight-style=docs/highlight.theme \
95
+
--template=docs/template.html
96
+
```
97
+
98
+
## avoiding javascript
99
+
100
+
The "sidebar" style table-of-contents needs to be collapsed
101
+
on mobile displays. Most of the engines I evaluated seem to
102
+
require JS to collapse and expand the sidebar, with MkDocs
103
+
being the outlier, it uses a checkbox with the
104
+
[`:checked`](https://developer.mozilla.org/en-US/docs/Web/CSS/Reference/Selectors/:checked)
105
+
pseudo-class trick to avoid JS.
106
+
107
+
The other ways to do this are:
108
+
109
+
- Use `<details` and `<summary>`: this is definitely a
110
+
"hack", clicking outside the sidebar does not collapse it.
111
+
Using Ctrl+F or "Find in page" still works through the
112
+
details tag though.
113
+
- Use the new `popover` API: this seems like the perfect fit
114
+
for a "sidebar" component.
115
+
116
+
The bar at the top includes a button to trigger the popover:
117
+
118
+
```html
119
+
<button popovertarget="toc-popover">Table of Contents</button>
120
+
```
121
+
122
+
And a `fixed` position div includes the TOC itself:
123
+
124
+
```html
125
+
<div id="toc-popover" popover class="fixed top-0">
126
+
<ul>
127
+
Quick Start
128
+
<li>...</li>
129
+
<li>...</li>
130
+
<li>...</li>
131
+
</ul>
132
+
</div>
133
+
```
134
+
135
+
The TOC is scrollable independently and can be collapsed by
136
+
clicking anywhere on the screen outside the sidebar.
137
+
Searching for content in the page via "Find in page" does
138
+
not show any results that are present in the popover
139
+
however. The collapsible TOC is only available on smaller
140
+
viewports, the TOC is not hidden on larger viewports.
141
+
142
+
## search
143
+
144
+
There is native search on the site for now. Taking
145
+
inspiration from https://htmx.org's search bar, our search
146
+
bar also simply redirects to Google:
147
+
148
+
```
149
+
<form action="https://google.com/search">
150
+
<input type="hidden" name="q" value="+[inurl:https://docs.tangled.org]">
151
+
...
152
+
</form>
153
+
```
154
+
155
+
I mentioned earlier that Ctrl+F has typically worked better
156
+
for me than, say, the search engine provided by Docusaurus.
157
+
To that end, the same docs have been exported to a ["single
158
+
page" format](https://docs.tangled.org/single-page.html), by
159
+
just removing the `chunkedhtml` related options:
160
+
161
+
```diff
162
+
pandoc docs/DOCS.md \
163
+
-o out/ \
164
+
- -t chunkedhtml \
165
+
--variable toc \
166
+
--toc-depth=2 \
167
+
--css=docs/stylesheet.css \
168
+
- --chunk-template="%i.html" \
169
+
--highlight-style=docs/highlight.theme \
170
+
--template=docs/template.html
171
+
```
172
+
173
+
With all the content on a single page, it is trivial to
174
+
search through the entire site with the browser. If the docs
175
+
do outgrow this, I will consider other options!
176
+
177
+
## building and deploying
178
+
179
+
We use [nix](https://nixos.org) and
180
+
[colmena](https://colmena.cli.rs/) to build and deploy all
181
+
Tangled services. A nix derivation to [build the
182
+
documentation](https://tangled.org/tangled.org/core/blob/master/nix/pkgs/docs.nix)
183
+
site is written very easily with the `runCommandLocal`
184
+
helper:
185
+
186
+
```nix
187
+
runCommandLocal "docs" {} ''
188
+
.
189
+
.
190
+
.
191
+
${pandoc}/bin/pandoc ${src}/docs/DOCS.md ...
192
+
.
193
+
.
194
+
.
195
+
''
196
+
```
197
+
198
+
The nixos machine is configured to serve the site [via
199
+
nginx](https://tangled.org/tangled.org/infra/blob/master/hosts/nixery/services/nginx.nix#L7):
200
+
201
+
```nix
202
+
services.nginx = {
203
+
enable = true;
204
+
virtualHosts = {
205
+
"docs.tangled.org" = {
206
+
root = "${tangled-pkgs.docs}";
207
+
locations."/" = {
208
+
tryFiles = "$uri $uri/ =404";
209
+
index = "index.html";
210
+
};
211
+
};
212
+
};
213
+
};
214
+
```
215
+
216
+
And deployed using `colmena`:
217
+
218
+
```bash
219
+
nix run nixpkgs#colmena -- apply
220
+
```
221
+
222
+
To update the site, I first run:
223
+
224
+
```bash
225
+
nix flake update tangled
226
+
```
227
+
228
+
Which bumps the `tangled` flake input, and thus
229
+
`tangled-pkgs.docs`. The above `colmena` invocation applies
230
+
the changes to the machine serving the site.
231
+
232
+
## notes
233
+
234
+
Going homegrown has made it a lot easier to style the
235
+
documentation site to match the main site. Unfortunately
236
+
there are still a few discrepancies between pandoc's
237
+
markdown rendering and
238
+
[goldmark's](https://pkg.go.dev/github.com/yuin/goldmark/)
239
+
markdown rendering (which is what we use in Tangled). We may
240
+
yet roll our own SSG,
241
+
[TigerStyle](https://tigerbeetle.com/blog/2025-02-27-why-we-designed-tigerbeetles-docs-from-scratch/)!
static/img/docs_homepage.png
static/img/docs_homepage.png
This is a binary file and will not be displayed.
+30
-30
templates/index.html
+30
-30
templates/index.html
···
10
10
{{ .Meta.title }}
11
11
</title>
12
12
13
-
<body class="bg-slate-100 dark:bg-gray-900 flex flex-col min-h-screen">
14
-
{{ template "partials/nav.html" }}
15
-
<div class="prose dark:prose-invert mx-auto px-1 pt-4 flex-grow flex flex-col container">
16
-
<main>
17
-
<header class="px-6">
18
-
<h1 class="mb-0">{{ index .Meta "title" }}</h1>
19
-
<h2 class="font-light mt-1 mb-0 text-lg">{{ index .Meta "subtitle" }}</h2>
20
-
</header>
13
+
<body class="bg-slate-100 dark:bg-gray-900 flex flex-col items-center min-h-screen">
14
+
{{ template "partials/nav.html" }}
15
+
<div class="px-1 pt-4 flex-grow flex flex-col w-full max-w-[75ch]">
16
+
<main>
17
+
<header class="px-6">
18
+
<h1 class="mb-0 text-2xl font-bold text-black dark:text-white">{{ index .Meta "title" }}</h1>
19
+
<h2 class="font-light text-gray-600 dark:text-gray-400 mt-1 mb-0 text-lg">{{ index .Meta "subtitle" }}</h2>
20
+
</header>
21
21
22
-
{{ .Body }}
22
+
{{ .Body }}
23
23
24
-
<section class="py-4">
25
-
<ul class="px-0">
24
+
<section class="py-4">
25
+
<ul class="px-0 space-y-4">
26
26
{{ $posts := .Extra.blog }}
27
27
{{ range $posts }}
28
-
<li class="mt-5 bg-white dark:bg-gray-800 py-4 px-6 rounded drop-shadow-sm list-none">
29
-
{{ $dateStr := .Meta.date }}
30
-
{{ $date := parsedate $dateStr }}
31
-
<div class="post-date py-1 mb-0 text-sm">{{ $date.Format "02 Jan, 2006" }}</div>
32
-
<div>
33
-
<a class="title mb-0 text-xl no-underline font-bold" href="/{{ .Meta.slug }}.html">{{ .Meta.title }}</a>
34
-
{{ if .Meta.draft }}
35
-
<span class="text-red-500">[draft]</span>
36
-
{{ end }}
37
-
<p class="italic mt-1 mb-0">{{ .Meta.subtitle }}</p>
38
-
</div>
39
-
</li>
28
+
<li class="bg-white dark:bg-gray-800 py-4 px-6 rounded drop-shadow-sm list-none">
29
+
{{ $dateStr := .Meta.date }}
30
+
{{ $date := parsedate $dateStr }}
31
+
<div class="post-date py-1 mb-0 text-sm text-gray-600 dark:text-gray-400">{{ $date.Format "02 Jan, 2006" }}</div>
32
+
<div>
33
+
<a class="title mb-0 text-xl no-underline font-bold" href="/{{ .Meta.slug }}.html">{{ .Meta.title }}</a>
34
+
{{ if .Meta.draft }}
35
+
<span class="text-red-500">[draft]</span>
36
+
{{ end }}
37
+
<p class="italic mt-1 mb-0 text-gray-600 dark:text-gray-400">{{ .Meta.subtitle }}</p>
38
+
</div>
39
+
</li>
40
40
{{ end }}
41
41
</ul>
42
-
</section>
43
-
</main>
44
-
</div>
45
-
<footer class="w-full">
46
-
{{ template "partials/footer.html" }}
47
-
</footer>
48
-
</body>
42
+
</section>
43
+
</main>
44
+
</div>
45
+
<footer class="w-full">
46
+
{{ template "partials/footer.html" }}
47
+
</footer>
48
+
</body>
49
49
50
50
</html>
+10
-2
templates/text.html
+10
-2
templates/text.html
···
5
5
<meta name="description" content="{{ index .Meta "subtitle" }}">
6
6
<meta property="og:title" content="{{ .Meta.title }}" />
7
7
<meta property="og:description" content="{{ .Meta.subtitle }}" />
8
-
<meta property="og:url" content="https://blog.tangled.sh/{{ .Meta.slug }}" />
9
-
<meta property="og:image" content="{{ .Meta.image }}" />
8
+
<meta property="og:url" content="https://blog.tangled.org/{{ .Meta.slug }}" />
9
+
<meta property="og:image" content="https://blog.tangled.org{{ .Meta.image }}" />
10
10
<meta property="og:type" content="website" />
11
+
<meta property="og:image:width" content="1200" />
12
+
<meta property="og:image:height" content="630" />
13
+
14
+
<meta name="twitter:card" content="summary_large_image" />
15
+
<meta name="twitter:title" content="{{ .Meta.title }}" />
16
+
<meta name="twitter:description" content="{{ .Meta.subtitle }}" />
17
+
<meta name="twitter:image" content="https://blog.tangled.org{{ .Meta.image }}" />
18
+
11
19
<title>
12
20
{{ index .Meta "title" }}
13
21
</title>