Compare commits

...

7 commits

Author SHA1 Message Date
0314e0bbaa
wip: update main page stuff
All checks were successful
continuous-integration/drone/push Build is passing
2025-10-11 22:54:36 +03:30
07dc355c6c
style: add gruvbox dark & gruvbox light themes 2025-10-11 22:54:22 +03:30
e72406c6e8
build: add lucide-svelte & svelte-simple-icons as dependency 2025-10-11 22:54:01 +03:30
19b67d0b53
assets: some images 2025-10-11 22:53:26 +03:30
e629777933
feat: project component 2025-10-11 22:53:08 +03:30
a6462d8bd3
feat: theme component 2025-10-11 22:52:53 +03:30
2e7009fef2
feat: tiltcard component 2025-10-11 22:52:43 +03:30
14 changed files with 633 additions and 98 deletions

View file

@ -38,5 +38,9 @@
"typescript-eslint": "^8.20.0", "typescript-eslint": "^8.20.0",
"vite": "^5.4.14", "vite": "^5.4.14",
"vitest": "^2.1.8" "vitest": "^2.1.8"
},
"dependencies": {
"lucide-svelte": "^0.544.0",
"svelte-simple-icons": "^1.0.3"
} }
} }

75
pnpm-lock.yaml generated
View file

@ -7,6 +7,13 @@ settings:
importers: importers:
.: .:
dependencies:
lucide-svelte:
specifier: ^0.544.0
version: 0.544.0(svelte@5.19.0)
svelte-simple-icons:
specifier: ^1.0.3
version: 1.0.3
devDependencies: devDependencies:
'@eslint/compat': '@eslint/compat':
specifier: ^1.2.5 specifier: ^1.2.5
@ -16,13 +23,13 @@ importers:
version: 9.18.0 version: 9.18.0
'@sveltejs/adapter-static': '@sveltejs/adapter-static':
specifier: ^3.0.8 specifier: ^3.0.8
version: 3.0.8(@sveltejs/kit@2.16.0(@sveltejs/vite-plugin-svelte@4.0.4(svelte@5.19.0)(vite@5.4.11))(svelte@5.19.0)(vite@5.4.11)) version: 3.0.8(@sveltejs/kit@2.16.0(@sveltejs/vite-plugin-svelte@4.0.4(svelte@5.19.0)(vite@5.4.19))(svelte@5.19.0)(vite@5.4.19))
'@sveltejs/kit': '@sveltejs/kit':
specifier: ^2.0.0 specifier: ^2.0.0
version: 2.16.0(@sveltejs/vite-plugin-svelte@4.0.4(svelte@5.19.0)(vite@5.4.11))(svelte@5.19.0)(vite@5.4.11) version: 2.16.0(@sveltejs/vite-plugin-svelte@4.0.4(svelte@5.19.0)(vite@5.4.19))(svelte@5.19.0)(vite@5.4.19)
'@sveltejs/vite-plugin-svelte': '@sveltejs/vite-plugin-svelte':
specifier: ^4.0.0 specifier: ^4.0.0
version: 4.0.4(svelte@5.19.0)(vite@5.4.11) version: 4.0.4(svelte@5.19.0)(vite@5.4.19)
'@tailwindcss/container-queries': '@tailwindcss/container-queries':
specifier: ^0.1.1 specifier: ^0.1.1
version: 0.1.1(tailwindcss@3.4.17) version: 0.1.1(tailwindcss@3.4.17)
@ -72,8 +79,8 @@ importers:
specifier: ^8.20.0 specifier: ^8.20.0
version: 8.20.0(eslint@9.18.0(jiti@1.21.7))(typescript@5.7.3) version: 8.20.0(eslint@9.18.0(jiti@1.21.7))(typescript@5.7.3)
vite: vite:
specifier: ^5.4.11 specifier: ^5.4.14
version: 5.4.11 version: 5.4.19
vitest: vitest:
specifier: ^2.1.8 specifier: ^2.1.8
version: 2.1.8 version: 2.1.8
@ -1049,6 +1056,11 @@ packages:
lru-cache@10.4.3: lru-cache@10.4.3:
resolution: {integrity: sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==} resolution: {integrity: sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==}
lucide-svelte@0.544.0:
resolution: {integrity: sha512-8kBxSivf8SJdEUJRHBpu9bRw0S/qfVK+Yfb92KQnRRBdP425RzT6aQfrIfZctG1oucPVTBQe1ZXgmth/3qVICg==}
peerDependencies:
svelte: ^3 || ^4 || ^5.0.0-next.42
magic-string@0.30.17: magic-string@0.30.17:
resolution: {integrity: sha512-sNPKHvyjVf7gyjwS4xGTaW/mCnF8wnjtifKBEhxfZ7E/S8tQ0rssrwGNn6q8JH/ohItJfSQp9mBtQYuTlH5QnA==} resolution: {integrity: sha512-sNPKHvyjVf7gyjwS4xGTaW/mCnF8wnjtifKBEhxfZ7E/S8tQ0rssrwGNn6q8JH/ohItJfSQp9mBtQYuTlH5QnA==}
@ -1449,6 +1461,9 @@ packages:
svelte: svelte:
optional: true optional: true
svelte-simple-icons@1.0.3:
resolution: {integrity: sha512-BmQPPrV9fHMH+BJSf4xBKNP0oW2XCtWizZnfAnlOBn54UWRLWmk8uTt0JRAF5NfS+74DD09oHADAroneC94eFg==}
svelte@5.19.0: svelte@5.19.0:
resolution: {integrity: sha512-qvd2GvvYnJxS/MteQKFSMyq8cQrAAut28QZ39ySv9k3ggmhw4Au4Rfcsqva74i0xMys//OhbhVCNfXPrDzL/Bg==} resolution: {integrity: sha512-qvd2GvvYnJxS/MteQKFSMyq8cQrAAut28QZ39ySv9k3ggmhw4Au4Rfcsqva74i0xMys//OhbhVCNfXPrDzL/Bg==}
engines: {node: '>=18'} engines: {node: '>=18'}
@ -1539,8 +1554,8 @@ packages:
engines: {node: ^18.0.0 || >=20.0.0} engines: {node: ^18.0.0 || >=20.0.0}
hasBin: true hasBin: true
vite@5.4.11: vite@5.4.19:
resolution: {integrity: sha512-c7jFQRklXua0mTzneGW9QVyxFjUgwcihC4bXEtujIo2ouWCe1Ajt/amn2PCxYnhYfd5k09JX3SB7OYWFKYqj8Q==} resolution: {integrity: sha512-qO3aKv3HoQC8QKiNSTuUM1l9o/XX3+c+VTgLHbJWHZGeTPVAg2XwazI9UWzoxjIJCGCV2zU60uqMzjeLZuULqA==}
engines: {node: ^18.0.0 || >=20.0.0} engines: {node: ^18.0.0 || >=20.0.0}
hasBin: true hasBin: true
peerDependencies: peerDependencies:
@ -1878,13 +1893,13 @@ snapshots:
'@rollup/rollup-win32-x64-msvc@4.30.1': '@rollup/rollup-win32-x64-msvc@4.30.1':
optional: true optional: true
'@sveltejs/adapter-static@3.0.8(@sveltejs/kit@2.16.0(@sveltejs/vite-plugin-svelte@4.0.4(svelte@5.19.0)(vite@5.4.11))(svelte@5.19.0)(vite@5.4.11))': '@sveltejs/adapter-static@3.0.8(@sveltejs/kit@2.16.0(@sveltejs/vite-plugin-svelte@4.0.4(svelte@5.19.0)(vite@5.4.19))(svelte@5.19.0)(vite@5.4.19))':
dependencies: dependencies:
'@sveltejs/kit': 2.16.0(@sveltejs/vite-plugin-svelte@4.0.4(svelte@5.19.0)(vite@5.4.11))(svelte@5.19.0)(vite@5.4.11) '@sveltejs/kit': 2.16.0(@sveltejs/vite-plugin-svelte@4.0.4(svelte@5.19.0)(vite@5.4.19))(svelte@5.19.0)(vite@5.4.19)
'@sveltejs/kit@2.16.0(@sveltejs/vite-plugin-svelte@4.0.4(svelte@5.19.0)(vite@5.4.11))(svelte@5.19.0)(vite@5.4.11)': '@sveltejs/kit@2.16.0(@sveltejs/vite-plugin-svelte@4.0.4(svelte@5.19.0)(vite@5.4.19))(svelte@5.19.0)(vite@5.4.19)':
dependencies: dependencies:
'@sveltejs/vite-plugin-svelte': 4.0.4(svelte@5.19.0)(vite@5.4.11) '@sveltejs/vite-plugin-svelte': 4.0.4(svelte@5.19.0)(vite@5.4.19)
'@types/cookie': 0.6.0 '@types/cookie': 0.6.0
cookie: 0.6.0 cookie: 0.6.0
devalue: 5.1.1 devalue: 5.1.1
@ -1897,27 +1912,27 @@ snapshots:
set-cookie-parser: 2.7.1 set-cookie-parser: 2.7.1
sirv: 3.0.0 sirv: 3.0.0
svelte: 5.19.0 svelte: 5.19.0
vite: 5.4.11 vite: 5.4.19
'@sveltejs/vite-plugin-svelte-inspector@3.0.1(@sveltejs/vite-plugin-svelte@4.0.4(svelte@5.19.0)(vite@5.4.11))(svelte@5.19.0)(vite@5.4.11)': '@sveltejs/vite-plugin-svelte-inspector@3.0.1(@sveltejs/vite-plugin-svelte@4.0.4(svelte@5.19.0)(vite@5.4.19))(svelte@5.19.0)(vite@5.4.19)':
dependencies: dependencies:
'@sveltejs/vite-plugin-svelte': 4.0.4(svelte@5.19.0)(vite@5.4.11) '@sveltejs/vite-plugin-svelte': 4.0.4(svelte@5.19.0)(vite@5.4.19)
debug: 4.4.0 debug: 4.4.0
svelte: 5.19.0 svelte: 5.19.0
vite: 5.4.11 vite: 5.4.19
transitivePeerDependencies: transitivePeerDependencies:
- supports-color - supports-color
'@sveltejs/vite-plugin-svelte@4.0.4(svelte@5.19.0)(vite@5.4.11)': '@sveltejs/vite-plugin-svelte@4.0.4(svelte@5.19.0)(vite@5.4.19)':
dependencies: dependencies:
'@sveltejs/vite-plugin-svelte-inspector': 3.0.1(@sveltejs/vite-plugin-svelte@4.0.4(svelte@5.19.0)(vite@5.4.11))(svelte@5.19.0)(vite@5.4.11) '@sveltejs/vite-plugin-svelte-inspector': 3.0.1(@sveltejs/vite-plugin-svelte@4.0.4(svelte@5.19.0)(vite@5.4.19))(svelte@5.19.0)(vite@5.4.19)
debug: 4.4.0 debug: 4.4.0
deepmerge: 4.3.1 deepmerge: 4.3.1
kleur: 4.1.5 kleur: 4.1.5
magic-string: 0.30.17 magic-string: 0.30.17
svelte: 5.19.0 svelte: 5.19.0
vite: 5.4.11 vite: 5.4.19
vitefu: 1.0.5(vite@5.4.11) vitefu: 1.0.5(vite@5.4.19)
transitivePeerDependencies: transitivePeerDependencies:
- supports-color - supports-color
@ -2025,13 +2040,13 @@ snapshots:
chai: 5.1.2 chai: 5.1.2
tinyrainbow: 1.2.0 tinyrainbow: 1.2.0
'@vitest/mocker@2.1.8(vite@5.4.11)': '@vitest/mocker@2.1.8(vite@5.4.19)':
dependencies: dependencies:
'@vitest/spy': 2.1.8 '@vitest/spy': 2.1.8
estree-walker: 3.0.3 estree-walker: 3.0.3
magic-string: 0.30.17 magic-string: 0.30.17
optionalDependencies: optionalDependencies:
vite: 5.4.11 vite: 5.4.19
'@vitest/pretty-format@2.1.8': '@vitest/pretty-format@2.1.8':
dependencies: dependencies:
@ -2541,6 +2556,10 @@ snapshots:
lru-cache@10.4.3: {} lru-cache@10.4.3: {}
lucide-svelte@0.544.0(svelte@5.19.0):
dependencies:
svelte: 5.19.0
magic-string@0.30.17: magic-string@0.30.17:
dependencies: dependencies:
'@jridgewell/sourcemap-codec': 1.5.0 '@jridgewell/sourcemap-codec': 1.5.0
@ -2861,6 +2880,8 @@ snapshots:
optionalDependencies: optionalDependencies:
svelte: 5.19.0 svelte: 5.19.0
svelte-simple-icons@1.0.3: {}
svelte@5.19.0: svelte@5.19.0:
dependencies: dependencies:
'@ampproject/remapping': 2.3.0 '@ampproject/remapping': 2.3.0
@ -2978,7 +2999,7 @@ snapshots:
debug: 4.4.0 debug: 4.4.0
es-module-lexer: 1.6.0 es-module-lexer: 1.6.0
pathe: 1.1.2 pathe: 1.1.2
vite: 5.4.11 vite: 5.4.19
transitivePeerDependencies: transitivePeerDependencies:
- '@types/node' - '@types/node'
- less - less
@ -2990,7 +3011,7 @@ snapshots:
- supports-color - supports-color
- terser - terser
vite@5.4.11: vite@5.4.19:
dependencies: dependencies:
esbuild: 0.21.5 esbuild: 0.21.5
postcss: 8.5.1 postcss: 8.5.1
@ -2998,14 +3019,14 @@ snapshots:
optionalDependencies: optionalDependencies:
fsevents: 2.3.3 fsevents: 2.3.3
vitefu@1.0.5(vite@5.4.11): vitefu@1.0.5(vite@5.4.19):
optionalDependencies: optionalDependencies:
vite: 5.4.11 vite: 5.4.19
vitest@2.1.8: vitest@2.1.8:
dependencies: dependencies:
'@vitest/expect': 2.1.8 '@vitest/expect': 2.1.8
'@vitest/mocker': 2.1.8(vite@5.4.11) '@vitest/mocker': 2.1.8(vite@5.4.19)
'@vitest/pretty-format': 2.1.8 '@vitest/pretty-format': 2.1.8
'@vitest/runner': 2.1.8 '@vitest/runner': 2.1.8
'@vitest/snapshot': 2.1.8 '@vitest/snapshot': 2.1.8
@ -3021,7 +3042,7 @@ snapshots:
tinyexec: 0.3.2 tinyexec: 0.3.2
tinypool: 1.0.2 tinypool: 1.0.2
tinyrainbow: 1.2.0 tinyrainbow: 1.2.0
vite: 5.4.11 vite: 5.4.19
vite-node: 2.1.8 vite-node: 2.1.8
why-is-node-running: 2.3.0 why-is-node-running: 2.3.0
transitivePeerDependencies: transitivePeerDependencies:

View file

@ -1,79 +1,97 @@
<div> <script>
<span class="text-2xl">Portfolio under construction...</span> import Project from './project.svelte';
<br /> import ThemeToggle from './theme.svelte';
import TiltCard from './tiltcard.svelte';
If you felt disappointed, let it be a practice to control your expectations... But I promise to // Assuming icons for Github, Gitlab, Codeberg (Code), Forgejo (GitBranch)
tidy this place up soon!!! :D import { Github, Gitlab, Code, GitBranch } from 'lucide-svelte';
<br />
Anyways, here are some links to all my <span class="text-accent_text"> social handles </span>: const repository = {
<br /> label: 'Forge',
<br /> url: 'https://git.light7734.com/light7734/light',
iconComponent: GitBranch
};
Mail: const mirrors = [
<a href="mailto:light7734@tuta.io" class="text-blue"> light7734@tuta.io </a> { label: 'Github', url: 'https://github.com/light7734/light', iconComponent: Github },
<br /> { label: 'Gitlab', url: 'https://gitlab.com/light7734/light', iconComponent: Gitlab },
{ label: 'Codeberg', url: 'https://codeberg.org/light7734/light', iconComponent: Code }
];
Youtube: const gallery = [
<a href="https://www.youtube.com/@light.7734" class="text-blue"> @light.7734 </a> 'https://placekittens.com/720/480',
<br /> 'https://placekittens.com/720/481',
'https://placekittens.com/720/482',
'https://placekittens.com/720/483',
'https://placekittens.com/720/484',
'https://placekittens.com/720/484'
];
Twitter: const features = ['MSAA', 'SSAO', 'PBR Lighting'];
<a href="https://x.com/light7734" class="text-blue"> @light7734 </a>
<br />
Bluesky: const languages = [{ name: 'C++23', icon: 'cplusplus.svg' }, { name: 'CMake' }];
<a href="https://bsky.app/profile/light7734.bsky.social" class="text-blue"> @light7734 </a>
<br />
Reddit: const graphicsApis = ['Vulkan', 'Metal', 'DirectX12'];
<a href="https://www.reddit.com/user/Light7734/" class="text-blue"> u/light7734 </a>
<br />
Itcho.io: const cicd = ['Drone', 'Docker'];
<a href="https://light7734.itch.io/" class="text-blue"> light7734 </a> </script>
<br />
Discord: @light7734 <div
<br /> class="flex min-h-screen bg-gruvboxLight-bg text-gruvboxLight-fg transition-colors duration-300 dark:bg-gruvboxDark-bg dark:text-gruvboxDark-fg"
>
Discord Community: @light7734 <div class="flex-1 bg-[#1d2021]"></div>
<br /> <main class="bg-card relative flex-[10] p-8 transition-all duration-300 ease-out">
<div class="absolute right-0 top-0">
Telegram Dailies: soon <ThemeToggle />
<br />
Instagram: soon
<br />
Forgejo (git repos):
<a href="https://git.light7734.com/light7734" class="text-blue"> git.light7734.com </a>
<br />
Codeberg (mirrors):
<a href="https://codeberg.org/light7734" class="text-blue"> @light7734 </a>
<br />
Github (mirrors):
<a href="https://codeberg.org/light7734" class="text-blue"> @light7734 </a>
<br />
Gitlab (mirrors):
<a href="https://gitlab.com/Light7734" class="text-blue"> @light7734 </a>
<br />
</div> </div>
<h1>Bio</h1>
<br />
<Project
title="LIGHT"
description_preview="Dependency free, cross-platform, cross-graphics-api, bleeding-edge game engine."
description="Dependency free, cross-platform, cross-graphics-api, bleeding-edge game engine."
icon="/light.svg"
{repository}
{mirrors}
{gallery}
{features}
{languages}
{graphicsApis}
{cicd}
/>
<br />
<Project
title="LIGHT"
description_preview="Dependency free, cross-platform, cross-graphics-api, bleeding-edge game engine."
description="Detailed description here."
icon="/light.svg"
{repository}
{mirrors}
{gallery}
{features}
{languages}
{graphicsApis}
{cicd}
/>
</main>
<div class="flex-1 bg-[#1d2021]"></div>
</div>
<!-- Brief Bio <!-- Brief Bio
--> -->
<!--OPEN SOURCE PROJECTS --> <!--OPEN SOURCE PROJECTS -->
<!-- Brief Dazzle <!-- Description
I love teaching! I've learned that it's the most effective way to solidify your knowledge and leave I love teaching! I've learned that it's the most effective way to solidify your knowledge and leave
little to no gaps. Dazzle is the collection of my articles teaching a subject in depth. It delves little to no gaps. Dazzle is the collection of my articles teaching a subject in depth. It delves
into topics such as rendering, mathematics, guidelines and more. It's just my way of giving back to into topics such as rendering, mathematics, guidelines and more. It's just my way of giving back to
the community! the community!
--> -->
<!-- Gallery <!-- Gallery
<...images...>
--> -->
<!-- Technical Details <!-- Technical Details
Technology behind Dazzle: Technology behind Dazzle:
@ -107,9 +125,6 @@ Technology behind Light:
CICD: CICD:
Drone, Docker, Drone, Docker,
Prominent vendor libs:
EnTT, Glfw3.4, Dear (truly a dear) ImGui,
Check out the source code on any of the official mirrors or on the main self-hosted repo. Check out the source code on any of the official mirrors or on the main self-hosted repo.
Self-hosted with <3 using Forgejo (a fork of Gittea) Self-hosted with <3 using Forgejo (a fork of Gittea)
CodeBerg mirror CodeBerg mirror

192
src/routes/project.svelte Normal file
View file

@ -0,0 +1,192 @@
<script lang="ts">
import { ChevronDown, ChevronUp, ExternalLink } from 'lucide-svelte';
import TiltCard from './tiltcard.svelte';
import { Github, Gitlab, Code, GitBranch } from 'lucide-svelte';
interface ProjectLink {
label: string;
url: string;
iconComponent: any;
}
interface Language {
name: string;
icon?: string;
}
export let title: string;
export let description_preview: string;
export let description: string;
export let icon: string;
export let repository: ProjectLink = { label: 'Forgejo', url: '', iconComponent: GitBranch };
export let mirrors: ProjectLink[] = [
{ label: 'Github', url: '', iconComponent: Github },
{ label: 'Gitlab', url: '', iconComponent: Gitlab },
{ label: 'Codeberg', url: '', iconComponent: Code }
];
export let gallery: string[] = [];
export let features: string[] = [];
export let languages: Language[] = [];
export let graphicsApis: string[] = [];
export let cicd: string[] = [];
let expansionStage = 0; // 0: collapsed, 1: description, 2: gallery, 3: source code
const MAX_STAGE = 3;
function toggleExpansion() {
expansionStage = expansionStage >= MAX_STAGE ? 0 : expansionStage + 1;
}
import { slide } from 'svelte/transition';
</script>
<div
class="bg-card border-border relative mx-auto w-full max-w-2xl rounded-lg border transition-all duration-300 ease-out hover:scale-[1.01] hover:shadow-[5px_5px_5px_#000000]"
>
<div class="p-6">
<div class="mb-4 flex items-center gap-4">
<div class="flex-shrink-0">
<TiltCard imageSrc={icon} imageAlt="{title} icon" width="256px" height="256px" />
</div>
<div class="min-w-0 flex-1">
<TiltCard imageSrc="/light_text.svg" imageAlt="{title} icon" width="auto" height="auto" />
</div>
</div>
<p class="text-muted-foreground leading-relaxed">
{expansionStage >= 1 ? description : description_preview}
</p>
{#if expansionStage >= 1}
<div class="py-4" transition:slide={{ duration: 300 }}>
{#if features.length > 0 || languages.length > 0 || graphicsApis.length > 0 || cicd.length > 0}
{#if features.length > 0}
<h3 class="text-muted-foreground mb-3 text-sm font-semibold uppercase tracking-wide">
Features
</h3>
<ul class="text-muted-foreground mb-2 list-disc pl-5">
{#each features as feature}
<li>{feature}</li>
{/each}
</ul>
{/if}
<h4 class="mb-1 font-medium">Technology behind {title}:</h4>
{#if languages.length > 0}
<h5 class="mb-1 text-sm">Languages:</h5>
<div class="mb-2 flex flex-wrap gap-2">
{#each languages as lang}
<span class="flex items-center gap-1">
{#if lang.icon}
<img src={lang.icon} alt="{lang.name} icon" class="ivert h-4 w-4" />
{/if}
{lang.name}
</span>
{/each}
</div>
{/if}
{#if graphicsApis.length > 0}
<h5 class="mb-1 text-sm">Graphics APIs:</h5>
<div class="mb-2 flex flex-wrap gap-2">
{#each graphicsApis as api}
<span>{api}</span>
{/each}
</div>
{/if}
{#if cicd.length > 0}
<h5 class="mb-1 text-sm">CICD:</h5>
<div class="mb-2 flex flex-wrap gap-2">
{#each cicd as tool}
<span>{tool}</span>
{/each}
</div>
{/if}
{/if}
</div>
{/if}
{#if expansionStage >= 2 && gallery.length > 0}
<div transition:slide={{ duration: 300 }}>
<h3 class="text-muted-foreground mb-3 text-sm font-semibold uppercase tracking-wide">
Gallery
</h3>
<div class="mb-4 grid grid-cols-3 gap-2">
{#each gallery as img}
<img src={img} alt="{title} screenshot" class="rounded object-cover" />
{/each}
</div>
</div>
{/if}
{#if expansionStage >= 3}
<div transition:slide={{ duration: 300 }}>
<div class="border-border border-t pt-4">
<div class="flex items-start justify-start gap-0">
<div class="flex-none">
<h3 class="text-muted-foreground mb-2 text-sm font-semibold uppercase tracking-wide">
Repository
</h3>
<div class="flex flex-wrap gap-3">
<a
href={repository.url}
target="_blank"
rel="noopener noreferrer"
class="text-accent-foreground flex items-center gap-1 transition-colors duration-200 hover:underline"
>
<svelte:component this={repository.iconComponent} class="h-4 w-4" />
{repository.label}
</a>
</div>
</div>
<div class="border-border mx-4 h-8 self-center border-l"></div>
<div class="flex-none">
<h3 class="text-muted-foreground mb-2 text-sm font-semibold uppercase tracking-wide">
Mirrors
</h3>
<div class="flex flex-wrap gap-3">
{#each mirrors as link}
<a
href={link.url}
target="_blank"
rel="noopener noreferrer"
class="text-accent-foreground flex items-center gap-1 transition-colors duration-200 hover:underline"
>
<svelte:component this={link.iconComponent} class="h-4 w-4" />
{link.label}
</a>
{/each}
</div>
</div>
</div>
</div>
</div>
{/if}
</div>
<button
on:click={toggleExpansion}
class="bg-muted/50 hover:bg-muted text-muted-foreground hover:text-foreground border-border group flex w-full items-center justify-center gap-2 rounded-b-lg border-t px-6 py-3 transition-colors duration-200"
>
{#if expansionStage >= MAX_STAGE}
<ChevronUp
class="h-4 w-16 transition-transform duration-200 group-hover:translate-y-[-2px]"
/>
{:else}
<ChevronDown
class="h-30 w-30 transition-transform duration-200 group-hover:translate-y-[2px]"
/>
<span class="font-bold">
{#if expansionStage === 0}
FEATURES
{:else if expansionStage === 1}
GALLERY
{:else if expansionStage === 2}
SOURCE
{/if}
</span>
<ChevronDown
class="h-30 w-30 transition-transform duration-200 group-hover:translate-y-[2px]"
/>
{/if}
</button>
</div>

41
src/routes/theme.svelte Normal file
View file

@ -0,0 +1,41 @@
<script lang="ts">
import { onMount } from 'svelte';
import { writable } from 'svelte/store';
import { Sun, Moon } from 'lucide-svelte';
const theme = writable<'light' | 'dark'>('light');
onMount(() => {
// Load from localStorage if exists
const stored = localStorage.getItem('theme');
if (stored === 'dark') {
document.documentElement.classList.add('dark');
theme.set('dark');
}
});
function toggleTheme() {
theme.update((current) => {
const next = current === 'light' ? 'dark' : 'light';
if (next === 'dark') {
document.documentElement.classList.add('dark');
} else {
document.documentElement.classList.remove('dark');
}
localStorage.setItem('theme', next);
return next;
});
}
</script>
<button
on:click={toggleTheme}
class="text-gruvboxLight-fg dark:text-gruvboxDark-fg rounded-md px-4 py-2 transition-all duration-300 ease-out hover:scale-[1.05] hover:drop-shadow-[2px_2px_1px_#000000]"
>
{#if $theme === 'light'}
<Moon class="size-8" />
{/if}
{#if $theme === 'dark'}
<Sun class="size-8" />
{/if}
</button>

View file

@ -0,0 +1,74 @@
<script lang="ts">
export let imageSrc: string;
export let imageAlt: string = 'Card image';
export let width: string = '300px';
export let height: string = '400px';
let cardElement: HTMLDivElement;
let rotateX = 0;
let rotateY = 0;
let isHovered = false;
// Derived shadow offsets
let shadowX = 0;
let shadowY = 0;
let shadowBlur = 20;
function handleMouseMove(event: MouseEvent) {
if (!cardElement) return;
const rect = cardElement.getBoundingClientRect();
const x = event.clientX - rect.left;
const y = event.clientY - rect.top;
const centerX = rect.width / 2;
const centerY = rect.height / 2;
// Calculate rotation based on cursor position (max tilt of 10 degrees)
rotateY = ((x - centerX) / centerX) * 10 * 2;
rotateX = ((centerY - y) / centerY) * 10 * 2;
// Make shadow move opposite to tilt for a realistic lighting effect
shadowX = -rotateY * 0.7; // exaggerate slightly
shadowY = rotateX * 0.7;
}
function handleMouseEnter() {
isHovered = true;
}
function handleMouseLeave() {
isHovered = false;
rotateX = 0;
rotateY = 0;
shadowX = 0;
shadowY = 0;
}
</script>
<div
bind:this={cardElement}
class="cursor-pointer"
class:scale-[1.03]={isHovered}
style="
width: {width};
height: {height};
perspective: 1000px;
filter: drop-shadow({shadowX}px {shadowY}px 0px rgba(0, 0, 0, 0.5));
"
on:mousemove={handleMouseMove}
on:mouseenter={handleMouseEnter}
on:mouseleave={handleMouseLeave}
role="img"
aria-label={imageAlt}
>
<img
src={imageSrc}
alt={imageAlt}
class="h-full w-full rounded-xl object-cover transition-transform duration-100 ease-out"
style="
transform: rotateX({rotateX}deg) rotateY({rotateY}deg);
transform-style: preserve-3d;
"
/>
</div>

1
static/cplusplus.svg Normal file
View file

@ -0,0 +1 @@
<svg role="img" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><title>C++</title><path d="M22.394 6c-.167-.29-.398-.543-.652-.69L12.926.22c-.509-.294-1.34-.294-1.848 0L2.26 5.31c-.508.293-.923 1.013-.923 1.6v10.18c0 .294.104.62.271.91.167.29.398.543.652.69l8.816 5.09c.508.293 1.34.293 1.848 0l8.816-5.09c.254-.147.485-.4.652-.69.167-.29.27-.616.27-.91V6.91c.003-.294-.1-.62-.268-.91zM12 19.11c-3.92 0-7.109-3.19-7.109-7.11 0-3.92 3.19-7.11 7.11-7.11a7.133 7.133 0 016.156 3.553l-3.076 1.78a3.567 3.567 0 00-3.08-1.78A3.56 3.56 0 008.444 12 3.56 3.56 0 0012 15.555a3.57 3.57 0 003.08-1.778l3.078 1.78A7.135 7.135 0 0112 19.11zm7.11-6.715h-.79v.79h-.79v-.79h-.79v-.79h.79v-.79h.79v.79h.79zm2.962 0h-.79v.79h-.79v-.79h-.79v-.79h.79v-.79h.79v.79h.79z"/></svg>

After

Width:  |  Height:  |  Size: 764 B

57
static/drawing.svg Normal file
View file

@ -0,0 +1,57 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
width="512"
height="256"
viewBox="0 0 135.46667 67.733334"
version="1.1"
id="svg1"
inkscape:export-filename="light_text.svg"
inkscape:export-xdpi="96"
inkscape:export-ydpi="96"
inkscape:version="1.4.2 (ebf0e940d0, 2025-05-08)"
sodipodi:docname="drawing.svg"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg">
<sodipodi:namedview
id="namedview1"
pagecolor="#ffffff"
bordercolor="#000000"
borderopacity="0.25"
inkscape:showpageshadow="2"
inkscape:pageopacity="0.0"
inkscape:pagecheckerboard="0"
inkscape:deskcolor="#d1d1d1"
inkscape:document-units="px"
inkscape:zoom="4.1228676"
inkscape:cx="335.20359"
inkscape:cy="230.30087"
inkscape:window-width="2560"
inkscape:window-height="1368"
inkscape:window-x="0"
inkscape:window-y="0"
inkscape:window-maximized="1"
inkscape:current-layer="layer1" />
<defs
id="defs1" />
<g
inkscape:label="Layer 1"
inkscape:groupmode="layer"
id="layer1">
<text
xml:space="preserve"
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:39.5424px;font-family:'Noto Sans Elbasan';-inkscape-font-specification:'Noto Sans Elbasan';text-align:start;writing-mode:lr-tb;direction:ltr;text-anchor:start;fill:#e21a41;fill-opacity:1;stroke:#000000;stroke-width:13.1808;stroke-linecap:round;stroke-linejoin:round;stroke-opacity:0"
x="12.127329"
y="50.100418"
id="text1"
transform="scale(1.0765584,0.92888597)"><tspan
sodipodi:role="line"
id="tspan1"
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-family:'XP Ziba';-inkscape-font-specification:'XP Ziba';fill:#e21a41;fill-opacity:1;stroke:#000000;stroke-width:13.1808;stroke-opacity:0"
x="12.127329"
y="50.100418">LIGHT</tspan></text>
</g>
</svg>

After

Width:  |  Height:  |  Size: 2.2 KiB

BIN
static/light.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.9 KiB

79
static/light.svg Normal file
View file

@ -0,0 +1,79 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
width="200"
height="200"
viewBox="0 0 52.916667 52.916669"
version="1.1"
id="svg5998"
sodipodi:docname="light.svg"
inkscape:version="1.4.2 (ebf0e940d0, 2025-05-08)"
inkscape:export-filename="light.png"
inkscape:export-xdpi="96"
inkscape:export-ydpi="96"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg">
<sodipodi:namedview
id="namedview10"
pagecolor="#505050"
bordercolor="#eeeeee"
borderopacity="1"
inkscape:showpageshadow="0"
inkscape:pageopacity="0"
inkscape:pagecheckerboard="0"
inkscape:deskcolor="#505050"
inkscape:document-units="mm"
showgrid="false"
inkscape:zoom="1.1083899"
inkscape:cx="153.37563"
inkscape:cy="221.94356"
inkscape:window-width="1130"
inkscape:window-height="1269"
inkscape:window-x="26"
inkscape:window-y="23"
inkscape:window-maximized="0"
inkscape:current-layer="svg5998">
<inkscape:grid
type="xygrid"
id="grid2988"
originx="0"
originy="0"
spacingy="1"
spacingx="1"
units="mm" />
</sodipodi:namedview>
<defs
id="defs5995" />
<g
id="g3561"
transform="matrix(1.0702951,0,0,1.0725059,5.8605666,5.8179937)">
<path
style="fill:none;stroke:#e21a41;stroke-width:2.11667;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:3.8;stroke-dasharray:none;stroke-opacity:1;paint-order:stroke fill markers;stop-color:#000000"
d="M 19.245009,12.226594 9.8870095,0.52916664 H 28.602909 Z"
id="path1118"
sodipodi:nodetypes="cccc" />
<path
style="fill:none;stroke:#e21a41;stroke-width:2.11667;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:3.8;stroke-dasharray:none;stroke-opacity:1;paint-order:stroke fill markers;stop-color:#000000"
d="M 19.245009,19.245046 9.8870095,7.5476166 0.52910948,19.245046 c 6.23860002,0 18.83849952,-1.05e-4 18.71589952,0 z"
id="path1120"
sodipodi:nodetypes="cccc" />
<path
style="fill:none;stroke:#e21a41;stroke-width:2.11667;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:3.8;stroke-dasharray:none;stroke-opacity:1;paint-order:stroke fill markers;stop-color:#000000"
d="M 0.52910948,19.245046 19.245009,37.960915 V 19.245046 Z"
id="path1122"
sodipodi:nodetypes="cccc" />
<path
style="fill:none;stroke:#e21a41;stroke-width:2.11667;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:3.8;stroke-dasharray:none;stroke-opacity:1;paint-order:stroke fill markers;stop-color:#000000"
d="m 37.960809,19.245046 -18.7158,18.715869 V 19.245046 Z"
id="path1124"
sodipodi:nodetypes="cccc" />
<path
style="fill:none;stroke:#e21a41;stroke-width:2.11667;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:3.8;stroke-dasharray:none;stroke-opacity:1;paint-order:stroke fill markers;stop-color:#000000"
d="m 37.960809,19.245046 -9.3579,-11.6974294 -9.3579,11.6974294 c 6.2386,0 18.8385,-1.05e-4 18.7158,0 z"
id="path1126"
sodipodi:nodetypes="cccc" />
</g>
</svg>

After

Width:  |  Height:  |  Size: 3.3 KiB

BIN
static/light_banner.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 34 KiB

28
static/light_text.svg Normal file
View file

@ -0,0 +1,28 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
width="512"
height="256"
viewBox="0 0 135.46667 67.733334"
version="1.1"
id="svg1"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg">
<defs
id="defs1" />
<g
id="layer1">
<text
xml:space="preserve"
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:39.5424px;font-family:'Noto Sans Elbasan';-inkscape-font-specification:'Noto Sans Elbasan';text-align:start;writing-mode:lr-tb;direction:ltr;text-anchor:start;fill:#e21a41;fill-opacity:1;stroke:#000000;stroke-width:13.1808;stroke-linecap:round;stroke-linejoin:round;stroke-opacity:0"
x="12.127329"
y="50.100418"
id="text1"
transform="scale(1.0765584,0.92888597)"><tspan
id="tspan1"
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-family:'XP Ziba';-inkscape-font-specification:'XP Ziba';fill:#e21a41;fill-opacity:1;stroke:#000000;stroke-width:13.1808;stroke-opacity:0"
x="12.127329"
y="50.100418">LIGHT</tspan></text>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.2 KiB

BIN
static/pfp.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 620 KiB

View file

@ -3,28 +3,51 @@ import typography from '@tailwindcss/typography';
import type { Config } from 'tailwindcss'; import type { Config } from 'tailwindcss';
export default { export default {
darkMode: 'class', // important for theme switching
content: ['./src/**/*.{html,js,svelte,ts}'], content: ['./src/**/*.{html,js,svelte,ts}'],
theme: { theme: {
extend: {}, extend: {
colors: { colors: {
foundation: '#282828', // Gruvbox Light
primary: '#282828', gruvboxLight: {
secondary: '#000000', bg: '#fbf1c7',
accent: '#ff0000', fg: '#3c3836',
primary: '#458588',
secondary: '#b16286',
accent: '#d79921',
neutral: '#a89984',
muted: '#7c6f64',
red: '#cc241d',
green: '#98971a',
blue: '#458588',
yellow: '#d79921',
orange: '#d65d0e',
purple: '#b16286',
aqua: '#689d6a'
},
neutral: '#000000', // Gruvbox Dark
gruvboxDark: {
primary_text: '#fbf1c7', bg: '#282828',
muted_text: '#a89984', fg: '#ebdbb2',
accent_text: '#fb4934', primary: '#83a598',
secondary: '#d3869b',
red: '#000000', accent: '#fabd2f',
green: '#000000', neutral: '#928374',
muted: '#7c6f64',
red: '#fb4934',
green: '#b8bb26',
blue: '#83a598', blue: '#83a598',
yellow: '#000000' yellow: '#fabd2f',
orange: '#fe8019',
purple: '#d3869b',
aqua: '#8ec07c'
}
}
} }
}, },
plugins: [typography, containerQueries] plugins: [typography, containerQueries]
} satisfies Config; } satisfies Config;