+ 404 +
+ ++ System Breach? Not really. +
++ The page you are looking for has been moved, deleted, or never existed in this terminal. +
+diff --git a/install.sh b/install.sh
new file mode 100644
index 0000000..d497edc
--- /dev/null
+++ b/install.sh
@@ -0,0 +1,59 @@
+#!/bin/bash
+
+# Farby pre krajší terminál
+GREEN='\033[0;32m'
+BLUE='\033[0;34m'
+YELLOW='\033[1;33m'
+RED='\033[0;31m'
+NC='\033[0m'
+
+echo -e "${BLUE}==========================================${NC}"
+echo -e "${BLUE} Jetomit_Bio - Automatická Inštalácia ${NC}"
+echo -e "${BLUE}==========================================${NC}"
+
+# 1. Kontrola prostredia
+echo -e "\n${YELLOW}🔍 Kontrolujem prostredie...${NC}"
+if ! command -v npm &> /dev/null; then
+ echo -e "${RED}❌ Chyba: NPM nie je nainštalované. Nainštaluj si Node.js.${NC}"
+ exit 1
+fi
+
+# 2. Inštalácia závislostí
+echo -e "${GREEN}📦 Inštalujem balíčky (Next.js, Framer Motion, Resend, Lucide, Tailwind)...${NC}"
+npm install
+
+# 3. Kontrola .env súboru
+echo -e "${GREEN}🔑 Kontrolujem konfiguračné súbory...${NC}"
+if [ ! -f .env ]; then
+ echo -e "${YELLOW}📝 Súbor .env nebol nájdený. Vytváram šablónu...${NC}"
+ echo "RESEND_API_KEY=re_tvoj_kluc_tu" > .env
+ echo -e "${RED}⚠ DOPLŇ SI API KĽÚČ DO SÚBORU .env!${NC}"
+else
+ echo -e "${BLUE}✅ Súbor .env už existuje.${NC}"
+fi
+
+# 4. Kontrola dátových súborov (JSON)
+echo -e "${GREEN}📂 Kontrolujem dátovú štruktúru...${NC}"
+FILES=("src/data/navbar.json" "src/data/main_page.json" "src/data/socials.json" "src/data/mainprojects.json" "src/data/contact.json")
+
+for file in "${FILES[@]}"; do
+ if [ ! -f "$file" ]; then
+ echo -e "${YELLOW}⚠ Varovanie: Súbor $file chýba. Skontroluj si priečinok src/data/.${NC}"
+ fi
+done
+
+# 5. Príprava priečinkov pre obrázky
+echo -e "${GREEN}🖼️ Pripravujem priečinky pre médiá...${NC}"
+mkdir -p public/images
+
+if [ ! -f public/images/me1.jpg ] || [ ! -f public/images/me2.jpg ]; then
+ echo -e "${YELLOW}📸 Nezabudni pridať fotky me1.jpg a me2.jpg do public/images/ pre správne zobrazenie polaroidov.${NC}"
+fi
+
+# 6. Finálne nastavenie práv pre skripty
+chmod +x install.sh
+
+echo -e "\n${BLUE}==========================================${NC}"
+echo -e "${GREEN}✅ Všetko je pripravené!${NC}"
+echo -e "${BLUE}Spusti projekt príkazom:${NC} ${YELLOW}npm run dev${NC}"
+echo -e "${BLUE}==========================================${NC}"
\ No newline at end of file
diff --git a/package-lock.json b/package-lock.json
index 4ca9b35..33e3762 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -8,9 +8,13 @@
"name": "jetomit-portfolio",
"version": "0.1.0",
"dependencies": {
+ "framer-motion": "^12.29.0",
+ "lucide-react": "^0.562.0",
+ "matter-js": "^0.20.0",
"next": "16.1.4",
"react": "19.2.3",
- "react-dom": "19.2.3"
+ "react-dom": "19.2.3",
+ "resend": "^6.8.0"
},
"devDependencies": {
"@tailwindcss/postcss": "^4",
@@ -1234,6 +1238,12 @@
"dev": true,
"license": "MIT"
},
+ "node_modules/@stablelib/base64": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/@stablelib/base64/-/base64-1.0.1.tgz",
+ "integrity": "sha512-1bnPQqSxSuc3Ii6MhBysoWCg58j97aUjuCSZrGSmDxNqtytIi0k8utUenAwTZN4V5mXXYGsVUI9zeBqy+jBOSQ==",
+ "license": "MIT"
+ },
"node_modules/@swc/helpers": {
"version": "0.5.15",
"resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.15.tgz",
@@ -3217,6 +3227,7 @@
"integrity": "sha512-whOE1HFo/qJDyX4SnXzP4N6zOWn79WhnCUY/iDR0mPfQZO8wcYE4JClzI2oZrhBnnMUCBCHZhO6VQyoBU95mZA==",
"dev": true,
"license": "MIT",
+ "peer": true,
"dependencies": {
"@rtsao/scc": "^1.1.0",
"array-includes": "^3.1.9",
@@ -3501,6 +3512,12 @@
"dev": true,
"license": "MIT"
},
+ "node_modules/fast-sha256": {
+ "version": "1.3.0",
+ "resolved": "https://registry.npmjs.org/fast-sha256/-/fast-sha256-1.3.0.tgz",
+ "integrity": "sha512-n11RGP/lrWEFI/bWdygLxhI+pVeo1ZYIVwvvPkW7azl/rOy+F3HYRZ2K5zeE9mmkhQppyv9sQFx0JM9UabnpPQ==",
+ "license": "Unlicense"
+ },
"node_modules/fastq": {
"version": "1.20.1",
"resolved": "https://registry.npmjs.org/fastq/-/fastq-1.20.1.tgz",
@@ -3591,6 +3608,33 @@
"url": "https://github.com/sponsors/ljharb"
}
},
+ "node_modules/framer-motion": {
+ "version": "12.29.0",
+ "resolved": "https://registry.npmjs.org/framer-motion/-/framer-motion-12.29.0.tgz",
+ "integrity": "sha512-1gEFGXHYV2BD42ZPTFmSU9buehppU+bCuOnHU0AD18DKh9j4DuTx47MvqY5ax+NNWRtK32qIcJf1UxKo1WwjWg==",
+ "license": "MIT",
+ "dependencies": {
+ "motion-dom": "^12.29.0",
+ "motion-utils": "^12.27.2",
+ "tslib": "^2.4.0"
+ },
+ "peerDependencies": {
+ "@emotion/is-prop-valid": "*",
+ "react": "^18.0.0 || ^19.0.0",
+ "react-dom": "^18.0.0 || ^19.0.0"
+ },
+ "peerDependenciesMeta": {
+ "@emotion/is-prop-valid": {
+ "optional": true
+ },
+ "react": {
+ "optional": true
+ },
+ "react-dom": {
+ "optional": true
+ }
+ }
+ },
"node_modules/function-bind": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz",
@@ -4839,6 +4883,15 @@
"yallist": "^3.0.2"
}
},
+ "node_modules/lucide-react": {
+ "version": "0.562.0",
+ "resolved": "https://registry.npmjs.org/lucide-react/-/lucide-react-0.562.0.tgz",
+ "integrity": "sha512-82hOAu7y0dbVuFfmO4bYF1XEwYk/mEbM5E+b1jgci/udUBEE/R7LF5Ip0CCEmXe8AybRM8L+04eP+LGZeDvkiw==",
+ "license": "ISC",
+ "peerDependencies": {
+ "react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0"
+ }
+ },
"node_modules/magic-string": {
"version": "0.30.21",
"resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz",
@@ -4859,6 +4912,12 @@
"node": ">= 0.4"
}
},
+ "node_modules/matter-js": {
+ "version": "0.20.0",
+ "resolved": "https://registry.npmjs.org/matter-js/-/matter-js-0.20.0.tgz",
+ "integrity": "sha512-iC9fYR7zVT3HppNnsFsp9XOoQdQN2tUyfaKg4CHLH8bN+j6GT4Gw7IH2rP0tflAebrHFw730RR3DkVSZRX8hwA==",
+ "license": "MIT"
+ },
"node_modules/merge2": {
"version": "1.4.1",
"resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz",
@@ -4906,6 +4965,21 @@
"url": "https://github.com/sponsors/ljharb"
}
},
+ "node_modules/motion-dom": {
+ "version": "12.29.0",
+ "resolved": "https://registry.npmjs.org/motion-dom/-/motion-dom-12.29.0.tgz",
+ "integrity": "sha512-3eiz9bb32yvY8Q6XNM4AwkSOBPgU//EIKTZwsSWgA9uzbPBhZJeScCVcBuwwYVqhfamewpv7ZNmVKTGp5qnzkA==",
+ "license": "MIT",
+ "dependencies": {
+ "motion-utils": "^12.27.2"
+ }
+ },
+ "node_modules/motion-utils": {
+ "version": "12.27.2",
+ "resolved": "https://registry.npmjs.org/motion-utils/-/motion-utils-12.27.2.tgz",
+ "integrity": "sha512-B55gcoL85Mcdt2IEStY5EEAsrMSVE2sI14xQ/uAdPL+mfQxhKKFaEag9JmfxedJOR4vZpBGoPeC/Gm13I/4g5Q==",
+ "license": "MIT"
+ },
"node_modules/ms": {
"version": "2.1.3",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
@@ -5458,6 +5532,26 @@
"url": "https://github.com/sponsors/ljharb"
}
},
+ "node_modules/resend": {
+ "version": "6.8.0",
+ "resolved": "https://registry.npmjs.org/resend/-/resend-6.8.0.tgz",
+ "integrity": "sha512-fDOXGqafQfQXl8nXe93wr93pus8tW7YPpowenE3SmG7dJJf0hH3xUWm3xqacnPvhqjCQTJH9xETg07rmUeSuqQ==",
+ "license": "MIT",
+ "dependencies": {
+ "svix": "1.84.1"
+ },
+ "engines": {
+ "node": ">=20"
+ },
+ "peerDependencies": {
+ "@react-email/render": "*"
+ },
+ "peerDependenciesMeta": {
+ "@react-email/render": {
+ "optional": true
+ }
+ }
+ },
"node_modules/resolve": {
"version": "1.22.11",
"resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.11.tgz",
@@ -5827,6 +5921,16 @@
"dev": true,
"license": "MIT"
},
+ "node_modules/standardwebhooks": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/standardwebhooks/-/standardwebhooks-1.0.0.tgz",
+ "integrity": "sha512-BbHGOQK9olHPMvQNHWul6MYlrRTAOKn03rOe4A8O3CLWhNf4YHBqq2HJKKC+sfqpxiBY52pNeesD6jIiLDz8jg==",
+ "license": "MIT",
+ "dependencies": {
+ "@stablelib/base64": "^1.0.0",
+ "fast-sha256": "^1.3.0"
+ }
+ },
"node_modules/stop-iteration-iterator": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/stop-iteration-iterator/-/stop-iteration-iterator-1.1.0.tgz",
@@ -6026,6 +6130,16 @@
"url": "https://github.com/sponsors/ljharb"
}
},
+ "node_modules/svix": {
+ "version": "1.84.1",
+ "resolved": "https://registry.npmjs.org/svix/-/svix-1.84.1.tgz",
+ "integrity": "sha512-K8DPPSZaW/XqXiz1kEyzSHYgmGLnhB43nQCMeKjWGCUpLIpAMMM8kx3rVVOSm6Bo6EHyK1RQLPT4R06skM/MlQ==",
+ "license": "MIT",
+ "dependencies": {
+ "standardwebhooks": "1.0.0",
+ "uuid": "^10.0.0"
+ }
+ },
"node_modules/tailwindcss": {
"version": "4.1.18",
"resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.1.18.tgz",
@@ -6386,6 +6500,19 @@
"punycode": "^2.1.0"
}
},
+ "node_modules/uuid": {
+ "version": "10.0.0",
+ "resolved": "https://registry.npmjs.org/uuid/-/uuid-10.0.0.tgz",
+ "integrity": "sha512-8XkAphELsDnEGrDxUOHB3RGvXz6TeuYSGEZBOjtTtPm2lwhGBjLgOzLHB63IUWfBpNucQjND6d3AOudO+H3RWQ==",
+ "funding": [
+ "https://github.com/sponsors/broofa",
+ "https://github.com/sponsors/ctavan"
+ ],
+ "license": "MIT",
+ "bin": {
+ "uuid": "dist/bin/uuid"
+ }
+ },
"node_modules/which": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",
diff --git a/package.json b/package.json
index c741151..042e138 100644
--- a/package.json
+++ b/package.json
@@ -9,9 +9,13 @@
"lint": "eslint"
},
"dependencies": {
+ "framer-motion": "^12.29.0",
+ "lucide-react": "^0.562.0",
+ "matter-js": "^0.20.0",
"next": "16.1.4",
"react": "19.2.3",
- "react-dom": "19.2.3"
+ "react-dom": "19.2.3",
+ "resend": "^6.8.0"
},
"devDependencies": {
"@tailwindcss/postcss": "^4",
diff --git a/public/images/me1.jpg b/public/images/me1.jpg
new file mode 100644
index 0000000..05f03fd
Binary files /dev/null and b/public/images/me1.jpg differ
diff --git a/public/images/me2.jpg b/public/images/me2.jpg
new file mode 100644
index 0000000..f95732c
Binary files /dev/null and b/public/images/me2.jpg differ
diff --git a/src/app/api/send/route.ts b/src/app/api/send/route.ts
new file mode 100644
index 0000000..dc75d66
--- /dev/null
+++ b/src/app/api/send/route.ts
@@ -0,0 +1,53 @@
+import { NextResponse } from 'next/server';
+import { Resend } from 'resend';
+import contactData from "@/data/contact.json";
+import { promises as fs } from 'fs';
+import path from 'path';
+
+// Inicializácia Resend s API kľúčom z .env.local
+const resend = new Resend(process.env.RESEND_API_KEY);
+
+export async function POST(request: Request) {
+ try {
+ // 1. Získanie dát z prichádzajúceho JSON requestu
+ const { firstName, lastName, email, subject, body } = await request.json();
+
+ // 2. Cesta k HTML šablóne a jej načítanie
+ const filePath = path.join(process.cwd(), 'src/data/email.html');
+ let htmlContent = await fs.readFile(filePath, 'utf8');
+
+ // 3. Dynamické nahradenie placeholderov v HTML súbore za reálne dáta
+ // Používame globálny replace (/.../g), ak by sa premenná v HTML opakovala
+ const finalHtml = htmlContent
+ .replace(/{{firstName}}/g, firstName)
+ .replace(/{{lastName}}/g, lastName)
+ .replace(/{{subject}}/g, subject)
+ .replace(/{{email}}/g, email)
+ .replace(/{{body}}/g, body.replace(/\n/g, '
')); // Ošetrenie nových riadkov
+
+ // 4. Odoslanie e-mailu cez službu Resend
+ const { data, error } = await resend.emails.send({
+ from: contactData.config.from_email,
+ to: [contactData.config.to_email],
+ subject: `📩 New Message: ${subject}`,
+ replyTo: email, // Umožní ti odpovedať priamo odosielateľovi kliknutím na "Reply"
+ html: finalHtml,
+ });
+
+ // 5. Ošetrenie chýb z Resend API
+ if (error) {
+ console.error("Resend Error:", error);
+ return NextResponse.json({ error }, { status: 400 });
+ }
+
+ // 6. Úspešná odpoveď
+ return NextResponse.json({ success: true, data });
+
+ } catch (error: any) {
+ console.error("Server Error:", error);
+ return NextResponse.json(
+ { error: "Internal Server Error", details: error.message },
+ { status: 500 }
+ );
+ }
+}
\ No newline at end of file
diff --git a/src/app/favicon.ico b/src/app/favicon.ico
index 718d6fe..a713067 100644
Binary files a/src/app/favicon.ico and b/src/app/favicon.ico differ
diff --git a/src/app/globals.css b/src/app/globals.css
index a2dc41e..c136ea0 100644
--- a/src/app/globals.css
+++ b/src/app/globals.css
@@ -1,26 +1,96 @@
@import "tailwindcss";
+@theme {
+ --color-pill-gray: #bcbfc7;
+ --color-pill-dark: #1c1c1e;
+}
+
:root {
- --background: #ffffff;
- --foreground: #171717;
+ --background: #0d0d0d;
+ --foreground: #ffffff;
}
-@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: #0a0a0a;
- --foreground: #ededed;
- }
+/* KĽÚČOVÁ ZMENA TU */
+html {
+ background-color: var(--background);
+ /* Vynúti stabilný priestor pre scrollbar na všetkých stránkach */
+ scrollbar-gutter: stable !important;
+ overflow-x: hidden;
+ width: 100%;
}
body {
- background: var(--background);
color: var(--foreground);
- font-family: Arial, Helvetica, sans-serif;
+ margin: 0;
+ -webkit-font-smoothing: antialiased;
+ width: 100%;
+ overflow-x: hidden;
}
+
+/* Zvyšok tvojho CSS zostáva rovnaký */
+a {
+ text-decoration: none !important;
+ color: inherit;
+ transition: all 0.2s ease;
+}
+
+.force-black {
+ color: #000000 !important;
+}
+
+.btn-pill-primary {
+ display: inline-flex;
+ align-items: center;
+ gap: 12px;
+ padding: 12px 24px;
+ background-color: #bcbfc7;
+ color: #000000 !important;
+ border-radius: 9999px;
+ font-weight: 700;
+ transition: transform 0.2s ease;
+}
+
+.btn-pill-primary:hover {
+ transform: scale(1.05);
+}
+
+.btn-pill-secondary {
+ display: inline-flex;
+ align-items: center;
+ gap: 12px;
+ padding: 12px 24px;
+ background-color: #1c1c1e;
+ color: #ffffff !important;
+ border-radius: 9999px;
+ font-weight: 600;
+ border: 1px solid rgba(255,255,255,0.05);
+ transition: background-color 0.2s;
+}
+
+.btn-pill-secondary:hover {
+ background-color: #2a2a2c;
+}
+
+.polaroid-frame {
+ background: white;
+ padding: 12px 12px 48px 12px;
+ box-shadow: 0 25px 50px -12px rgba(0, 0, 0, 0.5);
+ border: 1px solid rgba(0,0,0,0.1);
+ transition: transform 0.3s ease-in-out;
+}
+
+.polaroid-frame:hover {
+ transform: scale(1.05) rotate(0deg) !important;
+ z-index: 40;
+}
+
+::-webkit-scrollbar {
+ width: 8px;
+}
+::-webkit-scrollbar-track {
+ background: #0d0d0d;
+}
+::-webkit-scrollbar-thumb {
+ background: #1c1c1e;
+ border-radius: 10px;
+}
\ No newline at end of file
diff --git a/src/app/layout.tsx b/src/app/layout.tsx
index f7fa87e..6383a96 100644
--- a/src/app/layout.tsx
+++ b/src/app/layout.tsx
@@ -1,34 +1,40 @@
import type { Metadata } from "next";
-import { Geist, Geist_Mono } from "next/font/google";
+import { Inter } from "next/font/google";
import "./globals.css";
-const geistSans = Geist({
- variable: "--font-geist-sans",
- subsets: ["latin"],
-});
-
-const geistMono = Geist_Mono({
- variable: "--font-geist-mono",
+// Použijeme font Inter, ktorý je pre "PRPL" štýl ideálny
+const inter = Inter({
subsets: ["latin"],
+ weight: ['400', '700', '900']
});
export const metadata: Metadata = {
- title: "Create Next App",
- description: "Generated by create next app",
+ title: "Jetomit_ Bio | Portfolio",
+ description: "13-year-old Full-stack developer building digital experiences.",
};
export default function RootLayout({
children,
-}: Readonly<{
+}: {
children: React.ReactNode;
-}>) {
+}) {
return (
-
+
+ The page you are looking for has been moved, deleted, or never existed in this terminal. +
+- Looking for a starting point or more instructions? Head over to{" "} - - Templates - {" "} - or the{" "} - - Learning - {" "} - center. -
+
+ {mainData.hero.description}
+ Specializing in {mainData.hero.specialization.tech1} and {mainData.hero.specialization.tech2}.
+
+ {project.description} +
+ + ))} ++ {projectsPageData.header.description} +
++ {project.description} +
+ ++ {item.role || projectsPageData.sections.default_role} +
++ Drag them anywhere +
+Have a project in mind? Let's build it.
+