process
parent
2b0113b25b
commit
753702aa69
Binary file not shown.
421
css/style.css
421
css/style.css
|
@ -1,331 +1,186 @@
|
|||
/* =========================================
|
||||
Obai Albeek – Portfolio Styles (Dark)
|
||||
Palette: Onyx + Jade | v2
|
||||
Obai Albek – Portfolio Styles (Dark)
|
||||
Palette: Onyx + Jade | v3
|
||||
========================================= */
|
||||
|
||||
/* ---------- CSS Variables ---------- */
|
||||
:root{
|
||||
--bg: #0E1116; /* Onyx (Seitenhintergrund) */
|
||||
--surface: #101721; /* Dunkle Card/Fläche */
|
||||
--surface-2: #0B111A; /* noch dunklere Fläche */
|
||||
--text: #E6E9EF; /* Primärtext hell */
|
||||
--muted: #A8B0BF; /* Sekundärtext */
|
||||
--line: rgba(255,255,255,.08);
|
||||
|
||||
--accent: #12B3A4; /* Jade/Türkis */
|
||||
--accent-ink: #061014; /* Text auf Akzent (dunkel, angenehmer als weiß) */
|
||||
|
||||
--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;
|
||||
--bg:#0E1116; --surface:#101721; --surface-2:#0B111A;
|
||||
--text:#E6E9EF; --muted:#A8B0BF; --line:rgba(255,255,255,.08);
|
||||
--accent:#12B3A4; --accent-ink:#061014;
|
||||
--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 ---------- */
|
||||
*,
|
||||
*::before,
|
||||
*::after{ box-sizing: border-box; }
|
||||
html, body{ height: 100%; scroll-behavior: smooth; }
|
||||
|
||||
*,*::before,*::after{box-sizing:border-box}
|
||||
html,body{height:100%;scroll-behavior:smooth}
|
||||
body{
|
||||
margin: 0;
|
||||
font-family: "Roboto", system-ui, -apple-system, Segoe UI, Arial, sans-serif;
|
||||
color: var(--text);
|
||||
margin:0;font-family:"Roboto",system-ui,-apple-system,Segoe UI,Arial,sans-serif;
|
||||
color:var(--text);
|
||||
background:
|
||||
radial-gradient(900px 600px at 20% -10%, #121925 0%, transparent 55%),
|
||||
radial-gradient(1100px 700px at 110% -20%, #0F1721 0%, transparent 60%),
|
||||
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{
|
||||
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;
|
||||
top: 0;
|
||||
z-index: 50;
|
||||
display: flex; /* NEU: Flex-Layout */
|
||||
align-items: center;
|
||||
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;
|
||||
position:sticky;top:0;z-index:50;display:flex;align-items:center;justify-content:space-between;
|
||||
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);
|
||||
}
|
||||
.topbar .brand{display:flex;align-items:center;gap:.6rem;text-decoration:none}
|
||||
.brand-mark{
|
||||
display: inline-grid;
|
||||
place-items: center;
|
||||
width: 36px; height: 36px;
|
||||
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;
|
||||
display:inline-grid;place-items:center;width:36px;height:36px;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;letter-spacing:.2px}
|
||||
.nav{margin-left:auto;display:flex;gap:.6rem;align-items:center}
|
||||
.nav-link{
|
||||
--pad-x: 1rem;
|
||||
display: inline-block;
|
||||
padding: .55rem var(--pad-x);
|
||||
text-decoration: none;
|
||||
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%);
|
||||
--pad-x:1rem;display:inline-block;padding:.55rem var(--pad-x);text-decoration:none;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%)}
|
||||
|
||||
/* ---------- Hero ---------- */
|
||||
.hero{ padding: clamp(3rem, 5vw, 5rem) var(--space-7); }
|
||||
|
||||
.hero{ padding: clamp(2rem, 3.6vw, 3rem) var(--space-7); }
|
||||
.hero-card{
|
||||
position: relative;
|
||||
display: grid;
|
||||
grid-template-columns: 1.1fr 1.4fr;
|
||||
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);
|
||||
position:relative;display:grid;grid-template-columns:1.05fr 1.6fr;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.2rem,2.4vw,1.9rem);box-shadow:var(--shadow);
|
||||
}
|
||||
|
||||
.hero-media{ position: relative; min-height: 240px; }
|
||||
|
||||
.hero-media{position:relative;min-height:clamp(220px, 24vw, 300px)}
|
||||
.hero-photo{
|
||||
position: absolute;
|
||||
inset: auto 0 0 50%;
|
||||
transform: translate(-50%, 18%) scale(1.02);
|
||||
width: min(320px, 78%);
|
||||
aspect-ratio: 1/1;
|
||||
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;
|
||||
position:absolute;inset:auto 0 0 50%;transform:translate(-50%,6%) scale(1);
|
||||
width:clamp(185px, 24vw, 300px);aspect-ratio:1/1;object-fit:cover;border-radius:50%;
|
||||
border:12px solid var(--surface);
|
||||
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%);
|
||||
background:#111;
|
||||
}
|
||||
|
||||
/* Textblock */
|
||||
.kicker{ margin: 0 0 .25rem 0; color: var(--muted); letter-spacing: .4px; }
|
||||
.title{
|
||||
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 ---------- */
|
||||
.kicker{margin:0 0 .25rem;color:var(--muted);letter-spacing:.4px}
|
||||
.title{margin:0 0 .35rem;font-size:clamp(2rem,5vw,3.1rem);line-height:1.15;color:var(--text)}
|
||||
.subtitle{margin:0 0 var(--space-7);color:var(--muted)}
|
||||
.actions{display:flex;flex-wrap:wrap;gap:.75rem;margin-bottom:var(--space-6)}
|
||||
.btn{
|
||||
--px: 1.1rem;
|
||||
display: inline-block;
|
||||
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;
|
||||
--px:1.1rem;display:inline-block;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}
|
||||
|
||||
/* ---------- Social ---------- */
|
||||
.social{ display:flex; gap:.5rem; margin:0; padding:0; list-style:none; }
|
||||
.iconlink{
|
||||
display: inline-grid;
|
||||
place-items: center;
|
||||
width: 40px; height: 40px;
|
||||
border-radius: 12px;
|
||||
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);
|
||||
.social{display:flex;gap:.5rem;margin:0;padding:0;list-style:none}
|
||||
.hero .social{margin-top:var(--space-7)}
|
||||
a.iconlink{
|
||||
display:inline-grid;place-items:center;width:44px;height:44px;border-radius:12px;border:1px solid var(--line);
|
||||
background:rgba(255,255,255,.03);color:var(--text);text-decoration:none;box-shadow:var(--shadow-sm);
|
||||
transition:background .2s ease,border-color .2s ease,transform .06s ease;
|
||||
}
|
||||
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 ---------- */
|
||||
.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{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)}
|
||||
.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 */
|
||||
.about .highlights{
|
||||
display: grid;
|
||||
grid-template-columns: 1fr;
|
||||
gap: .5rem;
|
||||
padding: 0; margin: var(--space-6) 0 0 0;
|
||||
list-style: "✓ ";
|
||||
}
|
||||
.about .highlights li{ padding-left: .4rem; color: var(--text); }
|
||||
/* ---------- Skills ---------- */
|
||||
.skills .skill-grid{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}
|
||||
.skill p{color:var(--muted);margin:0}
|
||||
|
||||
/* Skills */
|
||||
.skills .skill-grid{
|
||||
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);
|
||||
}
|
||||
/* ---------- Projekte ---------- */
|
||||
.project-list{display:grid;gap:var(--space-6);grid-template-columns:repeat(3,1fr)}
|
||||
.project-card{
|
||||
background: var(--surface);
|
||||
border: 1px solid var(--line);
|
||||
border-radius: calc(var(--radius) - 6px);
|
||||
padding: var(--space-7);
|
||||
display: grid;
|
||||
gap: .9rem;
|
||||
box-shadow: var(--shadow-sm);
|
||||
background:var(--surface);border:1px solid var(--line);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-actions{ display: flex; gap: .5rem; }
|
||||
.project-card .meta{color:var(--muted);margin:.25rem 0 0}
|
||||
.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{
|
||||
margin-top: var(--space-6);
|
||||
display: grid;
|
||||
gap: var(--space-5);
|
||||
grid-template-columns: 1fr 1fr;
|
||||
margin-top:var(--space-6);
|
||||
display:grid;
|
||||
gap:var(--space-5);
|
||||
grid-template-columns:1fr 1fr;
|
||||
align-items:start;
|
||||
}
|
||||
.field{ display: grid; gap: .35rem; }
|
||||
.field:nth-child(3){ grid-column: 1 / -1; }
|
||||
label{ font-weight: 700; color: var(--text); }
|
||||
input, textarea{
|
||||
width: 100%;
|
||||
border: 1px solid var(--line);
|
||||
border-radius: 14px;
|
||||
padding: .85rem 1rem;
|
||||
font: inherit;
|
||||
background: #0A0F16;
|
||||
color: var(--text);
|
||||
transition: border-color .2s ease, box-shadow .2s ease, background .2s ease;
|
||||
.field{display:grid;gap:.35rem}
|
||||
label{font-weight:700;color:var(--text)}
|
||||
/* Nachricht über zwei Spalten – robust gegen Honeypot */
|
||||
.contact .contact-form > .field:nth-of-type(3){ grid-column:1 / -1; }
|
||||
|
||||
input,textarea{
|
||||
width:100%;border:1px solid var(--line);border-radius:14px;padding:.85rem 1rem;font:inherit;background:#0A0F16;color:var(--text);
|
||||
transition:border-color .2s,box-shadow .2s,background .2s;
|
||||
}
|
||||
input::placeholder, textarea::placeholder{ color: #8A93A5; }
|
||||
input:focus, textarea:focus{
|
||||
border-color: color-mix(in oklab, var(--accent), white 15%);
|
||||
box-shadow: 0 0 0 6px color-mix(in oklab, var(--accent), black 75%);
|
||||
input::placeholder,textarea::placeholder{color:#8A93A5}
|
||||
input:focus,textarea:focus{
|
||||
border-color:color-mix(in oklab,var(--accent),white 15%);
|
||||
box-shadow:0 0 0 3px color-mix(in oklab,var(--accent),black 78%);
|
||||
}
|
||||
|
||||
/* Footer */
|
||||
.footer{
|
||||
border-top: 1px solid var(--line);
|
||||
background: #0A0F16;
|
||||
padding: var(--space-6) 0;
|
||||
text-align: center;
|
||||
color: var(--muted);
|
||||
/* Senden-Button */
|
||||
.contact .contact-form .btn{
|
||||
grid-column:1 / -1;
|
||||
justify-self:start; /* center => mittig, start => links */
|
||||
width:auto;
|
||||
display:inline-flex; align-items:center;
|
||||
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 ---------- */
|
||||
@media (max-width: 1000px){
|
||||
.hero-card{
|
||||
grid-template-columns: 1fr;
|
||||
padding-top: clamp(2rem, 6vw, 3rem);
|
||||
}
|
||||
.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:1000px){
|
||||
.hero-card{grid-template-columns:1fr;padding-top:clamp(2rem,6vw,3rem)}
|
||||
.hero-photo{position:static;display:block;margin:-3.0rem auto 0;transform:none;width:200px}
|
||||
.skills .skill-grid{grid-template-columns:repeat(2,1fr)}
|
||||
.project-list{grid-template-columns:repeat(2,1fr)}
|
||||
}
|
||||
|
||||
@media (max-width: 640px){
|
||||
.nav{ display: none; } /* optional: auf sehr klein ausblenden */
|
||||
.container{ padding-inline: var(--space-5); }
|
||||
.project-list, .skills .skill-grid{ grid-template-columns: 1fr; }
|
||||
.contact .contact-form{ grid-template-columns: 1fr; }
|
||||
@media (max-width:640px){
|
||||
.nav{display:none}
|
||||
.container{padding-inline:var(--space-5)}
|
||||
.project-list,.skills .skill-grid{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%; }
|
||||
}
|
||||
|
|
|
@ -1,27 +1,33 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<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>
|
||||
<html lang="de">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<title>Obai Albek | Portfolio</title>
|
||||
|
||||
<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 -->
|
||||
<header class="topbar" role="banner">
|
||||
<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>
|
||||
|
||||
<nav class="nav" aria-label="Hauptnavigation">
|
||||
|
@ -32,49 +38,56 @@
|
|||
</nav>
|
||||
</header>
|
||||
|
||||
<!-- Hero Section (Card + Overlap Image) -->
|
||||
<!-- Hero -->
|
||||
<main id="home" class="hero" role="main">
|
||||
<div class="hero-card" aria-labelledby="intro-title">
|
||||
<div class="hero-media">
|
||||
<!-- Bild-Link absichtlich leer lassen -->
|
||||
<img class="hero-photo" src="../assets/myPhoto.jpeg" alt="Porträt von Obai Albeek">
|
||||
<!-- eigenes Bild -->
|
||||
<img class="hero-photo" src="../assets/myPhoto.jpeg" alt="Porträt von Obai Albek" />
|
||||
</div>
|
||||
|
||||
<div class="hero-copy">
|
||||
<p class="kicker">Hallo, ich bin</p>
|
||||
<h1 id="intro-title" class="title">Obai Albek</h1>
|
||||
<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>
|
||||
|
||||
<div class="actions">
|
||||
<a class="btn primary" href="#projects">Mein Lebenslauf</a>
|
||||
<a class="btn ghost" href="#contact">Mit mir zusammenarbeiten</a>
|
||||
<a class="btn primary" href="../assets/Lebenslauf_Obai_Albek.pdf" target="_blank" rel="noopener" download>
|
||||
Mein Lebenslauf
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<!-- Social -->
|
||||
<ul class="social" aria-label="Social Links">
|
||||
<li><a class="iconlink" href="#" aria-label="E-Mail">@
|
||||
</a></li>
|
||||
<li><a class="iconlink" href="#" aria-label="GitHub">
|
||||
<span aria-hidden="true">GH</span>
|
||||
</a></li>
|
||||
<li><a class="iconlink" href="#" aria-label="LinkedIn">
|
||||
<span aria-hidden="true">in</span>
|
||||
</a></li>
|
||||
<li>
|
||||
<a class="iconlink" href="https://github.com/ObaiAlbek" target="_blank" rel="noopener" aria-label="GitHub">
|
||||
<svg width="22" height="22" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true">
|
||||
<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"/>
|
||||
</svg>
|
||||
</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>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
|
||||
<!-- About Section -->
|
||||
<!-- About -->
|
||||
<section id="about" class="section about">
|
||||
<div class="container">
|
||||
<h2>Über mich</h2>
|
||||
<p>
|
||||
Ich studiere Informatik an der TH Mannheim und baue saubere,
|
||||
wartbare Software. In Java fühle ich mich wie zu Hause
|
||||
(OOP, Collections, Streams). Im Web arbeite ich gern full-stack
|
||||
mit HTML/CSS/JS und PHP (+SQL).
|
||||
Ich studiere Informatik an der TH Mannheim und baue saubere, wartbare Software.
|
||||
In Java fühle ich mich zu Hause (OOP, Collections, Streams). Im Web arbeite ich
|
||||
full-stack mit HTML/CSS/JS und PHP (+SQL).
|
||||
</p>
|
||||
<ul class="highlights">
|
||||
<li>Fokus: Java-Backend & Web-Apps</li>
|
||||
|
@ -84,14 +97,14 @@
|
|||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Skills Section -->
|
||||
<!-- Skills -->
|
||||
<section id="skills" class="section skills" aria-labelledby="skills-title">
|
||||
<div class="container">
|
||||
<h2 id="skills-title">Skills</h2>
|
||||
<div class="skill-grid">
|
||||
<article class="skill">
|
||||
<h3>Java</h3>
|
||||
<p>OOP, Streams, Collections, JDBC/JPA, Testing</p>
|
||||
<h3>Programmiersprachen</h3>
|
||||
<p>Java, C/C++, Go, Haskell, Python</p>
|
||||
</article>
|
||||
<article class="skill">
|
||||
<h3>Web</h3>
|
||||
|
@ -109,58 +122,166 @@
|
|||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Projects Section -->
|
||||
<!-- Projekte -->
|
||||
<section id="projects" class="section projects" aria-labelledby="projects-title">
|
||||
<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">
|
||||
<article class="project-card">
|
||||
<div class="project-meta">
|
||||
<h3>Bibliotheksverwaltung (Java)</h3>
|
||||
<p class="meta">OOP · JDBC/JPA · MVC</p>
|
||||
</div>
|
||||
<p>Ausleihe/Retouren, Gebührenlogik, Admin-Zahlungen, Katalog.</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>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>
|
||||
<!-- Filter & Suche -->
|
||||
<div class="project-controls" role="region" aria-label="Projektfilter">
|
||||
<input id="projectSearch" class="project-search" type="search"
|
||||
placeholder="Suche nach Titel/Technologie …" aria-label="Projekte durchsuchen" />
|
||||
<div class="filter-chips" role="group" aria-label="Technologie-Filter">
|
||||
<button class="chip is-active" data-filter="all">Alle</button>
|
||||
<button class="chip" data-filter="Java">Java</button>
|
||||
<button class="chip" data-filter="Web">Web</button>
|
||||
<button class="chip" data-filter="Go">Go</button>
|
||||
<button class="chip" data-filter="UI/UX">UI/UX</button>
|
||||
</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<T></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>
|
||||
|
||||
<!-- Contact Section -->
|
||||
<!-- Kontakt -->
|
||||
<section id="contact" class="section contact" aria-labelledby="contact-title">
|
||||
<div class="container">
|
||||
<h2 id="contact-title">Kontakt</h2>
|
||||
<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">
|
||||
<label for="name">Name</label>
|
||||
<input id="name" name="name" type="text" autocomplete="name" required>
|
||||
|
@ -173,20 +294,28 @@
|
|||
<label for="msg">Nachricht</label>
|
||||
<textarea id="msg" name="message" rows="5" required></textarea>
|
||||
</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>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<footer class="footer" role="contentinfo">
|
||||
<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>
|
||||
<script>
|
||||
// kleines JS: Jahr automatisch
|
||||
document.getElementById('year').textContent = new Date().getFullYear();
|
||||
</script>
|
||||
|
||||
<!-- Kontakt-Handler (Validation + Fetch) -->
|
||||
<script src="../js/contact.js" defer></script>
|
||||
</footer>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
</html>
|
||||
|
|
|
@ -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>
|
|
@ -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;
|
||||
}
|
||||
});
|
||||
})();
|
|
@ -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);
|
||||
}
|
||||
})();
|
|
@ -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
|
|
@ -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 · %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()]);
|
||||
}
|
Loading…
Reference in New Issue