Rewrite page in Svelte 5 #49

Merged
Neshura merged 94 commits from rewrite/svelte into main 2024-01-06 14:12:09 +00:00
11 changed files with 556 additions and 526 deletions
Showing only changes of commit eeaeca1006 - Show all commits

View file

@ -7,9 +7,9 @@
%sveltekit.head% %sveltekit.head%
</head> </head>
<body <body
data-sveltekit-preload-data="hover" data-sveltekit-preload-data="hover"
style="background-image: url('/assets/background.avif')" style="background-image: url('/assets/background.avif')"
class="overflow-hidden h-screen" class="h-screen overflow-hidden"
> >
<div style="display: contents">%sveltekit.body%</div> <div style="display: contents">%sveltekit.body%</div>
</body> </body>

View file

@ -3,113 +3,113 @@
@tailwind utilities; @tailwind utilities;
@layer base { @layer base {
:root { :root {
--background: 0 0% 100%; --background: 0 0% 100%;
--foreground: 222.2 84% 4.9%; --foreground: 222.2 84% 4.9%;
--muted: 210 40% 96.1%; --muted: 210 40% 96.1%;
--muted-foreground: 215.4 16.3% 46.9%; --muted-foreground: 215.4 16.3% 46.9%;
--popover: 0 0% 100%; --popover: 0 0% 100%;
--popover-foreground: 222.2 84% 4.9%; --popover-foreground: 222.2 84% 4.9%;
--card: 0 0% 100%; --card: 0 0% 100%;
--card-foreground: 222.2 84% 4.9%; --card-foreground: 222.2 84% 4.9%;
--border: 214.3 31.8% 91.4%; --border: 214.3 31.8% 91.4%;
--input: 214.3 31.8% 91.4%; --input: 214.3 31.8% 91.4%;
--primary: 222.2 47.4% 11.2%; --primary: 222.2 47.4% 11.2%;
--primary-foreground: 210 40% 98%; --primary-foreground: 210 40% 98%;
--secondary: 210 40% 96.1%; --secondary: 210 40% 96.1%;
--secondary-foreground: 222.2 47.4% 11.2%; --secondary-foreground: 222.2 47.4% 11.2%;
--accent: 210 40% 96.1%; --accent: 210 40% 96.1%;
--accent-foreground: 222.2 47.4% 11.2%; --accent-foreground: 222.2 47.4% 11.2%;
--destructive: 0 72.2% 50.6%; --destructive: 0 72.2% 50.6%;
--destructive-foreground: 210 40% 98%; --destructive-foreground: 210 40% 98%;
--ring: 222.2 84% 4.9%; --ring: 222.2 84% 4.9%;
--radius: 0.5rem; --radius: 0.5rem;
} }
.dark { .dark {
--background: 222.2 84% 4.9%; --background: 222.2 84% 4.9%;
--foreground: 210 40% 98%; --foreground: 210 40% 98%;
--muted: 217.2 32.6% 17.5%; --muted: 217.2 32.6% 17.5%;
--muted-foreground: 215 20.2% 65.1%; --muted-foreground: 215 20.2% 65.1%;
--popover: 222.2 84% 4.9%; --popover: 222.2 84% 4.9%;
--popover-foreground: 210 40% 98%; --popover-foreground: 210 40% 98%;
--card: 222.2 84% 4.9%; --card: 222.2 84% 4.9%;
--card-foreground: 210 40% 98%; --card-foreground: 210 40% 98%;
--border: 217.2 32.6% 17.5%; --border: 217.2 32.6% 17.5%;
--input: 217.2 32.6% 17.5%; --input: 217.2 32.6% 17.5%;
--primary: 210 40% 98%; --primary: 210 40% 98%;
--primary-foreground: 222.2 47.4% 11.2%; --primary-foreground: 222.2 47.4% 11.2%;
--secondary: 217.2 32.6% 17.5%; --secondary: 217.2 32.6% 17.5%;
--secondary-foreground: 210 40% 98%; --secondary-foreground: 210 40% 98%;
--accent: 217.2 32.6% 17.5%; --accent: 217.2 32.6% 17.5%;
--accent-foreground: 210 40% 98%; --accent-foreground: 210 40% 98%;
--destructive: 0 62.8% 30.6%; --destructive: 0 62.8% 30.6%;
--destructive-foreground: 210 40% 98%; --destructive-foreground: 210 40% 98%;
--ring: hsl(212.7,26.8%,83.9); --ring: hsl(212.7, 26.8%, 83.9);
} }
.nordlys { .nordlys {
--background: 0 0% 0%; /* #000000 */ --background: 0 0% 0%; /* #000000 */
--foreground: 173 80% 40%; /* #14b8a6 */ --foreground: 173 80% 40%; /* #14b8a6 */
--muted: 183 100% 96%; /* #ecfeff */ --muted: 183 100% 96%; /* #ecfeff */
--muted-foreground: 176 61% 19%; /* #134e4a */ --muted-foreground: 176 61% 19%; /* #134e4a */
--popover: 0 0% 0%; /* #000000 */ --popover: 0 0% 0%; /* #000000 */
--popover-foreground: 173 80% 40%; /* #14b8a6 */ --popover-foreground: 173 80% 40%; /* #14b8a6 */
--card: 0 0% 0%; /* #000000 */ --card: 0 0% 0%; /* #000000 */
--card-foreground: 173 80% 40%; /* #14b8a6 */ --card-foreground: 173 80% 40%; /* #14b8a6 */
--border: 183 100% 96%; /* #ecfeff */ --border: 183 100% 96%; /* #ecfeff */
--input: 183 100% 96%; /* #ecfeff */ --input: 183 100% 96%; /* #ecfeff */
--primary: 173 80% 40%; /* #14b8a6 */ --primary: 173 80% 40%; /* #14b8a6 */
--primary-foreground: 221 39% 11%; /* #111827 */ --primary-foreground: 221 39% 11%; /* #111827 */
--secondary: 183 100% 96%; /* #ecfeff */ --secondary: 183 100% 96%; /* #ecfeff */
--secondary-foreground: 173 80% 40%; /* #14b8a6 */ --secondary-foreground: 173 80% 40%; /* #14b8a6 */
--accent: 183 100% 96%; /* #ecfeff */ --accent: 183 100% 96%; /* #ecfeff */
--accent-foreground: 173 80% 40%; /* #14b8a6 */ --accent-foreground: 173 80% 40%; /* #14b8a6 */
--destructive: 0 70% 35%; /* #991b1b */ --destructive: 0 70% 35%; /* #991b1b */
--destructive-foreground: 173 80% 40%; /* #14b8a6 */ --destructive-foreground: 173 80% 40%; /* #14b8a6 */
--offline: var(--destructive); --offline: var(--destructive);
--online: 142 76% 36%; /* #16a34a */ --online: 142 76% 36%; /* #16a34a */
--pending: 25 95% 53%; /* #f97316 */ --pending: 25 95% 53%; /* #f97316 */
--maintenance: 224 76% 48%; /* #1d4ed8 */ --maintenance: 224 76% 48%; /* #1d4ed8 */
/* that border thingy when you tab through stuff */ /* that border thingy when you tab through stuff */
--ring: hsl(168 84% 78%); /* #99f6e4 */ --ring: hsl(168 84% 78%); /* #99f6e4 */
} }
} }
@layer base { @layer base {
* { * {
@apply border-border; @apply border-border;
} }
body { body {
@apply bg-background text-foreground; @apply bg-background text-foreground;
} }
} }

View file

@ -1,66 +1,100 @@
<svelte:options runes={true} /> <svelte:options runes={true} />
<script lang="ts"> <script lang="ts">
import {OpenInNewWindow} from "radix-icons-svelte"; import { OpenInNewWindow } from 'radix-icons-svelte';
import {quintInOut} from "svelte/easing"; import { quintInOut } from 'svelte/easing';
import { slide } from "svelte/transition"; import { slide } from 'svelte/transition';
let {service} = $props(); let { service } = $props();
let hover = $state({ let hover = $state({
title: false, title: false,
link: false, link: false,
ext: false ext: false
}) });
</script> </script>
<div class="flex flex-col border-t-4 rounded-xl bg-black/55 border-maintenance p-4 gap-y-2 w-[30rem] h-48"> <div
<div class="flex flex-row justify-between pb-4"> class="flex h-48 w-[30rem] flex-col gap-y-2 rounded-xl border-t-4 border-maintenance bg-black/55 p-4"
<div class="flex flex-row gap-1 items-center" on:mouseover={() => hover.title = true} on:mouseleave={() => hover.title = false}> >
{#if service.icon} <div class="flex flex-row justify-between pb-4">
<img width="24px" class="h-6 w-6 cursor-pointer" src={service.icon} alt="{service.name} Logo"> <div
{:else} class="flex flex-row items-center gap-1"
{/if} on:mouseover={() => (hover.title = true)}
<a href={service.href} class="text-accent font-bold {hover.title ? 'text-primary': 'text-secondary'}">{service.name}</a> on:mouseleave={() => (hover.title = false)}
{#if hover.title} >
<div transition:slide={{ delay: 100, duration: 200, easing: quintInOut, axis: 'x' }} class="grid items-center"> {#if service.icon}
<OpenInNewWindow color={ hover.title ? "hsl(var(--primary))" : "hsl(var(--secondary)"} class="self-center"/> <img
</div> width="24px"
{/if} class="h-6 w-6 cursor-pointer"
</div> src={service.icon}
alt="{service.name} Logo"
/>
{:else}{/if}
<a
href={service.href}
class="font-bold text-accent {hover.title ? 'text-primary' : 'text-secondary'}"
>{service.name}</a
>
{#if hover.title}
<div
transition:slide={{ delay: 100, duration: 200, easing: quintInOut, axis: 'x' }}
class="grid items-center"
>
<OpenInNewWindow
color={hover.title ? 'hsl(var(--primary))' : 'hsl(var(--secondary)'}
class="self-center"
/>
</div>
{/if}
</div>
<h1 class="border-b-2 rounded-md text-sm text-center w-16 text-maintenance border-maintenance">Loading</h1> <h1 class="w-16 rounded-md border-b-2 border-maintenance text-center text-sm text-maintenance">
</div> Loading
<p class="text-sm text-center text-accent text-wrap">{service.desc}</p> </h1>
<p class="text-sm text-center font-bold text-destructive">{service.warn}</p> </div>
<div class="grid {service.extLink ? 'grid-cols-2' : 'grid-cols-1'} justify-items-center mt-auto"> <p class="text-wrap text-center text-sm text-accent">{service.desc}</p>
<a <p class="text-center text-sm font-bold text-destructive">{service.warn}</p>
class="flex flex-row text-sm border-x-2 rounded-md px-2 text-accent hover:border-primary hover:text-primary" <div class="grid {service.extLink ? 'grid-cols-2' : 'grid-cols-1'} mt-auto justify-items-center">
href={service.href} <a
on:mouseover={() => hover.link = true} class="flex flex-row rounded-md border-x-2 px-2 text-sm text-accent hover:border-primary hover:text-primary"
on:mouseleave={() => hover.link = false} href={service.href}
> on:mouseover={() => (hover.link = true)}
Open on:mouseleave={() => (hover.link = false)}
{#if hover.link} >
<div transition:slide={{ delay: 100, duration: 200, easing: quintInOut, axis: 'x' }} class="pl-1 pr-0 grid items-center"> Open
<OpenInNewWindow color={ hover.link ? "hsl(var(--primary))" : "hsl(var(--secondary)"} class="self-center"/> {#if hover.link}
</div> <div
{/if} transition:slide={{ delay: 100, duration: 200, easing: quintInOut, axis: 'x' }}
</a> class="grid items-center pl-1 pr-0"
{#if service.extLink} >
<a <OpenInNewWindow
class="flex flex-row text-sm border-x-2 rounded-md px-2 text-accent hover:border-primary hover:text-primary" color={hover.link ? 'hsl(var(--primary))' : 'hsl(var(--secondary)'}
href={service.extLink} class="self-center"
on:mouseover={() => hover.ext = true} />
on:mouseleave={() => hover.ext = false} </div>
> {/if}
Official Site </a>
{#if hover.ext} {#if service.extLink}
<div transition:slide={{ delay: 100, duration: 200, easing: quintInOut, axis: 'x' }} class="pl-1 pr-0 grid items-center"> <a
<OpenInNewWindow color={ hover.ext ? "hsl(var(--primary))" : "hsl(var(--secondary)"} class="self-center"/> class="flex flex-row rounded-md border-x-2 px-2 text-sm text-accent hover:border-primary hover:text-primary"
</div> href={service.extLink}
{/if} on:mouseover={() => (hover.ext = true)}
</a> on:mouseleave={() => (hover.ext = false)}
{/if} >
</div> Official Site
{#if hover.ext}
<div
transition:slide={{ delay: 100, duration: 200, easing: quintInOut, axis: 'x' }}
class="grid items-center pl-1 pr-0"
>
<OpenInNewWindow
color={hover.ext ? 'hsl(var(--primary))' : 'hsl(var(--secondary)'}
class="self-center"
/>
</div>
{/if}
</a>
{/if}
</div>
</div> </div>

View file

@ -1,155 +1,155 @@
{ {
"services": [ "services": [
{ {
"name": "Nextcloud", "name": "Nextcloud",
"icon": "/assets/icons/nextcloud-logo.svg", "icon": "/assets/icons/nextcloud-logo.svg",
"href": "https://nextcloud.neshweb.net/", "href": "https://nextcloud.neshweb.net/",
"desc": "Self-hosted Cloud Storage Service", "desc": "Self-hosted Cloud Storage Service",
"warn": "Note: Registration requires approval", "warn": "Note: Registration requires approval",
"extLink": "https://nextcloud.com/", "extLink": "https://nextcloud.com/",
"id": 7 "id": 7
}, },
{ {
"name": "Kavita", "name": "Kavita",
"icon": "/assets/icons/kavita-logo.svg", "icon": "/assets/icons/kavita-logo.svg",
"href": "https://kavita.neshweb.net", "href": "https://kavita.neshweb.net",
"desc": "Self-hosted Manga Library", "desc": "Self-hosted Manga Library",
"warn": "Registration via Admin invite", "warn": "Registration via Admin invite",
"id": 5 "id": 5
}, },
{ {
"name": "Images", "name": "Images",
"icon": "/assets/icons/images-logo.svg", "icon": "/assets/icons/images-logo.svg",
"href": "https://imgs.neshweb.net/", "href": "https://imgs.neshweb.net/",
"desc": "Self-hosted Chevereto Image Service", "desc": "Self-hosted Chevereto Image Service",
"warn": "", "warn": "",
"extLink": "https://chevereto.com/", "extLink": "https://chevereto.com/",
"id": 4 "id": 4
}, },
{ {
"name": "Calibre Web", "name": "Calibre Web",
"icon": "/assets/icons/calibre-logo.avif", "icon": "/assets/icons/calibre-logo.avif",
"href": "https://calibre.neshweb.net/", "href": "https://calibre.neshweb.net/",
"desc": "Self-hosted Ebook Library Service", "desc": "Self-hosted Ebook Library Service",
"warn": "Note: Registration only via Admin", "warn": "Note: Registration only via Admin",
"id": 6 "id": 6
}, },
{ {
"name": "PeerTube", "name": "PeerTube",
"icon": "/assets/icons/peertube-logo.svg", "icon": "/assets/icons/peertube-logo.svg",
"href": "https://tube.neshweb.net/", "href": "https://tube.neshweb.net/",
"desc": "Self-hosted PeerTube Instance", "desc": "Self-hosted PeerTube Instance",
"warn": "Note: Registration only via Admin", "warn": "Note: Registration only via Admin",
"id": 8 "id": 8
}, },
{ {
"name": "Mastodon", "name": "Mastodon",
"icon": "/assets/icons/mastodon-logo.svg", "icon": "/assets/icons/mastodon-logo.svg",
"href": "https://mastodon.neshweb.net/", "href": "https://mastodon.neshweb.net/",
"desc": "Self-hosted Mastodon Instance", "desc": "Self-hosted Mastodon Instance",
"warn": "Note: Registration requires approval", "warn": "Note: Registration requires approval",
"id": 3 "id": 3
}, },
{ {
"name": "Vaultwarden", "name": "Vaultwarden",
"icon": "/assets/icons/vaultwarden-logo.svg", "icon": "/assets/icons/vaultwarden-logo.svg",
"href": "https://vault.neshweb.net", "href": "https://vault.neshweb.net",
"desc": "Self-hosted Password Manager", "desc": "Self-hosted Password Manager",
"warn": "Note: Invite only", "warn": "Note: Invite only",
"id": 9 "id": 9
}, },
{ {
"name": "Jellyfin", "name": "Jellyfin",
"icon": "/assets/icons/jellyfin-logo.svg", "icon": "/assets/icons/jellyfin-logo.svg",
"href": "https://jellyfin.neshweb.net/", "href": "https://jellyfin.neshweb.net/",
"desc": "Open-Source, Self-Hosted Media Platform", "desc": "Open-Source, Self-Hosted Media Platform",
"warn": "Note: Registration only via Admin", "warn": "Note: Registration only via Admin",
"id": 37 "id": 37
}, },
{ {
"name": "Navidrome", "name": "Navidrome",
"icon": "/assets/icons/navidrome-logo.avif", "icon": "/assets/icons/navidrome-logo.avif",
"href": "https://navidrome.neshweb.net/", "href": "https://navidrome.neshweb.net/",
"desc": "Open-Source, Self-Hosted Music Streaming Platform", "desc": "Open-Source, Self-Hosted Music Streaming Platform",
"warn": "Note: Registration only via Admin", "warn": "Note: Registration only via Admin",
"id": 10 "id": 10
}, },
{ {
"name": "Gitlab", "name": "Gitlab",
"icon": "/assets/icons/gitlab-logo.svg", "icon": "/assets/icons/gitlab-logo.svg",
"href": "https://gitlab.neshweb.net/", "href": "https://gitlab.neshweb.net/",
"desc": "Self-hosted Git Service", "desc": "Self-hosted Git Service",
"warn": "Note: Registration only via Admin", "warn": "Note: Registration only via Admin",
"id": 2 "id": 2
}, },
{ {
"name": "Forgejo", "name": "Forgejo",
"icon": "/assets/icons/forgejo-logo.svg", "icon": "/assets/icons/forgejo-logo.svg",
"href": "https://forgejo.neshweb.net/", "href": "https://forgejo.neshweb.net/",
"desc": "Self-hosted Git Service", "desc": "Self-hosted Git Service",
"warn": "Note: Registration only via Admin", "warn": "Note: Registration only via Admin",
"id": 36 "id": 36
}, },
{ {
"name": "Portainer", "name": "Portainer",
"icon": "/assets/icons/portainer-logo.avif", "icon": "/assets/icons/portainer-logo.avif",
"href": "https://portainer.neshweb.net/", "href": "https://portainer.neshweb.net/",
"desc": "Docker Container Manager", "desc": "Docker Container Manager",
"warn": "Note: Admin Only", "warn": "Note: Admin Only",
"id": 34 "id": 34
}, },
{ {
"name": "Nginx", "name": "Nginx",
"icon": "/assets/icons/npm-logo.avif", "icon": "/assets/icons/npm-logo.avif",
"href": "https://nginx.neshweb.net/", "href": "https://nginx.neshweb.net/",
"desc": "Web-based Nginx Proxy Manager", "desc": "Web-based Nginx Proxy Manager",
"warn": "Note: Admin Only", "warn": "Note: Admin Only",
"id": 31 "id": 31
}, },
{ {
"name": "Proxmox", "name": "Proxmox",
"icon": "/assets/icons/proxmox-logo.avif", "icon": "/assets/icons/proxmox-logo.avif",
"href": "https://proxmox.neshweb.net/", "href": "https://proxmox.neshweb.net/",
"desc": "Hypervisor Webinterface", "desc": "Hypervisor Webinterface",
"warn": "Note: Admin Only", "warn": "Note: Admin Only",
"id": 33 "id": 33
}, },
{ {
"name": "Dockge", "name": "Dockge",
"icon": "/assets/icons/dockge-logo.avif", "icon": "/assets/icons/dockge-logo.avif",
"href": "https://dockge.neshweb.net/", "href": "https://dockge.neshweb.net/",
"desc": "Docker Compose WebUI", "desc": "Docker Compose WebUI",
"warn": "Note: Admin Only", "warn": "Note: Admin Only",
"id": 35 "id": 35
} }
], ],
"games": { "games": {
"minecraft": { "minecraft": {
"name": "Minecraft", "name": "Minecraft",
"icon": "/assets/icons/minecraft-logo.avif", "icon": "/assets/icons/minecraft-logo.avif",
"href": "https://minecraft.neshweb.net/", "href": "https://minecraft.neshweb.net/",
"desc": "View all currently available Minecraft Servers and their mods" "desc": "View all currently available Minecraft Servers and their mods"
}, },
"ready_or_not": { "ready_or_not": {
"name": "Ready or Not", "name": "Ready or Not",
"icon": "/assets/icons/ron-logo.avif", "icon": "/assets/icons/ron-logo.avif",
"href": "https://readyornot.neshweb.net/", "href": "https://readyornot.neshweb.net/",
"desc": "Collection of Floor Plans for the Game 'Ready or Not'" "desc": "Collection of Floor Plans for the Game 'Ready or Not'"
}, },
"zomboid": { "zomboid": {
"name": "Zomboid", "name": "Zomboid",
"icon": "/assets/icons/zomboid-logo.avif", "icon": "/assets/icons/zomboid-logo.avif",
"ip": "91.13.248.30", "ip": "91.13.248.30",
"status": "Online" "status": "Online"
}, },
"factorio": { "factorio": {
"name": "Factorio", "name": "Factorio",
"status": "Online" "status": "Online"
}, },
"space_engineers": { "space_engineers": {
"name": "Space Engineers", "name": "Space Engineers",
"status": "Online" "status": "Online"
} }
} }
} }

View file

@ -1,15 +1,15 @@
<script lang="ts"> <script lang="ts">
import { Button as ButtonPrimitive } from "bits-ui"; import { Button as ButtonPrimitive } from 'bits-ui';
import { cn } from "$lib/utils"; import { cn } from '$lib/utils';
import { buttonVariants, type Props, type Events } from "."; import { buttonVariants, type Props, type Events } from '.';
type $$Props = Props; type $$Props = Props;
type $$Events = Events; type $$Events = Events;
let className: $$Props["class"] = undefined; let className: $$Props['class'] = undefined;
export let variant: $$Props["variant"] = "default"; export let variant: $$Props['variant'] = 'default';
export let size: $$Props["size"] = "default"; export let size: $$Props['size'] = 'default';
export let builders: $$Props["builders"] = []; export let builders: $$Props['builders'] = [];
export { className as class }; export { className as class };
</script> </script>

View file

@ -1,37 +1,34 @@
import type { Button as ButtonPrimitive } from "bits-ui"; import type { Button as ButtonPrimitive } from 'bits-ui';
import { tv, type VariantProps } from "tailwind-variants"; import { tv, type VariantProps } from 'tailwind-variants';
import Root from "./button.svelte"; import Root from './button.svelte';
const buttonVariants = tv({ const buttonVariants = tv({
base: "inline-flex items-center justify-center rounded-md text-sm font-medium whitespace-nowrap transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50", base: 'inline-flex items-center justify-center rounded-md text-sm font-medium whitespace-nowrap transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50',
variants: { variants: {
variant: { variant: {
default: default: 'bg-primary text-primary-foreground shadow hover:bg-primary/90',
"bg-primary text-primary-foreground shadow hover:bg-primary/90", destructive: 'bg-destructive text-destructive-foreground shadow-sm hover:bg-destructive/90',
destructive:
"bg-destructive text-destructive-foreground shadow-sm hover:bg-destructive/90",
outline: outline:
"border border-input bg-transparent shadow-sm hover:bg-accent hover:text-accent-foreground", 'border border-input bg-transparent shadow-sm hover:bg-accent hover:text-accent-foreground',
secondary: secondary: 'bg-secondary text-secondary-foreground shadow-sm hover:bg-secondary/80',
"bg-secondary text-secondary-foreground shadow-sm hover:bg-secondary/80", ghost: 'hover:bg-accent hover:text-accent-foreground',
ghost: "hover:bg-accent hover:text-accent-foreground", link: 'text-primary underline-offset-4 hover:underline'
link: "text-primary underline-offset-4 hover:underline"
}, },
size: { size: {
default: "h-9 px-4 py-2", default: 'h-9 px-4 py-2',
sm: "h-8 rounded-md px-3 text-xs", sm: 'h-8 rounded-md px-3 text-xs',
lg: "h-10 rounded-md px-8", lg: 'h-10 rounded-md px-8',
icon: "h-9 w-9" icon: 'h-9 w-9'
} }
}, },
defaultVariants: { defaultVariants: {
variant: "default", variant: 'default',
size: "default" size: 'default'
} }
}); });
type Variant = VariantProps<typeof buttonVariants>["variant"]; type Variant = VariantProps<typeof buttonVariants>['variant'];
type Size = VariantProps<typeof buttonVariants>["size"]; type Size = VariantProps<typeof buttonVariants>['size'];
type Props = ButtonPrimitive.Props & { type Props = ButtonPrimitive.Props & {
variant?: Variant; variant?: Variant;

View file

@ -1,62 +1,56 @@
import { type ClassValue, clsx } from "clsx"; import { type ClassValue, clsx } from 'clsx';
import { twMerge } from "tailwind-merge"; import { twMerge } from 'tailwind-merge';
import { cubicOut } from "svelte/easing"; import { cubicOut } from 'svelte/easing';
import type { TransitionConfig } from "svelte/transition"; import type { TransitionConfig } from 'svelte/transition';
export function cn(...inputs: ClassValue[]) { export function cn(...inputs: ClassValue[]) {
return twMerge(clsx(inputs)); return twMerge(clsx(inputs));
} }
type FlyAndScaleParams = { type FlyAndScaleParams = {
y?: number; y?: number;
x?: number; x?: number;
start?: number; start?: number;
duration?: number; duration?: number;
}; };
export const flyAndScale = ( export const flyAndScale = (
node: Element, node: Element,
params: FlyAndScaleParams = { y: -8, x: 0, start: 0.95, duration: 150 } params: FlyAndScaleParams = { y: -8, x: 0, start: 0.95, duration: 150 }
): TransitionConfig => { ): TransitionConfig => {
const style = getComputedStyle(node); const style = getComputedStyle(node);
const transform = style.transform === "none" ? "" : style.transform; const transform = style.transform === 'none' ? '' : style.transform;
const scaleConversion = ( const scaleConversion = (valueA: number, scaleA: [number, number], scaleB: [number, number]) => {
valueA: number, const [minA, maxA] = scaleA;
scaleA: [number, number], const [minB, maxB] = scaleB;
scaleB: [number, number]
) => {
const [minA, maxA] = scaleA;
const [minB, maxB] = scaleB;
const percentage = (valueA - minA) / (maxA - minA); const percentage = (valueA - minA) / (maxA - minA);
const valueB = percentage * (maxB - minB) + minB; const valueB = percentage * (maxB - minB) + minB;
return valueB; return valueB;
}; };
const styleToString = ( const styleToString = (style: Record<string, number | string | undefined>): string => {
style: Record<string, number | string | undefined> return Object.keys(style).reduce((str, key) => {
): string => { if (style[key] === undefined) return str;
return Object.keys(style).reduce((str, key) => { return str + `${key}:${style[key]};`;
if (style[key] === undefined) return str; }, '');
return str + `${key}:${style[key]};`; };
}, "");
};
return { return {
duration: params.duration ?? 200, duration: params.duration ?? 200,
delay: 0, delay: 0,
css: (t) => { css: (t) => {
const y = scaleConversion(t, [0, 1], [params.y ?? 5, 0]); const y = scaleConversion(t, [0, 1], [params.y ?? 5, 0]);
const x = scaleConversion(t, [0, 1], [params.x ?? 0, 0]); const x = scaleConversion(t, [0, 1], [params.x ?? 0, 0]);
const scale = scaleConversion(t, [0, 1], [params.start ?? 0.95, 1]); const scale = scaleConversion(t, [0, 1], [params.start ?? 0.95, 1]);
return styleToString({ return styleToString({
transform: `${transform} translate3d(${x}px, ${y}px, 0) scale(${scale})`, transform: `${transform} translate3d(${x}px, ${y}px, 0) scale(${scale})`,
opacity: t opacity: t
}); });
}, },
easing: cubicOut easing: cubicOut
}; };
}; };

View file

@ -1,6 +1,6 @@
<script> <script>
import '../app.pcss'; import '../app.pcss';
import Header from "./Header.svelte"; import Header from './Header.svelte';
</script> </script>
<Header /> <Header />

View file

@ -1,86 +1,86 @@
<div class="overflow-auto h-full"> <div class="h-full overflow-auto">
<h1>Welcome to SvelteKit</h1> <h1>Welcome to SvelteKit</h1>
<p>Visit <a href="https://kit.svelte.dev">kit.svelte.dev</a> to read the documentation</p> <p>Visit <a href="https://kit.svelte.dev">kit.svelte.dev</a> to read the documentation</p>
<p>gap</p> <p>gap</p>
<p>gap</p> <p>gap</p>
<p>gap</p> <p>gap</p>
<p>gap</p> <p>gap</p>
<p>gap</p> <p>gap</p>
<p>gap</p> <p>gap</p>
<p>gap</p> <p>gap</p>
<p>gap</p> <p>gap</p>
<p>gap</p> <p>gap</p>
<p>gap</p> <p>gap</p>
<p>gap</p> <p>gap</p>
<p>gap</p> <p>gap</p>
<p>gap</p> <p>gap</p>
<p>gap</p> <p>gap</p>
<p>gap</p> <p>gap</p>
<p>gap</p> <p>gap</p>
<p>gap</p> <p>gap</p>
<p>gap</p> <p>gap</p>
<p>gap</p> <p>gap</p>
<p>gap</p> <p>gap</p>
<p>gap</p> <p>gap</p>
<p>gap</p> <p>gap</p>
<p>gap</p> <p>gap</p>
<p>gap</p> <p>gap</p>
<p>gap</p> <p>gap</p>
<p>gap</p> <p>gap</p>
<p>gap</p> <p>gap</p>
<p>gap</p> <p>gap</p>
<p>gap</p> <p>gap</p>
<p>gap</p> <p>gap</p>
<p>gap</p> <p>gap</p>
<p>gap</p> <p>gap</p>
<p>gap</p> <p>gap</p>
<p>gap</p> <p>gap</p>
<p>gap</p> <p>gap</p>
<p>gap</p> <p>gap</p>
<p>gap</p> <p>gap</p>
<p>gap</p> <p>gap</p>
<p>gap</p> <p>gap</p>
<p>gap</p> <p>gap</p>
<p>gap</p> <p>gap</p>
<p>gap</p> <p>gap</p>
<p>gap</p> <p>gap</p>
<p>gap</p> <p>gap</p>
<p>gap</p> <p>gap</p>
<p>gap</p> <p>gap</p>
<p>gap</p> <p>gap</p>
<p>gap</p> <p>gap</p>
<p>gap</p> <p>gap</p>
<p>gap</p> <p>gap</p>
<p>gap</p> <p>gap</p>
<p>gap</p> <p>gap</p>
<p>gap</p> <p>gap</p>
<p>gap</p> <p>gap</p>
<p>gap</p> <p>gap</p>
<p>gap</p> <p>gap</p>
<p>gap</p> <p>gap</p>
<p>gap</p> <p>gap</p>
<p>gap</p> <p>gap</p>
<p>gap</p> <p>gap</p>
<p>gap</p> <p>gap</p>
<p>gap</p> <p>gap</p>
<p>gap</p> <p>gap</p>
<p>gap</p> <p>gap</p>
<p>gap</p> <p>gap</p>
<p>gap</p> <p>gap</p>
<p>gap</p> <p>gap</p>
<p>gap</p> <p>gap</p>
<p>gap</p> <p>gap</p>
<p>gap</p> <p>gap</p>
<p>gap</p> <p>gap</p>
<p>gap</p> <p>gap</p>
<p>gap</p> <p>gap</p>
<p>gap</p> <p>gap</p>
<p>gap</p> <p>gap</p>
<p>gap</p> <p>gap</p>
<p>gap</p> <p>gap</p>
<p>gap</p> <p>gap</p>
<p>gap</p> <p>gap</p>
<p>gap</p> <p>gap</p>
<p>gap</p> <p>gap</p>
<p>gap</p> <p>gap</p>
</div> </div>

View file

@ -1,47 +1,53 @@
<script lang="ts"> <script lang="ts">
import {page} from "$app/stores"; import { page } from '$app/stores';
import { Button } from "$lib/components/ui/button"; import { Button } from '$lib/components/ui/button';
const button = "border-t-2 bg-black/55 hover:bg-black/70 hover:border-primary w-28";
const button = 'border-t-2 bg-black/55 hover:bg-black/70 hover:border-primary w-28';
</script> </script>
<ul
<ul class="w-full h-16 border-b sticky flex flex-row items-center justify-center gap-3 backdrop-blur-sm bg-black/40"> class="sticky flex h-16 w-full flex-row items-center justify-center gap-3 border-b bg-black/40 backdrop-blur-sm"
<li> >
<Button <li>
variant="ghost" <Button
href="/" variant="ghost"
class="{button} + {$page.url.pathname == '/' ? 'border-primary text-primary' : 'text-accent'}" href="/"
> class="{button} + {$page.url.pathname === '/' ? 'border-primary text-primary' : 'text-accent'}"
Home >
</Button> Home
</li> </Button>
<li> </li>
<Button <li>
variant="ghost" <Button
href="/servers" variant="ghost"
class="{button} + {$page.url.pathname.startsWith('/servers') ? 'border-primary text-primary' : 'text-accent'}" href="/servers"
> class="{button} + {$page.url.pathname.startsWith('/servers')
Servers ? 'border-primary text-primary'
</Button> : 'text-accent'}"
</li> >
<li> Servers
<Button </Button>
variant="ghost" </li>
href="/services" <li>
class="{button} + {$page.url.pathname.startsWith('/services') ? 'border-primary text-primary' : 'text-accent'}" <Button
> variant="ghost"
Services href="/services"
</Button> class="{button} + {$page.url.pathname.startsWith('/services')
</li> ? 'border-primary text-primary'
<li> : 'text-accent'}"
<Button >
variant="ghost" Services
href="/about" </Button>
class="{button} + {$page.url.pathname.startsWith('/about') ? 'border-primary text-primary' : 'text-accent'}" </li>
> <li>
About <Button
</Button> variant="ghost"
</li> href="/about"
class="{button} + {$page.url.pathname.startsWith('/about')
? 'border-primary text-primary'
: 'text-accent'}"
>
About
</Button>
</li>
</ul> </ul>

View file

@ -1,20 +1,19 @@
<svelte:options runes={true} /> <svelte:options runes={true} />
<script lang="ts"> <script lang="ts">
import pages from '$lib/components/pages.json'; import pages from '$lib/components/pages.json';
import Card from "$lib/components/Card.svelte"; import Card from '$lib/components/Card.svelte';
const services = $state(pages.services); const services = $state(pages.services);
</script> </script>
<svelte:head> <svelte:head>
<title>Services</title> <title>Services</title>
<meta name="description" content="Overview of Services running on neshweb.net"> <meta name="description" content="Overview of Services running on neshweb.net" />
</svelte:head> </svelte:head>
<div class="flex h-full flex-row flex-wrap justify-center gap-10 overflow-auto p-8">
<div class="flex flex-row flex-wrap justify-center gap-10 p-8 overflow-auto h-full"> {#each services as service}
{#each services as service} <Card {service} />
<Card {service} /> {/each}
{/each}
</div> </div>