engineering blog at https://blog.tangled.sh

Compare changes

Choose any two refs to compare.

+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/@icyphox.sh/vite" 78 + "url": "https://tangled.org/oppi.li/vite" 79 79 }, 80 80 "original": { 81 81 "type": "git", 82 - "url": "https://tangled.sh/@icyphox.sh/vite" 82 + "url": "https://tangled.org/oppi.li/vite" 83 83 } 84 84 } 85 85 },
+1 -1
flake.nix
··· 4 4 inputs = { 5 5 nixpkgs.url = "github:nixos/nixpkgs"; 6 6 vite = { 7 - url = "git+https://tangled.sh/@icyphox.sh/vite"; 7 + url = "git+https://tangled.org/oppi.li/vite"; 8 8 flake = true; 9 9 }; 10 10 inter-fonts-src = {
+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 }
+90 -46
pages/blog/6-months.md
··· 2 2 atroot: true 3 3 template: 4 4 slug: 6-months 5 - title: 6 months of tangling 6 - subtitle: new domain, a quick recap, and notes on the future 7 - date: 2025-09-18 8 - image: /static/img/logo_with_text.jpeg 5 + title: 6 months of Tangled 6 + subtitle: a quick recap, and notes on the future 7 + date: 2025-10-21 8 + image: /static/img/6-months.png 9 9 authors: 10 10 - name: Anirudh 11 - email: anirudh@tangled.sh 12 - handle: icyphox.sh 11 + email: anirudh@tangled.org 12 + handle: anirudh.fi 13 13 - name: Akshay 14 - email: akshay@tangled.sh 14 + email: akshay@tangled.org 15 15 handle: oppi.li 16 - draft: true 16 + draft: false 17 17 --- 18 18 19 19 Hello Tanglers! It's been over 6 months since we first announced 20 - Tangled, so we figured we'd do a quick retrospective on what we built so 20 + Tangled, so we figured we'd do a quick retrospective of what we built so 21 21 far and what's next. 22 22 23 - But before that, we've got a big announcement! 24 - 25 - ## tangled.sh is now tangled.org! 26 - 27 - [Chris Maytag](https://bsky.app/profile/cpm5280.bsky.social) very kindly 28 - let us have the tangled.org domain! 29 - 30 - We're still in the process of migrating and there may be stray 31 - references to the .sh domain in places so bear with us. Rest assured: 32 - your current links to tangled.sh will be redirected to tangled.org. 23 + If you're new here, here's a quick overview: Tangled is a git hosting 24 + and collaboration platform built on top of the [AT 25 + Protocol](https://atproto.com). You can read a bit more about our 26 + architecture [here](/intro). 33 27 34 28 ## new logo and mascot: dolly! 35 29 36 30 Tangled finally has a logo! Designed by Akshay himself, Dolly is in 37 - reference to the first ever *cloned* mammal. 31 + reference to the first ever *cloned* mammal. For a full set of brand assets and guidelines, see our new [branding page](https://tangled.org/brand). 38 32 39 33 ![logo with text](/static/img/logo_with_text.jpeg) 40 34 41 35 With that, let's recap the major platform improvements so far! 42 36 43 - ### pull requests 37 + ## pull requests: doubling down on jujutsu 44 38 45 - One of the first major features we built was our pull requests system, 46 - which follows a unique round-based submission & review approach. This 47 - was really fun to innovate on -- it remains one of Tangled's core 48 - differentiators, and one we plan to keep improving. 39 + One of the first major features we built was our [pull requests 40 + system](/pulls), which follows a unique round-based submission & review 41 + approach. This was really fun to innovate on -- it remains one of 42 + Tangled's core differentiators, and one we plan to keep improving. 49 43 50 44 In the same vein, we're the first ever code forge to support [stacking 51 45 pull requests](/stacking) using Jujutsu! We're big fans of the tool and 52 46 we use it everyday as we hack on 53 - [core](https://tangled.sh/@tangled.sh/core). Ultimately, we think 54 - PR-based collaboration should evolve beyond the traditional model, and 55 - we're excited to keep experimenting with new ideas that make code review 56 - and contribution easier! 47 + [tangled.org/core](https://tangled.org/@tangled.org/core). 57 48 58 - ### spindle 49 + Ultimately, we think PR-based collaboration should evolve beyond the 50 + traditional model, and we're excited to keep experimenting with new 51 + ideas that make code review and contribution easier! 52 + 53 + ## spindle 59 54 60 55 CI was our most requested feature, and we spent a *lot* of time debating 61 56 how to approach it. We considered integrating with existing platforms, 62 57 but none were good fits. So we gave in to NIH and [built spindle 63 - ourselves](/ci)! This turned out great -- we could go all in on Nix and 64 - make it "atproto native". 58 + ourselves](/ci)! This allowed us to go in on Nix using Nixery to build 59 + CI images on the fly and cache them. 65 60 66 - Spindle is still early but designed to be extensible. The current 67 - Docker-based engine is limiting -- we plan to switch to micro VMs 68 - eventually. Meanwhile, if you've got ideas for other spindle backends 69 - (Kubernetes?!), we'd love to [hear from you](https://chat.tangled.sh). 61 + Spindle is still early but designed to be extensible and is AT-native. 62 + The current Docker/Nixery-based engine is limiting -- we plan to switch 63 + to micro VMs down the line to run full-fledged NixOS (and other base 64 + images). Meanwhile, if you've got ideas for other spindle backends 65 + (Kubernetes?!), we'd love to [hear from you](https://chat.tangled.org). 70 66 71 - ### XRPC APIs 67 + ## XRPC APIs 72 68 73 69 We introduced a complete migration of the knotserver to an 74 70 [XRPC](https://atproto.com/specs/xrpc) API. Alongside this, we also ··· 80 76 81 77 [lexicons]: https://tangled.sh/@tangled.sh/core/tree/master/lexicons 82 78 83 - ### issues rework 79 + ## issues rework 84 80 85 81 Issues got a major rework (and facelift) too! They are now threaded: 86 82 top-level comments with replies. This makes Q/A style discussions much ··· 88 84 89 85 ![issue thread](/static/img/issue-threading.webp) 90 86 91 - ### hosted PDS 87 + ## hosted PDS 92 88 93 89 A complaint we often recieved was the need for a Bluesky account to use 94 90 Tangled; and besides, we realised that the overlap between Bluesky users 95 91 and possible Tangled users only goes so far -- we aim to be a generic 96 - code forge after all, atproto just happens to be an implementation 92 + code forge after all, AT just happens to be an implementation 97 93 detail. 98 94 99 95 To address this, we spun up the tngl.sh PDS hosted right here in ··· 102 98 experience as a generic PDS host, but we're still working out details 103 99 around that. 104 100 101 + ## labels 102 + 103 + You can easily categorize issues and pulls via labels! There is plenty 104 + of customization available: 105 + 106 + - labels can be basic, or they can have a key and value set, for example: 107 + `wontfix` or `priority/high` 108 + - labels can be constrained to a set of values: `priority: [high medium low]` 109 + - there can be multiple labels of a given type: `reviewed-by: @oppi.li`, 110 + `reviewed-by: @anirudh.fi` 111 + 112 + The options are endless! You can access them via your repo's settings page. 113 + 114 + <div class="flex justify-center items-center gap-2"> 115 + <figure class="w-full m-0 flex flex-col items-center"> 116 + <a href="static/img/labels_vignette.webp"> 117 + <img class="my-1 w-full h-auto cursor-pointer" src="static/img/labels_vignette.webp" alt="A set of labels applied to an issue."> 118 + </a> 119 + <figcaption class="text-center">A set of labels applied to an issue.</figcaption> 120 + </figure> 121 + 122 + <figure class="w-1/3 m-0 flex flex-col items-center"> 123 + <a href="static/img/new_label_modal.png"> 124 + <img class="my-1 w-full h-auto cursor-pointer" src="static/img/new_label_modal.png" alt="Create custom key-value type labels."> 125 + </a> 126 + <figcaption class="text-center">Create custom key-value type labels.</figcaption> 127 + </figure> 128 + </div> 129 + 130 + 131 + ## notifications 132 + 133 + In-app notifications now exist! You get notifications for a variety of events now: 134 + 135 + * new issues/pulls on your repos (also for collaborators) 136 + * comments on your issues/pulls (also for collaborators) 137 + * close/reopen (or merge) of issues/pulls 138 + * new stars 139 + * new follows 140 + 141 + All of this can be fine-tuned in [/settings/notifications](https://tangled.org/settings/notifications). 142 + 143 + ![notifications](static/img/notifications.png) 144 + 145 + 105 146 ## the future 106 147 107 148 We're working on a *lot* of exciting new things and possibly some big 108 - announcements to come: 149 + announcements to come. Be on the lookout for: 109 150 110 - * labels 111 - * notifications: both in-app and emails 151 + * email notifications 112 152 * preliminary support for issue and PR search 113 - * total federation 114 - * network playback 153 + * total "atprotation" [^1] -- the last two holdouts here are repo and pull records 154 + * total federation -- i.e. supporting third-party appviews by making it 155 + reproducible 156 + * achieve complete independence from Bluesky PBC by hosting our own relay 115 157 116 - That's all for now; we'll see you in the atmosphere! 158 + That's all for now; we'll see you in the atmosphere! Meanwhile, if you'd like to contribute to projects on Tangled, make sure to check out the [good first issues page](https://tangled.org/goodfirstissues) to get started! 159 + 160 + [^1]: atprotation implies a two-way sync between the PDS and appview. Currently, pull requests and repositories are not ingested -- so writing/updating either records on your PDS will not show up on the appview.
+1 -1
pages/blog/ci.md
··· 119 119 in your terminal if you've got Docker installed: 120 120 121 121 ``` 122 - docker run nixery.dev/bash/hello-go hello 122 + docker run nixery.dev/bash/hello-go hello-go 123 123 ``` 124 124 125 125 This should output `Hello, world!`. This is running the
+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 + ![docs.tangled.org](/static/img/docs_homepage.png) 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/6-months.png

This is a binary file and will not be displayed.

static/img/docs_homepage.png

This is a binary file and will not be displayed.

static/img/labels_vignette.webp

This is a binary file and will not be displayed.

static/img/new_label_modal.png

This is a binary file and will not be displayed.

static/img/notifications.png

This is a binary file and will not be displayed.

+30 -32
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 - {{ if .Allowed }} 29 - <li class="mt-5 bg-white dark:bg-gray-800 py-4 px-6 rounded drop-shadow-sm list-none"> 30 - {{ $dateStr := .Meta.date }} 31 - {{ $date := parsedate $dateStr }} 32 - <div class="post-date py-1 mb-0 text-sm">{{ $date.Format "02 Jan, 2006" }}</div> 33 - <div> 34 - <a class="title mb-0 text-xl no-underline font-bold" href="/{{ .Meta.slug }}.html">{{ .Meta.title }}</a> 35 - {{ if .Meta.draft }} 36 - <span class="text-red-500">[draft]</span> 37 - {{ end }} 38 - <p class="italic mt-1 mb-0">{{ .Meta.subtitle }}</p> 39 - </div> 40 - </li> 41 - {{ end }} 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> 42 40 {{ end }} 43 41 </ul> 44 - </section> 45 - </main> 46 - </div> 47 - <footer class="w-full"> 48 - {{ template "partials/footer.html" }} 49 - </footer> 50 - </body> 42 + </section> 43 + </main> 44 + </div> 45 + <footer class="w-full"> 46 + {{ template "partials/footer.html" }} 47 + </footer> 48 + </body> 51 49 52 50 </html>
+5 -9
templates/partials/nav.html
··· 1 - <div class="w-full"> 2 - <div class="container mx-auto max-w-7xl px-4"> 3 - <div class="flex justify-start py-4 mb-8"> 4 - <a href="/" class="text-2xl no-underline hover:no-underline"> 5 - {{ template "fragments/logotypeSmall" }} 6 - </a> 7 - </div> 8 - </div> 9 - </div> 1 + <nav class="w-full flex items-center dark:text-white drop-shadow-sm bg-white dark:bg-gray-800 px-6 py-2 h-[44px]"> 2 + <a href="/" class="text-2xl no-underline hover:no-underline"> 3 + {{ template "fragments/logotypeSmall" }} 4 + </a> 5 + </nav>
+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>