Rewrite website as a Hyprland desktop
This commit is contained in:
1 parent
7c52779cf6
commit
3065ad6092
197 files changed
+5609
-17779
No files matched your search
+72
-204
@@ -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>
|
||||
Reference in new issue
Block a user