A vibe coded tangled fork which supports pijul.
at 90571506ff21b14e9226b941bed0baa928d99ccd 619 lines 29 kB view raw
1{{ define "title" }}tangled &middot; tightly-knit social coding{{ end }} 2 3{{ define "extrameta" }} 4 <!-- Open Graph Meta Tags --> 5 <meta property="og:title" content="tangled · tightly-knit social coding" /> 6 <meta property="og:type" content="website" /> 7 <meta property="og:url" content="https://tangled.org" /> 8 <meta property="og:description" content="The next-generation social coding platform." /> 9 <meta property="og:image" content="https://assets.tangled.network/tangled_og.png" /> 10 <meta property="og:image:width" content="1200" /> 11 <meta property="og:image:height" content="630" /> 12 13 <!-- Twitter Card Meta Tags --> 14 <meta name="twitter:card" content="summary_large_image" /> 15 <meta name="twitter:title" content="Tangled" /> 16 <meta name="twitter:description" content="The next-generation social coding platform." /> 17 <meta name="twitter:image" content="https://assets.tangled.network/tangled_og.png" /> 18 19 <!-- Additional SEO --> 20 <meta name="description" content="The next-generation social coding platform. Host repos on your infrastructure with knots, use stacked pull requests, and run CI with spindles." /> 21 <link rel="canonical" href="https://tangled.org" /> 22{{ end }} 23 24 25{{ define "content" }} 26 <div class="flex flex-col gap-24 md:gap-40 my-24 md:my-32"> 27 {{ template "timeline/fragments/hero" . }} 28 {{ template "timeline/fragments/preview" . }} 29 {{ template "features1" . }} 30 {{ template "features2" . }} 31 {{ template "recentUpdates" . }} 32 {{ template "community" . }} 33 </div> 34{{ end }} 35 36{{ block "topbarLayout" . }} 37 <header class="max-w-screen-xl mx-auto w-full col-span-full md:col-span-1 md:col-start-2" style="z-index: 20;"> 38 {{ if .LoggedInUser }} 39 <div id="upgrade-banner" 40 hx-get="/upgradeBanner" 41 hx-trigger="load" 42 hx-swap="innerHTML"> 43 </div> 44 {{ end }} 45 {{ template "layouts/fragments/topbar" . }} 46 </header> 47{{ end }} 48 49{{ block "footerLayout" . }} 50 <footer class="z-10"> 51 {{ template "layouts/fragments/footer" . }} 52 </footer> 53{{ end }} 54 55{{ block "bodyClasses" . }} 56 bg-transparent bg-gradient-to-b from-white to-slate-100 dark:bg-none dark:bg-gray-900 57{{ end }} 58 59{{ block "mainLayout" . }} 60 <div class="flex-grow relative"> 61 <div 62 class="absolute opacity-50 dark:opacity-5 inset-x-0 top-0 bottom-[-50px] md:bottom-[-150px] pointer-events-none bg-[url('https://assets.tangled.network/yarn_ball.svg')] bg-no-repeat bg-right-bottom bg-[length:500px] md:bg-[length:1000px] w-full"> 63 </div> 64 <div class="max-w-screen-xl mx-auto flex flex-col gap-4 relative z-10"> 65 {{ block "contentLayout" . }} 66 <main> 67 {{ block "content" . }}{{ end }} 68 </main> 69 {{ end }} 70 71 {{ block "contentAfterLayout" . }} 72 <main> 73 {{ block "contentAfter" . }}{{ end }} 74 </main> 75 {{ end }} 76 </div> 77 </div> 78{{ end }} 79 80{{ define "features1" }} 81 {{ $labelStyle := "normal-case cursor-pointer w-auto md:w-full p-4 md:px-6 rounded bg-white dark:bg-gray-800 font-medium text-base md:text-lg opacity-50 border border-gray-200 dark:border-gray-700 relative overflow-hidden" }} 82 {{ $spanStyle := "z-10 items-center justify-between gap-2 w-full" }} 83 {{ $connectorStyle := "w-0.5 h-6 bg-gray-300 dark:bg-gray-600 opacity-0 mx-auto" }} 84 {{ $contentStyle := "hidden bg-white dark:bg-gray-800 rounded shadow-sm p-6 border border-gray-200 dark:border-gray-700 grid-cols-1 md:grid-cols-2" }} 85 {{ $progressOverlayStyle := "absolute inset-0 bg-gray-600/10 dark:bg-gray-100/10 w-0 transition-none" }} 86 87 <style> 88 @media (max-width: 768px) { 89 .features-grid:has(#feature-prs:checked) { 90 grid-template-columns: 1fr auto auto; 91 } 92 93 .features-grid:has(#feature-knots:checked) { 94 grid-template-columns: auto 1fr auto; 95 } 96 97 .features-grid:has(#feature-spindles:checked) { 98 grid-template-columns: auto auto 1fr; 99 } 100 101 #feature-prs:checked ~ label[for="feature-prs"] .label-text, 102 #feature-knots:checked ~ label[for="feature-knots"] .label-text, 103 #feature-spindles:checked ~ label[for="feature-spindles"] .label-text { 104 display: inline-flex !important; 105 } 106 #feature-prs:checked ~ label[for="feature-prs"] .icon-only, 107 #feature-knots:checked ~ label[for="feature-knots"] .icon-only, 108 #feature-spindles:checked ~ label[for="feature-spindles"] .icon-only { 109 display: none !important; 110 } 111 } 112 </style> 113 114 <div class="features-grid w-full grid grid-cols-3 gap-x-6 px-2"> 115 <input type="radio" id="feature-prs" name="feature" class="peer/prs hidden" checked /> 116 <input type="radio" id="feature-knots" name="feature" class="peer/knots hidden" /> 117 <input type="radio" id="feature-spindles" name="feature" class="peer/spindles hidden" /> 118 119 <label for="feature-prs" class="{{ $labelStyle }} peer-checked/prs:opacity-100 peer-checked/prs:shadow-sm"> 120 <span class="label-text hidden md:inline-flex {{ $spanStyle }}">A better way to review {{ i "git-pull-request" "size-5" }}</span> 121 <span class="icon-only inline-flex md:hidden {{ $spanStyle }}">{{ i "git-pull-request" "size-5" }}</span> 122 <div class="{{ $progressOverlayStyle }}" data-progress="prs"></div> 123 </label> 124 125 <label for="feature-knots" class="{{ $labelStyle }} peer-checked/knots:opacity-100 peer-checked/knots:shadow-sm"> 126 <span class="label-text hidden md:inline-flex {{ $spanStyle }}">Completely self-hostable {{ i "hard-drive" "size-5" }}</span> 127 <span class="icon-only inline-flex md:hidden {{ $spanStyle }}">{{ i "hard-drive" "size-5" }}</span> 128 <div class="{{ $progressOverlayStyle }}" data-progress="knots"></div> 129 </label> 130 131 <label for="feature-spindles" class="{{ $labelStyle }} peer-checked/spindles:opacity-100 peer-checked/spindles:shadow-sm"> 132 <span class="label-text hidden md:inline-flex {{ $spanStyle }}">Quick and easy CI {{ i "layers-2" "size-5" }}</span> 133 <span class="icon-only inline-flex md:hidden {{ $spanStyle }}">{{ i "layers-2" "size-5" }}</span> 134 <div class="{{ $progressOverlayStyle }}" data-progress="spindles"></div> 135 </label> 136 137 <div class="{{ $connectorStyle }} peer-checked/prs:opacity-100"></div> 138 <div class="{{ $connectorStyle }} peer-checked/knots:opacity-100"></div> 139 <div class="{{ $connectorStyle }} peer-checked/spindles:opacity-100"></div> 140 141 {{ $titleStyle := "text-xl md:text-2xl font-bold my-2 text-gray-900 dark:text-gray-100" }} 142 {{ $textContentStyle := "pb-6 md:p-6 md:pr-12 md:text-lg" }} 143 {{ $imgContentStyle := "w-full p-6 min-h-96 min-h-[400px] md:min-h-[500px] rounded overflow-hidden place-content-center bg-gradient-to-b from-slate-50 to-slate-100 dark:from-gray-800 dark:to-gray-900 border border-gray-200 dark:border-gray-700" }} 144 145 <div class="col-span-3 {{ $contentStyle }} peer-checked/prs:grid "> 146 <div class="{{ $textContentStyle }} flex flex-col gap-4 md:gap-8 place-self-center"> 147 <section> 148 <h1 class="{{ $titleStyle }}">Stacked PRs</h1> 149 <p> 150 Break down large features into small, reviewable chunks. Stack pull 151 requests on top of each other and ship faster with better code 152 reviews. Tangled natively supports stacking using Jujutsu <a 153 href="https://docs.jj-vcs.dev/latest/glossary/#change-id" 154 class="underline">Change-Ids</a>. 155 </p> 156 </section> 157 <section> 158 <h1 class="{{ $titleStyle }}">Round-based review</h1> 159 <p> 160 Pull-requests in Tangled are round-based, which means, each 161 submission is "immutable". To update a PR, the author must start a 162 second revision or "round". As a reviewer, you never have to worry 163 about a PR changing <em>during</em> a review! 164 </p> 165 </section> 166 </div> 167 <div class="{{ $imgContentStyle }} pl-24 md:pl-40"> 168 <div class="flex flex-col" style="transform: perspective(1000px) rotateX(15deg)"> 169 {{ $mockPRs := list 170 (dict "Id" 941 "Title" "spindle/nixery: update setup command docs" "State" "open" ) 171 (dict "Id" 940 "Title" "appview/pulls: fix search not updating count of pull requests" "State" "open" ) 172 (dict "Id" 939 "Title" "appview/pages: improved seo tags for home, repo and profile" "State" "open" ) 173 (dict "Id" 938 "Title" "appview/state: update robots.txt" "State" "merged" ) 174 (dict "Id" 937 "Title" "appview/profile: show dummy profile when no tangled profile" "State" "merged" ) 175 (dict "Id" 936 "Title" "appview/pipelines: fix incorrect totals" "State" "merged" ) 176 }} 177 {{ range $index, $pr := $mockPRs }} 178 {{ $scale := 1.4 }} 179 {{ if gt $index 0 }} 180 {{ $scale = subf64 1.4 (mulf64 (f64 $index) 0.04) }} 181 {{ if lt $scale 1.0 }}{{ $scale = 1.0 }}{{ end }} 182 {{ end }} 183 {{ $zIndex := sub 100 $index }} 184 {{ template "mockPRRow" (dict "PR" $pr "Scale" $scale "ZIndex" $zIndex) }} 185 {{ end }} 186 </div> 187 </div> 188 </div> 189 190 <div class="col-span-3 {{ $contentStyle }} peer-checked/knots:grid"> 191 <div class="{{ $textContentStyle }} flex flex-col gap-4 md:gap-8 place-self-center"> 192 <section> 193 <h1 class="{{ $titleStyle }}">Knots</h1> 194 <p> 195 Host your repositories on your own infrastructure with <a 196 href="https://docs.tangled.org/knot-self-hosting-guide.html#knot-self-hosting-guide" class="underline">knots</a>. 197 Knots are lightweight git repository hosts that syndicate git 198 operations across the network. You can setup a knot server on a 199 machine as small as a Raspberry Pi! 200 <br> 201 <br> 202 If you want to try Tangled without self-hosting, fear not! All users 203 are added to our hosted knot by default. 204 </p> 205 </section> 206 <section> 207 <h1 class="{{ $titleStyle }}">Spindles</h1> 208 <p> 209 Host CI runners on your own infrastructure with <a 210 href="https://docs.tangled.org/spindles.html#self-hosting-guide" class="underline">spindles</a>. 211 Spindles are responsible for queuing up CI jobs and syndicating 212 pipeline statuses to the network. Presently, the task of sandboxing 213 workflows and caching dependencies is delegated to Docker. 214 </p> 215 </section> 216 </div> 217 {{ $planetWrapperStyle := "absolute left-1/2 bottom-0 flex items-start justify-center -translate-x-1/2 translate-y-1/2" }} 218 {{ $orbitStyle := "absolute left-1/2 bottom-0 rounded-full border-2 border-gray-200 dark:border-gray-700 -translate-x-1/2 translate-y-1/2 shadow-lg" }} 219 {{ $planetStyle := "-mt-8 size-16 md:-mt-10 md:size-20 rounded-full shadow-sm border flex items-center justify-center" }} 220 <div class="{{ $imgContentStyle }} min-h-[500px] relative"> 221 <div class="size-[250px] {{ $orbitStyle }}"></div> 222 <div class="size-[500px] {{ $orbitStyle }}"></div> 223 <div class="size-[750px] {{ $orbitStyle }}"></div> 224 225 <!-- planets on inner orbit --> 226 <div class="{{ $planetWrapperStyle }} size-[250px] rotate-[12deg]"> 227 <div class="{{ $planetStyle }} -rotate-[12deg] bg-blue-100 dark:bg-blue-900 border-blue-500 dark:border-blue-500"> 228 {{ i "server" "size-8" "text-blue-500" "dark:text-blue-500" }} 229 </div> 230 </div> 231 232 <!-- Planets on middle orbit --> 233 <div class="{{ $planetWrapperStyle }} size-[500px] -rotate-[32deg]"> 234 <div class="{{ $planetStyle }} rotate-[32deg] bg-green-100 dark:bg-green-900 border-green-500 dark:border-green-500"> 235 {{ i "hard-drive" "size-8" "text-green-500" "dark:text-green-500" }} 236 </div> 237 </div> 238 <div class="{{ $planetWrapperStyle }} size-[500px] rotate-[24deg]"> 239 <div class="{{ $planetStyle }} -rotate-[24deg] bg-amber-100 dark:bg-amber-900 border-amber-500 dark:border-amber-500"> 240 {{ i "server" "size-8" "text-amber-500" "dark:text-amber-500" }} 241 </div> 242 </div> 243 244 <!-- Planets on outer orbit --> 245 <div class="{{ $planetWrapperStyle }} size-[750px] -rotate-[40deg]"> 246 <div class="{{ $planetStyle }} rotate-[40deg] bg-blue-100 dark:bg-blue-900 border-blue-500 dark:border-blue-500"> 247 {{ i "server" "size-8" "text-blue-500" "dark:text-blue-500" }} 248 </div> 249 </div> 250 <div class="{{ $planetWrapperStyle }} size-[750px] rotate-[3deg]"> 251 <div class="{{ $planetStyle }} -rotate-[3deg] bg-green-100 dark:bg-green-900 border-green-500 dark:border-green-500"> 252 {{ i "router" "size-8" "text-green-500" "dark:text-green-500" }} 253 </div> 254 </div> 255 <div class="{{ $planetWrapperStyle }} size-[750px] rotate-[44deg]"> 256 <div class="{{ $planetStyle }} -rotate-[44deg] bg-amber-100 dark:bg-amber-900 border-amber-500 dark:border-amber-500"> 257 {{ i "database" "size-8" "text-amber-500" "dark:text-amber-500" }} 258 </div> 259 </div> 260 </div> 261 </div> 262 263 <div class="col-span-3 {{ $contentStyle }} peer-checked/spindles:grid"> 264 <div class="{{ $textContentStyle }} flex flex-col gap-4 md:gap-8 place-self-center"> 265 <section> 266 <h1 class="{{ $titleStyle }}">Nix-powered CI</h1> 267 <p> 268 Pick and choose dependencies for your CI pipelines from <a 269 href="https://docs.tangled.org/spindles.html#dependencies" 270 class="underline"><code>nixpkgs</code></a>, one of the biggest 271 package repositories. 272 <br> 273 <br> 274 All dependencies and workflow images are cached using <a 275 href="https://nixery.dev/">nixery</a>. Subsuquent runs of your CI 276 pipeline will load all dependencies almost instantly. 277 </p> 278 </section> 279 <section> 280 <div class="flex items-center gap-2 my-4"> 281 <h1 class="{{ $titleStyle }}">Customizable Engines</h1> 282 <span class="text-sm text-blue-500 dark:text-blue-300 bg-blue-100 dark:bg-blue-900/50 rounded p-1">coming soon</span> 283 </div> 284 <p> 285 We know nix is not for everybody! Spindles are built to have 286 swappable engines, on our roadmap are Docker and Firecracker based 287 engines. 288 </p> 289 </section> 290 </div> 291 <div class="{{ $imgContentStyle }} flex items-end pb-0"> 292 <div class="w-full mx-auto border-t border-l border-r border-gray-200 dark:border-gray-700 rounded-t overflow-hidden shadow-lg"> 293 <div class="bg-white dark:bg-gray-800 px-4 py-2 border-b border-gray-200 dark:border-gray-700 flex items-center gap-2 justify-between"> 294 <span class="font-mono text-gray-600 dark:text-gray-400 text-sm md:text-lg">.tangled/workflows/test.yml</span> 295 <div class="flex items-center gap-2"> 296 <div class="size-3 rounded-full bg-red-500"></div> 297 <div class="size-3 rounded-full bg-yellow-500"></div> 298 <div class="size-3 rounded-full bg-green-500"></div> 299 </div> 300 </div> 301 <div class="bg-white dark:bg-gray-800 p-4 overflow-x-auto text-sm md:text-lg"> 302 {{ $yamlContent := `when: 303 - event: push 304 branch: main 305 306dependencies: 307 nixpkgs: 308 - go 309 - gcc 310 311environment: 312 CGO_ENABLED: 1 313 314steps: 315 - name: run all tests 316 command: go test -v ./...` }} 317 <div class="whitespace-pre">{{ code $yamlContent ".tangled/workflows/test.yml" | escapeHtml }}</div> 318 </div> 319 </div> 320 </div> 321 </div> 322 </div> 323 324 <script> 325 document.addEventListener('DOMContentLoaded', function() { 326 const featureIds = ['feature-prs', 'feature-knots', 'feature-spindles']; 327 const progressNames = ['prs', 'knots', 'spindles']; 328 let currentIndex = 0; 329 let timerInterval = null; 330 331 function stopTimer() { 332 if (timerInterval) { 333 clearInterval(timerInterval); 334 timerInterval = null; 335 } 336 document.querySelectorAll('[data-progress]').forEach(el => { 337 el.classList.remove('animate-progress'); 338 }); 339 } 340 341 const firstProgress = document.querySelector('[data-progress="prs"]'); 342 if (firstProgress) { 343 firstProgress.classList.add('animate-progress'); 344 } 345 346 document.querySelectorAll('label[for^="feature-"]').forEach(label => { 347 label.addEventListener('click', stopTimer); 348 }); 349 350 timerInterval = setInterval(function() { 351 document.querySelectorAll('[data-progress]').forEach(el => { 352 el.classList.remove('animate-progress'); 353 void el.offsetWidth; // force reflow 354 }); 355 356 currentIndex = (currentIndex + 1) % featureIds.length; 357 document.getElementById(featureIds[currentIndex]).checked = true; 358 359 const activeProgress = document.querySelector('[data-progress="' + progressNames[currentIndex] + '"]'); 360 if (activeProgress) { 361 activeProgress.classList.add('animate-progress'); 362 } 363 }, 5000); 364 }); 365 </script> 366{{ end }} 367 368{{ define "mockPRRow" }} 369 {{ $pr := .PR }} 370 <div class="flex text-sm items-center justify-between w-full bg-white dark:bg-gray-800 border border-gray-200 dark:border-gray-700 rounded p-3 shadow relative" style="transform: scale({{ .Scale }}); transform-origin: top center; z-index: {{ .ZIndex }};"> 371 <div class="flex items-center gap-2 min-w-0 flex-1 pr-2"> 372 <div class="flex-shrink-0"> 373 {{ if eq $pr.State "open" }} 374 {{ i "git-pull-request" "size-4 text-green-500" }} 375 {{ else }} 376 {{ i "git-merge" "size-4 text-purple-500" }} 377 {{ end }} 378 </div> 379 <span class="truncate text-sm text-gray-800 dark:text-gray-200"> 380 <span class="text-gray-500 dark:text-gray-400">#{{ $pr.Id }}</span> 381 {{ $pr.Title }} 382 </span> 383 </div> 384 </div> 385{{ end }} 386 387{{ define "features2" }} 388 {{ $cardStyle := "bg-white dark:bg-gray-800 rounded shadow-sm border border-gray-200 dark:border-gray-700" }} 389 {{ $cardInnerStyle := "flex flex-row items-center gap-4 md:gap-6 p-4 md:p-6 md:pt-8 relative" }} 390 {{ $contentStyle := "flex-1 flex flex-col" }} 391 {{ $titleStyle := "text-xl md:text-2xl font-bold mb-2 md:mb-3 text-gray-900 dark:text-gray-100" }} 392 {{ $descriptionStyle := "text-gray-600 dark:text-gray-300 text-sm md:text-base leading-relaxed mb-3 md:mb-0" }} 393 {{ $linkMobileStyle := "hover:no-underline inline-flex md:hidden items-center gap-2 text-sm text-gray-700 dark:text-gray-300 font-medium" }} 394 {{ $iconContainerStyle := "relative shrink-0 w-24 h-24 md:w-48 md:h-48" }} 395 {{ $iconCircleStyle := "w-full h-full rounded-full flex items-center justify-center" }} 396 {{ $linkDesktopStyle := "hover:no-underline hidden md:inline-flex absolute -bottom-10 -right-14 items-center gap-2 p-3 text-base btn" }} 397 398 <div class="w-full flex flex-col gap-6 md:gap-40 max-w-5xl mx-auto px-2"> 399 <div class="{{ $cardStyle }} md:mr-32"> 400 <div class="{{ $cardInnerStyle }}"> 401 <div class="{{ $contentStyle }}"> 402 <h3 class="{{ $titleStyle }}">Built on AT Protocol</h3> 403 <p class="{{ $descriptionStyle }}"> 404 AT Protocol enables federated code-collaboration. Submit 405 pull-requests or bug-reports to any repository hosted on any 406 server. 407 <br> 408 <br> 409 Bring an existing AT Protocol account (such as your Bluesky 410 account), or signup for one with us. 411 <a class="underline" href="https://docs.tangled.org/quick-start-guide.html#login-or-sign-up">Read the docs to know more.</a> 412 </p> 413 <a href="TODO" class="{{ $linkMobileStyle }}"> 414 Learn more 415 {{ i "arrow-right" "size-4" }} 416 </a> 417 </div> 418 <div class="{{ $iconContainerStyle }}"> 419 <div class="{{ $iconCircleStyle }} bg-blue-100 dark:bg-blue-900/50"> 420 {{ i "at-sign" "size-12 md:size-24" "text-blue-500 dark:text-blue-500" "rotate-12" }} 421 </div> 422 <a href="TODO" class="{{ $linkDesktopStyle }}"> 423 Learn more 424 {{ i "arrow-right" "size-4" }} 425 </a> 426 </div> 427 </div> 428 </div> 429 430 <div class="{{ $cardStyle }} md:ml-32"> 431 <div class="{{ $cardInnerStyle }}"> 432 <div class="{{ $contentStyle }}"> 433 <h3 class="{{ $titleStyle }}">Free and Open-source</h3> 434 <p class="{{ $descriptionStyle }}"> 435 All of Tangled is open-source and built in the open! 436 Check out the <a class="underline" href="https://tangled.org/core">monorepo</a> and join in on the fun. 437 <br> 438 <br> 439 We welcome contributions however big or small. You can start contributing by picking up a 440 <a class="underline" href="https://tangled.org/tangled.org/core/issues">good-first-issue</a>. 441 </p> 442 <a href="TODO" class="{{ $linkMobileStyle }}"> 443 Learn more 444 {{ i "arrow-right" "size-4" }} 445 </a> 446 </div> 447 <div class="{{ $iconContainerStyle }}"> 448 <div class="{{ $iconCircleStyle }} bg-green-100 dark:bg-green-900/50"> 449 {{ i "package-open" "size-12 md:size-24" "text-green-500 dark:text-green-500" "-rotate-12" }} 450 </div> 451 <a href="TODO" class="{{ $linkDesktopStyle }}"> 452 Learn more 453 {{ i "arrow-right" "size-4" }} 454 </a> 455 </div> 456 </div> 457 </div> 458 459 <div class="{{ $cardStyle }} md:mr-32"> 460 <div class="{{ $cardInnerStyle }}"> 461 <div class="{{ $contentStyle }}"> 462 <h3 class="{{ $titleStyle }}">Social coding is back</h3> 463 <p class="{{ $descriptionStyle }}"> 464 Discover trending projects, follow your friends and star your favourite repositories. Coding is better together! 465 <br> 466 <br> 467 You can use one account for all of the atmosphere. If you have 468 friends on Bluesky, you will find them on Tangled with the same 469 handle. 470 </p> 471 <a href="TODO" class="{{ $linkMobileStyle }}"> 472 Learn more 473 {{ i "arrow-right" "size-4" }} 474 </a> 475 </div> 476 <div class="{{ $iconContainerStyle }}"> 477 <div class="{{ $iconCircleStyle }} bg-amber-100 dark:bg-amber-900/50"> 478 {{ i "message-circle-heart" "size-12 md:size-24" "text-amber-500 dark:text-amber-500" "rotate-12" }} 479 </div> 480 <a href="TODO" class="{{ $linkDesktopStyle }}"> 481 Learn more 482 {{ i "arrow-right" "size-4" }} 483 </a> 484 </div> 485 </div> 486 </div> 487 </div> 488{{ end }} 489 490{{ define "changelog" }} 491 <div class="w-full px-2 py-16 md:py-24"> 492 <h2 class="text-3xl md:text-4xl font-bold mb-8 text-gray-900 dark:text-gray-100">Changelog</h2> 493 494 <div class="grid grid-cols-1 gap-6 mb-6"> 495 {{ range $index := list 0 1 2 3 }} 496 <div class="bg-white dark:bg-gray-800 rounded shadow-sm border border-gray-200 dark:border-gray-700 p-6"> 497 <div class="flex items-center gap-2 mb-3"> 498 <span class="text-xs font-medium text-gray-500 dark:text-gray-400">v1.{{ sub 3 $index }}.0</span> 499 <span class="text-xs text-gray-400 dark:text-gray-500"></span> 500 <span class="text-xs text-gray-500 dark:text-gray-400">Feb {{ sub 16 $index }}, 2026</span> 501 </div> 502 <h3 class="text-lg font-semibold mb-2 text-gray-900 dark:text-gray-100">Feature Update {{ sub 4 $index }}</h3> 503 <p class="text-sm text-gray-600 dark:text-gray-300 leading-relaxed"> 504 Improvements to the platform including bug fixes, performance enhancements, and new features. 505 </p> 506 </div> 507 {{ end }} 508 </div> 509 510 <div class="flex justify-end"> 511 <a href="/changelog" class="hover:no-underline inline-flex items-center gap-2 px-4 py-2 text-base btn"> 512 View full changelog 513 {{ i "arrow-right" "size-4" }} 514 </a> 515 </div> 516 </div> 517{{ end }} 518 519{{ define "recentUpdates" }} 520 <div class="w-full px-2 py-16 md:py-24"> 521 <h2 class="px-4 text-3xl md:text-4xl font-bold text-gray-900 dark:text-gray-100 mb-2">Recent updates</h2> 522 <p class="px-4 mb-8 text-gray-500 dark:text-gray-400"> 523 Follow <a href="https://bsky.app/profile/tangled.org">@tangled.org</a> on Bluesky for more! 524 </p> 525 <div class="columns-1 md:columns-2 lg:columns-3 gap-6"> 526 {{ range $index, $post := .BlueskyPosts }} 527 <div class="{{ if ge $index 3 }}hidden md:block{{ end }}"> 528 {{ template "post" $post }} 529 </div> 530 {{ end }} 531 </div> 532 </div> 533{{ end }} 534 535{{ define "post" }} 536 <div class="bg-white dark:bg-gray-800 rounded shadow-sm border border-gray-200 dark:border-gray-700 px-6 py-4 flex flex-col gap-2 break-inside-avoid mb-6"> 537 <div class="flex items-center justify-between text-sm text-gray-500 dark:text-gray-400"> 538 <span class="flex items-center gap-2"> 539 {{ template "user/fragments/picHandle" "tangled.org" }} 540 </span> 541 <span>{{ template "repo/fragments/shortTimeAgo" .CreatedAt }}</span> 542 </div> 543 <p class="text-gray-900 dark:text-gray-100 text-base leading-relaxed whitespace-pre-wrap">{{ .Text }}</p> 544 545 {{ if .Embed }} 546 {{ if .Embed.EmbedImages_View }} 547 <div class="grid {{ if eq (len .Embed.EmbedImages_View.Images) 1 }}grid-cols-1{{ else if eq (len .Embed.EmbedImages_View.Images) 2 }}grid-cols-2{{ else }}grid-cols-2{{ end }} gap-2"> 548 {{ range .Embed.EmbedImages_View.Images }} 549 <img src="{{ .Fullsize }}" alt="{{ .Alt }}" class="rounded w-full h-auto object-cover border border-gray-200 dark:border-gray-700" loading="lazy" /> 550 {{ end }} 551 </div> 552 {{ else if .Embed.EmbedExternal_View }} 553 <a href="{{ .Embed.EmbedExternal_View.External.Uri }}" target="_blank" rel="noopener noreferrer" class="hover:no-underline block border border-gray-200 dark:border-gray-700 rounded overflow-hidden hover:bg-gray-50 dark:hover:bg-gray-700 transition-colors"> 554 {{ if .Embed.EmbedExternal_View.External.Thumb }} 555 <img src="{{ .Embed.EmbedExternal_View.External.Thumb }}" alt="" class="w-full h-48 object-cover" loading="lazy" /> 556 {{ end }} 557 <div class="p-3"> 558 <div class="font-medium text-gray-900 dark:text-gray-100 text-sm mb-1">{{ .Embed.EmbedExternal_View.External.Title }}</div> 559 <div class="text-xs text-gray-600 dark:text-gray-400 line-clamp-2">{{ .Embed.EmbedExternal_View.External.Description }}</div> 560 <div class="text-xs text-gray-500 dark:text-gray-500 mt-1">{{ .Embed.EmbedExternal_View.External.Uri }}</div> 561 </div> 562 </a> 563 {{ else if .Embed.EmbedVideo_View }} 564 <div class="rounded overflow-hidden bg-gray-100 dark:bg-gray-700 aspect-video flex items-center justify-center"> 565 <span class="text-gray-500 dark:text-gray-400 text-sm">Video embed</span> 566 </div> 567 {{ end }} 568 {{ end }} 569 570 <a href="https://bsky.app/profile/tangled.org/post/{{ .Rkey }}" 571 target="_blank" 572 rel="noopener noreferrer" 573 class="flex items-center justify-between gap-4 text-sm text-gray-500 dark:text-gray-400 pt-2 no-underline hover:no-underline"> 574 <div class="flex items-center gap-4"> 575 <div class="flex items-center gap-1"> 576 {{ i "heart" "size-4" }} 577 <span>{{ .LikeCount }}</span> 578 </div> 579 <div class="flex items-center gap-1"> 580 {{ i "repeat-2" "size-4" }} 581 <span>{{ .RepostCount }}</span> 582 </div> 583 <div class="flex items-center gap-1"> 584 {{ i "reply" "size-4 -scale-x-1" }} 585 <span>{{ add64 .ReplyCount .QuoteCount }}</span> 586 </div> 587 </div> 588 {{ i "arrow-up-right" "size-4" }} 589 </a> 590 </div> 591{{ end }} 592 593{{ define "community" }} 594 <div class="w-full px-2 py-16 md:py-24"> 595 <div class="max-w-2xl mx-auto text-center space-y-6"> 596 <h2 class="text-3xl md:text-6xl font-bold text-gray-900 dark:text-gray-100">Join the network</h2> 597 <p class="text-xl text-gray-600 dark:text-gray-300"> 598 You can participate in the Tangled network with an AT account. If you 599 don't know what that is, you can sign up for one below. 600 <br> 601 <a href="https://docs.tangled.org/quick-start-guide.html#login-or-sign-up">Read more on the docs.</a> 602 </p> 603 <form class="flex gap-2 items-stretch w-full md:max-w-md mx-auto p-2 border border-gray-200 dark:border-gray-700 bg-white dark:bg-gray-800 rounded shadow-sm"> 604 <input 605 type="email" 606 id="email" 607 name="email" 608 tabindex="4" 609 required 610 placeholder="Enter your email" 611 class="py-2 w-full" 612 /> 613 <button class="btn-create flex items-center gap-2 text-base whitespace-nowrap" type="submit"> 614 join now 615 {{ i "arrow-right" "size-4" }} 616 </button> 617 </form> 618 </div> 619{{ end }}