Add frontpage, move pakubiiti to subpage. Add new fonts, styling tweaks

This commit is contained in:
Mihkel Martin Kasterpalu 2025-01-21 17:39:17 +02:00
parent ac3649fae2
commit e7126efb44
13 changed files with 223 additions and 132 deletions

View file

@ -28,6 +28,7 @@
"eslint-config-prettier": "^10.0.1",
"eslint-plugin-svelte": "^2.46.1",
"globals": "^15.14.0",
"lucide-svelte": "^0.473.0",
"prettier": "^3.4.2",
"prettier-plugin-svelte": "^3.3.3",
"prettier-plugin-tailwindcss": "^0.6.10",
@ -43,8 +44,8 @@
"vite": "^5.4.11"
},
"dependencies": {
"@fontsource-variable/kode-mono": "^5.1.1",
"@fontsource-variable/smooch-sans": "^5.1.1",
"lucide-svelte": "^0.473.0",
"mode-watcher": "^0.5.0",
"nanoid": "^5.0.9",
"spotify-web-api-node": "^5.0.2",

14
pnpm-lock.yaml generated
View file

@ -8,12 +8,12 @@ importers:
.:
dependencies:
'@fontsource-variable/kode-mono':
specifier: ^5.1.1
version: 5.1.1
'@fontsource-variable/smooch-sans':
specifier: ^5.1.1
version: 5.1.1
lucide-svelte:
specifier: ^0.473.0
version: 0.473.0(svelte@5.19.0)
mode-watcher:
specifier: ^0.5.0
version: 0.5.0(svelte@5.19.0)
@ -72,6 +72,9 @@ importers:
globals:
specifier: ^15.14.0
version: 15.14.0
lucide-svelte:
specifier: ^0.473.0
version: 0.473.0(svelte@5.19.0)
prettier:
specifier: ^3.4.2
version: 3.4.2
@ -312,6 +315,9 @@ packages:
'@floating-ui/utils@0.2.9':
resolution: {integrity: sha512-MDWhGtE+eHw5JW7lq4qhc5yRLS11ERl1c7Z6Xd0a58DozHES6EnNNwUWbMiG4J9Cgj053Bhk8zvlhFYKVhULwg==}
'@fontsource-variable/kode-mono@5.1.1':
resolution: {integrity: sha512-lS06Ow0N3YEFnSxr886WJPA2be0ndCm6mZFvCPYkTfv4BLxoiISNhPFL4M17jatKQ3/MoGbs95mjQv+YGkGcDg==}
'@fontsource-variable/smooch-sans@5.1.1':
resolution: {integrity: sha512-ADcY3Pjkvu74x4T+p2gNIo8Ba7DGrX1IhQgJFefztWB8Tf5nX7Qb+tqAbtW7O/KYWJi4EjzrxCefHBM1PfOOsg==}
@ -1949,6 +1955,8 @@ snapshots:
'@floating-ui/utils@0.2.9': {}
'@fontsource-variable/kode-mono@5.1.1': {}
'@fontsource-variable/smooch-sans@5.1.1': {}
'@humanfs/core@0.19.1': {}

View file

@ -3,73 +3,86 @@
@tailwind utilities;
@layer base {
:root {
--background: 0 0% 100%;
--foreground: 20 14.3% 4.1%;
--muted: 60 4.8% 95.9%;
--muted-foreground: 25 5.3% 44.7%;
--popover: 0 0% 100%;
--popover-foreground: 20 14.3% 4.1%;
--card: 0 0% 100%;
--card-foreground: 20 14.3% 4.1%;
--border: 20 5.9% 90%;
--input: 20 5.9% 90%;
--primary: 24 9.8% 10%;
--primary-foreground: 60 9.1% 97.8%;
--secondary: 60 4.8% 95.9%;
--secondary-foreground: 24 9.8% 10%;
--accent: 60 4.8% 95.9%;
--accent-foreground: 24 9.8% 10%;
--destructive: 0 72.2% 50.6%;
--destructive-foreground: 60 9.1% 97.8%;
--ring: 20 14.3% 4.1%;
--radius: 0.5rem;
--sidebar-background: 0 0% 98%;
--sidebar-foreground: 240 5.3% 26.1%;
--sidebar-primary: 240 5.9% 10%;
--sidebar-primary-foreground: 0 0% 98%;
--sidebar-accent: 240 4.8% 95.9%;
--sidebar-accent-foreground: 240 5.9% 10%;
--sidebar-border: 220 13% 91%;
--sidebar-ring: 217.2 91.2% 59.8%;
}
:root {
--background: 0 0% 100%;
--foreground: 20 14.3% 4.1%;
--muted: 60 4.8% 95.9%;
--muted-foreground: 25 5.3% 44.7%;
--popover: 0 0% 100%;
--popover-foreground: 20 14.3% 4.1%;
--card: 0 0% 100%;
--card-foreground: 20 14.3% 4.1%;
--border: 20 5.9% 90%;
--input: 20 5.9% 90%;
--primary: 24 9.8% 10%;
--primary-foreground: 60 9.1% 94%;
--secondary: 60 4.8% 95.9%;
--secondary-foreground: 24 9.8% 10%;
--accent: 60 4.8% 95.9%;
--accent-foreground: 24 9.8% 10%;
--destructive: 0 72.2% 50.6%;
--destructive-foreground: 60 9.1% 94%;
--ring: 20 14.3% 4.1%;
--radius: 0.5rem;
--sidebar-background: 0 0% 98%;
--sidebar-foreground: 240 5.3% 26.1%;
--sidebar-primary: 240 5.9% 10%;
--sidebar-primary-foreground: 0 0% 98%;
--sidebar-accent: 240 4.8% 95.9%;
--sidebar-accent-foreground: 240 5.9% 10%;
--sidebar-border: 220 13% 91%;
--sidebar-ring: 217.2 91.2% 59.8%;
}
.dark {
--background: 20 14.3% 4.1%;
--foreground: 60 9.1% 97.8%;
--muted: 12 6.5% 15.1%;
--muted-foreground: 24 5.4% 63.9%;
--popover: 20 14.3% 4.1%;
--popover-foreground: 60 9.1% 97.8%;
--card: 20 14.3% 4.1%;
--card-foreground: 60 9.1% 97.8%;
--border: 12 6.5% 15.1%;
--input: 12 6.5% 15.1%;
--primary: 60 9.1% 97.8%;
--primary-foreground: 24 9.8% 10%;
--secondary: 12 6.5% 15.1%;
--secondary-foreground: 60 9.1% 97.8%;
--accent: 12 6.5% 15.1%;
--accent-foreground: 60 9.1% 97.8%;
--destructive: 0 62.8% 30.6%;
--destructive-foreground: 60 9.1% 97.8%;
--ring: 24 5.7% 82.9%;
--sidebar-background: 240 5.9% 10%;
--sidebar-foreground: 240 4.8% 95.9%;
--sidebar-primary: 224.3 76.3% 48%;
--sidebar-primary-foreground: 0 0% 100%;
--sidebar-accent: 240 3.7% 15.9%;
--sidebar-accent-foreground: 240 4.8% 95.9%;
--sidebar-border: 240 3.7% 15.9%;
--sidebar-ring: 217.2 91.2% 59.8%;
}
.dark {
--background: 20 14.3% 4.1%;
--foreground: 60 9.1% 94%;
--muted: 12 6.5% 15.1%;
--muted-foreground: 24 5.4% 63.9%;
--popover: 20 14.3% 4.1%;
--popover-foreground: 60 9.1% 94%;
--card: 20 14.3% 4.1%;
--card-foreground: 60 9.1% 94%;
--border: 12 6.5% 15.1%;
--input: 12 6.5% 15.1%;
--primary: 60 9.1% 94%;
--primary-foreground: 24 9.8% 10%;
--secondary: 12 6.5% 15.1%;
--secondary-foreground: 60 9.1% 94%;
--accent: 12 6.5% 15.1%;
--accent-foreground: 60 9.1% 94%;
--destructive: 0 62.8% 30.6%;
--destructive-foreground: 60 9.1% 94%;
--ring: 24 5.7% 82.9%;
--sidebar-background: 240 5.9% 10%;
--sidebar-foreground: 240 4.8% 95.9%;
--sidebar-primary: 224.3 76.3% 48%;
--sidebar-primary-foreground: 0 0% 100%;
--sidebar-accent: 240 3.7% 15.9%;
--sidebar-accent-foreground: 240 4.8% 95.9%;
--sidebar-border: 240 3.7% 15.9%;
--sidebar-ring: 217.2 91.2% 59.8%;
}
}
@layer base {
* {
@apply border-border;
}
body {
@apply bg-background text-foreground;
}
}
* {
@apply border-border;
}
body {
@apply bg-background text-foreground;
}
}
@layer components {
.shadow-sharp {
box-shadow: hsl(var(--primary)) 4px 3px 0px;
}
button.shadow-sharp,
a.shadow-sharp {
&:hover {
box-shadow: hsl(var(--primary)) 2px 1px 0px;
translate: 2px 1px;
}
}
}

View file

@ -31,7 +31,7 @@
{#each items as item, i (item.id)}
<div animate:flip={{ duration: flipDurationMs, easing: expoOut }}>
<Card.Root
class="overflow-hidden rounded-xl border bg-card text-card-foreground shadow {type ===
class="select-none overflow-hidden rounded-xl border bg-card text-card-foreground shadow shadow-foreground/15 transition-shadow hover:shadow-foreground/25 {type ===
'names'
? 'border-red-400 '
: type === 'artists'

View file

@ -1,5 +1,4 @@
import type { Player } from '$lib/types';
import { getContext, setContext } from 'svelte';
export class PlayerState {
players = $state<Player[]>([]);

View file

@ -0,0 +1,7 @@
<script lang="ts">
let { children } = $props();
</script>
<div class="container mt-32 flex flex-col items-center py-4">
{@render children()}
</div>

View file

@ -0,0 +1,30 @@
<script lang="ts">
const games = [
{
name: 'Paku biiti',
image: '',
href: 'pakubiiti'
}
];
</script>
<header class="mb-24 flex flex-col items-center font-title">
<h1 class="mb-1 scroll-m-20 text-6xl font-extrabold tracking-tight lg:text-7xl">
stuff.kasterpalu.ee
</h1>
<p class="text-2xl font-semibold text-muted-foreground">Minimängud ja muud huvitavat</p>
</header>
<main class="grid w-full max-w-4xl justify-items-center">
{#each games as { name, image, href }}
<a
class="shadow-sharp flex aspect-[4/1] w-full max-w-sm items-center justify-center rounded-xl border-2 border-current bg-contain bg-no-repeat transition-shadow transition-transform"
style="background-image: url('{image}')"
draggable="false"
{href}
>
<span class="relative block select-none rounded font-mono text-2xl font-semibold lg:text-3xl">
{name}
</span>
</a>
{/each}
</main>

View file

@ -0,0 +1,7 @@
<script lang="ts">
let { children } = $props();
</script>
<div class="container flex min-h-screen flex-col items-center justify-center py-4">
{@render children()}
</div>

View file

@ -33,17 +33,20 @@ export const load: PageServerLoad = async ({ fetch, locals }) => {
};
}
const albumData = fetch(`/api/getAlbums/${count}`)
const albumData = fetch(`/api/pakubiiti/getAlbums/${count}`)
.then((res) => {
return res.json();
})
.then((data) => {
const albumNames = data.albums.map((album) => ({ id: nanoid(), value: album.name }));
const albumImages = data.albums.map((album) => ({
const albumNames = data.albums.map((album: SpotifyApi.AlbumObjectSimplified) => ({
id: nanoid(),
value: album.images.at(0).url
value: album.name
}));
const albumArtists = data.albums.map((album) => ({
const albumImages = data.albums.map((album: SpotifyApi.AlbumObjectSimplified) => ({
id: nanoid(),
value: album.images.at(0)?.url || ''
}));
const albumArtists = data.albums.map((album: SpotifyApi.AlbumObjectSimplified) => ({
id: nanoid(),
value: album.artists.map((artist) => artist.name).join(', ')
}));

View file

@ -70,46 +70,56 @@
</AlertDialog.Content>
</AlertDialog.Root>
<form
action="?/submit"
method="POST"
use:enhance
class="grid w-full gap-6 transition-all {loading || data?.playing === false ? 'grayscale' : ''}"
>
{#if data?.streamed?.albums}
{#await data.streamed.albums}
{#each { length: 2 } as _}
<header class="mb-24 flex flex-col items-center font-title">
<h1 class="mb-1 scroll-m-20 text-6xl font-extrabold tracking-tight lg:text-7xl">Paku biiti</h1>
<p class="text-2xl font-semibold text-muted-foreground">
Lohista kokku õiged albumi <span class="text-red-600 dark:text-red-400">nimed</span>,
<span class="text-purple-600 dark:text-purple-400">artistid</span> ja
<span class="text-blue-600 dark:text-blue-400">pildid</span>.
</p>
</header>
<main class="w-full max-w-4xl">
<form
action="?/submit"
method="POST"
use:enhance
class="grid w-full gap-6 transition-all {loading || data?.playing === false ? 'grayscale' : ''}"
>
{#if data?.streamed?.albums}
{#await data.streamed.albums}
{#each { length: 2 } as _}
<section class="grid grid-cols-3 items-center gap-14">
{#each { length: 3 } as _}
<Skeleton class="h-[5.25rem] w-full rounded-xl " />
{/each}
</section>
<Separator />
{/each}
<section class="grid grid-cols-3 items-center gap-14">
{#each { length: 3 } as _}
<Skeleton class="h-[5.25rem] w-full rounded-xl " />
<Skeleton class="aspect-square h-auto max-w-full rounded-xl object-cover" />
{/each}
</section>
{@render footer(true)}
{:then albums}
<DndGroup items={albums.names} type="names"></DndGroup>
<Separator />
{/each}
<DndGroup items={albums.artists} type="artists"></DndGroup>
<Separator />
<DndGroup items={albums.images} image type="images"></DndGroup>
<section class="grid grid-cols-3 items-center gap-14">
{#each { length: 3 } as _}
<Skeleton class="aspect-square h-auto max-w-full rounded-xl object-cover" />
{/each}
</section>
{@render footer(true)}
{:then albums}
<DndGroup items={albums.names} type="names"></DndGroup>
{@render footer(false)}
{/await}
{:else}
<DndGroup items={oldAlbums.names} type="names"></DndGroup>
<Separator />
<DndGroup items={albums.artists} type="artists"></DndGroup>
<DndGroup items={oldAlbums.artists} type="artists"></DndGroup>
<Separator />
<DndGroup items={albums.images} image type="images"></DndGroup>
<DndGroup items={oldAlbums.images} image type="images"></DndGroup>
{@render footer(false)}
{/await}
{:else}
<DndGroup items={oldAlbums.names} type="names"></DndGroup>
<Separator />
<DndGroup items={oldAlbums.artists} type="artists"></DndGroup>
<Separator />
<DndGroup items={oldAlbums.images} image type="images"></DndGroup>
{@render footer(false)}
{/if}
</form>
{/if}
</form>
</main>

View file

@ -1,13 +1,32 @@
<script lang="ts">
import '../app.css';
import '@fontsource-variable/smooch-sans';
import '@fontsource-variable/kode-mono';
import { ModeWatcher, toggleMode } from 'mode-watcher';
import { ModeWatcher, resetMode, setMode } from 'mode-watcher';
import { Button } from '$lib/components/ui/button/index.js';
import Sun from 'lucide-svelte/icons/sun';
import Moon from 'lucide-svelte/icons/moon';
import LaptopMinimal from 'lucide-svelte/icons/laptop-minimal';
let { children } = $props();
let theme: string = $state('system');
const cycleTheme = () => {
if (theme === 'dark') {
theme = 'light';
setMode('light');
} else if (theme === 'light') {
theme = 'system';
resetMode();
} else {
theme = 'dark';
setMode('dark');
}
console.log(theme);
};
</script>
<ModeWatcher />
@ -16,27 +35,17 @@
<a href="/">
<img src="/favicon.svg" alt="Mihkel Martin Kasterpalu logo" class="h-10" />
</a>
<Button onclick={toggleMode} variant="outline" size="icon">
<Sun
class="h-[1.2rem] w-[1.2rem] rotate-0 scale-100 transition-all dark:-rotate-90 dark:scale-0"
/>
<Moon
class="absolute h-[1.2rem] w-[1.2rem] rotate-90 scale-0 transition-all dark:rotate-0 dark:scale-100"
/>
<Button onclick={() => cycleTheme()} variant="outline" size="icon">
{#if theme === 'dark'}
<Moon class="absolute h-[1.2rem] w-[1.2rem]" />
{:else if theme === 'light'}
<Sun class="h-[1.2rem] w-[1.2rem] " />
{:else}
<LaptopMinimal class="h-[1.2rem] w-[1.2rem]" />
{/if}
<span class="sr-only">Toggle theme</span>
</Button>
</header>
<div class="container flex min-h-screen flex-col items-center justify-center py-4">
<header class="font-title mb-24 flex flex-col items-center">
<h1 class="mb-1 scroll-m-20 text-6xl font-extrabold tracking-tight lg:text-7xl">Paku biiti</h1>
<p class="text-2xl font-semibold text-muted-foreground">
Lohista kokku õiged albumi <span class="text-red-600 dark:text-red-400">nimed</span>,
<span class="text-purple-600 dark:text-purple-400">artistid</span> ja
<span class="text-blue-600 dark:text-blue-400">pildid</span>.
</p>
</header>
<main class="w-full max-w-4xl">
{@render children()}
</main>
</div>
{@render children()}

View file

@ -68,7 +68,8 @@ const config: Config = {
},
fontFamily: {
sans: [...fontFamily.sans],
title: ['Smooch Sans Variable', ...fontFamily.sans]
title: ['Smooch Sans Variable', ...fontFamily.sans],
mono: ['Kode Mono Variable', ...fontFamily.mono]
},
keyframes: {
'accordion-down': {
@ -88,6 +89,9 @@ const config: Config = {
'accordion-down': 'accordion-down 0.2s ease-out',
'accordion-up': 'accordion-up 0.2s ease-out',
'caret-blink': 'caret-blink 1.25s ease-out infinite'
},
transitionTimingFunction: {
default: 'cubic-bezier(0.16, 1, 0.3, 1)'
}
}
},