first commit/push
This commit is contained in:
parent
efbdff72c6
commit
cc60991fc0
27 changed files with 11489 additions and 1 deletions
BIN
.DS_Store
vendored
Normal file
BIN
.DS_Store
vendored
Normal file
Binary file not shown.
6
.gitignore
vendored
6
.gitignore
vendored
|
|
@ -1,3 +1,4 @@
|
||||||
|
<<<<<<< HEAD
|
||||||
# ---> Nextjs
|
# ---> Nextjs
|
||||||
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
|
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
|
||||||
|
|
||||||
|
|
@ -182,3 +183,8 @@ vite.config.js.timestamp-*
|
||||||
vite.config.ts.timestamp-*
|
vite.config.ts.timestamp-*
|
||||||
.vite/
|
.vite/
|
||||||
|
|
||||||
|
=======
|
||||||
|
node_modules/
|
||||||
|
.next/
|
||||||
|
.idea/
|
||||||
|
>>>>>>> 2b92431 (first commit/push)
|
||||||
|
|
|
||||||
97
README.md
97
README.md
|
|
@ -1,3 +1,98 @@
|
||||||
# dvorakmortonxd.dev
|
# dvorakmortonxd.dev
|
||||||
|
|
||||||
The code for the website https://dvorakmortonxd.dev/
|
> My personal website — socials, projects, services, and hardware all in one place. Welcome to the goblin cave. >:)
|
||||||
|
|
||||||
|
  
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## $ whoami
|
||||||
|
|
||||||
|
Hey, I'm **Martin Dvořák** — a 17-year-old IT student based in the Czech Republic, specializing in network management and administration at [sssvt.cz](https://sssvt.cz). I'm a passionate homelabber and developer heavily focused on self-hosting literally everything, writing code, and tinkering with Linux systems and retro hardware.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🌐 Live Site
|
||||||
|
|
||||||
|
**[dvorakmortonxd.dev](https://dvorakmortonxd.dev)**
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🛠️ Tech Stack
|
||||||
|
|
||||||
|
- **Framework:** Next.js (TypeScript)
|
||||||
|
- **Styling:** PostCSS / Tailwind
|
||||||
|
- **Deployed at:** [dvorakmortonxd.dev](https://dvorakmortonxd.dev)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 📁 Project Structure
|
||||||
|
|
||||||
|
```
|
||||||
|
dvorakmortonxd.dev/
|
||||||
|
├── public/ # Static assets (icons, SVGs)
|
||||||
|
├── src/ # App source code
|
||||||
|
├── next.config.ts
|
||||||
|
├── tsconfig.json
|
||||||
|
└── postcss.config.mjs
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🚀 Running Locally
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git clone https://git.dvorakmortonxd.dev/dvorakmortonxd/dvorakmortonxd.dev
|
||||||
|
cd dvorakmortonxd.dev
|
||||||
|
npm install
|
||||||
|
npm run dev
|
||||||
|
```
|
||||||
|
|
||||||
|
Open [http://localhost:3000](http://localhost:3000) in your browser.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🖥️ Self-Hosted Services
|
||||||
|
|
||||||
|
| Service | Description | Link |
|
||||||
|
|---|---|---|
|
||||||
|
| Proxmox | Server Management | [proxmox.dvorakmortonxd.dev](https://proxmox.dvorakmortonxd.dev) |
|
||||||
|
| Ollama | Local AI Server | [ai.dvorakmortonxd.dev](https://ai.dvorakmortonxd.dev) |
|
||||||
|
| Jellyfin | Local Media Server | [jelly.dvorakmortonxd.dev](https://jelly.dvorakmortonxd.dev) |
|
||||||
|
| MCSManager | Game Server Panel | [fake-pterodactyl.dvorakmortonxd.dev](https://fake-pterodactyl.dvorakmortonxd.dev) |
|
||||||
|
| Navidrome | Music Streaming | [navidrome.dvorakmortonxd.dev](https://navidrome.dvorakmortonxd.dev) |
|
||||||
|
| Mailcow | Email Server | [mailcow.dvorakmortonxd.dev](https://mailcow.dvorakmortonxd.dev) |
|
||||||
|
| SearxNG | Private Search | [search.dvorakmortonxd.dev](https://search.dvorakmortonxd.dev) |
|
||||||
|
| ConvertX | File Converter | [convert.dvorakmortonxd.dev](https://convert.dvorakmortonxd.dev) |
|
||||||
|
| GitDXD | Self-hosted Git | [git.dvorakmortonxd.dev](https://git.dvorakmortonxd.dev) |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🧪 Projects
|
||||||
|
|
||||||
|
- **[Hospital Execution](https://martin-dv.itch.io/hospital-execution-demo)** — First real game, made in Pygame
|
||||||
|
- **[Underbaranoia](https://martin-dv.itch.io/underbaranoia)** — Undertale-like infinite fight simulator in Pygame
|
||||||
|
- **[RustyWolf](https://git.dvorakmortonxd.dev/dvorakmortonxd/RustyWolf)** — Lightweight browser written in Rust with WebKitGTK
|
||||||
|
- **[dxdwm](https://git.dvorakmortonxd.dev/dvorakmortonxd/dxdwm)** — Small X11 window manager written in Rust
|
||||||
|
- **[Venturesome](https://git.dvorakmortonxd.dev/dvorakmortonxd/Venturesome)** — Browser written in Python with PyQtWebEngine
|
||||||
|
- **[Mandelbrot Set in Pygame](https://git.dvorakmortonxd.dev/dvorakmortonxd/mandelbrot)** — Pygame Mandelbrot set renderer
|
||||||
|
- **[Underpygametale](https://git.dvorakmortonxd.dev/dvorakmortonxd/underpygametale)** — Undertale remake in Pygame
|
||||||
|
- **[Batch MSDOS Simulator](https://git.dvorakmortonxd.dev/dvorakmortonxd/DvorakMSDOS)** — Batch OS simulator with games, browser, and surprises
|
||||||
|
- **[Offline Memer](https://martin-dv.itch.io/offline-memer)** — Dank Memer parody made in Batch
|
||||||
|
- **[Softcraft](https://dvorakmortonxd.dev/softcraft)** — My Minecraft server with a [live BlueMap](https://bluemap.dvorakmortonxd.dev)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 📬 Contact
|
||||||
|
|
||||||
|
- **Email:** [martin@dvorakmortonxd.dev](mailto:martin@dvorakmortonxd.dev)
|
||||||
|
- **Instagram:** [@dvorakmortonxd](https://www.instagram.com/dvorakmortonxd/)
|
||||||
|
- **LinkedIn:** [Martin Dvořák](https://www.linkedin.com/in/martin-dvorak-32012a394/)
|
||||||
|
- **Discord:** [dvorakmortonxd](https://discord.com/users/555745543763001364)
|
||||||
|
- **Itch.io:** [martin-dv.itch.io](https://martin-dv.itch.io)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 📄 License
|
||||||
|
|
||||||
|
© 2025–2026 Martin Dvořák. All rights reserved (not really, I don't care).
|
||||||
25
eslint.config.mjs
Normal file
25
eslint.config.mjs
Normal file
|
|
@ -0,0 +1,25 @@
|
||||||
|
import { dirname } from "path";
|
||||||
|
import { fileURLToPath } from "url";
|
||||||
|
import { FlatCompat } from "@eslint/eslintrc";
|
||||||
|
|
||||||
|
const __filename = fileURLToPath(import.meta.url);
|
||||||
|
const __dirname = dirname(__filename);
|
||||||
|
|
||||||
|
const compat = new FlatCompat({
|
||||||
|
baseDirectory: __dirname,
|
||||||
|
});
|
||||||
|
|
||||||
|
const eslintConfig = [
|
||||||
|
...compat.extends("next/core-web-vitals", "next/typescript"),
|
||||||
|
{
|
||||||
|
ignores: [
|
||||||
|
"node_modules/**",
|
||||||
|
".next/**",
|
||||||
|
"out/**",
|
||||||
|
"build/**",
|
||||||
|
"next-env.d.ts",
|
||||||
|
],
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
export default eslintConfig;
|
||||||
6
next-env.d.ts
vendored
Normal file
6
next-env.d.ts
vendored
Normal file
|
|
@ -0,0 +1,6 @@
|
||||||
|
/// <reference types="next" />
|
||||||
|
/// <reference types="next/image-types/global" />
|
||||||
|
/// <reference path="./.next/types/routes.d.ts" />
|
||||||
|
|
||||||
|
// NOTE: This file should not be edited
|
||||||
|
// see https://nextjs.org/docs/app/api-reference/config/typescript for more information.
|
||||||
14
next.config.ts
Normal file
14
next.config.ts
Normal file
|
|
@ -0,0 +1,14 @@
|
||||||
|
import { NextConfig } from "next";
|
||||||
|
|
||||||
|
const nextConfig: NextConfig = {
|
||||||
|
webpack(config) {
|
||||||
|
config.module.rules.push({
|
||||||
|
test: /\.svg$/i,
|
||||||
|
issuer: /\.[jt]sx?$/,
|
||||||
|
use: ["@svgr/webpack"],
|
||||||
|
});
|
||||||
|
return config;
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export default nextConfig;
|
||||||
8336
package-lock.json
generated
Normal file
8336
package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load diff
31
package.json
Normal file
31
package.json
Normal file
|
|
@ -0,0 +1,31 @@
|
||||||
|
{
|
||||||
|
"name": "dvorakmortonxd",
|
||||||
|
"version": "0.1.0",
|
||||||
|
"private": true,
|
||||||
|
"scripts": {
|
||||||
|
"dev": "next dev --turbopack",
|
||||||
|
"build": "next build --turbopack",
|
||||||
|
"start": "next start",
|
||||||
|
"lint": "eslint"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"next": "^15.5.18",
|
||||||
|
"nextjs": "^0.0.3",
|
||||||
|
"react": "19.1.0",
|
||||||
|
"react-dom": "19.1.0"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@eslint/eslintrc": "^3",
|
||||||
|
"@svgr/webpack": "^8.1.0",
|
||||||
|
"@tailwindcss/postcss": "^4",
|
||||||
|
"@types/node": "^20",
|
||||||
|
"@types/react": "^19",
|
||||||
|
"@types/react-dom": "^19",
|
||||||
|
"autoprefixer": "^10.4.21",
|
||||||
|
"eslint": "^9",
|
||||||
|
"eslint-config-next": "15.5.4",
|
||||||
|
"postcss": "^8.5.6",
|
||||||
|
"tailwindcss": "^4.1.14",
|
||||||
|
"typescript": "^5"
|
||||||
|
}
|
||||||
|
}
|
||||||
5
postcss.config.mjs
Normal file
5
postcss.config.mjs
Normal file
|
|
@ -0,0 +1,5 @@
|
||||||
|
const config = {
|
||||||
|
plugins: ["@tailwindcss/postcss"],
|
||||||
|
};
|
||||||
|
|
||||||
|
export default config;
|
||||||
BIN
src/.DS_Store
vendored
Normal file
BIN
src/.DS_Store
vendored
Normal file
Binary file not shown.
BIN
src/app/.DS_Store
vendored
Normal file
BIN
src/app/.DS_Store
vendored
Normal file
Binary file not shown.
35
src/app/api/ping/route.ts
Normal file
35
src/app/api/ping/route.ts
Normal file
|
|
@ -0,0 +1,35 @@
|
||||||
|
import { NextResponse } from 'next/server';
|
||||||
|
|
||||||
|
export async function GET(request: Request) {
|
||||||
|
const { searchParams } = new URL(request.url);
|
||||||
|
const url = searchParams.get('url');
|
||||||
|
|
||||||
|
if (!url) return NextResponse.json({ status: 'down' });
|
||||||
|
|
||||||
|
if (url.startsWith('minecraft://')) {
|
||||||
|
const address = url.replace('minecraft://', '');
|
||||||
|
try {
|
||||||
|
const res = await fetch(`https://api.mcsrvstat.us/3/${address}`);
|
||||||
|
const data = await res.json();
|
||||||
|
return NextResponse.json({ status: data.online ? 'up' : 'down' });
|
||||||
|
} catch {
|
||||||
|
return NextResponse.json({ status: 'down' });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const httpRes = await fetch(url, {
|
||||||
|
method: 'GET',
|
||||||
|
signal: AbortSignal.timeout(5000)
|
||||||
|
});
|
||||||
|
|
||||||
|
// Accept all non-5xx responses as "up". 5xx usually means origin or gateway errors (like Cloudflare 521, 522, etc.)
|
||||||
|
if (httpRes && httpRes.status < 500) {
|
||||||
|
return NextResponse.json({ status: 'up' });
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
console.error(`Failed to ping ${url}`, e);
|
||||||
|
}
|
||||||
|
|
||||||
|
return NextResponse.json({ status: 'down' });
|
||||||
|
}
|
||||||
BIN
src/app/components/.DS_Store
vendored
Normal file
BIN
src/app/components/.DS_Store
vendored
Normal file
Binary file not shown.
36
src/app/components/JokerLogo.tsx
Normal file
36
src/app/components/JokerLogo.tsx
Normal file
|
|
@ -0,0 +1,36 @@
|
||||||
|
"use client";
|
||||||
|
|
||||||
|
import { useState } from "react";
|
||||||
|
import Image from "next/image";
|
||||||
|
|
||||||
|
export default function JokerLogo() {
|
||||||
|
const [jokerActive, setJokerActive] = useState(false);
|
||||||
|
|
||||||
|
const handleClick = () => {
|
||||||
|
if (jokerActive) return;
|
||||||
|
setJokerActive(true);
|
||||||
|
setTimeout(() => {
|
||||||
|
setJokerActive(false);
|
||||||
|
}, 1200);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Image
|
||||||
|
src="/icon.png"
|
||||||
|
alt="Logo"
|
||||||
|
className="logo"
|
||||||
|
width={100}
|
||||||
|
height={100}
|
||||||
|
onClick={handleClick}
|
||||||
|
style={{ cursor: "pointer" }}
|
||||||
|
/>
|
||||||
|
{jokerActive && (
|
||||||
|
<div className="joker-flash-overlay">
|
||||||
|
<Image src="/joker.png" alt="Joker" className="joker-img" width={500} height={500} unoptimized />
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
42
src/app/components/ServiceButton.tsx
Normal file
42
src/app/components/ServiceButton.tsx
Normal file
|
|
@ -0,0 +1,42 @@
|
||||||
|
"use client";
|
||||||
|
|
||||||
|
import React, { useEffect, useState, ReactNode } from 'react';
|
||||||
|
|
||||||
|
export default function ServiceButton({ url, checkUrl, className, icon, name, desc }: { url: string, checkUrl?: string, className: string, icon: ReactNode, name: string, desc: string }) {
|
||||||
|
const [status, setStatus] = useState<'pending' | 'up' | 'down'>('pending');
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const checkStatus = async () => {
|
||||||
|
try {
|
||||||
|
const targetUrl = checkUrl || url;
|
||||||
|
const res = await fetch(`/api/ping?url=${encodeURIComponent(targetUrl)}`);
|
||||||
|
const data = await res.json();
|
||||||
|
setStatus(data.status);
|
||||||
|
} catch {
|
||||||
|
setStatus('down');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
checkStatus();
|
||||||
|
}, [url, checkUrl]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<a href={url} target="_blank" rel="noopener noreferrer" className={`service-button ${className}`} style={{ position: 'relative' }}>
|
||||||
|
<div style={{
|
||||||
|
position: 'absolute',
|
||||||
|
top: '8px',
|
||||||
|
right: '8px',
|
||||||
|
width: '10px',
|
||||||
|
height: '10px',
|
||||||
|
borderRadius: '50%',
|
||||||
|
backgroundColor: status === 'up' ? '#4ade80' : status === 'down' ? '#ef4444' : '#9ca3af',
|
||||||
|
boxShadow: status === 'up' ? '0 0 5px #4ade80' : status === 'down' ? '0 0 5px #ef4444' : 'none',
|
||||||
|
transition: 'all 0.3s ease'
|
||||||
|
}} title={status === 'up' ? 'Active' : status === 'down' ? 'Dead' : 'Checking...'} />
|
||||||
|
<span className="service-icon">{icon}</span>
|
||||||
|
<span className="service-text">
|
||||||
|
<span className="service-name">{name}</span>
|
||||||
|
<span className="service-desc">{desc}</span>
|
||||||
|
</span>
|
||||||
|
</a>
|
||||||
|
);
|
||||||
|
}
|
||||||
105
src/app/components/SlBackground.tsx
Normal file
105
src/app/components/SlBackground.tsx
Normal file
|
|
@ -0,0 +1,105 @@
|
||||||
|
"use client";
|
||||||
|
|
||||||
|
import { useEffect, useState } from "react";
|
||||||
|
|
||||||
|
const TRAIN_ASCII = ` ==== ________ ___________
|
||||||
|
_D _| |_______/ \\__I_I_____===__|_________|
|
||||||
|
|(_)--- | H\\________/ | | =|___ ___| _________________ _________________ _________________
|
||||||
|
/ | | H | | | | ||_| |_|| _| \\_____| \\_____| \\_____A
|
||||||
|
| | | H |__--------------------| [___] | =| | | |
|
||||||
|
| ________|___H__/__|_____/[][]~\\_______| | -| | | |
|
||||||
|
|/ | |-----------I_____I [][] [] D |=======|____|________________________|_______________________|_______________________|_
|
||||||
|
__/ =| o |=-~~\\ /~~\\ /~~\\ /~~\\ ____Y___________|__|__________________________|_______________________|_______________________|
|
||||||
|
|/-=|___|= || || || |_____/~\\___/ |_D__D__D_| |_D__D__D_| |_D__D__D_| |_D__D__D_| |_D__D__D_| |_D__D__D_|
|
||||||
|
\\_/ \\O=====O=====O=====O_/ \\_/ \\_/ \\_/ \\_/ \\_/ \\_/ \\_/ \\_/ \\_/ \\_/ \\_/ \\_/ \\_/`;
|
||||||
|
|
||||||
|
export default function SlBackground() {
|
||||||
|
const [pos, setPos] = useState(150);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
let animationFrameId: number;
|
||||||
|
let lastTime = 0;
|
||||||
|
|
||||||
|
const animate = (timestamp: number) => {
|
||||||
|
if (!lastTime) lastTime = timestamp;
|
||||||
|
const delta = timestamp - lastTime;
|
||||||
|
|
||||||
|
// Update position every ~30ms to create a retro sliding terminal refresh feel
|
||||||
|
if (delta > 30) {
|
||||||
|
setPos((prev) => {
|
||||||
|
if (prev <= -150) return 150;
|
||||||
|
return prev - 1.5;
|
||||||
|
});
|
||||||
|
lastTime = timestamp;
|
||||||
|
}
|
||||||
|
animationFrameId = requestAnimationFrame(animate);
|
||||||
|
};
|
||||||
|
|
||||||
|
animationFrameId = requestAnimationFrame(animate);
|
||||||
|
return () => cancelAnimationFrame(animationFrameId);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<style>
|
||||||
|
{`
|
||||||
|
@keyframes rainbowShift {
|
||||||
|
0% { background-position: 0% 50%; }
|
||||||
|
50% { background-position: 100% 50%; }
|
||||||
|
100% { background-position: 0% 50%; }
|
||||||
|
}
|
||||||
|
`}
|
||||||
|
</style>
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
position: "fixed",
|
||||||
|
top: 0,
|
||||||
|
left: 0,
|
||||||
|
width: "100%",
|
||||||
|
height: "100%",
|
||||||
|
pointerEvents: "none",
|
||||||
|
zIndex: 0,
|
||||||
|
overflow: "hidden",
|
||||||
|
display: "flex",
|
||||||
|
alignItems: "flex-end",
|
||||||
|
paddingBottom: "5vh",
|
||||||
|
justifyContent: "center",
|
||||||
|
userSelect: "none",
|
||||||
|
backgroundImage: "linear-gradient(rgba(29, 32, 33, 0.94), rgba(29, 32, 33, 0.94)), url('/gruvbox.png')",
|
||||||
|
backgroundSize: "cover",
|
||||||
|
backgroundPosition: "center",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<pre
|
||||||
|
style={{
|
||||||
|
margin: 0,
|
||||||
|
whiteSpace: "pre",
|
||||||
|
fontFamily: "var(--font-custom-mono), monospace",
|
||||||
|
fontSize: "1rem",
|
||||||
|
transform: "translateX(" + pos + "vw)",
|
||||||
|
willChange: "transform",
|
||||||
|
background: "linear-gradient(90deg, var(--red), var(--accent2), var(--accent), var(--green), var(--aqua), var(--blue), var(--purple))",
|
||||||
|
backgroundSize: "200% auto",
|
||||||
|
WebkitBackgroundClip: "text",
|
||||||
|
WebkitTextFillColor: "transparent",
|
||||||
|
animation: "rainbowShift 3s linear infinite",
|
||||||
|
opacity: 0.35,
|
||||||
|
filter: "drop-shadow(0 0 12px rgba(250, 189, 47, 0.5))",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{TRAIN_ASCII}
|
||||||
|
</pre>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
710
src/app/darecek/darecek.css
Normal file
710
src/app/darecek/darecek.css
Normal file
|
|
@ -0,0 +1,710 @@
|
||||||
|
/* Valentine's Page for Kiki */
|
||||||
|
|
||||||
|
.kiki-container {
|
||||||
|
min-height: 100vh;
|
||||||
|
width: 100%;
|
||||||
|
background: linear-gradient(
|
||||||
|
135deg,
|
||||||
|
#1a1a2e 0%,
|
||||||
|
#16213e 25%,
|
||||||
|
#1a1a2e 50%,
|
||||||
|
#2d132c 75%,
|
||||||
|
#1a1a2e 100%
|
||||||
|
);
|
||||||
|
background-size: 400% 400%;
|
||||||
|
animation: gradientBG 15s ease infinite;
|
||||||
|
position: relative;
|
||||||
|
overflow: hidden;
|
||||||
|
font-family: "Inter", "Segoe UI", system-ui, sans-serif;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes gradientBG {
|
||||||
|
0% { background-position: 0% 50%; }
|
||||||
|
50% { background-position: 100% 50%; }
|
||||||
|
100% { background-position: 0% 50%; }
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Floating Hearts */
|
||||||
|
.hearts-container {
|
||||||
|
position: fixed;
|
||||||
|
inset: 0;
|
||||||
|
pointer-events: none;
|
||||||
|
overflow: hidden;
|
||||||
|
z-index: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.floating-heart {
|
||||||
|
position: absolute;
|
||||||
|
bottom: -50px;
|
||||||
|
animation: floatUp linear infinite;
|
||||||
|
opacity: 0.7;
|
||||||
|
filter: drop-shadow(0 0 10px rgba(255, 105, 180, 0.5));
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes floatUp {
|
||||||
|
0% {
|
||||||
|
transform: translateY(0) rotate(0deg) scale(1);
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
10% {
|
||||||
|
opacity: 0.7;
|
||||||
|
}
|
||||||
|
90% {
|
||||||
|
opacity: 0.7;
|
||||||
|
}
|
||||||
|
100% {
|
||||||
|
transform: translateY(-110vh) rotate(720deg) scale(0.5);
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Sparkles */
|
||||||
|
.sparkles {
|
||||||
|
position: fixed;
|
||||||
|
inset: 0;
|
||||||
|
pointer-events: none;
|
||||||
|
z-index: 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sparkle {
|
||||||
|
position: absolute;
|
||||||
|
width: 6px;
|
||||||
|
height: 6px;
|
||||||
|
background: white;
|
||||||
|
border-radius: 50%;
|
||||||
|
animation: sparkle 3s ease-in-out infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes sparkle {
|
||||||
|
0%, 100% {
|
||||||
|
opacity: 0;
|
||||||
|
transform: scale(0);
|
||||||
|
}
|
||||||
|
50% {
|
||||||
|
opacity: 1;
|
||||||
|
transform: scale(1);
|
||||||
|
box-shadow:
|
||||||
|
0 0 10px #fff,
|
||||||
|
0 0 20px #ff69b4,
|
||||||
|
0 0 30px #ff69b4;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Rose Petals */
|
||||||
|
.petals-container {
|
||||||
|
position: fixed;
|
||||||
|
inset: 0;
|
||||||
|
pointer-events: none;
|
||||||
|
overflow: hidden;
|
||||||
|
z-index: 3;
|
||||||
|
}
|
||||||
|
|
||||||
|
.petal {
|
||||||
|
position: absolute;
|
||||||
|
top: -50px;
|
||||||
|
font-size: 24px;
|
||||||
|
animation: fallPetal linear infinite;
|
||||||
|
filter: drop-shadow(0 0 5px rgba(255, 182, 193, 0.5));
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes fallPetal {
|
||||||
|
0% {
|
||||||
|
transform: translateY(0) rotate(0deg) translateX(0);
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
10% {
|
||||||
|
opacity: 0.8;
|
||||||
|
}
|
||||||
|
90% {
|
||||||
|
opacity: 0.8;
|
||||||
|
}
|
||||||
|
100% {
|
||||||
|
transform: translateY(110vh) rotate(360deg) translateX(100px);
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Main Content */
|
||||||
|
.kiki-main {
|
||||||
|
position: relative;
|
||||||
|
z-index: 10;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
padding: 60px 20px;
|
||||||
|
min-height: 100vh;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Title */
|
||||||
|
.kiki-title {
|
||||||
|
font-size: clamp(2.5rem, 8vw, 5rem);
|
||||||
|
font-weight: 900;
|
||||||
|
text-align: center;
|
||||||
|
background: linear-gradient(
|
||||||
|
135deg,
|
||||||
|
#ff6b9d 0%,
|
||||||
|
#ff8fab 20%,
|
||||||
|
#ffc3d8 40%,
|
||||||
|
#ff8fab 60%,
|
||||||
|
#ff6b9d 80%,
|
||||||
|
#e84a8a 100%
|
||||||
|
);
|
||||||
|
background-size: 300% 300%;
|
||||||
|
-webkit-background-clip: text;
|
||||||
|
-webkit-text-fill-color: transparent;
|
||||||
|
background-clip: text;
|
||||||
|
animation: titleGradient 5s ease-in-out infinite;
|
||||||
|
margin-bottom: 0.5rem;
|
||||||
|
opacity: 0;
|
||||||
|
transform: translateY(30px);
|
||||||
|
transition: all 1s cubic-bezier(0.34, 1.56, 0.64, 1);
|
||||||
|
filter: drop-shadow(0 0 30px rgba(255, 107, 157, 0.5));
|
||||||
|
}
|
||||||
|
|
||||||
|
.kiki-title.show {
|
||||||
|
opacity: 1;
|
||||||
|
transform: translateY(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes titleGradient {
|
||||||
|
0%, 100% { background-position: 0% 50%; }
|
||||||
|
50% { background-position: 100% 50%; }
|
||||||
|
}
|
||||||
|
|
||||||
|
.kiki-subtitle {
|
||||||
|
font-size: clamp(2rem, 6vw, 4rem);
|
||||||
|
font-weight: 800;
|
||||||
|
color: #ffc3d8;
|
||||||
|
text-align: center;
|
||||||
|
margin-bottom: 3rem;
|
||||||
|
opacity: 0;
|
||||||
|
transform: translateY(30px);
|
||||||
|
transition: all 1s cubic-bezier(0.34, 1.56, 0.64, 1) 0.2s;
|
||||||
|
text-shadow:
|
||||||
|
0 0 20px rgba(255, 195, 216, 0.6),
|
||||||
|
0 0 40px rgba(255, 107, 157, 0.4),
|
||||||
|
0 0 60px rgba(255, 107, 157, 0.2);
|
||||||
|
animation: subtitlePulse 2s ease-in-out infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
.kiki-subtitle.show {
|
||||||
|
opacity: 1;
|
||||||
|
transform: translateY(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes subtitlePulse {
|
||||||
|
0%, 100% {
|
||||||
|
text-shadow:
|
||||||
|
0 0 20px rgba(255, 195, 216, 0.6),
|
||||||
|
0 0 40px rgba(255, 107, 157, 0.4),
|
||||||
|
0 0 60px rgba(255, 107, 157, 0.2);
|
||||||
|
}
|
||||||
|
50% {
|
||||||
|
text-shadow:
|
||||||
|
0 0 30px rgba(255, 195, 216, 0.8),
|
||||||
|
0 0 60px rgba(255, 107, 157, 0.6),
|
||||||
|
0 0 90px rgba(255, 107, 157, 0.4);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Photo Frame */
|
||||||
|
.photo-frame {
|
||||||
|
width: 100%;
|
||||||
|
max-width: 400px;
|
||||||
|
margin: 0 auto 3rem;
|
||||||
|
opacity: 0;
|
||||||
|
transform: scale(0.8) rotateY(-10deg);
|
||||||
|
transition: all 1.2s cubic-bezier(0.34, 1.56, 0.64, 1) 0.4s;
|
||||||
|
perspective: 1000px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.photo-frame.show {
|
||||||
|
opacity: 1;
|
||||||
|
transform: scale(1) rotateY(0deg);
|
||||||
|
}
|
||||||
|
|
||||||
|
.photo-inner {
|
||||||
|
background: linear-gradient(145deg, #ff6b9d, #ff8fab, #ffc3d8);
|
||||||
|
padding: 12px;
|
||||||
|
border-radius: 24px;
|
||||||
|
box-shadow:
|
||||||
|
0 25px 80px rgba(255, 107, 157, 0.4),
|
||||||
|
0 10px 30px rgba(0, 0, 0, 0.3),
|
||||||
|
inset 0 2px 0 rgba(255, 255, 255, 0.3);
|
||||||
|
overflow: hidden;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.photo-inner::before {
|
||||||
|
content: "";
|
||||||
|
position: absolute;
|
||||||
|
inset: 0;
|
||||||
|
background: linear-gradient(
|
||||||
|
135deg,
|
||||||
|
rgba(255, 255, 255, 0.2) 0%,
|
||||||
|
transparent 50%
|
||||||
|
);
|
||||||
|
pointer-events: none;
|
||||||
|
border-radius: 24px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.photo-slider {
|
||||||
|
display: flex;
|
||||||
|
transition: transform 0.8s cubic-bezier(0.4, 0, 0.2, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.photo-slide {
|
||||||
|
min-width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.photo {
|
||||||
|
width: 100%;
|
||||||
|
height: 350px;
|
||||||
|
object-fit: cover;
|
||||||
|
border-radius: 16px;
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.photo-dots {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
gap: 10px;
|
||||||
|
margin-top: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.photo-dot {
|
||||||
|
width: 12px;
|
||||||
|
height: 12px;
|
||||||
|
border-radius: 50%;
|
||||||
|
border: none;
|
||||||
|
background: rgba(255, 107, 157, 0.3);
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.photo-dot:hover {
|
||||||
|
background: rgba(255, 107, 157, 0.6);
|
||||||
|
transform: scale(1.2);
|
||||||
|
}
|
||||||
|
|
||||||
|
.photo-dot.active {
|
||||||
|
background: #ff6b9d;
|
||||||
|
box-shadow: 0 0 15px rgba(255, 107, 157, 0.8);
|
||||||
|
transform: scale(1.3);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Love Letter */
|
||||||
|
.love-letter {
|
||||||
|
width: 100%;
|
||||||
|
max-width: 600px;
|
||||||
|
margin: 0 auto 3rem;
|
||||||
|
opacity: 0;
|
||||||
|
transform: translateY(50px) rotateX(-10deg);
|
||||||
|
transition: all 1.2s cubic-bezier(0.34, 1.56, 0.64, 1) 0.6s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.love-letter.show {
|
||||||
|
opacity: 1;
|
||||||
|
transform: translateY(0) rotateX(0deg);
|
||||||
|
}
|
||||||
|
|
||||||
|
.letter-content {
|
||||||
|
background: linear-gradient(
|
||||||
|
145deg,
|
||||||
|
rgba(255, 255, 255, 0.08),
|
||||||
|
rgba(255, 255, 255, 0.03)
|
||||||
|
);
|
||||||
|
backdrop-filter: blur(20px);
|
||||||
|
border: 1px solid rgba(255, 107, 157, 0.3);
|
||||||
|
border-radius: 24px;
|
||||||
|
padding: 2.5rem;
|
||||||
|
box-shadow:
|
||||||
|
0 20px 60px rgba(0, 0, 0, 0.3),
|
||||||
|
0 0 40px rgba(255, 107, 157, 0.15),
|
||||||
|
inset 0 1px 0 rgba(255, 255, 255, 0.1);
|
||||||
|
position: relative;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.letter-content::before {
|
||||||
|
content: "💌";
|
||||||
|
position: absolute;
|
||||||
|
top: -10px;
|
||||||
|
right: -10px;
|
||||||
|
font-size: 80px;
|
||||||
|
opacity: 0.1;
|
||||||
|
transform: rotate(15deg);
|
||||||
|
}
|
||||||
|
|
||||||
|
.letter-greeting {
|
||||||
|
font-size: 1.5rem;
|
||||||
|
font-weight: 700;
|
||||||
|
color: #ff8fab;
|
||||||
|
margin-bottom: 1.5rem;
|
||||||
|
font-style: italic;
|
||||||
|
}
|
||||||
|
|
||||||
|
.letter-text {
|
||||||
|
color: #fff;
|
||||||
|
font-size: 1.1rem;
|
||||||
|
line-height: 1.8;
|
||||||
|
margin-bottom: 1.2rem;
|
||||||
|
text-shadow: 0 2px 10px rgba(0, 0, 0, 0.3);
|
||||||
|
}
|
||||||
|
|
||||||
|
.letter-signature {
|
||||||
|
color: #ffc3d8;
|
||||||
|
font-size: 1.2rem;
|
||||||
|
text-align: right;
|
||||||
|
margin-top: 2rem;
|
||||||
|
font-style: italic;
|
||||||
|
line-height: 1.6;
|
||||||
|
}
|
||||||
|
|
||||||
|
.signature-name {
|
||||||
|
font-weight: 700;
|
||||||
|
font-size: 1.4rem;
|
||||||
|
background: linear-gradient(135deg, #ff6b9d, #ffc3d8);
|
||||||
|
-webkit-background-clip: text;
|
||||||
|
-webkit-text-fill-color: transparent;
|
||||||
|
background-clip: text;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Big Heart */
|
||||||
|
.big-heart {
|
||||||
|
margin: 2rem 0;
|
||||||
|
opacity: 0;
|
||||||
|
transform: scale(0);
|
||||||
|
transition: all 1s cubic-bezier(0.34, 1.56, 0.64, 1) 0.8s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.big-heart.show {
|
||||||
|
opacity: 1;
|
||||||
|
transform: scale(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.heart-pulse {
|
||||||
|
font-size: 100px;
|
||||||
|
animation: heartBeat 1.5s ease-in-out infinite;
|
||||||
|
filter: drop-shadow(0 0 30px rgba(255, 107, 157, 0.6));
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes heartBeat {
|
||||||
|
0%, 100% {
|
||||||
|
transform: scale(1);
|
||||||
|
}
|
||||||
|
10% {
|
||||||
|
transform: scale(1.15);
|
||||||
|
}
|
||||||
|
20% {
|
||||||
|
transform: scale(1);
|
||||||
|
}
|
||||||
|
30% {
|
||||||
|
transform: scale(1.15);
|
||||||
|
}
|
||||||
|
40% {
|
||||||
|
transform: scale(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Cute Messages */
|
||||||
|
.cute-messages {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
justify-content: center;
|
||||||
|
gap: 1rem;
|
||||||
|
margin-top: 2rem;
|
||||||
|
opacity: 0;
|
||||||
|
transform: translateY(30px);
|
||||||
|
transition: all 1s ease 1s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cute-messages.show {
|
||||||
|
opacity: 1;
|
||||||
|
transform: translateY(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
.cute-msg {
|
||||||
|
background: linear-gradient(135deg, rgba(255, 107, 157, 0.3), rgba(255, 143, 171, 0.2));
|
||||||
|
backdrop-filter: blur(10px);
|
||||||
|
padding: 12px 24px;
|
||||||
|
border-radius: 999px;
|
||||||
|
color: #ffc3d8;
|
||||||
|
font-weight: 600;
|
||||||
|
font-size: 0.95rem;
|
||||||
|
border: 1px solid rgba(255, 107, 157, 0.3);
|
||||||
|
animation: floatMsg 3s ease-in-out infinite;
|
||||||
|
box-shadow: 0 5px 20px rgba(255, 107, 157, 0.2);
|
||||||
|
}
|
||||||
|
|
||||||
|
.cute-msg:nth-child(1) { animation-delay: 0s; }
|
||||||
|
.cute-msg:nth-child(2) { animation-delay: 0.5s; }
|
||||||
|
.cute-msg:nth-child(3) { animation-delay: 1s; }
|
||||||
|
|
||||||
|
@keyframes floatMsg {
|
||||||
|
0%, 100% { transform: translateY(0); }
|
||||||
|
50% { transform: translateY(-10px); }
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Responsive */
|
||||||
|
@media (max-width: 600px) {
|
||||||
|
.kiki-main {
|
||||||
|
padding: 40px 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.photo {
|
||||||
|
height: 280px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.letter-content {
|
||||||
|
padding: 1.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.letter-text {
|
||||||
|
font-size: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.heart-pulse {
|
||||||
|
font-size: 70px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cute-messages {
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Extra glow effect overlay */
|
||||||
|
.kiki-container::before {
|
||||||
|
content: "";
|
||||||
|
position: fixed;
|
||||||
|
top: 30%;
|
||||||
|
left: 50%;
|
||||||
|
transform: translateX(-50%);
|
||||||
|
width: 600px;
|
||||||
|
height: 600px;
|
||||||
|
background: radial-gradient(circle, rgba(255, 107, 157, 0.15) 0%, transparent 70%);
|
||||||
|
pointer-events: none;
|
||||||
|
z-index: 0;
|
||||||
|
animation: glowPulse 4s ease-in-out infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes glowPulse {
|
||||||
|
0%, 100% { opacity: 0.5; transform: translateX(-50%) scale(1); }
|
||||||
|
50% { opacity: 1; transform: translateX(-50%) scale(1.2); }
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Question Screen */
|
||||||
|
.question-screen {
|
||||||
|
position: relative;
|
||||||
|
z-index: 10;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
min-height: 100vh;
|
||||||
|
padding: 40px 20px;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.question-heart {
|
||||||
|
font-size: 75px;
|
||||||
|
animation: heartBounce 2s ease infinite;
|
||||||
|
filter: drop-shadow(0 0 40px rgba(255, 107, 157, 0.6));
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes heartBounce {
|
||||||
|
0%, 100% { transform: scale(1) rotate(-5deg); }
|
||||||
|
25% { transform: scale(1.1) rotate(5deg); }
|
||||||
|
50% { transform: scale(1) rotate(-5deg); }
|
||||||
|
75% { transform: scale(1.15) rotate(5deg); }
|
||||||
|
}
|
||||||
|
|
||||||
|
.question-title {
|
||||||
|
font-size: clamp(2rem, 6vw, 3.5rem);
|
||||||
|
font-weight: 800;
|
||||||
|
color: #ffc3d8;
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
text-shadow:
|
||||||
|
0 0 20px rgba(255, 195, 216, 0.6),
|
||||||
|
0 0 40px rgba(255, 107, 157, 0.4);
|
||||||
|
animation: fadeInDown 1s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes fadeInDown {
|
||||||
|
from {
|
||||||
|
opacity: 0;
|
||||||
|
transform: translateY(-30px);
|
||||||
|
}
|
||||||
|
to {
|
||||||
|
opacity: 1;
|
||||||
|
transform: translateY(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.question-text {
|
||||||
|
font-size: clamp(1.8rem, 5vw, 3rem);
|
||||||
|
font-weight: 900;
|
||||||
|
background: linear-gradient(
|
||||||
|
135deg,
|
||||||
|
#ff6b9d 0%,
|
||||||
|
#ff8fab 25%,
|
||||||
|
#ffc3d8 50%,
|
||||||
|
#ff8fab 75%,
|
||||||
|
#ff6b9d 100%
|
||||||
|
);
|
||||||
|
background-size: 300% 300%;
|
||||||
|
-webkit-background-clip: text;
|
||||||
|
-webkit-text-fill-color: transparent;
|
||||||
|
background-clip: text;
|
||||||
|
animation: titleGradient 4s ease-in-out infinite, fadeInUp 1s ease 0.3s both;
|
||||||
|
margin-bottom: 3rem;
|
||||||
|
filter: drop-shadow(0 0 30px rgba(255, 107, 157, 0.5));
|
||||||
|
}
|
||||||
|
|
||||||
|
.question-buttons {
|
||||||
|
display: flex;
|
||||||
|
gap: 2rem;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
position: relative;
|
||||||
|
min-height: 80px;
|
||||||
|
width: 100%;
|
||||||
|
max-width: 500px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.yes-button {
|
||||||
|
padding: 20px 50px;
|
||||||
|
font-size: 1.5rem;
|
||||||
|
font-weight: 700;
|
||||||
|
color: white;
|
||||||
|
background: linear-gradient(135deg, #ff6b9d, #ff8fab, #ff6b9d);
|
||||||
|
background-size: 200% 200%;
|
||||||
|
border: none;
|
||||||
|
border-radius: 999px;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all 0.4s cubic-bezier(0.175, 0.885, 0.32, 1.275);
|
||||||
|
box-shadow:
|
||||||
|
0 10px 40px rgba(255, 107, 157, 0.5),
|
||||||
|
0 5px 15px rgba(0, 0, 0, 0.2),
|
||||||
|
inset 0 2px 0 rgba(255, 255, 255, 0.3);
|
||||||
|
animation: yesButtonPulse 2s ease-in-out infinite, fadeInUp 1s ease 0.5s both;
|
||||||
|
z-index: 10;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes yesButtonPulse {
|
||||||
|
0%, 100% {
|
||||||
|
transform: scale(1);
|
||||||
|
box-shadow:
|
||||||
|
0 10px 40px rgba(255, 107, 157, 0.5),
|
||||||
|
0 5px 15px rgba(0, 0, 0, 0.2),
|
||||||
|
inset 0 2px 0 rgba(255, 255, 255, 0.3);
|
||||||
|
}
|
||||||
|
50% {
|
||||||
|
transform: scale(1.05);
|
||||||
|
box-shadow:
|
||||||
|
0 15px 50px rgba(255, 107, 157, 0.7),
|
||||||
|
0 8px 25px rgba(0, 0, 0, 0.3),
|
||||||
|
inset 0 2px 0 rgba(255, 255, 255, 0.3);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.yes-button:hover {
|
||||||
|
transform: scale(1.15) !important;
|
||||||
|
background-position: 100% 0;
|
||||||
|
box-shadow:
|
||||||
|
0 20px 60px rgba(255, 107, 157, 0.7),
|
||||||
|
0 10px 30px rgba(0, 0, 0, 0.3),
|
||||||
|
inset 0 2px 0 rgba(255, 255, 255, 0.4);
|
||||||
|
}
|
||||||
|
|
||||||
|
.no-button {
|
||||||
|
padding: 15px 35px;
|
||||||
|
font-size: 1.1rem;
|
||||||
|
font-weight: 600;
|
||||||
|
color: #ff8fab;
|
||||||
|
background: rgba(30, 20, 40, 0.95);
|
||||||
|
border: 2px solid rgba(255, 107, 157, 0.5);
|
||||||
|
border-radius: 999px;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all 0.3s cubic-bezier(0.175, 0.885, 0.32, 1.275);
|
||||||
|
backdrop-filter: blur(10px);
|
||||||
|
animation: fadeInUp 1s ease 0.6s both;
|
||||||
|
z-index: 5;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.no-button:hover {
|
||||||
|
background: rgba(50, 30, 60, 0.95);
|
||||||
|
border-color: rgba(255, 107, 157, 0.7);
|
||||||
|
}
|
||||||
|
|
||||||
|
.no-button.runaway {
|
||||||
|
z-index: 99999 !important;
|
||||||
|
animation: popIn 0.25s cubic-bezier(0.175, 0.885, 0.32, 1.275);
|
||||||
|
box-shadow:
|
||||||
|
0 10px 40px rgba(255, 107, 157, 0.5),
|
||||||
|
0 0 20px rgba(255, 107, 157, 0.3),
|
||||||
|
0 0 60px rgba(255, 107, 157, 0.2);
|
||||||
|
background: rgba(30, 20, 40, 0.98);
|
||||||
|
border: 2px solid rgba(255, 107, 157, 0.8);
|
||||||
|
pointer-events: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes popIn {
|
||||||
|
0% {
|
||||||
|
transform: scale(0.3) rotate(-10deg);
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
60% {
|
||||||
|
transform: scale(1.15) rotate(3deg);
|
||||||
|
}
|
||||||
|
100% {
|
||||||
|
transform: scale(1) rotate(0deg);
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.hint-text {
|
||||||
|
margin-top: 2rem;
|
||||||
|
color: #ffc3d8;
|
||||||
|
font-size: 1.1rem;
|
||||||
|
font-style: italic;
|
||||||
|
animation: fadeIn 0.5s ease;
|
||||||
|
text-shadow: 0 2px 10px rgba(0, 0, 0, 0.3);
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes fadeIn {
|
||||||
|
from { opacity: 0; }
|
||||||
|
to { opacity: 1; }
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Make the Yes button grow bigger over time as No keeps running away */
|
||||||
|
.yes-button:active {
|
||||||
|
transform: scale(0.95) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Responsive adjustments for question screen */
|
||||||
|
@media (max-width: 600px) {
|
||||||
|
.question-heart {
|
||||||
|
font-size: 70px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.question-buttons {
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 1.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.yes-button {
|
||||||
|
padding: 18px 45px;
|
||||||
|
font-size: 1.3rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.no-button {
|
||||||
|
padding: 12px 30px;
|
||||||
|
font-size: 1rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
370
src/app/darecek/page.tsx
Normal file
370
src/app/darecek/page.tsx
Normal file
|
|
@ -0,0 +1,370 @@
|
||||||
|
"use client";
|
||||||
|
|
||||||
|
import { useEffect, useState } from "react";
|
||||||
|
import Image from "next/image";
|
||||||
|
import "./darecek.css";
|
||||||
|
|
||||||
|
export default function KikiPage() {
|
||||||
|
const [hasAnswered, setHasAnswered] = useState(false);
|
||||||
|
const [showMessage, setShowMessage] = useState(false);
|
||||||
|
const [currentPhotoIndex, setCurrentPhotoIndex] = useState(0);
|
||||||
|
const [hearts, setHearts] = useState<{ id: number; left: number; delay: number; duration: number; size: number }[]>([]);
|
||||||
|
const [noButtonStyle, setNoButtonStyle] = useState<React.CSSProperties>({});
|
||||||
|
const [noButtonText, setNoButtonText] = useState("ne 😢");
|
||||||
|
const [noHoverCount, setNoHoverCount] = useState(0);
|
||||||
|
const [isRunning, setIsRunning] = useState(false);
|
||||||
|
|
||||||
|
const noButtonTexts = [
|
||||||
|
"ne 😢",
|
||||||
|
"fakt? 🥺",
|
||||||
|
"pjosim? 🥺",
|
||||||
|
"hej 😡",
|
||||||
|
"stop 👿",
|
||||||
|
"nech toooo 😭",
|
||||||
|
"😭😭😭",
|
||||||
|
"budu urazeny 🥺",
|
||||||
|
"grrrrr 👿👿👿",
|
||||||
|
"stejnak nemas na vyber 😝😝😝",
|
||||||
|
];
|
||||||
|
|
||||||
|
// Add your photos here - just put images in /public/darecek/ folder
|
||||||
|
// named photo1.jpg, photo2.jpg, etc.
|
||||||
|
const photos = [
|
||||||
|
"/darecek/photo1.jpg",
|
||||||
|
"/darecek/photo2.jpg",
|
||||||
|
"/darecek/photo3.jpg",
|
||||||
|
"/darecek/photo4.jpg",
|
||||||
|
"/darecek/photo5.jpg",
|
||||||
|
];
|
||||||
|
|
||||||
|
const moveNoButton = (e: React.MouseEvent | React.TouchEvent) => {
|
||||||
|
// Get current mouse/touch position
|
||||||
|
let currentX = 0;
|
||||||
|
let currentY = 0;
|
||||||
|
|
||||||
|
if ('touches' in e) {
|
||||||
|
currentX = e.touches[0].clientX;
|
||||||
|
currentY = e.touches[0].clientY;
|
||||||
|
} else {
|
||||||
|
currentX = e.clientX;
|
||||||
|
currentY = e.clientY;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
const viewportWidth = window.innerWidth;
|
||||||
|
const viewportHeight = window.innerHeight;
|
||||||
|
const buttonWidth = 200;
|
||||||
|
const buttonHeight = 60;
|
||||||
|
const padding = 20;
|
||||||
|
const minDistanceFromMouse = 200; // Minimum distance from mouse
|
||||||
|
|
||||||
|
// Calculate safe bounds
|
||||||
|
const minX = padding;
|
||||||
|
const maxX = viewportWidth - buttonWidth - padding;
|
||||||
|
const minY = padding;
|
||||||
|
const maxY = viewportHeight - buttonHeight - padding;
|
||||||
|
|
||||||
|
let newX: number;
|
||||||
|
let newY: number;
|
||||||
|
let attempts = 0;
|
||||||
|
const maxAttempts = 50;
|
||||||
|
|
||||||
|
// Keep trying until we find a position far enough from mouse
|
||||||
|
do {
|
||||||
|
newX = minX + Math.random() * (maxX - minX);
|
||||||
|
newY = minY + Math.random() * (maxY - minY);
|
||||||
|
attempts++;
|
||||||
|
|
||||||
|
// Calculate distance from mouse
|
||||||
|
const distance = Math.sqrt(
|
||||||
|
Math.pow(newX + buttonWidth / 2 - currentX, 2) +
|
||||||
|
Math.pow(newY + buttonHeight / 2 - currentY, 2)
|
||||||
|
);
|
||||||
|
|
||||||
|
// If far enough from mouse, use this position
|
||||||
|
if (distance > minDistanceFromMouse) break;
|
||||||
|
|
||||||
|
} while (attempts < maxAttempts);
|
||||||
|
|
||||||
|
// If still too close after max attempts, move to opposite side of screen
|
||||||
|
const buttonCenterX = newX + buttonWidth / 2;
|
||||||
|
const buttonCenterY = newY + buttonHeight / 2;
|
||||||
|
const distanceFromMouse = Math.sqrt(
|
||||||
|
Math.pow(buttonCenterX - currentX, 2) +
|
||||||
|
Math.pow(buttonCenterY - currentY, 2)
|
||||||
|
);
|
||||||
|
|
||||||
|
if (distanceFromMouse < minDistanceFromMouse) {
|
||||||
|
// Move to opposite quadrant of the screen
|
||||||
|
if (currentX < viewportWidth / 2) {
|
||||||
|
newX = maxX - Math.random() * (maxX / 3);
|
||||||
|
} else {
|
||||||
|
newX = minX + Math.random() * (maxX / 3);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (currentY < viewportHeight / 2) {
|
||||||
|
newY = maxY - Math.random() * (maxY / 3);
|
||||||
|
} else {
|
||||||
|
newY = minY + Math.random() * (maxY / 3);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensure bounds
|
||||||
|
newX = Math.max(minX, Math.min(maxX, newX));
|
||||||
|
newY = Math.max(minY, Math.min(maxY, newY));
|
||||||
|
|
||||||
|
setNoButtonStyle({
|
||||||
|
position: 'fixed',
|
||||||
|
left: `${newX}px`,
|
||||||
|
top: `${newY}px`,
|
||||||
|
});
|
||||||
|
|
||||||
|
setIsRunning(true);
|
||||||
|
setNoHoverCount(prev => prev + 1);
|
||||||
|
setNoButtonText(noButtonTexts[Math.min(noHoverCount + 1, noButtonTexts.length - 1)]);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleYesClick = () => {
|
||||||
|
setHasAnswered(true);
|
||||||
|
// Show message after transition
|
||||||
|
setTimeout(() => setShowMessage(true), 500);
|
||||||
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
// Generate floating hearts
|
||||||
|
const newHearts = Array.from({ length: 30 }, (_, i) => ({
|
||||||
|
id: i,
|
||||||
|
left: Math.random() * 100,
|
||||||
|
delay: Math.random() * 5,
|
||||||
|
duration: 5 + Math.random() * 10,
|
||||||
|
size: 15 + Math.random() * 25,
|
||||||
|
}));
|
||||||
|
setHearts(newHearts);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!hasAnswered) return;
|
||||||
|
|
||||||
|
// Auto-rotate photos
|
||||||
|
const interval = setInterval(() => {
|
||||||
|
setCurrentPhotoIndex((prev) => (prev + 1) % photos.length);
|
||||||
|
}, 4000);
|
||||||
|
return () => clearInterval(interval);
|
||||||
|
}, [hasAnswered, photos.length]);
|
||||||
|
|
||||||
|
// Question screen
|
||||||
|
if (!hasAnswered) {
|
||||||
|
return (
|
||||||
|
<div className="kiki-container">
|
||||||
|
{/* Floating Hearts Background */}
|
||||||
|
<div className="hearts-container">
|
||||||
|
{hearts.map((heart) => (
|
||||||
|
<div
|
||||||
|
key={heart.id}
|
||||||
|
className="floating-heart"
|
||||||
|
style={{
|
||||||
|
left: `${heart.left}%`,
|
||||||
|
animationDelay: `${heart.delay}s`,
|
||||||
|
animationDuration: `${heart.duration}s`,
|
||||||
|
fontSize: `${heart.size}px`,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
💖
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Sparkles */}
|
||||||
|
<div className="sparkles">
|
||||||
|
{Array.from({ length: 50 }).map((_, i) => (
|
||||||
|
<div
|
||||||
|
key={i}
|
||||||
|
className="sparkle"
|
||||||
|
style={{
|
||||||
|
left: `${Math.random() * 100}%`,
|
||||||
|
top: `${Math.random() * 100}%`,
|
||||||
|
animationDelay: `${Math.random() * 3}s`,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Question Content */}
|
||||||
|
<div className="question-screen">
|
||||||
|
<div className="question-heart">😝</div>
|
||||||
|
<h1 className="question-title">kikiiiii</h1>
|
||||||
|
<h2 className="question-text">budes muj valentyn? ❤️</h2>
|
||||||
|
|
||||||
|
<div className="question-buttons">
|
||||||
|
<button className="yes-button" onClick={handleYesClick}>
|
||||||
|
jo 😘
|
||||||
|
</button>
|
||||||
|
{!isRunning && (
|
||||||
|
<button
|
||||||
|
className="no-button"
|
||||||
|
onMouseEnter={moveNoButton}
|
||||||
|
onTouchStart={moveNoButton}
|
||||||
|
onClick={moveNoButton}
|
||||||
|
>
|
||||||
|
{noButtonText}
|
||||||
|
</button>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{noHoverCount > 3 && (
|
||||||
|
<p className="hint-text">
|
||||||
|
{noHoverCount > 6 ? "dej ano tele 😛" : "to tlacitko ti zdrha 😝😝😝"}
|
||||||
|
</p>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Runaway No Button - rendered outside the question-buttons container */}
|
||||||
|
{isRunning && (
|
||||||
|
<button
|
||||||
|
className="no-button runaway"
|
||||||
|
style={noButtonStyle}
|
||||||
|
onMouseEnter={moveNoButton}
|
||||||
|
onTouchStart={moveNoButton}
|
||||||
|
onClick={moveNoButton}
|
||||||
|
>
|
||||||
|
{noButtonText}
|
||||||
|
</button>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="kiki-container">
|
||||||
|
{/* Floating Hearts Background */}
|
||||||
|
<div className="hearts-container">
|
||||||
|
{hearts.map((heart) => (
|
||||||
|
<div
|
||||||
|
key={heart.id}
|
||||||
|
className="floating-heart"
|
||||||
|
style={{
|
||||||
|
left: `${heart.left}%`,
|
||||||
|
animationDelay: `${heart.delay}s`,
|
||||||
|
animationDuration: `${heart.duration}s`,
|
||||||
|
fontSize: `${heart.size}px`,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
💘
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Sparkles */}
|
||||||
|
<div className="sparkles">
|
||||||
|
{Array.from({ length: 50 }).map((_, i) => (
|
||||||
|
<div
|
||||||
|
key={i}
|
||||||
|
className="sparkle"
|
||||||
|
style={{
|
||||||
|
left: `${Math.random() * 100}%`,
|
||||||
|
top: `${Math.random() * 100}%`,
|
||||||
|
animationDelay: `${Math.random() * 3}s`,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Main Content */}
|
||||||
|
<main className="kiki-main">
|
||||||
|
{/* Title */}
|
||||||
|
<h1 className={`kiki-title ${showMessage ? "show" : ""}`}>
|
||||||
|
stastnej valentynnnn
|
||||||
|
</h1>
|
||||||
|
|
||||||
|
<h2 className={`kiki-subtitle ${showMessage ? "show" : ""}`}>
|
||||||
|
laskoooo 😘😘😝
|
||||||
|
</h2>
|
||||||
|
|
||||||
|
{/* Photo Gallery */}
|
||||||
|
<div className={`photo-frame ${showMessage ? "show" : ""}`}>
|
||||||
|
<div className="photo-inner">
|
||||||
|
<div
|
||||||
|
className="photo-slider"
|
||||||
|
style={{ transform: `translateX(-${currentPhotoIndex * 100}%)` }}
|
||||||
|
>
|
||||||
|
{photos.map((photo, index) => (
|
||||||
|
<div key={index} className="photo-slide">
|
||||||
|
<Image
|
||||||
|
src={photo}
|
||||||
|
alt={`Memory ${index + 1}`}
|
||||||
|
className="photo"
|
||||||
|
width={500}
|
||||||
|
height={500}
|
||||||
|
onError={(e) => {
|
||||||
|
// Fallback if image doesn't exist
|
||||||
|
(e.target as HTMLImageElement).src = "data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='300' height='300' viewBox='0 0 300 300'%3E%3Crect fill='%23ff6b9d' width='300' height='300'/%3E%3Ctext x='150' y='150' text-anchor='middle' dy='.3em' fill='white' font-size='60'%3E💕%3C/text%3E%3C/svg%3E";
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="photo-dots">
|
||||||
|
{photos.map((_, index) => (
|
||||||
|
<button
|
||||||
|
key={index}
|
||||||
|
className={`photo-dot ${index === currentPhotoIndex ? "active" : ""}`}
|
||||||
|
onClick={() => setCurrentPhotoIndex(index)}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Love Letter */}
|
||||||
|
<div className={`love-letter ${showMessage ? "show" : ""}`}>
|
||||||
|
<div className="letter-content">
|
||||||
|
<p className="letter-greeting">pro moji princeznu,</p>
|
||||||
|
<p className="letter-text">
|
||||||
|
tohle je nas druhej valentyn a jsem moc moc stastnej, ze ho muzu stravit s tebou 😘
|
||||||
|
</p>
|
||||||
|
<p className="letter-text">
|
||||||
|
si ta nejlepsi, nejkrasnejsi a nejroztomilejsi holka na svete, kterou bych si kdy mohl prat, a jsem vdecnej, ze si to semnou vydrzela, i pres ty moje nalady 🥺😝❤️
|
||||||
|
</p>
|
||||||
|
<p className="letter-text">
|
||||||
|
tak strasne moc te milujuuuu a chci byt s tebou pro zbytek svyho zivota, lasko moje ❤️🥹
|
||||||
|
</p>
|
||||||
|
<p className="letter-signature">
|
||||||
|
tvoje laska,<br />
|
||||||
|
<span className="signature-name">Martin ❤️</span>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Animated Heart */}
|
||||||
|
<div className={`big-heart ${showMessage ? "show" : ""}`}>
|
||||||
|
<div className="heart-pulse">❤️</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Cute Messages */}
|
||||||
|
<div className={`cute-messages ${showMessage ? "show" : ""}`}>
|
||||||
|
<span className="cute-msg">jsi moje 👸🏽</span>
|
||||||
|
<span className="cute-msg">miluju te nejvic ve vesmiru 😝</span>
|
||||||
|
<span className="cute-msg">chci jenom tebee 😍</span>
|
||||||
|
</div>
|
||||||
|
</main>
|
||||||
|
|
||||||
|
{/* Rose petals falling */}
|
||||||
|
<div className="petals-container">
|
||||||
|
{Array.from({ length: 20 }).map((_, i) => (
|
||||||
|
<div
|
||||||
|
key={i}
|
||||||
|
className="petal"
|
||||||
|
style={{
|
||||||
|
left: `${Math.random() * 100}%`,
|
||||||
|
animationDelay: `${Math.random() * 8}s`,
|
||||||
|
animationDuration: `${8 + Math.random() * 7}s`,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
💝
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
BIN
src/app/favicon.ico
Normal file
BIN
src/app/favicon.ico
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 15 KiB |
26
src/app/globals.css
Normal file
26
src/app/globals.css
Normal file
|
|
@ -0,0 +1,26 @@
|
||||||
|
@import "tailwindcss";
|
||||||
|
|
||||||
|
:root {
|
||||||
|
--background: #1d2021;
|
||||||
|
--foreground: #ebdbb2;
|
||||||
|
}
|
||||||
|
|
||||||
|
@theme inline {
|
||||||
|
--color-background: var(--background);
|
||||||
|
--color-foreground: var(--foreground);
|
||||||
|
--font-sans: var(--font-geist-sans);
|
||||||
|
--font-mono: var(--font-geist-mono);
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (prefers-color-scheme: dark) {
|
||||||
|
:root {
|
||||||
|
--background: #1d2021;
|
||||||
|
--foreground: #ebdbb2;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
body {
|
||||||
|
background: var(--background);
|
||||||
|
color: var(--foreground);
|
||||||
|
font-family: "Inter", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
|
||||||
|
}
|
||||||
BIN
src/app/icon.png
Normal file
BIN
src/app/icon.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 857 KiB |
33
src/app/layout.tsx
Normal file
33
src/app/layout.tsx
Normal file
|
|
@ -0,0 +1,33 @@
|
||||||
|
import type { Metadata } from "next";
|
||||||
|
// Removed unused google fonts since we use local Custom Mono
|
||||||
|
import "./globals.css";
|
||||||
|
|
||||||
|
export const metadata: Metadata = {
|
||||||
|
title: "dvorakmortonxd.dev",
|
||||||
|
description: "Martin Dvořák's personal website.",
|
||||||
|
openGraph: {
|
||||||
|
title: "dvorakmortonxd.dev",
|
||||||
|
description: "Martin Dvořák's personal website.",
|
||||||
|
url: "https://dvorakmortonxd.dev",
|
||||||
|
siteName: "dvorakmortonxd.dev",
|
||||||
|
},
|
||||||
|
twitter: {
|
||||||
|
card: "summary_large_image",
|
||||||
|
title: "dvorakmortonxd.dev",
|
||||||
|
description: "Martin Dvořák's personal website.",
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export default function RootLayout({
|
||||||
|
children,
|
||||||
|
}: Readonly<{
|
||||||
|
children: React.ReactNode;
|
||||||
|
}>) {
|
||||||
|
return (
|
||||||
|
<html lang="en">
|
||||||
|
<body className="antialiased">
|
||||||
|
{children}
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
);
|
||||||
|
}
|
||||||
875
src/app/page.css
Normal file
875
src/app/page.css
Normal file
|
|
@ -0,0 +1,875 @@
|
||||||
|
@font-face {
|
||||||
|
font-family: 'Monocraft';
|
||||||
|
src: url('/Monocraft.ttf') format('truetype');
|
||||||
|
font-weight: normal;
|
||||||
|
font-style: normal;
|
||||||
|
}
|
||||||
|
|
||||||
|
@font-face {
|
||||||
|
font-family: 'Monocraft';
|
||||||
|
src: url('/Monocraft-Bold.ttf') format('truetype');
|
||||||
|
font-weight: bold;
|
||||||
|
font-style: normal;
|
||||||
|
}
|
||||||
|
|
||||||
|
@font-face {
|
||||||
|
font-family: 'Monocraft';
|
||||||
|
src: url('/Monocraft-Italic.ttf') format('truetype');
|
||||||
|
font-weight: normal;
|
||||||
|
font-style: italic;
|
||||||
|
}
|
||||||
|
|
||||||
|
:root {
|
||||||
|
/* Gruvbox Dark Theme */
|
||||||
|
--bg: #1d2021;
|
||||||
|
--bg-soft: #282828;
|
||||||
|
--bg-hard: #32302f;
|
||||||
|
--fg: #ebdbb2;
|
||||||
|
--gray: #a89984;
|
||||||
|
--accent: #fabd2f; /* Gruvbox Yellow */
|
||||||
|
--accent2: #fe8019; /* Gruvbox Orange */
|
||||||
|
--accent3: #d79921; /* Gruvbox Dark Yellow */
|
||||||
|
--green: #b8bb26; /* Gruvbox Green */
|
||||||
|
--aqua: #8ec07c; /* Gruvbox Aqua */
|
||||||
|
--blue: #83a598; /* Gruvbox Blue */
|
||||||
|
--purple: #d3869b; /* Gruvbox Purple */
|
||||||
|
--red: #fb4934; /* Gruvbox Red */
|
||||||
|
--darkpink: #ff0043; /* Custom Dark Pink */
|
||||||
|
--font-custom-mono: 'Monocraft', 'JetBrains Mono', 'Fira Code', 'Source Code Pro', 'Courier New', monospace;
|
||||||
|
--card-bg: rgba(40, 40, 40, 0.65);
|
||||||
|
--card-border: rgba(168, 153, 132, 0.35);
|
||||||
|
--tui-border: var(--gray);
|
||||||
|
--tui-border-highlight: var(--accent);
|
||||||
|
--radius: 0px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* RESET */
|
||||||
|
* {
|
||||||
|
box-sizing: border-box;
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
html {
|
||||||
|
scroll-behavior: smooth;
|
||||||
|
}
|
||||||
|
|
||||||
|
body {
|
||||||
|
min-height: 100vh;
|
||||||
|
background: var(--bg);
|
||||||
|
color: var(--fg);
|
||||||
|
font-family: var(--font-custom-mono);
|
||||||
|
-webkit-font-smoothing: antialiased;
|
||||||
|
-moz-osx-font-smoothing: grayscale;
|
||||||
|
overflow-x: hidden;
|
||||||
|
font-size: 14px;
|
||||||
|
line-height: 1.5;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Container */
|
||||||
|
.container {
|
||||||
|
width: 100%;
|
||||||
|
max-width: 1080px;
|
||||||
|
margin: 0 auto;
|
||||||
|
padding: 0 16px;
|
||||||
|
position: relative;
|
||||||
|
z-index: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ============================================
|
||||||
|
HEADER - TUI top bar
|
||||||
|
============================================ */
|
||||||
|
.header {
|
||||||
|
position: sticky;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
width: 100%;
|
||||||
|
z-index: 1000;
|
||||||
|
border-bottom: 1px solid var(--tui-border);
|
||||||
|
background-color: rgba(29, 32, 33, 0.4) !important;
|
||||||
|
backdrop-filter: blur(20px) saturate(150%) !important;
|
||||||
|
-webkit-backdrop-filter: blur(20px) saturate(150%) !important;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.header-center {
|
||||||
|
max-width: 1100px;
|
||||||
|
margin: 0 auto;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
padding: 8px 16px;
|
||||||
|
gap: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.logo {
|
||||||
|
height: 28px;
|
||||||
|
width: 28px;
|
||||||
|
border-radius: 4px;
|
||||||
|
object-fit: cover;
|
||||||
|
border: 1px solid var(--accent);
|
||||||
|
opacity: 0.9;
|
||||||
|
transition: opacity 0.15s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.logo:hover {
|
||||||
|
opacity: 1;
|
||||||
|
box-shadow: 0 0 8px rgba(250, 189, 47, 0.4);
|
||||||
|
}
|
||||||
|
|
||||||
|
.site-name {
|
||||||
|
font-weight: 700;
|
||||||
|
font-size: 1rem;
|
||||||
|
font-family: var(--font-custom-mono), 'JetBrains Mono', monospace;
|
||||||
|
color: var(--accent);
|
||||||
|
letter-spacing: 0.02em;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ============================================
|
||||||
|
HERO - TUI welcome panel
|
||||||
|
============================================ */
|
||||||
|
.hero {
|
||||||
|
text-align: left;
|
||||||
|
padding: 0;
|
||||||
|
position: relative;
|
||||||
|
z-index: 2;
|
||||||
|
margin-top: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hero-panel {
|
||||||
|
border: 1px solid var(--accent);
|
||||||
|
background: rgba(29, 32, 33, 0.75);
|
||||||
|
backdrop-filter: blur(12px);
|
||||||
|
-webkit-backdrop-filter: blur(12px);
|
||||||
|
padding: 20px 24px;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hero-panel-label {
|
||||||
|
position: absolute;
|
||||||
|
top: -1px;
|
||||||
|
left: 16px;
|
||||||
|
transform: translateY(-50%);
|
||||||
|
background: rgba(29, 32, 33, 0.95);
|
||||||
|
padding: 0 8px;
|
||||||
|
font-size: 0.8rem;
|
||||||
|
font-weight: 700;
|
||||||
|
color: var(--accent);
|
||||||
|
letter-spacing: 0.08em;
|
||||||
|
text-transform: uppercase;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hero-title {
|
||||||
|
font-size: 1.6rem;
|
||||||
|
font-weight: 800;
|
||||||
|
color: var(--accent);
|
||||||
|
margin-bottom: 8px;
|
||||||
|
letter-spacing: -0.01em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hero-subtitle {
|
||||||
|
font-size: 0.9rem;
|
||||||
|
color: var(--gray);
|
||||||
|
line-height: 1.6;
|
||||||
|
max-width: none;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.text-green {
|
||||||
|
color: var(--green);
|
||||||
|
}
|
||||||
|
|
||||||
|
.text-blue {
|
||||||
|
color: var(--blue);
|
||||||
|
}
|
||||||
|
|
||||||
|
.mail-contact {
|
||||||
|
text-align: center;
|
||||||
|
margin-bottom: 18px;
|
||||||
|
color: var(--fg);
|
||||||
|
font-size: 0.9rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mail-contact a {
|
||||||
|
color: var(--blue);
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.about-section {
|
||||||
|
margin-top: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.about-content {
|
||||||
|
padding: 20px 24px;
|
||||||
|
color: var(--fg);
|
||||||
|
font-family: var(--font-custom-mono);
|
||||||
|
font-size: 0.9rem;
|
||||||
|
line-height: 1.6;
|
||||||
|
}
|
||||||
|
|
||||||
|
.label-blue { color: var(--blue); }
|
||||||
|
.label-yellow { color: var(--accent); }
|
||||||
|
.label-green { color: var(--green); }
|
||||||
|
|
||||||
|
.cursor-blink {
|
||||||
|
display: inline-block;
|
||||||
|
width: 8px;
|
||||||
|
height: 1.1em;
|
||||||
|
background: var(--accent);
|
||||||
|
margin-left: 4px;
|
||||||
|
vertical-align: text-bottom;
|
||||||
|
animation: cursorBlink 1s step-end infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes cursorBlink {
|
||||||
|
0%, 100% { opacity: 1; }
|
||||||
|
50% { opacity: 0; }
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ============================================
|
||||||
|
SOCIAL BUTTONS - TUI list style
|
||||||
|
============================================ */
|
||||||
|
.social-section {
|
||||||
|
margin-top: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tui-panel {
|
||||||
|
border: 1px solid var(--tui-border);
|
||||||
|
background: rgba(29, 32, 33, 0.75);
|
||||||
|
backdrop-filter: blur(12px);
|
||||||
|
-webkit-backdrop-filter: blur(12px);
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tui-panel-label {
|
||||||
|
position: absolute;
|
||||||
|
top: -1px;
|
||||||
|
left: 16px;
|
||||||
|
transform: translateY(-50%);
|
||||||
|
background: rgba(29, 32, 33, 0.95);
|
||||||
|
padding: 0 8px;
|
||||||
|
font-size: 0.8rem;
|
||||||
|
font-weight: 700;
|
||||||
|
letter-spacing: 0.08em;
|
||||||
|
text-transform: uppercase;
|
||||||
|
}
|
||||||
|
|
||||||
|
.social-buttons {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(auto-fill, minmax(220px, 1fr));
|
||||||
|
gap: 0;
|
||||||
|
padding: 12px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.social-button {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 10px;
|
||||||
|
padding: 6px 20px;
|
||||||
|
background: transparent;
|
||||||
|
border: none;
|
||||||
|
color: var(--fg);
|
||||||
|
text-decoration: none;
|
||||||
|
font-weight: 500;
|
||||||
|
font-size: 0.85rem;
|
||||||
|
font-family: var(--font-custom-mono), 'JetBrains Mono', monospace;
|
||||||
|
transition: all 0.3s cubic-bezier(0.25, 0.8, 0.25, 1);
|
||||||
|
position: relative;
|
||||||
|
border-bottom: 2px solid transparent;
|
||||||
|
border-radius: 0;
|
||||||
|
overflow: hidden;
|
||||||
|
z-index: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.social-button::before {
|
||||||
|
content: '';
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: -100%;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
background: linear-gradient(90deg, transparent, rgba(250, 189, 47, 0.2), transparent);
|
||||||
|
transition: left 0.5s ease;
|
||||||
|
z-index: -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.social-button:hover::before {
|
||||||
|
left: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.social-button:hover {
|
||||||
|
background: rgba(250, 189, 47, 0.08);
|
||||||
|
color: var(--accent);
|
||||||
|
}
|
||||||
|
|
||||||
|
.social-icon {
|
||||||
|
width: 16px;
|
||||||
|
height: 16px;
|
||||||
|
opacity: 0.7;
|
||||||
|
transition: opacity 0.15s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.social-button:hover .social-icon {
|
||||||
|
opacity: 1;
|
||||||
|
filter: drop-shadow(0 0 4px rgba(250, 189, 47, 0.4));
|
||||||
|
}
|
||||||
|
|
||||||
|
.social-label {
|
||||||
|
transition: color 0.15s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ============================================
|
||||||
|
SERVICES SECTION - TUI grid
|
||||||
|
============================================ */
|
||||||
|
.services-section {
|
||||||
|
margin-top: 16px;
|
||||||
|
padding-top: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.services-title {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.services-buttons {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(3, 1fr);
|
||||||
|
gap: 0;
|
||||||
|
padding: 12px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
.service-button {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 12px;
|
||||||
|
padding: 10px 20px;
|
||||||
|
background: transparent;
|
||||||
|
border: none;
|
||||||
|
border-radius: 0;
|
||||||
|
text-decoration: none;
|
||||||
|
color: var(--fg);
|
||||||
|
font-family: var(--font-custom-mono), 'JetBrains Mono', monospace;
|
||||||
|
min-width: 0;
|
||||||
|
position: relative;
|
||||||
|
overflow: hidden;
|
||||||
|
z-index: 1;
|
||||||
|
transition: all 0.3s cubic-bezier(0.25, 0.8, 0.25, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.service-button::before {
|
||||||
|
content: '';
|
||||||
|
position: absolute;
|
||||||
|
top: -100%;
|
||||||
|
left: 0;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
background: linear-gradient(180deg, transparent, rgba(250, 189, 47, 0.15), transparent);
|
||||||
|
transition: top 0.4s ease;
|
||||||
|
z-index: -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.service-button:hover::before {
|
||||||
|
top: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.service-button:last-child {
|
||||||
|
border-bottom: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.service-button:hover {
|
||||||
|
background: rgba(250, 189, 47, 0.08);
|
||||||
|
transform: translateY(-3px);
|
||||||
|
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3);
|
||||||
|
}
|
||||||
|
|
||||||
|
.service-icon {
|
||||||
|
font-size: 1.4rem;
|
||||||
|
transition: transform 0.15s ease;
|
||||||
|
width: 28px;
|
||||||
|
text-align: center;
|
||||||
|
filter: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.service-button:hover .service-icon {
|
||||||
|
transform: scale(1.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.service-button.friend .service-icon {
|
||||||
|
display: grid;
|
||||||
|
place-items: center;
|
||||||
|
width: 56px;
|
||||||
|
height: 56px;
|
||||||
|
border-radius: 50%;
|
||||||
|
overflow: hidden;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.friend-icon {
|
||||||
|
display: block;
|
||||||
|
width: 56px;
|
||||||
|
height: 56px;
|
||||||
|
border-radius: 50%;
|
||||||
|
object-fit: cover;
|
||||||
|
}
|
||||||
|
|
||||||
|
.service-text {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: flex-start;
|
||||||
|
gap: 1px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.service-name {
|
||||||
|
font-size: 0.9rem;
|
||||||
|
font-weight: 700;
|
||||||
|
color: var(--fg);
|
||||||
|
transition: color 0.15s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.service-button.proxmox:hover .service-name { color: var(--blue); }
|
||||||
|
.service-button.ai:hover .service-name { color: var(--green); }
|
||||||
|
.service-button.jellyfin:hover .service-name { color: var(--purple); }
|
||||||
|
.service-button.pterodactyl:hover .service-name { color: var(--red); }
|
||||||
|
.service-button.funkwhale:hover .service-name { color: var(--darkpink); }
|
||||||
|
.service-button.mailcow:hover .service-name { color: var(--accent); }
|
||||||
|
.service-button.searxng:hover .service-name { color: var(--aqua); }
|
||||||
|
.service-button.convertx:hover .service-name { color: var(--gray); }
|
||||||
|
.service-button.gitdxd:hover .service-name { color: var(--purple); }
|
||||||
|
|
||||||
|
.service-button.proxmox { border-color: rgba(168, 153, 132, 0.1); }
|
||||||
|
.service-button.ai { border-color: rgba(168, 153, 132, 0.1); }
|
||||||
|
.service-button.jellyfin { border-color: rgba(168, 153, 132, 0.1); }
|
||||||
|
.service-button.pterodactyl { border-color: rgba(168, 153, 132, 0.1); }
|
||||||
|
.service-button.funkwhale { border-color: rgba(255, 0, 67, 0.1); }
|
||||||
|
.service-button.mailcow { border-color: rgba(168, 153, 132, 0.1); }
|
||||||
|
.service-button.searxng { border-color: rgba(168, 153, 132, 0.1); }
|
||||||
|
.service-button.convertx { border-color: rgba(168, 153, 132, 0.1); }
|
||||||
|
.service-button.gitdxd { border-color: rgba(255, 0, 67, 0.1); }
|
||||||
|
|
||||||
|
.service-desc {
|
||||||
|
font-size: 0.75rem;
|
||||||
|
color: var(--gray);
|
||||||
|
transition: color 0.15s ease;
|
||||||
|
font-family: var(--font-custom-mono), 'JetBrains Mono', monospace;
|
||||||
|
}
|
||||||
|
|
||||||
|
.service-button:hover .service-desc {
|
||||||
|
color: var(--fg);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ============================================
|
||||||
|
PROJECTS - TUI sections
|
||||||
|
============================================ */
|
||||||
|
.projects {
|
||||||
|
margin-top: 16px;
|
||||||
|
padding: 0;
|
||||||
|
text-align: left;
|
||||||
|
position: relative;
|
||||||
|
z-index: 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
.section-title {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.subsection-title {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.subsection-wrapper {
|
||||||
|
margin-top: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.subsection-panel {
|
||||||
|
border: 1px solid var(--tui-border);
|
||||||
|
background: rgba(29, 32, 33, 0.75);
|
||||||
|
backdrop-filter: blur(12px);
|
||||||
|
-webkit-backdrop-filter: blur(12px);
|
||||||
|
position: relative;
|
||||||
|
padding: 20px 16px 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.subsection-panel-label {
|
||||||
|
position: absolute;
|
||||||
|
top: -1px;
|
||||||
|
left: 16px;
|
||||||
|
transform: translateY(-50%);
|
||||||
|
background: rgba(29, 32, 33, 0.95);
|
||||||
|
padding: 0 8px;
|
||||||
|
font-size: 0.8rem;
|
||||||
|
font-weight: 700;
|
||||||
|
color: var(--accent2);
|
||||||
|
letter-spacing: 0.08em;
|
||||||
|
text-transform: uppercase;
|
||||||
|
}
|
||||||
|
|
||||||
|
.subsection-panel.coding-panel .subsection-panel-label {
|
||||||
|
color: var(--purple);
|
||||||
|
}
|
||||||
|
|
||||||
|
.subsection-panel.friends-panel .subsection-panel-label {
|
||||||
|
color: var(--aqua);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Project grid & cards */
|
||||||
|
.project-grid {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(auto-fill, minmax(310px, 1fr));
|
||||||
|
gap: 12px;
|
||||||
|
max-width: none;
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.friends-grid {
|
||||||
|
grid-template-columns: repeat(auto-fill, minmax(45%, 1fr));
|
||||||
|
}
|
||||||
|
|
||||||
|
.video-grid {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(4, 1fr);
|
||||||
|
gap: 16px;
|
||||||
|
max-width: none;
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.project-card {
|
||||||
|
background: rgba(50, 48, 47, 0.35);
|
||||||
|
border: 1px solid rgba(168, 153, 132, 0.18);
|
||||||
|
border-radius: 0;
|
||||||
|
padding: 12px;
|
||||||
|
width: 100%;
|
||||||
|
text-decoration: none;
|
||||||
|
color: inherit;
|
||||||
|
transition: all 0.4s cubic-bezier(0.175, 0.885, 0.32, 1.275);
|
||||||
|
position: relative;
|
||||||
|
overflow: hidden;
|
||||||
|
box-shadow: none;
|
||||||
|
backdrop-filter: none;
|
||||||
|
transform: translateZ(0);
|
||||||
|
will-change: transform;
|
||||||
|
}
|
||||||
|
|
||||||
|
.project-card::after {
|
||||||
|
content: '';
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: -150%;
|
||||||
|
width: 50%;
|
||||||
|
height: 100%;
|
||||||
|
background: linear-gradient(to right, rgba(255,255,255,0) 0%, rgba(255,255,255,0.06) 50%, rgba(255,255,255,0) 100%);
|
||||||
|
transform: skewX(-25deg);
|
||||||
|
transition: all 0.6s ease;
|
||||||
|
z-index: 2;
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.project-card:hover::after {
|
||||||
|
left: 167%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.project-card:hover {
|
||||||
|
border-color: var(--accent);
|
||||||
|
background: rgba(50, 48, 47, 0.6);
|
||||||
|
transform: translateY(-6px) scale(1.01);
|
||||||
|
}
|
||||||
|
|
||||||
|
.project-image {
|
||||||
|
height: 160px;
|
||||||
|
width: 100%;
|
||||||
|
background-size: cover;
|
||||||
|
background-position: center;
|
||||||
|
background-repeat: no-repeat;
|
||||||
|
border-radius: 0;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
border: 1px solid rgba(168, 153, 132, 0.15);
|
||||||
|
transition: all 0.2s ease;
|
||||||
|
box-shadow: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.project-card:hover .project-image {
|
||||||
|
border-color: rgba(250, 189, 47, 0.3);
|
||||||
|
transform: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.project-title {
|
||||||
|
font-size: 0.95rem;
|
||||||
|
font-weight: 700;
|
||||||
|
color: var(--fg);
|
||||||
|
margin-bottom: 4px;
|
||||||
|
font-family: var(--font-custom-mono), 'JetBrains Mono', monospace;
|
||||||
|
transition: color 0.15s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.project-card:hover .project-title {
|
||||||
|
color: var(--accent);
|
||||||
|
text-shadow: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.project-desc {
|
||||||
|
color: var(--gray);
|
||||||
|
font-size: 0.8rem;
|
||||||
|
line-height: 1.5;
|
||||||
|
margin-top: 4px;
|
||||||
|
font-family: var(--font-custom-mono), 'JetBrains Mono', monospace;
|
||||||
|
}
|
||||||
|
|
||||||
|
.project-card:hover .project-desc {
|
||||||
|
color: var(--fg);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Friends section styles */
|
||||||
|
.friend-card-wrapper {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
.friend-header {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
background-color: #3c3836;
|
||||||
|
padding: 4px 8px;
|
||||||
|
border-top-left-radius: 3px;
|
||||||
|
border-top-right-radius: 3px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.window-icon {
|
||||||
|
width: 16px;
|
||||||
|
height: 16px;
|
||||||
|
margin-right: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.friend-iframe-container {
|
||||||
|
flex-grow: 1;
|
||||||
|
overflow: hidden;
|
||||||
|
border: 1px solid var(--card-border);
|
||||||
|
border-top: none;
|
||||||
|
aspect-ratio: 2 / 1;
|
||||||
|
transform: translateZ(0);
|
||||||
|
will-change: transform;
|
||||||
|
}
|
||||||
|
|
||||||
|
.friend-iframe {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
border: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ============================================
|
||||||
|
FOOTER - TUI status bar
|
||||||
|
============================================ */
|
||||||
|
.footer {
|
||||||
|
margin-top: 16px;
|
||||||
|
margin-bottom: 24px;
|
||||||
|
padding: 10px 20px;
|
||||||
|
text-align: center;
|
||||||
|
color: var(--gray);
|
||||||
|
border: 1px solid var(--tui-border);
|
||||||
|
font-size: 0.8rem;
|
||||||
|
font-family: var(--font-custom-mono), 'JetBrains Mono', monospace;
|
||||||
|
position: relative;
|
||||||
|
z-index: 2;
|
||||||
|
background: rgba(29, 32, 33, 0.75);
|
||||||
|
backdrop-filter: blur(12px);
|
||||||
|
-webkit-backdrop-filter: blur(12px);
|
||||||
|
box-shadow: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.footer-label {
|
||||||
|
position: absolute;
|
||||||
|
top: -1px;
|
||||||
|
right: 16px;
|
||||||
|
transform: translateY(-50%);
|
||||||
|
background: rgba(29, 32, 33, 0.95);
|
||||||
|
padding: 0 8px;
|
||||||
|
font-size: 0.7rem;
|
||||||
|
font-weight: 700;
|
||||||
|
color: var(--gray);
|
||||||
|
letter-spacing: 0.08em;
|
||||||
|
text-transform: uppercase;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ============================================
|
||||||
|
ANIMATIONS
|
||||||
|
============================================ */
|
||||||
|
@keyframes fadeIn {
|
||||||
|
from { opacity: 0; }
|
||||||
|
to { opacity: 1; }
|
||||||
|
}
|
||||||
|
|
||||||
|
.hero { animation: fadeIn 0.5s ease both; }
|
||||||
|
.social-section { animation: fadeIn 0.5s ease 0.1s both; }
|
||||||
|
.services-section { animation: fadeIn 0.5s ease 0.2s both; }
|
||||||
|
.projects { animation: fadeIn 0.5s ease 0.3s both; }
|
||||||
|
.footer { animation: fadeIn 0.5s ease 0.4s both; }
|
||||||
|
|
||||||
|
.project-card {
|
||||||
|
opacity: 0;
|
||||||
|
animation: fadeIn 0.3s ease forwards;
|
||||||
|
}
|
||||||
|
|
||||||
|
.project-grid .project-card:nth-child(1) { animation-delay: 0.05s; }
|
||||||
|
.project-grid .project-card:nth-child(2) { animation-delay: 0.1s; }
|
||||||
|
.project-grid .project-card:nth-child(3) { animation-delay: 0.15s; }
|
||||||
|
.project-grid .project-card:nth-child(4) { animation-delay: 0.2s; }
|
||||||
|
.project-grid .project-card:nth-child(5) { animation-delay: 0.25s; }
|
||||||
|
.project-grid .project-card:nth-child(6) { animation-delay: 0.3s; }
|
||||||
|
.project-grid .project-card:nth-child(7) { animation-delay: 0.35s; }
|
||||||
|
.project-grid .project-card:nth-child(8) { animation-delay: 0.4s; }
|
||||||
|
.project-grid .project-card:nth-child(9) { animation-delay: 0.45s; }
|
||||||
|
.project-grid .project-card:nth-child(10) { animation-delay: 0.5s; }
|
||||||
|
.project-grid .project-card:nth-child(11) { animation-delay: 0.55s; }
|
||||||
|
.project-grid .project-card:nth-child(12) { animation-delay: 0.6s; }
|
||||||
|
.project-grid .project-card:nth-child(13) { animation-delay: 0.65s; }
|
||||||
|
.project-grid .project-card:nth-child(14) { animation-delay: 0.7s; }
|
||||||
|
|
||||||
|
/* Custom scrollbar - thin TUI style */
|
||||||
|
::-webkit-scrollbar {
|
||||||
|
width: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
::-webkit-scrollbar-track {
|
||||||
|
background: var(--bg);
|
||||||
|
}
|
||||||
|
|
||||||
|
::-webkit-scrollbar-thumb {
|
||||||
|
background: var(--gray);
|
||||||
|
border: 2px solid var(--bg);
|
||||||
|
}
|
||||||
|
|
||||||
|
::-webkit-scrollbar-thumb:hover {
|
||||||
|
background: var(--accent);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Selection styling */
|
||||||
|
::selection {
|
||||||
|
background: rgba(250, 189, 47, 0.35);
|
||||||
|
color: var(--fg);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ============================================
|
||||||
|
RESPONSIVE
|
||||||
|
============================================ */
|
||||||
|
@media (max-width: 768px) {
|
||||||
|
.container {
|
||||||
|
padding: 0 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hero-panel {
|
||||||
|
padding: 16px 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hero-title {
|
||||||
|
font-size: 1.4rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.project-grid {
|
||||||
|
grid-template-columns: 1fr;
|
||||||
|
gap: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.social-buttons {
|
||||||
|
grid-template-columns: 1fr 1fr;
|
||||||
|
}
|
||||||
|
|
||||||
|
.services-buttons {
|
||||||
|
grid-template-columns: 1fr;
|
||||||
|
}
|
||||||
|
|
||||||
|
.friends-buttons {
|
||||||
|
grid-template-columns: 1fr;
|
||||||
|
}
|
||||||
|
|
||||||
|
.friends-grid {
|
||||||
|
grid-template-columns: 1fr;
|
||||||
|
}
|
||||||
|
|
||||||
|
.video-grid {
|
||||||
|
grid-template-columns: repeat(2, 1fr);
|
||||||
|
gap: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.friend-iframe-container {
|
||||||
|
aspect-ratio: 16 / 9;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 520px) {
|
||||||
|
.container {
|
||||||
|
padding: 0 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hero-title {
|
||||||
|
font-size: 1.2rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.social-buttons {
|
||||||
|
grid-template-columns: 1fr;
|
||||||
|
}
|
||||||
|
|
||||||
|
.project-grid {
|
||||||
|
grid-template-columns: 1fr;
|
||||||
|
}
|
||||||
|
|
||||||
|
.video-grid {
|
||||||
|
grid-template-columns: 1fr;
|
||||||
|
}
|
||||||
|
|
||||||
|
.project-image {
|
||||||
|
height: 130px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.subsection-panel {
|
||||||
|
padding: 12px 8px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Reduced motion for accessibility */
|
||||||
|
@media (prefers-reduced-motion: reduce) {
|
||||||
|
*, *::before, *::after {
|
||||||
|
animation-duration: 0.01ms !important;
|
||||||
|
animation-iteration-count: 1 !important;
|
||||||
|
transition-duration: 0.01ms !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Joker Animation */
|
||||||
|
.joker-flash-overlay {
|
||||||
|
position: fixed;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
width: 100vw;
|
||||||
|
height: 100vh;
|
||||||
|
z-index: 99999;
|
||||||
|
pointer-events: none;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
background: rgba(0, 0, 0, 0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.joker-img {
|
||||||
|
max-width: 90vw;
|
||||||
|
max-height: 90vh;
|
||||||
|
animation: jokerFlash 1.2s cubic-bezier(0.175, 0.885, 0.32, 1.275) forwards;
|
||||||
|
filter: drop-shadow(0 0 50px rgba(255, 0, 67, 0.8));
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes jokerFlash {
|
||||||
|
0% {
|
||||||
|
opacity: 0;
|
||||||
|
transform: scale(0.1);
|
||||||
|
}
|
||||||
|
15% {
|
||||||
|
opacity: 1;
|
||||||
|
transform: scale(2.5) rotate(-5deg);
|
||||||
|
}
|
||||||
|
60% {
|
||||||
|
opacity: 1;
|
||||||
|
transform: scale(1.5) rotate(5deg);
|
||||||
|
}
|
||||||
|
100% {
|
||||||
|
opacity: 0;
|
||||||
|
transform: scale(0) rotate(-20deg);
|
||||||
|
}
|
||||||
|
}
|
||||||
484
src/app/page.tsx
Normal file
484
src/app/page.tsx
Normal file
|
|
@ -0,0 +1,484 @@
|
||||||
|
import Image from "next/image";
|
||||||
|
import "./page.css";
|
||||||
|
import { Metadata } from 'next';
|
||||||
|
import JokerLogo from "./components/JokerLogo";
|
||||||
|
import ServiceButton from "./components/ServiceButton";
|
||||||
|
import SlBackground from "./components/SlBackground";
|
||||||
|
|
||||||
|
export const metadata: Metadata = {
|
||||||
|
title: 'dvorakmortonxd.dev',
|
||||||
|
description:
|
||||||
|
'Welcome to my personal website, my social media links and projects lurk below... I use gentoo btw :)',
|
||||||
|
openGraph: {
|
||||||
|
title: 'dvorakmortonxd.dev',
|
||||||
|
description: 'Welcome to my personal website, my social media links and projects lurk below... I use gentoo btw :)',
|
||||||
|
url: 'https://dvorakmortonxd.dev',
|
||||||
|
siteName: 'dvorakmortonxd.dev',
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
// Example projects array (you can edit this)
|
||||||
|
const codingProjects = [
|
||||||
|
{
|
||||||
|
title: "Hospital Execution",
|
||||||
|
desc: "the first ever real game I made, in pygame.",
|
||||||
|
img: "/coding-1.png",
|
||||||
|
url: "https://martin-dv.itch.io/hospital-execution-demo",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "Underbaranoia",
|
||||||
|
desc: "basically a undertale like infinite fight simulator that gets progressively harder, made in pygame.",
|
||||||
|
img: "/coding-2.png",
|
||||||
|
url: "https://martin-dv.itch.io/underbaranoia",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "Offline memer",
|
||||||
|
desc: "parody of the discord bot \"Dank Memer\" made in batch. (this was technically my first ever game I published).",
|
||||||
|
img: "/coding-3.png",
|
||||||
|
url: "https://martin-dv.itch.io/offline-memer",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "4y",
|
||||||
|
desc: "just a renamed fork of the ly login manager (based login manager btw I love it), no extra features, just a rename, its actually outdated so dont use it.",
|
||||||
|
img: "/coding-4.png",
|
||||||
|
url: "https://git.dvorakmortonxd.dev/dvorakmortonxd/4y",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "4rch linux",
|
||||||
|
desc: "same as above, basically just a renamed fork of arch linux with calamares installer, also outdated and not maintained at all so no use please.",
|
||||||
|
img: "/coding-5.png",
|
||||||
|
url: "https://git.dvorakmortonxd.dev/dvorakmortonxd/4rch-linux",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "You_Are_An_Idiot_Virus.py",
|
||||||
|
desc: "this is just a python script that just spams blinking \"you are an idiot\" windows, made it as a prank I think, not really sure rn.",
|
||||||
|
img: "/coding-6.png",
|
||||||
|
url: "https://git.dvorakmortonxd.dev/dvorakmortonxd/uranidiot",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "Venturesome",
|
||||||
|
desc: "a browser I wrote in python using PyQtWebEngine with PyQt5, actually pretty cool and modular but I couldnt get past the stage of adding a tab system, basically surpassed by my other project: RustyWolf.",
|
||||||
|
img: "/coding-7.png",
|
||||||
|
url: "https://git.dvorakmortonxd.dev/dvorakmortonxd/Venturesome",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "Mandelbrot set in Pygame",
|
||||||
|
desc: "a python pygame script that renders the mandelbrot set, made it when I was fascinated by how it looks and ngl it still looks cool af.",
|
||||||
|
img: "/coding-8.png",
|
||||||
|
url: "https://git.dvorakmortonxd.dev/dvorakmortonxd/mandelbrot",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "Underpygametale",
|
||||||
|
desc: "a remake of undertale written in pygame, got it to about the flowey part (at the beginning) and didnt touch it in a long time.",
|
||||||
|
img: "/coding-9.png",
|
||||||
|
url: "https://git.dvorakmortonxd.dev/dvorakmortonxd/underpygametale",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "Many discord bots...",
|
||||||
|
desc: "so many of them, once I (allegedly, pls discord dont ban me) even wrote a \"self-bot\" which auto-advertised a minecraft server on minecraft discord server in lfg-hosting every two hours, around 800 people joined in total.",
|
||||||
|
img: "/coding-10.png",
|
||||||
|
url: "https://git.dvorakmortonxd.dev/dvorakmortonxd/PYGETEX",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "MMO minecraft plugin",
|
||||||
|
desc: "a minceraft plugin with custom bosses, items, and functions. pretty barebones and basic but I did it all manually before vibecoding was even a thing so what.",
|
||||||
|
img: "/coding-11.png",
|
||||||
|
url: "https://www.youtube.com/watch?v=cSAX5Ds61ac",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "Many many failed minecraft servers..",
|
||||||
|
desc: "creative games, softcraft, frostville, eusmp, eurthsmp, softsmp and many other SMPs that died later on. I cant even count how many mc servers I had in total.",
|
||||||
|
img: "/coding-12.png",
|
||||||
|
url: "https://discord.gg/KtHRm9kRmy",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "Swords and Sandals 2 pygame remake",
|
||||||
|
desc: "an unfinished remake of swords and sandals 2 in pygame, I got bored of it and never finished it but ill finish it one day for sure lol.",
|
||||||
|
img: "/coding-13.png",
|
||||||
|
url: "https://git.dvorakmortonxd.dev/dvorakmortonxd/sas2py",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "Ollama server",
|
||||||
|
desc: "my ollama vm running on my proxmox with a gpu passthroughed RTX 3070, dm me if u wanna test it lol.",
|
||||||
|
img: "/coding-14.png",
|
||||||
|
url: "https://ai.dvorakmortonxd.dev/",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "Batch MSDOS Simulator",
|
||||||
|
desc: "a freshman school project on making games in batch, it was like an OS simulator with a web browser, games, photos, even a video suprise (youll have to try it to figure that one out lol).",
|
||||||
|
img: "/coding-15.png",
|
||||||
|
url: "https://git.dvorakmortonxd.dev/dvorakmortonxd/DvorakMSDOS",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "RustyWolf",
|
||||||
|
desc: "a browser I made in rust with webkitgtk, I made it to be lightweight and to be able to be ran on macbook unibody 2010 before I realised I got scammed.. It's the successor to my previous project Venturesome :)",
|
||||||
|
img: "/coding-16.png",
|
||||||
|
url: "https://git.dvorakmortonxd.dev/dvorakmortonxd/RustyWolf",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "dxdwm",
|
||||||
|
desc: "a small x11 window manager written in rust.",
|
||||||
|
img: "/coding-17.png",
|
||||||
|
url: "https://git.dvorakmortonxd.dev/dvorakmortonxd/dxdwm",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "proxmox setup",
|
||||||
|
desc: "my proxmox setup with more than 8 vms for my services, this is running literally everything at my house.",
|
||||||
|
img: "/proxmox.png",
|
||||||
|
url: "https://proxmox.dvorakmortonxd.dev/",
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
export default function Home() {
|
||||||
|
const socials = [
|
||||||
|
{ name: "Main Instagram", icon: "/instagram.svg", url: "https://www.instagram.com/dvorakmortonxd/" },
|
||||||
|
{ name: "Gym Instagram", icon: "/instagram.svg", url: "https://www.instagram.com/dvorakmortonxd2/" },
|
||||||
|
{ name: "SoundCloud", icon: "/soundcloud.svg", url: "https://soundcloud.com/martin-dvorak-185636305" },
|
||||||
|
{ name: "TikTok", icon: "/tiktok.svg", url: "https://www.tiktok.com/@dvorakmortonxd" },
|
||||||
|
{ name: "Snapchat", icon: "/snapchat.svg", url: "https://www.snapchat.com/add/dvorakmortonxd" },
|
||||||
|
{ name: "GitDXD", icon: "/github.svg", url: "https://git.dvorakmortonxd.dev/dvorakmortonxd" },
|
||||||
|
{ name: "LinkedIn", icon: "/linkedin.svg", url: "https://www.linkedin.com/in/martin-dvorak-32012a394/" },
|
||||||
|
{ name: "Itch.io", icon: "/itchio.svg", url: "https://martin-dv.itch.io" },
|
||||||
|
{ name: "Discord", icon: "/discord.svg", url: "https://discord.com/users/555745543763001364" },
|
||||||
|
{ name: "Reddit", icon: "/reddit.svg", url: "https://www.reddit.com/user/M4RT1NYT/" },
|
||||||
|
{ name: "Twitter", icon: "/twitter.svg", url: "https://x.com/pepicskopcecek" },
|
||||||
|
{ name: "Quipee", icon: "/quip.svg", url: "https://quipee.me/dvorakmortonxd" },
|
||||||
|
];
|
||||||
|
|
||||||
|
const techProjects = [
|
||||||
|
{
|
||||||
|
title: "Gentoo Setup",
|
||||||
|
desc: "my main computer with gentoo. specs: Ryzen 5 3600 (6c/12t), GTX 1050 Ti (yes im aware I could use the rtx 3070 from the proxmox server but I dont need it and its much better off being in there cuz of local ai), 16gb of ram and a 240gb ssd.",
|
||||||
|
image: "/gentoo-setup.png",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "Proxmox Server",
|
||||||
|
desc: "my personal proxmox server (dm me on ig if u need a VPS/hosting), specs: Xeon E5 2680 v4 (14c/28t), RTX 3070 (for local ai), 68gb ddr4 rdimm ram (2*16gb, 1*32gb, 1*4gb), 256gb of sata m2 space + 1tb hdd + 750gb hdd.",
|
||||||
|
image: "/proxmox-server.png",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "Xbox 360 S with badupdate",
|
||||||
|
desc: "xbox 360 slim (trinity) with badupdate exploit, allowing to boot custom software. (not my photo), the specs are XCGPU (3c/6t), 0.5GB of gddr3 ram and a 500gb hdd.",
|
||||||
|
image: "/xbox360.png",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "Macbook Air M4 13\"",
|
||||||
|
desc: "my main device for basically everything except gaming. specs are Apple M4 (cpu:10c,gpu:8c), 16gb of unified ram (lpddr5x), 256gb Storage.",
|
||||||
|
image: "/macbook-air.png",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "Macbook Air 2013 11\"",
|
||||||
|
desc: "bought this for tinkering lol, specs are intel core i5 (2c/4t), 8gb of ddr3 ram and 250gb ssd, installed arch linux on it with i3-gaps (this is my laptop not josef's I gave it to him lol).",
|
||||||
|
image: "/macbook-air-11.png",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "iPhone 7 128gb",
|
||||||
|
desc: "somehow found a listing for 4 bucks (refurbed.cz) for it so I got it lol, it has a little dent I marked it on the photo (which isnt mine btw), except cosmetic damage its pretty much fully functional, specs are Apple A10 (4c/4t), 2gb of ram and 128gb of storage",
|
||||||
|
image: "/iphone-7.png",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "Macbook Unibody 2010",
|
||||||
|
desc: "found random listing on bazos for 15 bucks but after I bought a power adapter it turned out to be non functioning so I got scammed :(",
|
||||||
|
image: "/macbook2010.png",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "ThinkPad T400s 14\"",
|
||||||
|
desc: "got it from a random guy for 20 bucks, its an old laptop from 2009 and I disabled intel AMT and slapped void linux on it and i3wm on it, specs are intel core 2 duo (2c/2t), 8gb ddr3 ram and 800gb hdd.",
|
||||||
|
image: "/thinkpadt400s.png",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "Asus N56JR 15.6\"",
|
||||||
|
desc: "one of my first laptops my dad got for me when I was like 12, still works but the battery is KO, specs are intel core i7 4700HQ (4c/8t), 8gb ddr3 ram and 240gb ssd, it currently runs ubuntu linux 24.04LTS and my mom uses it for movies and shows.",
|
||||||
|
image: "/asuslaptop.png",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "iPhone 11 128gb",
|
||||||
|
desc: "my first iphone I got from my parents at 13, rn my dad has it because I switched over to the iphone 16 pro in 2025, specs are Apple A13 (6c/6t), 4gb of ram and 128gb of storage. (NOT MY IMAGE)",
|
||||||
|
image: "/iphone11.png",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "iPhone 16 Pro 128gb",
|
||||||
|
desc: "originally wanted iphone 13 mini (because of the size) but I somehow decided to get 16 pro, which is a bit smaller than my older iphone 11, specs are Apple A18 pro (6c/6t), (btw this chip is the same chip used in the macbook neo so thats cool) and 8gb of ram and 128gb of storage, my main phone.",
|
||||||
|
image: "/iphone16pro.png",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "Xbox one S 2TB",
|
||||||
|
desc: "my dad bought it for me also in like 2018 or 2019 and I sometimes play the console edition of minecraft because I like to play the gliding minigame on it when someone comes over.",
|
||||||
|
image: "/xboxones.png",
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
const friends = [
|
||||||
|
{
|
||||||
|
name: "Josef Skopec",
|
||||||
|
url: "/skopec.html",
|
||||||
|
realUrl: "https://skopec.dev/",
|
||||||
|
img: "/skopec.svg",
|
||||||
|
desc: "skopec.dev",
|
||||||
|
iframe: true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Filip Bukovina",
|
||||||
|
url: "https://bukovinafilip.com/",
|
||||||
|
realUrl: "https://bukovinafilip.com/",
|
||||||
|
img: "/bukovina.svg",
|
||||||
|
desc: "bukovinafilip.com",
|
||||||
|
iframe: true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Tomáš Vlk",
|
||||||
|
url: "https://vlcak.me",
|
||||||
|
realUrl: "https://vlcak.me",
|
||||||
|
img: "/vlcak.svg",
|
||||||
|
desc: "vlcak.me",
|
||||||
|
iframe: true
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
return <>
|
||||||
|
<SlBackground />
|
||||||
|
<div className="container">
|
||||||
|
|
||||||
|
{/* Header - TUI top bar */}
|
||||||
|
<header className="header">
|
||||||
|
<div className="header-center">
|
||||||
|
<JokerLogo />
|
||||||
|
<h1 className="site-name">dvorakmortonxd.dev</h1>
|
||||||
|
</div>
|
||||||
|
</header>
|
||||||
|
|
||||||
|
{/* Hero - TUI welcome panel */}
|
||||||
|
<main className="hero">
|
||||||
|
<div className="hero-panel">
|
||||||
|
<span className="hero-panel-label">~ README.MD</span>
|
||||||
|
<h2 className="hero-title">$ whoami</h2>
|
||||||
|
<p className="hero-subtitle">
|
||||||
|
Welcome to my website, visit the land down under to get to find out about me and my social media, services, hardware and projects. welcome to my goblin cave. {'>'}:)<span className="cursor-blink"></span>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Social Links Panel */}
|
||||||
|
<div className="social-section">
|
||||||
|
<div className="tui-panel">
|
||||||
|
<span className="tui-panel-label label-blue">├─ SOCIALS.JS</span>
|
||||||
|
<div className="social-buttons">
|
||||||
|
{socials.map((s) => (
|
||||||
|
<a
|
||||||
|
key={s.name}
|
||||||
|
href={s.url}
|
||||||
|
target="_blank"
|
||||||
|
rel="noopener noreferrer"
|
||||||
|
className="social-button"
|
||||||
|
>
|
||||||
|
<Image src={s.icon} alt={s.name} width={16} height={16} className="social-icon" />
|
||||||
|
<span className="social-label">{s.name}</span>
|
||||||
|
</a>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
<div className="mail-contact">
|
||||||
|
mail if u ever wanna contact me: <a href="mailto:dvorak@dvorakmortonxd.dev">dvorak@dvorakmortonxd.dev</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* About Me Panel */}
|
||||||
|
<div className="about-section">
|
||||||
|
<div className="tui-panel">
|
||||||
|
<span className="tui-panel-label label-yellow">├─ about_me.txt</span>
|
||||||
|
<div className="about-content">
|
||||||
|
<p>
|
||||||
|
<span className="text-green">martin@dvorakmortonxd</span>:<span className="text-blue">~</span>$ cat about_me.txt
|
||||||
|
</p>
|
||||||
|
<div style={{ color: '#a3a3a3' }}>
|
||||||
|
<p>
|
||||||
|
Hey, I'm Martin Dvořák — a 17-year-old IT student based in the Czech Republic, specializing in network management and administration at <span className="text-pink-700"><a href="https://sssvt.cz">sssvt.cz</a></span>. I'm a freshman and I have wanted to go to this school since 7th grade (2022) so I'm happy I got in in 2025, the school is pretty cool so if you are in middle school and interested in IT, check it.
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
I'm a passionate homelabber and developer heavily focused on self-hosting <i>literally everything</i>, writing code, and tinkering with Linux systems and retro hardware. Whether it's configuring Proxmox, reviving an old Thinkpad with Void Linux, or compiling Gentoo on my main rig, I'm always looking at bazos to get more retro hardware.
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
In my free time I mostly spend time with my girl {'<'}3, also constructing some of my self hosted stuff, and also when I'm in my gaming mood (which is kinda rare nowadays) I play terraria (around 1300hr playtime btw) and minecraft modpacks with mekanism. I also go to the gym around 3-5 times a week, running a PPL split focused on strength/powerlifting, my SBD total is 398kg (Squat:125kg, Bench:108kg, Deadlift:165kg) with a bodyweight of 73kg.<span className="cursor-blink"></span>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Services Panel */}
|
||||||
|
<div className="services-section">
|
||||||
|
<div className="tui-panel">
|
||||||
|
<span className="tui-panel-label label-green">├─ SERVICES.HTML</span>
|
||||||
|
<div className="services-buttons">
|
||||||
|
<ServiceButton
|
||||||
|
url="https://proxmox.dvorakmortonxd.dev/"
|
||||||
|
className="proxmox"
|
||||||
|
icon="🖥️"
|
||||||
|
name="Proxmox"
|
||||||
|
desc="Server Management"
|
||||||
|
/>
|
||||||
|
<ServiceButton
|
||||||
|
url="https://ai.dvorakmortonxd.dev/"
|
||||||
|
className="ai"
|
||||||
|
icon="🦾"
|
||||||
|
name="Ollama"
|
||||||
|
desc="Local AI Server"
|
||||||
|
/>
|
||||||
|
<ServiceButton
|
||||||
|
url="https://jelly.dvorakmortonxd.dev/"
|
||||||
|
className="jellyfin"
|
||||||
|
icon="🎬"
|
||||||
|
name="Jellyfin"
|
||||||
|
desc="Local Media Server"
|
||||||
|
/>
|
||||||
|
<ServiceButton
|
||||||
|
url="https://fake-pterodactyl.dvorakmortonxd.dev/"
|
||||||
|
className="pterodactyl"
|
||||||
|
icon="📦"
|
||||||
|
name="MCSManager"
|
||||||
|
desc="Game Server Panel"
|
||||||
|
/>
|
||||||
|
<ServiceButton
|
||||||
|
url="https://navidrome.dvorakmortonxd.dev/"
|
||||||
|
className="funkwhale"
|
||||||
|
icon="🎸"
|
||||||
|
name="Navidrome"
|
||||||
|
desc="Music Streaming"
|
||||||
|
/>
|
||||||
|
<ServiceButton
|
||||||
|
url="https://mailcow.dvorakmortonxd.dev/"
|
||||||
|
className="mailcow"
|
||||||
|
icon="💌"
|
||||||
|
name="Mailcow"
|
||||||
|
desc="Email Server"
|
||||||
|
/>
|
||||||
|
<ServiceButton
|
||||||
|
url="https://search.dvorakmortonxd.dev/"
|
||||||
|
className="searxng"
|
||||||
|
icon="🔦"
|
||||||
|
name="SearxNG"
|
||||||
|
desc="Private Search"
|
||||||
|
/>
|
||||||
|
<ServiceButton
|
||||||
|
url="https://convert.dvorakmortonxd.dev/"
|
||||||
|
className="convertx"
|
||||||
|
icon="📽️"
|
||||||
|
name="ConvertX"
|
||||||
|
desc="File Converter"
|
||||||
|
/>
|
||||||
|
<ServiceButton
|
||||||
|
url="https://git.dvorakmortonxd.dev"
|
||||||
|
className="gitdxd"
|
||||||
|
icon="🐙"
|
||||||
|
name="GitDXD"
|
||||||
|
desc="Better Github"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Softcraft Panel */}
|
||||||
|
<div className="services-section" style={{ marginTop: '20px' }}>
|
||||||
|
<div className="tui-panel">
|
||||||
|
<span className="tui-panel-label" style={{ color: '#d946ef' }}>├─ SOFTCRAFT.JAR</span>
|
||||||
|
<div className="services-buttons" style={{ gap: '1rem' }}>
|
||||||
|
<ServiceButton
|
||||||
|
url="https://dvorakmortonxd.dev/softcraft"
|
||||||
|
checkUrl="minecraft://46.30.237.43:6767"
|
||||||
|
className="softcraft"
|
||||||
|
icon="⛏️"
|
||||||
|
name="Softcraft"
|
||||||
|
desc="Play on my Minecraft Server!"
|
||||||
|
/>
|
||||||
|
<ServiceButton
|
||||||
|
url="https://bluemap.dvorakmortonxd.dev"
|
||||||
|
className="bluemap"
|
||||||
|
icon="🗺️"
|
||||||
|
name="BlueMap"
|
||||||
|
desc="Live SoftCraft Map"
|
||||||
|
/>
|
||||||
|
<ServiceButton
|
||||||
|
url="https://discord-sc.dvorakmortonxd.dev"
|
||||||
|
className="discord-sc"
|
||||||
|
icon={<Image src="/discord.svg" alt="Discord" width={28} height={28} />}
|
||||||
|
name="Discord"
|
||||||
|
desc="Join our community"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</main>
|
||||||
|
|
||||||
|
{/* Projects */}
|
||||||
|
<section className="projects">
|
||||||
|
{/* Tech Section */}
|
||||||
|
<div className="subsection-wrapper">
|
||||||
|
<div className="subsection-panel">
|
||||||
|
<span className="subsection-panel-label">├─ TECH_LIST.JSON</span>
|
||||||
|
<div className="project-grid">
|
||||||
|
{techProjects.map((project) => (
|
||||||
|
<div key={project.title} className="project-card">
|
||||||
|
<div
|
||||||
|
className="project-image"
|
||||||
|
style={{ backgroundImage: `url(${project.image})` }}
|
||||||
|
/>
|
||||||
|
<h4 className="project-title">{project.title}</h4>
|
||||||
|
<p className="project-desc">{project.desc}</p>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Coding Section */}
|
||||||
|
<div className="subsection-wrapper">
|
||||||
|
<div className="subsection-panel coding-panel">
|
||||||
|
<span className="subsection-panel-label">├─ PROJECTS.DOCX</span>
|
||||||
|
<div className="project-grid">
|
||||||
|
{codingProjects.map((project, idx) => (
|
||||||
|
<a
|
||||||
|
key={idx}
|
||||||
|
href={project.url}
|
||||||
|
target="_blank"
|
||||||
|
rel="noopener noreferrer"
|
||||||
|
className="project-card"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
className="project-image"
|
||||||
|
style={{ backgroundImage: `url(${project.img})` }}
|
||||||
|
/>
|
||||||
|
<h4 className="project-title">{project.title}</h4>
|
||||||
|
<p className="project-desc">{project.desc}</p>
|
||||||
|
</a>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Friends Section */}
|
||||||
|
<div className="subsection-wrapper">
|
||||||
|
<div className="subsection-panel friends-panel">
|
||||||
|
<span className="subsection-panel-label">└─ FRIENDS.TSX</span>
|
||||||
|
<div className="services-buttons" style={{ gap: '1rem' }}>
|
||||||
|
{friends.map((friend, idx) => (
|
||||||
|
<ServiceButton
|
||||||
|
key={idx}
|
||||||
|
url={friend.realUrl || friend.url}
|
||||||
|
className="friend"
|
||||||
|
icon={<Image src={friend.img} alt={friend.name} width={68} height={68} className="friend-icon" style={{ borderRadius: '50%' }} />}
|
||||||
|
name={friend.name}
|
||||||
|
desc={friend.desc}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<footer className="footer">
|
||||||
|
<span className="footer-label">v1.6.7</span>
|
||||||
|
© 2025-2026 Martin Dvořák. All rights reserved (not really I dont care)
|
||||||
|
</footer>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
}
|
||||||
227
src/app/softcraft/page.tsx
Normal file
227
src/app/softcraft/page.tsx
Normal file
|
|
@ -0,0 +1,227 @@
|
||||||
|
import Image from "next/image";
|
||||||
|
import "../page.css";
|
||||||
|
import { Metadata } from 'next';
|
||||||
|
import JokerLogo from "../components/JokerLogo";
|
||||||
|
import ServiceButton from "../components/ServiceButton";
|
||||||
|
|
||||||
|
export const metadata: Metadata = {
|
||||||
|
title: 'dvorakmortonxd.dev/softcraft',
|
||||||
|
description: 'Welcome to the Softcraft Minecraft Server info page!',
|
||||||
|
openGraph: {
|
||||||
|
title: 'dvorakmortonxd.dev/softcraft',
|
||||||
|
description: 'Welcome to the Softcraft Minecraft Server info page!',
|
||||||
|
url: 'https://dvorakmortonxd.dev/softcraft',
|
||||||
|
siteName: 'dvorakmortonxd.dev',
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export default function Softcraft() {
|
||||||
|
return <>
|
||||||
|
<style>{`
|
||||||
|
@keyframes mc-panorama-pan {
|
||||||
|
0% { background-position: 0% center; }
|
||||||
|
100% { background-position: 100% center; }
|
||||||
|
}
|
||||||
|
|
||||||
|
.header, .hero-panel, .tui-panel, .footer, .service-button, .social-button, .video-container, .hero-panel-label, .tui-panel-label, .footer-label {
|
||||||
|
background-image: linear-gradient(rgba(10, 10, 10, 0.85), rgba(10, 10, 10, 0.85)), url(/dirt.png) !important;
|
||||||
|
background-repeat: repeat !important;
|
||||||
|
background-size: 64px !important;
|
||||||
|
image-rendering: pixelated !important;
|
||||||
|
background-color: transparent !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.terminal-box {
|
||||||
|
background-image: linear-gradient(rgba(0, 0, 0, 0.95), rgba(0, 0, 0, 0.95)), url(/dirt.png) !important;
|
||||||
|
background-repeat: repeat !important;
|
||||||
|
background-size: 64px !important;
|
||||||
|
image-rendering: pixelated !important;
|
||||||
|
}
|
||||||
|
`}</style>
|
||||||
|
<div style={{
|
||||||
|
position: 'fixed',
|
||||||
|
top: 0,
|
||||||
|
left: 0,
|
||||||
|
width: '100vw',
|
||||||
|
height: '100vh',
|
||||||
|
zIndex: -1,
|
||||||
|
backgroundImage: 'url(/minecrafttest2.png)',
|
||||||
|
backgroundSize: 'cover',
|
||||||
|
animation: 'mc-panorama-pan 120s linear infinite alternate',
|
||||||
|
filter: 'brightness(0.6)'
|
||||||
|
}} />
|
||||||
|
<div className="container">
|
||||||
|
|
||||||
|
{/* Header - TUI top bar */}
|
||||||
|
<header className="header">
|
||||||
|
<div className="header-center">
|
||||||
|
<JokerLogo />
|
||||||
|
<h1 className="site-name">dvorakmortonxd.dev/softcraft</h1>
|
||||||
|
</div>
|
||||||
|
</header>
|
||||||
|
|
||||||
|
{/* Hero - TUI welcome panel */}
|
||||||
|
<main className="hero">
|
||||||
|
<div className="hero-panel">
|
||||||
|
<span className="hero-panel-label">~ ABOUT_SOFTCRAFT.MD</span>
|
||||||
|
<h2 className="hero-title">$ ./start_softcraft.sh</h2>
|
||||||
|
<p className="hero-subtitle">
|
||||||
|
Welcome to the Softcraft info page! {'>'}:)<span className="cursor-blink"></span>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Server Info Panel */}
|
||||||
|
<div className="about-section">
|
||||||
|
<div className="tui-panel">
|
||||||
|
<span className="tui-panel-label label-yellow">├─ neofetch</span>
|
||||||
|
<div className="about-content">
|
||||||
|
<p>
|
||||||
|
<span className="text-green">martin@dvorakmortonxd</span>:<span className="text-blue">~/softcraft</span>$ neofetch
|
||||||
|
</p>
|
||||||
|
<div style={{ display: 'flex', gap: '2rem', marginTop: '1.5rem', alignItems: 'center', flexWrap: 'wrap', color: '#a3a3a3' }}>
|
||||||
|
|
||||||
|
<div style={{ flexShrink: 0 }}>
|
||||||
|
<Image
|
||||||
|
src="/softcraft.png"
|
||||||
|
alt="Softcraft Logo"
|
||||||
|
width={128}
|
||||||
|
height={128}
|
||||||
|
style={{ imageRendering: 'pixelated', borderRadius: '8px' }}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div style={{ flex: 1, minWidth: '250px', lineHeight: '1.6' }}>
|
||||||
|
<div className="terminal-box" style={{ padding: '15px', borderRadius: '8px', border: '1px dashed #444', marginTop: '-10px' }}>
|
||||||
|
<p style={{ margin: '0 0 5px 0', color: '#22c55e', fontWeight: 'bold' }}>martin<span style={{ color: '#fff' }}>@</span>softcraft</p>
|
||||||
|
<p style={{ margin: '0 0 10px 0' }}>----------------</p>
|
||||||
|
<p style={{ margin: '0' }}><strong>OS</strong>: Purpur 1.21.11 or newer on Debian 13 Trixie</p>
|
||||||
|
<p style={{ margin: '0' }}><strong>Host/IP Adress</strong>: <span style={{ color: '#fff' }}>mc.dvorakmortonxd.dev</span></p>
|
||||||
|
<p style={{ margin: '0' }}><strong>Uptime</strong>: 24/7</p>
|
||||||
|
<p style={{ margin: '0' }}><strong>Rules</strong>: Little to no chat control, just enjoy the game bro</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div style={{ display: 'flex', gap: '0.5rem', marginTop: '15px' }}>
|
||||||
|
<span style={{ width: '16px', height: '16px', backgroundColor: '#000000', display: 'inline-block' }}></span>
|
||||||
|
<span style={{ width: '16px', height: '16px', backgroundColor: '#ef4444', display: 'inline-block' }}></span>
|
||||||
|
<span style={{ width: '16px', height: '16px', backgroundColor: '#22c55e', display: 'inline-block' }}></span>
|
||||||
|
<span style={{ width: '16px', height: '16px', backgroundColor: '#eab308', display: 'inline-block' }}></span>
|
||||||
|
<span style={{ width: '16px', height: '16px', backgroundColor: '#3b82f6', display: 'inline-block' }}></span>
|
||||||
|
<span style={{ width: '16px', height: '16px', backgroundColor: '#d946ef', display: 'inline-block' }}></span>
|
||||||
|
<span style={{ width: '16px', height: '16px', backgroundColor: '#06b6d4', display: 'inline-block' }}></span>
|
||||||
|
<span style={{ width: '16px', height: '16px', backgroundColor: '#ffffff', display: 'inline-block' }}></span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Links Panel */}
|
||||||
|
<div className="services-section" style={{ marginTop: '20px' }}>
|
||||||
|
<div className="tui-panel">
|
||||||
|
<span className="tui-panel-label label-blue">├─ LINKS.SH</span>
|
||||||
|
<div className="services-buttons" style={{ display: 'grid', gridTemplateColumns: 'repeat(3, 1fr)', gap: '1rem' }}>
|
||||||
|
<ServiceButton
|
||||||
|
url="https://bluemap.dvorakmortonxd.dev"
|
||||||
|
className="bluemap"
|
||||||
|
icon="🗺️"
|
||||||
|
name="Live Server Map"
|
||||||
|
desc="View the world in 3D"
|
||||||
|
/>
|
||||||
|
<ServiceButton
|
||||||
|
url="https://discord-sc.dvorakmortonxd.dev"
|
||||||
|
className="discord-sc"
|
||||||
|
icon={<Image src="/discord.svg" alt="Discord" width={28} height={28} />}
|
||||||
|
name="Community Discord"
|
||||||
|
desc="Chat with other players"
|
||||||
|
/>
|
||||||
|
<ServiceButton
|
||||||
|
url="https://dvorakmortonxd.dev"
|
||||||
|
className="back-home"
|
||||||
|
icon="🏠"
|
||||||
|
name="Main Site"
|
||||||
|
desc="cd .."
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Vote Panel */}
|
||||||
|
<div className="services-section" style={{ marginTop: '20px' }}>
|
||||||
|
<div className="tui-panel">
|
||||||
|
<span className="tui-panel-label label-yellow">├─ VOTE_HERE.JS</span>
|
||||||
|
<div className="services-buttons" style={{ display: 'grid', gridTemplateColumns: 'repeat(4, 1fr)', gap: '1rem' }}>
|
||||||
|
<ServiceButton
|
||||||
|
url="https://czech-craft.eu/server/softcraft-refreshed/"
|
||||||
|
className="vote"
|
||||||
|
icon="⭐"
|
||||||
|
name="Czech-Craft"
|
||||||
|
desc="Vote for us!"
|
||||||
|
/>
|
||||||
|
<ServiceButton
|
||||||
|
url="https://www.minecraft-list.cz/server/softcraft-refreshed"
|
||||||
|
className="vote"
|
||||||
|
icon="⭐"
|
||||||
|
name="Minecraft-List.cz"
|
||||||
|
desc="Vote for us!"
|
||||||
|
/>
|
||||||
|
<ServiceButton
|
||||||
|
url="https://minecraftservery.eu/server/softcraft-refreshed"
|
||||||
|
className="vote"
|
||||||
|
icon="⭐"
|
||||||
|
name="MinecraftServery.eu"
|
||||||
|
desc="Vote for us!"
|
||||||
|
/>
|
||||||
|
<ServiceButton
|
||||||
|
url="https://craftlist.org/softcraft"
|
||||||
|
className="vote"
|
||||||
|
icon="⭐"
|
||||||
|
name="CraftList.org"
|
||||||
|
desc="Vote for us!"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Video Panel */}
|
||||||
|
<div className="video-section" style={{ marginTop: '20px' }}>
|
||||||
|
<div className="tui-panel">
|
||||||
|
<span className="tui-panel-label" style={{ color: '#ec4899' }}>└─ BASH 'LS ./VIDEOS/'</span>
|
||||||
|
<div className="video-grid" style={{ marginTop: '10px' }}>
|
||||||
|
{[
|
||||||
|
"ifBnNS8X3O8",
|
||||||
|
"fQ3T374cpZk",
|
||||||
|
"yYrzQkhp8CQ",
|
||||||
|
"gIl01RaQjcI",
|
||||||
|
"e3gjtyPNZjg",
|
||||||
|
"P97CC3BWvg4",
|
||||||
|
"uPNN6LppOsg",
|
||||||
|
"NCy-h29lfzM"
|
||||||
|
].map(id => (
|
||||||
|
<div key={id} className="video-container" style={{ width: '100%', aspectRatio: '16/9', borderRadius: '8px', overflow: 'hidden', transform: 'translateZ(0)' }}>
|
||||||
|
<iframe
|
||||||
|
width="100%"
|
||||||
|
height="100%"
|
||||||
|
src={`https://www.youtube.com/embed/${id}`}
|
||||||
|
title="YouTube video player"
|
||||||
|
frameBorder="0"
|
||||||
|
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share"
|
||||||
|
allowFullScreen
|
||||||
|
loading="lazy"
|
||||||
|
style={{ border: 'none' }}
|
||||||
|
></iframe>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</main>
|
||||||
|
|
||||||
|
<footer className="footer">
|
||||||
|
<span className="footer-label">v1.6.7</span>
|
||||||
|
© 2025-2026 Martin Dvořák. All rights reserved
|
||||||
|
</footer>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
}
|
||||||
BIN
src/favicon.ico
Normal file
BIN
src/favicon.ico
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 9.4 KiB |
27
tsconfig.json
Normal file
27
tsconfig.json
Normal file
|
|
@ -0,0 +1,27 @@
|
||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"target": "ES2017",
|
||||||
|
"lib": ["dom", "dom.iterable", "esnext"],
|
||||||
|
"allowJs": true,
|
||||||
|
"skipLibCheck": true,
|
||||||
|
"strict": true,
|
||||||
|
"noEmit": true,
|
||||||
|
"esModuleInterop": true,
|
||||||
|
"module": "esnext",
|
||||||
|
"moduleResolution": "bundler",
|
||||||
|
"resolveJsonModule": true,
|
||||||
|
"isolatedModules": true,
|
||||||
|
"jsx": "preserve",
|
||||||
|
"incremental": true,
|
||||||
|
"plugins": [
|
||||||
|
{
|
||||||
|
"name": "next"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"paths": {
|
||||||
|
"@/*": ["./src/*"]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
|
||||||
|
"exclude": ["node_modules"]
|
||||||
|
}
|
||||||
Loading…
Add table
Add a link
Reference in a new issue