Rewrite website as a Hyprland desktop

This commit is contained in:
SirBlob committed 2026-10-26 14:33:05 -04:00
1 parent 7c52779cf6
commit 3065ad6092
197 files changed
+5609 -17779

No files matched your search

+72 -204
View File
@@ -1,215 +1,83 @@
<script lang="ts">
import { onMount } from "svelte";
import TerminalTUI from "$lib/components/TerminalTUI.svelte";
import { user } from "$lib/config";
import { getPageSpeedMultiplier, getPageAutoscroll } from "$lib";
import { lines as sourceLines } from "$lib/pages/about";
import type { TerminalAPI } from "$lib/components/tui/types";
import { onDestroy } from "svelte";
import Icon from '@iconify/svelte';
import { about, profile } from '#lib/content';
import PageHeader from '#lib/components/ui/PageHeader.svelte';
import ProfileIntro from '#lib/components/ui/ProfileIntro.svelte';
import SectionNav from '#lib/components/ui/SectionNav.svelte';
import Section from '#lib/components/ui/Section.svelte';
import Panel from '#lib/components/ui/Panel.svelte';
import LinkButton from '#lib/components/ui/LinkButton.svelte';
let lines = $state(sourceLines.map((l) => ({ ...l })));
const byRating = (first: { rating: number }, second: { rating: number }) => second.rating - first.rating;
const shows = [...about.shows].sort(byRating);
const movies = [...about.movies].sort(byRating);
const speed = getPageSpeedMultiplier("about");
const autoscroll = getPageAutoscroll("about");
let { data } = $props();
let songs = $derived(data.songs);
let terminal = $state<TerminalAPI>();
let currentSongIndex = $state(0);
let isPlaying = $state(false);
let progress = $state(0);
let timer: ReturnType<typeof setInterval>;
onMount(() => {
// Music player will start when terminal finishes typing
});
function handleTerminalComplete() {
if (songs && songs.length > 0) {
// Initial update
updatePlayerDisplay();
togglePlay();
}
}
onDestroy(() => {
if (timer) clearInterval(timer);
});
function togglePlay() {
isPlaying = !isPlaying;
if (isPlaying) {
timer = setInterval(tick, 500);
} else {
clearInterval(timer);
}
updatePlayerDisplay();
}
function tick() {
progress += 0.5; // Increment progress
if (progress >= 100) {
nextSong();
} else {
updatePlayerDisplay();
}
}
function nextSong() {
currentSongIndex = (currentSongIndex + 1) % songs.length;
progress = 0;
updatePlayerDisplay();
}
function prevSong() {
currentSongIndex = (currentSongIndex - 1 + songs.length) % songs.length;
progress = 0;
updatePlayerDisplay();
}
function updatePlayerDisplay() {
if (!terminal) return;
const song = songs[currentSongIndex];
terminal.updateById("music-player", {
children: [
{
type: "image",
content: "",
image: song.thumbnail,
imageAlt: song.title,
inline: true,
imageWidth: 200,
},
{
type: "group",
content: "",
groupDirection: "column",
groupAlign: "stretch",
inline: true,
children: [
{
type: "group",
content: "",
groupDirection: "row",
groupAlign: "center",
inline: false,
children: [
{
type: "output",
content: `(&primary)Now Playing:(&)`,
inline: true,
},
{
type: "link",
content: `(&blue)${song.title}(&)`,
href: song.url,
inline: true,
external: true,
},
]
},
{
type: "group",
content: "",
groupDirection: "row",
groupAlign: "center",
inline: false,
children: [
{
type: "output",
content: `(&primary)Artist:(&)`,
inline: true,
},
{
type: "link",
content: `(&blue)${song.author}(&)`,
href: song.authorUrl,
inline: true,
external: true,
},
]
},
{
type: "progress",
content: "",
progress: progress,
progressLabel: `${formatTime(Math.floor((progress / 100) * parseDuration(song.duration || "0:00")))} / ${song.duration}`,
inline: false,
progressStyle: "text",
},
]
},
{
type: "group",
content: "",
groupDirection: "row",
display: "flex",
groupExpand: true,
children: [
{
type: "button",
content: "⏮",
style: "secondary",
action: prevSong,
textStyle: "center",
},
{
type: "button",
content: isPlaying ? "⏸" : "▶",
style: "primary",
action: togglePlay,
textStyle: "center",
},
{
type: "button",
content: "⏭",
style: "secondary",
action: nextSong,
textStyle: "center",
}
],
}
],
});
}
function parseDuration(duration: string): number {
const parts = duration.split(":").map(Number);
if (parts.length === 2) return parts[0] * 60 + parts[1];
if (parts.length === 3)
return parts[0] * 3600 + parts[1] * 60 + parts[2];
return 0;
}
function formatTime(seconds: number): string {
const mins = Math.floor(seconds / 60);
const secs = Math.floor(seconds % 60);
return `${mins}:${secs.toString().padStart(2, "0")}`;
}
const sections = [
{ id: 'hobbies', label: 'Hobbies', color: 'blue' },
{ id: 'games', label: 'Games', color: 'orange' },
{ id: 'shows', label: 'Shows', color: 'yellow' },
{ id: 'movies', label: 'Movies', color: 'green' }
];
</script>
<svelte:head>
<title>About | {user.displayname}</title>
<meta name="description" content="About {user.displayname}" />
</svelte:head>
<div class="page">
<PageHeader command="cat ~/about.md" title="About" subtitle="About {profile.displayName}: hobbies, games, shows and movies." />
<div class="about-container">
<TerminalTUI
{lines}
title="~/about"
interactive={true}
{speed}
{autoscroll}
bind:terminal
/>
<ProfileIntro heading={profile.displayName} subheading={profile.title} text={profile.bio} />
<SectionNav {sections} />
<Section title="Hobbies" id="hobbies">
<div class="card-grid">
{#each about.hobbies as hobby (hobby.name)}
<Panel title={hobby.name} icon={hobby.icon}>
<p class="muted">{hobby.description}</p>
</Panel>
{/each}
</div>
</Section>
<Section title="Games" id="games">
<div class="card-grid">
{#each about.games as game (game.name)}
<Panel title={game.name}>
<img class="game-image" src={game.image} alt={game.name} loading="lazy" />
<LinkButton href={game.url} icon="proicons:game">Play</LinkButton>
</Panel>
{/each}
</div>
</Section>
{#each [{ id: 'shows', title: 'TV Shows', items: shows }, { id: 'movies', title: 'Movies', items: movies }] as group (group.id)}
<Section title={group.title} id={group.id}>
<div class="card-grid">
{#each group.items as item (item.name)}
<Panel title={item.name} href={item.url}>
<div class="rating">
<Icon icon="mdi:star" width="16" />
<span>{item.rating}/10</span>
</div>
</Panel>
{/each}
</div>
</Section>
{/each}
</div>
<style>
.about-container {
padding: 0;
min-height: calc(100vh - 60px);
p {
margin: 0;
}
.game-image {
width: 100%;
aspect-ratio: 16 / 9;
object-fit: cover;
}
.rating {
display: flex;
align-items: center;
gap: 0.35rem;
color: var(--yellow);
}
</style>