336 lines
12 KiB
PHP
336 lines
12 KiB
PHP
<?php
|
||
/**
|
||
* @file 404.php
|
||
* @brief Fehlerseite für nicht gefundene Dokumente (HTTP 404 Not Found)
|
||
*
|
||
* @details Diese Datei wird aufgerufen, wenn ein Benutzer eine URL aufruft,
|
||
* die auf dem Server nicht existiert. Sie sendet den korrekten HTTP-Statuscode 404 an den Browser,
|
||
* liest die angeforderte URI aus und bietet dem Benutzer benutzerfreundliche Möglichkeiten,
|
||
* zurück zur Startseite oder zur vorherigen Seite zu navigieren. Ein ansprechendes Design
|
||
* mit Glitch-Effekten macht die Fehlerseite visuell interessant.
|
||
*
|
||
* @author Fabian Schieder / Geizkragen Team
|
||
* @version 1.0
|
||
*/
|
||
|
||
// Wichtig: Echten 404-Status setzen, damit Suchmaschinen und Browser erkennen, dass die Seite nicht existiert.
|
||
// !!! Funktioniert in der Produktion nur zuverlässig (z.B. auf geizkragen.fabianschieder.com),
|
||
// wenn die Fehlerseite im VirtualHost oder der .htaccess entsprechend für 404-Fehler konfiguriert ist !!!
|
||
http_response_code(404);
|
||
|
||
// Request-Daten absichern, um XSS (Cross-Site Scripting) Angriffe bei der Ausgabe zu verhindern.
|
||
// htmlspecialchars wandelt spezielle Zeichen (<, >, &, ", ') in ungefährliche HTML-Entitäten um.
|
||
$requestUri = htmlspecialchars($_SERVER['REQUEST_URI'] ?? '', ENT_QUOTES, 'UTF-8');
|
||
$method = htmlspecialchars($_SERVER['REQUEST_METHOD'] ?? '', ENT_QUOTES, 'UTF-8');
|
||
|
||
// Optional: Einfaches Logging der 404-Fehler für administrative Zwecke.
|
||
// Schreibt die IP-Adresse des Clients, die HTTP-Methode und die angeforderte fehlerhafte URI in das PHP Error-Log.
|
||
error_log("[404] " . ($_SERVER['REMOTE_ADDR'] ?? '') . " $method $requestUri");
|
||
?>
|
||
<!DOCTYPE html>
|
||
<html lang="de">
|
||
<head>
|
||
<!-- Metadaten zur Zeichenkodierung und für ein responsives Layout -->
|
||
<meta charset="UTF-8">
|
||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||
|
||
<!-- Einbindung des Favicons -->
|
||
<link rel="icon" href="/assets/images/favicon.ico" sizes="any">
|
||
|
||
<!-- Einbindung des globalen Stylesheets -->
|
||
<link rel="stylesheet" href="/style.css">
|
||
|
||
<!-- Titel der Seite, der im Browser-Tab angezeigt wird -->
|
||
<title>404 – Seite nicht gefunden | Geizkragen</title>
|
||
</head>
|
||
<body>
|
||
|
||
<!-- Hauptinhaltsbereich der Fehlerseite -->
|
||
<main>
|
||
<!-- Container für die Zentrierung und Ausrichtung der Inhalte -->
|
||
<div class="container" style="
|
||
display: flex;
|
||
flex-direction: column;
|
||
align-items: center;
|
||
justify-content: center;
|
||
min-height: 100vh;
|
||
text-align: center;
|
||
padding-block: 4rem;
|
||
">
|
||
<!--
|
||
404 Glitch-Zahl:
|
||
Ein dekoratives Element, das die Zahl 404 groß und mit einem visuellen Störungs-Effekt (Glitch) darstellt.
|
||
Das Attribut aria-hidden="true" versteckt es vor Screenreadern, da es rein dekorativ ist.
|
||
-->
|
||
<div class="error-code" aria-hidden="true">404</div>
|
||
|
||
<!--
|
||
Error Card:
|
||
Eine visuelle Karte, die den Text, das Icon und die Navigations-Aktionen strukturiert zusammenfasst.
|
||
-->
|
||
<div class="error-card">
|
||
|
||
<!-- Icon-Bereich in der Fehlerkarte -->
|
||
<div class="error-card__icon">
|
||
<!-- SVG-Icon: Ein durchgestrichener Kreis zur symbolischen Darstellung eines Fehlers -->
|
||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"
|
||
stroke-linecap="round" stroke-linejoin="round" width="28" height="28">
|
||
<circle cx="11" cy="11" r="8"/>
|
||
<path d="M21 21l-4.35-4.35"/>
|
||
<line x1="8" y1="11" x2="14" y2="11"/>
|
||
</svg>
|
||
</div>
|
||
|
||
<!-- Überschrift für die Fehlerkarte -->
|
||
<h1 class="error-card__title">Seite nicht gefunden</h1>
|
||
|
||
<!-- Erklärender Text für den Benutzer -->
|
||
<p class="error-card__text">
|
||
Die Seite, die du suchst, existiert leider nicht oder wurde verschoben.
|
||
</p>
|
||
|
||
<!-- Ausgabe des angeforderten Pfads, der zum Fehler geführt hat -->
|
||
<div class="error-card__path"><?php echo $requestUri; ?></div>
|
||
|
||
<!-- Container für die Navigationsbuttons -->
|
||
<div class="error-card__actions">
|
||
<!-- Button: Zurück zur Startseite -->
|
||
<a href="/index.php" class="btn btn--primary">
|
||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"
|
||
stroke-linecap="round" stroke-linejoin="round" width="18" height="18">
|
||
<path d="M3 9l9-7 9 7v11a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2z"/>
|
||
<polyline points="9 22 9 12 15 12 15 22"/>
|
||
</svg>
|
||
Zur Startseite
|
||
</a>
|
||
|
||
<!-- Button: Zurück zur vorherigen Seite im Browser-Verlauf -->
|
||
<button onclick="history.back()" class="btn btn--ghost">
|
||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"
|
||
stroke-linecap="round" stroke-linejoin="round" width="18" height="18">
|
||
<line x1="19" y1="12" x2="5" y2="12"/>
|
||
<polyline points="12 19 5 12 12 5"/>
|
||
</svg>
|
||
Zurück
|
||
</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</main>
|
||
|
||
<!-- Inlined CSS-Bereich für das spezifische Styling der 404-Komponenten -->
|
||
<style>
|
||
/**
|
||
* @brief Styling für die 404 Glitch-Zahl
|
||
* @details Definiert eine große, animierte Textdarstellung mit einem Farbgradienten.
|
||
*/
|
||
/* ── 404 Glitch-Zahl ── */
|
||
.error-code {
|
||
font-size: clamp(7rem, 18vw, 12rem);
|
||
font-weight: 900;
|
||
line-height: 1;
|
||
letter-spacing: -0.04em;
|
||
/* Lineares Farbverlaufs-Hintergrundbild für den Text */
|
||
background: linear-gradient(135deg, var(--color-primary), #4f46e5, var(--color-accent));
|
||
background-size: 200% 200%;
|
||
/* Wendet den Hintergrund nur auf den Text an (Webkit und Standard) */
|
||
-webkit-background-clip: text;
|
||
-webkit-text-fill-color: transparent;
|
||
background-clip: text;
|
||
/* Animation zur Verschiebung des Gradienten */
|
||
animation: gradientShift 4s ease-in-out infinite;
|
||
margin-bottom: 1.5rem;
|
||
position: relative;
|
||
display: inline-block;
|
||
}
|
||
|
||
/**
|
||
* @brief Pseudo-Elemente für den Glitch-Tearing-Effekt
|
||
* @details Erstellt Kopien des Textes, die beschnitten und verschoben werden.
|
||
*/
|
||
.error-code::before,
|
||
.error-code::after {
|
||
content: '404';
|
||
position: absolute;
|
||
top: 0; left: 0; right: 0;
|
||
overflow: hidden;
|
||
background: inherit;
|
||
-webkit-background-clip: text;
|
||
-webkit-text-fill-color: transparent;
|
||
background-clip: text;
|
||
}
|
||
|
||
/* Oberer Teil des Glitches */
|
||
.error-code::before {
|
||
clip-path: inset(0 0 65% 0);
|
||
animation: glitch1 3s infinite linear alternate-reverse;
|
||
}
|
||
|
||
/* Unterer Teil des Glitches */
|
||
.error-code::after {
|
||
clip-path: inset(65% 0 0 0);
|
||
animation: glitch2 3s infinite linear alternate-reverse;
|
||
}
|
||
|
||
/** Keyframes-Animationen für den wackeligen Glitch-Effekt */
|
||
@keyframes glitch1 {
|
||
0%,92% { transform: translate(0); }
|
||
93% { transform: translate(-6px,-2px); }
|
||
94% { transform: translate(3px,1px); }
|
||
95% { transform: translate(-2px,2px); }
|
||
96%,100%{ transform: translate(0); }
|
||
}
|
||
|
||
@keyframes glitch2 {
|
||
0%,90% { transform: translate(0); }
|
||
91% { transform: translate(4px,2px); }
|
||
92% { transform: translate(-5px,-1px); }
|
||
93% { transform: translate(2px,-2px); }
|
||
94%,100%{ transform: translate(0); }
|
||
}
|
||
|
||
/**
|
||
* @brief Styling für die Error-Karten-Komponente (Glassmorphism-Effekt)
|
||
*/
|
||
/* ── Card ── */
|
||
.error-card {
|
||
background: rgba(31, 41, 55, 0.55);
|
||
backdrop-filter: blur(20px);
|
||
-webkit-backdrop-filter: blur(20px);
|
||
border: 1px solid var(--border-subtle);
|
||
border-radius: var(--radius-xl);
|
||
padding: 2.5rem 2.5rem 2rem;
|
||
max-width: 520px;
|
||
width: 100%;
|
||
box-shadow: var(--shadow-md), 0 0 60px var(--color-primary-glow);
|
||
animation: fadeInUp 0.6s ease-out 0.1s both;
|
||
}
|
||
|
||
/* Styling für den Kreis mit dem Icon in der Karte */
|
||
.error-card__icon {
|
||
width: 56px;
|
||
height: 56px;
|
||
margin: 0 auto 1.25rem;
|
||
background: var(--color-primary-soft);
|
||
border-radius: 50%;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
color: var(--color-primary);
|
||
animation: pulse-glow 3s ease-in-out infinite;
|
||
}
|
||
|
||
/* Typografie für den Karten-Titel */
|
||
.error-card__title {
|
||
font-size: 1.4rem;
|
||
font-weight: 800;
|
||
color: var(--text-primary);
|
||
margin-bottom: 0.6rem;
|
||
}
|
||
|
||
/* Typografie für den Info-Text */
|
||
.error-card__text {
|
||
color: var(--text-muted);
|
||
font-size: 0.95rem;
|
||
line-height: 1.6;
|
||
margin-bottom: 0.25rem;
|
||
}
|
||
|
||
/* Darstellung des Pfad-Textes im Monospace-Look */
|
||
.error-card__path {
|
||
display: inline-block;
|
||
background: var(--color-primary-soft);
|
||
border: 1px solid rgba(39, 74, 151, 0.25);
|
||
color: #6b8fd8;
|
||
font-family: 'Courier New', Courier, monospace;
|
||
font-size: 0.82rem;
|
||
font-weight: 600;
|
||
padding: 0.35rem 0.9rem;
|
||
border-radius: var(--radius-md);
|
||
margin: 1rem 0 1.5rem;
|
||
word-break: break-all;
|
||
max-width: 100%;
|
||
}
|
||
|
||
/**
|
||
* @brief Layout und Styling der Aktions-Buttons
|
||
*/
|
||
/* ── Buttons ── */
|
||
.error-card__actions {
|
||
display: flex;
|
||
gap: 0.75rem; /* Abstand zwischen den Buttons */
|
||
justify-content: center;
|
||
flex-wrap: wrap; /* Erlaubt Umbruch bei kleinen Bildschirmen */
|
||
}
|
||
|
||
/* Basis-Klasse für Buttons mit Flex-Eigenschaften und Transitionen */
|
||
.btn {
|
||
display: inline-flex;
|
||
align-items: center;
|
||
gap: 0.45rem;
|
||
padding: 0.7rem 1.4rem;
|
||
border-radius: var(--radius-md);
|
||
font-size: 0.9rem;
|
||
font-weight: 600;
|
||
font-family: var(--font-family);
|
||
text-decoration: none;
|
||
cursor: pointer;
|
||
border: none;
|
||
transition: all var(--transition-smooth);
|
||
}
|
||
|
||
/* Haupt-Button (Primäraktion) */
|
||
.btn--primary {
|
||
background: var(--color-primary);
|
||
color: #fff;
|
||
box-shadow: 0 4px 14px var(--color-primary-glow);
|
||
}
|
||
|
||
/* Hover-Zustand des Haupt-Buttons */
|
||
.btn--primary:hover {
|
||
background: var(--color-primary-hover);
|
||
transform: translateY(-2px); /* Hebe-Effekt */
|
||
box-shadow: 0 8px 24px var(--color-primary-glow);
|
||
color: #fff;
|
||
}
|
||
|
||
/* Sekundärer Ghost-Button (Transparenter Hintergrund) */
|
||
.btn--ghost {
|
||
background: rgba(255,255,255,0.04);
|
||
color: var(--text-muted);
|
||
border: 1px solid var(--border-subtle);
|
||
}
|
||
|
||
/* Hover-Zustand des Ghost-Buttons */
|
||
.btn--ghost:hover {
|
||
background: rgba(255,255,255,0.09);
|
||
color: var(--text-primary);
|
||
transform: translateY(-2px);
|
||
}
|
||
|
||
/**
|
||
* @brief Responsive Anpassungen (Media Queries)
|
||
* @details Passt das Layout auf kleineren Bildschirmen (z.B. Mobilgeräten) an.
|
||
*/
|
||
/* ── Responsive ── */
|
||
@media (max-width: 480px) {
|
||
/* Verringerte Polsterung der Karte für kleine Screens */
|
||
.error-card {
|
||
padding: 1.8rem 1.25rem 1.5rem;
|
||
}
|
||
/* Stapeln der Buttons untereinander */
|
||
.error-card__actions {
|
||
flex-direction: column;
|
||
}
|
||
/* Buttons füllen die gesamte Breite aus und zentrieren ihren Inhalt */
|
||
.btn {
|
||
justify-content: center;
|
||
width: 100%;
|
||
}
|
||
}
|
||
</style>
|
||
|
||
</body>
|
||
</html>
|
||
|