newsbot/index.html
2026-04-14 11:21:55 +00:00

859 lines
26 KiB
HTML

<!DOCTYPE html>
<html lang="de">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>noctura.dev</title>
<link rel="preconnect" href="https://fonts.googleapis.com" />
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
<link href="https://fonts.googleapis.com/css2?family=Syne:wght@400;500;600;700;800&family=DM+Mono:ital,wght@0,300;0,400;1,300&display=swap" rel="stylesheet" />
<style>
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
:root {
--bg: #07080f;
--bg2: #0d0f1c;
--bg3: #111428;
--bg4: #171a31;
--surface: rgba(255,255,255,0.04);
--border: rgba(255,255,255,0.07);
--border-hover: rgba(255,255,255,0.14);
--text: #e8eaf2;
--muted: #8d93ad;
--accent: #7c6dfa;
--accent2: #4fd1c5;
--accent3: #f7886a;
--glow: rgba(124,109,250,0.12);
--font: 'Syne', sans-serif;
--mono: 'DM Mono', monospace;
}
html { scroll-behavior: smooth; }
body {
background: var(--bg);
color: var(--text);
font-family: var(--font);
line-height: 1.6;
min-height: 100vh;
overflow-x: hidden;
}
/* --- STAR FIELD --- */
#stars {
position: fixed;
inset: 0;
pointer-events: none;
z-index: 0;
}
/* --- NEBULA GLOW --- */
.nebula {
position: fixed;
border-radius: 50%;
filter: blur(120px);
pointer-events: none;
z-index: 0;
}
.nebula-1 { width: 600px; height: 600px; background: radial-gradient(circle, rgba(124,109,250,0.08) 0%, transparent 70%); top: -100px; left: -100px; }
.nebula-2 { width: 500px; height: 500px; background: radial-gradient(circle, rgba(79,209,197,0.06) 0%, transparent 70%); bottom: 200px; right: -50px; }
.nebula-3 { width: 400px; height: 400px; background: radial-gradient(circle, rgba(247,136,106,0.05) 0%, transparent 70%); top: 50%; left: 40%; }
/* --- LAYOUT --- */
.wrapper {
position: relative;
z-index: 1;
max-width: 900px;
margin: 0 auto;
padding: 0 2rem;
}
/* --- NAV --- */
nav {
position: fixed;
top: 0; left: 0; right: 0;
z-index: 100;
padding: 1.25rem 0;
backdrop-filter: blur(20px);
border-bottom: 1px solid var(--border);
background: rgba(7,8,15,0.6);
}
.nav-inner {
max-width: 900px;
margin: 0 auto;
padding: 0 2rem;
display: flex;
align-items: center;
justify-content: space-between;
}
.nav-logo {
font-size: 1rem;
font-weight: 700;
letter-spacing: 0.05em;
color: var(--text);
text-decoration: none;
}
.nav-logo span { color: var(--accent); }
.nav-links { display: flex; gap: 2rem; list-style: none; }
.nav-links a {
font-size: 0.8rem;
font-weight: 500;
letter-spacing: 0.08em;
text-transform: uppercase;
color: var(--muted);
text-decoration: none;
transition: color 0.2s;
}
.nav-links a:hover { color: var(--text); }
/* --- HERO --- */
.hero {
padding: 176px 0 110px;
position: relative;
}
.hero-tag {
display: inline-flex;
align-items: center;
gap: 8px;
font-family: var(--mono);
font-size: 0.75rem;
color: var(--accent2);
letter-spacing: 0.06em;
margin-bottom: 1.1rem;
opacity: 0;
animation: fadeUp 0.6s ease forwards 0.2s;
}
.hero-tag::before {
content: '';
display: inline-block;
width: 6px; height: 6px;
border-radius: 50%;
background: var(--accent2);
box-shadow: 0 0 8px var(--accent2);
animation: pulse 2s ease-in-out infinite;
}
@keyframes pulse { 0%,100%{opacity:1;transform:scale(1)} 50%{opacity:0.6;transform:scale(0.8)} }
.hero h1 {
max-width: 780px;
font-size: clamp(3.1rem, 8vw, 4.4rem);
font-weight: 800;
line-height: 0.96;
letter-spacing: -0.045em;
margin-bottom: 1.25rem;
opacity: 0;
animation: fadeUp 0.6s ease forwards 0.35s;
}
.hero h1 .line2 {
display: block;
color: #ffffff;
}
.hero h1 .line3 {
display: block;
color: var(--accent);
}
.hero-desc {
max-width: 640px;
font-size: 1rem;
color: var(--muted);
line-height: 1.75;
margin-bottom: 2rem;
opacity: 0;
animation: fadeUp 0.6s ease forwards 0.5s;
}
.hero-desc strong {
color: var(--text);
font-weight: 600;
}
.hero-actions {
display: flex;
gap: 1rem;
flex-wrap: wrap;
margin-bottom: 2.5rem;
opacity: 0;
animation: fadeUp 0.6s ease forwards 0.65s;
}
.btn {
display: inline-flex;
align-items: center;
gap: 8px;
padding: 0.7rem 1.4rem;
border-radius: 8px;
font-family: var(--font);
font-size: 0.85rem;
font-weight: 600;
text-decoration: none;
transition: all 0.2s;
cursor: pointer;
border: none;
}
.btn-primary {
background: var(--accent);
color: #fff;
}
.btn-primary:hover { background: #9284fc; transform: translateY(-1px); }
.btn-outline {
background: transparent;
color: var(--text);
border: 1px solid var(--border-hover);
}
.btn-outline:hover { border-color: var(--accent); color: var(--accent); transform: translateY(-1px); }
.hero-strip {
display: grid;
grid-template-columns: repeat(3, minmax(0, 1fr));
gap: 0.9rem;
max-width: 860px;
opacity: 0;
animation: fadeUp 0.6s ease forwards 0.8s;
}
.hero-metric {
padding: 1rem 1.1rem;
background: rgba(255,255,255,0.03);
border: 1px solid var(--border);
border-radius: 14px;
backdrop-filter: blur(12px);
}
.hero-metric-label {
font-family: var(--mono);
font-size: 0.62rem;
letter-spacing: 0.14em;
text-transform: uppercase;
color: var(--accent2);
margin-bottom: 0.45rem;
}
.hero-metric-value {
font-size: 1rem;
font-weight: 700;
letter-spacing: -0.02em;
margin-bottom: 0.2rem;
}
.hero-metric-copy {
font-size: 0.78rem;
color: var(--muted);
line-height: 1.55;
}
.intro-panel {
display: grid;
grid-template-columns: minmax(0, 1.1fr) minmax(260px, 0.9fr);
gap: 1rem;
align-items: stretch;
}
.intro-copy,
.intro-notes {
background: var(--surface);
border: 1px solid var(--border);
border-radius: 18px;
padding: 1.6rem;
}
.intro-copy p {
color: var(--muted);
margin-bottom: 1rem;
}
.intro-copy p:last-child { margin-bottom: 0; }
.intro-notes {
background: linear-gradient(180deg, rgba(23,26,49,0.92), rgba(10,11,20,0.9));
}
.note-list {
display: grid;
gap: 0.85rem;
list-style: none;
}
.note-list li {
padding-left: 1rem;
position: relative;
color: var(--muted);
font-size: 0.88rem;
}
.note-list li::before {
content: '';
position: absolute;
left: 0;
top: 0.6rem;
width: 6px;
height: 6px;
border-radius: 50%;
background: var(--accent3);
box-shadow: 0 0 10px rgba(247,136,106,0.5);
}
/* --- SECTION --- */
section { padding: 80px 0; }
.section-label {
font-family: var(--mono);
font-size: 0.7rem;
letter-spacing: 0.15em;
text-transform: uppercase;
color: var(--accent2);
margin-bottom: 0.75rem;
}
.section-title {
font-size: clamp(1.6rem, 3vw, 2.2rem);
font-weight: 700;
letter-spacing: -0.02em;
margin-bottom: 0.5rem;
}
.section-subtitle {
color: var(--muted);
font-size: 0.95rem;
margin-bottom: 3rem;
}
.section-divider {
height: 1px;
background: linear-gradient(90deg, transparent, var(--border) 20%, var(--border) 80%, transparent);
margin-bottom: 80px;
}
/* --- SKILLS --- */
.skills-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(260px, 1fr));
gap: 1px;
background: var(--border);
border: 1px solid var(--border);
border-radius: 16px;
overflow: hidden;
}
.skill-card {
background: var(--bg);
padding: 2rem;
transition: background 0.25s;
position: relative;
}
.skill-card:hover { background: var(--bg3); }
.skill-card::before {
content: '';
position: absolute;
top: 0; left: 0; right: 0;
height: 2px;
opacity: 0;
transition: opacity 0.3s;
}
.skill-card:hover::before { opacity: 1; }
.skill-card.purple::before { background: linear-gradient(90deg, var(--accent), transparent); }
.skill-card.teal::before { background: linear-gradient(90deg, var(--accent2), transparent); }
.skill-card.orange::before { background: linear-gradient(90deg, var(--accent3), transparent); }
.skill-icon {
font-size: 1.6rem;
margin-bottom: 1rem;
display: block;
}
.skill-name {
font-size: 1rem;
font-weight: 700;
margin-bottom: 0.5rem;
}
.skill-desc {
font-size: 0.82rem;
color: var(--muted);
line-height: 1.6;
margin-bottom: 1.25rem;
}
.skill-tags {
display: flex;
flex-wrap: wrap;
gap: 6px;
}
.tag {
font-family: var(--mono);
font-size: 0.67rem;
padding: 3px 8px;
border-radius: 4px;
border: 1px solid var(--border);
color: var(--muted);
letter-spacing: 0.03em;
}
.tag.purple { border-color: rgba(124,109,250,0.3); color: rgba(124,109,250,0.8); background: rgba(124,109,250,0.06); }
.tag.teal { border-color: rgba(79,209,197,0.3); color: rgba(79,209,197,0.8); background: rgba(79,209,197,0.06); }
.tag.orange { border-color: rgba(247,136,106,0.3); color: rgba(247,136,106,0.8); background: rgba(247,136,106,0.06); }
/* --- PROJECTS --- */
.projects-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(260px, 1fr));
gap: 16px;
}
.project-card {
background: var(--surface);
border: 1px solid var(--border);
border-radius: 14px;
padding: 1.5rem;
transition: border-color 0.25s, transform 0.2s;
text-decoration: none;
color: var(--text);
display: flex;
flex-direction: column;
gap: 0.75rem;
cursor: pointer;
}
.project-card:hover {
border-color: var(--border-hover);
transform: translateY(-2px);
}
.project-header {
display: flex;
align-items: flex-start;
justify-content: space-between;
gap: 1rem;
}
.project-icon {
font-size: 1.4rem;
line-height: 1;
}
.project-arrow {
color: var(--muted);
font-size: 0.9rem;
transition: color 0.2s, transform 0.2s;
flex-shrink: 0;
}
.project-card:hover .project-arrow {
color: var(--accent);
transform: translate(2px,-2px);
}
.project-eyebrow {
font-family: var(--mono);
font-size: 0.66rem;
color: var(--accent2);
letter-spacing: 0.08em;
text-transform: uppercase;
}
.project-name {
font-size: 0.95rem;
font-weight: 700;
letter-spacing: -0.01em;
}
.project-desc {
font-size: 0.8rem;
color: var(--muted);
line-height: 1.55;
flex: 1;
}
.project-status {
display: inline-flex;
align-items: center;
gap: 5px;
font-family: var(--mono);
font-size: 0.65rem;
letter-spacing: 0.04em;
}
.status-dot {
width: 5px; height: 5px;
border-radius: 50%;
flex-shrink: 0;
}
.status-live { background: #4ade80; box-shadow: 0 0 6px #4ade80; }
.status-wip { background: #facc15; box-shadow: 0 0 6px #facc15; }
.status-planned{ background: var(--muted); }
/* --- SNAPSHOT --- */
.snapshot-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
gap: 12px;
}
.snapshot-card {
background: var(--surface);
border: 1px solid var(--border);
border-radius: 10px;
padding: 1.1rem 1.25rem;
min-height: 148px;
transition: border-color 0.2s, background 0.2s, transform 0.2s;
}
.snapshot-card:hover {
border-color: var(--border-hover);
background: rgba(255,255,255,0.06);
transform: translateY(-2px);
}
.snapshot-kicker {
font-family: var(--mono);
font-size: 0.66rem;
text-transform: uppercase;
letter-spacing: 0.12em;
color: var(--accent2);
margin-bottom: 0.65rem;
}
.snapshot-value {
font-size: 1.4rem;
font-weight: 700;
letter-spacing: -0.03em;
margin-bottom: 0.4rem;
}
.snapshot-desc {
font-size: 0.82rem;
color: var(--muted);
line-height: 1.6;
}
/* --- CONTACT --- */
.contact-box {
background: linear-gradient(180deg, rgba(17,20,40,0.95), rgba(10,12,24,0.92));
border: 1px solid var(--border);
border-radius: 20px;
padding: 3rem;
text-align: center;
}
.contact-box h2 { font-size: 2rem; font-weight: 800; margin-bottom: 0.5rem; }
.contact-box p { color: var(--muted); margin-bottom: 2rem; }
/* --- FOOTER --- */
footer {
border-top: 1px solid var(--border);
padding: 2rem 0;
font-family: var(--mono);
font-size: 0.7rem;
color: var(--muted);
}
.footer-inner {
max-width: 900px;
margin: 0 auto;
padding: 0 2rem;
display: flex;
justify-content: space-between;
align-items: center;
flex-wrap: wrap;
gap: 1rem;
}
/* --- ANIMATIONS --- */
@keyframes fadeUp {
from { opacity: 0; transform: translateY(20px); }
to { opacity: 1; transform: translateY(0); }
}
.reveal {
opacity: 0;
transform: translateY(24px);
transition: opacity 0.6s ease, transform 0.6s ease;
}
.reveal.visible {
opacity: 1;
transform: none;
}
@media (max-width: 600px) {
.nav-links { display: none; }
.hero { padding: 120px 0 60px; }
.contact-box { padding: 2rem 1.5rem; }
}
@media (max-width: 860px) {
.hero-strip,
.intro-panel {
grid-template-columns: 1fr;
}
}
</style>
</head>
<body>
<!-- Stars -->
<canvas id="stars"></canvas>
<!-- Nebula glows -->
<div class="nebula nebula-1"></div>
<div class="nebula nebula-2"></div>
<div class="nebula nebula-3"></div>
<!-- Nav -->
<nav>
<div class="nav-inner">
<a href="#" class="nav-logo">noctura<span>.dev</span></a>
<ul class="nav-links">
<li><a href="#about">Über mich</a></li>
<li><a href="#skills">Skills</a></li>
<li><a href="#projects">Projekte</a></li>
<li><a href="#snapshot">Profil</a></li>
<li><a href="#contact">Kontakt</a></li>
</ul>
</div>
</nav>
<!-- Hero -->
<div class="wrapper">
<section class="hero">
<div class="hero-tag">Persönliches Portfolio / Web & Infrastruktur</div>
<h1>
Web entwickeln.
<span class="line2">Systeme verstehen.</span>
<span class="line3">Sauber bauen.</span>
</h1>
<p class="hero-desc">
Ich arbeite an Projekten, die nicht nur gut aussehen, sondern auch technisch nachvollziehbar aufgebaut sind.
Frontend, Backend, Linux und Hosting gehören für mich zusammen.
</p>
<div class="hero-actions">
<a href="#projects" class="btn btn-primary">Projekte ansehen</a>
<a href="#contact" class="btn btn-outline">Kontakt</a>
</div>
<div class="hero-strip">
<div class="hero-metric">
<div class="hero-metric-label">Fokus</div>
<div class="hero-metric-value">Frontend bis Deployment</div>
<p class="hero-metric-copy">Ich denke nicht nur in Screens, sondern in vollständigen Umsetzungen.</p>
</div>
<div class="hero-metric">
<div class="hero-metric-label">Arbeitsweise</div>
<div class="hero-metric-value">Direkt und praktisch</div>
<p class="hero-metric-copy">Lieber echte Projekte und klare Entscheidungen als Buzzwords und leere Claims.</p>
</div>
<div class="hero-metric">
<div class="hero-metric-label">Stack</div>
<div class="hero-metric-value">HTML, CSS, JS, Linux, Docker</div>
<p class="hero-metric-copy">Genug Breite, um Interfaces, Services und Betrieb zusammen zu denken.</p>
</div>
</div>
</section>
<div class="section-divider"></div>
<section id="about">
<div class="reveal">
<div class="section-label">// Über mich</div>
<h2 class="section-title">Weniger Firmen-Sprache, mehr echte Arbeit</h2>
<p class="section-subtitle">Genau so sollte sich ein Portfolio für Bewerbungen lesen.</p>
</div>
<div class="intro-panel reveal">
<div class="intro-copy">
<p>
Ich entwickle nicht nur Oberflächen, sondern kümmere mich auch darum, wie Anwendungen betrieben,
abgesichert und langfristig verständlich gehalten werden.
</p>
<p>
Gerade deshalb passt zu mir eher ein Portfolio mit Persönlichkeit: Projekte, Entscheidungen,
Infrastruktur und Lernweg sichtbar machen, statt wie eine kleine IT-Firma aufzutreten.
</p>
</div>
<div class="intro-notes">
<div class="hero-card-label">Was Recruiter hier schnell sehen sollen</div>
<ul class="note-list">
<li>Ich kann gestalten, bauen und deployen.</li>
<li>Ich arbeite eigenständig und technisch neugierig.</li>
<li>Meine Projekte sind nicht nur Demos, sondern echte Systeme.</li>
</ul>
</div>
</div>
</section>
<div class="section-divider"></div>
<!-- Skills -->
<section id="skills">
<div class="reveal">
<div class="section-label">// Fähigkeiten</div>
<h2 class="section-title">Was ich kann</h2>
<p class="section-subtitle">Breit genug für komplette Projekte, fokussiert genug für gute Details.</p>
</div>
<div class="skills-grid reveal">
<div class="skill-card purple">
<span class="skill-icon">⟨/⟩</span>
<div class="skill-name">Frontend & Interfaces</div>
<p class="skill-desc">Responsive Oberflächen, klares UI und saubere Struktur. Ich mag Seiten, die Charakter haben und trotzdem verständlich bleiben.</p>
<div class="skill-tags">
<span class="tag purple">HTML / CSS</span>
<span class="tag purple">JavaScript</span>
<span class="tag purple">UI Design</span>
<span class="tag purple">Responsive Layouts</span>
</div>
</div>
<div class="skill-card teal">
<span class="skill-icon"></span>
<div class="skill-name">Backend & Infrastruktur</div>
<p class="skill-desc">Services selbst aufsetzen, verbinden und betreiben. Nicht nur deployen, sondern verstehen, was dahinter läuft.</p>
<div class="skill-tags">
<span class="tag teal">Docker</span>
<span class="tag teal">Caddy</span>
<span class="tag teal">Node.js</span>
<span class="tag teal">Forgejo</span>
<span class="tag teal">REST APIs</span>
</div>
</div>
<div class="skill-card orange">
<span class="skill-icon">$_</span>
<div class="skill-name">Linux & Betrieb</div>
<p class="skill-desc">Server einrichten, absichern und dauerhaft sinnvoll betreiben. Besonders spannend finde ich alles rund um Self-hosting und Wartbarkeit.</p>
<div class="skill-tags">
<span class="tag orange">Debian Linux</span>
<span class="tag orange">SSH</span>
<span class="tag orange">systemd</span>
<span class="tag orange">ufw / fail2ban</span>
</div>
</div>
</div>
</section>
<div class="section-divider"></div>
<!-- Projects -->
<section id="projects">
<div class="reveal">
<div class="section-label">// Portfolio</div>
<h2 class="section-title">Ausgewählte Arbeiten</h2>
<p class="section-subtitle">Nicht als Produktkatalog, sondern als Ausschnitt meiner Denkweise.</p>
</div>
<div class="projects-grid reveal">
<div class="project-card">
<div class="project-header">
<div>
<div class="project-eyebrow">Tooling</div>
<div class="project-name">Developer Utilities</div>
</div>
<span class="project-arrow"></span>
</div>
<p class="project-desc">Sammlung kleiner Web-Tools, bei denen UX und Geschwindigkeit wichtiger sind als Marketing. Gut geeignet, um Pragmatismus und Produktgefühl zu zeigen.</p>
<div class="project-status">
<span class="status-dot status-wip"></span>
<span style="color: var(--muted);">In Entwicklung</span>
</div>
</div>
<div class="project-card">
<div class="project-header">
<div>
<div class="project-eyebrow">Backend</div>
<div class="project-name">Custom Backend API</div>
</div>
<span class="project-arrow"></span>
</div>
<p class="project-desc">Eigene API-Struktur für persönliche Projekte. Der Reiz liegt für mich darin, Datenmodelle, Routing und Hosting selbst sauber zu kontrollieren.</p>
<div class="project-status">
<span class="status-dot status-wip"></span>
<span style="color: var(--muted);">In Entwicklung</span>
</div>
</div>
<div class="project-card">
<div class="project-header">
<div>
<div class="project-eyebrow">Experiment</div>
<div class="project-name">Lab / Playground</div>
</div>
<span class="project-arrow"></span>
</div>
<p class="project-desc">Platz für Prototypen, Designideen und technische Versuche. Gerade dieser Bereich macht sichtbar, wie ich lerne und neue Themen erschließe.</p>
<div class="project-status">
<span class="status-dot status-planned"></span>
<span style="color: var(--muted);">Geplant</span>
</div>
</div>
<div class="project-card">
<div class="project-header">
<div>
<div class="project-eyebrow">Infrastruktur</div>
<div class="project-name">noctura.dev Infrastruktur</div>
</div>
<span class="project-arrow"></span>
</div>
<p class="project-desc">Mein stärkstes Portfolio-Stück im Hintergrund: ein selbst verwalteter VPS mit echten Services, nicht nur eine statische Visitenkarte.</p>
<div class="project-status">
<span class="status-dot status-live"></span>
<span style="color: var(--muted);">Live</span>
</div>
</div>
</div>
</section>
<div class="section-divider"></div>
<section id="snapshot">
<div class="reveal">
<div class="section-label">// Profil</div>
<h2 class="section-title">Was hängen bleibt</h2>
<p class="section-subtitle">Die kurze Zusammenfassung für Bewerbungen und schnelle Erstgespräche.</p>
</div>
<div class="snapshot-grid reveal">
<div class="snapshot-card">
<div class="snapshot-kicker">Arbeitsweise</div>
<div class="snapshot-value">Praktisch</div>
<p class="snapshot-desc">Ich lerne am liebsten über echte Umsetzungen, nicht nur über Theorie oder Tutorial-Nachbau.</p>
</div>
<div class="snapshot-card">
<div class="snapshot-kicker">Stärke</div>
<div class="snapshot-value">Full stack denken</div>
<p class="snapshot-desc">Vom Interface bis zum Server kann ich Entscheidungen zusammenhängend betrachten und umsetzen.</p>
</div>
<div class="snapshot-card">
<div class="snapshot-kicker">Motivation</div>
<div class="snapshot-value">Eigene Systeme verstehen</div>
<p class="snapshot-desc">Mich reizt Technik dann besonders, wenn ich sie selbst aufbauen, betreiben und verbessern kann.</p>
</div>
<div class="snapshot-card">
<div class="snapshot-kicker">Eindruck</div>
<div class="snapshot-value">Kein Buzzword-Profil</div>
<p class="snapshot-desc">Die Seite soll zeigen, wie ich denke und arbeite, nicht nur welche Begriffe ich aufzählen kann.</p>
</div>
</div>
</section>
<div class="section-divider"></div>
<!-- Contact -->
<section id="contact">
<div class="contact-box reveal">
<h2>Kontakt für Bewerbungen oder Projekte</h2>
<p>Wenn du einen Entwickler suchst, der nicht nur UI klickt, sondern Systeme wirklich verstehen will, schreib mir.</p>
<a href="mailto:kontakt@noctura.dev" class="btn btn-primary">kontakt@noctura.dev</a>
</div>
</section>
</div>
<!-- Footer -->
<footer>
<div class="footer-inner">
<span>noctura.dev - persönliches Portfolio mit eigener Infrastruktur</span>
<span>HTML · CSS · JavaScript · Debian 12</span>
</div>
</footer>
<script>
// Star field
const canvas = document.getElementById('stars');
const ctx = canvas.getContext('2d');
let stars = [];
function resize() {
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
stars = Array.from({length: 180}, () => ({
x: Math.random() * canvas.width,
y: Math.random() * canvas.height,
r: Math.random() * 1.2 + 0.2,
o: Math.random() * 0.6 + 0.1,
s: Math.random() * 0.4 + 0.1,
t: Math.random() * Math.PI * 2
}));
}
function drawStars(ts) {
ctx.clearRect(0,0,canvas.width,canvas.height);
stars.forEach(s => {
s.t += 0.008 * s.s;
const o = s.o * (0.6 + 0.4 * Math.sin(s.t));
ctx.beginPath();
ctx.arc(s.x, s.y, s.r, 0, Math.PI*2);
ctx.fillStyle = `rgba(200,210,255,${o})`;
ctx.fill();
});
requestAnimationFrame(drawStars);
}
window.addEventListener('resize', resize);
resize();
requestAnimationFrame(drawStars);
// Scroll reveal
const reveals = document.querySelectorAll('.reveal');
const io = new IntersectionObserver(entries => {
entries.forEach((e,i) => {
if (e.isIntersecting) {
setTimeout(() => e.target.classList.add('visible'), i * 80);
io.unobserve(e.target);
}
});
}, { threshold: 0.12 });
reveals.forEach(el => io.observe(el));
</script>
</body>
</html>