FULL ON WEBSITE REMAKE
This commit is contained in:
parent
d2f99c87b9
commit
a14df3f3b3
11 changed files with 1140 additions and 830 deletions
81
public/gentoo.svg
Normal file
81
public/gentoo.svg
Normal file
|
|
@ -0,0 +1,81 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg width="493.3" height="519.3" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:cc="http://creativecommons.org/ns#" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<title>Gentoo Logo Vector</title>
|
||||
<defs>
|
||||
<linearGradient id="vlpa">
|
||||
<stop style="stop-opacity:.4592" offset="0"/>
|
||||
<stop style="stop-color:#9999e6" offset="1"/>
|
||||
</linearGradient>
|
||||
<radialGradient id="vlpd" cx="240.2" cy="350.8" r="303.3" gradientTransform="matrix(.691 1.012 -.8154 .557 374.4 -116.5)" gradientUnits="userSpaceOnUse">
|
||||
<stop style="stop-color:#fff" offset="0"/>
|
||||
<stop style="stop-color:#fff" offset=".4866"/>
|
||||
<stop style="stop-color:#d9d9f6" offset=".5931"/>
|
||||
<stop style="stop-color:#adadeb" offset="1"/>
|
||||
</radialGradient>
|
||||
<radialGradient id="vlpc" cx="402.9" cy="273.5" r="77.79" gradientTransform="scale(1.095 .9133)" gradientUnits="userSpaceOnUse">
|
||||
<stop style="stop-color:#fff" offset="0"/>
|
||||
<stop style="stop-color:#fff" offset=".3036"/>
|
||||
<stop style="stop-color:#e6e6f9" offset=".6071"/>
|
||||
<stop style="stop-color:#9999e6" offset="1"/>
|
||||
</radialGradient>
|
||||
<linearGradient id="vlph" x1="192.6" x2="234.9" y1="404.7" y2="331.9" gradientTransform="scale(1.083 .9232)" gradientUnits="userSpaceOnUse" xlink:href="#vlpa"/>
|
||||
<linearGradient id="vlpg" x1="188.6" x2="450.4" y1="785.1" y2="519.5" gradientTransform="scale(1.348 .7417)" gradientUnits="userSpaceOnUse" xlink:href="#vlpa"/>
|
||||
<linearGradient id="vlpf" x1="508.2" x2="271" y1="428.6" y2="659.8" gradientTransform="scale(1.201 .8329)" gradientUnits="userSpaceOnUse">
|
||||
<stop style="stop-color:#fff" offset="0"/>
|
||||
<stop style="stop-color:#fff;stop-opacity:0" offset="1"/>
|
||||
</linearGradient>
|
||||
<filter id="vlpi">
|
||||
<feGaussianBlur stdDeviation="1.3094535"/>
|
||||
</filter>
|
||||
<linearGradient id="vlpe" x1="354.2" x2="314.4" y1="83.33" y2="322.3" gradientUnits="userSpaceOnUse">
|
||||
<stop style="stop-color:#7b7bdf" offset="0"/>
|
||||
<stop style="stop-color:#a6a6e9" offset="1"/>
|
||||
</linearGradient>
|
||||
<radialGradient id="vlpb" cx="364.3" cy="341" r="35.68" gradientTransform="matrix(1.504 -.05645 .04112 1.095 -156.9 -46.75)" gradientUnits="userSpaceOnUse">
|
||||
<stop offset="0"/>
|
||||
<stop style="stop-color:#9999e6" offset="1"/>
|
||||
</radialGradient>
|
||||
</defs>
|
||||
<metadata>
|
||||
<rdf:RDF>
|
||||
<cc:Work rdf:about="">
|
||||
<dc:format>image/svg+xml</dc:format>
|
||||
<dc:type rdf:resource="http://purl.org/dc/dcmitype/StillImage"/>
|
||||
<dc:title>Gentoo Logo Vector</dc:title>
|
||||
<dc:creator>
|
||||
<cc:Agent>
|
||||
<dc:title>Matteo 'Peach' Pescarin</dc:title>
|
||||
</cc:Agent>
|
||||
</dc:creator>
|
||||
<dc:description/>
|
||||
<cc:license rdf:resource="http://creativecommons.org/licenses/by-sa/2.5/"/>
|
||||
<dc:contributor>
|
||||
<cc:Agent>
|
||||
<dc:title>Daniel Robbins as creator of the original logo</dc:title>
|
||||
</cc:Agent>
|
||||
</dc:contributor>
|
||||
</cc:Work>
|
||||
<cc:License rdf:about="http://creativecommons.org/licenses/by-sa/2.5/">
|
||||
<cc:permits rdf:resource="http://creativecommons.org/ns#Reproduction"/>
|
||||
<cc:permits rdf:resource="http://creativecommons.org/ns#Distribution"/>
|
||||
<cc:requires rdf:resource="http://creativecommons.org/ns#Notice"/>
|
||||
<cc:requires rdf:resource="http://creativecommons.org/ns#Attribution"/>
|
||||
<cc:permits rdf:resource="http://creativecommons.org/ns#DerivativeWorks"/>
|
||||
<cc:requires rdf:resource="http://creativecommons.org/ns#ShareAlike"/>
|
||||
</cc:License>
|
||||
</rdf:RDF>
|
||||
</metadata>
|
||||
<g transform="translate(-152 -139)">
|
||||
<g transform="translate(-20.81 8.922)">
|
||||
<path d="m196.3 489.8s-18.67 23.07-12.08 61.51c6.59 38.44 26.36 57.66 26.36 57.66s27.46 28.01 65.35 30.2c37.89 2.197 76.89-15.38 76.89-15.38s67-30.75 101-51.07c34.05-20.32 81.28-52.17 120.8-87.32 39.54-35.15 62.06-67 62.06-67s18.12-26.91 19.22-44.48c1.098-17.57-6.041-36.8-17.57-57.66-11.53-20.87-48.33-68.65-96.66-105.4-48.33-36.8-107.6-66.45-159.3-70.3-51.62-3.844-88.97 17.02-110.4 31.85-21.42 14.83-61.51 49.98-74.14 87.87-12.69 38.06-7.139 65.9 23.61 91.16 30.75 25.26 72.49 45.03 72.49 45.03l-97.75 93.36z" style="fill-opacity:.3333;fill-rule:evenodd"/>
|
||||
<path d="m294.7 165c-40.55 22.33-70.77 56.56-80.79 77.7-20.59 43.39-18.71 64.44 3.844 91.71 17.12 20.7 79.63 53.27 79.63 53.27s-53.82 47.23-74.69 67c-20.87 19.77-37.34 48.33-26.36 92.26 10.98 43.93 45.03 59.31 45.03 59.31s44.48 26.36 110.4-2.197c65.9-28.56 114.8-59.31 114.8-59.31s97.2-66.45 123.6-97.2c26.36-30.75 62.06-60.96 45.58-110.4-16.48-49.43-90.07-115.3-90.07-115.3s-73.33-56.1-126.2-69.85c-57.24-14.89-105.6 2.44-124.8 13.01z" style="fill-rule:evenodd;fill:url(#vlpe);stroke-linecap:round;stroke-linejoin:round;stroke-width:2.485;stroke:#000"/>
|
||||
<path d="m339.1 358.5s-20.87 24.71-42.84 43.93c-21.97 19.22-30.45 27.98-53.16 51.18-13.18 13.46-24.44 27.47-27.58 53.73-2.285 19.14 2.289 37.47 15.01 48.23s30.22 20.97 65.18 14.27c40.72-7.807 71.54-24.71 71.54-24.71s92.89-47 142.9-84.89c49.98-37.89 100.6-94.62 100.6-94.62s17.53-11.52-1.565-46.21c-19.54-35.5-76.95-85.97-96.72-100.3-19.77-14.28-82.76-65.52-138.2-63.87s-91.71 28.56-91.71 28.56c-11.53 9.885-51.62 39.27-60.96 64.25-9.405 25.17-4.943 45.58 13.18 58.76 18.12 13.18 75.79 37.34 75.79 37.34z" style="fill-rule:evenodd;fill:url(#vlpd)"/>
|
||||
<path d="m370.4 305.8s31.3 26.91 59.86 24.71c28.56-2.197 30.75-19.77 30.75-19.77s6.59-23.07-10.43-41.74c-17.02-18.67-41.74-32.95-62.06-32.4-20.32 0.5492-39.54 20.32-38.99 36.25 0.5492 15.93 20.87 32.95 20.87 32.95z" style="fill-opacity:.75;fill-rule:evenodd;fill:url(#vlpc)"/>
|
||||
<path d="m397.3 256.4c-18.12 0.2746-26.37 8.441-24.71 23.07 1.648 14.55 33.75 33.26 48.33 30.75 15.11-2.597 19.07-8.77 18.12-20.87-0.9005-11.55-24.16-33.23-41.74-32.95z" style="fill-opacity:.75;fill-rule:evenodd;fill:url(#vlpb)"/>
|
||||
<path d="m207.8 281.1s3.295 17.57 25.81 33.5c22.52 15.93 64.23 31.94 86.14 44.64 4.199 2.434 4.91 6.049-2.972 13.06-7.554 6.719-12.48 10.97-12.48 10.97s-63.09-34.93-79.84-50.68c-27.08-25.46-16.65-51.49-16.65-51.49z" style="fill-opacity:.75;fill-rule:evenodd;fill:url(#vlph)"/>
|
||||
<path d="m202.3 543.3s20.59 37.55 69.23 37.66c39.23 0.0847 76.56-11.63 135.1-41.19 82.89-41.85 146.6-92.16 178.4-120.2 31.85-28.01 49.18-50.48 49.18-50.48s-18.38 42.7-44.74 67.96c-26.36 25.26-91.71 82.93-154.9 117.5-63.16 34.6-106.2 58.4-150.3 56.23-41.88-2.054-61.68-26.58-70.47-38.11-8.787-11.53-11.57-29.42-11.57-29.42z" style="fill-opacity:.75;fill-rule:evenodd;fill:url(#vlpg)"/>
|
||||
<path d="m315.5 562.8s38.44-8.238 111.5-48.33c73.04-40.09 103.5-66.85 125.4-85.02s54.25-45.06 64.69-57.69c10.43-12.63 7.496-21.39 3.048-23.77-3.423-1.833-13.12 5.869-18.6 11.93s-46.6 44.17-69.66 63.94-87.87 71.39-126.9 93.91c-38.99 22.52-89.52 45.03-89.52 45.03z" style="fill-rule:evenodd;fill:url(#vlpf);filter:url(#vlpi)"/>
|
||||
<path transform="translate(172.8 130)" d="m227.8 167.6c-4.822 0-9.519 0.6888-12.28 1.188 10.74 7.478 24.44 12.75 32.62 11.34 3.176-0.5457 5.845-1.242 8.094-2.125-5.276-3.068-14.9-8.321-21.19-9.719-2.25-0.5-4.766-0.6875-7.25-0.6875z" style="color:#000000"/>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 6.9 KiB |
90
src/app/components/AndroidHome.tsx
Normal file
90
src/app/components/AndroidHome.tsx
Normal 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>
|
||||
);
|
||||
}
|
||||
|
|
@ -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}
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
);
|
||||
}
|
||||
|
|
@ -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 & 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>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
</>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -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 }
|
||||
: 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"
|
||||
|
|
|
|||
|
|
@ -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) => (
|
||||
<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="win-startmenu-item"
|
||||
className="kickoff-item"
|
||||
onClick={() => openApp(app.id)}
|
||||
>
|
||||
<span className="win-startmenu-icon" style={{ background: app.color }}>
|
||||
{app.icon}
|
||||
</span>
|
||||
{app.title}
|
||||
<span className="kickoff-icon">{app.icon}</span>
|
||||
<span className="kickoff-label">{app.title}</span>
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
<div className="win-startmenu-foot">
|
||||
{SERVICE_SHORTCUTS.map((s) =>
|
||||
s.appId ? (
|
||||
<button
|
||||
key={s.id}
|
||||
type="button"
|
||||
className="win-startmenu-power"
|
||||
onClick={() => {
|
||||
toggle();
|
||||
setStartOpen(false);
|
||||
}}
|
||||
className="kickoff-item"
|
||||
onClick={() => openApp(s.appId as string)}
|
||||
>
|
||||
⏻ Switch to {theme === "skeuo" ? "Terminal" : "Aero"}
|
||||
<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>
|
||||
|
|
|
|||
|
|
@ -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'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 'Peach' 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 & 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])
|
||||
);
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
|
|
|||
1056
src/app/skeuo.css
1056
src/app/skeuo.css
File diff suppressed because it is too large
Load diff
Loading…
Add table
Add a link
Reference in a new issue