This repository has no description
0

Configure Feed

Select the types of activity you want to include in your feed.

core / appview / pages / templates / timeline / home.html
28 kB 559 lines
1{{ define "title" }}Tangled &middot; The next-generation social coding platform{{ end }} 2 3{{ define "extrameta" }} 4 <!-- Open Graph Meta Tags --> 5 <meta property="og:title" content="Tangled · The next-generation social coding platform" /> 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 repositories on your infrastructure with knots, use stacked pull requests, and run CI with spindles." /> 21 <link rel="canonical" href="https://tangled.org" /> 22 23 <!-- hls.js: play bluesky video embeds --> 24 <script> 25 document.addEventListener('DOMContentLoaded', () => { 26 const videos = document.querySelectorAll('video[data-hls]'); 27 if (!videos.length) return; 28 29 const attach = () => { 30 videos.forEach((video) => { 31 const src = video.dataset.hls; 32 if (video.canPlayType('application/vnd.apple.mpegurl')) { 33 // safari can play HLS natively 34 video.src = src; 35 } else if (window.Hls && Hls.isSupported()) { 36 const hls = new Hls(); 37 hls.loadSource(src); 38 hls.attachMedia(video); 39 } 40 }); 41 }; 42 43 // only fetch hls.js when a browser actually needs it 44 const needsHls = Array.from(videos).some( 45 (v) => !v.canPlayType('application/vnd.apple.mpegurl') 46 ); 47 if (!needsHls) { 48 attach(); 49 return; 50 } 51 52 const script = document.createElement('script'); 53 script.src = '/static/hls.min.js'; 54 script.onload = attach; 55 document.head.appendChild(script); 56 }); 57 </script> 58{{ end }} 59 60 61{{ define "content" }} 62 <div class="flex flex-col gap-20 md:gap-40 my-20 md:my-32"> 63 {{ template "timeline/fragments/hero" . }} 64 {{ template "timeline/fragments/preview" . }} 65 {{ template "features1" . }} 66 {{ template "features2" . }} 67 {{ template "recentUpdates" . }} 68 {{ if .ShowNewsletter }}{{ template "newsletter" . }}{{ end }} 69 </div> 70{{ end }} 71 72{{ block "topbarLayout" . }} 73 <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;"> 74 {{ if .LoggedInUser }} 75 <div id="upgrade-banner" 76 hx-get="/upgradeBanner" 77 hx-trigger="load" 78 hx-swap="innerHTML"> 79 </div> 80 {{ end }} 81 {{ template "layouts/fragments/topbar" . }} 82 </header> 83{{ end }} 84 85{{ block "footerLayout" . }} 86 <footer class="z-10"> 87 {{ template "layouts/fragments/footerFull" . }} 88 </footer> 89{{ end }} 90 91{{ block "bodyClasses" . }} 92 bg-transparent bg-gradient-to-b from-white to-slate-100 dark:bg-none dark:bg-gray-900 93{{ end }} 94 95{{ block "mainLayout" . }} 96 <div class="flex-grow relative"> 97 <div 98 class="absolute opacity-50 dark:opacity-5 inset-x-0 top-0 bottom-[-50px] md:bottom-[-150px] pointer-events-none bg-slate-200 dark:bg-slate-400 [mask-image:url('https://assets.tangled.network/yarn_ball_mask.svg')] [mask-repeat:no-repeat] [mask-position:right_bottom] [-webkit-mask-image:url('https://assets.tangled.network/yarn_ball_mask.svg')] [-webkit-mask-repeat:no-repeat] [-webkit-mask-position:right_bottom] w-full"> 99 </div> 100 <div class="max-w-screen-xl mx-auto flex flex-col gap-4 relative z-10"> 101 {{ block "contentLayout" . }} 102 <main> 103 {{ block "content" . }}{{ end }} 104 </main> 105 {{ end }} 106 107 {{ block "contentAfterLayout" . }} 108 <main> 109 {{ block "contentAfter" . }}{{ end }} 110 </main> 111 {{ end }} 112 </div> 113 </div> 114{{ end }} 115 116{{ define "features1" }} 117 {{ $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" }} 118 {{ $spanStyle := "z-10 items-center justify-between gap-2 w-full" }} 119 {{ $connectorStyle := "w-0.5 h-6 bg-gray-300 dark:bg-gray-600 opacity-0 mx-auto" }} 120 {{ $contentStyle := "hidden bg-white dark:bg-gray-800 rounded shadow-sm border border-gray-200 dark:border-gray-700 grid-cols-1 animate-fadein" }} 121 {{ $progressOverlayStyle := "absolute inset-0 bg-gray-600/10 dark:bg-gray-100/10 w-0 transition-none" }} 122 123 <style> 124 @media (max-width: 768px) { 125 .features-grid:has(#feature-prs:checked) { 126 grid-template-columns: 1fr auto auto; 127 } 128 129 .features-grid:has(#feature-knots:checked) { 130 grid-template-columns: auto 1fr auto; 131 } 132 133 .features-grid:has(#feature-spindles:checked) { 134 grid-template-columns: auto auto 1fr; 135 } 136 137 #feature-prs:checked ~ label[for="feature-prs"] .label-text, 138 #feature-knots:checked ~ label[for="feature-knots"] .label-text, 139 #feature-spindles:checked ~ label[for="feature-spindles"] .label-text { 140 display: inline-flex !important; 141 } 142 #feature-prs:checked ~ label[for="feature-prs"] .icon-only, 143 #feature-knots:checked ~ label[for="feature-knots"] .icon-only, 144 #feature-spindles:checked ~ label[for="feature-spindles"] .icon-only { 145 display: none !important; 146 } 147 } 148 </style> 149 150 <div class="features-grid w-full grid grid-cols-3 gap-x-6 px-2"> 151 <input type="radio" id="feature-prs" name="feature" class="peer/prs hidden" checked /> 152 <input type="radio" id="feature-knots" name="feature" class="peer/knots hidden" /> 153 <input type="radio" id="feature-spindles" name="feature" class="peer/spindles hidden" /> 154 155 <label for="feature-prs" class="{{ $labelStyle }} peer-checked/prs:opacity-100 peer-checked/prs:shadow-sm"> 156 <span class="label-text hidden md:inline-flex {{ $spanStyle }}">A better way to review {{ i "git-pull-request" "size-5" }}</span> 157 <span class="icon-only inline-flex md:hidden {{ $spanStyle }}">{{ i "git-pull-request" "size-5" }}</span> 158 <div class="{{ $progressOverlayStyle }}" data-progress="prs"></div> 159 </label> 160 161 <label for="feature-knots" class="{{ $labelStyle }} peer-checked/knots:opacity-100 peer-checked/knots:shadow-sm"> 162 <span class="label-text hidden md:inline-flex {{ $spanStyle }}">Completely self-hostable {{ i "hard-drive" "size-5" }}</span> 163 <span class="icon-only inline-flex md:hidden {{ $spanStyle }}">{{ i "hard-drive" "size-5" }}</span> 164 <div class="{{ $progressOverlayStyle }}" data-progress="knots"></div> 165 </label> 166 167 <label for="feature-spindles" class="{{ $labelStyle }} peer-checked/spindles:opacity-100 peer-checked/spindles:shadow-sm"> 168 <span class="label-text hidden md:inline-flex {{ $spanStyle }}">Quick and easy CI {{ i "layers-2" "size-5" }}</span> 169 <span class="icon-only inline-flex md:hidden {{ $spanStyle }}">{{ i "layers-2" "size-5" }}</span> 170 <div class="{{ $progressOverlayStyle }}" data-progress="spindles"></div> 171 </label> 172 173 <div class="{{ $connectorStyle }} peer-checked/prs:opacity-100"></div> 174 <div class="{{ $connectorStyle }} peer-checked/knots:opacity-100"></div> 175 <div class="{{ $connectorStyle }} peer-checked/spindles:opacity-100"></div> 176 177 {{ $titleStyle := "text-2xl md:text-6xl my-2 md:mb-4 text-black dark:text-white font-medium" }} 178 {{ $textContentStyle := "p-4 md:p-6 md:text-xl" }} 179 {{ $imgContentStyle := "w-full overflow-hidden place-content-center bg-gradient-to-b from-slate-50 to-slate-100 dark:from-gray-800 dark:to-gray-900 border-t border-gray-200 dark:border-gray-700" }} 180 {{ $linkDesktopStyle := "hover:no-underline items-center gap-2 p-3 text-base btn" }} 181 182 <div class="col-span-3 {{ $contentStyle }} peer-checked/prs:grid"> 183 <div class="{{ $textContentStyle }} flex gap-4 md:gap-8"> 184 <section class="space-y-4"> 185 <h1 class="{{ $titleStyle }}">Pull requests, reimagined</h1> 186 <p class="text-gray-600 dark:text-gray-400"> 187 Break down large features into small, reviewable chunks. Stack pull 188 requests on top of each other and ship faster with round-based code 189 reviews. Tangled natively supports stacking using Jujutsu. 190 </p> 191 <a href="https://blog.tangled.org/stacking" class="hover:no-underline inline-flex items-center gap-2 text-sm md:text-base text-black dark:text-white font-medium"> 192 Learn more 193 {{ i "arrow-right" "size-4" }} 194 </a> 195 </section> 196 <div class="w-fit hidden md:flex items-center"> 197 <div class="size-32 rounded-full flex items-center justify-center bg-yellow-100 dark:bg-yellow-900 -mr-4" > 198 {{ i "wand-sparkles" "size-16 text-yellow-500 dark:text-yellow-500" }} 199 </div> 200 <div class="size-40 rounded-full flex items-center justify-center bg-green-100 dark:bg-green-900 z-10"> 201 {{ i "git-pull-request-arrow" "size-24 text-green-500 dark:text-green-500 rotate-12" }} 202 </div> 203 </div> 204 </div> 205 <div class="{{ $imgContentStyle }} relative overflow-hidden flex items-center justify-center"> 206 <div class="w-[120%] md:w-full flex"> 207 <picture class="w-full"> 208 <source srcset="https://assets.tangled.network/home-page-prs-dark.svg" media="(prefers-color-scheme: dark)" /> 209 <img src="https://assets.tangled.network/home-page-prs-light.svg" class="w-full block" alt="A list of stacked pull requests with CI checks and PR comments"/> 210 </picture> 211 </div> 212 </div> 213 </div> 214 215 <div class="col-span-3 {{ $contentStyle }} peer-checked/knots:grid"> 216 <div class="{{ $textContentStyle }} flex place-content-between gap-4 md:gap-8"> 217 <section class="space-y-4"> 218 <h1 class="{{ $titleStyle }}">Run it at home</h1> 219 <p class="text-gray-600 dark:text-gray-400"> 220 Host your repositories on your own infrastructure with <a href="https://docs.tangled.org/knot-self-hosting-guide.html#knot-self-hosting-guide" class="no-underline">knots</a>. 221 Run CI on your own machines with <a href="https://docs.tangled.org/spindles.html#self-hosting-guide" class="no-underline">spindles</a>. 222 <br> 223 Don't want to self-host? All are welcome on our hosted instances. 224 </p> 225 <a href="https://docs.tangled.org/knot-self-hosting-guide.html#knot-self-hosting-guide" class="hover:no-underline inline-flex items-center gap-2 text-sm md:text-base text-black dark:text-white font-medium"> 226 Learn more 227 {{ i "arrow-right" "size-4" }} 228 </a> 229 </section> 230 <div class="w-fit hidden md:flex items-center"> 231 <div class="size-32 rounded-full flex items-center justify-center bg-blue-100 dark:bg-blue-900 -mr-4" > 232 {{ i "workflow" "size-16 text-blue-500 dark:text-blue-500" }} 233 </div> 234 <div class="size-40 rounded-full flex items-center justify-center bg-yellow-100 dark:bg-yellow-900 z-10"> 235 {{ i "server" "size-24 text-yellow-500 dark:text-yellow-500" }} 236 </div> 237 </div> 238 </div> 239 <div class="{{ $imgContentStyle }} relative overflow-hidden flex items-center justify-center"> 240 <div class="w-[120%] md:w-full flex"> 241 <picture class="w-full"> 242 <source srcset="https://assets.tangled.network/home-page-self-host-dark.svg" media="(prefers-color-scheme: dark)" /> 243 <img src="https://assets.tangled.network/home-page-self-host-light.svg" class="w-full block" alt="A world map with knots hosting code on servers in different locations across the world" /> 244 </picture> 245 </div> 246 </div> 247 </div> 248 249 <div class="col-span-3 {{ $contentStyle }} peer-checked/spindles:grid"> 250 <div class="{{ $textContentStyle }} flex place-content-between gap-4 md:gap-8"> 251 <section class="space-y-4"> 252 <h1 class="{{ $titleStyle }}">Nix-powered CI</h1> 253 <p class="text-gray-600 dark:text-gray-400"> 254 Pick and choose dependencies for your CI pipelines from <a 255 href="https://docs.tangled.org/spindles.html#dependencies" 256 class="no-underline"><code>nixpkgs</code></a>, one of the biggest 257 package repositories. 258 <br> 259 Support for Docker and MicroVM based runners coming soon! 260 </p> 261 <a href="https://docs.tangled.org/spindles.html#pipelines" class="hover:no-underline inline-flex items-center gap-2 text-sm md:text-base text-black dark:text-white font-medium"> 262 Learn more 263 {{ i "arrow-right" "size-4" }} 264 </a> 265 </section> 266 <div class="w-fit hidden md:flex items-center"> 267 <div class="size-32 rounded-full flex items-center justify-center bg-green-100 dark:bg-green-900 -mr-4" > 268 {{ i "package" "size-16 text-green-500 dark:text-green-500" }} 269 </div> 270 <div class="size-40 rounded-full flex items-center justify-center bg-indigo-100 dark:bg-indigo-900 z-10"> 271 {{ i "snowflake" "size-24 text-indigo-500 dark:text-indigo-500 rotate-12" }} 272 </div> 273 </div> 274 </div> 275 <div class="{{ $imgContentStyle }} flex items-end justify-center pb-0 overflow-hidden"> 276 <div class="w-[120%] md:w-full flex"> 277 <picture class="w-full"> 278 <source srcset="https://assets.tangled.network/home-page-ci-dark.svg" media="(prefers-color-scheme: dark)" /> 279 <img src="https://assets.tangled.network/home-page-ci-light.svg" class="w-full block" alt="The Tangled UI showing status checks, a Nix-based CI workflow YAML file, and logs from a CI job"/> 280 </picture> 281 </div> 282 </div> 283 </div> 284 </div> 285 286 <script> 287 document.addEventListener('DOMContentLoaded', function() { 288 const featureIds = ['feature-prs', 'feature-knots', 'feature-spindles']; 289 const progressNames = ['prs', 'knots', 'spindles']; 290 let currentIndex = 0; 291 let timerInterval = null; 292 293 function stopTimer() { 294 if (timerInterval) { 295 clearInterval(timerInterval); 296 timerInterval = null; 297 } 298 document.querySelectorAll('[data-progress]').forEach(el => { 299 el.classList.remove('animate-progress'); 300 }); 301 } 302 303 const firstProgress = document.querySelector('[data-progress="prs"]'); 304 if (firstProgress) { 305 firstProgress.classList.add('animate-progress'); 306 } 307 308 document.querySelectorAll('label[for^="feature-"]').forEach(label => { 309 label.addEventListener('click', stopTimer); 310 }); 311 312 timerInterval = setInterval(function() { 313 document.querySelectorAll('[data-progress]').forEach(el => { 314 el.classList.remove('animate-progress'); 315 void el.offsetWidth; // force reflow 316 }); 317 318 currentIndex = (currentIndex + 1) % featureIds.length; 319 document.getElementById(featureIds[currentIndex]).checked = true; 320 321 const activeProgress = document.querySelector('[data-progress="' + progressNames[currentIndex] + '"]'); 322 if (activeProgress) { 323 activeProgress.classList.add('animate-progress'); 324 } 325 }, 10000); 326 }); 327 </script> 328{{ end }} 329 330{{ define "features2" }} 331 {{ $cardStyle := "bg-white dark:bg-gray-800 rounded shadow-sm border border-gray-200 dark:border-gray-700" }} 332 {{ $cardInnerStyle := "flex flex-row items-center gap-4 md:gap-6 p-4 md:p-6 md:pt-8 relative" }} 333 {{ $contentStyle := "flex-1 flex flex-col" }} 334 {{ $titleStyle := "text-xl md:text-2xl font-bold mb-2 md:mb-3 text-gray-900 dark:text-gray-100" }} 335 {{ $descriptionStyle := "text-gray-600 dark:text-gray-300 text-sm md:text-base leading-relaxed mb-5 md:mb-0" }} 336 {{ $linkMobileStyle := "hover:no-underline inline-flex md:hidden items-center gap-2 text-sm text-black dark:text-white font-medium" }} 337 {{ $iconContainerStyle := "relative shrink-0 w-24 h-24 md:w-48 md:h-48" }} 338 {{ $iconCircleStyle := "rounded-full flex items-center justify-center border border-gray-200 dark:border-gray-700" }} 339 {{ $linkDesktopStyle := "hover:no-underline hidden md:inline-flex absolute -bottom-10 -right-14 items-center gap-2 p-3 text-base btn" }} 340 341 <div class="w-full flex flex-col gap-6 md:gap-40 max-w-5xl mx-auto px-2"> 342 <div class="{{ $cardStyle }} md:mr-32"> 343 <div class="{{ $cardInnerStyle }}"> 344 {{ $moreLink := "https://docs.tangled.org/quick-start-guide.html#login-or-sign-up" }} 345 <div class="{{ $contentStyle }}"> 346 <h3 class="{{ $titleStyle }}">Built on AT Protocol</h3> 347 <p class="{{ $descriptionStyle }}"> 348 <a class="underline" href="https://atproto.com/">AT Protocol</a> enables federated code-collaboration. Submit 349 pull-requests or bug-reports to any repository hosted on any 350 server. 351 <br> 352 <br> 353 Bring an existing AT Protocol account (such as your Bluesky 354 account), or signup for one with us. 355 </p> 356 <a href="{{ $moreLink }}" class="{{ $linkMobileStyle }}"> 357 Learn more 358 {{ i "arrow-right" "size-4" }} 359 </a> 360 </div> 361 <div class="{{ $iconContainerStyle }}"> 362 <div class="{{ $iconCircleStyle }} w-full h-full bg-blue-100 dark:bg-blue-900/50"> 363 {{ i "at-sign" "size-12 md:size-24" "text-blue-500 dark:text-blue-500" "rotate-12" }} 364 </div> 365 <div class="{{ $iconCircleStyle }} absolute -top-2 -left-2 w-10 h-10 md:w-16 md:h-16 rounded-full bg-white dark:bg-gray-800 -rotate-12"> 366 {{ i "globe" "size-4 md:size-8" "text-blue-500 dark:text-blue-500" }} 367 </div> 368 <a href="{{ $moreLink }}" class="{{ $linkDesktopStyle }}"> 369 Learn more 370 {{ i "arrow-right" "size-4" }} 371 </a> 372 </div> 373 </div> 374 </div> 375 376 <div class="{{ $cardStyle }} md:ml-32"> 377 <div class="{{ $cardInnerStyle }}"> 378 {{ $moreLink := "https://tangled.org/core" }} 379 <div class="{{ $contentStyle }}"> 380 <h3 class="{{ $titleStyle }}">Free and open source</h3> 381 <p class="{{ $descriptionStyle }}"> 382 All of Tangled is open source and built with the community! 383 Check out the <a class="underline" href="https://tangled.org/core">monorepo</a> and join in on the fun. 384 <br> 385 <br> 386 We welcome contributions however big or small. You can start contributing by picking up a 387 <a class="underline" href="https://tangled.org/tangled.org/core/issues?q=state%3Aopen+label%3Agood-first-issue">good-first-issue</a>. 388 </p> 389 <a href="{{ $moreLink }}" class="{{ $linkMobileStyle }}"> 390 View source 391 {{ i "arrow-right" "size-4" }} 392 </a> 393 </div> 394 <div class="{{ $iconContainerStyle }}"> 395 <div class="{{ $iconCircleStyle }} w-full h-full bg-green-100 dark:bg-green-900/50"> 396 {{ i "package-open" "size-12 md:size-24" "text-green-500 dark:text-green-500" "-rotate-12" }} 397 </div> 398 <div class="absolute -top-2 -left-2 w-10 h-10 md:w-16 md:h-16 rounded-full bg-white dark:bg-gray-800 flex items-center justify-center border border-gray-200 dark:border-gray-700 rotate-12"> 399 {{ i "heart" "size-4 md:size-8" "text-green-500 dark:text-green-500" }} 400 </div> 401 <a href="{{ $moreLink }}" class="{{ $linkDesktopStyle }}"> 402 View source 403 {{ i "arrow-right" "size-4" }} 404 </a> 405 </div> 406 </div> 407 </div> 408 409 <div class="{{ $cardStyle }} md:mr-32"> 410 <div class="{{ $cardInnerStyle }}"> 411 {{ $moreLink := "/timeline" }} 412 <div class="{{ $contentStyle }}"> 413 <h3 class="{{ $titleStyle }}">Social coding is back</h3> 414 <p class="{{ $descriptionStyle }}"> 415 Discover trending projects, follow your friends and star your favourite repositories. Coding is better together! 416 </p> 417 <a href="{{ $moreLink }}" class="{{ $linkMobileStyle }}"> 418 Explore timeline 419 {{ i "arrow-right" "size-4" }} 420 </a> 421 </div> 422 <div class="{{ $iconContainerStyle }}"> 423 <div class="{{ $iconCircleStyle }} w-full h-full bg-amber-100 dark:bg-amber-900/50"> 424 {{ i "message-circle-heart" "size-12 md:size-24" "text-amber-500 dark:text-amber-500" "rotate-12" }} 425 </div> 426 <div class="absolute -top-2 -left-2 w-10 h-10 md:w-16 md:h-16 rounded-full bg-white dark:bg-gray-800 flex items-center justify-center border border-gray-200 dark:border-gray-700 -rotate-12"> 427 {{ i "users" "size-4 md:size-8" "text-amber-500 dark:text-amber-500" }} 428 </div> 429 <a href="{{ $moreLink }}" class="{{ $linkDesktopStyle }}"> 430 Explore timeline 431 {{ i "arrow-right" "size-4" }} 432 </a> 433 </div> 434 </div> 435 </div> 436 </div> 437{{ end }} 438 439{{ define "changelog" }} 440 <div class="w-full px-2 py-16 md:py-24"> 441 <h2 class="text-3xl md:text-4xl font-bold mb-8 text-gray-900 dark:text-gray-100">Changelog</h2> 442 443 <div class="grid grid-cols-1 gap-6 mb-6"> 444 {{ range $index := list 0 1 2 3 }} 445 <div class="bg-white dark:bg-gray-800 rounded shadow-sm border border-gray-200 dark:border-gray-700 p-6"> 446 <div class="flex items-center gap-2 mb-3"> 447 <span class="text-xs font-medium text-gray-500 dark:text-gray-400">v1.{{ sub 3 $index }}.0</span> 448 <span class="text-xs text-gray-400 dark:text-gray-500"></span> 449 <span class="text-xs text-gray-500 dark:text-gray-400">Feb {{ sub 16 $index }}, 2026</span> 450 </div> 451 <h3 class="text-lg font-semibold mb-2 text-gray-900 dark:text-gray-100">Feature Update {{ sub 4 $index }}</h3> 452 <p class="text-sm text-gray-600 dark:text-gray-300 leading-relaxed"> 453 Improvements to the platform including bug fixes, performance enhancements, and new features. 454 </p> 455 </div> 456 {{ end }} 457 </div> 458 459 <div class="flex justify-end"> 460 <a href="/changelog" class="hover:no-underline inline-flex items-center gap-2 px-4 py-2 text-base btn"> 461 View full changelog 462 {{ i "arrow-right" "size-4" }} 463 </a> 464 </div> 465 </div> 466{{ end }} 467 468{{ define "recentUpdates" }} 469 <div class="w-full px-2 py-16 md:py-24"> 470 <h2 class="px-4 text-3xl md:text-4xl font-bold text-gray-900 dark:text-gray-100 mb-2">Recent updates</h2> 471 <p class="px-4 mb-8 text-gray-500 dark:text-gray-400"> 472 Follow <a href="https://bsky.app/profile/tangled.org">@tangled.org</a> on Bluesky for more! 473 </p> 474 <div class="columns-1 md:columns-2 lg:columns-3 gap-6"> 475 {{ range $index, $post := .BlueskyPosts }} 476 <div class="{{ if ge $index 3 }}hidden md:block{{ end }}"> 477 {{ template "post" $post }} 478 </div> 479 {{ end }} 480 </div> 481 </div> 482{{ end }} 483 484{{ define "post" }} 485 {{ $author := or .AuthorDid "tangled.org" }} 486 <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"> 487 <div class="flex items-center justify-between text-sm text-gray-500 dark:text-gray-400"> 488 <a href="https://bsky.app/profile/{{ $author }}" target="_blank" rel="noopener noreferrer" class="flex items-center gap-2 no-underline hover:no-underline"> 489 {{ template "user/fragments/picHandle" $author }} 490 </a> 491 <span>{{ template "repo/fragments/shortTimeAgo" .CreatedAt }}</span> 492 </div> 493 <p class="text-gray-900 dark:text-gray-100 text-base leading-relaxed whitespace-pre-wrap">{{ .Text }}</p> 494 495 {{ if .Embed }} 496 {{ if .Embed.EmbedImages_View }} 497 <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"> 498 {{ range .Embed.EmbedImages_View.Images }} 499 <img src="{{ .Fullsize }}" alt="{{ .Alt }}" class="rounded w-full h-auto object-cover border border-gray-200 dark:border-gray-700" loading="lazy" /> 500 {{ end }} 501 </div> 502 {{ else if .Embed.EmbedExternal_View }} 503 <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"> 504 {{ if .Embed.EmbedExternal_View.External.Thumb }} 505 <img src="{{ .Embed.EmbedExternal_View.External.Thumb }}" alt="" class="w-full h-48 object-cover" loading="lazy" /> 506 {{ end }} 507 <div class="p-3"> 508 <div class="font-medium text-gray-900 dark:text-gray-100 text-sm mb-1">{{ .Embed.EmbedExternal_View.External.Title }}</div> 509 <div class="text-xs text-gray-600 dark:text-gray-400 line-clamp-2">{{ .Embed.EmbedExternal_View.External.Description }}</div> 510 <div class="text-xs text-gray-500 dark:text-gray-500 mt-1">{{ .Embed.EmbedExternal_View.External.Uri }}</div> 511 </div> 512 </a> 513 {{ else if .Embed.EmbedVideo_View }} 514 <video 515 class="rounded w-full h-auto max-h-[32rem] bg-black border border-gray-200 dark:border-gray-700" 516 controls 517 preload="none" 518 playsinline 519 {{ with .Embed.EmbedVideo_View.Thumbnail }}poster="{{ . }}"{{ end }} 520 {{ with .Embed.EmbedVideo_View.Alt }}aria-label="{{ . }}"{{ end }} 521 data-hls="{{ .Embed.EmbedVideo_View.Playlist }}" 522 ></video> 523 {{ end }} 524 {{ end }} 525 526 <a href="https://bsky.app/profile/{{ $author }}/post/{{ .Rkey }}" 527 target="_blank" 528 rel="noopener noreferrer" 529 class="flex items-center justify-between gap-4 text-sm text-gray-500 dark:text-gray-400 pt-2 no-underline hover:no-underline"> 530 <div class="flex items-center gap-4"> 531 <div class="flex items-center gap-1"> 532 {{ i "heart" "size-4" }} 533 <span>{{ .LikeCount }}</span> 534 </div> 535 <div class="flex items-center gap-1"> 536 {{ i "repeat-2" "size-4" }} 537 <span>{{ .RepostCount }}</span> 538 </div> 539 <div class="flex items-center gap-1"> 540 {{ i "message-square" "size-4 -scale-x-1" }} 541 <span>{{ add64 .ReplyCount .QuoteCount }}</span> 542 </div> 543 </div> 544 {{ i "arrow-up-right" "size-4" }} 545 </a> 546 </div> 547{{ end }} 548 549{{ define "newsletter" }} 550 <div class="w-full px-2 py-16 md:py-24"> 551 <div class="max-w-2xl mx-auto text-center space-y-6"> 552 <h2 class="text-3xl md:text-6xl font-bold text-gray-900 dark:text-gray-100">Stay in the loop</h2> 553 <p class="text-xl text-gray-600 dark:text-gray-300"> 554 We've got a newsletter! Punch in your email to stay updated on what we're shipping at Tangled. 555 </p> 556 {{ template "timeline/fragments/newsletterForm" (dict "Id" "home" "Variant" "hero") }} 557 </div> 558 </div> 559{{ end }}