main
Obai Albek 2025-09-18 18:17:19 -04:00
parent 2b0113b25b
commit 753702aa69
8 changed files with 596 additions and 369 deletions

Binary file not shown.

View File

@ -1,331 +1,186 @@
/* ========================================= /* =========================================
Obai Albeek Portfolio Styles (Dark) Obai Albek Portfolio Styles (Dark)
Palette: Onyx + Jade | v2 Palette: Onyx + Jade | v3
========================================= */ ========================================= */
/* ---------- CSS Variables ---------- */
:root{ :root{
--bg: #0E1116; /* Onyx (Seitenhintergrund) */ --bg:#0E1116; --surface:#101721; --surface-2:#0B111A;
--surface: #101721; /* Dunkle Card/Fläche */ --text:#E6E9EF; --muted:#A8B0BF; --line:rgba(255,255,255,.08);
--surface-2: #0B111A; /* noch dunklere Fläche */ --accent:#12B3A4; --accent-ink:#061014;
--text: #E6E9EF; /* Primärtext hell */ --radius:22px;
--muted: #A8B0BF; /* Sekundärtext */ --shadow:0 16px 40px rgba(0,0,0,.55);
--line: rgba(255,255,255,.08); --shadow-sm:0 8px 24px rgba(0,0,0,.45);
--container:1100px;
--accent: #12B3A4; /* Jade/Türkis */ --space-1:.25rem; --space-2:.5rem; --space-3:.75rem; --space-4:1rem;
--accent-ink: #061014; /* Text auf Akzent (dunkel, angenehmer als weiß) */ --space-5:1.25rem; --space-6:1.5rem; --space-7:2rem; --space-8:2.5rem; --space-9:3rem;
--radius: 22px;
--shadow: 0 16px 40px rgba(0,0,0,.55);
--shadow-sm: 0 8px 24px rgba(0,0,0,.45);
--container: 1100px;
--space-1: .25rem;
--space-2: .5rem;
--space-3: .75rem;
--space-4: 1rem;
--space-5: 1.25rem;
--space-6: 1.5rem;
--space-7: 2rem;
--space-8: 2.5rem;
--space-9: 3rem;
} }
/* ---------- Reset & Base ---------- */ /* ---------- Reset & Base ---------- */
*, *,*::before,*::after{box-sizing:border-box}
*::before, html,body{height:100%;scroll-behavior:smooth}
*::after{ box-sizing: border-box; }
html, body{ height: 100%; scroll-behavior: smooth; }
body{ body{
margin: 0; margin:0;font-family:"Roboto",system-ui,-apple-system,Segoe UI,Arial,sans-serif;
font-family: "Roboto", system-ui, -apple-system, Segoe UI, Arial, sans-serif; color:var(--text);
color: var(--text);
background: background:
radial-gradient(900px 600px at 20% -10%, #121925 0%, transparent 55%), radial-gradient(900px 600px at 20% -10%, #121925 0%, transparent 55%),
radial-gradient(1100px 700px at 110% -20%, #0F1721 0%, transparent 60%), radial-gradient(1100px 700px at 110% -20%, #0F1721 0%, transparent 60%),
var(--bg); var(--bg);
line-height: 1.6; line-height:1.6;
} }
:focus-visible{outline:3px solid color-mix(in oklab,var(--accent),white 15%);outline-offset:2px}
.container{max-width:var(--container);margin-inline:auto;padding-inline:var(--space-7)}
:focus-visible{ /* ---------- Topbar ---------- */
outline: 3px solid color-mix(in oklab, var(--accent), white 15%);
outline-offset: 2px;
}
.container{
max-width: var(--container);
margin-inline: auto;
padding-inline: var(--space-7);
}
/* ---------- Sticky Topbar (Nav rechts, Name links) ---------- */
.topbar{ .topbar{
position: sticky; position:sticky;top:0;z-index:50;display:flex;align-items:center;justify-content:space-between;
top: 0; padding:var(--space-4) var(--space-7);
z-index: 50; backdrop-filter:saturate(1.05) blur(8px);
display: flex; /* NEU: Flex-Layout */ background:color-mix(in oklab,var(--bg),black 20% / 65%);
align-items: center; border-bottom:1px solid var(--line);
justify-content: space-between;/* sorgt dafür, dass Nav ganz rechts sitzt */
padding: var(--space-4) var(--space-7);
backdrop-filter: saturate(1.05) blur(8px);
background: color-mix(in oklab, var(--bg), black 20% / 65%);
border-bottom: 1px solid var(--line);
}
/* Brand / Name links */
.topbar .brand{
display: flex;
align-items: center;
gap: .6rem;
text-decoration: none;
} }
.topbar .brand{display:flex;align-items:center;gap:.6rem;text-decoration:none}
.brand-mark{ .brand-mark{
display: inline-grid; display:inline-grid;place-items:center;width:36px;height:36px;border-radius:12px;
place-items: center; background:var(--accent);color:var(--accent-ink);font-weight:800;letter-spacing:.5px;
width: 36px; height: 36px; box-shadow:0 8px 20px color-mix(in oklab,var(--accent),black 65%);
border-radius: 12px;
background: var(--accent);
color: var(--accent-ink);
font-weight: 800;
letter-spacing: .5px;
box-shadow: 0 8px 20px color-mix(in oklab, var(--accent), black 65%);
}
.brand-text{
color: var(--text);
font-weight: 800;
font-size: 1.08rem; /* Name deutlicher sichtbar */
letter-spacing: .2px;
}
/* Nav ganz rechts */
.nav{
margin-left: auto; /* bleibt als Absicherung */
display: flex;
gap: .6rem;
align-items: center;
} }
.brand-text{color:var(--text);font-weight:800;font-size:1.08rem;letter-spacing:.2px}
.nav{margin-left:auto;display:flex;gap:.6rem;align-items:center}
.nav-link{ .nav-link{
--pad-x: 1rem; --pad-x:1rem;display:inline-block;padding:.55rem var(--pad-x);text-decoration:none;color:var(--text);
display: inline-block; background:rgba(255,255,255,.03);border:1px solid var(--line);border-radius:999px;
padding: .55rem var(--pad-x); transition:transform .06s ease,box-shadow .2s ease,background .2s ease,border-color .2s ease;
text-decoration: none; box-shadow:var(--shadow-sm);
color: var(--text);
background: rgba(255,255,255,.03);
border: 1px solid var(--line);
border-radius: 999px;
transition: transform .06s ease, box-shadow .2s ease, background .2s ease, border-color .2s ease;
box-shadow: var(--shadow-sm);
}
.nav-link:hover{
transform: translateY(-1px);
background: rgba(255,255,255,.06);
border-color: rgba(255,255,255,.12);
}
.nav-link.cta{
background: var(--accent);
color: var(--accent-ink);
border-color: transparent;
box-shadow: 0 10px 26px color-mix(in oklab, var(--accent), black 58%);
} }
.nav-link:hover{transform:translateY(-1px);background:rgba(255,255,255,.06);border-color:rgba(255,255,255,.12)}
.nav-link.cta{background:var(--accent);color:var(--accent-ink);border-color:transparent;box-shadow:0 10px 26px color-mix(in oklab,var(--accent),black 58%)}
/* ---------- Hero ---------- */ /* ---------- Hero ---------- */
.hero{ padding: clamp(3rem, 5vw, 5rem) var(--space-7); } .hero{ padding: clamp(2rem, 3.6vw, 3rem) var(--space-7); }
.hero-card{ .hero-card{
position: relative; position:relative;display:grid;grid-template-columns:1.05fr 1.6fr;gap:var(--space-8);align-items:center;
display: grid; max-width:var(--container);margin:0 auto;background:linear-gradient(180deg,var(--surface) 0%,color-mix(in oklab,var(--surface),black 8%) 100%);
grid-template-columns: 1.1fr 1.4fr; border:1px solid var(--line);border-radius:var(--radius);padding:clamp(1.2rem,2.4vw,1.9rem);box-shadow:var(--shadow);
gap: var(--space-8);
align-items: center;
max-width: var(--container);
margin: 0 auto;
background: linear-gradient(180deg, var(--surface) 0%, color-mix(in oklab, var(--surface), black 8%) 100%);
border: 1px solid var(--line);
border-radius: var(--radius);
padding: clamp(1.25rem, 3vw, 2rem);
box-shadow: var(--shadow);
} }
.hero-media{position:relative;min-height:clamp(220px, 24vw, 300px)}
.hero-media{ position: relative; min-height: 240px; }
.hero-photo{ .hero-photo{
position: absolute; position:absolute;inset:auto 0 0 50%;transform:translate(-50%,6%) scale(1);
inset: auto 0 0 50%; width:clamp(185px, 24vw, 300px);aspect-ratio:1/1;object-fit:cover;border-radius:50%;
transform: translate(-50%, 18%) scale(1.02); border:12px solid var(--surface);
width: min(320px, 78%); box-shadow:0 0 0 12px color-mix(in oklab,var(--accent),transparent 85%),0 26px 60px color-mix(in oklab,var(--accent),black 70%);
aspect-ratio: 1/1; background:#111;
object-fit: cover;
border-radius: 50%;
border: 10px solid var(--surface);
/* dezentes Teal-Glow */
box-shadow:
0 0 0 10px color-mix(in oklab, var(--accent), transparent 85%),
0 26px 60px color-mix(in oklab, var(--accent), black 70%);
background: #111;
} }
.kicker{margin:0 0 .25rem;color:var(--muted);letter-spacing:.4px}
/* Textblock */ .title{margin:0 0 .35rem;font-size:clamp(2rem,5vw,3.1rem);line-height:1.15;color:var(--text)}
.kicker{ margin: 0 0 .25rem 0; color: var(--muted); letter-spacing: .4px; } .subtitle{margin:0 0 var(--space-7);color:var(--muted)}
.title{ .actions{display:flex;flex-wrap:wrap;gap:.75rem;margin-bottom:var(--space-6)}
margin: 0 0 .35rem 0;
font-size: clamp(2rem, 5vw, 3rem);
line-height: 1.15;
color: var(--text);
}
.subtitle{ margin: 0 0 var(--space-7) 0; color: var(--muted); }
/* ---------- Buttons ---------- */
.btn{ .btn{
--px: 1.1rem; --px:1.1rem;display:inline-block;padding:.75rem var(--px);border-radius:999px;text-decoration:none;font-weight:800;line-height:1;
display: inline-block; border:1px solid transparent;transition:transform .06s ease,box-shadow .2s ease,background .2s ease,border-color .2s ease;cursor:pointer;
padding: .75rem var(--px);
border-radius: 999px;
text-decoration: none;
font-weight: 800;
line-height: 1;
border: 1px solid transparent;
transition: transform .06s ease, box-shadow .2s ease, background .2s ease, border-color .2s ease;
cursor: pointer;
}
.btn:hover{ transform: translateY(-1px); }
.btn.primary{
background: var(--accent);
color: var(--accent-ink);
box-shadow: 0 10px 26px color-mix(in oklab, var(--accent), black 58%);
}
.btn.ghost{
background: rgba(255,255,255,.04);
color: var(--text);
border-color: var(--line);
}
.btn.small{
padding: .55rem .9rem;
font-weight: 700;
} }
.btn:hover{transform:translateY(-1px)}
.btn.primary{background:var(--accent);color:var(--accent-ink);box-shadow:0 10px 26px color-mix(in oklab,var(--accent),black 58%)}
.btn.ghost{background:rgba(255,255,255,.04);color:var(--text);border-color:var(--line)}
.btn.small{padding:.55rem .9rem;font-weight:700}
/* ---------- Social ---------- */ /* ---------- Social ---------- */
.social{ display:flex; gap:.5rem; margin:0; padding:0; list-style:none; } .social{display:flex;gap:.5rem;margin:0;padding:0;list-style:none}
.iconlink{ .hero .social{margin-top:var(--space-7)}
display: inline-grid; a.iconlink{
place-items: center; display:inline-grid;place-items:center;width:44px;height:44px;border-radius:12px;border:1px solid var(--line);
width: 40px; height: 40px; background:rgba(255,255,255,.03);color:var(--text);text-decoration:none;box-shadow:var(--shadow-sm);
border-radius: 12px; transition:background .2s ease,border-color .2s ease,transform .06s ease;
border: 1px solid var(--line);
background: rgba(255,255,255,.03);
color: var(--text);
text-decoration: none;
box-shadow: var(--shadow-sm);
}
.iconlink:hover{
transform: translateY(-1px);
background: rgba(255,255,255,.06);
} }
a.iconlink:hover{transform:translateY(-1px);background:rgba(255,255,255,.08);border-color:rgba(255,255,255,.14)}
a.iconlink svg{display:block}
/* ---------- Sections ---------- */ /* ---------- Sections ---------- */
.section{ padding: clamp(2.5rem, 5vw, 4rem) 0; scroll-margin-top: 90px; } .section{padding:clamp(2.5rem,5vw,4rem) 0;scroll-margin-top:90px}
.section h2{ font-size: clamp(1.6rem, 3.4vw, 2rem); margin: 0 0 var(--space-5) 0; } .section h2{font-size:clamp(1.6rem,3.4vw,2rem);margin:0 0 var(--space-5)}
.about .highlights{display:grid;grid-template-columns:1fr;gap:.5rem;padding:0;margin:var(--space-6) 0 0;list-style:"✓ "}
.about .highlights li{padding-left:.4rem;color:var(--text)}
/* About */ /* ---------- Skills ---------- */
.about .highlights{ .skills .skill-grid{display:grid;gap:var(--space-6);grid-template-columns:repeat(4,1fr)}
display: grid; .skill{background:var(--surface-2);border:1px solid var(--line);border-radius:calc(var(--radius) - 6px);padding:var(--space-7);box-shadow:var(--shadow-sm)}
grid-template-columns: 1fr; .skill h3{margin:0 0 .35rem}
gap: .5rem; .skill p{color:var(--muted);margin:0}
padding: 0; margin: var(--space-6) 0 0 0;
list-style: "✓ ";
}
.about .highlights li{ padding-left: .4rem; color: var(--text); }
/* Skills */ /* ---------- Projekte ---------- */
.skills .skill-grid{ .project-list{display:grid;gap:var(--space-6);grid-template-columns:repeat(3,1fr)}
display: grid;
gap: var(--space-6);
grid-template-columns: repeat(4, 1fr);
}
.skill{
background: var(--surface-2);
border: 1px solid var(--line);
border-radius: calc(var(--radius) - 6px);
padding: var(--space-7);
box-shadow: var(--shadow-sm);
}
.skill h3{ margin: 0 0 .35rem 0; }
.skill p{ color: var(--muted); margin: 0; }
/* Projects */
.project-list{
display: grid;
gap: var(--space-6);
grid-template-columns: repeat(3, 1fr);
}
.project-card{ .project-card{
background: var(--surface); background:var(--surface);border:1px solid var(--line);border-radius:calc(var(--radius) - 6px);
border: 1px solid var(--line); padding:var(--space-7);display:grid;gap:.9rem;box-shadow:var(--shadow-sm);
border-radius: calc(var(--radius) - 6px);
padding: var(--space-7);
display: grid;
gap: .9rem;
box-shadow: var(--shadow-sm);
} }
.project-card .meta{ color: var(--muted); margin: .25rem 0 0 0; } .project-card .meta{color:var(--muted);margin:.25rem 0 0}
.project-actions{ display: flex; gap: .5rem; } .project-actions{display:flex;gap:.5rem}
/* Kontakt */ /* Filter/Search/Tags */
.project-controls{display:flex;gap:1rem;align-items:center;flex-wrap:wrap;margin-bottom:var(--space-6)}
.project-search{
flex:1 1 280px;min-width:260px;border:1px solid var(--line);border-radius:999px;padding:.8rem 1rem;background:#0A0F16;color:var(--text)
}
.filter-chips{display:flex;gap:.5rem;flex-wrap:wrap}
.chip{
padding:.55rem .9rem;border-radius:999px;background:rgba(255,255,255,.04);color:var(--text);border:1px solid var(--line);cursor:pointer
}
.chip.is-active{background:var(--accent);color:var(--accent-ink);border-color:transparent}
.tags{display:flex;gap:.4rem;flex-wrap:wrap;list-style:none;padding:0;margin:.25rem 0 0}
.tags li{
display:inline-flex;align-items:center;justify-content:center;
height:30px;padding:0 .65rem;width:auto;border-radius:999px;
font-size:.85rem;line-height:1;background:rgba(255,255,255,.05);border:1px solid var(--line);color:var(--muted)
}
.project-card[hidden]{display:none !important}
/* ---------- Kontakt ---------- */
.contact .contact-form{ .contact .contact-form{
margin-top: var(--space-6); margin-top:var(--space-6);
display: grid; display:grid;
gap: var(--space-5); gap:var(--space-5);
grid-template-columns: 1fr 1fr; grid-template-columns:1fr 1fr;
align-items:start;
} }
.field{ display: grid; gap: .35rem; } .field{display:grid;gap:.35rem}
.field:nth-child(3){ grid-column: 1 / -1; } label{font-weight:700;color:var(--text)}
label{ font-weight: 700; color: var(--text); } /* Nachricht über zwei Spalten robust gegen Honeypot */
input, textarea{ .contact .contact-form > .field:nth-of-type(3){ grid-column:1 / -1; }
width: 100%;
border: 1px solid var(--line); input,textarea{
border-radius: 14px; width:100%;border:1px solid var(--line);border-radius:14px;padding:.85rem 1rem;font:inherit;background:#0A0F16;color:var(--text);
padding: .85rem 1rem; transition:border-color .2s,box-shadow .2s,background .2s;
font: inherit;
background: #0A0F16;
color: var(--text);
transition: border-color .2s ease, box-shadow .2s ease, background .2s ease;
} }
input::placeholder, textarea::placeholder{ color: #8A93A5; } input::placeholder,textarea::placeholder{color:#8A93A5}
input:focus, textarea:focus{ input:focus,textarea:focus{
border-color: color-mix(in oklab, var(--accent), white 15%); border-color:color-mix(in oklab,var(--accent),white 15%);
box-shadow: 0 0 0 6px color-mix(in oklab, var(--accent), black 75%); box-shadow:0 0 0 3px color-mix(in oklab,var(--accent),black 78%);
} }
/* Footer */ /* Senden-Button */
.footer{ .contact .contact-form .btn{
border-top: 1px solid var(--line); grid-column:1 / -1;
background: #0A0F16; justify-self:start; /* center => mittig, start => links */
padding: var(--space-6) 0; width:auto;
text-align: center; display:inline-flex; align-items:center;
color: var(--muted); padding:.8rem 1.2rem;
} }
/* ---------- Footer ---------- */
.footer{border-top:1px solid var(--line);background:#0A0F16;padding:var(--space-6) 0;text-align:center;color:var(--muted)}
.count{font-size:.9rem;color:var(--muted);margin-left:.5rem}
/* ---------- Responsive ---------- */ /* ---------- Responsive ---------- */
@media (max-width: 1000px){ @media (max-width:1000px){
.hero-card{ .hero-card{grid-template-columns:1fr;padding-top:clamp(2rem,6vw,3rem)}
grid-template-columns: 1fr; .hero-photo{position:static;display:block;margin:-3.0rem auto 0;transform:none;width:200px}
padding-top: clamp(2rem, 6vw, 3rem); .skills .skill-grid{grid-template-columns:repeat(2,1fr)}
} .project-list{grid-template-columns:repeat(2,1fr)}
.hero-photo{
position: static;
display: block;
margin: -3.5rem auto 0 auto;
transform: none;
width: 180px;
}
.skills .skill-grid{ grid-template-columns: repeat(2, 1fr); }
.project-list{ grid-template-columns: repeat(2, 1fr); }
} }
@media (max-width:640px){
@media (max-width: 640px){ .nav{display:none}
.nav{ display: none; } /* optional: auf sehr klein ausblenden */ .container{padding-inline:var(--space-5)}
.container{ padding-inline: var(--space-5); } .project-list,.skills .skill-grid{grid-template-columns:1fr}
.project-list, .skills .skill-grid{ grid-template-columns: 1fr; } .contact .contact-form{grid-template-columns:1fr}
.contact .contact-form{ grid-template-columns: 1fr; } .contact .contact-form > .field:nth-of-type(3){ grid-column:auto; }
.contact .contact-form .btn{ justify-self:stretch;width:100%; }
} }

View File

@ -1,27 +1,33 @@
<!DOCTYPE html> <!DOCTYPE html>
<html lang="en"> <html lang="de">
<head> <head>
<meta charset="UTF-8"> <meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta name="viewport" content="width=device-width, initial-scale=1" />
<title>Obai Albek | Portfolio</title> <title>Obai Albek | Portfolio</title>
<meta name="description" content="Portfolio von Obai Albeek - Informatik Student, Java Entwickler, Webentwicklung (HTML, CSS, JavaScript, PHP).">
<meta name="keywords" content="Obai Albeek, Portfolio, Informatik, Java, Webentwicklung, HTML, CSS, JavaScript, PHP, Projekte">
<meta name="author" content="Obai Albeek">
<link rel="icon" type="image/png" href="favicon.png">
<meta property="og:title" content="Obai Albeek | Portfolio">
<meta property="og:description" content="Schau dir meine Projekte in Java, Webentwicklung und Informatik an.">
<meta property="og:image" content="preview.png">
<meta property="og:url" content="https://deine-portfolio-url.com">
<meta property="og:type" content="website">
<link href="https://fonts.googleapis.com/css2?family=Roboto:wght@400;700&display=swap" rel="stylesheet">
<link rel="stylesheet" href="../css/style.css">
</head>
<body> <meta name="description" content="Portfolio von Obai Albek Informatik-Student, Java-Entwickler und Webentwicklung (HTML, CSS, JavaScript, PHP)." />
<meta name="keywords" content="Obai Albek, Portfolio, Informatik, Java, Webentwicklung, HTML, CSS, JavaScript, PHP, Projekte" />
<meta name="author" content="Obai Albek" />
<meta property="og:title" content="Obai Albek | Portfolio" />
<meta property="og:description" content="Schau dir meine Projekte in Java, Webentwicklung und Informatik an." />
<meta property="og:image" content="assets/preview.png" />
<meta property="og:url" content="https://deine-portfolio-url.com" />
<meta property="og:type" content="website" />
<link rel="icon" type="image/png" href="assets/favicon.png" />
<link href="https://fonts.googleapis.com/css2?family=Roboto:wght@400;700&display=swap" rel="stylesheet" />
<link rel="stylesheet" href="../css/style.css" />
<!-- Projekt-Filter (deine bestehende Datei) -->
<script src="../js/projektFilter.js" defer></script>
</head>
<body>
<!-- Sticky Topbar --> <!-- Sticky Topbar -->
<header class="topbar" role="banner"> <header class="topbar" role="banner">
<a class="brand" href="#home" aria-label="Start"> <a class="brand" href="#home" aria-label="Start">
<span class="brand-mark">OA</span><span class="brand-text">Obai Albeek</span> <span class="brand-mark">OA</span>
<span class="brand-text">Obai Albek</span>
</a> </a>
<nav class="nav" aria-label="Hauptnavigation"> <nav class="nav" aria-label="Hauptnavigation">
@ -32,49 +38,56 @@
</nav> </nav>
</header> </header>
<!-- Hero Section (Card + Overlap Image) --> <!-- Hero -->
<main id="home" class="hero" role="main"> <main id="home" class="hero" role="main">
<div class="hero-card" aria-labelledby="intro-title"> <div class="hero-card" aria-labelledby="intro-title">
<div class="hero-media"> <div class="hero-media">
<!-- Bild-Link absichtlich leer lassen --> <!-- eigenes Bild -->
<img class="hero-photo" src="../assets/myPhoto.jpeg" alt="Porträt von Obai Albeek"> <img class="hero-photo" src="../assets/myPhoto.jpeg" alt="Porträt von Obai Albek" />
</div> </div>
<div class="hero-copy"> <div class="hero-copy">
<p class="kicker">Hallo, ich bin</p> <p class="kicker">Hallo, ich bin</p>
<h1 id="intro-title" class="title">Obai Albek</h1> <h1 id="intro-title" class="title">Obai Albek</h1>
<p class="subtitle"> <p class="subtitle">
Informatik-Student & Java-Entwickler · Web (HTML, CSS, JavaScript, PHP, JQuery, React) Informatik-Student & Java-Entwickler · Web (HTML, CSS, JavaScript, PHP, jQuery, React)
</p> </p>
<div class="actions"> <div class="actions">
<a class="btn primary" href="#projects">Mein Lebenslauf</a> <a class="btn primary" href="../assets/Lebenslauf_Obai_Albek.pdf" target="_blank" rel="noopener" download>
<a class="btn ghost" href="#contact">Mit mir zusammenarbeiten</a> Mein Lebenslauf
</a>
</div> </div>
<!-- Social -->
<ul class="social" aria-label="Social Links"> <ul class="social" aria-label="Social Links">
<li><a class="iconlink" href="#" aria-label="E-Mail">@ <li>
</a></li> <a class="iconlink" href="https://github.com/ObaiAlbek" target="_blank" rel="noopener" aria-label="GitHub">
<li><a class="iconlink" href="#" aria-label="GitHub"> <svg width="22" height="22" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true">
<span aria-hidden="true">GH</span> <path d="M12 .5a12 12 0 0 0-3.79 23.4c.6.11.82-.26.82-.58 0-.29-.01-1.06-.02-2.08-3.34.73-4.04-1.61-4.04-1.61-.55-1.38-1.34-1.75-1.34-1.75-1.09-.75.08-.74.08-.74 1.2.08 1.83 1.24 1.83 1.24 1.07 1.84 2.8 1.31 3.48 1 .11-.78.42-1.31.76-1.61-2.67-.3-5.48-1.34-5.48-5.98 0-1.32.47-2.39 1.24-3.24-.12-.3-.54-1.52.12-3.16 0 0 1.01-.32 3.3 1.23a11.5 11.5 0 0 1 6 0c2.28-1.55 3.29-1.23 3.29-1.23.66 1.64.24 2.86.12 3.16.77.85 1.23 1.92 1.23 3.24 0 4.65-2.81 5.67-5.49 5.97.43.37.81 1.1.81 2.22 0 1.6-.02 2.88-.02 3.27 0 .32.21.7.83.58A12 12 0 0 0 12 .5Z"/>
</a></li> </svg>
<li><a class="iconlink" href="#" aria-label="LinkedIn"> </a>
<span aria-hidden="true">in</span> </li>
</a></li> <li>
<a class="iconlink" href="https://www.linkedin.com/in/obai-albek-85365a357" target="_blank" rel="noopener" aria-label="LinkedIn">
<svg width="22" height="22" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true">
<path d="M20.45 20.45h-3.55v-5.6c0-1.34-.02-3.06-1.87-3.06-1.87 0-2.16 1.46-2.16 2.96v5.7H9.32V9h3.41v1.56h.05c.47-.89 1.63-1.83 3.35-1.83 3.58 0 4.24 2.36 4.24 5.43v6.29ZM5.34 7.43a2.06 2.06 0 1 1 0-4.12 2.06 2.06 0 0 1 0 4.12ZM3.57 20.45h3.55V9H3.57v11.45Z"/>
</svg>
</a>
</li>
</ul> </ul>
</div> </div>
</div> </div>
</main> </main>
<!-- About Section --> <!-- About -->
<section id="about" class="section about"> <section id="about" class="section about">
<div class="container"> <div class="container">
<h2>Über mich</h2> <h2>Über mich</h2>
<p> <p>
Ich studiere Informatik an der TH&nbsp;Mannheim und baue saubere, Ich studiere Informatik an der TH Mannheim und baue saubere, wartbare Software.
wartbare Software. In Java fühle ich mich wie zu Hause In Java fühle ich mich zu Hause (OOP, Collections, Streams). Im Web arbeite ich
(OOP, Collections, Streams). Im Web arbeite ich gern full-stack full-stack mit HTML/CSS/JS und PHP (+SQL).
mit HTML/CSS/JS und PHP (+SQL).
</p> </p>
<ul class="highlights"> <ul class="highlights">
<li>Fokus: Java-Backend & Web-Apps</li> <li>Fokus: Java-Backend & Web-Apps</li>
@ -84,14 +97,14 @@
</div> </div>
</section> </section>
<!-- Skills Section --> <!-- Skills -->
<section id="skills" class="section skills" aria-labelledby="skills-title"> <section id="skills" class="section skills" aria-labelledby="skills-title">
<div class="container"> <div class="container">
<h2 id="skills-title">Skills</h2> <h2 id="skills-title">Skills</h2>
<div class="skill-grid"> <div class="skill-grid">
<article class="skill"> <article class="skill">
<h3>Java</h3> <h3>Programmiersprachen</h3>
<p>OOP, Streams, Collections, JDBC/JPA, Testing</p> <p>Java, C/C++, Go, Haskell, Python</p>
</article> </article>
<article class="skill"> <article class="skill">
<h3>Web</h3> <h3>Web</h3>
@ -109,58 +122,166 @@
</div> </div>
</section> </section>
<!-- Projects Section --> <!-- Projekte -->
<section id="projects" class="section projects" aria-labelledby="projects-title"> <section id="projects" class="section projects" aria-labelledby="projects-title">
<div class="container"> <div class="container">
<h2 id="projects-title">Ausgewählte Projekte</h2> <h2 id="projects-title">Projekte <span id="projectCount" class="count"></span></h2>
<div class="project-list"> <!-- Filter & Suche -->
<article class="project-card"> <div class="project-controls" role="region" aria-label="Projektfilter">
<div class="project-meta"> <input id="projectSearch" class="project-search" type="search"
<h3>Bibliotheksverwaltung (Java)</h3> placeholder="Suche nach Titel/Technologie …" aria-label="Projekte durchsuchen" />
<p class="meta">OOP · JDBC/JPA · MVC</p> <div class="filter-chips" role="group" aria-label="Technologie-Filter">
</div> <button class="chip is-active" data-filter="all">Alle</button>
<p>Ausleihe/Retouren, Gebührenlogik, Admin-Zahlungen, Katalog.</p> <button class="chip" data-filter="Java">Java</button>
<div class="project-actions"> <button class="chip" data-filter="Web">Web</button>
<a class="btn small" href="#" target="_blank" rel="noopener">Code</a> <button class="chip" data-filter="Go">Go</button>
<a class="btn small ghost" href="#" target="_blank" rel="noopener">Demo</a> <button class="chip" data-filter="UI/UX">UI/UX</button>
</div>
</article>
<article class="project-card">
<div class="project-meta">
<h3>Parking-Garage System (Java)</h3>
<p class="meta">CLI · Gebühren · Nummernschilder</p>
</div>
<p>Simulation von Ein/Ausfahrt, Zeitstempel, E-Auto-Rabatt.</p>
<div class="project-actions">
<a class="btn small" href="#" target="_blank" rel="noopener">Code</a>
<a class="btn small ghost" href="#" target="_blank" rel="noopener">Demo</a>
</div>
</article>
<article class="project-card">
<div class="project-meta">
<h3>Learning-Website (Full-Stack)</h3>
<p class="meta">HTML/CSS/JS · PHP · MySQL</p>
</div>
<p>Login, Kurse, Quiz, responsives Design, barrierearm.</p>
<div class="project-actions">
<a class="btn small" href="#" target="_blank" rel="noopener">Code</a>
<a class="btn small ghost" href="#" target="_blank" rel="noopener">Live</a>
</div>
</article>
</div> </div>
</div> </div>
<div class="project-list">
<!-- Local Chat -->
<article class="project-card" data-tags="Java,OOP,Maven,JUnit,GUI,Facade,Networking,Sockets,Domain">
<div class="project-meta">
<h3>Local Chat (Java, Maven, JUnit)</h3>
<p class="meta">GUI · Domain/Fassade · ChatServer (Sockets)</p>
</div>
<p>Lokaler Chat ohne DB: Benutzer/IDs, ChatRoom, Server/Client-Sockets, Unit-Tests, saubere Schichten (Domain, GUI, Fassade).</p>
<ul class="tags"><li>Java</li><li>Maven</li><li>JUnit</li><li>GUI</li><li>Fassade</li><li>Sockets</li></ul>
<div class="project-actions">
<a class="btn small" href="https://github.com/ObaiAlbek/MyLocalchat" target="_blank" rel="noopener">Code</a>
</div>
</article>
<!-- Bibliotheksverwaltung -->
<article class="project-card" data-tags="Java,OOP,JDBC,JPA,MVC,CLI,GUI">
<div class="project-meta">
<h3>Bibliotheksverwaltung (Java)</h3>
<p class="meta">OOP · JDBC/JPA · MVC</p>
</div>
<p>Ausleihe/Retouren, Gebühren, Admin-Zahlungen, Katalog.</p>
<ul class="tags"><li>Java</li><li>MVC</li><li>JPA</li></ul>
<div class="project-actions">
<a class="btn small" href="https://github.com/ObaiAlbek/Bibliotheksverwaltungssystem" target="_blank" rel="noopener">Code</a>
</div>
</article>
<!-- Parkhaus -->
<article class="project-card" data-tags="Java,MVC,Simulation">
<div class="project-meta">
<h3>Parkhaussimulator (Java)</h3>
<p class="meta">MVC · Gebühren · Nummernschilder</p>
</div>
<p>Ein/Ausfahrt, Zeitstempel, E-Auto-Rabatt, Abrechnung.</p>
<ul class="tags"><li>Java</li><li>Simulation</li></ul>
<div class="project-actions">
<a class="btn small" href="https://github.com/ObaiAlbek/Parkhaus" target="_blank" rel="noopener">Code</a>
</div>
</article>
<!-- E-Mail Client -->
<article class="project-card" data-tags="Java,GUI,MVC">
<div class="project-meta">
<h3>E-Mail Client (Java, GUI, MVC)</h3>
<p class="meta">GUI · MVC</p>
</div>
<p>GUI-basierte Mail-App mit sauberer Schichtentrennung.</p>
<ul class="tags"><li>Java</li><li>GUI</li><li>MVC</li></ul>
<div class="project-actions">
<a class="btn small" href="https://github.com/ObaiAlbek/SimpleMailSystem" target="_blank" rel="noopener">Code</a>
</div>
</article>
<!-- Flughafensimulation -->
<article class="project-card" data-tags="Go,Concurrency,Channels,Goroutines">
<div class="project-meta">
<h3>Flughafensimulation (Go)</h3>
<p class="meta">Goroutines · Channels</p>
</div>
<p>Modellierung von Abfertigung/Security/Gates mit Concurrency-Mustern.</p>
<ul class="tags"><li>Go</li><li>Concurrency</li></ul>
<div class="project-actions">
<a class="btn small" href="https://github.com/ObaiAlbek/Flughafensimulation" target="_blank" rel="noopener">Code</a>
</div>
</article>
<!-- Lernplattform -->
<article class="project-card" data-tags="Web,HTML,CSS,JavaScript,PHP,SQL,Auth">
<div class="project-meta">
<h3>Lernplattform (Full-Stack)</h3>
<p class="meta">HTML/CSS/JS · PHP · MySQL</p>
</div>
<p>Login, Kurse, Quiz, responsives Design.</p>
<ul class="tags"><li>Web</li><li>PHP</li><li>MySQL</li></ul>
<div class="project-actions">
<a class="btn small" href="#" target="_blank" rel="noopener">Code</a>
<a class="btn small ghost" href="https://github.com/ObaiAlbek/HTML-CSS_Lernwebseite" target="_blank" rel="noopener">Live</a>
</div>
</article>
<!-- UI/UX -->
<article class="project-card" data-tags="UI/UX,Figma,Design">
<div class="project-meta">
<h3>Urlaub-Transport-App (UI/UX)</h3>
<p class="meta">Figma · User Flows</p>
</div>
<p>App-Design für Transportbuchungen, Fokus auf Usability.</p>
<ul class="tags"><li>UI/UX</li><li>Figma</li></ul>
<div class="project-actions">
<a class="btn small ghost" href="https://github.com/ObaiAlbek/Urlaub-Transport-App" target="_blank" rel="noopener">Mockups</a>
</div>
</article>
<!-- Spiele -->
<article class="project-card" data-tags="Java,GUI,Games">
<div class="project-meta">
<h3>Spiele (Java GUI)</h3>
<p class="meta">Tic-Tac-Toe · Pong · Hitori</p>
</div>
<p>Kleine Spiele mit Java-GUI und sauberem OOP-Design.</p>
<ul class="tags"><li>Java</li><li>GUI</li></ul>
<div class="project-actions">
<a class="btn small" href="https://github.com/ObaiAlbek/MyHitori-Spiel" target="_blank" rel="noopener">Code</a>
</div>
</article>
<!-- React Auth -->
<article class="project-card" data-tags="Web,React,JavaScript,Auth">
<div class="project-meta">
<h3>React Auth Form</h3>
<p class="meta">React · useState · Conditional Rendering</p>
</div>
<p>Login/Signup-Formular mit Validation und CSS.</p>
<ul class="tags"><li>Web</li><li>React</li></ul>
<div class="project-actions">
<a class="btn small" href="https://github.com/ObaiAlbek/reactAuthForm" target="_blank" rel="noopener">Code</a>
</div>
</article>
<!-- Parallel -->
<article class="project-card" data-tags="Java,Threads,Locks,Concurrency">
<div class="project-meta">
<h3>Parallelprogrammierung (Java)</h3>
<p class="meta">Dining Philosophers · ThreadsafeSimplifiedList&lt;T&gt;</p>
</div>
<p>Concurrency mit Threads/Locks und Hand-over-Hand-Locking.</p>
<ul class="tags"><li>Java</li><li>Concurrency</li></ul>
<div class="project-actions">
<a class="btn small" href="https://github.com/ObaiAlbek/concurrency-dining-philosophers" target="_blank" rel="noopener">Code</a>
</div>
</article>
</div>
</div>
</section> </section>
<!-- Contact Section --> <!-- Kontakt -->
<section id="contact" class="section contact" aria-labelledby="contact-title"> <section id="contact" class="section contact" aria-labelledby="contact-title">
<div class="container"> <div class="container">
<h2 id="contact-title">Kontakt</h2> <h2 id="contact-title">Kontakt</h2>
<p>Schreib mir deine Nachricht</p> <p>Schreib mir deine Nachricht</p>
<form class="contact-form" action="#" method="post"> <form id="contactForm" class="contact-form" action="php/send.php" method="post" novalidate>
<div class="field"> <div class="field">
<label for="name">Name</label> <label for="name">Name</label>
<input id="name" name="name" type="text" autocomplete="name" required> <input id="name" name="name" type="text" autocomplete="name" required>
@ -173,20 +294,28 @@
<label for="msg">Nachricht</label> <label for="msg">Nachricht</label>
<textarea id="msg" name="message" rows="5" required></textarea> <textarea id="msg" name="message" rows="5" required></textarea>
</div> </div>
<button class="btn primary" type="submit">Nachricht senden</button>
<div id="formAlert" aria-live="polite" style="margin:.25rem 0 0;color:var(--muted)"></div>
<button class="btn primary" type="submit" id="sendBtn">Nachricht senden</button>
<!-- Honeypot (unsichtbar, am Ende → stört Layout nicht) -->
<input type="text" name="website" id="website" class="hp" tabindex="-1" autocomplete="off" aria-hidden="true"
style="position:absolute;left:-9999px;top:auto;width:1px;height:1px;overflow:hidden">
</form> </form>
</div> </div>
</section> </section>
<footer class="footer" role="contentinfo"> <footer class="footer" role="contentinfo">
<div class="container"> <div class="container">
<p>© <span id="year"></span> Obai Albeek · Alle Rechte vorbehalten.</p> <p>© <span id="year"></span> Obai Albek · Alle Rechte vorbehalten.</p>
</div> </div>
<script> <script>
// kleines JS: Jahr automatisch
document.getElementById('year').textContent = new Date().getFullYear(); document.getElementById('year').textContent = new Date().getFullYear();
</script> </script>
<!-- Kontakt-Handler (Validation + Fetch) -->
<script src="../js/contact.js" defer></script>
</footer> </footer>
</body> </body>
</html>
</html>

26
html/thanks.html 100644
View File

@ -0,0 +1,26 @@
<!DOCTYPE html>
<html lang="de">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>Danke!</title>
<link rel="stylesheet" href="../css/style.css" />
<style>
.center-wrap{min-height:60vh;display:grid;place-items:center;padding:2rem}
.thanks{
max-width:680px;margin:auto;background:var(--surface);border:1px solid var(--line);
border-radius:var(--radius);padding:2rem;box-shadow:var(--shadow);text-align:center;
}
.thanks h1{margin-top:0}
</style>
</head>
<body>
<div class="center-wrap">
<div class="thanks">
<h1>Danke für dein Feedback! 🙌</h1>
<p>Deine Nachricht wurde erfolgreich gesendet.</p>
<a href="myPortfolio.html" class="btn primary" style="display:inline-block;margin-top:1rem">Zur Startseite</a>
</div>
</div>
</body>
</html>

67
js/contact.js 100644
View File

@ -0,0 +1,67 @@
/* js/contact.js */
(() => {
const $ = s => document.querySelector(s);
const form = $('#contactForm');
const nameI = $('#name');
const mailI = $('#email');
const msgI = $('#msg');
const hp = $('#website'); // Honeypot
const alert = $('#formAlert');
const btn = $('#sendBtn');
if (!form) return;
const emailRx = /^[^\s@]+@[^\s@]+\.[^\s@]{2,}$/i;
const setAlert = (msg, good=false) => {
if (!alert) return;
alert.textContent = msg || '';
alert.style.color = good ? 'var(--accent)' : '#ff9aa2';
};
function validate() {
const errs = [];
if (!nameI.value.trim() || nameI.value.trim().length < 2) errs.push('Bitte gib einen gültigen Namen ein.');
if (!emailRx.test(mailI.value.trim())) errs.push('Bitte gib eine gültige E-Mail an.');
if (!msgI.value.trim() || msgI.value.trim().length < 10) errs.push('Nachricht bitte mit mindestens 10 Zeichen.');
if (hp.value) errs.push('Spam erkannt.');
return errs;
}
form.addEventListener('submit', async (e) => {
e.preventDefault();
setAlert('');
const errs = validate();
if (errs.length) {
setAlert(errs[0]);
return;
}
btn.disabled = true;
const orig = btn.textContent;
btn.textContent = 'Wird gesendet …';
try {
const fd = new FormData(form);
const res = await fetch(form.getAttribute('action') || 'php/send.php', {
method: 'POST',
body: fd,
headers: {'Accept': 'application/json'}
});
const data = await res.json().catch(() => ({}));
if (!res.ok || !data.ok) {
throw new Error(data.error || 'Senden fehlgeschlagen.');
}
// Erfolg → Danke-Seite
window.location.href = 'thanks.html';
} catch (err) {
setAlert(err.message || 'Es ist ein Fehler aufgetreten.');
btn.disabled = false;
btn.textContent = orig;
}
});
})();

View File

@ -0,0 +1,56 @@
/* projects.js Suche + Filter für Projekte */
(() => {
const $ = s => document.querySelector(s);
const $$ = s => Array.from(document.querySelectorAll(s));
const search = $('#projectSearch');
const chips = $$('.chip');
const cards = $$('.project-card');
let active = localStorage.getItem('proj.filter') || 'all';
const norm = s => (s || '').toLowerCase();
function apply() {
const term = norm(search?.value);
cards.forEach(card => {
const tags = norm(card.dataset.tags);
const title = norm(card.querySelector('h3')?.textContent);
const text = norm(card.textContent);
const inFilter = active === 'all' || tags.includes(norm(active));
const inSearch = !term || tags.includes(term) || title.includes(term) || text.includes(term);
card.hidden = !(inFilter && inSearch);
});
chips.forEach(c => c.classList.toggle('is-active', (c.dataset.filter || 'all') === active));
const counter = $('#projectCount');
if (counter) counter.textContent = cards.filter(c => !c.hidden).length;
}
function setFilter(f) {
active = f || 'all';
localStorage.setItem('proj.filter', active);
apply();
}
chips.forEach(ch => ch.addEventListener('click', () => setFilter(ch.dataset.filter)));
if (search) {
let t;
search.addEventListener('input', () => {
clearTimeout(t);
t = setTimeout(apply, 120);
});
search.addEventListener('keydown', (e) => {
if (e.key === 'Escape') { search.value = ''; apply(); }
});
}
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', () => setFilter(active));
} else {
setFilter(active);
}
})();

8
php/.env 100644
View File

@ -0,0 +1,8 @@
SMTP_HOST=smtp.deine-domain.de
SMTP_PORT=587
SMTP_USER=dein-smtp-user
SMTP_PASS=dein-smtp-passwort
MAIL_FROM=no-reply@deine-domain.de
MAIL_FROM_NAME="Portfolio Kontakt"
MAIL_TO=deinpostfach@deine-domain.de

86
php/send.php 100644
View File

@ -0,0 +1,86 @@
<?php
// php/send.php
declare(strict_types=1);
use PHPMailer\PHPMailer\PHPMailer;
use PHPMailer\PHPMailer\Exception;
require __DIR__ . '/../vendor/autoload.php';
header('Content-Type: application/json; charset=UTF-8');
try {
// ENV laden (SMTP-Daten außerhalb des Codes)
$dotenv = Dotenv\Dotenv::createImmutable(__DIR__ . '/..');
$dotenv->safeLoad();
// Nur POST zulassen
if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
http_response_code(405);
echo json_encode(['ok' => false, 'error' => 'Method not allowed']);
exit;
}
// Honeypot (Bot check)
if (!empty($_POST['website'])) {
http_response_code(400);
echo json_encode(['ok' => false, 'error' => 'Spam detected']);
exit;
}
// Daten einsammeln & validieren
$name = trim((string)($_POST['name'] ?? ''));
$email = trim((string)($_POST['email'] ?? ''));
$message = trim((string)($_POST['message'] ?? ''));
if (mb_strlen($name) < 2) throw new Exception('Bitte einen gültigen Namen eingeben.');
if (!filter_var($email, FILTER_VALIDATE_EMAIL)) throw new Exception('Bitte eine gültige E-Mail angeben.');
if (mb_strlen($message) < 10) throw new Exception('Nachricht ist zu kurz.');
// Mailer konfigurieren
$mail = new PHPMailer(true);
$mail->isSMTP();
$mail->Host = $_ENV['SMTP_HOST'] ?? 'smtp.example.com';
$mail->SMTPAuth = true;
$mail->Username = $_ENV['SMTP_USER'] ?? 'user@example.com';
$mail->Password = $_ENV['SMTP_PASS'] ?? 'secret';
$mail->Port = (int)($_ENV['SMTP_PORT'] ?? 587);
$mail->SMTPSecure = PHPMailer::ENCRYPTION_STARTTLS;
// Absender & Empfänger
// Wichtig: setFrom sollte eine Domain nutzen, die zu deinem SMTP passt.
$fromEmail = $_ENV['MAIL_FROM'] ?? 'no-reply@deine-domain.de';
$fromName = $_ENV['MAIL_FROM_NAME'] ?? 'Portfolio Kontakt';
$toEmail = $_ENV['MAIL_TO'] ?? 'ich@deine-domain.de';
$mail->setFrom($fromEmail, $fromName);
$mail->addAddress($toEmail, 'Obai Albek');
$mail->addReplyTo($email, $name); // Antworten gehen an den Absender
// Inhalt
$mail->isHTML(true);
$mail->Subject = 'Neue Nachricht über das Kontaktformular';
$body = sprintf('<h2>Neue Nachricht</h2>
<p><strong>Name:</strong> %s</p>
<p><strong>E-Mail:</strong> %s</p>
<p><strong>Nachricht:</strong><br>%s</p>
<hr>
<small>IP: %s &middot; %s</small>',
htmlspecialchars($name, ENT_QUOTES, 'UTF-8'),
htmlspecialchars($email, ENT_QUOTES, 'UTF-8'),
nl2br(htmlspecialchars($message, ENT_QUOTES, 'UTF-8')),
$_SERVER['REMOTE_ADDR'] ?? 'n/a',
date('Y-m-d H:i:s')
);
$mail->Body = $body;
$mail->AltBody = "Neue Nachricht\n\nName: $name\nE-Mail: $email\n\n$message";
// Senden
$mail->send();
echo json_encode(['ok' => true]);
} catch (Exception $e) {
http_response_code(422);
echo json_encode(['ok' => false, 'error' => $e->getMessage()]);
}