<script module lang="ts">
import { tv, type VariantProps } from "tailwind-variants";
export const tile = tv({
slots: {
root: "flex flex-col gap-xs rounded-md border border-border-default p-md",
heading:
"flex items-center gap-xs typography-paragraph-regular text-foreground-default",
body: "flex-1 typography-paragraph-small text-foreground-muted",
action: "ml-auto flex w-fit items-center gap-2xs typography-paragraph-small text-foreground-default no-underline hover:underline"
},
variants: {
span: {
full: { root: "sm:col-span-2" },
tall: { root: "sm:row-span-2" },
single: { root: "" }
},
link: {
true: {
root: "flex-1 gap-2xs no-underline transition-colors hover:bg-background-subtle hover:no-underline"
},
false: { root: "" }
}
},
defaultVariants: {
span: "single",
link: false
}
});
export type TileVariants = VariantProps<typeof tile>;
const TILE_STAGGER_MS = 60;
const TILE_MAX_DELAY_MS = 140;
function tileDelay(index: number) {
return Math.min(index * TILE_STAGGER_MS, TILE_MAX_DELAY_MS);
}
</script>
<script lang="ts">
import type { Component } from "svelte";
import type { SvelteHTMLElements } from "svelte/elements";
import { quintOut } from "svelte/easing";
import { fly } from "svelte/transition";
import { prefersReducedMotion } from "svelte/motion";
import { resolve } from "$app/paths";
import ArrowRight from "$icon/arrow-right";
import Check from "$icon/check";
import BookMarked from "$icon/book-marked";
import Download from "$icon/download";
import GitPullRequest from "$icon/git-pull-request";
import Layers2 from "$icon/layers-2";
import MessageCircle from "$icon/message-circle";
import Server from "$icon/server";
import Twitter from "$icon/twitter";
import Bluesky from "#lib/components/icons/Bluesky.svelte";
import StepHeader from "#lib/components/welcome/StepHeader.svelte";
import { pluralize } from "#lib/format.ts";
let { migrated = 0 }: { migrated?: number } = $props();
const slot = tile();
function tileTransition(index: number) {
return {
y: prefersReducedMotion.current ? 0 : 8,
duration: prefersReducedMotion.current ? 0 : 220,
delay: prefersReducedMotion.current ? 0 : tileDelay(index),
easing: quintOut
};
}
const coreHref = resolve("/tangled.org/core" as "/");
const newRepoHref = resolve("/repo/new");
const migrateHref = resolve("/repo/migrate");
const docs: {
icon: Component<SvelteHTMLElements["svg"]>;
title: string;
body: string;
href: string;
}[] = [
{
icon: Server,
title: "Host your own knot",
body: "Host repositories on your own infrastructure with knots.",
href: "https://docs.tangled.org/knots/self-hosting/"
},
{
icon: Layers2,
title: "Host your own spindle",
body: "Run CI jobs on your own infrastructure using spindles.",
href: "https://docs.tangled.org/spindles/self-hosting/"
}
];
const community: {
icon: Component<SvelteHTMLElements["svg"]>;
title: string;
body: string;
href: string;
}[] = [
{
icon: Bluesky,
title: "Bluesky",
body: "Follow @tangled.org for updates.",
href: "https://bsky.app/profile/tangled.org"
},
{
icon: MessageCircle,
title: "Discord",
body: "Chat with the community.",
href: "https://chat.tangled.org"
},
{
icon: Twitter,
title: "X (Twitter)",
body: "Follow @tangled_org for updates.",
href: "https://x.com/tangled_org"
}
];
</script>
<div class="flex flex-col gap-xl">
<StepHeader title="You're all set!" subtitle="Here are a few things to explore next." />
<div class="flex flex-col gap-sm">
<div class="grid grid-cols-1 gap-sm sm:grid-cols-2">
<div class={slot.root()} in:fly={tileTransition(0)}>
{@render heading(Download, "Migrate your repositories")}
<p class={slot.body()}>
Point us at your GitHub account and move your repositories to a knot of your
choice.
</p>
{#if migrated > 0}
<span
class="flex items-center gap-2xs typography-paragraph-small text-foreground-success"
>
<Check class="size-3.5 shrink-0" aria-hidden="true" />
{pluralize(migrated, "repository", "repositories")} migrated
</span>
{/if}
<a href={migrateHref} class={slot.action()}>
{migrated > 0 ? "Migrate more" : "Migrate"}
<ArrowRight class="size-4 shrink-0" aria-hidden="true" />
</a>
</div>
<div class={slot.root()} in:fly={tileTransition(1)}>
{@render heading(BookMarked, "Create a repository")}
<p class={slot.body()}>Kick things off by creating your first repository.</p>
<a href={newRepoHref} class={slot.action()}>
Create
<ArrowRight class="size-3.5 shrink-0" aria-hidden="true" />
</a>
</div>
<div class={tile({ span: "tall" }).root()} in:fly={tileTransition(1)}>
{@render heading(GitPullRequest, "Contribute to the Tangled project")}
<p class={slot.body()}>
Tangled is fully open-source: open a feature request, or submit a bug-fix!
<br /><br />
All contributions are welcome.
</p>
<a href={coreHref} class={slot.action()}>
Contribute
<ArrowRight class="size-3.5 shrink-0" aria-hidden="true" />
</a>
</div>
{#each docs as doc, i (doc.title)}
<div class={slot.root()} in:fly={tileTransition(2 + i)}>
{@render heading(doc.icon, doc.title)}
<p class={slot.body()}>{doc.body}</p>
<a
href={doc.href}
target="_blank"
rel="external noopener noreferrer"
class={slot.action()}
>
Read the docs
<ArrowRight class="size-3.5 shrink-0" aria-hidden="true" />
</a>
</div>
{/each}
</div>
<ul class="grid grid-cols-1 gap-sm sm:grid-cols-3">
{#each community as link, i (link.title)}
<li class="flex" in:fly={tileTransition(4 + i)}>
<a
href={link.href}
target="_blank"
rel="external noopener noreferrer"
class={tile({ link: true }).root()}
>
{@render heading(link.icon, link.title)}
<span class={slot.body()}>{link.body}</span>
</a>
</li>
{/each}
</ul>
</div>
</div>
{#snippet heading(icon: Component<SvelteHTMLElements["svg"]>, title: string)}
{@const Icon = icon}
<span class={slot.heading()}>
<Icon class="size-3.5 shrink-0" aria-hidden="true" />
{title}
</span>
{/snippet}