FULL ON WEBSITE REMAKE

This commit is contained in:
dvorakmortonxd 2026-09-22 15:36:04 +02:00
commit a14df3f3b3
11 changed files with 1140 additions and 830 deletions

View file

@ -0,0 +1,90 @@
"use client";
import { useState } from "react";
import { APPS, APP_BY_ID } from "./apps";
import Clock from "./Clock";
import { useTheme } from "./useTheme";
import { WALLPAPER } from "../data";
const DOCK_IDS = ["projects", "services", "socials", "contributions"];
export default function AndroidHome() {
const { theme, toggle } = useTheme();
const [openId, setOpenId] = useState<string | null>(null);
const openApp = openId ? APP_BY_ID[openId] : null;
const renderIcon = (appId: string, inDock = false) => {
const app = APP_BY_ID[appId];
if (!app) return null;
return (
<button
key={app.id}
type="button"
className={"android-app" + (inDock ? " in-dock" : "")}
onClick={() => setOpenId(app.id)}
>
<span className="android-app-icon" style={{ background: app.color }}>
{app.icon}
</span>
{!inDock && <span className="android-app-label">{app.title}</span>}
</button>
);
};
return (
<div className="android-root">
{openApp ? (
<div className="android-app-view">
<header className="android-appbar">
<button
type="button"
className="android-back"
onClick={() => setOpenId(null)}
aria-label="Back"
>
←
</button>
<span className="android-appbar-title">{openApp.title}</span>
</header>
<div className="android-app-body app-surface">{openApp.content}</div>
</div>
) : (
<div className="android-home" style={{ backgroundImage: `url('${WALLPAPER}')` }}>
<div className="android-statusbar">
<span className="android-status-time">
<Clock />
</span>
<span className="android-status-icons" aria-hidden="true">
ᯤ ▮
</span>
</div>
<button
type="button"
className="android-theme-chip"
onClick={toggle}
aria-label={`Switch to ${theme === "skeuo" ? "gruvbox" : "Desktop"} theme`}
>
<span aria-hidden="true">🎨</span>
<span>{theme === "skeuo" ? "Gruvbox" : "Desktop"}</span>
</button>
<div className="android-glance">
<div className="android-glance-time">
<Clock />
</div>
<div className="android-glance-sub">dvorakmortonxd.dev</div>
</div>
<div className="android-grid">{APPS.map((app) => renderIcon(app.id))}</div>
<div className="android-dock">{DOCK_IDS.map((id) => renderIcon(id, true))}</div>
<div className="android-navbar">
<span className="android-navbar-pill" />
</div>
</div>
)}
</div>
);
}

View file

@ -1,9 +1,20 @@
"use client";
import { useEffect, useState } from "react";
import { useSyncExternalStore } from "react";
function format(date: Date) {
return date.toLocaleTimeString([], { hour: "2-digit", minute: "2-digit" });
function getTime(withSeconds: boolean) {
const now = new Date();
return now.toLocaleTimeString(
[],
withSeconds
? { hour: "2-digit", minute: "2-digit", second: "2-digit" }
: { hour: "2-digit", minute: "2-digit" }
);
}
function subscribe(callback: () => void) {
const id = window.setInterval(callback, 1000);
return () => window.clearInterval(id);
}
export default function Clock({
@ -13,28 +24,12 @@ export default function Clock({
className?: string;
withSeconds?: boolean;
}) {
const [now, setNow] = useState<Date | null>(() =>
typeof window === "undefined" ? null : new Date()
const text = useSyncExternalStore(
subscribe,
() => getTime(withSeconds),
() => ""
);
useEffect(() => {
const id = window.setInterval(
() => setNow(new Date()),
withSeconds ? 1000 : 10000
);
return () => window.clearInterval(id);
}, [withSeconds]);
const text = now
? withSeconds
? now.toLocaleTimeString([], {
hour: "2-digit",
minute: "2-digit",
second: "2-digit",
})
: format(now)
: "";
return (
<span className={className} suppressHydrationWarning>
{text}

View file

@ -1,77 +0,0 @@
"use client";
import { useState } from "react";
import { APPS, APP_BY_ID } from "./apps";
import Clock from "./Clock";
import { useTheme } from "./useTheme";
import { WALLPAPER } from "../data";
export default function IosHome() {
const { theme, toggle } = useTheme();
const [openId, setOpenId] = useState<string | null>(null);
const openApp = openId ? APP_BY_ID[openId] : null;
return (
<div className="ios-root">
{/* iOS app view */}
{openApp ? (
<div className="ios-app-view">
<div className="ios-navbar">
<button type="button" className="ios-back" onClick={() => setOpenId(null)}>
‹ Home
</button>
<span className="ios-navbar-title">{openApp.title}</span>
<span className="ios-navbar-spacer" />
</div>
<div className="ios-app-body app-surface">{openApp.content}</div>
</div>
) : (
<div
className="ios-home"
style={{ backgroundImage: `url('${WALLPAPER}')` }}
>
<div className="ios-statusbar">
<span className="ios-status-time">
<Clock />
</span>
<span className="ios-status-icons" aria-hidden="true">
●●● ᯤ ▮
</span>
</div>
<button
type="button"
className="ios-theme-app"
onClick={toggle}
aria-label={`Switch to ${theme === "skeuo" ? "terminal" : "Aero"} theme`}
>
<span className="ios-theme-app-icon" aria-hidden="true">
{theme === "skeuo" ? "▚" : "❖"}
</span>
<span className="ios-theme-app-label">{theme === "skeuo" ? "Terminal" : "Aero"}</span>
</button>
<div className="ios-grid">
{APPS.map((app) => (
<button
key={app.id}
type="button"
className="ios-app"
onClick={() => setOpenId(app.id)}
>
<span className="ios-app-icon" style={{ background: app.color }}>
{app.icon}
</span>
<span className="ios-app-label">{app.title}</span>
</button>
))}
</div>
<div className="ios-dock">
<span className="ios-dock-slots" aria-hidden="true" />
</div>
</div>
)}
</div>
);
}

View file

@ -4,7 +4,6 @@ import SlBackground from "./SlBackground";
import JokerLogo from "./JokerLogo";
import ServiceButton from "./ServiceButton";
import ContributionHeatmap from "./ContributionHeatmap";
import Clock from "./Clock";
import { useTheme } from "./useTheme";
import { socials, codingProjects, techProjects, friends } from "../data";
@ -16,7 +15,7 @@ const panelStyle = (color: string, style: CSSProperties = {}): PanelStyle => ({
});
export default function TerminalLayout() {
const { theme, toggle } = useTheme();
const { toggle } = useTheme();
return (
<>
@ -26,6 +25,9 @@ export default function TerminalLayout() {
<div className="header-center">
<JokerLogo />
<h1 className="site-name">dvorakmortonxd.dev</h1>
<button type="button" className="header-theme-btn" onClick={toggle}>
Desktop
</button>
</div>
</header>
@ -44,31 +46,6 @@ export default function TerminalLayout() {
</p>
</div>
<div className="search-section">
<div className="tui-panel" style={panelStyle("var(--white)")}>
<span
className="tui-panel-label"
style={{ color: "var(--white)", borderColor: "var(--white)" }}
>
SEARXNG.C
</span>
<form
action="https://search.dvorakmortonxd.dev/search"
method="GET"
target="_blank"
rel="noopener noreferrer"
className="search-form"
>
<input
type="text"
name="q"
placeholder="Search... (only works for those who are whitelisted)"
className="search-bar"
/>
</form>
</div>
</div>
<div className="social-section">
<div className="tui-panel" style={panelStyle("var(--accent)")}>
<span
@ -611,97 +588,6 @@ export default function TerminalLayout() {
/>
))}
</div>
<div
style={{
marginTop: "12px",
display: "flex",
flexDirection: "column",
alignItems: "center",
gap: "16px",
}}
>
<h3 style={{ fontSize: "1.5rem", color: "var(--accent)", fontWeight: "600" }}>
Quipee by Bukovina Filip &amp; Vlk Tomáš
</h3>
<div
style={{
width: "100%",
borderRadius: "8px",
overflow: "hidden",
border: "1px solid rgba(255, 255, 0, 0.3)",
boxShadow: "0 0 20px rgba(255, 255, 0, 0.1)",
}}
>
<Image
src="/quipee.webp"
alt="Quipee Thumbnail"
width={1280}
height={720}
style={{ width: "100%", height: "auto", display: "block" }}
/>
</div>
<div
style={{
display: "flex",
gap: "12px",
flexWrap: "wrap",
justifyContent: "center",
}}
>
<a
href="https://quipee.me"
target="_blank"
rel="noopener noreferrer"
style={{
backgroundColor: "rgba(255, 255, 255, 0.04)",
color: "var(--white)",
padding: "6px 16px",
borderRadius: "6px",
fontSize: "1rem",
fontWeight: "500",
textDecoration: "none",
border: "1px solid rgba(255, 255, 0, 0.3)",
}}
>
Web
</a>
<a
href="https://drive.google.com/drive/folders/1oi76JyWs6J84MUmCGV7GvtLUexXI8Jel?usp=share_link"
target="_blank"
rel="noopener noreferrer"
style={{
backgroundColor: "rgba(255, 255, 255, 0.04)",
color: "var(--white)",
padding: "6px 16px",
borderRadius: "6px",
fontSize: "1rem",
fontWeight: "500",
textDecoration: "none",
border: "1px solid rgba(255, 255, 0, 0.3)",
}}
>
Android
</a>
<a
href="https://testflight.apple.com/join/hP2a1E5x"
target="_blank"
rel="noopener noreferrer"
style={{
backgroundColor: "rgba(255, 255, 255, 0.04)",
color: "var(--white)",
padding: "6px 16px",
borderRadius: "6px",
fontSize: "1rem",
fontWeight: "500",
textDecoration: "none",
border: "1px solid rgba(255, 255, 0, 0.3)",
}}
>
IOS
</a>
</div>
</div>
</div>
</div>
</section>
@ -712,15 +598,6 @@ export default function TerminalLayout() {
the GPL3.0 License)
</footer>
</div>
<div className="tui-statusbar">
<span className="tui-statusbar-item">theme: terminal</span>
<button type="button" className="tui-statusbar-btn" onClick={toggle}>
[ switch to {theme === "skeuo" ? "terminal" : "Aero"} ]
</button>
<span className="tui-statusbar-spacer" />
<Clock className="tui-statusbar-clock" />
</div>
</>
);
}

View file

@ -3,7 +3,7 @@
import { useTheme } from "./useTheme";
import TerminalLayout from "./TerminalLayout";
import WindowsDesktop from "./WindowsDesktop";
import IosHome from "./IosHome";
import AndroidHome from "./AndroidHome";
export default function ThemeRoot() {
const { theme } = useTheme();
@ -17,8 +17,8 @@ export default function ThemeRoot() {
<div className="skeuo-desktop-root">
<WindowsDesktop />
</div>
<div className="skeuo-ios-root">
<IosHome />
<div className="skeuo-mobile-root">
<AndroidHome />
</div>
</>
);

View file

@ -1,6 +1,6 @@
"use client";
import { useRef, type CSSProperties } from "react";
import { type CSSProperties, type PointerEvent as ReactPointerEvent } from "react";
import type { AppDef } from "./apps";
export interface WinState {
@ -13,6 +13,7 @@ export interface WinState {
z: number;
minimized: boolean;
maximized: boolean;
centered: boolean;
}
interface Props {
@ -36,17 +37,52 @@ export default function WinWindow({
onToggleMaximize,
onMove,
}: Props) {
const drag = useRef<{ sx: number; sy: number; ox: number; oy: number } | null>(null);
const centered = win.centered && !win.maximized;
const style: CSSProperties = win.maximized
? { left: 0, top: 0, width: "100%", height: "100%", zIndex: win.z }
: { left: win.x, top: win.y, width: win.w, height: win.h, zIndex: win.z };
: centered
? { width: win.w, height: win.h, zIndex: win.z }
: { left: win.x, top: win.y, width: win.w, height: win.h, zIndex: win.z };
const startDrag = (e: ReactPointerEvent<HTMLDivElement>) => {
onFocus();
if (win.maximized) return;
if ((e.target as HTMLElement).closest(".win-controls")) return;
e.preventDefault();
let ox = win.x;
let oy = win.y;
if (win.centered) {
const node = e.currentTarget.closest(".win-window") as HTMLElement | null;
const rect = node?.getBoundingClientRect();
if (rect) {
ox = rect.left;
oy = rect.top;
}
}
const sx = e.clientX;
const sy = e.clientY;
const handleMove = (ev: PointerEvent) => {
onMove(ox + (ev.clientX - sx), oy + (ev.clientY - sy));
};
const handleUp = () => {
window.removeEventListener("pointermove", handleMove);
window.removeEventListener("pointerup", handleUp);
window.removeEventListener("pointercancel", handleUp);
};
window.addEventListener("pointermove", handleMove);
window.addEventListener("pointerup", handleUp);
window.addEventListener("pointercancel", handleUp);
};
return (
<section
className={
"win-window" +
(active ? " active" : "") +
(centered ? " centered" : "") +
(win.minimized ? " minimized" : "") +
(win.maximized ? " maximized" : "")
}
@ -55,34 +91,9 @@ export default function WinWindow({
role="dialog"
aria-label={app.title}
>
<div
className="win-titlebar"
onPointerDown={(e) => {
onFocus();
if (win.maximized) return;
if ((e.target as HTMLElement).closest(".win-controls")) return;
drag.current = { sx: e.clientX, sy: e.clientY, ox: win.x, oy: win.y };
e.currentTarget.setPointerCapture(e.pointerId);
}}
onPointerMove={(e) => {
const d = drag.current;
if (!d) return;
onMove(d.ox + (e.clientX - d.sx), d.oy + (e.clientY - d.sy));
}}
onPointerUp={(e) => {
drag.current = null;
try {
e.currentTarget.releasePointerCapture(e.pointerId);
} catch {
/* ignore */
}
}}
>
<span className="win-titlebar-icon" style={{ background: app.color }} aria-hidden="true">
{app.icon}
</span>
<div className="win-titlebar" onPointerDown={startDrag}>
<span className="win-titlebar-text">{app.title}</span>
<div className="win-controls">
<div className="win-controls" onPointerDown={(e) => e.stopPropagation()}>
<button
type="button"
className="win-ctl win-ctl-min"

View file

@ -1,13 +1,38 @@
"use client";
import { useRef, useState } from "react";
import Image from "next/image";
import { APPS, APP_BY_ID } from "./apps";
import WinWindow, { type WinState } from "./WinWindow";
import Clock from "./Clock";
import { useTheme } from "./useTheme";
import { WALLPAPER } from "../data";
import { WALLPAPER, friends } from "../data";
const TASKBAR_HEIGHT = 40;
const TASKBAR_HEIGHT = 48;
const LEFT_APPS = APPS.filter((a) => a.id !== "services" && a.id !== "friends");
const SERVICE_SHORTCUTS: {
id: string;
icon: string;
label: string;
url?: string;
appId?: string;
}[] = [
{ id: "svc-proxmox", icon: "🖥️", label: "Proxmox", appId: "alacritty" },
{ id: "svc-ai", icon: "🦾", label: "Llama.cpp", url: "https://ai.dvorakmortonxd.dev/" },
{ id: "svc-jellyfin", icon: "🎬", label: "Jellyfin", url: "https://jelly.dvorakmortonxd.dev/" },
{ id: "svc-smp", icon: "🎮", label: "SMP", url: "https://discord.gg/vyvhk7d8bX" },
{ id: "svc-navidrome", icon: "🎸", label: "Navidrome", url: "https://navidrome.dvorakmortonxd.dev/" },
{ id: "svc-gitdxd", icon: "🐙", label: "GitDXD", url: "https://git.dvorakmortonxd.dev" },
];
const FRIEND_SHORTCUTS = friends.map((f, i) => ({
id: `friend-${i}`,
label: f.name,
url: f.realUrl || f.url,
img: f.img,
}));
export default function WindowsDesktop() {
const { theme, toggle } = useTheme();
@ -23,6 +48,7 @@ export default function WindowsDesktop() {
z: 10,
minimized: false,
maximized: false,
centered: true,
},
]);
const [activeId, setActiveId] = useState<number | null>(1);
@ -31,7 +57,6 @@ export default function WindowsDesktop() {
const idRef = useRef(2);
const zRef = useRef(11);
const spawnRef = useRef(1);
const focus = (id: number) => {
setActiveId(id);
@ -54,12 +79,14 @@ export default function WindowsDesktop() {
}
const app = APP_BY_ID[appId];
if (!app) return;
const n = spawnRef.current++;
const id = idRef.current++;
const maxX = Math.max(20, (typeof window !== "undefined" ? window.innerWidth : 1280) - app.width - 20);
const maxY = Math.max(20, (typeof window !== "undefined" ? window.innerHeight : 800) - TASKBAR_HEIGHT - app.height - 20);
const x = Math.min(maxX, 90 + (n % 8) * 30);
const y = Math.min(maxY, 56 + (n % 8) * 28);
const vw = window.innerWidth;
const vh = window.innerHeight - TASKBAR_HEIGHT;
const cascade = Math.min(wins.length, 8) * 30;
let x = Math.round((vw - app.width) / 2) + cascade;
let y = Math.round((vh - app.height) / 2) + cascade;
x = Math.max(10, Math.min(x, vw - app.width - 10));
y = Math.max(10, Math.min(y, vh - app.height - 10));
setWins((prev) => [
...prev,
{
@ -72,6 +99,7 @@ export default function WindowsDesktop() {
z: ++zRef.current,
minimized: false,
maximized: false,
centered: false,
},
]);
setActiveId(id);
@ -86,14 +114,17 @@ export default function WindowsDesktop() {
prev.map((w) => (w.id === id ? { ...w, maximized: !w.maximized } : w))
);
const moveWin = (id: number, x: number, y: number) => {
const maxX =
(typeof window !== "undefined" ? window.innerWidth : 1280) - 120;
const maxY =
(typeof window !== "undefined" ? window.innerHeight : 800) - TASKBAR_HEIGHT - 20;
const maxX = (typeof window !== "undefined" ? window.innerWidth : 1280) - 120;
const maxY = (typeof window !== "undefined" ? window.innerHeight : 800) - TASKBAR_HEIGHT;
setWins((prev) =>
prev.map((w) =>
w.id === id
? { ...w, x: Math.max(-w.w + 120, Math.min(maxX, x)), y: Math.max(0, Math.min(maxY, y)) }
? {
...w,
centered: false,
x: Math.max(-w.w + 120, Math.min(maxX, x)),
y: Math.max(0, Math.min(maxY, y)),
}
: w
)
);
@ -129,13 +160,15 @@ export default function WindowsDesktop() {
{/* Desktop icons */}
<div className="win-icons" onPointerDown={(e) => e.stopPropagation()}>
{APPS.map((app) => (
{LEFT_APPS.map((app) => (
<button
key={app.id}
type="button"
className={"win-icon" + (selectedIcon === app.id ? " selected" : "")}
onClick={() => setSelectedIcon(app.id)}
onDoubleClick={() => openApp(app.id)}
onClick={() => {
setSelectedIcon(app.id);
openApp(app.id);
}}
title={`Open ${app.title}`}
>
<span className="win-icon-glyph" style={{ background: app.color }}>
@ -146,6 +179,61 @@ export default function WindowsDesktop() {
))}
</div>
{/* Services - top right */}
<div
className="win-icons win-icons-tr"
onPointerDown={(e) => e.stopPropagation()}
>
{SERVICE_SHORTCUTS.map((s) =>
s.appId ? (
<button
key={s.id}
type="button"
className="win-icon"
title={s.label}
onClick={() => openApp(s.appId as string)}
>
<span className="win-icon-glyph">{s.icon}</span>
<span className="win-icon-label">{s.label}</span>
</button>
) : (
<a
key={s.id}
href={s.url}
target="_blank"
rel="noopener noreferrer"
className="win-icon"
title={s.label}
>
<span className="win-icon-glyph">{s.icon}</span>
<span className="win-icon-label">{s.label}</span>
</a>
)
)}
</div>
{/* Friends - bottom right */}
<div
className="win-icons win-icons-br"
onPointerDown={(e) => e.stopPropagation()}
>
{FRIEND_SHORTCUTS.map((f) => (
<a
key={f.id}
href={f.url}
target="_blank"
rel="noopener noreferrer"
className="win-icon"
title={f.label}
>
<span className="win-icon-glyph">
<Image src={f.img} alt={f.label} width={38} height={38} className="win-icon-img" />
</span>
<span className="win-icon-label">{f.label}</span>
</a>
))}
</div>
{/* Windows */}
<div className="win-layer">
{wins.map((w) => (
@ -163,52 +251,84 @@ export default function WindowsDesktop() {
))}
</div>
{/* Start menu */}
{/* Application launcher */}
{startOpen && (
<div className="win-startmenu" onPointerDown={(e) => e.stopPropagation()}>
<div className="win-startmenu-user">
<span className="win-startmenu-avatar">🧑‍💻</span>
<span>Martin Dvořák</span>
</div>
<div className="win-startmenu-list">
{APPS.map((app) => (
<button
key={app.id}
type="button"
className="win-startmenu-item"
onClick={() => openApp(app.id)}
>
<span className="win-startmenu-icon" style={{ background: app.color }}>
{app.icon}
</span>
{app.title}
</button>
))}
</div>
<div className="win-startmenu-foot">
<button
type="button"
className="win-startmenu-power"
onClick={() => {
toggle();
setStartOpen(false);
}}
>
⏻ Switch to {theme === "skeuo" ? "Terminal" : "Aero"}
</button>
<div className="kickoff" onPointerDown={(e) => e.stopPropagation()}>
<div className="kickoff-content">
<div className="kickoff-section-title">Applications</div>
<div className="kickoff-list">
{LEFT_APPS.map((app) => (
<button
key={app.id}
type="button"
className="kickoff-item"
onClick={() => openApp(app.id)}
>
<span className="kickoff-icon">{app.icon}</span>
<span className="kickoff-label">{app.title}</span>
</button>
))}
{SERVICE_SHORTCUTS.map((s) =>
s.appId ? (
<button
key={s.id}
type="button"
className="kickoff-item"
onClick={() => openApp(s.appId as string)}
>
<span className="kickoff-icon">{s.icon}</span>
<span className="kickoff-label">{s.label}</span>
</button>
) : (
<a
key={s.id}
className="kickoff-item"
href={s.url}
target="_blank"
rel="noopener noreferrer"
>
<span className="kickoff-icon">{s.icon}</span>
<span className="kickoff-label">{s.label}</span>
</a>
)
)}
{FRIEND_SHORTCUTS.map((f) => (
<a
key={f.id}
className="kickoff-item"
href={f.url}
target="_blank"
rel="noopener noreferrer"
>
<span className="kickoff-icon">
<Image
src={f.img}
alt={f.label}
width={20}
height={20}
className="kickoff-icon-img"
/>
</span>
<span className="kickoff-label">{f.label}</span>
</a>
))}
</div>
</div>
</div>
)}
{/* Taskbar */}
{/* Plasma panel */}
<div className="win-taskbar" onPointerDown={(e) => e.stopPropagation()}>
<button
type="button"
className={"win-start" + (startOpen ? " open" : "")}
onClick={() => setStartOpen((v) => !v)}
aria-label="Start"
aria-label="Application Launcher"
title="Application Launcher"
>
<span className="win-start-orb">⊞</span>
<span className="win-start-orb">
<Image src="/gentoo.svg" alt="Gentoo" width={26} height={27} className="gentoo-logo" />
</span>
</button>
<div className="win-tb-buttons">
@ -236,37 +356,24 @@ export default function WindowsDesktop() {
</div>
<div className="win-tray">
<form
className="win-search"
action="https://search.dvorakmortonxd.dev/search"
method="GET"
target="_blank"
rel="noopener noreferrer"
>
<span className="win-search-glyph" aria-hidden="true">
⌕
</span>
<input
type="text"
name="q"
placeholder="Search the web..."
aria-label="Search the web"
/>
</form>
<button
type="button"
className="win-theme-toggle"
onClick={toggle}
title={`Switch to ${theme === "skeuo" ? "terminal" : "Aero"} theme`}
title={`Switch to ${theme === "skeuo" ? "gruvbox" : "Desktop"} theme`}
>
<span aria-hidden="true">{theme === "skeuo" ? "▚" : "❖"}</span>
<span>{theme === "skeuo" ? "Terminal" : "Aero"}</span>
<span>{theme === "skeuo" ? "Gruvbox" : "Desktop"}</span>
</button>
<Clock className="win-clock" />
<button type="button" className="win-showdesktop" aria-label="Show desktop" onClick={() => setWins((prev) => prev.map((w) => ({ ...w, minimized: true })))} />
<button
type="button"
className="win-showdesktop"
aria-label="Show desktop"
onClick={() => setWins((prev) => prev.map((w) => ({ ...w, minimized: true })))}
/>
</div>
</div>
</div>

View file

@ -12,6 +12,7 @@ import {
import ServiceButton from "./ServiceButton";
import ContributionHeatmap from "./ContributionHeatmap";
import JokerLogo from "./JokerLogo";
import { setTheme } from "./useTheme";
export interface AppDef {
id: string;
@ -30,7 +31,7 @@ function WelcomeApp() {
<JokerLogo />
<div>
<h2>Welcome to dvorakmortonxd.dev</h2>
<p className="app-muted">A Windows 7 desktop edition of my personal website.</p>
<p className="app-muted">A KDE Plasma desktop edition of my personal website.</p>
</div>
</div>
<p>
@ -41,15 +42,27 @@ function WelcomeApp() {
<ul className="app-list">
<li>Drag windows around by their title bar.</li>
<li>Use the buttons at the top-right to minimize, maximize or close.</li>
<li>Search the web from the taskbar at the bottom.</li>
<li>Switch back to the terminal theme from the tray, bottom-right.</li>
<li>Open apps from the Gentoo launcher, bottom-left.</li>
<li>Switch back to the Gruvbox theme from the panel tray, bottom-right.</li>
</ul>
<p className="app-preference">
If you&#39;d prefer a normal website{" "}
<button
type="button"
className="app-inline-link"
onClick={() => setTheme("terminal")}
>
click here
</button>
.
</p>
<p className="app-muted app-fineprint">
Wallpaper from{" "}
<a href={WALLPAPER_CREDIT} target="_blank" rel="noopener noreferrer">
wallpaperaccess.com/open-source
</a>
.
. Gentoo logo by Matteo &apos;Peach&apos; Pescarin (CC BY-SA 2.5), via Wikimedia
Commons.
</p>
</div>
);
@ -255,38 +268,6 @@ function FriendsApp() {
/>
))}
</div>
<div className="friends-extra">
<h3>Quipee by Bukovina Filip &amp; Vlk Tomáš</h3>
<div className="app-shot">
<Image
src="/quipee.webp"
alt="Quipee Thumbnail"
width={1280}
height={720}
style={{ width: "100%", height: "auto", display: "block" }}
/>
</div>
<div className="app-linkrow">
<a href="https://quipee.me" target="_blank" rel="noopener noreferrer">
Web
</a>
<a
href="https://drive.google.com/drive/folders/1oi76JyWs6J84MUmCGV7GvtLUexXI8Jel?usp=share_link"
target="_blank"
rel="noopener noreferrer"
>
Android
</a>
<a
href="https://testflight.apple.com/join/hP2a1E5x"
target="_blank"
rel="noopener noreferrer"
>
IOS
</a>
</div>
</div>
</div>
);
}
@ -405,6 +386,50 @@ function RustyWolfApp() {
);
}
const SIKE_ART = [
" ▄▄▄▄▄▄▄▄▄▄▄▄▄▄ ▄▄▄▄▄▄▄▄ ▄▄▄▄▄▄▄▄ ▄▄▄▄▄▄▄▄ ▄▄▄▄▄▄▄▄▄▄▄▄▄▄",
" ▄▀▀ ░ ▄ ▓ ▄ ▓ ▄ █ ▄▀ ░ ░▒▓░",
"▐▌ ▄▄▄▄ ▒▒ █ ▒ █ ▒ ▒ ▒░ ▐▌ ░▒▒",
"█ ░ ░ ░▓ ▓ ░ ▓ ░ ░ ░▒ █ ░▓",
"▒ ▒ ▀▀▀▀▀▀▀▀ ▒ █ ▒ █ █ ▐▌ ▓ ▄▄▄▄▄▄▄▄█",
"▐▌ ▀▄▄▄▄▄▄ ░ █ ░ █▄▄█ ▄▀ ░ ▓▄▄▄▄ · ",
" ▀▄▄ ┼┼▀▄ █┼ █ █┼ ▒ █ ┼ ▄▄▄▄▒ ·",
" ▀▀▀▀▀▄▄┼├├┼├├▌ █┼ ┼█ █┼ ┼█▀▀█┼ ▀▄ █┼┼┼ ┼▓▄▄▄▄▄▄▄▄",
"▀▀▀▀▀▀▀░ ▒┼├├┼├├▓ █┼┼ ┼┼░ █┼┼ ┼┼░ █┼ ▐▌ █┼┼┼ ┼┼┼┼┼┼┼┼┼┼┼░",
"▒┼┼├┼┼┼▒▄▄▓┼┼├┼┼▐▌ █┼┼┼┼┼┼▒ █┼┼┼┼┼┼▒ █┼┼┼ ┼┼▒ ▐▌┼┼┼┼┼┼┼┼┼┼┼┼┼┼▒",
"░┼├┼┼┼┼┼┼┼┼┼┼┼▄▀ █┼┼┼┼┼┼▓ █┼┼┼┼┼┼▓ █┼┼┼┼┼┼▓ ·▀▄┼┼┼┼┼┼┼┼┼┼┼┼┼▓",
"▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀ ▀▀▀▀▀▀▀▀ ▀▀▀▀▀▀▀▀ ▀▀▀▀▀▀▀▀ ▀▀▀▀▀▀▀▀▀▀▀▀▀▀",
].join("\n");
function SikeTerminal() {
return (
<div className="alacritty">
<div className="alacritty-line">
<span className="alacritty-prompt">martin@gentoo</span>
<span className="alacritty-path"> ~ </span>
<span className="alacritty-cmd">$ ssh root@proxmox.dvorakmortonxd.dev</span>
</div>
<pre className="alacritty-art">{SIKE_ART}</pre>
<div className="alacritty-line">
<span className="alacritty-prompt">martin@gentoo</span>
<span className="alacritty-path"> ~ </span>
<span className="alacritty-cmd">$ </span>
<span className="alacritty-cursor" aria-hidden="true" />
</div>
</div>
);
}
export const ALACRITTY_APP: AppDef = {
id: "alacritty",
title: "Alacritty",
icon: ">_",
color: "#1d1f21",
width: 760,
height: 440,
content: <SikeTerminal />,
};
export const APPS: AppDef[] = [
{ id: "welcome", title: "Welcome", icon: "👋", color: "#2f7fc4", width: 620, height: 430, content: <WelcomeApp /> },
{ id: "about", title: "About Me", icon: "🧑‍💻", color: "#0d5aa7", width: 720, height: 470, content: <AboutApp /> },
@ -422,5 +447,5 @@ export const APPS: AppDef[] = [
];
export const APP_BY_ID: Record<string, AppDef> = Object.fromEntries(
APPS.map((a) => [a.id, a])
[...APPS, ALACRITTY_APP].map((a) => [a.id, a])
);

View file

@ -931,41 +931,6 @@ body {
}
}
/* ============================================
SEARCH BAR
============================================ */
.search-section {
margin-top: 20px;
}
.search-form {
display: flex;
padding: 20px 24px;
}
.search-bar {
width: 100%;
padding: 12px 16px;
background: rgba(255, 255, 255, 0.05); /* very transparent */
backdrop-filter: blur(10px) saturate(120%);
-webkit-backdrop-filter: blur(10px) saturate(120%);
border: 1px solid var(--tui-border);
color: var(--fg);
font-family: var(--font-custom-mono), monospace;
font-size: 1rem;
outline: none;
transition: all 0.3s ease;
}
.search-bar:focus {
border-color: var(--red);
box-shadow: 0 0 10px rgba(74, 233, 52, 0.3);
}
.search-bar::placeholder {
color: var(--gray);
}
/* Joker Animation */
.joker-flash-overlay {
position: fixed;
@ -1270,56 +1235,22 @@ body {
}
/* ============================================
TUI STATUS BAR (terminal theme)
HEADER THEME SWITCH (terminal theme)
============================================ */
body {
padding-bottom: 44px;
}
.tui-statusbar {
position: fixed;
left: 0;
right: 0;
bottom: 0;
z-index: 5000;
display: flex;
align-items: center;
gap: 12px;
padding: 8px 16px;
background: rgba(29, 32, 33, 0.92);
border-top: 1px solid var(--tui-border);
backdrop-filter: blur(12px);
-webkit-backdrop-filter: blur(12px);
font-family: var(--font-custom-mono), 'JetBrains Mono', monospace;
font-size: 0.75rem;
color: var(--gray);
}
.tui-statusbar-item {
color: var(--gray);
}
.tui-statusbar-btn {
padding: 3px 8px;
.header-theme-btn {
margin-left: auto;
padding: 4px 12px;
background: transparent;
border: 1px solid var(--gray);
color: var(--fg);
font-family: inherit;
font-size: 0.72rem;
font-family: var(--font-custom-mono), 'JetBrains Mono', monospace;
font-size: 0.75rem;
cursor: pointer;
transition: all 0.15s ease;
}
.tui-statusbar-btn:hover {
.header-theme-btn:hover {
border-color: var(--accent);
color: var(--accent);
box-shadow: 0 0 8px rgba(250, 189, 47, 0.35);
}
.tui-statusbar-spacer {
flex: 1;
}
.tui-statusbar-clock {
color: var(--gray);
}

File diff suppressed because it is too large Load diff