feat: move structure

This commit is contained in:
Bart van der Braak 2024-02-16 08:40:42 +01:00
parent abe1d003c6
commit 0fa3061777
220 changed files with 116 additions and 67 deletions

View file

@ -0,0 +1,18 @@
<script lang="ts">
import * as Popover from '$lib/components/ui/popover';
import { Button } from '$lib/components/ui/button';
import SuperDebug from 'sveltekit-superforms/client/SuperDebug.svelte';
export let data: object;
</script>
<Popover.Root>
<Popover.Trigger asChild let:builder>
<Button builders={[builder]} variant="ghost" size="icon" class="block">
{'{}'}
</Button>
</Popover.Trigger>
<Popover.Content class="w-auto">
<SuperDebug label="$layout data" status={false} {data} />
</Popover.Content>
</Popover.Root>

View file

@ -0,0 +1,13 @@
<svg
stroke="currentColor"
fill="currentColor"
stroke-width="0"
viewBox="0 0 384 512"
height="1em"
width="1em"
xmlns="http://www.w3.org/2000/svg"
{...$$restProps}
><path
d="M318.7 268.7c-.2-36.7 16.4-64.4 50-84.8-18.8-26.9-47.2-41.7-84.7-44.6-35.5-2.8-74.3 20.7-88.5 20.7-15 0-49.4-19.7-76.4-19.7C63.3 141.2 4 184.8 4 273.5q0 39.3 14.4 81.2c12.8 36.7 59 126.7 107.2 125.2 25.2-.6 43-17.9 75.8-17.9 31.8 0 48.3 17.9 76.4 17.9 48.6-.7 90.4-82.5 102.6-119.3-65.2-30.7-61.7-90-61.7-91.9zm-56.6-164.2c27.3-32.4 24.8-61.9 24-72.5-24.1 1.4-52 16.4-67.9 34.9-17.5 19.8-27.8 44.3-25.6 71.9 26.1 2 49.9-11.4 69.5-34.3z"
></path></svg
>

After

Width:  |  Height:  |  Size: 638 B

View file

@ -0,0 +1,13 @@
<svg
stroke="currentColor"
fill="currentColor"
stroke-width="0"
viewBox="0 0 512 512"
height="1em"
width="1em"
xmlns="http://www.w3.org/2000/svg"
{...$$restProps}
><path
d="M22.2 32A16 16 0 0 0 6 47.8a26.35 26.35 0 0 0 .2 2.8l67.9 412.1a21.77 21.77 0 0 0 21.3 18.2h325.7a16 16 0 0 0 16-13.4L505 50.7a16 16 0 0 0-13.2-18.3 24.58 24.58 0 0 0-2.8-.2L22.2 32zm285.9 297.8h-104l-28.1-147h157.3l-25.2 147z"
></path></svg
>

After

Width:  |  Height:  |  Size: 429 B

View file

@ -0,0 +1,13 @@
<svg
stroke="currentColor"
fill="currentColor"
stroke-width="0"
viewBox="0 0 640 512"
height="1em"
width="1em"
xmlns="http://www.w3.org/2000/svg"
{...$$restProps}
><path
d="M524.531,69.836a1.5,1.5,0,0,0-.764-.7A485.065,485.065,0,0,0,404.081,32.03a1.816,1.816,0,0,0-1.923.91,337.461,337.461,0,0,0-14.9,30.6,447.848,447.848,0,0,0-134.426,0,309.541,309.541,0,0,0-15.135-30.6,1.89,1.89,0,0,0-1.924-.91A483.689,483.689,0,0,0,116.085,69.137a1.712,1.712,0,0,0-.788.676C39.068,183.651,18.186,294.69,28.43,404.354a2.016,2.016,0,0,0,.765,1.375A487.666,487.666,0,0,0,176.02,479.918a1.9,1.9,0,0,0,2.063-.676A348.2,348.2,0,0,0,208.12,430.4a1.86,1.86,0,0,0-1.019-2.588,321.173,321.173,0,0,1-45.868-21.853,1.885,1.885,0,0,1-.185-3.126c3.082-2.309,6.166-4.711,9.109-7.137a1.819,1.819,0,0,1,1.9-.256c96.229,43.917,200.41,43.917,295.5,0a1.812,1.812,0,0,1,1.924.233c2.944,2.426,6.027,4.851,9.132,7.16a1.884,1.884,0,0,1-.162,3.126,301.407,301.407,0,0,1-45.89,21.83,1.875,1.875,0,0,0-1,2.611,391.055,391.055,0,0,0,30.014,48.815,1.864,1.864,0,0,0,2.063.7A486.048,486.048,0,0,0,610.7,405.729a1.882,1.882,0,0,0,.765-1.352C623.729,277.594,590.933,167.465,524.531,69.836ZM222.491,337.58c-28.972,0-52.844-26.587-52.844-59.239S193.056,219.1,222.491,219.1c29.665,0,53.306,26.82,52.843,59.239C275.334,310.993,251.924,337.58,222.491,337.58Zm195.38,0c-28.971,0-52.843-26.587-52.843-59.239S388.437,219.1,417.871,219.1c29.667,0,53.307,26.82,52.844,59.239C470.715,310.993,447.538,337.58,417.871,337.58Z"
></path></svg
>

After

Width:  |  Height:  |  Size: 1.5 KiB

View file

@ -0,0 +1,13 @@
<svg
stroke="currentColor"
fill="currentColor"
stroke-width="0"
viewBox="0 0 448 512"
height="1em"
width="1em"
xmlns="http://www.w3.org/2000/svg"
{...$$restProps}
><path
d="M400 32H48A48 48 0 0 0 0 80v352a48 48 0 0 0 48 48h137.25V327.69h-63V256h63v-54.64c0-62.15 37-96.48 93.67-96.48 27.14 0 55.52 4.84 55.52 4.84v61h-31.27c-30.81 0-40.42 19.12-40.42 38.73V256h68.78l-11 71.69h-57.78V480H400a48 48 0 0 0 48-48V80a48 48 0 0 0-48-48z"
></path></svg
>

After

Width:  |  Height:  |  Size: 461 B

View file

@ -0,0 +1,4 @@
<svg viewBox="0 0 24 24" {...$$restProps}>
<path d="M0 0h24v24H0z" />
<path d="M3 19h18l-9 -15z" />
</svg>

After

Width:  |  Height:  |  Size: 109 B

View file

@ -0,0 +1,13 @@
<svg
stroke="currentColor"
fill="currentColor"
stroke-width="0"
viewBox="0 0 512 512"
height="1em"
width="1em"
xmlns="http://www.w3.org/2000/svg"
{...$$restProps}
><path
d="M503.5 204.6L502.8 202.8L433.1 21.02C431.7 17.45 429.2 14.43 425.9 12.38C423.5 10.83 420.8 9.865 417.9 9.57C415 9.275 412.2 9.653 409.5 10.68C406.8 11.7 404.4 13.34 402.4 15.46C400.5 17.58 399.1 20.13 398.3 22.9L351.3 166.9H160.8L113.7 22.9C112.9 20.13 111.5 17.59 109.6 15.47C107.6 13.35 105.2 11.72 102.5 10.7C99.86 9.675 96.98 9.295 94.12 9.587C91.26 9.878 88.51 10.83 86.08 12.38C82.84 14.43 80.33 17.45 78.92 21.02L9.267 202.8L8.543 204.6C-1.484 230.8-2.72 259.6 5.023 286.6C12.77 313.5 29.07 337.3 51.47 354.2L51.74 354.4L52.33 354.8L158.3 434.3L210.9 474L242.9 498.2C246.6 500.1 251.2 502.5 255.9 502.5C260.6 502.5 265.2 500.1 268.9 498.2L300.9 474L353.5 434.3L460.2 354.4L460.5 354.1C482.9 337.2 499.2 313.5 506.1 286.6C514.7 259.6 513.5 230.8 503.5 204.6z"
></path></svg
>

After

Width:  |  Height:  |  Size: 967 B

View file

@ -0,0 +1,13 @@
<svg
stroke="currentColor"
fill="currentColor"
stroke-width="0"
viewBox="0 0 488 512"
height="1em"
width="1em"
xmlns="http://www.w3.org/2000/svg"
{...$$restProps}
><path
d="M488 261.8C488 403.3 391.1 504 248 504 110.8 504 0 393.2 0 256S110.8 8 248 8c66.8 0 123 24.5 166.3 64.9l-67.5 64.9C258.5 52.6 94.3 116.6 94.3 256c0 86.5 69.1 156.6 153.7 156.6 98.2 0 135-70.4 140.8-106.9H248v-85.3h236.1c2.3 12.7 3.9 24.9 3.9 41.4z"
></path></svg
>

After

Width:  |  Height:  |  Size: 450 B

View file

@ -0,0 +1,35 @@
import type { Icon as LucideIcon } from 'lucide-svelte';
import { ArrowRight, Loader2 } from 'lucide-svelte';
import { GithubLogo, VercelLogo, LinkedinLogo } from 'radix-icons-svelte';
import Logo from './logo.svelte';
import Svelte from './svelte.svelte';
import MicrosoftLogo from './microsoft.svelte';
import AppleLogo from './apple.svelte';
import GitLabLogo from './gitlab.svelte';
import BitBucketLogo from './bitbucket.svelte';
import DiscordLogo from './discord.svelte';
import FacebookLogo from './facebook.svelte';
import GoogleLogo from './google.svelte';
import InstagramLogo from './instagram.svelte';
import TwitterLogo from './twitter.svelte';
export type Icon = LucideIcon;
export const Icons = {
logo: Logo,
gitHub: GithubLogo,
microsoft: MicrosoftLogo,
svelte: Svelte,
vercel: VercelLogo,
linkedIn: LinkedinLogo,
spinner: Loader2,
arrowRight: ArrowRight,
apple: AppleLogo,
bitBucket: BitBucketLogo,
gitLab: GitLabLogo,
discord: DiscordLogo,
facebook: FacebookLogo,
google: GoogleLogo,
instagram: InstagramLogo,
twitter: TwitterLogo
};

View file

@ -0,0 +1,13 @@
<svg
stroke="currentColor"
fill="currentColor"
stroke-width="0"
viewBox="0 0 448 512"
height="1em"
width="1em"
xmlns="http://www.w3.org/2000/svg"
{...$$restProps}
><path
d="M224.1 141c-63.6 0-114.9 51.3-114.9 114.9s51.3 114.9 114.9 114.9S339 319.5 339 255.9 287.7 141 224.1 141zm0 189.6c-41.1 0-74.7-33.5-74.7-74.7s33.5-74.7 74.7-74.7 74.7 33.5 74.7 74.7-33.6 74.7-74.7 74.7zm146.4-194.3c0 14.9-12 26.8-26.8 26.8-14.9 0-26.8-12-26.8-26.8s12-26.8 26.8-26.8 26.8 12 26.8 26.8zm76.1 27.2c-1.7-35.9-9.9-67.7-36.2-93.9-26.2-26.2-58-34.4-93.9-36.2-37-2.1-147.9-2.1-184.9 0-35.8 1.7-67.6 9.9-93.9 36.1s-34.4 58-36.2 93.9c-2.1 37-2.1 147.9 0 184.9 1.7 35.9 9.9 67.7 36.2 93.9s58 34.4 93.9 36.2c37 2.1 147.9 2.1 184.9 0 35.9-1.7 67.7-9.9 93.9-36.2 26.2-26.2 34.4-58 36.2-93.9 2.1-37 2.1-147.8 0-184.8zM398.8 388c-7.8 19.6-22.9 34.7-42.6 42.6-29.5 11.7-99.5 9-132.1 9s-102.7 2.6-132.1-9c-19.6-7.8-34.7-22.9-42.6-42.6-11.7-29.5-9-99.5-9-132.1s-2.6-102.7 9-132.1c7.8-19.6 22.9-34.7 42.6-42.6 29.5-11.7 99.5-9 132.1-9s102.7-2.6 132.1 9c19.6 7.8 34.7 22.9 42.6 42.6 11.7 29.5 9 99.5 9 132.1s2.7 102.7-9 132.1z"
></path></svg
>

After

Width:  |  Height:  |  Size: 1.1 KiB

View file

@ -0,0 +1,24 @@
<script>
export let colours = {
topLeft: '#FF6C22',
topRight: '#FF9209',
bottomLeft: '#FFD099',
bottomRight: '#3B48D3'
};
</script>
<svg
xmlns="http://www.w3.org/2000/svg"
width="24"
height="24"
viewBox="0 0 24 24"
fill="none"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
>
<rect width="7" height="9" x="3" y="3" rx="1" stroke={colours.topLeft} />
<rect width="7" height="5" x="14" y="3" rx="1" stroke={colours.topRight} />
<rect width="7" height="5" x="3" y="16" rx="1" stroke={colours.bottomLeft} />
<rect width="7" height="9" x="14" y="12" rx="1" stroke={colours.bottomRight} />
</svg>

View file

@ -0,0 +1,12 @@
<svg
stroke="currentColor"
fill="currentColor"
stroke-width="0"
version="1.2"
baseProfile="tiny"
viewBox="0 0 24 24"
{...$$restProps}
><path
d="M10 12.5c0-.3-.2-.5-.5-.5h-6c-.3 0-.5.2-.5.5v5c0 .3.2.5.5.6l6 .7c.3 0 .5-.2.5-.4v-5.9zM11.5 12c-.3 0-.5.2-.5.5v5.9c0 .3.2.5.5.6l9 1c.3 0 .5-.2.5-.4v-7c0-.3-.2-.5-.5-.5l-9-.1zM10 4.7c0-.3-.2-.5-.5-.4l-6 .7c-.3 0-.5.2-.5.5v5c0 .3.2.5.5.5h6c.3 0 .5-.2.5-.5v-5.8zM11.5 4.1c-.3 0-.5.3-.5.6v5.9c0 .3.2.5.5.5h9c.3 0 .5-.2.5-.5v-7c0-.3-.2-.5-.5-.4l-9 .9z"
></path></svg
>

After

Width:  |  Height:  |  Size: 519 B

View file

@ -0,0 +1,15 @@
<svg
class="inline-svg"
stroke="currentColor"
fill="currentColor"
stroke-width="0"
role="img"
viewBox="0 0 24 24"
height="1em"
width="1em"
xmlns="http://www.w3.org/2000/svg"
{...$$restProps}
><path
d="M10.354 21.125a4.44 4.44 0 0 1-4.765-1.767 4.109 4.109 0 0 1-.703-3.107 3.898 3.898 0 0 1 .134-.522l.105-.321.287.21a7.21 7.21 0 0 0 2.186 1.092l.208.063-.02.208a1.253 1.253 0 0 0 .226.83 1.337 1.337 0 0 0 1.435.533 1.231 1.231 0 0 0 .343-.15l5.59-3.562a1.164 1.164 0 0 0 .524-.778 1.242 1.242 0 0 0-.211-.937 1.338 1.338 0 0 0-1.435-.533 1.23 1.23 0 0 0-.343.15l-2.133 1.36a4.078 4.078 0 0 1-1.135.499 4.44 4.44 0 0 1-4.765-1.766 4.108 4.108 0 0 1-.702-3.108 3.855 3.855 0 0 1 1.742-2.582l5.589-3.563a4.072 4.072 0 0 1 1.135-.499 4.44 4.44 0 0 1 4.765 1.767 4.109 4.109 0 0 1 .703 3.107 3.943 3.943 0 0 1-.134.522l-.105.321-.286-.21a7.204 7.204 0 0 0-2.187-1.093l-.208-.063.02-.207a1.255 1.255 0 0 0-.226-.831 1.337 1.337 0 0 0-1.435-.532 1.231 1.231 0 0 0-.343.15L8.62 9.368a1.162 1.162 0 0 0-.524.778 1.24 1.24 0 0 0 .211.937 1.338 1.338 0 0 0 1.435.533 1.235 1.235 0 0 0 .344-.151l2.132-1.36a4.067 4.067 0 0 1 1.135-.498 4.44 4.44 0 0 1 4.765 1.766 4.108 4.108 0 0 1 .702 3.108 3.857 3.857 0 0 1-1.742 2.583l-5.589 3.562a4.072 4.072 0 0 1-1.135.499m10.358-17.95C18.484-.015 14.082-.96 10.9 1.068L5.31 4.63a6.412 6.412 0 0 0-2.896 4.295 6.753 6.753 0 0 0 .666 4.336 6.43 6.43 0 0 0-.96 2.396 6.833 6.833 0 0 0 1.168 5.167c2.229 3.19 6.63 4.135 9.812 2.108l5.59-3.562a6.41 6.41 0 0 0 2.896-4.295 6.756 6.756 0 0 0-.665-4.336 6.429 6.429 0 0 0 .958-2.396 6.831 6.831 0 0 0-1.167-5.168Z"
/></svg
>

After

Width:  |  Height:  |  Size: 1.6 KiB

View file

@ -0,0 +1,13 @@
<svg
stroke="currentColor"
fill="currentColor"
stroke-width="0"
viewBox="0 0 512 512"
height="1em"
width="1em"
xmlns="http://www.w3.org/2000/svg"
{...$$restProps}
><path
d="M459.37 151.716c.325 4.548.325 9.097.325 13.645 0 138.72-105.583 298.558-298.558 298.558-59.452 0-114.68-17.219-161.137-47.106 8.447.974 16.568 1.299 25.34 1.299 49.055 0 94.213-16.568 130.274-44.832-46.132-.975-84.792-31.188-98.112-72.772 6.498.974 12.995 1.624 19.818 1.624 9.421 0 18.843-1.3 27.614-3.573-48.081-9.747-84.143-51.98-84.143-102.985v-1.299c13.969 7.797 30.214 12.67 47.431 13.319-28.264-18.843-46.781-51.005-46.781-87.391 0-19.492 5.197-37.36 14.294-52.954 51.655 63.675 129.3 105.258 216.365 109.807-1.624-7.797-2.599-15.918-2.599-24.04 0-57.828 46.782-104.934 104.934-104.934 30.213 0 57.502 12.67 76.67 33.137 23.715-4.548 46.456-13.32 66.599-25.34-7.798 24.366-24.366 44.833-46.132 57.827 21.117-2.273 41.584-8.122 60.426-16.243-14.292 20.791-32.161 39.308-52.628 54.253z"
></path></svg
>

After

Width:  |  Height:  |  Size: 994 B

View file

@ -0,0 +1,4 @@
<svg viewBox="0 0 24 24" {...$$restProps}>
<path stroke="none" d="M0 0h24v24H0z" />
<path d="M3 19h18l-9 -15z" />
</svg>

After

Width:  |  Height:  |  Size: 123 B

View file

@ -0,0 +1,9 @@
export { default as Metadata } from './metadata.svelte';
export { default as SiteFooter } from './site-footer.svelte';
export { default as SiteNavBar } from './site-navbar.svelte';
export { default as TailwindIndicator } from './tailwind-indicator.svelte';
export { default as ModeToggle } from './mode-toggle.svelte';
export { default as Particles } from './particles.svelte';
export * from './icons';
export * from './nav';

View file

@ -0,0 +1,36 @@
<script lang="ts">
import { page } from '$app/stores';
import { siteConfig } from '$lib/config/site';
export let title: string = siteConfig.name;
$: title = $page.data?.name ? `${$page.data.name} — ${siteConfig.name}` : siteConfig.name;
$: description = $page.data?.subTitle ?? siteConfig.description;
$: ogImage = encodeURI(
`${siteConfig.ogImage}?title=${$page.data.title}&subTitle=${$page.data.subTitle}`
);
</script>
<svelte:head>
<title>{title}</title>
<meta name="description" content={description} />
<meta name="keywords" content={siteConfig.keywords} />
<meta name="author" content="Bart van der Braak" />
<meta name="twitter:card" content="summary_large_image" />
<meta name="twitter:site" content={siteConfig.url} />
<meta name="twitter:title" content={title} />
<meta name="twitter:description" content={description} />
<meta name="twitter:image" content={ogImage} />
<meta name="twitter:image:alt" content={siteConfig.name} />
<meta name="twitter:creator" content="Bart van der Braak" />
<meta property="og:title" content={title} />
<meta property="og:type" content="article" />
<meta property="og:url" content={siteConfig.url + $page.url.pathname} />
<meta property="og:image" content={ogImage} />
<meta property="og:image:alt" content={siteConfig.name} />
<meta property="og:image:width" content="1200" />
<meta property="og:image:height" content="630" />
<meta property="og:description" content={description} />
<meta property="og:site_name" content={siteConfig.name} />
<meta property="og:locale" content="EN_US" />
</svelte:head>

View file

@ -0,0 +1,25 @@
<script lang="ts">
import { Moon, Sun } from 'lucide-svelte';
import { Button } from '../ui/button';
import * as DropdownMenu from '../ui/dropdown-menu';
import { resetMode, setMode } from 'mode-watcher';
</script>
<DropdownMenu.Root>
<DropdownMenu.Trigger asChild let:builder>
<Button builders={[builder]} variant="ghost" class="h-9 w-9">
<Sun
class="absolute h-4 w-4 rotate-0 scale-100 transition-all dark:-rotate-90 dark:scale-0"
/>
<Moon
class="absolute h-4 w-4 rotate-90 scale-0 transition-all dark:rotate-0 dark:scale-100"
/>
<span class="sr-only">Toggle theme</span>
</Button>
</DropdownMenu.Trigger>
<DropdownMenu.Content align="end">
<DropdownMenu.Item on:click={() => setMode('light')}>Light</DropdownMenu.Item>
<DropdownMenu.Item on:click={() => setMode('dark')}>Dark</DropdownMenu.Item>
<DropdownMenu.Item on:click={() => resetMode()}>System</DropdownMenu.Item>
</DropdownMenu.Content>
</DropdownMenu.Root>

View file

@ -0,0 +1,2 @@
export { default as MainNav } from './main-nav.svelte';
export { default as MobileNav } from './mobile-nav.svelte';

View file

@ -0,0 +1,25 @@
<script lang="ts">
import { page } from '$app/stores';
import { cn } from '$lib/utils';
import { navConfig } from '$lib/config/nav';
export let authenticated = false;
</script>
<div class="mr-4 hidden md:flex">
<nav class="flex items-center space-x-6 text-sm font-medium">
{#each navConfig.mainNav as navItem, index (navItem + index.toString())}
{#if navItem.href && (navItem.auth == authenticated || navItem.always)}
<a
href={navItem.href}
class={cn(
'transition-colors hover:text-foreground/80',
$page.url.pathname === navItem.href ? 'text-foreground' : 'text-foreground/60'
)}
>
{navItem.title}
</a>
{/if}
{/each}
</nav>
</div>

View file

@ -0,0 +1,23 @@
<script lang="ts">
import { page } from '$app/stores';
import { cn } from '$lib/utils';
export let href: string;
export let open: boolean;
let className: string | undefined | null = undefined;
export { className as class };
</script>
<a
{href}
on:click={() => (open = false)}
class={cn(
$page.url.pathname === href ? 'text-foreground' : 'text-foreground/60',
'hover:text-foreground',
className
)}
{...$$restProps}
>
<slot />
</a>

View file

@ -0,0 +1,68 @@
<script lang="ts">
import * as Sheet from '$lib/components/ui/sheet/';
import { HamburgerMenu } from 'radix-icons-svelte';
import { Button } from '$lib/components/ui/button';
import { navConfig } from '$lib/config/nav';
import { siteConfig } from '$lib/config/site';
import { Icons } from '../icons';
import MobileLink from './mobile-link.svelte';
let open = false;
export let authenticated = false;
</script>
<Sheet.Root bind:open>
<Sheet.Trigger asChild let:builder>
<Button
builders={[builder]}
variant="ghost"
class="mr-2 px-0 text-base hover:bg-transparent focus-visible:bg-transparent focus-visible:ring-0 focus-visible:ring-offset-0 md:hidden"
>
<HamburgerMenu class="h-5 w-5" />
<span class="sr-only">Toggle Menu</span>
</Button>
</Sheet.Trigger>
<Sheet.Content side="right" class="pr-0">
<MobileLink href="/" class="flex items-center" bind:open>
<span class="sr-only">Logo icon (return home)</span>
<div class="mr-4 rounded-sm bg-gray-950 p-0.5 dark:bg-transparent">
<Icons.logo />
</div>
<span class="font-mono font-bold tracking-tighter">{siteConfig.name}</span>
</MobileLink>
<div class="my-4 h-[calc(100vh-8rem)] overflow-auto pl-1 pt-10">
<div class="flex flex-col space-y-3">
{#each navConfig.mainNav as navItem, index (navItem + index.toString())}
{#if navItem.href && (navItem.auth == authenticated || navItem.always)}
<MobileLink href={navItem.href} bind:open class="pt-2 text-5xl font-bold">
{navItem.title}
</MobileLink>
{/if}
{/each}
</div>
<div class="flex flex-col space-y-2">
{#each navConfig.sidebarNav as navItem, index (index)}
<div class="flex flex-col space-y-3 pt-6">
<h4 class="font-medium">{navItem.title}</h4>
{#if navItem?.items?.length}
{#each navItem.items as item}
{#if !item.disabled && item.href}
<MobileLink href={item.href} bind:open class="text-muted-foreground">
{item.title}
{#if item.label}
<span
class="ml-2 rounded-md bg-[#adfa1d] px-1.5 py-0.5 text-xs leading-none text-[#000000]"
>
{item.label}
</span>
{/if}
</MobileLink>
{/if}
{/each}
{/if}
</div>
{/each}
</div>
</div>
</Sheet.Content>
</Sheet.Root>

View file

@ -0,0 +1,49 @@
<script lang="ts">
import * as Avatar from '$lib/components/ui/avatar';
import { Button } from '$lib/components/ui/button';
import * as DropdownMenu from '$lib/components/ui/dropdown-menu';
import type { BaseAuthStore } from 'pocketbase';
export let authenticated = false;
export let user: BaseAuthStore['model'];
</script>
{#if authenticated}
<DropdownMenu.Root>
<DropdownMenu.Trigger asChild let:builder>
<Button variant="ghost" builders={[builder]} class="relative h-8 w-8 rounded-full">
<Avatar.Root class="h-9 w-9">
<Avatar.Image src={user?.avatarUrl} alt={user?.name} />
<Avatar.Fallback>{user?.initials}</Avatar.Fallback>
</Avatar.Root>
</Button>
</DropdownMenu.Trigger>
<DropdownMenu.Content class="w-56" align="end">
<DropdownMenu.Label class="font-normal">
<div class="flex flex-col space-y-1">
<p class="text-sm font-medium leading-none">{user?.name || user?.username}</p>
<p class="text-xs leading-none text-muted-foreground">{user?.email}</p>
</div>
</DropdownMenu.Label>
<DropdownMenu.Separator />
<DropdownMenu.Item>Dashboards</DropdownMenu.Item>
<DropdownMenu.Item>Connectors</DropdownMenu.Item>
<DropdownMenu.Separator />
<DropdownMenu.Label class="text-xs leading-none text-muted-foreground">
Settings
</DropdownMenu.Label>
<DropdownMenu.Group>
<DropdownMenu.Item href="/settings">Profile</DropdownMenu.Item>
<DropdownMenu.Item href="/settings/appearance">Appearance</DropdownMenu.Item>
<DropdownMenu.Item href="/settings/notifications">Notifications</DropdownMenu.Item>
</DropdownMenu.Group>
<DropdownMenu.Separator />
<DropdownMenu.Item href="/logout">
Log out
<DropdownMenu.Shortcut>⇧⌘Q</DropdownMenu.Shortcut>
</DropdownMenu.Item>
</DropdownMenu.Content>
</DropdownMenu.Root>
{:else}
<Button href="/auth">Login</Button>
{/if}

View file

@ -0,0 +1,248 @@
<script>
import { mode } from 'mode-watcher';
import { onMount, beforeUpdate, onDestroy } from 'svelte';
import { writable } from 'svelte/store';
const mousePositionStore = writable({ x: 0, y: 0 });
let x = 0;
let y = 0;
const handleMouseMove = (/** @type {{ clientX: number; clientY: number; }} */ event) => {
x = event.clientX;
y = event.clientY;
mousePositionStore.set({ x, y });
};
export let className = 'h-full';
export let quantity = 30;
export let staticity = 50;
export let ease = 50;
export let vx = 0;
export let vy = 0;
let color = '#ffffff';
let rgb = hexToRgb(color);
/**
* @type {HTMLCanvasElement}
*/
let canvasRef;
/**
* @type {HTMLDivElement}
*/
let canvasContainerRef;
/**
* @type {CanvasRenderingContext2D | null}
*/
let context;
/**
* @type {any[]}
*/
let circles = [];
let mousePosition = mousePositionStore;
let mouse = { x: 0, y: 0 };
let canvasSize = { w: 0, h: 0 };
let dpr = typeof window !== 'undefined' ? window.devicePixelRatio : 1;
/**
* @param {string} hex
*/
function hexToRgb(hex) {
hex = hex.replace('#', '');
const hexInt = parseInt(hex, 16);
const red = (hexInt >> 16) & 255;
const green = (hexInt >> 8) & 255;
const blue = hexInt & 255;
return [red, green, blue];
}
mode.subscribe((value) => {
color = value === 'dark' ? '#ffffff' : '#000000';
rgb = hexToRgb(color);
});
/**
* @param {{ x: any; y: any; translateX: any; translateY: any; size: any; alpha: any; targetAlpha?: number; dx?: number; dy?: number; magnetism?: number; }} circle
*/
function drawCircle(circle, update = false) {
if (context) {
const { x, y, translateX, translateY, size, alpha } = circle;
context.translate(translateX, translateY);
context.beginPath();
context.arc(x, y, size, 0, 2 * Math.PI);
context.fillStyle = `rgba(${rgb.join(', ')}, ${alpha})`;
context.fill();
context.setTransform(dpr, 0, 0, dpr, 0, 0);
if (!update) {
circles.push(circle);
}
}
}
function initCanvas() {
resizeCanvas();
drawParticles();
}
function onMouseMove() {
if (canvasRef) {
const rect = canvasRef.getBoundingClientRect();
const { w, h } = canvasSize;
const x = $mousePosition.x - rect.left - w / 2;
const y = $mousePosition.y - rect.top - h / 2;
const inside = x < w / 2 && x > -w / 2 && y < h / 2 && y > -h / 2;
if (inside) {
mouse.x = x;
mouse.y = y;
}
}
}
function resizeCanvas() {
if (canvasContainerRef && canvasRef && context) {
circles = [];
canvasSize.w = canvasContainerRef.offsetWidth;
canvasSize.h = canvasContainerRef.offsetHeight;
canvasRef.width = canvasSize.w * dpr;
canvasRef.height = canvasSize.h * dpr;
canvasRef.style.width = `${canvasSize.w}px`;
canvasRef.style.height = `${canvasSize.h}px`;
context.scale(dpr, dpr);
}
}
function circleParams() {
const x = Math.floor(Math.random() * canvasSize.w);
const y = Math.floor(Math.random() * canvasSize.h);
const translateX = 0;
const translateY = 0;
const size = Math.floor(Math.random() * 2) + 1;
const alpha = 0;
const targetAlpha = parseFloat((Math.random() * 0.6 + 0.1).toFixed(1));
const dx = (Math.random() - 0.5) * 0.2;
const dy = (Math.random() - 0.5) * 0.2;
const magnetism = 0.1 + Math.random() * 4;
return {
x,
y,
translateX,
translateY,
size,
alpha,
targetAlpha,
dx,
dy,
magnetism
};
}
function clearContext() {
if (context) {
context.clearRect(0, 0, canvasSize.w, canvasSize.h);
}
}
function drawParticles() {
clearContext();
const particleCount = quantity;
for (let i = 0; i < particleCount; i++) {
const circle = circleParams();
drawCircle(circle);
}
}
/**
* @param {number} value
* @param {number} start1
* @param {number} end1
* @param {number} start2
* @param {number} end2
*/
function remapValue(value, start1, end1, start2, end2) {
const remapped = ((value - start1) * (end2 - start2)) / (end1 - start1) + start2;
return remapped > 0 ? remapped : 0;
}
function animate() {
clearContext();
circles.forEach((circle, i) => {
const edge = [
circle.x + circle.translateX - circle.size,
canvasSize.w - circle.x - circle.translateX - circle.size,
circle.y + circle.translateY - circle.size,
canvasSize.h - circle.y - circle.translateY - circle.size
];
const closestEdge = edge.reduce((a, b) => Math.min(a, b));
const remapClosestEdge = parseFloat(remapValue(closestEdge, 0, 20, 0, 1).toFixed(2));
if (remapClosestEdge > 1) {
circle.alpha += 0.02;
if (circle.alpha > circle.targetAlpha) {
circle.alpha = circle.targetAlpha;
}
} else {
circle.alpha = circle.targetAlpha * remapClosestEdge;
}
circle.x += circle.dx + vx;
circle.y += circle.dy + vy;
circle.translateX += (mouse.x / (staticity / circle.magnetism) - circle.translateX) / ease;
circle.translateY += (mouse.y / (staticity / circle.magnetism) - circle.translateY) / ease;
if (
circle.x < -circle.size ||
circle.x > canvasSize.w + circle.size ||
circle.y < -circle.size ||
circle.y > canvasSize.h + circle.size
) {
circles.splice(i, 1);
const newCircle = circleParams();
drawCircle(newCircle);
} else {
drawCircle(
{
...circle,
x: circle.x,
y: circle.y,
translateX: circle.translateX,
translateY: circle.translateY,
alpha: circle.alpha
},
true
);
}
});
setTimeout(() => {
requestAnimationFrame(animate);
}, 1000 / 60); // Limit the frame rate to 60 FPS
}
onMount(() => {
window.addEventListener('mousemove', handleMouseMove);
if (canvasRef) {
context = canvasRef?.getContext('2d');
}
initCanvas();
animate();
window.addEventListener('resize', initCanvas);
return () => {
window.removeEventListener('resize', initCanvas);
window.removeEventListener('mousemove', handleMouseMove);
};
});
beforeUpdate(() => {
onMouseMove();
});
onDestroy(() => {
if (canvasRef) {
window.removeEventListener('resize', initCanvas);
}
});
</script>
<div class={className} bind:this={canvasContainerRef} aria-hidden="true">
<canvas bind:this={canvasRef}></canvas>
</div>

View file

@ -0,0 +1,12 @@
<script lang="ts">
import { siteConfig } from '$lib/config/site';
</script>
<footer class="container py-6">
<div class="space-y-1">
<p class="text-center text-sm text-muted-foreground">
&copy; {new Date().getFullYear()} &mdash; {siteConfig.name} by {siteConfig.author}. Licensed
under GPL-3.0.
</p>
</div>
</footer>

View file

@ -0,0 +1,28 @@
<script lang="ts">
import { Icons, ModeToggle, MainNav, MobileNav } from '$lib/components/site';
import { siteConfig } from '$lib/config/site';
import UserNav from './nav/user-nav.svelte';
export let authenticated = false;
export let user: object | null = null;
</script>
<header
class="sticky top-0 z-50 w-full border-b bg-background/95 shadow-sm backdrop-blur supports-[backdrop-filter]:bg-background/60"
>
<div class="container flex h-14 items-center">
<a href="/" class="mr-6 flex items-center space-x-2">
<span class="sr-only">Logo (return home)</span>
<div class="rounded-sm bg-gray-950 p-0.5 dark:bg-transparent">
<Icons.logo />
</div>
<span class="text-xl font-bold tracking-tight">{siteConfig.name}</span>
</a>
<MainNav {authenticated} />
<div class="flex flex-1 items-center justify-end space-x-2 sm:space-x-4">
<ModeToggle />
<UserNav {authenticated} {user} />
<MobileNav {authenticated} />
</div>
</div>
</header>

View file

@ -0,0 +1,14 @@
<script>
import { Button } from '$lib/components/ui/button';
</script>
<Button variant="ghost" size="icon" class="block sm:hidden">xs</Button>
<Button variant="ghost" size="icon" class="hidden sm:block md:hidden lg:hidden xl:hidden 2xl:hidden"
>sm</Button
>
<Button variant="ghost" size="icon" class="hidden md:block lg:hidden xl:hidden 2xl:hidden"
>md</Button
>
<Button variant="ghost" size="icon" class="hidden lg:block xl:hidden 2xl:hidden">lg</Button>
<Button variant="ghost" size="icon" class="hidden xl:block 2xl:hidden">xl</Button>
<Button variant="ghost" size="icon" class="hidden 2xl:block">2xl</Button>