Hi ich bin

Patrick Hofer Patrick Hofer Hover
/ Webdesigner, Lehrling

Patrick Hofer

Hey! Ich bin Patrick, Webdesign-Lehrling aus Tirol. Ich erstelle moderne, professionelle Websites für Unternehmen und liebe es, mit neuen Technologien zu experimentieren.

Seit September 2024 in der Ausbildung, arbeite ich nebenbei an eigenen Projekten und ersten Kundenprojekten - von der Planung bis zum Launch.

/ 01

HTML

Das Fundament jeder Website. Ich strukturiere Inhalte semantisch, barrierefrei und suchmaschinenfreundlich – sauberer Code von Anfang an.

Semantisches Markup, zugängliche Formulare, korrekte Dokumentenstruktur. Die Basis auf der alles aufbaut.

— klick mich —

Hallo, ich bin Patrick.

Webdesigner aus Tirol

index.html
<h2>Hallo, ich bin Patrick.</h2>
<p>Webdesigner aus Tirol</p>
<button>Kontakt</button>
<button>E-Mail</button>

— klick mich —

Hallo, ich bin Patrick.

Webdesigner aus Tirol

style.css
<!DOCTYPE html>
<html lang="de">
<head>
<meta charset="UTF-8">
<title>Patrick</title>
<link href="https://fonts.googleapis.com/css2?family=Playfair+Display:wght@600&family=Inter:wght@400;500;600&display=swap" rel="stylesheet">
<style>
body {
  margin: 0;
  height: 100vh;
  display: flex;
  align-items: center;
  justify-content: center;
  background: #e9e9e9;
  font-family: 'Inter', sans-serif;
}
.container {
  text-align: center;
}
h1 {
  font-family: 'Playfair Display', serif;
  font-size: 56px;
  margin: 0 0 10px 0;
  color: #111;
}
.subtitle {
  font-size: 24px;
  color: #666;
  margin-bottom: 40px;
}
.buttons {
  display: flex;
  gap: 20px;
  justify-content: center;
}
.btn {
  padding: 16px 40px;
  border-radius: 40px;
  font-size: 20px;
  font-weight: 600;
  text-decoration: none;
  transition: all 0.25s ease;
}
.btn-primary {
  background: #7b5aa6;
  color: white;
}
.btn-primary:hover {
  background: #6b4c93;
  transform: translateY(-3px);
}
.btn-outline {
  border: 3px solid #7b5aa6;
  color: #7b5aa6;
}
.btn-outline:hover {
  background: #7b5aa6;
  color: white;
  transform: translateY(-3px);
}
</style>
</head>
<body>
<div class="container">
  <h1>Hallo, ich bin Patrick.</h1>
  <div class="subtitle">Webdesigner aus Tirol</div>
  <div class="buttons">
    <a href="#" class="btn btn-primary">Kontakt</a>
    <a href="#" class="btn btn-outline">E-Mail</a>
  </div>
</div>
</body>
</html>
/ 02

CSS

Von simplen Layouts bis zu komplexen Animationen. Ich gestalte Interfaces, die nicht nur funktionieren, sondern begeistern.

Flexbox, Grid, Custom Properties, Animationen & moderne Effekte. Design, das im Gedächtnis bleibt.

/ 03

JavaScript

Interaktivität, die funktioniert. Von Typing-Effekten über Canvas-Animationen bis zu API-Calls – ich bringe Websites zum Leben.

DOM Manipulation, Event Handling, Animationen, moderne ES6+ Syntax. Code, der nicht nur läuft, sondern begeistert.

— klick mich —

hover über die box

Hallo, ich bin Patrick.

Webdesigner aus Tirol

script.js
<!DOCTYPE html>
<html lang="de">
<head>
  <meta charset="UTF-8">
  <meta name="viewport"
    content="width=device-width,
    initial-scale=1.0">
  <title>Patrick –
    Webdesigner aus Tirol
  </title>
  <link
    href="https://fonts.googleapis
    .com/css2?family=Syne:
    wght@700;800&family=
    DM+Sans:wght@400;500
    &display=swap"
    rel="stylesheet">
  <style>
    *, *::before, *::after {
      box-sizing: border-box;
      margin: 0; padding: 0; }

    body {
      min-height: 100vh;
      display: flex;
      flex-direction: column;
      align-items: center;
      justify-content: center;
      background: #f0f0f0;
      font-family: 'DM Sans',
        sans-serif; }

    .hint {
      letter-spacing: 0.18em;
      font-size: 0.72rem;
      text-transform: uppercase;
      color: #aaa;
      margin-bottom: 28px;
      transition: opacity 0.4s; }

    /* ── CARD ── */
    .card {
      position: relative;
      width: min(680px, 92vw);
      padding: 52px 48px 48px;
      border-radius: 22px;
      cursor: pointer;
      overflow: hidden;
      background: #1a1a1f;
      border: 1.5px solid #333;
      box-shadow: 0 4px 32px
        rgba(0,0,0,0.18);
      transition:
        transform 0.55s
          cubic-bezier(.22,1,.36,1),
        box-shadow 0.55s
          cubic-bezier(.22,1,.36,1),
        border-color 0.55s ease; }

    .card::before {
      content: '';
      position: absolute;
      inset: 0;
      border-radius: inherit;
      background: radial-gradient(
        ellipse at 50% 110%,
        rgba(120,80,200,0.55) 0%,
        transparent 70%);
      opacity: 0;
      transition: opacity 0.55s ease;
      pointer-events: none;
      z-index: 0; }

    .card::after {
      content: '';
      position: absolute;
      inset: -3px;
      border-radius: 24px;
      background: transparent;
      box-shadow: 0 0 0px 0px
        rgba(130,80,220,0);
      opacity: 0;
      transition:
        opacity 0.55s ease,
        box-shadow 0.55s ease;
      pointer-events: none;
      z-index: -1; }

    .card:hover {
      transform: scale(1.045);
      border-color:
        rgba(150,100,240,0.7);
      box-shadow:
        0 0 0 1.5px
          rgba(150,100,240,0.35),
        0 24px 60px
          rgba(100,60,180,0.35),
        0 4px 16px
          rgba(0,0,0,0.3); }

    .card:hover::before { opacity: 1; }
    .card:hover::after {
      opacity: 1;
      box-shadow: 0 0 80px 20px
        rgba(120,70,200,0.3); }

    .card-inner {
      position: relative;
      z-index: 1;
      display: flex;
      flex-direction: column;
      align-items: center;
      text-align: center;
      gap: 14px; }

    #particles {
      position: absolute;
      inset: 0;
      border-radius: inherit;
      pointer-events: none;
      z-index: 0;
      opacity: 0;
      transition: opacity 0.4s ease; }
    .card:hover #particles {
      opacity: 1; }

    .name {
      font-family: 'Syne', sans-serif;
      font-weight: 800;
      font-size: clamp(
        1.9rem, 5vw, 2.6rem);
      color: #e0e0e0;
      transition: color 0.5s ease;
      line-height: 1.1; }
    .card:hover .name {
      color: #ffffff; }

    .role {
      font-size: 1rem;
      color: #666;
      letter-spacing: 0.02em;
      transition: color 0.5s ease; }
    .card:hover .role {
      color: rgba(200,170,255,0.85); }

    .buttons {
      display: flex;
      gap: 14px;
      margin-top: 10px; }

    .btn {
      padding: 13px 32px;
      border-radius: 50px;
      border: 1.5px solid #444;
      background: #2a2a2f;
      color: #888;
      font-family: 'DM Sans',
        sans-serif;
      font-size: 0.95rem;
      font-weight: 500;
      cursor: pointer;
      position: relative;
      overflow: hidden;
      transition:
        background 0.45s ease,
        border-color 0.45s ease,
        color 0.45s ease,
        transform 0.2s ease,
        box-shadow 0.45s ease; }

    .btn-ripple {
      position: absolute;
      border-radius: 50%;
      background:
        rgba(200,160,255,0.3);
      transform: scale(0);
      animation: ripple 0.6s linear;
      pointer-events: none; }
    @keyframes ripple {
      to { transform: scale(4);
           opacity: 0; } }

    .card:hover .btn {
      background:
        rgba(100,60,180,0.55);
      border-color:
        rgba(170,120,255,0.6);
      color: #e8d8ff;
      box-shadow: 0 2px 18px
        rgba(120,70,200,0.25); }
    .card:hover .btn:hover {
      background:
        rgba(120,70,200,0.75);
      transform:
        translateY(-2px) scale(1.04);
      box-shadow: 0 6px 24px
        rgba(120,70,200,0.45); }
    .card:hover .btn:active {
      transform:
        translateY(0) scale(0.98); }

    .dot {
      position: absolute;
      border-radius: 50%;
      background:
        rgba(140,90,230,0.5);
      pointer-events: none;
      opacity: 0;
      transition: opacity 0.5s ease;
      filter: blur(1px); }
    .card:hover .dot { opacity: 1; }
    .dot-1 { width:6px; height:6px;
      top:18px; right:32px;
      animation: floatDot 3.2s
        ease-in-out infinite; }
    .dot-2 { width:4px; height:4px;
      top:38px; right:58px;
      animation: floatDot 2.7s
        ease-in-out infinite 0.6s; }
    .dot-3 { width:5px; height:5px;
      bottom:24px; left:28px;
      animation: floatDot 3.8s
        ease-in-out infinite 1.2s; }
    @keyframes floatDot {
      0%,100% {
        transform: translateY(0); }
      50% {
        transform: translateY(-8px); }
    }
  </style>
</head>
<body>

  <p class="hint">
    Hover über die Box
  </p>

  <div class="card"
    id="card">
    <canvas
      id="particles"></canvas>

    <span class="dot dot-1"></span>
    <span class="dot dot-2"></span>
    <span class="dot dot-3"></span>

    <div class="card-inner">
      <h1 class="name">
        Hallo, ich bin Patrick.
      </h1>
      <p class="role">
        Webdesigner aus Tirol
      </p>
      <div class="buttons">
        <button class="btn"
          id="btnKontakt">
          Kontakt
        </button>
        <button class="btn"
          id="btnEmail">
          E-Mail
        </button>
      </div>
    </div>
  </div>

  <script>
    /* ── Particle system ── */
    const canvas = document
      .getElementById('particles');
    const ctx = canvas
      .getContext('2d');
    const card = document
      .getElementById('card');
    let particles = [];
    let animFrame;
    let running = false;

    function resizeCanvas() {
      canvas.width =
        card.offsetWidth;
      canvas.height =
        card.offsetHeight;
    }
    resizeCanvas();
    window.addEventListener(
      'resize', resizeCanvas);

    class Particle {
      constructor() {
        this.reset(true);
      }
      reset(init = false) {
        this.x = Math.random()
          * canvas.width;
        this.y = init
          ? Math.random()
            * canvas.height
          : canvas.height + 4;
        this.r = Math.random()
          * 1.8 + 0.4;
        this.vy = -(Math.random()
          * 0.6 + 0.25);
        this.vx =
          (Math.random() - 0.5)
          * 0.4;
        this.alpha =
          Math.random() * 0.5 + 0.2;
        this.hue =
          Math.random() * 40 + 260;
      }
      update() {
        this.x += this.vx;
        this.y += this.vy;
        this.alpha -= 0.0015;
        if (this.y < -4
          || this.alpha <= 0)
          this.reset();
      }
      draw() {
        ctx.save();
        ctx.globalAlpha =
          Math.max(0,
            this.alpha);
        ctx.fillStyle =
          `hsl(${this.hue},70%,70%)`;
        ctx.beginPath();
        ctx.arc(this.x,
          this.y,
          this.r,
          0, Math.PI * 2);
        ctx.fill();
        ctx.restore();
      }
    }

function initParticles() {
      particles =
        Array.from(
          { length: 55 },
          () => new Particle());
    }

    function animate() {
      ctx.clearRect(0, 0,
        canvas.width,
        canvas.height);
      particles.forEach(
        p => {
          p.update();
          p.draw();
        });
      animFrame =
        requestAnimationFrame(
          animate);
    }

    card.addEventListener(
    'mouseenter', () => {
      if (!running) {
        running = true;
        initParticles();
        animate();
      }
    });
    card.addEventListener(
    'mouseleave', () => {
      running = false;
      cancelAnimationFrame(
        animFrame);
      ctx.clearRect(0, 0,
        canvas.width,
        canvas.height);
    });

    /* ── Button ripple ── */
    document.querySelectorAll(
    '.btn').forEach(btn => {
      btn.addEventListener(
      'click', function(e) {
        const rect =
          btn
          .getBoundingClientRect();
        const ripple =
          document
          .createElement('span');
        ripple.className =
          'btn-ripple';
        const size = Math.max(
          rect.width,
          rect.height);
        ripple.style.cssText = `
          width:${size}px;
          height:${size}px;
          left:${e.clientX
            - rect.left
            - size/2}px;
          top:${e.clientY
            - rect.top
            - size/2}px;
        `;
        btn.appendChild(ripple);
     ripple.addEventListener(
          'animationend',
          () => ripple.remove());
      });
    });
  </script>
</body>
</html>