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