mirror of
https://github.com/thiloho/thiloho.github.io.git
synced 2025-12-23 14:33:36 +01:00
Compare commits
3 Commits
claude/enh
...
bfad268f1f
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
bfad268f1f | ||
|
|
70a0434236 | ||
|
|
323ec46753 |
8
package-lock.json
generated
8
package-lock.json
generated
@@ -4582,6 +4582,7 @@
|
||||
"integrity": "sha512-I7AIg5boAr5R0FFtJ6rCfD+LFsWHp81dolrFD8S79U9tb8Az2nGrJncnMSnys+bpQJfRUzqs9hnA81OAA3hCuQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"bin": {
|
||||
"prettier": "bin/prettier.cjs"
|
||||
},
|
||||
@@ -4598,6 +4599,7 @@
|
||||
"integrity": "sha512-RiBETaaP9veVstE4vUwSIcdATj6dKmXljouXc/DDNwBSPTp8FRkLGDSGFClKsAFeeg+13SB0Z1JZvbD76bigJw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@astrojs/compiler": "^2.9.1",
|
||||
"prettier": "^3.0.0",
|
||||
@@ -5028,6 +5030,7 @@
|
||||
"resolved": "https://registry.npmjs.org/rollup/-/rollup-4.40.0.tgz",
|
||||
"integrity": "sha512-Noe455xmA96nnqH5piFtLobsGbCij7Tu+tb3c1vYjNbTkfzGqXqQXG3wJaYXkRZuQ0vEYN4bhwg7QnIrqB5B+w==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@types/estree": "1.0.7"
|
||||
},
|
||||
@@ -5322,7 +5325,8 @@
|
||||
"version": "4.1.14",
|
||||
"resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.1.14.tgz",
|
||||
"integrity": "sha512-b7pCxjGO98LnxVkKjaZSDeNuljC4ueKUddjENJOADtubtdo8llTaJy7HwBMeLNSSo2N5QIAgklslK1+Ir8r6CA==",
|
||||
"license": "MIT"
|
||||
"license": "MIT",
|
||||
"peer": true
|
||||
},
|
||||
"node_modules/tapable": {
|
||||
"version": "2.3.0",
|
||||
@@ -5806,6 +5810,7 @@
|
||||
"resolved": "https://registry.npmjs.org/vite/-/vite-6.4.1.tgz",
|
||||
"integrity": "sha512-+Oxm7q9hDoLMyJOYfUYBuHQo+dkAloi33apOPP56pzj+vsdJDzr+j1NISE5pyaAuKL4A3UD34qd0lx5+kfKp2g==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"esbuild": "^0.25.0",
|
||||
"fdir": "^6.4.4",
|
||||
@@ -6013,6 +6018,7 @@
|
||||
"resolved": "https://registry.npmjs.org/zod/-/zod-3.25.76.tgz",
|
||||
"integrity": "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/colinhacks"
|
||||
}
|
||||
|
||||
@@ -9,7 +9,7 @@ interface Props {
|
||||
const { href, variant = "text", title, id } = Astro.props;
|
||||
|
||||
const baseClasses =
|
||||
"border-transparent border-b-2 p-2 cursor-pointer hover:border-[var(--color-accent-cyan)] hover:bg-neutral-200 hover:dark:border-[var(--color-accent-cyan-light)] hover:dark:bg-neutral-700 active:bg-neutral-200 active:dark:bg-neutral-700 active:border-[var(--color-accent-cyan)] active:dark:border-[var(--color-accent-cyan-light)] transition-colors duration-200";
|
||||
"border-transparent border-b-2 p-2 cursor-pointer hover:border-neutral-300 hover:bg-neutral-200 hover:dark:border-neutral-600 hover:dark:bg-neutral-700 active:bg-neutral-200 active:dark:bg-neutral-700 active:border-neutral-300 active:dark:border-neutral-600";
|
||||
const classes = `${baseClasses} ${variant === "icon" && href ? "inline-grid place-content-center" : "inline-block"}`;
|
||||
---
|
||||
|
||||
|
||||
@@ -4,7 +4,6 @@ import Icon from "./Icon.astro";
|
||||
import Button from "./Button.astro";
|
||||
|
||||
const routes = ["blog", "tracks", "services"];
|
||||
const currentPath = Astro.url.pathname;
|
||||
---
|
||||
|
||||
<nav class="sticky top-0 z-20 max-w-none bg-white dark:bg-neutral-800">
|
||||
@@ -16,32 +15,18 @@ const currentPath = Astro.url.pathname;
|
||||
</a>
|
||||
<div class="flex overflow-x-auto">
|
||||
{
|
||||
routes.map((route) => {
|
||||
const isActive = currentPath.startsWith(`/${route}`);
|
||||
return (
|
||||
<span class="relative">
|
||||
<Button href={`/${route}`}>
|
||||
{route
|
||||
.split(" ")
|
||||
.map((word) => word.charAt(0).toUpperCase() + word.slice(1))
|
||||
.join(" ")}
|
||||
</Button>
|
||||
{isActive && (
|
||||
<span class="absolute right-0 bottom-0 left-0 h-0.5 bg-gradient-to-r from-[var(--color-accent-blue)] to-[var(--color-accent-cyan)] dark:from-[var(--color-accent-blue-light)] dark:to-[var(--color-accent-cyan-light)]" />
|
||||
)}
|
||||
</span>
|
||||
);
|
||||
})
|
||||
routes.map((route) => (
|
||||
<Button href={`/${route}`}>
|
||||
{route
|
||||
.split(" ")
|
||||
.map((word) => word.charAt(0).toUpperCase() + word.slice(1))
|
||||
.join(" ")}
|
||||
</Button>
|
||||
))
|
||||
}
|
||||
<Button id="theme-toggle" variant="icon" title="Toggle dark mode">
|
||||
<Icon
|
||||
name="moon"
|
||||
class="transition-colors duration-200 hover:text-[var(--color-accent-cyan)] dark:hidden"
|
||||
/>
|
||||
<Icon
|
||||
name="sun"
|
||||
class="hidden transition-colors duration-200 hover:text-[var(--color-accent-cyan-light)] dark:block"
|
||||
/>
|
||||
<Icon name="moon" class="dark:hidden" />
|
||||
<Icon name="sun" class="hidden dark:block" />
|
||||
</Button>
|
||||
<Button variant="icon" href="/rss.xml" title="RSS feed">
|
||||
<Icon name="rss" />
|
||||
|
||||
@@ -19,8 +19,8 @@ const thumbnail = `https://img.youtube.com/vi/${videoId}/maxresdefault.jpg`;
|
||||
|
||||
<a
|
||||
href={youtubeLink}
|
||||
class="relative mt-4 block p-4 duration-300 after:absolute after:inset-0 after:z-0 after:bg-[rgba(255,255,255,0.75)] after:content-[''] first:mt-0 hover:scale-105 dark:after:bg-[rgba(38,38,38,0.75)]"
|
||||
style={`word-break: break-word; background-image: url('${thumbnail}'); background-size: cover; background-position: center;`}
|
||||
class="relative mt-4 block bg-cover bg-center p-4 duration-300 after:absolute after:inset-0 after:z-0 after:bg-[rgba(255,255,255,0.75)] after:content-[''] first:mt-0 hover:scale-105 dark:after:bg-[rgba(38,38,38,0.75)]"
|
||||
style={`word-break: break-word; background-image: url('${thumbnail}')`}
|
||||
>
|
||||
<div
|
||||
class="relative z-10 flex flex-col gap-2 text-neutral-900 dark:text-white"
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -7,10 +7,20 @@ const tracks = await getCollection("tracks");
|
||||
---
|
||||
|
||||
<PageLayout
|
||||
title="Tracks"
|
||||
title={`Tracks (${tracks.length})`}
|
||||
description="My entire music playlist. It contains all kinds of songs."
|
||||
>
|
||||
<div class="not-prose">
|
||||
<div id="loading-indicator" class="flex flex-col items-center gap-2">
|
||||
<div
|
||||
class="h-8 w-8 animate-spin rounded-full border-4 border-neutral-300 border-t-neutral-900 dark:border-neutral-600 dark:border-t-white"
|
||||
>
|
||||
</div>
|
||||
<span
|
||||
id="loading-percentage"
|
||||
class="text-sm text-neutral-900 dark:text-white">0%</span
|
||||
>
|
||||
</div>
|
||||
<div id="tracks-container" class="not-prose hidden">
|
||||
{
|
||||
tracks.map(({ data: { title, artist, album, youtubeLink } }, index) => (
|
||||
<Track {title} {artist} {album} {youtubeLink} index={++index} />
|
||||
@@ -18,3 +28,40 @@ const tracks = await getCollection("tracks");
|
||||
}
|
||||
</div>
|
||||
</PageLayout>
|
||||
|
||||
<script is:inline define:vars={{ tracks }}>
|
||||
const loadThumbnails = async () => {
|
||||
const thumbnailUrls = tracks.map(({ data: { youtubeLink } }) => {
|
||||
const videoId = youtubeLink.split("v=")[1];
|
||||
return `https://img.youtube.com/vi/${videoId}/maxresdefault.jpg`;
|
||||
});
|
||||
|
||||
let loadedCount = 0;
|
||||
const total = thumbnailUrls.length;
|
||||
const percentageEl = document.getElementById("loading-percentage");
|
||||
|
||||
const preloadImages = thumbnailUrls.map((url) => {
|
||||
return new Promise((resolve) => {
|
||||
const img = new Image();
|
||||
const handleComplete = () => {
|
||||
loadedCount++;
|
||||
const percentage = Math.round((loadedCount / total) * 100);
|
||||
percentageEl.textContent = `${percentage}%`;
|
||||
resolve();
|
||||
};
|
||||
|
||||
img.addEventListener("load", handleComplete, { once: true });
|
||||
img.addEventListener("error", handleComplete, { once: true });
|
||||
img.src = url;
|
||||
});
|
||||
});
|
||||
|
||||
await Promise.all(preloadImages);
|
||||
|
||||
document.getElementById("loading-indicator").classList.add("hidden");
|
||||
document.getElementById("tracks-container").classList.remove("hidden");
|
||||
};
|
||||
|
||||
loadThumbnails();
|
||||
document.addEventListener("astro:after-swap", loadThumbnails);
|
||||
</script>
|
||||
|
||||
@@ -2,13 +2,6 @@
|
||||
@plugin "@tailwindcss/typography";
|
||||
@custom-variant dark (&:where(.dark, .dark *));
|
||||
|
||||
@theme {
|
||||
--color-accent-cyan: #0891b2;
|
||||
--color-accent-cyan-light: #67e8f9;
|
||||
--color-accent-blue: #1e40af;
|
||||
--color-accent-blue-light: #93c5fd;
|
||||
}
|
||||
|
||||
@layer base {
|
||||
body {
|
||||
font-family: "Tiempos Text", serif;
|
||||
@@ -23,32 +16,6 @@
|
||||
font-family: "Styrene A", sans-serif;
|
||||
}
|
||||
|
||||
/* Gradient effect on h1 and h2 */
|
||||
h1,
|
||||
h2 {
|
||||
background: linear-gradient(
|
||||
135deg,
|
||||
var(--color-accent-blue) 0%,
|
||||
var(--color-accent-cyan) 100%
|
||||
);
|
||||
background-clip: text;
|
||||
-webkit-background-clip: text;
|
||||
-webkit-text-fill-color: transparent;
|
||||
background-size: 100%;
|
||||
}
|
||||
|
||||
.dark h1,
|
||||
.dark h2 {
|
||||
background: linear-gradient(
|
||||
135deg,
|
||||
var(--color-accent-blue-light) 0%,
|
||||
var(--color-accent-cyan-light) 100%
|
||||
);
|
||||
background-clip: text;
|
||||
-webkit-background-clip: text;
|
||||
-webkit-text-fill-color: transparent;
|
||||
}
|
||||
|
||||
code,
|
||||
kbd,
|
||||
samp,
|
||||
@@ -57,15 +24,6 @@
|
||||
font-family: "Styrene B", monospace;
|
||||
}
|
||||
|
||||
/* Accent border on code blocks */
|
||||
pre {
|
||||
border-left: 4px solid var(--color-accent-cyan);
|
||||
}
|
||||
|
||||
.dark pre {
|
||||
border-left-color: var(--color-accent-cyan-light);
|
||||
}
|
||||
|
||||
mark {
|
||||
@apply bg-neutral-200 text-current dark:bg-neutral-600;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user