Zum Inhalt

HEADER & NAVIGATION

Der Header enthält die Hauptnavigation der Seite.
Er bleibt am oberen Rand fixiert und bietet Jump-Links zu allen Hauptbereichen.

Aufbau

<header id="home">
  <nav class="navbar">
    <div class="logo">Tim Langenauer</div>
    <div class="menu-icon"><i class='bx bx-menu'></i></div>
    <ul class="nav-links">
      <li><a href="#home">Home</a></li>
      <li><a href="#about">About</a></li>
      <li><a href="#skills">Skills</a></li>
      <li><a href="#projects">Projects</a></li>
      <li><a href="#education">Education</a></li>
      <li><a href="#contact">Contact</a></li>
    </ul>
  </nav>
</header>

Funktion

  • Navigation führt per Ankerlink zu den einzelnen Sektionen.

  • Auf mobilen Geräten wird die Navigation über ein Burger-Icon gesteuert.

  • Hintergrundfarbe: dunkel, Schrift hell → gute Lesbarkeit.

CSS-Ausschnitt

.navbar {
  display: flex;
  justify-content: space-between;
  align-items: center;
  background: #111;
  padding: 20px;
}

.nav-links a {
  color: white;
  text-decoration: none;
  padding: 10px;
}

.nav-links a:hover {
  color: #00bcd4;
}

HOME-BEREICH

Der Home-Bereich ist die Startseite und zeigt den Besucher*innen sofort, wer ich bin.

Aufbau

<section id="home" class="home">
        <div class="home-img">
            <div class="img-box">
                <div class="img-item">
                    <img src="tim.jpg" alt="Tim Langenauer">
                </div>
            </div>
        </div>
        <div class="home-info">
            <h1>Ich bin Tim</h1>
            <h2>Ich bin
                <span style="--i:4" data-text="Urnäscher">Urnäscher</span>
                <span style="--i:3" data-text="Techniker">Techniker</span>
                <span style="--i:2" data-text="Developer">Developer</span>
                <span style="--i:1" data-text="Teamplayer">Teamplayer</span>
            </h2>
            <p>
                Hey, ich bin Tim aus Urnäsch. Ich interessiere mich für Technik, Webentwicklung und alles, was mit Computern zu tun hat.  
                Ich mag es, Neues zu lernen, Probleme zu lösen und Dinge so zu gestalten, dass sie einfach funktionieren.
            </p>
            <div class="btn-sci">
                <a href="lebenslauf zsm.pdf"download class="btn">Download CV</a>
                <div class="sci">
                    <a href="https://github.com/tim9107?tab=repositories" target="_blank"><i class='bx bxl-github'></i></a>
                    <a href="https://www.linkedin.com/in/tim-langenauer-bb8789335/" target="blank"><i class='bx bxl-linkedin'></i></a>
                    <a href="https://www.instagram.com/tim.langenauer/" target="blank"><i class='bx bxl-instagram'></i></a>
                </div>
            </div>
        </div>
    </section>

Design & Funktion

  • Minimalistischer Hero-Bereich mit Text und Call-to-Action-Button

  • Hintergrund-Animation mit bewegten Bars / Linien

  • Button führt direkt zu den Projekten

CSS-Ausschnitt

.home{
    display: flex;
    align-items: center;
    gap: 50px;
    height: 100vh;
    padding: 60px 9% 0;
    color: #fff;
    visibility: hidden;
    opacity: 0;
    animation: show-content 1.5s linear forwards;
    animation-delay: 1.6s;
    position: relative;
    overflow: hidden;
}

/* Remove gray panel behind the image */
.home::after {
    display: none !important;
    content: none !important;
}

.home-info{
    max-width:720px;
    padding:3rem;
    flex: 1;
    background: linear-gradient(135deg, rgba(110,114,113,0.08), rgba(172,173,148,0.04));
    backdrop-filter: blur(12px);
    border-radius: 24px;
    border: 1px solid rgba(216,212,213,0.12);
    box-shadow: 0 20px 60px rgba(20,30,28,0.4);
    position: relative;
    z-index: 1;
}

/* Headline einheitlich, ohne zweite (konfliktierende) Definition */
.home-info h1{
    font-size:5.5rem;
    margin:0 0 1.5rem;
    background: linear-gradient(135deg, var(--c-white), var(--c-accent));
    -webkit-background-clip:text;
    -webkit-text-fill-color:transparent;
    background-clip:text;
    animation: none; /* statt wandernder Verlauf */
    letter-spacing:2px;
    font-weight: 900;
    text-transform: uppercase;
}

.home-info h2 {
    display: inline-grid;
    grid-template-columns: auto var(--max-width); /* Slot ist fix breit */
    column-gap: .5rem;
    align-items: baseline;
    position: relative;

    /* Parameter für den Typ-Effekt */
    --words: 4;
    --slot: 2s;
    --cycle: calc(var(--words) * var(--slot));
    --steps: 16;
    --max-width: 16ch; /* Breitenlimit für Slot */
}

.home-info h2 span {
    position: relative;
    display: inline-block;
    color: var(--c-accent);
    opacity: 0;
    white-space: nowrap;
    overflow: hidden;
    border-right: 2px solid transparent; /* Caret am Ende */
    will-change: max-width, opacity;

    /* 1) Sichtfenster (kein Überschlag)
       2) Tippen/Halten/Löschen
       3) Cursor-Blink */
    animation:
        wordGate var(--cycle) steps(1,end) infinite,
        typeSlot var(--cycle) steps(var(--steps),end) infinite,
        caretBlink .8s steps(1,end) infinite;

    /* immer gleicher Ort (gleiche Grid-Zelle), keine Pointer-Events nötig */
    grid-column: 2;
    grid-row: 1;
    justify-self: start;
    align-self: baseline;
}

/* Synchronisierte Delays (ein Slot = var(--slot)) */
.home-info h2 span:nth-child(1){ animation-delay: 0s, 0s, 0s; }
.home-info h2 span:nth-child(2){ animation-delay: calc(var(--slot) * 1), calc(var(--slot) * 1), calc(var(--slot) * 1); }
.home-info h2 span:nth-child(3){ animation-delay: calc(var(--slot) * 2), calc(var(--slot) * 2), calc(var(--slot) * 2); }
.home-info h2 span:nth-child(4){ animation-delay: calc(var(--slot) * 3), calc(var(--slot) * 3), calc(var(--slot) * 3); }

/* Gate: nur im eigenen 0–25%-Fenster sichtbar, sonst komplett unsichtbar
   steps(1,end) sorgt für harte Kante (kein Überblenden) */
@keyframes wordGate {
    0%     { visibility: visible; opacity: 1; }
    24.99% { visibility: visible; opacity: 1; }
    25%    { visibility: hidden; opacity: 0; }
    100%   { visibility: hidden; opacity: 0; }
}

/* Typewriter innerhalb des sichtbaren Fensters:
   - 0–10%: tippen (0 -> max)
   - 10–20%: halten (voll)
   - 20–24%: löschen (voll -> 0)
   - ab 25%: unsichtbar (Gate) */
@keyframes typeSlot {
    0%   { max-width: 0; }
    10%  { max-width: var(--max-width); }
    20%  { max-width: var(--max-width); }
    24%  { max-width: 0; }
    25%  { max-width: 0; }
    100% { max-width: 0; }
}

/* Cursor-Blink nur wenn Gate sichtbar ist (visibility steuert Darstellung mit) */
@keyframes caretBlink {
    0%,50% { border-right-color: var(--c-accent); }
    51%,100% { border-right-color: transparent; }
}

/* Alte Cursor-Variante über ::after und alte Word-Fades deaktivieren */
.home-info h2 span::after { content: none !important; display: none !important; }

/* Entferne unbenutzte alte Animationen vollständig */
@keyframes wordSlot {}
@keyframes wordFade {}
@keyframes display-text {}
@keyframes fill-text {}

.home-info h2 span::before { display:none !important; }

.home-info p{
    color:var(--c-light);
    font-size:1.1rem;
    line-height:1.9;
    max-width:60ch;
    margin-bottom: 2.5rem;
    opacity: 0.9;
    font-weight: 300;
}

.home-info .btn-sci {
    display: flex;
    align-items: center;
}
.home-info .btn-sci .btn{
    display: inline-block;
    padding: 10px 30px;
    background: var(--c-accent);
    border: 2px solid var(--c-accent);
    border-radius: 40px;
    box-shadow: 0 0 10px rgba(172,173,148,.55);
    font-size: 16px;
    color: var(--c-dark);
    font-weight: 600;
    transition: .5s;
}

.home-info .btn-sci .btn:hover{
    background: transparent;
    color: var(--c-accent);
    box-shadow: none;
}

.home-info .btn-sci .sci{
    margin-left: 20px;
}
.home-info .btn-sci .sci a{
    display: inline-flex;
    padding: 8px;
    border: 2px solid var(--c-accent);
    border-radius: 50%;
    font-size: 20px;
    margin: 0 8px;
    transition: .5s;
}

.home-info .btn-sci .sci a:hover {
    background: var(--c-accent);
    color: var(--c-dark);
    box-shadow: 0 0 10px rgba(110,114,113,.6);
}

.home-img .img-box{
    position: relative;
    width: clamp(220px, 32vw, 520px);
    height: clamp(220px, 32vw, 520px);
    border-radius: 50%;
    padding: 6px;
    overflow: hidden;
}

/* dunklen Hintergrund entfernen */
.home-img .img-box .img-item{
    background: transparent !important;
    border: 0 !important;
}

/* Bild vollflächig einpassen und nach oben ausrichten */
.home-img .img-box .img-item img{
    position: absolute;
    inset: 0;
    width: 100%;
    height: 100%;
    object-fit: cover;        /* füllt den Kreis komplett */
    object-position: center top; /* Fokus oben */
    mix-blend-mode: normal;
}

/* Rotations-/Deko-Ringe vollständig ausblenden */
.home-img .img-box::before,
.home-img .img-box::after{
    display: none !important;
    content: none !important;
}

ABOUT

Der About-Bereich stellt mich persönlich vor.

Aufbau

<section id="about" class="about">
        <div class="about-inner">
            <h2>Über mich</h2>
            <p class="about-lead">
                Ich bin Tim – ein motivierter Entwickler und angehender Informatiker aus Urnäsch.  
                Ich habe Freude daran, mit Code kreative Lösungen zu bauen und Technik besser zu verstehen.  
                Wenn ich nicht gerade programmiere, bin ich wahrscheinlich draussen unterwegs oder am Fussballspielen.
            </p>

            <div class="about-grid">
                <div class="about-card">
                    <h3>Kurz & Knapp</h3>
                    <ul class="about-list">
                        <li><span class="about-badge">Ort</span> Urnäsch, AR</li>
                        <li><span class="about-badge">Fokus</span> Web, Technik & Lernen</li>
                        <li><span class="about-badge">Mindset</span> neugierig, offen, motiviert</li>
                    </ul>
                </div>
                <div class="about-card">
                    <h3>Skills</h3>
                    <ul class="about-list">
                        <li><span class="about-badge">Frontend</span> HTML, CSS, JS</li>
                        <li><span class="about-badge">Tools</span> GitHub, VS Code</li>
                        <li><span class="about-badge">Extras</span> Responsive Design, einfache Animationen</li>
                    </ul>
                </div>
            </div>
        </div>
    </section>

Design

  • Zwei-Spalten-Layout mit Bild und Text

  • Auf kleineren Bildschirmen untereinander angeordnet

  • Einheitliche Schrift, weisse Typografie auf dunklem Hintergrund

CSS-Ausschnitt

.about {
    padding: 6rem 2rem;
    background: linear-gradient(180deg, rgba(56, 77, 72, 0.06), rgba(56, 77, 72, 0));
    overflow-x:hidden;
}

.about-inner {
    max-width: 1100px;
    margin: 0 auto;
    display: grid;
    gap: 2rem;
}

.about-inner h2 {
    font-size: 2.8rem;
    margin: 0 0 1rem;
    color: #E2E2E2;
    letter-spacing: 2px;
    text-transform: uppercase;
}

.about-lead {
    color: #E2E2E2;
    font-size: 1.1rem;
    line-height: 1.8;
    max-width: 70ch;
}

.about-grid {
    display: grid;
    grid-template-columns: repeat(auto-fit,minmax(260px,1fr));
    gap: 1.5rem;
    margin-top: 2rem;
}

.about-card {
    background: rgba(110, 114, 113, 0.18);
    border: 1px solid rgba(172,173,148,0.24);
    border-radius: 16px;
    padding: 1.5rem;
    color: #E2E2E2;
    box-shadow: 0 12px 30px rgba(56, 77, 72, 0.25);
    display:flex;
    flex-direction:column;
}

.about-card h3 {
    margin: 0 0 .6rem;
    color: #ACAD94;
    letter-spacing: 1px;
    font-size: 1.1rem;
    text-transform: uppercase;
}

.about-list {
    margin: 0;
    padding: 0;
    list-style: none;
    display: grid;
    gap: .5rem;
}

.about-list li {
    color: #E2E2E2;
    display: flex;
    align-items: center;
    gap: .6rem;
}

.about-badge {
    display: inline-block;
    background: #384D48;
    color: #E2E2E2;
    border: 1px solid #6E7271;
    padding: .35rem .6rem;
    border-radius: 999px;
    font-size: .85rem;
    white-space: nowrap;
}

SKILLS

Dieser Bereich zeigt meine Kenntnisse und Tools.

Aufbau

<section id="skills" class="skills">
        <div class="skills-inner">
            <h2>Fähigkeiten</h2>
            <div class="skills-grid">
                <div class="skill-card">
                    <h3>Frontend</h3>
                    <ul class="skill-list">
                        <li><span class="about-badge">HTML</span></li>
                        <li><span class="about-badge">CSS</span></li>
                        <li><span class="about-badge">JavaScript</span></li>
                    </ul>
                    <p class="skill-note">Ich baue einfache, saubere Webseiten mit modernen Standards.</p>
                </div>
                <div class="skill-card">
                    <h3>Tools</h3>
                    <ul class="skill-list">
                        <li><span class="about-badge">VS Code</span></li>
                        <li><span class="about-badge">GitHub</span></li>
                        <li><span class="about-badge">Figma</span></li>
                    </ul>
                    <p class="skill-note">Meine Tools zum Coden, Gestalten und Verwalten von Projekten.</p>
                </div>
                <div class="skill-card">
                    <h3>Soft Skills</h3>
                    <ul class="skill-list">
                        <li><span class="about-badge">Teamwork</span></li>
                        <li><span class="about-badge">Zuverlässigkeit</span></li>
                        <li><span class="about-badge">Lernfreude</span></li>
                    </ul>
                    <p class="skill-note">Ich arbeite gerne mit anderen zusammen und bleibe immer neugierig.</p>
                </div>
            </div>
        </div>
    </section>

Design

  • Grid-Layout mit gleichmässigen Boxen

  • Hover-Effekt für Interaktivität

  • Gut lesbar, klare Trennung der Skills

CSS-Ausschnitt

.skills{
    padding: 6rem 2rem;
    background: var(--c-dark);
}
.skills-inner{
    max-width:1100px; margin:0 auto; display:grid; gap:2rem;
}
.skills-inner h2{
    font-size:2.8rem; margin:0 0 1rem; color:var(--c-white);
    letter-spacing:2px; text-transform:uppercase;
}
.skills-grid{
    display:grid; grid-template-columns:repeat(3,minmax(0,1fr));
    gap:1.5rem; margin-top:1rem;
}
.skill-card{
    background: rgba(110,114,113,.18);
    border:1px solid rgba(172,173,148,.24);
    border-radius:16px; padding:1.5rem; color:var(--c-white);
    box-shadow:0 12px 30px rgba(56,77,72,.25);
    transition: transform .2s ease, box-shadow .2s ease, border-color .2s ease;
}
.skill-card:hover{
    transform:translateY(-6px);
    border-color: var(--c-accent);
    box-shadow:0 18px 40px rgba(56,77,72,.35);
}
.skill-card h3{
    margin:0 0 .8rem; color:var(--c-accent);
    letter-spacing:1px; font-size:1.1rem; text-transform:uppercase;
}
.skill-list{
    list-style:none; padding:0; margin:0 0 .8rem;
    display:flex; flex-wrap:wrap; gap:.5rem;
}
.skill-list li .about-badge{ /* Badge-Style wiederverwenden */
    /* unverändert – kommt aus .about-badge */
}
.skill-note{ color: var(--c-light); font-size:.95rem; opacity:.9; }

PROJECTS

Das Herzstück meiner Seite – mit eigenem JavaScript-Feature.

Aufbau

<section id="projects" class="projects">
        <div class="projects-inner">
            <div class="proj-info">
                <p class="proj-label">Projekte</p>
                <div class="proj-number"><span id="proj-idx">01</span></div>
                <h3 id="proj-title">Mein Portfolio</h3>
                <p id="proj-desc">Eine moderne, responsive Website, die mich und meine Skills zeigt. Erstellt mit HTML, CSS und etwas JS.</p>
                <div class="proj-links" id="proj-links"></div>
                <div class="proj-dots" id="proj-dots"></div>
            </div>

            <div class="proj-preview">
                <img id="proj-image" src="project1.png" alt="Projektvorschau">
                <div class="proj-nav">
                    <button class="prev" id="proj-prev" aria-label="Vorheriges Projekt"><i class='bx bx-chevron-left'></i></button>
                    <button class="next" id="proj-next" aria-label="Nächstes Projekt"><i class='bx bx-chevron-right'></i></button>
                </div>
                <div class="proj-img-dots" id="proj-img-dots"></div>
            </div>
        </div>
    </section>

AUSBILDUNG

Dieser Abschnitt zeigt meinen schulischen Werdegang und Ausbildung.

Aufbau

<section id="education" class="education">
        <div class="education-inner">
            <h2>Ausbildung</h2>

            <div class="timeline">
                <div class="edu-item">
                    <div class="edu-dot"></div>
                    <div class="edu-card">
                        <div class="edu-header">
                            <span class="edu-year">2023 – Heute</span>
                            <h3>Informatikmittelschule Kantonsschule am Brühl</h3>
                        </div>
                        <p>Informatiker EFZ mit Berufsmatura – Fokus auf Programmieren, Technik und Projekte.</p>
                    </div>
                </div>

                <div class="edu-item">
                    <div class="edu-dot"></div>
                    <div class="edu-card">
                        <div class="edu-header">
                            <span class="edu-year">2021 – 2023</span>
                            <h3>Sekundarschule Urnäsch</h3>
                        </div>
                        <p>Normale Sekundarschule im Niveau E mit Interesse an Informatik und Technik.</p>
                    </div>
                </div>
            </div>
        </div>
    </section>

Design

Schlicht gehalten, um Fokus auf Inhalte zu legen.

.education {
    overflow-x:hidden;
}
.timeline {
    /* kleinere linke Einrückung vermeiden auf sehr schmalen Screens */
    position:relative;
}
@media (max-width:640px){
    .timeline {
        padding-left:0;
    }
    .timeline::before {
        display:none;
    }
    .edu-item {
        grid-template-columns: 1fr; /* Dot über Text */
        row-gap:.6rem;
    }
    .edu-dot {
        justify-self:flex-start;
        margin-left:.2rem;
    }
    .edu-card {
        padding:1rem .95rem 1.05rem;
    }
}

/* leichte vertikale Trennung */
.edu-item + .edu-item {
    margin-top:.4rem;
}

/* === MOBILE REDESIGN EDUCATION (Ausbildungsstationen) === */
@media (max-width:640px){
    .education{
        padding:4.2rem 1.4rem;
    }
    .education-inner h2{
        font-size:2.2rem;
        margin-bottom:.8rem;
        text-align:center;
    }
    .timeline{
        padding-left:0;
        display:grid;
        gap:1rem;
    }
    .timeline::before{
        display:none;
    }
    .edu-item{
        display:block;
    }
    .edu-dot{
        display:none;
    }
    .edu-card{
        position:relative;
        padding:1rem .95rem 1.05rem 1.15rem;
        border:1px solid rgba(172,173,148,.28);
        background:rgba(110,114,113,.22);
        box-shadow:0 6px 16px rgba(56,77,72,.22);
        border-radius:14px;
        overflow:hidden;
    }
    .edu-card::before{
        content:"";
        position:absolute;
        left:0; top:0; bottom:0;
        width:6px;
        background:linear-gradient(180deg,var(--c-accent),rgba(172,173,148,.45));
        border-radius:6px 0 0 6px;
    }
    .edu-header{
        margin:0 0 .35rem;
        gap:.5rem;
    }
    .edu-year{
        background: var(--c-dark);
        font-size:.7rem;
        padding:.25rem .55rem;
        letter-spacing:.5px;
    }
    .edu-card h3{
        font-size:1rem;
        margin:.1rem 0 .1rem;
        letter-spacing:.4px;
    }
    .edu-card p{
        font-size:.85rem;
        line-height:1.45;
    }
}

@media (max-width:420px){
    .edu-card{
        padding:.85rem .75rem .9rem .95rem;
    }
    .edu-header{
        flex-direction:column;
        align-items:flex-start;
    }
    .edu-year{
        order: -1;
        margin-bottom:.25rem;
    }
    .edu-card h3{
        font-size:.95rem;
    }
    .edu-card p{
        font-size:.8rem;
    }
}

CONTACT

Der Contact-Bereich enthält ein Formular, das über Formspree gesendet wird.

Aufbau

<section id="contact" class="contact-section">
  <h2>Contact Me</h2>
  <form action="https://formspree.io/f/xyz123" method="POST">
    <input type="text" name="name" placeholder="Name" required>
    <input type="email" name="email" placeholder="E-Mail" required>
    <textarea name="message" placeholder="Nachricht" required></textarea>
    <button type="submit">Senden</button>
  </form>
</section>

CSS

.contact {
    padding: 6rem 2rem;
    background: var(--c-dark);
}
.contact-inner {
    max-width: 950px;
    margin: 0 auto;
}
.contact-inner h2 {
    font-size: 2.6rem;
    margin: 0 0 .6rem;
    letter-spacing: 2px;
    color: var(--c-white);
    text-transform: uppercase;
}
.contact-sub {
    margin: 0 0 1.8rem;
    color: var(--c-light);
    font-size: 1.05rem;
    line-height: 1.6;
    opacity: .85;
}
.contact-form {
    background: rgba(110,114,113,.18);
    border: 1px solid rgba(172,173,148,.24);
    border-radius: 22px;
    padding: 2rem 1.6rem 2rem;
    box-shadow: 0 12px 30px rgba(56,77,72,.25);
    backdrop-filter: blur(8px);
}

Der Footer ist schlicht gehalten und enthält Impressum, Datenschutz und einen Link zurück nach oben.

Aufbau

<footer class="site-footer">
        <nav class="footer-links" aria-label="Footer Navigation">
            <a href="impressum.html">Impressum</a>
            <a href="datenschutz.html">Datenschutz</a>
            <a href="mailto:deine.mail@domain.ch">Kontakt</a>
        </nav>
        <p class="footer-copy">© 2025 Tim Langenauer</p>
    </footer>

CSS

.site-footer {
    background: var(--c-dark) !important;
    padding: 2.4rem 1.6rem 2.1rem;
    margin-top: 3.5rem;
    display:flex;
    flex-direction:column;
    align-items:center;
    gap:1rem;
    border-top:1px solid rgba(172,173,148,.25);
    box-shadow: 0 -6px 22px rgba(0,0,0,.35);
    overflow:hidden;
}
.footer-links {
    display:flex;
    flex-wrap:wrap;
    gap:.9rem;
    justify-content:center;
    margin:0;
    padding:0;
}
.footer-links a {
    color: var(--c-light);
    font-size:.8rem;
    letter-spacing:.6px;
    padding:.45rem .75rem;
    border-radius:8px;
    transition: background .25s, color .25s;
}
.footer-links a:hover,
.footer-links a:focus {
    background: var(--c-accent);
    color: var(--c-dark);
    outline:none;
}
.footer-copy {
    margin:0;
    color: var(--c-mid);
    font-size:.7rem;
    letter-spacing:.55px;
}
@media (max-width:540px){
    .site-footer {
        padding:2rem 1.2rem 1.9rem;
        gap:.8rem;
    }
    .footer-links {
        gap:.6rem;
    }
    .footer-links a {
        font-size:.75rem;
        padding:.4rem .65rem;
    }
}

/* Entfernt alte schwarze Variante & Separator-Fetzen falls noch wirksam */
.site-footer::before,
.footer-separator,
.footer-bar {
    display:none !important;
}

/* ===== GENERAL GUARDS ===== */
.about, .education, .site-footer {
    max-width:100%;
}

/* Projects – Mobile: Bild unter dem Text */
@media (max-width: 900px){
    .projects-inner{
        grid-template-columns: 1fr;      /* einspaltig */
        align-items: start;
    }
    .proj-info{ order: 1; }
    .proj-preview{
        order: 2;
        margin-top: 1rem;               /* Luft unter dem Text */
        width: 100%;
    }
    /* falls globale .proj-links absolute gesetzt ist, hier neutralisieren */
    .proj-info .proj-links{
        position: static;
    }
}

/* Skills: auf kleinen Bildschirmen untereinander (einspaltig) */
@media (max-width: 740px){
    .skills-grid{
        grid-template-columns: 1fr !important; /* alles untereinander */
        gap: 1rem !important;
    }
    .skill-card{
        width: 100%;
        max-width: 680px;
        margin: 0 auto;              /* zentrieren */
    }
    .skills-inner h2{
        text-align: center;
    }
}

/* sehr kleine Geräte */
@media (max-width: 480px){
    .skill-card{
        padding: 1rem .9rem;         /* etwas kompakter */
    }
    .skill-card h3{
        font-size: 1rem;
    }
}

JAVA-SCRIPT Erklärung

Funktionsübersicht

Die Projektfunktion besteht aus mehreren Teilen:

  1. Datenhaltung → Projekte und Bilder liegen in einem Array
  2. Statusvariablen → speichern, welches Projekt/Bild aktiv ist
  3. Navigationselemente → Buttons & Dots
  4. Rendering → DOM-Elemente dynamisch aktualisieren
  5. Eventlistener → reagieren auf Klicks & wechseln Inhalte
  6. Verlinkung → Live-Demos & GitHub-Links werden automatisch eingefügt

Datenstruktur

Jedes Projekt wird in einem Array als Objekt gespeichert:

const projects = [
  {
    title: 'Dokumentationsseite mit Mkdocs',
    desc: 'Mkdocs Dokumentationsseite für Informatikmodule um Zusammenfassungen zu erstellen und weiteres.',
    images: ['project1.png'],
    live: 'https://162m-tim-ksb-4a5c416ce8446bb69cc6f6756d188f84b68600c6617f9ccb8b.gitlab.io/',
    repo: 'https://github.com/youruser/mkdocs'
  },
  {
    title: 'Projekt 2',
    desc: 'under construction',
    images: ['project2.png'],
    live: 'https://example.com/todo',
    repo: 'https://github.com/youruser/todo-app'
  },
  {
    title: 'Projekt 3 – Mini Game',
    desc: 'under construction',
    images: ['project3.png'],
    repo: 'https://github.com/youruser/mini-game'
  }
];

Erklärung

Schlüssel Bedeutung
title Name des Projekts
desc Beschreibung
images Array mit allen Bildern
live Link zur Live-Demo (optional)
repo Link zum GitHub-Repository (optional)

Statusvariablen

Diese beiden Variablen speichern den aktuellen Zustand:

let currentProject = 0; // aktives Projekt
let currentImage = 0;   // aktives Bild im Projekt

Projektnavigation (Dots)

Für jedes Projekt wird automatisch ein Auswahlpunkt (Dot) generiert:

const projDots = projects.map((_, i) => {
  const b = document.createElement('button');
  b.setAttribute('aria-label', `Projekt ${i + 1}`);
  b.addEventListener('click', () => goProject(i));
  projDotsWrap.appendChild(b);
  return b;
});

✅ Jeder Dot ruft beim Klick die Funktion goProject(i) auf
→ damit kann direkt zu einem bestimmten Projekt gesprungen werden.


Bildnavigation (Dots)

Pro Projekt können mehrere Bilder existieren.
Diese Funktion erstellt die Bild-Dots dynamisch:

function buildImageDots() {
  imgDotsWrap.innerHTML = '';
  imgDots = projects[currentProject].images.map((_, i) => {
    const b = document.createElement('button');
    b.setAttribute('aria-label', `Bild ${i + 1}`);
    b.addEventListener('click', () => goImage(i));
    imgDotsWrap.appendChild(b);
    return b;
  });
}

✅ Beim Wechsel des Projekts werden diese Dots neu generiert,
damit sie zu den aktuellen Bildern passen.


Render-Funktion

Die zentrale Funktion render() aktualisiert den gesamten DOM-Inhalt:

function render() {
  const p = projects[currentProject];
  const src = p.images[currentImage];

  idxEl.textContent = pad2(currentProject + 1);
  titleEl.textContent = p.title;
  descEl.textContent = p.desc;

  // Bild sanft einblenden
  imgEl.style.opacity = '0';
  const onLoad = () => {
    imgEl.style.opacity = '1';
    imgEl.removeEventListener('load', onLoad);
  };
  imgEl.addEventListener('load', onLoad);
  imgEl.src = src;

  // Dots aktualisieren
  projDots.forEach((d, i) => d.classList.toggle('active', i === currentProject));
  imgDots.forEach((d, i) => d.classList.toggle('active', i === currentImage));

  // Links (Live & GitHub)
  linksWrap.innerHTML = '';
  if (p.live) {
    const a = document.createElement('a');
    a.href = p.live;
    a.target = '_blank';
    a.rel = 'noopener noreferrer';
    a.textContent = 'Live';
    linksWrap.appendChild(a);
  }
  if (p.repo) {
    const a = document.createElement('a');
    a.href = p.repo;
    a.target = '_blank';
    a.rel = 'noopener noreferrer';
    a.textContent = 'GitHub';
    linksWrap.appendChild(a);
  }
}

Erklärung

Abschnitt Beschreibung
titleEl, descEl, idxEl setzen Titel, Beschreibung und Index
imgEl.src aktualisiert das Projektbild
Fade-Effekt lässt das neue Bild sanft erscheinen
projDots / imgDots aktive Punkte werden visuell hervorgehoben
linksWrap generiert automatisch die Buttons Live und GitHub

Projektwechsel

Die Funktion goProject() wechselt das aktive Projekt:

function goProject(i) {
  currentProject = (i + projects.length) % projects.length;
  currentImage = 0; // immer beim ersten Bild starten
  buildImageDots();
  render();
}

Erklärung

  • (i + projects.length) % projects.length → sorgt für Endlosschleife (Loop)
  • buildImageDots() → erzeugt neue Bild-Dots für das Projekt
  • render() → zeigt den neuen Inhalt an

Buttons:

prevBtn.addEventListener('click', () => goProject(currentProject - 1));
nextBtn.addEventListener('click', () => goProject(currentProject + 1));

Bildwechsel innerhalb eines Projekts

Wenn ein Projekt mehrere Screenshots hat, wird durch diese Funktion zwischen ihnen gewechselt:

function goImage(i) {
  const len = projects[currentProject].images.length;
  currentImage = (i + len) % len;
  render();
}

Auch hier sorgt der Modulo-Operator % dafür,
dass die Galerie endlos weiterläuft.


Initialisierung

Beim Laden der Seite wird der Slider einmal aufgebaut und angezeigt:

buildImageDots();
render();

So wird das erste Projekt mit seinem ersten Bild und allen Dots sofort dargestellt.