Compare commits

..

No commits in common. "fd0e12b665ecc210b98d8602fde2ae423dcb7f2c" and "7de863879609b695c3fbc61d2776078695ac7709" have entirely different histories.

26 changed files with 1007 additions and 827 deletions

View file

@ -13,42 +13,41 @@
"lint": "prettier --check . && eslint ."
},
"devDependencies": {
"@eslint/compat": "^1.2.6",
"@eslint/js": "^9.19.0",
"@eslint/compat": "^1.2.5",
"@eslint/js": "^9.18.0",
"@sveltejs/adapter-node": "^5.2.12",
"@sveltejs/enhanced-img": "^0.4.4",
"@sveltejs/kit": "^2.16.1",
"@sveltejs/vite-plugin-svelte": "^5.0.3",
"@tailwindcss/container-queries": "^0.1.1",
"@tailwindcss/forms": "^0.5.10",
"@tailwindcss/postcss": "^4.0.1",
"@tailwindcss/vite": "^4.0.1",
"@types/spotify-web-api-node": "^5.0.11",
"autoprefixer": "^10.4.20",
"bits-ui": "1.0.0-next.80",
"clsx": "^2.1.1",
"eslint": "^9.19.0",
"eslint": "^9.18.0",
"eslint-config-prettier": "^10.0.1",
"eslint-plugin-svelte": "^2.46.1",
"globals": "^15.14.0",
"lucide-svelte": "^0.474.0",
"lucide-svelte": "^0.473.0",
"prettier": "^3.4.2",
"prettier-plugin-svelte": "^3.3.3",
"prettier-plugin-tailwindcss": "^0.6.11",
"svelte": "^5.19.6",
"prettier-plugin-tailwindcss": "^0.6.10",
"svelte": "^5.19.1",
"svelte-check": "^4.1.4",
"svelte-dnd-action": "^0.9.56",
"tailwind-merge": "^3.0.1",
"svelte-dnd-action": "^0.9.55",
"tailwind-merge": "^2.6.0",
"tailwind-variants": "^0.3.1",
"tailwindcss": "^4.0.1",
"tailwindcss": "^3.4.17",
"tailwindcss-animate": "^1.0.7",
"typescript": "^5.7.3",
"typescript-eslint": "^8.22.0",
"typescript-eslint": "^8.21.0",
"vite": "^6.0.11"
},
"dependencies": {
"@fontsource-variable/kode-mono": "^5.1.1",
"@fontsource-variable/smooch-sans": "^5.1.1",
"mode-watcher": "^0.5.1",
"mode-watcher": "^0.5.0",
"nanoid": "^5.0.9",
"spotify-web-api-node": "^5.0.2",
"svelte-kit-sessions": "^0.4.0"

1195
pnpm-lock.yaml generated

File diff suppressed because it is too large Load diff

View file

@ -1,5 +1,6 @@
export default {
plugins: {
'@tailwindcss/postcss': {}
tailwindcss: {},
autoprefixer: {}
}
};

View file

@ -1,42 +1,6 @@
@import 'tailwindcss';
@config '../tailwind.config.ts';
/*
The default border color has changed to `currentColor` in Tailwind CSS v4,
so we've added these compatibility styles to make sure everything still
looks the same as it did with Tailwind CSS v3.
If we ever want to remove these styles, we need to add an explicit border
color utility to any element that depends on these defaults.
*/
@layer base {
*,
::after,
::before,
::backdrop,
::file-selector-button {
border-color: var(--color-gray-200, currentColor);
}
}
@utility shadow-sharp {
box-shadow: hsl(var(--primary)) 4px 3px 0px;
&:hover {
box-shadow: hsl(var(--primary)) 2px 1px 0px;
translate: 2px 1px;
}
&.pressed {
box-shadow: hsl(var(--primary)) 2px 1px 0px;
translate: 2px 1px;
}
}
@utility container {
margin-inline: auto;
padding-inline: 2rem;
}
@tailwind base;
@tailwind components;
@tailwind utilities;
@layer base {
:root {
@ -109,3 +73,20 @@
@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;
}
&.pressed {
box-shadow: hsl(var(--primary)) 2px 1px 0px;
translate: 2px 1px;
}
}
}

View file

@ -31,7 +31,7 @@
<Link class={className} />
{/if}
<span class="underline underline-offset-4">
<span>
{author}
</span>
{/snippet}
@ -49,10 +49,7 @@
{#each tags as { name, description }}
<HoverCard.Root>
<HoverCard.Trigger>
<Badge
class="font-title text-base font-semibold leading-relaxed tracking-wide"
variant={badgeVariant}>{name}</Badge
>
<Badge class="transition-none" variant={badgeVariant}>{name}</Badge>
</HoverCard.Trigger>
<HoverCard.Content>
{description}
@ -68,7 +65,7 @@
class="flex items-center justify-center px-4 py-2 font-mono text-xs font-semibold text-secondary/80 dark:text-primary/80"
>
{#if image.credit.href}
<a class="w-full" href={image.credit.href}>
<a class="w-full transition-colors hover:text-white" href={image.credit.href}>
{@render creditText(
image.credit.author,
image.credit.type,

View file

@ -18,7 +18,7 @@
<AlertDialogPrimitive.Content
bind:ref
class={cn(
'fixed left-[50%] top-[50%] z-50 grid w-full max-w-lg translate-x-[-50%] translate-y-[-50%] gap-4 border bg-background p-6 shadow-lg duration-200 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 sm:rounded-lg',
'fixed left-[50%] top-[50%] z-50 grid w-full max-w-lg translate-x-[-50%] translate-y-[-50%] gap-4 border bg-background p-6 shadow-lg duration-200 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[state=closed]:slide-out-to-left-1/2 data-[state=closed]:slide-out-to-top-[48%] data-[state=open]:slide-in-from-left-1/2 data-[state=open]:slide-in-from-top-[48%] sm:rounded-lg',
className
)}
{...restProps}

View file

@ -1,14 +1,14 @@
<script lang="ts" module>
import { type VariantProps, tv } from 'tailwind-variants';
export const badgeVariants = tv({
base: 'focus:ring-ring inline-flex select-none items-center rounded-md border px-2.5 py-0.5 text-xs font-semibold transition-colors focus:outline-hidden focus:ring-2 focus:ring-offset-2',
base: 'focus:ring-ring inline-flex select-none items-center rounded-md border px-2.5 py-0.5 text-xs font-semibold transition-colors focus:outline-none focus:ring-2 focus:ring-offset-2',
variants: {
variant: {
default: 'bg-primary text-primary-foreground hover:bg-primary/80 border-transparent shadow-sm',
default: 'bg-primary text-primary-foreground hover:bg-primary/80 border-transparent shadow',
secondary:
'bg-secondary text-secondary-foreground hover:bg-secondary/80 border-transparent',
destructive:
'bg-destructive text-destructive-foreground hover:bg-destructive/80 border-transparent shadow-sm',
'bg-destructive text-destructive-foreground hover:bg-destructive/80 border-transparent shadow',
outline: 'text-foreground'
}
},

View file

@ -4,14 +4,14 @@
import { type VariantProps, tv } from 'tailwind-variants';
export const buttonVariants = tv({
base: 'focus-visible:ring-ring inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-colors focus-visible:outline-hidden focus-visible:ring-1 disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0',
base: 'focus-visible:ring-ring inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-1 disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0',
variants: {
variant: {
default: 'bg-primary text-primary-foreground hover:bg-primary/90 shadow-sm',
destructive: 'bg-destructive text-destructive-foreground hover:bg-destructive/90 shadow-xs',
default: 'bg-primary text-primary-foreground hover:bg-primary/90 shadow',
destructive: 'bg-destructive text-destructive-foreground hover:bg-destructive/90 shadow-sm',
outline:
'border-input bg-background hover:bg-accent hover:text-accent-foreground border shadow-xs',
secondary: 'bg-secondary text-secondary-foreground hover:bg-secondary/80 shadow-xs',
'border-input bg-background hover:bg-accent hover:text-accent-foreground border shadow-sm',
secondary: 'bg-secondary text-secondary-foreground hover:bg-secondary/80 shadow-sm',
ghost: 'hover:bg-accent hover:text-accent-foreground',
link: 'text-primary underline-offset-4 hover:underline'
},

View file

@ -13,7 +13,7 @@
<div
bind:this={ref}
class={cn('rounded-xl border bg-card text-card-foreground shadow-sm', className)}
class={cn('rounded-xl border bg-card text-card-foreground shadow', className)}
{...restProps}
>
{@render children?.()}

View file

@ -20,7 +20,7 @@
{sideOffset}
{align}
class={cn(
"bg-popover text-popover-foreground z-50 w-64 rounded-md border p-4 shadow-md outline-hidden",
"bg-popover text-popover-foreground z-50 w-64 rounded-md border p-4 shadow-md outline-none",
className
)}
{...restProps}

View file

@ -1,16 +1,34 @@
import type { GamesObj, TagsObj } from './types';
export const site = {
name: 'Mihkel Martin Kasterpalu',
name: 'Stuff.Kasterpalu',
author: 'Mihkel Martin Kasterpalu',
description: 'Portfoolio - Arendaja, Disainer, Muusik, DJ, Ettevõtja.',
description: 'Minimängud ja muud huvitavat.',
image: '/web-app-manifest-512x512.png'
};
export const stuffSite = {
name: 'Kasterpalu Stuff',
author: 'Mihkel Martin Kasterpalu',
description:
'Minimängud ja muud huvitavat. Kui mul tekkis lahe idee ja suutsin sellest midagi lahedat arendada siis näed seda ka siin.',
image: '/web-app-manifest-512x512.png'
export const baseURL = 'https://stuff.kasterpalu.ee';
export const games: GamesObj = {
'/epochalypse': {
name: 'Epochalypse',
image: '',
description: 'Varsti veel üks Y2K. Kui nostalgiline!'
},
'/pakubiiti': {
name: 'Paku biiti',
image: '',
description: 'Sorteeri kolme suvalise muusika albumi pealkiri, artistid ja pilt.'
},
'': {
name: 'Rohkem mänge soon™',
image: '',
description: ''
}
};
export const baseURL = 'https://kasterpalu.ee';
export const badges: TagsObj = {
muusika: { name: 'Muusika', description: 'Tegelen siin muusikaga' },
veeb: { name: 'Veebileht', description: 'Disainisin/kirjutasin veebilehe' },
asutaja: { name: 'Asutaja', description: 'Olin osa selle loomisest' }
};

View file

@ -1,9 +0,0 @@
import type { TagsObj } from '$lib/types';
const badges: TagsObj = {
muusika: { name: 'Muusika', description: 'Tegelen siin muusikaga' },
veeb: { name: 'Veebileht', description: 'Disainisin/kirjutasin veebilehe' },
asutaja: { name: 'Asutaja', description: 'Olin osa selle loomisest' }
};
export default badges;

View file

@ -1,21 +0,0 @@
import type { GamesObj } from '$lib/types';
const games: GamesObj = {
epochalypse: {
name: 'Epochalypse',
image: '',
description: 'Varsti veel üks Y2K. Kui nostalgiline!'
},
pakubiiti: {
name: 'Paku biiti',
image: '',
description: 'Sorteeri kolme suvalise muusika albumi pealkiri, artistid ja pilt.'
},
'': {
name: 'Rohkem mänge soon™',
image: '',
description: ''
}
};
export default games;

View file

@ -1,82 +0,0 @@
import type { Project } from '$lib/types';
import badges from './badges';
import skpImg from '$lib/assets/skp.jpg?enhanced';
import dysasterImg from '$lib/assets/dysaster.jpg?enhanced';
import monospaceeImg from '$lib/assets/monospacee.jpg?enhanced';
import saueauguImg from '$lib/assets/saueaugu.jpg?enhanced';
const projects: Project[] = [
{
name: 'SUPIKÖÖGIPOSID',
image: {
src: skpImg,
credit: {
type: 'instagram',
author: 'Mimmu',
href: 'https://www.instagram.com/musamimmu/'
},
alt: 'Pilt neljaliikmeliseslt räpibändist SUPIKÖÖGIPOISID. Pilt on tehtud pilves ilmaga õues. Pildil on 4 mees valgete triiksärkide, mustade lipsude ja mustade tagidega.'
},
description: 'Räpikollektiiv. Alustasime 2019.',
link: 'https://skpoisid.bandcamp.com/',
tags: [badges['muusika']]
},
{
name: 'Dysaster Collective',
image: {
src: dysasterImg,
credit: {
type: 'instagram',
author: 'Mattias Mägi',
href: 'https://www.instagram.com/mattias.mix/'
},
alt: 'Sinakas digitaalne kollaž Dysaster Collective liigetest.'
},
description: '2022 asutatud mitmekülgne loomekollektiiv.',
link: 'https://dysaster.ee',
tags: [badges['asutaja'], badges['veeb']]
},
{
name: 'monospac.ee',
image: {
src: monospaceeImg,
credit: {
type: 'instagram',
author: 'Liisa Jõhvik',
href: 'https://www.instagram.com/liisajohvik.photo/'
},
alt: 'DJ duo monospacee nende Silent Disco setil Tartu Uus Teater saalis aastal 2024. Pilt on külje pealt, esiplaanil DJ Mimmu ja ta tagant paistab DJ Rx. Nad on valgustatud punasesega, taustal häguselt näha siniselt valgustatud kolmandat DJd.'
},
description: 'DJ duo kaasliikmega Rx.',
link: 'https://monospac.ee',
tags: [badges['muusika'], badges['veeb']]
},
{
name: 'Saueagu Teatritalu',
image: {
src: saueauguImg,
alt: 'Suvine Saueaugu teatrihoone. All paistab roheline muru ja katuse tagant paistab paari pilvega sinine taevas.'
},
description: 'Taluteater Läänemaal. Tegin veebilehe.',
link: 'https://saueaugu.ee',
tags: [badges['veeb']]
},
{
name: 'Tartu Häkkerikoda',
image: {
src: '/assets/hakkerikoda.svg',
credit: {
type: 'web',
author: 'treierxyz',
href: 'https://treier.xyz'
},
alt: 'Tartu Häkkerikoda logo'
},
description: 'Makerspace Tartus. Liige ning osa veebilehe loojatest.',
link: 'https://hakkerikoda.ee',
tags: [badges['veeb']]
}
];
export default projects;

View file

@ -1,15 +1,12 @@
import type { LayoutServerData } from './$types';
import { site } from '$lib/config';
import games from '$lib/data/games';
import { games, site } from '$lib/config';
export const load: LayoutServerData = async ({ url }) => {
if (!url?.pathname) {
return;
}
const gameSlug = url.pathname.split('/').at(-1);
const game = games[gameSlug];
const game = games[url.pathname];
if (!game) {
return;

View file

@ -0,0 +1,23 @@
<script lang="ts">
import { baseURL, site } from '$lib/config.js';
let { children, data } = $props();
const title = data?.name ? `${data.name} | ${site.name}` : site.name;
</script>
<svelte:head>
<title>{title}</title>
<meta property="og:title" content={title} />
{#if data?.description}
<meta name="description" content={data.description} />
<meta property="og:description" content={data.description} />
{/if}
{#if data?.image}
<meta property="og:image" content={baseURL + data.image} />
{/if}
</svelte:head>
{@render children()}

View file

@ -22,13 +22,14 @@
{#snippet clockStage(count: number, index: number, title: string, divider = true)}
<div class="flex flex-col items-center gap-2">
<div
class="card bg-muted ring-foreground/25 h-[2.75rem] overflow-hidden rounded-xl font-mono text-xl ring-3 drop-shadow-md"
class="card h-[2.75rem] overflow-hidden rounded-xl bg-muted font-mono text-xl ring ring-foreground/25 drop-shadow-md"
>
<div
style="translate: 0 calc({index} * -2rem); transition-duration: {index === count - 1
style="translate: 0 calc({index} * -2rem); transition-timing-function: cubic-bezier(.65,.01,.04,.97); transition-duration: {index ===
count - 1
? '600ms'
: '300ms'};"
class="ease-bouncy flex flex-col gap-1 px-4 py-2 transition-transform"
class="flex flex-col gap-1 px-4 py-2 transition-all"
>
{#each { length: count } as _, i}
{@render clockDigit(i)}
@ -36,24 +37,24 @@
</div>
</div>
<p class="text-muted-foreground text-sm">{title}</p>
<p class="text-sm text-muted-foreground">{title}</p>
</div>
{#if divider}
<p class="text-muted-foreground mb-7 block text-center text-lg font-semibold">:</p>
<p class="mb-7 block text-center text-lg font-semibold text-muted-foreground">:</p>
{:else}
<p
class="text-muted-foreground invisible hidden text-center text-lg font-semibold md:visible md:block"
class="invisible hidden text-center text-lg font-semibold text-muted-foreground md:visible md:block"
></p>
{/if}
{/snippet}
<header class="font-title mb-24 flex flex-col items-center text-center">
<header class="mb-24 flex flex-col items-center text-center font-title">
<h1 class="mb-1 scroll-m-20 text-5xl font-extrabold tracking-tight lg:text-6xl">Epochalypse</h1>
<p class="text-muted-foreground text-xl font-semibold">Ära muretse! Sul on veel aega:</p>
<p class="text-xl font-semibold text-muted-foreground">Ära muretse! Sul on veel aega:</p>
</header>
<main class="w-full max-w-4xl">
<div
class="bg-muted mx-auto grid w-full grid-cols-[auto_auto_auto] items-center justify-center gap-4 rounded-md p-12 md:flex"
class="mx-auto grid w-full grid-cols-[auto_auto_auto] items-center justify-center gap-4 rounded-md bg-muted p-12 md:flex"
>
{@render clockStage(13, timeUntil.years, 'aastat')}
{@render clockStage(13, timeUntil.months, 'kuud', false)}
@ -67,45 +68,45 @@
<Accordion.Item value="item-1">
<Accordion.Trigger>Mis asi see on??</Accordion.Trigger>
<Accordion.Content class="text-md">
<p class="leading-7 not-first:mt-6">
<p class="leading-7 [&:not(:first-child)]:mt-6">
Epochalypse nimeline probleem on sarnane 1990 lõpus toimunud <a
href="https://en.wikipedia.org/wiki/Year_2000_problem"
class="text-primary font-medium underline underline-offset-4">Y2K</a
class="font-medium text-primary underline underline-offset-4">Y2K</a
>-le <br />
Laialdaselt kasutatud süsteem aja ja kuupäeva märkimiseks <strong>saab otsa.</strong>
</p>
<p class="leading-7 not-first:mt-6">
<p class="leading-7 [&:not(:first-child)]:mt-6">
Enamus Linuxi baasiga Operatsioonisüsteeme kasutab aja märkimiseks <a
href="https://en.wikipedia.org/wiki/Unix_time"
class="text-primary font-medium underline underline-offset-4">UNIX aega</a
class="font-medium text-primary underline underline-offset-4">UNIX aega</a
>
- mitu sekundit on möödunud UNIX <i>epoch</i>-ist 00:00:00 19.01.1970 UTC.
</p>
<p class="leading-7 not-first:mt-6">
<p class="leading-7 [&:not(:first-child)]:mt-6">
Neid sekundeid hoitaks <i>signed 32-bit</i> täisarvuna. Selle andmetüübi maksimaalne
väärtus on
<code
class="bg-muted relative rounded px-[0.3rem] py-[0.2rem] font-mono text-sm font-semibold"
class="relative rounded bg-muted px-[0.3rem] py-[0.2rem] font-mono text-sm font-semibold"
>2<sup>31</sup>-1</code
>. Viimane kuupäev (<code
class="bg-muted relative rounded px-[0.3rem] py-[0.2rem] font-mono text-sm font-semibold"
class="relative rounded bg-muted px-[0.3rem] py-[0.2rem] font-mono text-sm font-semibold"
>UNIX epoch + 2<sup>31</sup>-1</code
>) on 03:14:07 19.01.2038 UTC.
</p>
<p class="leading-7 not-first:mt-6">
<p class="leading-7 [&:not(:first-child)]:mt-6">
Sekund pärast seda, kui arvutid lisavad ajale sekundi juurde, juhtub <a
href="https://en.wikipedia.org/wiki/Integer_overflow"
class="text-primary font-medium underline underline-offset-4">täisarvu ületäitumine</a
class="font-medium text-primary underline underline-offset-4">täisarvu ületäitumine</a
>
ja aja väärtuseks saab
<code
class="bg-muted relative rounded px-[0.3rem] py-[0.2rem] font-mono text-sm font-semibold"
class="relative rounded bg-muted px-[0.3rem] py-[0.2rem] font-mono text-sm font-semibold"
>-2<sup>31</sup></code
>. <br />
Arvutid loevad seda kui 20:45:52 13.12.1901 UTC, ehk varaseim võimalik aeg
<a
href="https://en.wikipedia.org/wiki/Unix_time"
class="text-primary font-medium underline underline-offset-4">UNIX aja</a
class="font-medium text-primary underline underline-offset-4">UNIX aja</a
> järgi.
</p>
</Accordion.Content>

View file

@ -41,7 +41,7 @@
>
{#each { length: 3 }}
{#if i < 2}
<Skeleton class="border-primary/5 h-[5rem] w-full rounded-xl border" />
<Skeleton class="h-[5rem] w-full rounded-xl border border-primary/5" />
{:else}
<Skeleton class="aspect-square h-auto max-w-full rounded-xl object-cover" />
{/if}
@ -86,9 +86,9 @@
</AlertDialog.Content>
</AlertDialog.Root>
<header class="font-title mb-12 flex flex-col items-center text-center">
<header class="mb-12 flex flex-col items-center text-center font-title">
<h1 class="mb-1 scroll-m-20 text-5xl font-extrabold tracking-tight lg:text-6xl">Paku biiti</h1>
<p class="text-muted-foreground text-xl font-semibold">
<p class="text-xl 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>.

View file

@ -40,23 +40,20 @@
<a href="/">
<img src="/favicon.svg" alt="Mihkel Martin Kasterpalu logo" class="h-9" />
</a>
<a class="font-mono font-medium underline underline-offset-4" href="/vinge"> vinge värk </a>
<Button onclick={() => cycleTheme()} variant="ghost" size="icon" class="h-12 w-12">
{#if theme === 'dark'}
<Moon class="h-6! w-6!" />
<Moon class="!h-6 !w-6" />
{:else if theme === 'light'}
<Sun class="h-6! w-6!" />
<Sun class="!h-6 !w-6" />
{:else}
<LaptopMinimal class="h-6! w-6!" />
<LaptopMinimal class="!h-6 !w-6" />
{/if}
<span class="sr-only">Toggle theme</span>
</Button>
</header>
<div class="container relative mt-8 flex flex-col items-center">
<div class="container relative flex flex-col items-center">
{@render children()}
</div>

View file

@ -1,35 +1,7 @@
<script lang="ts">
import type { EnhancedImage, Tag } from '$lib/types';
import { site, baseURL } from '$lib/config';
import SquareArrowOutUpRight from 'lucide-svelte/icons/square-arrow-out-up-right';
import { Button } from '$lib/components/ui/button/index.js';
import Image from '$lib/components/Image.svelte';
import projects from '$lib/data/projects';
import { site, baseURL, games } from '$lib/config';
</script>
{#snippet projectCard(
name: string,
description: string,
image: EnhancedImage,
tags: Tag[],
link: string
)}
<div class="mb-16 w-80 space-y-3 md:w-64">
<Image {image} {tags} class="aspect-4/5 object-cover" />
<div class="grid grid-cols-[1fr_auto] items-center text-sm">
<div class="mt-1 pr-4">
<h3 class="text-lg font-medium leading-none">{name}</h3>
<p class="mt-1.5 text-sm leading-5 text-muted-foreground">{description}</p>
</div>
<Button target="_blank" href={link} variant="secondary" size="icon">
<SquareArrowOutUpRight />
</Button>
</div>
</div>
{/snippet}
<svelte:head>
<title>{site.name}</title>
<meta property="og:title" content={site.name} />
@ -44,13 +16,31 @@
<h1 class="mb-1 scroll-m-20 text-5xl font-extrabold tracking-tight lg:text-6xl">
Hei! Mina olen Mihkel
</h1>
<p class="font-sans text-base text-muted-foreground">
Peale selle toreda saidi on mul veel palju hobisid
<p class="text-xl font-semibold text-muted-foreground">
Siin saidil on mu loodud minimängud ja muud huvitavat. Aitäh <a
href="https://neal.fun"
class="font-medium underline underline-offset-4">neal.fun</a
> inspo eest :]
</p>
<p class="text-xl font-semibold text-muted-foreground">
Vaata ka mu
<a href="/projektid" class="text-primary underline underline-offset-4">teisi projekte</a>.
</p>
</header>
<main class="flex w-full flex-wrap justify-center gap-x-8">
{#each projects as { name, description, image, tags, link }}
{@render projectCard(name, description, image, tags, link)}
<main class="grid w-full max-w-4xl justify-items-center gap-y-8 lg:grid-cols-2">
{#each Object.entries(games) as [href, { image, name }]}
<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-all {href ===
''
? 'pressed pointer-events-none'
: ''}"
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,139 @@
<script lang="ts">
import type { EnhancedImage, Project, Tag } from '$lib/types';
import { site, baseURL, badges } from '$lib/config';
import SquareArrowOutUpRight from 'lucide-svelte/icons/square-arrow-out-up-right';
import { Button } from '$lib/components/ui/button/index.js';
import Image from '$lib/components/Image.svelte';
import skpImg from '$lib/assets/skp.jpg?enhanced';
import dysasterImg from '$lib/assets/dysaster.jpg?enhanced';
import monospaceeImg from '$lib/assets/monospacee.jpg?enhanced';
import saueauguImg from '$lib/assets/saueaugu.jpg?enhanced';
export const projects: Project[] = [
{
name: 'SUPIKÖÖGIPOSID',
image: {
src: skpImg,
credit: {
type: 'instagram',
author: 'Mimmu',
href: 'https://www.instagram.com/musamimmu/'
},
alt: 'Pilt neljaliikmeliseslt räpibändist SUPIKÖÖGIPOISID. Pilt on tehtud pilves ilmaga õues. Pildil on 4 mees valgete triiksärkide, mustade lipsude ja mustade tagidega.'
},
description: 'Räpikollektiiv. Alustasime 2019.',
link: 'https://skpoisid.bandcamp.com/',
tags: [badges['muusika']]
},
{
name: 'Dysaster Collective',
image: {
src: dysasterImg,
credit: {
type: 'instagram',
author: 'Mattias Mägi',
href: 'https://www.instagram.com/mattias.mix/'
},
alt: 'Sinakas digitaalne kollaž Dysaster Collective liigetest.'
},
description: '2022 asutatud mitmekülgne loomekollektiiv.',
link: 'https://dysaster.ee',
tags: [badges['asutaja'], badges['veeb']]
},
{
name: 'monospac.ee',
image: {
src: monospaceeImg,
credit: {
type: 'instagram',
author: 'Liisa Jõhvik',
href: 'https://www.instagram.com/liisajohvik.photo/'
},
alt: 'DJ duo monospacee nende Silent Disco setil Tartu Uus Teater saalis aastal 2024. Pilt on külje pealt, esiplaanil DJ Mimmu ja ta tagant paistab DJ Rx. Nad on valgustatud punasesega, taustal häguselt näha siniselt valgustatud kolmandat DJd.'
},
description: 'DJ duo kaasliikmega Rx.',
link: 'https://monospac.ee',
tags: [badges['muusika'], badges['veeb']]
},
{
name: 'Saueagu Teatritalu',
image: {
src: saueauguImg,
alt: 'Suvine Saueaugu teatrihoone. All paistab roheline muru ja katuse tagant paistab paari pilvega sinine taevas.'
},
description: 'Taluteater Läänemaal. Tegin veebilehe.',
link: 'https://saueaugu.ee',
tags: [badges['veeb']]
},
{
name: 'Tartu Häkkerikoda',
image: {
src: '/assets/hakkerikoda.svg',
credit: {
type: 'web',
author: 'treierxyz',
href: 'https://treier.xyz'
},
alt: 'Tartu Häkkerikoda logo'
},
description: 'Makerspace Tartus. Liige ning osa veebilehe loojatest.',
link: 'https://hakkerikoda.ee',
tags: [badges['veeb']]
}
];
</script>
{#snippet projectCard(
name: string,
description: string,
image: EnhancedImage,
tags: Tag[],
link: string
)}
<div class="mb-16 w-80 space-y-3 md:w-64">
<Image {image} {tags} class="aspect-[4/5] object-cover" />
<div class="grid grid-cols-[1fr_auto] items-center text-sm">
<div class="mt-2 pr-4">
<h3 class="font-medium leading-none">{name}</h3>
<p class="mt-2 text-xs leading-5 text-muted-foreground">{description}</p>
</div>
<Button target="_blank" href={link} variant="secondary" size="icon">
<SquareArrowOutUpRight />
</Button>
</div>
</div>
{/snippet}
<svelte:head>
<title>Projektid | {site.name}</title>
<meta property="og:title" content={'Projektid | ' + site.name} />
<meta
name="description"
content="Projektid millega tegelenud, asjad mida teinud - tule uudista."
/>
<meta
property="og:description"
content="Projektid millega tegelenud, asjad mida teinud - tule uudista."
/>
<meta property="og:image" content={baseURL + site.image} />
</svelte:head>
<header class="mb-24 flex flex-col items-center text-center font-title">
<h1 class="mb-1 scroll-m-20 text-5xl font-extrabold tracking-tight lg:text-6xl">
Muud projektid
</h1>
<p class="text-xl font-semibold text-muted-foreground">
Peale selle toreda saidi on mul veel palju hobisid
</p>
</header>
<main class="flex w-full max-w-4xl flex-wrap justify-center gap-x-8">
{#each projects as { name, description, image, tags, link }}
{@render projectCard(name, description, image, tags, link)}
{/each}
</main>

View file

@ -1,21 +0,0 @@
<script lang="ts">
import { baseURL, stuffSite } from '$lib/config.js';
let { children, data } = $props();
const title = data?.name ? `${data.name} | ${stuffSite.name}` : stuffSite.name;
const description = data?.description || stuffSite.description;
const ogImage = data?.image || stuffSite.image;
</script>
<svelte:head>
<title>{title}</title>
<meta property="og:title" content={title} />
<meta name="description" content={description} />
<meta property="og:description" content={description} />
<meta property="og:image" content={baseURL + ogImage} />
</svelte:head>
{@render children()}

View file

@ -1,49 +0,0 @@
<script lang="ts">
import { site, baseURL } from '$lib/config';
import games from '$lib/data/games';
let { data } = $props();
$inspect(data);
</script>
<svelte:head>
<title>{site.name}</title>
<meta property="og:title" content={site.name} />
<meta name="description" content={site.description} />
<meta property="og:description" content={site.description} />
<meta property="og:image" content={baseURL + site.image} />
</svelte:head>
<header class="mb-24 flex flex-col items-center text-center font-title">
<h1 class="mb-1 scroll-m-20 text-5xl font-extrabold tracking-tight lg:text-6xl">Vinge värk</h1>
<p class="font-sans text-base leading-7 text-muted-foreground">
Minu loodud minimängud ja muud huvitavat.
</p>
<p class="font-sans text-base leading-7 text-muted-foreground">
Aitäh
<a href="https://neal.fun" class="font-medium underline underline-offset-4">neal.fun</a> inspo eest
:)
</p>
</header>
<div
class="mx-auto mb-24 flex w-full flex-wrap items-center justify-center justify-items-center gap-4 gap-x-8 gap-y-8 lg:grid-cols-2"
>
{#each Object.entries(games) as [href, { image, name }]}
<a
class="shadow-sharp flex aspect-4/1 min-h-20 max-w-sm items-center justify-center rounded-xl border-2 border-current bg-contain bg-no-repeat transition-all {href ===
''
? 'pressed pointer-events-none'
: ''}"
style="background-image: url('{image}')"
draggable="false"
href="/vinge/{href}"
>
<span class="relative block select-none rounded font-mono text-lg font-semibold lg:text-xl">
{name}
</span>
</a>
{/each}
</div>

View file

@ -3,9 +3,17 @@ import type { Config } from 'tailwindcss';
import tailwindcssAnimate from 'tailwindcss-animate';
const config: Config = {
darkMode: ['class', 'dark'],
darkMode: ['class'],
content: ['./src/**/*.{html,js,svelte,ts}'],
safelist: ['dark'],
theme: {
container: {
center: true,
padding: '2rem',
screens: {
'2xl': '1400px'
}
},
extend: {
colors: {
border: 'hsl(var(--border) / <alpha-value>)',
@ -83,11 +91,7 @@ const config: Config = {
'caret-blink': 'caret-blink 1.25s ease-out infinite'
},
transitionTimingFunction: {
default: 'cubic-bezier(0.16, 1, 0.3, 1)',
in: 'cubic-bezier(0.7, 0, 0.84, 0)',
out: 'cubic-bezier(0.16, 1, 0.3, 1)',
'in-out': 'cubic-bezier(0.87, 0, 0.13, 1)',
bouncy: 'cubic-bezier(.65,.01,.04,.97)'
default: 'cubic-bezier(0.16, 1, 0.3, 1)'
}
}
},