Styles and About Page Fixes

This commit is contained in:
2025-12-01 06:55:28 +00:00
parent e0bcba74d5
commit 62c8006295
9 changed files with 167 additions and 71 deletions

View File

@@ -20,7 +20,7 @@
"svelte-check": "^4.3.4", "svelte-check": "^4.3.4",
"tailwindcss": "^4.1.17", "tailwindcss": "^4.1.17",
"typescript": "^5.9.3", "typescript": "^5.9.3",
"vite": "^7.2.4" "vite": "^7.2.6"
}, },
"dependencies": { "dependencies": {
"@iconify/svelte": "^5.1.0", "@iconify/svelte": "^5.1.0",
@@ -31,7 +31,7 @@
"dotenv": "^17.2.3", "dotenv": "^17.2.3",
"express": "^5.1.0", "express": "^5.1.0",
"hotkeys-js": "^4.0.0-beta.7", "hotkeys-js": "^4.0.0-beta.7",
"three": "^0.181.2", "play-dl": "^1.9.7",
"ytpl": "^2.3.0" "three": "^0.181.2"
} }
} }

View File

@@ -65,7 +65,7 @@
} }
.tui-line.blank { .tui-line.blank {
min-height: 0.5em; min-height: 0.15em;
} }
@keyframes lineSlideIn { @keyframes lineSlideIn {
@@ -73,6 +73,7 @@
opacity: 0; opacity: 0;
transform: translateX(-5px); transform: translateX(-5px);
} }
to { to {
opacity: 1; opacity: 1;
transform: translateX(0); transform: translateX(0);
@@ -173,12 +174,10 @@
.divider-line { .divider-line {
flex: 1; flex: 1;
height: 1px; height: 1px;
background: linear-gradient( background: linear-gradient(to right,
to right,
transparent, transparent,
var(--terminal-border), var(--terminal-border),
transparent transparent);
);
} }
.divider-text { .divider-text {
@@ -223,15 +222,26 @@
width: 0.5em; width: 0.5em;
height: 1.1em; height: 1.1em;
background: var(--terminal-primary); background: var(--terminal-primary);
animation: cursorBlink 1s step-end infinite; background: var(--terminal-primary);
margin-left: 2px; margin-left: 2px;
vertical-align: baseline; vertical-align: baseline;
transform: translateY(0.15em); transform: translateY(0.15em);
} }
.cursor.blink {
animation: cursorBlink 1s step-end infinite;
}
@keyframes cursorBlink { @keyframes cursorBlink {
0%, 100% { opacity: 1; }
50% { opacity: 0; } 0%,
100% {
opacity: 1;
}
50% {
opacity: 0;
}
} }
/* Scrollbar */ /* Scrollbar */
@@ -265,6 +275,15 @@
flex-direction: column; flex-direction: column;
} }
/* Keep inline flow for specific containers */
.tui-card .tui-inline-group,
.tui-group .tui-inline-group,
.tui-accordion .tui-inline-group {
flex-direction: row;
flex-wrap: wrap;
align-items: flex-start;
}
.tui-inline-group .inline-image img { .tui-inline-group .inline-image img {
max-width: 100% !important; max-width: 100% !important;
width: 100%; width: 100%;

View File

@@ -137,7 +137,7 @@
class="symbol">$</span class="symbol">$</span
> >
</span> </span>
<span class="cursor"></span> <span class="cursor blink"></span>
</div> </div>
{/if} {/if}
</div> </div>

View File

@@ -6,7 +6,7 @@
getSegmentStyle, getSegmentStyle,
parseDimension, parseDimension,
} from "./utils"; } from "./utils";
import type { CardLine } from "./types"; import type { CardLine, TerminalLine } from "./types";
import TuiLine from "./TuiLine.svelte"; import TuiLine from "./TuiLine.svelte";
import "$lib/assets/css/tui-card.css"; import "$lib/assets/css/tui-card.css";
@@ -46,6 +46,43 @@
.filter(Boolean) .filter(Boolean)
.join("; "), .join("; "),
); );
const processedChildren = $derived.by(() => {
if (!line.children) return [];
const groups: Array<
| { kind: "single"; index: number; line: TerminalLine }
| {
kind: "inline";
items: Array<{ index: number; line: TerminalLine }>;
}
> = [];
let i = 0;
while (i < line.children.length) {
const child = line.children[i];
const isInline = child.inline || child.display === "inline";
if (isInline) {
const inlineItems: Array<{
index: number;
line: TerminalLine;
}> = [];
while (i < line.children.length) {
const nextChild = line.children[i];
const nextIsInline =
nextChild.inline || nextChild.display === "inline";
if (!nextIsInline) break;
inlineItems.push({ index: i, line: nextChild });
i++;
}
groups.push({ kind: "inline", items: inlineItems });
} else {
groups.push({ kind: "single", index: i, line: child });
i++;
}
}
return groups;
});
</script> </script>
<div class="tui-card" class:inline style={cardStyle}> <div class="tui-card" class:inline style={cardStyle}>
@@ -91,11 +128,29 @@
class:flex={line.display === "flex"} class:flex={line.display === "flex"}
class:grid={line.display === "grid"} class:grid={line.display === "grid"}
> >
{#each line.children as child, i} {#each processedChildren as group}
{#if group.kind === "inline"}
<div class="tui-inline-group">
{#each group.items as item}
<TuiLine <TuiLine
line={child} line={item.line}
index={i} index={item.index}
segments={parseColorText(child.content)} segments={parseColorText(item.line.content)}
complete={true}
showImage={true}
selectedIndex={-1}
inline={true}
{onButtonClick}
{onHoverButton}
{onLinkClick}
/>
{/each}
</div>
{:else}
<TuiLine
line={group.line}
index={group.index}
segments={parseColorText(group.line.content)}
complete={true} complete={true}
showImage={true} showImage={true}
selectedIndex={-1} selectedIndex={-1}
@@ -104,6 +159,7 @@
{onHoverButton} {onHoverButton}
{onLinkClick} {onLinkClick}
/> />
{/if}
{/each} {/each}
</div> </div>
{/if} {/if}

View File

@@ -120,10 +120,7 @@
} }
@media (max-width: 768px) { @media (max-width: 768px) {
.tui-group.inline { /* .tui-group.inline removed to allow inline flow on mobile */
flex-direction: column;
align-items: stretch;
}
.tui-group.grid { .tui-group.grid {
grid-template-columns: 1fr; grid-template-columns: 1fr;

View File

@@ -39,8 +39,7 @@ export const lines: TerminalLine[] = [
{ type: 'command', content: 'cat ~/about.md' }, { type: 'command', content: 'cat ~/about.md' },
{ type: 'blank', content: '' }, { type: 'blank', content: '' },
{ type: 'image', content: '', image: user.avatar, imageAlt: user.name, imageWidth: 180, inline: true }, { type: 'image', content: '', image: user.avatar, imageAlt: user.name, imageWidth: 200, inline: true },
{ {
type: 'group', type: 'group',
content: '', content: '',
@@ -48,6 +47,7 @@ export const lines: TerminalLine[] = [
groupAlign: 'start', groupAlign: 'start',
inline: true, inline: true,
children: [ children: [
{ type: 'blank', content: '' },
{ type: 'header', content: `(&bold)${user.name}(&)` }, { type: 'header', content: `(&bold)${user.name}(&)` },
{ type: 'info', content: `(&accent)${user.title}(&)` }, { type: 'info', content: `(&accent)${user.title}(&)` },
{ type: 'output', content: `(&white)Location:(&) (&primary)${user.location}(&)` }, { type: 'output', content: `(&white)Location:(&) (&primary)${user.location}(&)` },
@@ -57,18 +57,25 @@ export const lines: TerminalLine[] = [
{ type: 'blank', content: '' }, { type: 'blank', content: '' },
{ type: 'divider', content: 'Music Recommendations' }, { type: 'divider', content: 'Music Recommendations' },
{
type: 'group',
content: '',
groupDirection: 'row',
children: [
{ type: 'output', content: '(&accent)Artist:(&) ', inline: true }, { type: 'output', content: '(&accent)Artist:(&) ', inline: true },
...artist.flatMap((a, i): TerminalLine[] => { ...artist.flatMap((a, i): TerminalLine[] => {
const item: TerminalLine = { type: 'link', content: `(&white)${a.name}(&)`, href: a.url, inline: true }; const item: TerminalLine = { type: 'link', content: `(&white)${a.name}(&)`, href: a.url, inline: true };
return i < artist.length - 1 ? [item, { type: 'output', content: ' (&muted)•(&) ', inline: true }] : [item]; return i < artist.length - 1 ? [item, { type: 'output', content: ' (&muted)•(&) ', inline: true }] : [item];
}), }),
{ type: 'blank', content: '' }, ]
},
{ type: 'output', content: '(&accent)Songs:(&) ', inline: true }, // { type: 'blank', content: '' },
...songs.flatMap((s, i): TerminalLine[] => { // { type: 'output', content: '(&accent)Songs:(&) ', inline: true },
const item: TerminalLine = { type: 'link', content: `(&white)${s.name}(&)`, href: s.url, inline: true }; // ...songs.flatMap((s, i): TerminalLine[] => {
return i < songs.length - 1 ? [item, { type: 'output', content: ' (&muted)•(&) ', inline: true }] : [item]; // const item: TerminalLine = { type: 'link', content: `(&white)${s.name}(&)`, href: s.url, inline: true };
}), // return i < songs.length - 1 ? [item, { type: 'output', content: ' (&muted)•(&) ', inline: true }] : [item];
// }),
{ type: 'blank', content: '' }, { type: 'blank', content: '' },
{ {
type: 'card', type: 'card',

View File

@@ -141,7 +141,7 @@
</svelte:head> </svelte:head>
<div class="error-container"> <div class="error-container">
<TerminalTUI {lines} title="error" interactive={true} speed="fast" /> <TerminalTUI {lines} title="error" interactive={true} speed="instant" />
</div> </div>
<style> <style>

View File

@@ -1,25 +1,33 @@
import ytpl from 'ytpl'; import playdl, { YouTubeVideo } from 'play-dl';
function shuffleArray(array: any[]) {
for (let i = array.length - 1; i > 0; i--) {
const j = Math.floor(Math.random() * (i + 1));
[array[i], array[j]] = [array[j], array[i]];
}
return array;
}
/** @type {import('./$types').PageServerLoad} */ /** @type {import('./$types').PageServerLoad} */
export async function load({ params }) { export async function load({ params }) {
const playlistUrl = 'https://music.youtube.com/playlist?list=PLHqPHHvmOjJ6JwiYc3Fg-O5JGbVppI1VR&si=CIb9WIKhjKsRV3p3'; const playlistUrl = 'https://music.youtube.com/playlist?list=PLHqPHHvmOjJ6JwiYc3Fg-O5JGbVppI1VR&si=pJnM95siw3kQRlnr';
const playlist = await ytpl(playlistUrl, { limit: Infinity }); const playlist = await playdl.playlist_info(playlistUrl);
const songs = playlist.items.map((item, index) => { let songs = (await playlist.all_videos()).map((video: YouTubeVideo) => {
return { return {
index: index + 1, title: video.title,
title: item.title, url: video.url,
duration: item.duration, // format is usually mm:ss author: video.channel?.name,
author: item.author.name, authorUrl: video.channel?.url,
isLive: item.isLive, thumbnail: video.thumbnails[0].url,
url: item.shortUrl duration: video.durationRaw,
}; }
}); })
return { return {
songs songs: shuffleArray(songs)
}; };
} }

View File

@@ -77,11 +77,28 @@
children: [ children: [
{ {
type: "output", type: "output",
content: `(&primary)Now Playing:(&) (&text)${song.title}(&)`, content: `(&primary)Now Playing:(&)`,
inline: true,
}, },
{
type: "link",
content: `(&blue)${song.title}(&)`,
href: song.url,
inline: true,
external: true,
},
{ type: "blank", content: "" },
{ {
type: "output", type: "output",
content: `(&primary)Artist:(&) (&text)${song.author}(&)`, content: `(&primary)Artist:(&)`,
inline: true,
},
{
type: "link",
content: `(&blue)${song.author}(&)`,
href: song.authorUrl,
inline: true,
external: true,
}, },
{ {
type: "progress", type: "progress",
@@ -120,15 +137,7 @@
textStyle: "center", textStyle: "center",
} }
], ],
}, }
{
type: "button",
content: "(&icon, mdi:youtube) Listen on YouTube",
style: "primary",
href: song.url,
external: true,
textStyle: "center",
},
], ],
}); });
} }