+
Empfehlung des Tages
= $rModel ?>
ab = $rPriceFormatted ?> €
120 ? (function_exists('mb_substr') ? mb_substr($rDesc, 0, 120) : substr($rDesc, 0, 120)) . '...' : $rDesc;
?>
= $descShort ?>
Jetzt ansehen ›
+
+
diff --git a/admin_users.php b/admin_users.php
index 2191f81..90aa944 100644
--- a/admin_users.php
+++ b/admin_users.php
@@ -1,79 +1,142 @@
query("DELETE FROM userRoles WHERE userID = $deleteId");
+ /// @var mysqli_stmt $delStmt Bereitet das Lösch-Statement für die `users`-Tabelle vor.
$delStmt = $conn->prepare("DELETE FROM users WHERE userID = ?");
+ /// Bindet die Benutzer-ID (Parameter-Typ i=Integer) an das Statement.
$delStmt->bind_param("i", $deleteId);
+ /// Führt die Löschung in der Datenbank aus.
$delStmt->execute();
+ /// Schließt das Prepared Statement.
$delStmt->close();
+ /// @var string $successMsg Setzt die Erfolgsmeldung.
$successMsg = "Benutzer erfolgreich gelöscht.";
} else {
+ /// @var string $errorMsg Setzt die Fehlermeldung (Selbstlöschung unzulässig).
$errorMsg = "Du kannst dich nicht selbst löschen.";
}
}
-// 2b) Aktion: Rollen aktualisieren
+/**
+ * 2b) Aktion: Rollen aktualisieren
+ * @brief Verarbeitet Massen-Aktualisierungen von Benutzerrollen aus dem Formular.
+ * @details Diese Aktion wird ausgelöst, wenn der Administrator auf "Alle Rollen speichern" klickt.
+ */
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['update_all_roles'])) {
+ /// @var array $usersRolesData Die neu zugewiesenen Benutzerrollen (ID als Key, Rollen-ID als Value).
$usersRolesData = isset($_POST['user_roles']) && is_array($_POST['user_roles']) ? $_POST['user_roles'] : [];
+ /// @var array $submittedUsers Liste der Benutzer, deren Rollen übermittelt wurden.
$submittedUsers = isset($_POST['submitted_users']) && is_array($_POST['submitted_users']) ? $_POST['submitted_users'] : [];
+ /// Iteriert über jeden übermittelten Benutzer, um seine Rollenzuweisungen anzupassen.
foreach ($submittedUsers as $uId) {
+ /// @var int $updateId Die ID des aktuellen Benutzers.
$updateId = (int)$uId;
+ /// Übergeht den Administrator, der die Seite gerade aufruft (Sicherheitsmassnahme).
if ($updateId === (int)$_SESSION['user_id']) {
continue;
}
+ /// @var string $selectedRole Die aus dem Formular ausgewählte Rolle.
$selectedRole = isset($usersRolesData[$updateId]) ? $usersRolesData[$updateId] : '';
+ /// @var mysqli_stmt $delStmt Entfernt vorab bestehende Rollenzuweisungen des Benutzers aus der DB.
$delStmt = $conn->prepare("DELETE FROM userRoles WHERE userID = ?");
$delStmt->bind_param("i", $updateId);
$delStmt->execute();
$delStmt->close();
+ /// Falls eine neue Rolle gewählt wurde, fügt diese in die Datenbank ein.
if (!empty($selectedRole)) {
+ /// @var mysqli_stmt $insStmt Bereitet das Insert-Statement für `userRoles` vor.
$insStmt = $conn->prepare("INSERT INTO userRoles (userID, roleID) VALUES (?, ?)");
+ /// @var int $roleIdInt Die Rolle wird sicher auf Integer gecastet.
$roleIdInt = (int)$selectedRole;
+ /// Bindet UserID und Rollen-ID als Integer-Werte an die Query.
$insStmt->bind_param("ii", $updateId, $roleIdInt);
$insStmt->execute();
$insStmt->close();
}
}
+ /// Setzt Erfolgs-Feedback für den Bereich Rollenverwaltung.
$successMsg = "Rollen erfolgreich aktualisiert.";
}
-// 2c) Alle verfügbaren Rollen laden
+/**
+ * 2c) Alle verfügbaren Rollen laden
+ * @brief Liest alle Rollen aus, um sie in den Dropdowns zur Auswahl anzuzeigen.
+ */
$allRoles = [];
+/// Startet die Abfrage aller Datensätze der Tabelle `roles`, aufsteigend sortiert nach Namen.
$rolesQuery = $conn->query("SELECT roleID, name FROM roles ORDER BY name ASC");
if ($rolesQuery) {
+ /// Fügt jede gefundene Rolle dem Array hinzu.
while ($r = $rolesQuery->fetch_assoc()) {
$allRoles[] = $r;
}
}
-// 3) Alle Benutzer laden (mit Suche)
+/**
+ * 3) Alle Benutzer laden (mit Suche)
+ * @brief Verarbeitet Such- und Filterkriterien, um ein Set an Benutzern abzufragen.
+ */
+/// @var string $searchQuery Die übermittelte Suchanfrage.
$searchQuery = isset($_GET['search']) ? trim($_GET['search']) : '';
+/// @var string $searchParam Wird als Parameter für die SQL-Wildcard-Suche vorbereitet.
$searchParam = '%' . $searchQuery . '%';
+/// @var int $filterRole Ggf. übergebene Rollen-Filterung des Nutzers.
$filterRole = isset($_GET['role']) ? (int)$_GET['role'] : 0;
+/// @var string $sql Basis-Select zum Abfragen der Benutzerinformationen und der gruppierten Rollen-IDs.
$sql = "
SELECT u.userID, u.email, u.displayname, u.profilePicture, u.isActive,
GROUP_CONCAT(ur.roleID) as roleIDs
@@ -81,44 +144,61 @@ $sql = "
LEFT JOIN userRoles ur ON u.userID = ur.userID
";
+/// @var array $whereClauses Array, mit dem die WHERE-Bedingungen flexibel aufgebaut werden sollen.
$whereClauses = [];
+/// @var string $types Zusammenstellung von Typ-Spezifizierern für das `bind_param`.
$types = "";
+/// @var array $params Liste der Werte, die für Prepared Statements gebunden werden.
$params = [];
+/// Sofern die Suchanfrage nicht leer ist, wird nach Displayname und E-Mail gefiltert.
if ($searchQuery !== '') {
$whereClauses[] = "(u.displayname LIKE ? OR u.email LIKE ?)";
- $types .= "ss";
+ $types .= "ss"; // ss referenziert 2 String Parameter
$params[] = $searchParam;
$params[] = $searchParam;
}
+/// Fügt eine Bedingung hinzu, sofern mit einer bestimmten Rolle gefiltert werden soll.
if ($filterRole > 0) {
- // Da wir einen LEFT JOIN mit GROUP_CONCAT haben und auf Rollen filtern wollen,
- // können wir als einfache Lösung einen Subselect für EXISTS machen, damit
- // alle Rollen des Benutzers in GROUP_CONCAT erhalten bleiben,
- // aber nur Nutzer gezeigt werden, die auch die geforderte Rolle haben.
+ /**
+ * Da wir einen LEFT JOIN mit GROUP_CONCAT haben und auf Rollen filtern wollen,
+ * können wir als einfache Lösung einen Subselect für EXISTS machen, damit
+ * alle Rollen des Benutzers in GROUP_CONCAT erhalten bleiben,
+ * aber nur Nutzer gezeigt werden, die auch die geforderte Rolle haben.
+ */
$whereClauses[] = "EXISTS (SELECT 1 FROM userRoles sub_ur WHERE sub_ur.userID = u.userID AND sub_ur.roleID = ?)";
- $types .= "i";
+ $types .= "i"; // i referenziert einen Integer
$params[] = $filterRole;
}
+/// Erweitert den SQL-String um die zusammengefassten Filter-Clauses.
if (!empty($whereClauses)) {
$sql .= " WHERE " . implode(" AND ", $whereClauses);
}
+/// Fügt der SQL die finale Gruppierung und Sortierung nach User ID an.
$sql .= " GROUP BY u.userID ORDER BY u.userID ASC";
+/// @var mysqli_stmt $stmtUsers Bereitet das komplette Nutzer-Bezugs-Statement vor.
$stmtUsers = $conn->prepare($sql);
+/// Wenn Bindings existieren, binden wir sie dynamisch via `$params`-Spread-Operator an das SQL-Statement.
if (!empty($params)) {
$stmtUsers->bind_param($types, ...$params);
}
+/// Das vorbereitete SQL ausführen.
$stmtUsers->execute();
+/// @var mysqli_result $usersResult Empfängt das Result-Set aus der getätigten Fetch-Operation.
$usersResult = $stmtUsers->get_result();
+/**
+ * @brief Sammelt Filter-Attribute für die Fortführung der URL-Parameter bei Massenänderungen.
+ */
$formActionParams = [];
if ($searchQuery !== '') $formActionParams['search'] = $searchQuery;
if ($filterRole > 0) $formActionParams['role'] = $filterRole;
+/// @var string $formActionUrl Stellt die Request-URI des Formulars samt GET-Parametern zusammen.
$formActionUrl = "admin_users.php";
if (!empty($formActionParams)) {
$formActionUrl .= "?" . http_build_query($formActionParams);
@@ -126,9 +206,22 @@ if (!empty($formActionParams)) {
?>
-
+
+
+
-
+
= htmlspecialchars($successMsg) ?>
-
+
= htmlspecialchars($errorMsg) ?>
@@ -188,9 +285,13 @@ if (!empty($formActionParams)) {
- fetch_assoc()): ?>
+ fetch_assoc()): ?>
@@ -251,4 +352,7 @@ if (!empty($formActionParams)) {
-
+
diff --git a/api/search_products.php b/api/search_products.php
index 616052d..a2eed9c 100644
--- a/api/search_products.php
+++ b/api/search_products.php
@@ -1,39 +1,90 @@
15) {
$limit = 15;
}
- // Minimum query length to reduce load/noise
+ /**
+ * Mindestlänge für die Suche.
+ * Wenn der Suchbegriff leer ist oder aus weniger als 1 Zeichen besteht,
+ * wird ein leeres JSON-Array zurückgegeben und die Ausführung beendet.
+ */
if (mb_strlen($q, 'UTF-8') < 1) {
echo json_encode(['items' => []], JSON_UNESCAPED_UNICODE);
exit;
}
- // Escape LIKE wildcards (% _), then add %...%
+ /**
+ * @var string $like Der escapte Such-String für das LIKE-Statement in SQL.
+ * Spezielle Wildcard-Zeichen (%) und (_) werden escapt, um SQL-Fehler
+ * oder unerwartetes Verhalten bei Benutzereingaben zu verhindern.
+ */
$like = addcslashes($q, "%_\\");
$like = '%' . $like . '%';
- // Simple search: model + description
+ /**
+ * @var string $sql Die SQL-Abfrage zur Suche nach Produkten.
+ * Durchsucht die Spalten `model` und `description`.
+ * Sortiert Treffer im `model` vor Treffern in der `description`.
+ */
$sql = "
SELECT p.productID, p.model, p.description, p.imagePath
FROM products p
@@ -44,24 +95,49 @@ try {
LIMIT ?
";
+ /**
+ * @var mysqli_stmt|PDOStatement $stmt Das vorbereitete SQL-Statement.
+ */
$stmt = $conn->prepare($sql);
+
+ // Überprüfen, ob das Statement erfolgreich vorbereitet wurde
if (!$stmt) {
+ // HTTP-Statuscode 500 für einen internen Serverfehler setzen
http_response_code(500);
echo json_encode(['error' => 'DB-Query konnte nicht vorbereitet werden.'], JSON_UNESCAPED_UNICODE);
exit;
}
+ /**
+ * Binden der Parameter an das vorbereitete Statement.
+ * 'sssi' bedeutet: String, String, String, Integer.
+ */
$stmt->bind_param('sssi', $like, $like, $like, $limit);
+
+ // Ausführen der vorbereiteten Abfrage
$stmt->execute();
+
+ // Holen des Ergebnisses aus der Datenbank
$res = $stmt->get_result();
+ /**
+ * @var array $items Das Array zur Speicherung der aufbereiteten Suchergebnisse.
+ */
$items = [];
+
+ // Durchlaufen der einzelnen Datensätze / Zeilen
while ($row = $res->fetch_assoc()) {
+ /**
+ * @var int $id Die Produkt-ID iterierten Produkts.
+ */
$id = (int)($row['productID'] ?? 0);
+
+ // Überspringe ungültige oder defekte IDs
if ($id <= 0) {
continue;
}
+ // Hinzufügen des formatierten Produkts in das Result-Array
$items[] = [
'id' => $id,
'model' => (string)($row['model'] ?? ''),
@@ -71,9 +147,17 @@ try {
];
}
+ /**
+ * Rückgabe des Arrays als JSON-kodierter String.
+ * JSON_UNESCAPED_UNICODE verhindert, dass Umlaute escapet werden (z.B. %u00e4).
+ */
echo json_encode(['items' => $items], JSON_UNESCAPED_UNICODE);
+
} catch (Throwable $e) {
+ /**
+ * Fehlerbehandlung im Falle einer Exception (z.B. bei einem Datenbankfehler).
+ * Sendet einen 500er Statuscode und eine generische Fehlermeldung als JSON.
+ */
http_response_code(500);
echo json_encode(['error' => 'Serverfehler'], JSON_UNESCAPED_UNICODE);
}
-
diff --git a/assets/css/catbar.css b/assets/css/catbar.css
index 3f6dd6d..94959b2 100644
--- a/assets/css/catbar.css
+++ b/assets/css/catbar.css
@@ -1,7 +1,21 @@
-/* ==========================================================
- CATEGORY BAR
- ========================================================== */
+/**
+ * @file catbar.css
+ * @brief This stylesheet defines the styling for the category navigation bar and the attribute filter bar.
+ * It includes styles for navigation items, active/hover states, responsiveness, and filter form components.
+ * @author Geizkragen Project
+ */
+/**
+ * ==========================================================
+ * CATEGORY BAR
+ * ==========================================================
+ */
+
+/**
+ * @class .home-nav
+ * @brief Main container for the home navigation bar.
+ * Provides the background color, padding, and flexbox layout.
+ */
.home-nav {
width: 100%;
display: flex;
@@ -10,7 +24,11 @@
padding: 1px 0;
}
-/* Inner zentriert */
+/**
+ * @class .home-nav__inner
+ * @brief Inner wrapper for the home navigation bar.
+ * Ensures the content expands and is centered correctly using flexbox properties.
+ */
.home-nav__inner {
flex: 1;
width: 100%;
@@ -19,7 +37,11 @@
align-items: center;
}
-/* List */
+/**
+ * @class .home-nav__list
+ * @brief Represents the unordered list containing the navigation links.
+ * Removes default list styling and sets up a horizontal flexbox container.
+ */
.home-nav__list {
width: 100%;
display: flex;
@@ -28,16 +50,27 @@
padding: 0;
}
-/* LI nur als Container */
+/**
+ * @selector .home-nav__list li
+ * @brief Defines the list items inside the navigation list.
+ * Acts as a relative container for flex items.
+ */
.home-nav__list li {
flex: 1;
position: relative;
}
-/* =============================== */
-/* HOME CATEGORY LINKS */
-/* =============================== */
+/**
+ * ===============================
+ * HOME CATEGORY LINKS
+ * ===============================
+ */
+/**
+ * @selector .home-nav__list li a
+ * @brief Styles the anchor tags inside the list items.
+ * Sets up dimensions, padding, colors, font styling, and transitions for hover effects.
+ */
.home-nav__list li a {
width: 100%;
padding: 8px 0;
@@ -59,32 +92,53 @@
transition: background-color 0.2s ease;
}
-/* Hover */
+/**
+ * @selector .home-nav__list li a:hover
+ * @brief Defines the hover state for the navigation links.
+ * Applies a subtle white overlay using rgba.
+ */
.home-nav__list li a:hover {
background-color: rgba(255,255,255,0.18);
}
-/* Active (Mausklick) */
+/**
+ * @selector .home-nav__list li a:active
+ * @brief Defines the active (mouse click down) state for the navigation links.
+ * Increases the opacity of the white overlay for visual feedback.
+ */
.home-nav__list li a:active {
background-color: rgba(255,255,255,0.28);
}
-/* Fokus (Tastatur) */
+/**
+ * @selector .home-nav__list li a:focus-visible
+ * @brief Defines the focus state, mainly for keyboard navigation accessibility.
+ * Removes the default outline and adds a bespoke white box-shadow inset.
+ */
.home-nav__list li a:focus-visible {
outline: none;
box-shadow: inset 0 0 0 2px rgba(255,255,255,0.5);
}
-/* Aktive Kategorie (per PHP) */
+/**
+ * @selector .home-nav__list li a.active
+ * @brief Styles the currently active category link, typically set server-side via PHP or by JS.
+ */
.home-nav__list li a.active {
background-color: rgba(255,255,255,0.25);
}
-/* =============================== */
-/* TRENNSTRICHE ZWISCHEN KATEGORIEN */
-/* =============================== */
+/**
+ * ===============================
+ * TRENNSTRICHE ZWISCHEN KATEGORIEN
+ * ===============================
+ */
-/* Trennstrich rechts – außer beim letzten */
+/**
+ * @selector .home-nav__list li:not(:last-child)::after
+ * @brief Creates vertical separator lines between category items,
+ * except after the last item in the list.
+ */
.home-nav__list li:not(:last-child)::after {
content: "";
position: absolute;
@@ -96,7 +150,11 @@
background-color: rgba(255,255,255,0.3);
}
-/* ─── Responsive: horizontal scroll on mobile ─── */
+/**
+ * @media (max-width: 768px)
+ * @brief Responsive styles for mobile and small tablet screens.
+ * Enables horizontal scrolling for the category list with smooth scroll-snapping.
+ */
@media (max-width: 768px) {
.home-nav__inner {
justify-content: flex-start;
@@ -140,16 +198,28 @@
}
}
-/* ==========================================================
- ATTRIBUTE FILTER BAR
- ========================================================== */
+/**
+ * ==========================================================
+ * ATTRIBUTE FILTER BAR
+ * ==========================================================
+ */
+/**
+ * @class .attrbar
+ * @brief Main container for the attribute filtering section.
+ * Matches the background color of the category bar and provides bottom padding.
+ */
.attrbar {
width: 100%;
background-color: rgb(45, 59, 80); /* Same as catbar background */
padding: 0 0 16px 0;
}
+/**
+ * @class .attrbar__inner
+ * @brief Inner container for the filter bar.
+ * Includes top padding and a subtle top border to separate it from the category links above.
+ */
.attrbar__inner {
display: flex;
justify-content: center;
@@ -159,6 +229,11 @@
width: 100%;
}
+/**
+ * @class .attr-filter-form
+ * @brief Wrapper for the filter form components.
+ * Uses flexbox wrap to display filter items fluidly, aligning them appropriately across widths.
+ */
.attr-filter-form {
display: flex;
flex-wrap: wrap;
@@ -171,6 +246,11 @@
padding: 0 20px;
}
+/**
+ * @class .attr-filter-item
+ * @brief Container for a single filter component (e.g. property label + dropdown).
+ * Lays out child elements in a flex column.
+ */
.attr-filter-item {
display: flex;
flex-direction: column; /* Stack label and select */
@@ -183,6 +263,11 @@
max-width: 250px;
}
+/**
+ * @selector .attr-filter-item label
+ * @brief Styles the label text above each dropdown within the filter items.
+ * Uses uppercase text, smaller font size, and light spacing.
+ */
.attr-filter-item label {
font-weight: 600;
text-transform: uppercase;
@@ -191,6 +276,11 @@
color: rgba(255, 255, 255, 0.7);
}
+/**
+ * @selector .attr-filter-item select
+ * @brief Styles the select (dropdown) inputs in the filter bar.
+ * Removes default webkit/moz appearance and replaces it with custom backgrounds, borders, and a custom SVG arrow.
+ */
.attr-filter-item select {
width: 100%;
padding: 10px 14px;
@@ -209,28 +299,51 @@
background-size: 16px;
}
+/**
+ * @selector .attr-filter-item select:hover
+ * @brief Hover state for the select elements, adjusting background and border color.
+ */
.attr-filter-item select:hover {
background-color: rgba(25, 30, 40, 0.8);
border-color: rgba(255, 255, 255, 0.3);
}
+/**
+ * @selector .attr-filter-item select:focus
+ * @brief Focus state for the select elements, applying primary color highlights.
+ */
.attr-filter-item select:focus {
outline: none;
border-color: var(--color-primary, #274a97);
box-shadow: 0 0 0 3px var(--color-primary-soft, rgba(39, 74, 151, 0.15));
}
+/**
+ * @selector .attr-filter-item select option
+ * @brief Styles the option elements inside the dropdown.
+ * Sets background and text colors to match the dark theme.
+ */
.attr-filter-item select option {
background-color: var(--bg-surface, #2d3b50);
color: var(--text-primary, #ffffff);
}
+/**
+ * @class .attr-filter-action
+ * @brief Container for form action elements, like the reset button.
+ * Aligns to the bottom to match the input fields structurally.
+ */
.attr-filter-action {
display: flex;
align-items: flex-end;
padding-bottom: 2px; /* optical alignment with inputs */
}
+/**
+ * @class .attr-filter-reset
+ * @brief Styles for the filter reset button.
+ * Provides an inline-flex layout with a distinct danger (red) color scheme and hover effects.
+ */
.attr-filter-reset {
display: inline-flex;
align-items: center;
@@ -248,12 +361,22 @@
height: 40px; /* match select height */
}
+/**
+ * @selector .attr-filter-reset:hover
+ * @brief Hover state for the reset button.
+ * Creates a slight lift effect using transform and adds a shadow.
+ */
.attr-filter-reset:hover {
background-color: #b91c1c; /* slightly darker danger */
transform: translateY(-1px);
box-shadow: 0 4px 12px var(--color-danger-soft, rgba(217, 45, 32, 0.2));
}
+/**
+ * @media (max-width: 768px)
+ * @brief Adjustments to the attribute filter layout for mobile and tablet devices.
+ * Forces items to take up near-half width or full width, and correctly spaces action buttons.
+ */
@media (max-width: 768px) {
.attr-filter-item {
flex: 1 1 45%;
diff --git a/assets/css/compare.css b/assets/css/compare.css
index 19a5bf7..d388467 100644
--- a/assets/css/compare.css
+++ b/assets/css/compare.css
@@ -1,8 +1,28 @@
+/**
+ * @file compare.css
+ * @brief Stylesheet für die Produktvergleichsseite.
+ *
+ * Diese Datei enthält alle spezifischen Styles für die Darstellung
+ * von zu vergleichenden Produkten in einer übersichtlichen Tabelle.
+ * Sie definiert Farben, Ränder, Schatten und Hover-Effekte für
+ * ein ansprechendes Dark-Mode-Design.
+ */
+
+/**
+ * @brief Hauptcontainer für die Vergleichsseite.
+ *
+ * Setzt den oberen Abstand und das allgemeine Padding für die Seite.
+ */
.compare-page {
margin-top: 2rem;
padding: 1rem;
}
+/**
+ * @brief Stil für den Seitentitel der Vergleichsseite.
+ *
+ * Definiert Schriftgröße, Ausrichtung, Farbe und Schriftstärke.
+ */
.compare-title {
font-size: 2.5rem;
margin-bottom: 2rem;
@@ -11,6 +31,12 @@
font-weight: 700;
}
+/**
+ * @brief Styling für dem Fallstrick "Leerer Vergleich".
+ *
+ * Wird angezeigt, wenn keine Produkte zum Vergleich ausgewählt wurden.
+ * Enthält ein hervorgehobenes Box-Design mit Hintergrund und Schatten.
+ */
.compare-empty {
text-align: center;
color: #cbd5e1;
@@ -21,6 +47,11 @@
box-shadow: 0 10px 25px -5px rgba(0, 0, 0, 0.4);
}
+/**
+ * @brief Titel-Stil für Produktkategorien beim Vergleich.
+ *
+ * Unterteilt die Tabelle grafisch nach Kategorien mit entsprechenden Rändern.
+ */
.compare-category-title {
margin-top: 3rem;
margin-bottom: 1.5rem;
@@ -30,6 +61,12 @@
padding-bottom: 0.8rem;
}
+/**
+ * @brief Wrapper für die scrollbare Vergleichstabelle.
+ *
+ * Ermöglicht horizontales Scrollen bei vielen Produkten auf kleinen Bildschirmen
+ * und setzt das äußere Erscheinungsbild (Hintergrund, Rahmen, Schatten).
+ */
.compare-table-wrapper {
overflow-x: auto;
background: #1e293b;
@@ -40,6 +77,11 @@
border: 1px solid rgba(255, 255, 255, 0.05);
}
+/**
+ * @brief Grund-Styling der Vergleichstabelle.
+ *
+ * Definiert Mindestbreite und reguliert die Zellabstände (Border-Collapse).
+ */
.compare-table {
width: 100%;
border-collapse: separate;
@@ -47,6 +89,11 @@
min-width: 800px;
}
+/**
+ * @brief Stile für Tabellenköpfe und Datenzellen.
+ *
+ * Fügt Ränder, Abstände und zentrierte Ausrichtung sowie die Textfarbe hinzu.
+ */
.compare-table th,
.compare-table td {
padding: 1.5rem 1rem;
@@ -56,10 +103,18 @@
vertical-align: middle;
}
+/**
+ * @brief Entfernt den unteren Rand für die letzte Tabellenzeile.
+ */
.compare-table tbody tr:last-child td {
border-bottom: none;
}
+/**
+ * @brief Styling für die erste Spalte (Eigenschaftsnamen).
+ *
+ * Fixiert die Spalte links (Sticky) für leichteres horizontales Scrollen.
+ */
.compare-table th:first-child,
.compare-table td:first-child {
text-align: left;
@@ -73,6 +128,11 @@
box-shadow: 4px 0 6px -2px rgba(0, 0, 0, 0.2);
}
+/**
+ * @brief Styling für den Tabellenkopf.
+ *
+ * Hebt die Kopfzeile farblich vom Rest der Tabelle ab.
+ */
.compare-table th {
background: #0f172a;
font-weight: bold;
@@ -81,20 +141,41 @@
padding-bottom: 2rem;
}
+/**
+ * @brief Spezifisches Styling für die obere linke Zelle im Tabellenkopf.
+ *
+ * Erhöht den z-Index, damit sie beim Scrollen immer oben liegt.
+ */
.compare-table th:first-child {
background: #0f172a;
z-index: 3;
font-size: 1.2rem;
}
+/**
+ * @brief Hover-Effekt für Tabellenzeilen im Körper (Datenzellen).
+ *
+ * Hebt die gesamte Zeile hervor, wenn der Benutzer darüber fährt.
+ */
.compare-table tbody tr:hover td {
background: #2a3a52;
}
+/**
+ * @brief Hover-Effekt für die erste Zelle in der hovernden Tabellenzeile.
+ *
+ * Sorgt für eine konsistente Hervorhebung, auch bei der fixierten Spalte.
+ */
.compare-table tbody tr:hover td:first-child {
background: #2a3a52;
}
+/**
+ * @brief Styling für das Produktbild im Tabellenkopf.
+ *
+ * Passt die Bildgröße an, setzt einen hellen Verlaufshintergrund und
+ * implementiert einen abgerundeten Rahmen sowie eine Größenübergangsanimation.
+ */
.compare-product-img {
height: 140px;
object-fit: contain;
@@ -106,10 +187,20 @@
transition: transform 0.3s ease;
}
+/**
+ * @brief Hover-Animation für das Produktbild.
+ *
+ * Vergrößert das Bild leicht, wenn der Nutzer mit der Maus darüber fährt.
+ */
.compare-product-img:hover {
transform: scale(1.05);
}
+/**
+ * @brief Styling für den Produktnamen.
+ *
+ * Definiert Farbe, Schriftgröße und Zeilenhöhe für den anklickbaren Produktnamen.
+ */
.compare-product-name {
font-size: 1.15rem;
color: #60a5fa;
@@ -121,10 +212,20 @@
line-height: 1.4;
}
+/**
+ * @brief Hover-Effekt für den Produktnamen.
+ *
+ * Ändert die Textfarbe für visuelles Feedback beim Drüberfahren.
+ */
.compare-product-name:hover {
color: #93c5fd;
}
+/**
+ * @brief Styling für den Button zum Entfernen eines Produkts aus dem Vergleich.
+ *
+ * Roter Button (Pill-Shape) mit Schatten und Übergangseffekten.
+ */
.compare-remove-btn {
background: #ef4444;
color: white;
@@ -138,16 +239,32 @@
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2);
}
+/**
+ * @brief Hover-Effekt für den Entfernen-Button.
+ *
+ * Dunkelt das Rot ab, bewegt den Button leicht nach oben und vergrößert den Schatten.
+ */
.compare-remove-btn:hover {
background: #dc2626;
transform: translateY(-1px);
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.3);
}
+/**
+ * @brief Zebra-Streifen-Muster für gerade Tabellenzeilen.
+ *
+ * Wirkt sich nicht auf die fixierte erste Spalte aus, um die Lesbarkeit
+ * dunkel schattierter Hintergründe aufrechtzuerhalten.
+ */
.compare-table tbody tr:nth-child(even) td:not(:first-child) {
background: rgba(15, 23, 42, 0.2);
}
+/**
+ * @brief Styling für längere Beschreibungstexte im Vergleich.
+ *
+ * Begrenzt die Breite, richtet den Text linksbündig aus und optimiert die Zeilenhöhe.
+ */
.desc-text {
text-align: left;
display: inline-block;
@@ -155,4 +272,3 @@
line-height: 1.6;
font-size: 0.95rem;
}
-
diff --git a/assets/css/compcard.css b/assets/css/compcard.css
index e36f87d..4a623aa 100644
--- a/assets/css/compcard.css
+++ b/assets/css/compcard.css
@@ -1,8 +1,22 @@
-/* ==========================================================
- PRODUCT CARDS & SCROLL – Animated Glass Cards
- ========================================================== */
+/**
+ * @file compcard.css
+ * @brief Styles for Product Cards and Wishlist Cards.
+ * @details This file contains all CSS rules for displaying animated glass-style
+ * product cards, grid layouts, horizontal scroll sections, and the wishlist vertical layout.
+ * It also includes responsive design media queries for tablets and mobile devices.
+ */
-/* ─── Product Grid ─── */
+/**
+ * ==========================================================
+ * @section PRODUCT CARDS & SCROLL – Animated Glass Cards
+ * ==========================================================
+ */
+
+/**
+ * @brief Product Grid Container
+ * @details Creates a CSS grid with autofit columns of 260px each.
+ * Defines the spacing (gap) and padding around the grid.
+ */
.product-grid {
display: grid;
grid-template-columns: repeat(auto-fit, 260px);
@@ -11,7 +25,12 @@
justify-content: start;
}
-/* ─── Product Card ─── */
+/**
+ * @brief Base Product Card Styles
+ * @details Sets up the generic layout, colors, shadow, and transitions for a product card.
+ * The card is designed with a dark background (#2d3b50) and an outline/border,
+ * utilizing a flexbox column layout. A fade-in animation (fadeInUp) is applied.
+ */
.product-card {
background: #2d3b50;
border: 1px solid var(--border-subtle);
@@ -29,7 +48,11 @@
animation: fadeInUp 0.5s ease both;
}
-/* Gradient border glow on hover */
+/**
+ * @brief Gradient border glow effect on hover
+ * @details Uses a pseudo-element (::before) to create a glowing border using a linear gradient.
+ * It features a mask to only reveal the border area. Hidden by default (opacity: 0) and shown on hover.
+ */
.product-card::before {
content: "";
position: absolute;
@@ -51,7 +74,11 @@
pointer-events: none;
}
-/* Inner glow effect on hover */
+/**
+ * @brief Inner glow effect on hover
+ * @details Uses a pseudo-element (::after) to add a subtle gradient at the top of the card.
+ * It is hidden by default and becomes visible when the card is hovered over.
+ */
.product-card::after {
content: "";
position: absolute;
@@ -85,6 +112,11 @@
outline-offset: 3px;
}
+/**
+ * @brief Staggered entrance animation delays
+ * @details Applies incremental animation delays to cards within a horizontal scroll container
+ * to create a cascading fade-in effect.
+ */
/* Stagger cards in scroll */
.product-scroll .product-card:nth-child(1) { animation-delay: 0.05s; }
.product-scroll .product-card:nth-child(2) { animation-delay: 0.1s; }
@@ -95,7 +127,11 @@
.product-scroll .product-card:nth-child(7) { animation-delay: 0.35s; }
.product-scroll .product-card:nth-child(8) { animation-delay: 0.4s; }
-/* ─── Card Image ─── */
+/**
+ * @brief Card Image Styling
+ * @details Defines the size, padding, and positioning of the product image inside the card.
+ * Uses a white background and adds a bottom border.
+ */
.product-card img {
width: 100%;
height: 175px;
@@ -115,7 +151,11 @@
transform: scale(1.05);
}
-/* ─── Card Content ─── */
+/**
+ * @brief Card Content Container
+ * @details The wrapper for textual content (title, description, price) inside the card.
+ * Uses flexbox to space and align the text vertically.
+ */
.product-card__content {
padding: 1rem 1.25rem 1.15rem;
flex: 1 1 auto;
@@ -127,6 +167,11 @@
z-index: 2;
}
+/**
+ * @brief Card Title Styling
+ * @details Styles the heading, limits it to 2 lines using webkit-line-clamp,
+ * and sets up a transition for color changes on hover.
+ */
.product-card__content h3 {
font-size: 0.92rem;
font-weight: 600;
@@ -140,10 +185,19 @@
transition: color var(--transition-normal);
}
+/**
+ * @brief Card Title Hover State
+ * @details Changes the color of the heading when the parent card is hovered.
+ */
.product-card:hover .product-card__content h3 {
color: var(--text-invert);
}
+/**
+ * @brief Card Description Styling
+ * @details Styles the
paragraph tag. Limits text to 4 lines with ellipsis
+ * and handles word-breaking to prevent overflow.
+ */
.product-card__content p {
font-size: 0.8rem;
color: #ffffff;
@@ -156,6 +210,10 @@
line-height: 1.5;
}
+/**
+ * @brief Card Price Styling
+ * @details Formats the price element, making it bold, blue, and pushed to the bottom of the card.
+ */
.product-card__content .price {
font-size: 1.05rem;
font-weight: 700;
@@ -164,12 +222,19 @@
padding-top: 0.5rem;
}
-/* ─── Product Sections ─── */
+/**
+ * @brief Product Section Wrapper
+ * @details Adds padding and an entrance animation for sections containing product lists/scrolls.
+ */
.product-section {
padding: 2rem 0 0.5rem;
animation: fadeInUp 0.5s ease both;
}
+/**
+ * @brief Product Section Title
+ * @details Styles the
heading of the section, relative positioning is used for decorators.
+ */
.product-section h2 {
margin-left: 2rem;
margin-bottom: 1.25rem;
@@ -181,6 +246,11 @@
display: inline-block;
}
+/**
+ * @brief Animated Accent Line for Section Title
+ * @details Creates a vertical gradient line next to the tag that GROWS into place
+ * using a CSS keyframe animation.
+ */
/* Animated accent line */
.product-section h2::before {
content: "";
@@ -195,10 +265,18 @@
animation: lineGrow 0.6s ease 0.3s forwards;
}
+/**
+ * @brief Line Grow Keyframes
+ * @details Animation definition for the accent line, expanding its height to 80%.
+ */
@keyframes lineGrow {
to { height: 80%; }
}
+/**
+ * @brief Subtle Glow for Section Title
+ * @details Adds a blurred, colored circle behind the for a decorative lighting effect.
+ */
/* Subtle glow behind section title */
.product-section h2::after {
content: "";
@@ -214,7 +292,11 @@
opacity: 0.3;
}
-/* ─── Horizontal Scroll ─── */
+/**
+ * @brief Horizontal Scroll Container
+ * @details A flexible container that allows horizontal scrolling for product cards.
+ * Implements CSS scroll snapping and scroll masks to fade edges.
+ */
.product-scroll {
display: flex;
gap: 1.25rem;
@@ -229,32 +311,59 @@
-webkit-mask-image: linear-gradient(90deg, transparent, black 2rem, black calc(100% - 2rem), transparent);
}
+/**
+ * @brief Product Scroll Item
+ * @details Fixes the width of cards within the scroll container and enables scroll snapping alignments.
+ */
.product-scroll .product-card {
flex: 0 0 270px;
scroll-snap-align: start;
}
+/**
+ * @brief Hide Scrollbar
+ * @details Removes the default scrollbar for Webkit browsers for a cleaner look.
+ */
.product-scroll::-webkit-scrollbar {
display: none;
}
-/* ─── Responsive ─── */
+/**
+ * @section RESPONSIVE DESIGN FOR PRODUCT CARDS
+ */
+
+/**
+ * @brief Tablet Breakpoint (max-width: 768px)
+ * @details Adjusts grid columns, padding, font sizes, and card dimensions for tablet displays.
+ */
@media (max-width: 768px) {
+ /**
+ * @brief Responsive Product Grid
+ */
.product-grid {
grid-template-columns: repeat(auto-fit, 160px);
gap: 0.8rem;
padding: 1rem;
}
+ /**
+ * @brief Responsive Product Section Padding
+ */
.product-section {
padding: 1.25rem 0 0.25rem;
}
+ /**
+ * @brief Responsive Section Title
+ */
.product-section h2 {
margin-left: 1rem;
font-size: 1.1rem;
}
+ /**
+ * @brief Responsive Horizontal Scroll
+ */
.product-scroll {
padding: 0.5rem 1rem 1.5rem;
gap: 0.8rem;
@@ -262,69 +371,119 @@
-webkit-mask-image: linear-gradient(90deg, transparent, black 1rem, black calc(100% - 1rem), transparent);
}
+ /**
+ * @brief Responsive Product Scroll Item Size
+ */
.product-scroll .product-card {
flex: 0 0 200px;
min-height: 230px;
}
+ /**
+ * @brief Responsive Image Dimensions
+ */
.product-card img {
height: 130px;
padding: 10px;
}
+ /**
+ * @brief Responsive Content Padding
+ */
.product-card__content {
padding: 0.75rem 0.9rem 0.85rem;
gap: 0.25rem;
}
+ /**
+ * @brief Responsive Title Size
+ */
.product-card__content h3 {
font-size: 0.82rem;
}
+ /**
+ * @brief Responsive Description Size
+ * @details Further limits description lines to 2 on tablets.
+ */
.product-card__content p {
font-size: 0.72rem;
-webkit-line-clamp: 2;
}
+ /**
+ * @brief Responsive Price Size
+ */
.product-card__content .price {
font-size: 0.92rem;
}
}
+/**
+ * @brief Mobile Breakpoint (max-width: 480px)
+ * @details Optimizes layouts for very small screens, converting grids and making cards even smaller.
+ */
@media (max-width: 480px) {
+ /**
+ * @brief Mobile Strict 2-Column Grid
+ */
.product-grid {
grid-template-columns: repeat(2, 1fr);
gap: 0.6rem;
padding: 0.75rem;
}
+ /**
+ * @brief Mobile Horizontal Scroll Item
+ */
.product-scroll .product-card {
flex: 0 0 170px;
min-height: 210px;
}
+ /**
+ * @brief Mobile Card Image
+ */
.product-card img {
height: 110px;
padding: 8px;
}
+ /**
+ * @brief Mobile Content Padding
+ */
.product-card__content {
padding: 0.6rem 0.7rem 0.7rem;
}
+ /**
+ * @brief Mobile Title
+ * @details Limits the title to a single line.
+ */
.product-card__content h3 {
font-size: 0.78rem;
-webkit-line-clamp: 1;
}
+ /**
+ * @brief Mobile Description
+ * @details Completely hides the description text on small mobile screens to save space.
+ */
.product-card__content p {
display: none;
}
}
-/* ==========================================================
- WISHLIST – Vertical List Layout
- ========================================================== */
+/**
+ * ==========================================================
+ * @section WISHLIST – Vertical List Layout
+ * ==========================================================
+ */
+
+/**
+ * @brief Wishlist Layout Container
+ * @details Arranges wishlist items in a vertical, single-column flex column.
+ */
.wishlist-grid {
display: flex;
flex-direction: column;
@@ -334,6 +493,11 @@
margin: 0 auto;
}
+/**
+ * @brief Wishlist Item Card
+ * @details Modifies the default product card to a horizontal row layout specifically for the wishlist.
+ * Ensures an exact height and equal stretching.
+ */
.wishlist-card.product-card {
display: flex;
flex-direction: row;
@@ -351,6 +515,11 @@
overflow: hidden;
}
+/**
+ * @brief Wishlist Image Wrapper
+ * @details Styles the image specifically for the horizontal wishlist layout. Adds a border right
+ * instead of border bottom, ensuring it takes up full height with contain object-fit.
+ */
.wishlist-card.product-card img {
width: 160px;
flex: 0 0 160px;
@@ -376,6 +545,10 @@
transition: transform var(--transition-smooth);
}
+/**
+ * @brief Wishlist Content Wrapper
+ * @details Vertically centers text in the available remaining width alongside the image.
+ */
.wishlist-card.product-card .product-card__content {
display: flex;
flex-direction: column;
@@ -384,15 +557,27 @@
flex: 1;
}
+/**
+ * @brief Wishlist Hover Animation
+ * @details Moves the horizontal card slightly to the right entirely with a small scale increment.
+ */
.wishlist-card.product-card:hover {
transform: translateX(6px) scale(1.01);
}
+/**
+ * @brief Wishlist Image Hover Zoom
+ * @details Zooms into the product image when hovering over a wishlist card.
+ */
/* Bild-Zoom beim Hovern */
.wishlist-card.product-card:hover img {
transform: scale(1.08);
}
+/**
+ * @brief Staggered entrance animation delays for Wishlist
+ * @details Sequential entry animations for wishlist items to replicate the staggered scroll effect.
+ */
/* Stagger animation for wishlist items */
.wishlist-grid .wishlist-card:nth-child(1) { animation-delay: 0.05s; }
.wishlist-grid .wishlist-card:nth-child(2) { animation-delay: 0.1s; }
@@ -403,6 +588,14 @@
.wishlist-grid .wishlist-card:nth-child(7) { animation-delay: 0.35s; }
.wishlist-grid .wishlist-card:nth-child(8) { animation-delay: 0.4s; }
+/**
+ * @section RESPONSIVE DESIGN FOR WISHLIST
+ */
+
+/**
+ * @brief Tablet Breakpoint for Wishlist (max-width: 768px)
+ * @details Shrinks the total height, image size, and adjusts padding/font-size accordingly.
+ */
@media (max-width: 768px) {
.wishlist-grid {
padding: 0.5rem 1rem 1.5rem;
@@ -433,6 +626,10 @@
}
}
+/**
+ * @brief Mobile Breakpoint for Wishlist (max-width: 480px)
+ * @details Minimizes the card height and hides descriptions to maximize screen real estate on mobile devices.
+ */
@media (max-width: 480px) {
.wishlist-card.product-card {
height: 90px;
diff --git a/assets/css/login.css b/assets/css/login.css
index 35b306e..192f29c 100644
--- a/assets/css/login.css
+++ b/assets/css/login.css
@@ -1,8 +1,20 @@
+/**
+ * @file login.css
+ * @brief Stylesheet für die Authentifizierungsseiten (Login, Registrierung, Account).
+ * @details Dieses Stylesheet definiert das Layout, die Farben, Animationen und das responsive Verhalten
+ * für alle authentifizierungsbezogenen Ansichten. Es setzt auf ein "Animated Glassmorphism Design"
+ * und Grid-Layouts für strukturierte und ansprechende Benutzeroberflächen.
+ */
+
/* ==========================================================
AUTH PAGES – Login, Register, Account
Animated Glassmorphism Design
========================================================== */
+/**
+ * @section AuthWrapper Auth-Wrapper
+ * @brief Das Hauptelement, das die Authentifizierungsformulare umschließt.
+ */
/* ─── Auth Wrapper ─── */
.auth {
display: grid;
@@ -13,6 +25,10 @@
z-index: 1;
}
+/**
+ * @brief Dekorative animierte Kugel im Hintergrund.
+ * @details Nutzt einen radialen Farbverlauf und eine unendliche Float-Animation, um Tiefe zu erzeugen.
+ */
/* Decorative ambient orb */
.auth::before {
content: "";
@@ -29,6 +45,10 @@
animation: float 12s ease-in-out infinite;
}
+/**
+ * @brief Standard-Raster für das Auth-Formular.
+ * @details Zentriert die Elemente horizontal und passt sich an die Bildschirmbreite bis zu 480px an.
+ */
.auth__grid {
display: grid;
grid-template-columns: 1fr;
@@ -39,6 +59,10 @@
align-items: center;
}
+/**
+ * @section SideLayout Side Layout (Account page)
+ * @brief Layout für breitere Ansichten (z. B. auf der Account-Seite).
+ */
/* ─── Side Layout (Account page) ─── */
.auth__grid.auth__card__side {
grid-template-columns: max-content 1fr;
@@ -51,6 +75,9 @@
width: 100%;
}
+/**
+ * @brief Layout für die seitliche Profilbild-Anzeige.
+ */
.auth__grid.auth__card__side .auth__card.auth__card__side__picture {
display: inline-grid;
width: max-content;
@@ -68,6 +95,9 @@
margin-bottom: 0;
}
+/**
+ * @brief Styling und Hover-Effekte für das Profilbild.
+ */
.auth__grid.auth__card__side .auth__card.auth__card__side__picture img {
display: block;
max-width: none;
@@ -86,6 +116,11 @@
justify-items: center;
}
+/**
+ * @section CardGlassmorphism Card – Glassmorphism
+ * @brief Stil für die Container-Karten der Formulare.
+ * @details Nutzt Dunkelmodus-Farben mit feinen Box-Shadows und Hover-Effekten zur Hervorhebung.
+ */
/* ─── Card – Glassmorphism ─── */
.auth__card,
.auth__sideCard {
@@ -103,6 +138,10 @@
box-shadow: var(--shadow-md), var(--shadow-glow-blue);
}
+/**
+ * @brief Gestaffelte Animation für die Formular-Karten.
+ * @details Sorgt dafür, dass Elemente nacheinander eingeflogen werden.
+ */
/* Stagger cards */
.auth__grid .auth__card:nth-child(1) { animation-delay: 0s; }
.auth__grid .auth__card:nth-child(2) { animation-delay: 0.08s; }
@@ -113,6 +152,10 @@
padding: 22px;
}
+/**
+ * @section CardHeader Card Header
+ * @brief Kopfzeile der Formular-Karten (Logo und Titel).
+ */
/* ─── Card Header ─── */
.auth__header {
display: grid;
@@ -130,6 +173,9 @@
grid-row: span 2;
}
+/**
+ * @brief Styling des Haupttitels mit Text-Gradient-Effekt.
+ */
.auth__title {
margin: 0;
font-size: 1.45rem;
@@ -142,6 +188,9 @@
background-clip: text;
}
+/**
+ * @brief Untertitel im Header (z.B. Eingabeaufforderungen).
+ */
.auth__subtitle {
margin: 0;
color: var(--text-secondary);
@@ -149,6 +198,10 @@
line-height: 1.4;
}
+/**
+ * @section Alerts Animated Alerts
+ * @brief Verschiedene Benachrichtigungsboxen (Erfolg, Fehler, Warnungen).
+ */
/* ─── Alerts – Animated ─── */
.auth__alert__error {
margin: 12px 0 14px;
@@ -168,6 +221,9 @@
box-shadow: 0 8px 18px rgba(72, 142, 62, 0.18);
}
+/**
+ * @brief Standard-Benachrichtigungs-Container mit Gradient-Hintergrund.
+ */
.auth__alert {
margin: 0.75rem 0 1rem;
color: var(--text-invert);
@@ -187,6 +243,9 @@
margin: 0.25rem 0;
}
+/**
+ * @brief Spezifisches Styling für Fehlermeldungen innerhalb von Formularen.
+ */
.auth__error {
margin: 0.75rem 0;
color: var(--color-danger);
@@ -198,11 +257,18 @@
animation: scaleIn 0.3s ease;
}
+/**
+ * @section Formular Formular-Elemente
+ * @brief Standard-Layout für die inneren Formularelemente.
+ */
/* ─── Form ─── */
.auth__form {
margin-top: 0.5rem;
}
+/**
+ * @brief Abstände für einzelne Eingabefelder.
+ */
.auth__field {
margin-top: 1rem;
}
@@ -211,6 +277,9 @@
margin-top: 1px;
}
+/**
+ * @brief Styling der Labels über den Eingabefeldern.
+ */
.auth__field label {
display: block;
margin: 0 0 0.4rem;
@@ -224,6 +293,9 @@
color: var(--color-primary);
}
+/**
+ * @brief Grundlegendes Styling für Text-, Passwort- und Datei-Eingabefelder.
+ */
.auth__field input[type="text"],
.auth__field input[type="password"],
.auth__field input[type="email"],
@@ -241,6 +313,9 @@
transition: all var(--transition-smooth);
}
+/**
+ * @brief Fokus-Effekte (Ränder und Glow) für Eingabefelder.
+ */
.auth__field input[type="text"]:focus,
.auth__field input[type="password"]:focus,
.auth__field input[type="email"]:focus,
@@ -255,6 +330,9 @@
color: var(--text-muted);
}
+/**
+ * @brief Spezifisches Styling für den Datei-Auswahl-Button.
+ */
.auth__field input[type="file"]::file-selector-button {
margin-right: 10px;
padding: 10px 12px;
@@ -276,11 +354,18 @@
transform: translateY(0px);
}
+/**
+ * @section Actions Buttons & Aktionen
+ * @brief Hauptbuttons für die Formularübermittlung.
+ */
/* ─── Actions ─── */
.auth__actions {
margin-top: 14px;
}
+/**
+ * @brief Primärer Submit-Button mit Hover- und Active-Zuständen.
+ */
.auth__submit {
width: 100%;
padding: 12px 14px;
@@ -303,6 +388,9 @@
transform: translateY(0px);
}
+/**
+ * @brief Deaktivierter Zustand für Submit-Buttons.
+ */
.auth__submit:disabled {
opacity: 0.5;
cursor: not-allowed;
@@ -314,11 +402,18 @@
display: none;
}
+/**
+ * @brief Rote Variante des Submit-Buttons für gefährliche Aktionen (Löschen).
+ */
/* Danger button variant */
.auth__submit--danger {
background: #d92d20;
}
+/**
+ * @section Links Verlinkungen
+ * @brief Zusätzliche Textlinks unter den Formularen (z.B. Passwort vergessen, Registrieren).
+ */
/* ─── Links ─── */
.auth__links {
margin-top: 14px;
@@ -341,6 +436,10 @@
text-decoration: underline;
}
+/**
+ * @section SideCard Seiten-Box
+ * @brief Gestaltung der Informationsbox auf der Seite von Registrierungsformularen etc.
+ */
/* ─── Side Card ─── */
.auth__sideCard {
padding: 18px;
@@ -364,6 +463,10 @@
color: #ffffff;
}
+/**
+ * @section TipBox Tipp-Box
+ * @brief Container für hilfreiche Hinweise (oft neben Formularen platziert).
+ */
/* ─── Tip Box ─── */
.auth__tip {
margin-top: 14px;
@@ -374,10 +477,17 @@
color: #ffffff;
}
+/**
+ * @section AccountPage ACCOUNT PAGE
+ * @brief Layout und spezifische Stile für die Account-Ansicht (Profil & Einstellungen).
+ */
/* ==========================================================
ACCOUNT PAGE – Profile + Settings Layout
========================================================== */
+/**
+ * @brief Modulares Grid-Layout für die Profilübersicht.
+ */
/* ─── Main Grid ─── */
.account {
display: grid;
@@ -393,6 +503,10 @@
grid-column: 1 / -1;
}
+/**
+ * @section AccountProfile Profil-Sidebar
+ * @brief Der seitliche Bereich in der Account-Seite, der Profilbild und Name anzeigt.
+ */
/* ─── Profile Sidebar ─── */
.account__profile {
position: sticky;
@@ -401,6 +515,9 @@
padding: 2rem 1.5rem !important;
}
+/**
+ * @brief Äußerer Container für das Profilbild auf der Profilseite mit Hover-Effekten.
+ */
.account__avatar-wrapper {
width: 140px;
height: 140px;
@@ -425,6 +542,9 @@
display: block;
}
+/**
+ * @brief Der angezeigte Name auf dem Benutzerprofil.
+ */
.account__displayname {
margin: 0 0 1.25rem;
font-size: 1.35rem;
@@ -433,6 +553,10 @@
color: #ffffff;
}
+/**
+ * @section AccountDetails Benutzerinformationen
+ * @brief Zeilenbasiertes Layout für die Auflistung von Profil-Details (wie E-Mail, Beitrittsdatum).
+ */
/* ─── Detail Rows (User info) ─── */
.account__details {
margin: 0;
@@ -466,6 +590,10 @@
font-weight: 500;
}
+/**
+ * @section AccountSettings Account-Einstellungen
+ * @brief Der Hautpbereich für sämtliche Account-bezogene Aktionen und Einstellungen.
+ */
/* ─── Settings Column ─── */
.account__settings {
display: flex;
@@ -473,11 +601,17 @@
gap: 1.25rem;
}
+/**
+ * @brief Karten-Container in den Account-Einstellungen.
+ */
/* ─── Section Cards ─── */
.account__section {
padding: 1.5rem !important;
}
+/**
+ * @brief Bereichstitel mit optionalem Icon (Nutzt Flexlayout).
+ */
.account__section-title {
display: flex;
align-items: center;
@@ -497,6 +631,10 @@
color: var(--color-danger);
}
+/**
+ * @section QuickActions Quick Actions
+ * @brief Verknüpfungen zu bestimmten Aktionen oder externen Seiten im Profil.
+ */
/* ─── Quick Actions ─── */
.account__quick-actions {
display: flex;
@@ -520,6 +658,10 @@
opacity: 0.9;
}
+/**
+ * @section DangerSection Danger Zone
+ * @brief Rote Warnbereiche für unwiderrufliche Aktionen (z.B. Profil löschen).
+ */
/* ─── Danger Section ─── */
.account__section--danger {
border-color: rgba(217, 45, 32, 0.15);
@@ -537,6 +679,10 @@
line-height: 1.5;
}
+/**
+ * @section Responsive Responsive Design
+ * @brief Media-Queries für mobile Endgeräte und Tablet-Optimierungen.
+ */
/* ==========================================================
RESPONSIVE
========================================================== */
@@ -597,6 +743,9 @@
}
}
+/**
+ * @brief Optimierungen für Smartphones (max-width: 520px).
+ */
@media (max-width: 520px) {
.auth {
padding: 1rem 0.75rem 2rem;
@@ -650,6 +799,9 @@
}
}
+/**
+ * @brief Optimierungen für sehr schmale Bildschirme (max-width: 380px).
+ */
@media (max-width: 380px) {
.auth {
padding: 0.75rem 0.5rem 1.5rem;
diff --git a/assets/css/productAdder.css b/assets/css/productAdder.css
index 2e03e80..ab8dfb2 100644
--- a/assets/css/productAdder.css
+++ b/assets/css/productAdder.css
@@ -1,8 +1,26 @@
+/**
+ * @file productAdder.css
+ * @brief Stylesheet für die Produkt-Hinzufügen-Seite (Product Adder).
+ * @details Enthält spezielle Stile für Dropdowns (Select-Elemente) und Formulare,
+ * die für die Dateneingabe beim Hinzufügen von Produkten benötigt werden.
+ * Das grundlegende Layout basiert auf den gleichen Prinzipien wie die Auth-Bereiche,
+ * wurde jedoch für breitere Formulare (bis zu 1100px) angepasst.
+ */
+
/* ==========================================================
PRODUCT ADDER – Dropdown & Form Styles
========================================================== */
+/**
+ * @section SelectDropdown Dropdown-Auswahl
+ * @brief Stile für den Container und die Elemente eines Dropdown-Menüs.
+ */
/* ─── Select Wrapper ─── */
+/**
+ * @brief Umhüllender Container für das Select-Element.
+ * @details Zentriert das Element, beschränkt die Maximalbreite auf 520px und
+ * verwendet ein CSS-Grid für saubere Abstände (gap) zwischen Label und Feld.
+ */
.auth__select__wrap {
width: min(520px, 100%);
display: grid;
@@ -11,11 +29,20 @@
margin-top: 12px;
}
+/**
+ * @brief Label-Text für die Dropdown-Auswahl.
+ */
.auth__select__label {
font-size: 0.95rem;
color: #ffffff;
}
+/**
+ * @brief Hauptstil für das Dropdown-/Select-Feld.
+ * @details Beinhaltet Customizing des nativen Aussehens (appearance: none) und
+ * fügt ein eigenes Pfeil-Icon via Hintergrund-Gradient (background-image) hinzu.
+ * Das Feld besitzt Übergangseffekte für den Hover- und Focus-State.
+ */
.auth__select {
width: 100%;
box-sizing: border-box;
@@ -39,17 +66,31 @@
background-repeat: no-repeat;
}
+/**
+ * @brief Fokus-Status für das Dropdown-Feld.
+ * @details Erhält den gleichen leuchtenden Rand (Glow) wie die Standard-Eingabefelder.
+ */
.auth__select:focus {
border-color: rgba(37, 99, 235, 0.75);
box-shadow: 0 0 0 4px rgba(37, 99, 235, 0.28);
}
+/**
+ * @brief Stil der Auswahloptionen (options) innerhalb des Selects.
+ */
.auth__select option {
background: #1e2537;
color: #ffffff;
}
+/**
+ * @section ProductAdderLayout Seiten-Layout
+ * @brief Definition der Haupt-Grid-Strukturen auf der Product-Adder-Seite.
+ */
/* ─── Product Adder Layout ─── */
+/**
+ * @brief Äußerer Container für das Formular, der die volle Viewport-Höhe einnimmt.
+ */
.auth {
min-height: 100vh;
display: grid;
@@ -58,6 +99,11 @@
gap: 16px;
}
+/**
+ * @brief Das Raster für die Formular-Zellen/Karten.
+ * @details Erlaubt eine maximale Breite von 1100px. Bietet genügend Platz für
+ * umfangreiche Produkt-Eigenschaften und Beschreibungen.
+ */
.auth__grid {
width: min(1100px, 100%);
display: grid;
@@ -65,6 +111,10 @@
gap: 20px;
}
+/**
+ * @brief Die Kachel/Karte für einzelne Bereiche des Formulars.
+ * @details Hebt Formularabschnitte visuell mit Hintergrundfarbe, Rand und Schatteneffekt ab.
+ */
.auth__card {
background: #1f2937;
border: 1px solid #5e6075;
@@ -73,27 +123,47 @@
box-shadow: 0 8px 20px rgba(0, 0, 0, 0.2);
}
+/**
+ * @brief Kopfbereich innerhalb einer Kachel (auth__card).
+ */
.auth__header {
margin-bottom: 12px;
}
+/**
+ * @brief Titeltext der Kachel.
+ */
.auth__title {
margin: 0;
font-size: 1.1rem;
color: #ffffff;
}
+/**
+ * @section FormularElemente Formulare und Eingabefelder
+ * @brief Spezifische Layout-Vorgaben für die Formular-Bestandteile.
+ */
+/**
+ * @brief Ein einzelner Formular-Eintrag (Label + Input).
+ */
.auth__form {
display: grid;
gap: 8px;
margin-bottom: 12px;
}
+/**
+ * @brief Labels, die sich im Product-Adder-Formular befinden.
+ */
.auth__form label {
font-size: 0.95rem;
color: #ffffff;
}
+/**
+ * @brief Standard-Text-Eingabefeld.
+ * @details Verfügt über anpassbare Abstände, Randfarben und einen sanften Übergang bei Interaktionen.
+ */
.auth__input {
width: 100%;
box-sizing: border-box;
@@ -106,26 +176,47 @@
transition: border-color 140ms ease, box-shadow 140ms ease, background 140ms ease;
}
+/**
+ * @brief Zustand bei aktivem Feld (Fokus).
+ */
.auth__input:focus {
border-color: rgba(37, 99, 235, 0.75);
box-shadow: 0 0 0 4px rgba(37, 99, 235, 0.28);
}
+/**
+ * @brief Aussehen des Platzhalter-Textes.
+ */
.auth__input::placeholder {
color: #cbd5e1;
}
+/**
+ * @brief Mehrzeilige Textarea für die Beschreibung.
+ * @details Mindesthöhe definiert, nur vertikal anpassbar, verbesserte Zeilenhöhe für besseren Lesefluss.
+ */
textarea.auth__input {
min-height: 80px;
resize: vertical;
line-height: 1.5;
}
+/**
+ * @brief Verhindert unnötigen Abstand beim letzten Formular-Element innerhalb der Karte.
+ */
.auth__card .auth__form:last-child {
margin-bottom: 0;
}
+/**
+ * @section Responsive Responsive Design
+ * @brief Anpassungen für Tabets und mobile Geräte.
+ */
/* ─── Responsive ─── */
+/**
+ * @brief Ansichten bis zu 720px Breite (Tablets/kleine Bildschirme).
+ * @details Reduziert die Paddings, um mehr Platz für den Inhalt zu schaffen.
+ */
@media (max-width: 720px) {
.auth {
padding: 24px 12px;
@@ -136,6 +227,11 @@ textarea.auth__input {
}
}
+/**
+ * @brief Ansichten bis zu 480px Breite (Smartphones).
+ * @details Stark reduzierte Abstände (Padding, Gaps) und angepasste Schriftgrößen
+ * zur Sicherstellung der Lesbarkeit auf mobilen Displays.
+ */
@media (max-width: 480px) {
.auth {
padding: 1rem 0.5rem;
diff --git a/assets/css/productpage.css b/assets/css/productpage.css
index cf1f5bd..b51eb24 100644
--- a/assets/css/productpage.css
+++ b/assets/css/productpage.css
@@ -1,8 +1,24 @@
-/* ==========================================================
- PRODUCT PAGE – Animated Detail View & Shop Offers
- ========================================================== */
+/**
+ * @file productpage.css
+ * @brief Stylesheet für die Produktdetailseite
+ * @details Diese Datei enthält alle CSS-Regeln für die Darstellung der Produktdetails, der Shop-Angebote sowie der Bewertungsbereiche.
+ * Es werden moderne CSS-Features wie Flexbox, Grid, CSS-Variablen und Keyframe-Animationen verwendet.
+ * @version 1.0
+ */
-/* ─── Wrapper ─── */
+/**
+ * ==========================================================
+ * @section PRODUCT PAGE – Animated Detail View & Shop Offers
+ * @brief Hauptbereich der Produktseite, der das Bild und die Details umschließt.
+ * ==========================================================
+ */
+
+/**
+ * @class .product-wrapper
+ * @brief Haupt-Container für die Produktpräsentation.
+ * @details Setzt eine maximale Breite, zentriert den Inhalt und ordnet die linke und rechte Spalte nebeneinander an.
+ * Nutzt eine Fade-In-Animation beim Laden.
+ */
.product-wrapper {
max-width: 1200px;
margin: 3rem auto;
@@ -12,12 +28,25 @@
animation: fadeInUp 0.6s ease both;
}
-/* ─── Left Column (Image) ─── */
+/**
+ * ─── Left Column (Image) ───
+ */
+
+/**
+ * @class .product-left
+ * @brief Linke Spalte, die das Produktbild enthält.
+ * @details Nimmt 1 Teil des Flex-Containers ein und wird mit einer leichten Verzögerung eingeblendet.
+ */
.product-left {
flex: 1;
animation: fadeInUp 0.5s ease 0.1s both;
}
+/**
+ * @class .product-image-box
+ * @brief Box-Container für das eigentliche Produktbild.
+ * @details Beinhaltet Hintergrund, Schatten, Abrundungen und positioniert pseudo-Elemente für Hover-Effekte.
+ */
.product-image-box {
background: #ffffff;
padding: 40px;
@@ -29,7 +58,11 @@
overflow: hidden;
}
-/* Animated gradient border on hover */
+/**
+ * @pseudo .product-image-box::before
+ * @brief Animierter Gradient-Rahmen beim Hover.
+ * @details Nutzt Maskierungen (-webkit-mask), um einen leuchtenden Rahmen über den bestehenden Border zu zeichnen, der weich eingeblendet wird.
+ */
.product-image-box::before {
content: "";
position: absolute;
@@ -44,15 +77,29 @@
transition: opacity var(--transition-smooth);
}
+/**
+ * @state .product-image-box:hover
+ * @brief Hover-Zustand für die Bild-Box.
+ * @details Verstärkt den Schatten und hebt das Element leicht an (TranslateY).
+ */
.product-image-box:hover {
box-shadow: var(--shadow-lg), var(--shadow-glow-blue);
transform: translateY(-4px);
}
+/**
+ * @state .product-image-box:hover::before
+ * @brief Macht den Gradient-Rahmen beim Hover sichtbar.
+ */
.product-image-box:hover::before {
opacity: 1;
}
+/**
+ * @element .product-image-box img
+ * @brief Das Produktbild selbst.
+ * @details Skaliert sich weich beim Hovern.
+ */
.product-image-box img {
max-width: 100%;
height: auto;
@@ -60,16 +107,33 @@
transition: transform var(--transition-smooth);
}
+/**
+ * @state .product-image-box:hover img
+ * @brief Vergrößert das Bild beim Hover leicht (Scale).
+ */
.product-image-box:hover img {
transform: scale(1.03);
}
-/* ─── Right Column (Details) ─── */
+/**
+ * ─── Right Column (Details) ───
+ */
+
+/**
+ * @class .product-right
+ * @brief Rechte Spalte für die Produktdetails (Titel, Beschreibung, Spezifikationen).
+ * @details Nimmt 1.2 Teile des Flex-Platzes ein, etwas mehr als das Bild. Besitzt ebenfalls eine Fade-In Animation.
+ */
.product-right {
flex: 1.2;
animation: fadeInUp 0.5s ease 0.2s both;
}
+/**
+ * @class .product-title
+ * @brief Titel des Produkts.
+ * @details Große weiße Schrift mit einer Trennlinie nach unten.
+ */
.product-title {
font-size: 32px;
color: white;
@@ -79,6 +143,10 @@
padding-bottom: 15px;
}
+/**
+ * @class .product-desc
+ * @brief Kurze Beschreibungstexte zum Produkt.
+ */
.product-desc {
font-size: 23px;
line-height: 1.7;
@@ -86,6 +154,11 @@
margin-bottom: 15px;
}
+/**
+ * @class .product-specs
+ * @brief Container für die Haupt-Spezifikationen.
+ * @details Richtet Elemente vertikal aus (Flex-Column) mit entsprechendem Abstand (Gap).
+ */
.product-specs {
display: flex;
color: #ffffff;
@@ -93,6 +166,11 @@
gap: 12px;
}
+/**
+ * @element .product-specs p
+ * @brief Einzelner Spec-Absatz.
+ * @details Halbtransparenter Hintergrund mit Blur-Effekt, um Tiefe zu simulieren.
+ */
.product-specs p {
padding: 0.7rem 1rem;
background: rgba(27, 34, 48, 0.65);
@@ -103,19 +181,36 @@
transition: all var(--transition-normal);
}
+/**
+ * @state .product-specs p:hover
+ * @brief Hover-Effekt für Specs.
+ * @details Der Hintergrund wird deckender und der Text rückt leicht nach rechts ein.
+ */
.product-specs p:hover {
background: rgba(27, 34, 48, 0.9);
border-color: var(--border-default);
transform: translateX(4px);
}
+/**
+ * @element .product-specs p strong
+ * @brief Hervorhebung des Spec-Labels innerhalb eines Absatzes.
+ */
.product-specs p strong {
color: var(--text-muted);
font-weight: 500;
margin-right: 0.5rem;
}
-/* ─── Spec Rows ─── */
+/**
+ * ─── Spec Rows ───
+ */
+
+/**
+ * @class .spec-row
+ * @brief Zeilenbasierte Darstellung einer einzelnen Spezifikation.
+ * @details Nutzt Flexbox zur Verteilung von Label und Wert an die äußeren Ränder.
+ */
.spec-row {
display: flex;
justify-content: space-between;
@@ -126,22 +221,38 @@
transition: all var(--transition-normal);
}
+/**
+ * @state .spec-row:hover
+ * @brief Hover-Verhalten einer Spec-Row.
+ * @details Ähnlich wie bei .product-specs p, leichtes Verschieben nach rechts.
+ */
.spec-row:hover {
background: rgba(27, 34, 48, 0.9);
transform: translateX(4px);
}
+/**
+ * @class .spec-name
+ * @brief Der Name bzw. die Bezeichnung der Spezifikation in der Zeile.
+ */
.spec-name {
font-weight: 500;
color: var(--text-secondary);
}
+/**
+ * @class .spec-value
+ * @brief Der tatsächliche Wert der Spezifikation, fett hervorgehoben.
+ */
.spec-value {
font-weight: 600;
color: var(--text-primary);
}
-/* ─── Responsive Product Page ─── */
+/**
+ * ─── Responsive Product Page ───
+ * @details Media-Queries für Tablets und Smartphones im Produkt-Bereich.
+ */
@media (max-width: 900px) {
.product-wrapper {
flex-direction: column;
@@ -207,9 +318,18 @@
}
}
-/* ==========================================================
- SHOP OFFERS – Animated List
- ========================================================== */
+/**
+ * ==========================================================
+ * @section SHOP OFFERS – Animated List
+ * @brief Bereich für die Anzeige verfügbarer Angebote verschiedener Shops.
+ * ==========================================================
+ */
+
+/**
+ * @class .shop-offers
+ * @brief Container, der alle Shop-Angebote umschließt.
+ * @details Vertikales Flexbox-Layout mit Animation.
+ */
.shop-offers {
max-width: 1200px;
margin: 0 auto 4rem;
@@ -220,6 +340,11 @@
animation: fadeInUp 0.6s ease 0.3s both;
}
+/**
+ * @class .shop-line
+ * @brief Eine einzelne Zeile für ein Shop-Angebot.
+ * @details Nutzt ein CSS Grid mit 3 Spalten (Bild, Details, Preis/Button).
+ */
.shop-line {
display: grid;
grid-template-columns: 250px 1fr auto;
@@ -235,7 +360,10 @@
overflow: hidden;
}
-/* Gradient accent line on left */
+/**
+ * @pseudo .shop-line::before
+ * @brief Dekorationslinie (Gradient), die links auftaucht, wenn man über das Angebot fährt.
+ */
.shop-line::before {
content: "";
position: absolute;
@@ -249,29 +377,49 @@
transition: opacity var(--transition-smooth);
}
+/**
+ * @state .shop-line:hover
+ * @brief Interaktion mit einem Shop-Angebot.
+ * @details Ändert die Hintergrundfarbe, erzeugt Schatten und animiert nach oben.
+ */
.shop-line:hover {
background: #243248;
transform: translateY(-3px);
box-shadow: 0 8px 20px rgba(0,0,0,0.25);
}
+/**
+ * @state .shop-line:hover::before
+ * @brief Blendet die linke Dekorationslinie beim Hovern ein.
+ */
.shop-line:hover::before {
opacity: 1;
}
-/* Stagger shop lines */
+/**
+ * @brief Staggered Animations
+ * @details Verzögert das Einblenden der Listenelemente sukzessive für einen Wasserfall-Effekt.
+ */
.shop-line:nth-child(1) { animation-delay: 0.35s; }
.shop-line:nth-child(2) { animation-delay: 0.42s; }
.shop-line:nth-child(3) { animation-delay: 0.49s; }
.shop-line:nth-child(4) { animation-delay: 0.56s; }
.shop-line:nth-child(5) { animation-delay: 0.63s; }
+/**
+ * @class .shop-left
+ * @brief Linker Abschnitt in einem Shop-Angebot (Logo).
+ */
.shop-left {
display: flex;
align-items: center;
gap: 1rem;
}
+/**
+ * @class .shop-logo
+ * @brief Container für das Shop-Logo.
+ */
.shop-logo {
display: flex;
align-items: center;
@@ -279,6 +427,10 @@
flex-shrink: 0;
}
+/**
+ * @element .shop-logo img
+ * @brief Das Bild des Shop-Logos.
+ */
.shop-logo img {
max-height: 36px;
max-width: 90px;
@@ -287,25 +439,45 @@
transition: transform var(--transition-normal);
}
+/**
+ * @state .shop-line:hover .shop-logo img
+ * @brief Skaliert das Logo beim Hovern über die Zeile.
+ */
.shop-line:hover .shop-logo img {
transform: scale(1.08);
}
+/**
+ * @class .shop-name
+ * @brief Textanzeige des Shop-Namens.
+ */
.shop-name {
color: white;
font-weight: 600;
font-size: 16px;
}
+/**
+ * @element .shop-name a
+ * @brief Link auf den Shop, falls der Name geklickt wird.
+ */
.shop-name a {
color: white;
text-decoration: none;
}
+/**
+ * @state .shop-name a:hover
+ * @brief Unterstreichung beim Hover über den Shop-Namen.
+ */
.shop-name a:hover {
text-decoration: underline;
}
+/**
+ * @class .shop-middle
+ * @brief Mittlerer Abschnitt mit Zusatzinfos (Versand, Bestand).
+ */
.shop-middle {
display: flex;
align-items: center;
@@ -314,6 +486,10 @@
color: #cbd5e1;
}
+/**
+ * @class .shop-shipping
+ * @brief Bereich für Versandinformationen.
+ */
.shop-shipping {
display: flex;
flex-direction: column;
@@ -321,6 +497,10 @@
line-height: 1.4;
}
+/**
+ * @class .shop-stock
+ * @brief Lagerbestands-Anzeige.
+ */
.shop-stock {
font-weight: 500;
display: flex;
@@ -328,16 +508,28 @@
gap: 6px;
}
+/**
+ * @state .shop-stock.in-stock::before
+ * @brief Prefix-Icon für vorrätige Artikel (grünes Häkchen).
+ */
.shop-stock.in-stock::before {
content: "✔";
color: #22c55e;
}
+/**
+ * @state .shop-stock.out-stock::before
+ * @brief Prefix-Icon für nicht vorrätige Artikel (rotes X).
+ */
.shop-stock.out-stock::before {
content: "✖";
color: #ef4444;
}
+/**
+ * @class .shop-price
+ * @brief Preis-Hervorhebung ganz rechts in der Shop-Zeile.
+ */
.shop-price {
margin-left: auto;
font-size: 18px;
@@ -345,10 +537,18 @@
color: #4ade80;
}
+/**
+ * @state .shop-line:hover .shop-price
+ * @brief Skaliert den Preis leicht bei Interaktion.
+ */
.shop-line:hover .shop-price {
transform: scale(1.05);
}
+/**
+ * @class .no-shop
+ * @brief Fallback-Ansicht, wenn kein Angebot vorhanden ist.
+ */
.no-shop {
background: #1f2a3a;
padding: 20px;
@@ -357,7 +557,10 @@
text-align: center;
}
-/* ─── Responsive Shop Offers ─── */
+/**
+ * ─── Responsive Shop Offers ───
+ * @details Umbrüche für Shop-Zeilen bei kleineren Screens.
+ */
@media (max-width: 900px) {
.shop-line {
grid-template-columns: 1fr;
@@ -431,10 +634,17 @@
}
-/* ==========================================================
- PRODUCT REVIEWS
- ========================================================== */
+/**
+ * ==========================================================
+ * @section PRODUCT REVIEWS
+ * @brief Anzeige und Strukturierung der Produktbewertungen.
+ * ==========================================================
+ */
+/**
+ * @class .reviews
+ * @brief Wrapper für alle Bewertungen, inklusive Überschrift.
+ */
.reviews {
max-width: 1200px;
margin: 3rem auto 5rem;
@@ -445,6 +655,10 @@
animation: fadeInUp 0.6s ease 0.4s both;
}
+/**
+ * @class .reviews-title
+ * @brief Titel der Bewertungs-Sektion.
+ */
.reviews-title {
color: white;
font-size: 22px;
@@ -452,7 +666,11 @@
margin-bottom: 0.5rem;
}
-/* Review Card */
+/**
+ * @class .review-card
+ * @brief Einzelne Karte für eine Bewertung von einem User.
+ * @details Beinhaltet Hintergrund, Ränder und relative Positionierung für den Border-Effekt.
+ */
.review-card {
background: #1c2533; /* leicht dunkler als shop */
border: 1px solid #2a374a;
@@ -463,7 +681,10 @@
overflow: hidden;
}
-/* leichter Akzent rechts statt links */
+/**
+ * @pseudo .review-card::after
+ * @brief Gradient-Rahmenkante auf der rechten Seite, die beim Hover erscheint.
+ */
.review-card::after {
content: "";
position: absolute;
@@ -476,17 +697,28 @@
transition: opacity var(--transition-smooth);
}
+/**
+ * @state .review-card:hover
+ * @brief Hover-State der Bewertung, hellt Hintergrund leicht auf.
+ */
.review-card:hover {
background: #223047;
transform: translateY(-3px);
box-shadow: 0 8px 18px rgba(0,0,0,0.25);
}
+/**
+ * @state .review-card:hover::after
+ * @brief Blendet den farbigen Rand rechts ein.
+ */
.review-card:hover::after {
opacity: 1;
}
-/* Header */
+/**
+ * @class .review-header
+ * @brief Kopfzeile einer Bewertung (User links, Sterne/Datum z.B. rechts).
+ */
.review-header {
display: flex;
justify-content: space-between;
@@ -494,7 +726,10 @@
margin-bottom: 0.6rem;
}
-/* User-Info (Avatar + Name/Datum) */
+/**
+ * @class .review-user-info
+ * @brief Container für Avatar, Name und ggf. Datum.
+ */
.review-user-info {
display: flex;
align-items: center;
@@ -502,7 +737,10 @@
min-width: 0; /* erlaubt Text-Ellipsis/Umbruch in flex */
}
-/* Profilbild in Reviews: immer gleich groß und angenehm klein */
+/**
+ * @class .review-avatar
+ * @brief Profilbild in kleinen Abmessungen innerhalb der Review.
+ */
.review-avatar {
width: clamp(28px, 3.2vw, 34px);
height: clamp(28px, 3.2vw, 34px);
@@ -515,7 +753,10 @@
background: #0f172a; /* falls Bild transparent/fehlend */
}
-/* Optional: verhindert, dass lange Namen Layout sprengen */
+/**
+ * @class .review-user
+ * @brief Benutzername des Reviewers, wird abgeschnitten (ellipsis), wenn zu lang.
+ */
.review-user {
overflow: hidden;
text-overflow: ellipsis;
@@ -528,34 +769,55 @@
font-size: 15px;
}
-/* Sterne */
+/**
+ * @class .review-rating
+ * @brief Container für die Sterne-Bewertung.
+ */
.review-rating {
display: flex;
gap: 4px;
}
+/**
+ * @class .star
+ * @brief Ein einzelnes Stern-Symbol.
+ */
.star {
font-size: 16px;
color: #475569;
transition: transform 0.2s ease;
}
+/**
+ * @state .star.filled
+ * @brief Aktiver, goldener Stern.
+ */
.star.filled {
color: #fbbf24;
}
+/**
+ * @state .review-card:hover .star.filled
+ * @brief Skaliert goldene Sterne innerhalb einer Karte beim Hovern.
+ */
.review-card:hover .star.filled {
transform: scale(1.15);
}
-/* Kommentar */
+/**
+ * @class .review-comment
+ * @brief Textinhalt einer Bewertung.
+ */
.review-comment {
color: #cbd5e1;
font-size: 14px;
line-height: 1.6;
}
-/* Keine Reviews */
+/**
+ * @class .no-review
+ * @brief Fallback-Text, falls noch keine Bewertungen existieren.
+ */
.no-review {
background: #1f2a3a;
border: 1px solid #2f3c52;
@@ -565,10 +827,17 @@
}
-/* ==========================================================
- REVIEW OVERVIEW BOX (Linke Spalte) - Kompakte Version
- ========================================================== */
+/**
+ * ==========================================================
+ * @section REVIEW OVERVIEW BOX (Linke Spalte) - Kompakte Version
+ * @brief Zusammenfassung aller Bewertungen (Durchschnittsnote und Sterne-Verteilung).
+ * ==========================================================
+ */
+/**
+ * @class .review-overview-box
+ * @brief Sidebar-Box zur Übersicht der Gesamtbewertung.
+ */
.review-overview-box {
background: #1c2533;
border: 1px solid #2a374a;
@@ -580,6 +849,10 @@
animation: fadeInUp 0.5s ease 0.3s both;
}
+/**
+ * @class .overview-header
+ * @brief Headerbereich der Übersicht (Zentriert, Rand unten).
+ */
.overview-header {
text-align: center;
/* Abstände verringert */
@@ -588,6 +861,10 @@
border-bottom: 1px solid #2a374a;
}
+/**
+ * @class .overview-avg
+ * @brief Die durchschnittliche Bewertung als große Zahl.
+ */
.overview-avg {
/* Schriftgröße der Durchschnittsnote von 2.5rem auf 2rem verkleinert */
font-size: 2rem;
@@ -599,11 +876,19 @@
gap: 0.4rem;
}
+/**
+ * @element .overview-avg .star
+ * @brief Der Stern-Icon neben der Durchschnittsbewertung.
+ */
.overview-avg .star {
/* Stern etwas verkleinert */
font-size: 1.5rem;
}
+/**
+ * @class .overview-count
+ * @brief Anzahl der Gesamtbewertungen in kleinem Text.
+ */
.overview-count {
/* Textgröße leicht verkleinert */
font-size: 0.8rem;
@@ -611,6 +896,10 @@
margin-top: 0.2rem;
}
+/**
+ * @class .overview-breakdown
+ * @brief Container für die Verteilung (Progress-Bars) der einzelnen Sterne (5,4,3,2,1).
+ */
.overview-breakdown {
display: flex;
flex-direction: column;
@@ -618,6 +907,10 @@
gap: 0.4rem;
}
+/**
+ * @class .breakdown-row
+ * @brief Einzelne Zeile der Verteilung (Stellt z.B. 5 Sterne und deren Balken dar).
+ */
.breakdown-row {
display: flex;
align-items: center;
@@ -626,6 +919,10 @@
font-size: 0.8rem;
}
+/**
+ * @class .breakdown-stars
+ * @brief Beschriftung am Start des Balkens ("5 Sterne").
+ */
.breakdown-stars {
width: 50px;
white-space: nowrap;
@@ -633,6 +930,10 @@
color: #cbd5e1;
}
+/**
+ * @class .breakdown-bar-bg
+ * @brief Dunkler Hintergrund des Fortschrittsbalkens.
+ */
.breakdown-bar-bg {
flex: 1;
/* Höhe der Balken von 8px auf 6px reduziert */
@@ -642,6 +943,11 @@
overflow: hidden;
}
+/**
+ * @class .breakdown-bar-fill
+ * @brief Goldene Füllung des Balkens, je nach prozentualer Verteilung.
+ * @details Nutzt CSS-Transitions für einen aufbauenden Lade-Effekt.
+ */
.breakdown-bar-fill {
height: 100%;
background: #fbbf24;
@@ -649,12 +955,20 @@
transition: width 0.8s ease-out;
}
+/**
+ * @class .breakdown-num
+ * @brief Absolute Anzahl der Bewertungen für diese Stern-Reihe.
+ */
.breakdown-num {
width: 20px;
text-align: right;
color: #94a3b8;
}
+/**
+ * @class .overview-empty
+ * @brief Nachricht in der Übersicht, wenn keine Daten vorliegen.
+ */
.overview-empty {
text-align: center;
color: #94a3b8;
@@ -662,10 +976,17 @@
font-size: 0.85rem;
}
-/* ==========================================================
- REVIEW HINZUFÜGEN (Formular)
- ========================================================== */
+/**
+ * ==========================================================
+ * @section REVIEW HINZUFÜGEN (Formular)
+ * @brief Bereich zum Schreiben einer eigenen Bewertung.
+ * ==========================================================
+ */
+/**
+ * @class .review-add
+ * @brief Wrapper für das Eingabeformular.
+ */
.review-add {
max-width: 1200px;
margin: 0 auto 2rem;
@@ -673,13 +994,22 @@
animation: fadeInUp 0.5s ease 0.4s both;
}
+/**
+ * @class .review-input-form
+ * @brief Das eigentliche Formular-Layout (vertikal).
+ */
.review-input-form {
display: flex;
flex-direction: column;
gap: 1.2rem;
}
-/* --- Sterne-Auswahl (Radio-Button Trick) --- */
+/**
+ * --- Sterne-Auswahl (Radio-Button Trick) ---
+ * @class .rating-input
+ * @brief Behälter für die Radio-Buttons zur Sternbewertung.
+ * @details Nutzt `flex-direction: row-reverse;`, um CSS-basiertes Highlighten vorheriger Sterne durch Geschwister-Selektoren zu ermöglichen.
+ */
.rating-input {
display: flex;
/* Dreht die Reihenfolge um für den Hover-Effekt */
@@ -688,12 +1018,18 @@
gap: 0.3rem;
}
-/* Versteckt die eigentlichen runden Radio-Buttons */
+/**
+ * @element .rating-input input[type="radio"]
+ * @brief Die eigentlichen HTML-Radio-Elemente werden versteckt.
+ */
.rating-input input[type="radio"] {
display: none;
}
-/* Stylt die Labels (die Sterne) */
+/**
+ * @element .rating-input label
+ * @brief Das angezeigte Label, das visuell als auswählbarer Stern fungiert.
+ */
.rating-input label {
font-size: 2.2rem;
color: #475569; /* Dunkelgrau für leere Sterne */
@@ -701,19 +1037,30 @@
transition: color 0.2s ease, transform 0.2s ease;
}
-/* Hover-Logik: Färbt den gehoverten/geklickten Stern und alle "folgenden" (durch row-reverse sind das die linken) */
+/**
+ * @state .rating-input label:hover, ...
+ * @brief Hover- und Auswahl-Mechanik für die interaktiven Sterne.
+ * @details Wenn ein Stern gehovert oder angewählt (checked) wird, färben sich dieser und alle in der row-reverse-Reihenfolge nachfolgenden Sterne golden.
+ */
.rating-input label:hover,
.rating-input label:hover ~ label,
.rating-input input[type="radio"]:checked ~ label {
color: #fbbf24; /* Das gleiche Gelb wie bei deinen bestehenden Sternen */
}
-/* Kleiner Pop-Effekt beim drüberfahren */
+/**
+ * @state .rating-input label:hover
+ * @brief Kleiner Skalierungseffekt beim drüberfahren.
+ */
.rating-input label:hover {
transform: scale(1.15);
}
-/* --- Textarea --- */
+/**
+ * --- Textarea ---
+ * @class .review-comment-input
+ * @brief Das Mahrzeilen-Textfeld für den eigentlichen Kommentar.
+ */
.review-comment-input {
width: 100%;
background: #1f2a3a;
@@ -727,6 +1074,10 @@
transition: all var(--transition-smooth);
}
+/**
+ * @state .review-comment-input:focus
+ * @brief Klick/Fokus-Status für das Textfeld, zeigt grünen Rahmen.
+ */
.review-comment-input:focus {
outline: none;
border-color: #4ade80; /* Passt zu deinem grünen Button-Stil */
@@ -734,13 +1085,20 @@
box-shadow: 0 0 0 3px rgba(74, 222, 128, 0.1);
}
-/* --- Button Positionierung --- */
+/**
+ * --- Button Positionierung ---
+ * @element .review-input-form .auth__submit
+ * @brief Stilisierung des Absende-Buttons innerhalb der Reviews.
+ */
.review-input-form .auth__submit {
align-self: flex-start; /* Button bleibt linksbündig und wird nicht über die ganze Breite gestreckt */
padding: 0.8rem 2.5rem;
}
-/* Container für alle fertigen Bewertungen */
+/**
+ * @class .reviews-all
+ * @brief Container für die Auflistung aller existierenden Bewertungen (ohne das Eingabeformular).
+ */
.reviews-all {
display: flex;
flex-direction: column;
@@ -748,7 +1106,11 @@
margin-bottom: 3rem; /* Etwas Luft nach unten zum "Hinzufügen"-Formular */
}
-/* Kommentar-Text */
+/**
+ * @class .review-comment
+ * @brief (Wiederholung/Erweiterung) Anpassung des Kommentar-Texts für Wortumbrüche.
+ * @details Zwingt den Browser bei überlangen Wörtern rechtzeitig umzubrechen, damit das Layout nicht explodiert.
+ */
.review-comment {
color: #cbd5e1;
font-size: 14px;
diff --git a/assets/images/profilePictures/user_20_20260211-150621.jpg b/assets/images/profilePictures/user_20_20260211-150621.jpg
deleted file mode 100644
index d558368..0000000
Binary files a/assets/images/profilePictures/user_20_20260211-150621.jpg and /dev/null differ
diff --git a/assets/js/search-autocomplete.js b/assets/js/search-autocomplete.js
deleted file mode 100644
index ea1da7c..0000000
--- a/assets/js/search-autocomplete.js
+++ /dev/null
@@ -1,222 +0,0 @@
-(function () {
- 'use strict';
-
- function debounce(fn, delay) {
- var t;
- return function () {
- var ctx = this;
- var args = arguments;
- clearTimeout(t);
- t = setTimeout(function () { fn.apply(ctx, args); }, delay);
- };
- }
-
- function createDropdownEl() {
- var el = document.createElement('div');
- el.className = 'searchDropdown';
- el.hidden = true;
- el.setAttribute('role', 'listbox');
- return el;
- }
-
- function attachToInput(input) {
- if (!input || input.dataset.autocompleteBound === '1') return;
- input.dataset.autocompleteBound = '1';
-
- // wrapper for positioning
- var wrapper = input.closest('.nav__searchField');
- if (!wrapper) return;
- wrapper.classList.add('nav__searchField--hasDropdown');
-
- // dropdown
- var dropdown = createDropdownEl();
- wrapper.appendChild(dropdown);
-
- var activeIndex = -1;
- var items = [];
- var abortController = null;
-
- function close() {
- dropdown.hidden = true;
- dropdown.innerHTML = '';
- dropdown.classList.remove('is-open');
- activeIndex = -1;
- items = [];
- }
-
- function open() {
- dropdown.hidden = false;
- dropdown.classList.add('is-open');
- }
-
- function setStatus(text) {
- dropdown.innerHTML = '';
- var row = document.createElement('div');
- row.className = 'searchDropdown__status';
- row.textContent = text;
- dropdown.appendChild(row);
- open();
- }
-
- function render(list) {
- dropdown.innerHTML = '';
- items = list || [];
- activeIndex = -1;
-
- if (!items.length) {
- setStatus('Keine Produkte gefunden.');
- return;
- }
-
- for (var i = 0; i < items.length; i++) {
- (function (idx) {
- var it = items[idx];
-
- var a = document.createElement('a');
- a.className = 'searchDropdown__item';
- a.href = it.url;
- a.setAttribute('role', 'option');
- a.tabIndex = -1;
-
- var img = document.createElement('img');
- img.className = 'searchDropdown__img';
- img.alt = '';
- img.loading = 'lazy';
- img.decoding = 'async';
- img.src = it.imagePath && it.imagePath.length ? it.imagePath : 'assets/images/placeholder.png';
-
- var meta = document.createElement('div');
- meta.className = 'searchDropdown__meta';
-
- var title = document.createElement('div');
- title.className = 'searchDropdown__title';
- title.textContent = it.model || '';
-
- var desc = document.createElement('div');
- desc.className = 'searchDropdown__desc';
- desc.textContent = it.description || '';
-
- meta.appendChild(title);
- meta.appendChild(desc);
-
- a.appendChild(img);
- a.appendChild(meta);
-
- a.addEventListener('mouseenter', function () {
- setActive(idx);
- });
-
- dropdown.appendChild(a);
- })(i);
- }
-
- open();
- }
-
- function setActive(idx) {
- var children = dropdown.querySelectorAll('.searchDropdown__item');
- for (var i = 0; i < children.length; i++) {
- children[i].classList.toggle('is-active', i === idx);
- }
- activeIndex = idx;
- }
-
- function fetchResults(q) {
- if (abortController) {
- abortController.abort();
- }
- abortController = new AbortController();
-
- var url = 'api/search_products.php?q=' + encodeURIComponent(q) + '&limit=8';
-
- setStatus('Suche…');
-
- fetch(url, { signal: abortController.signal, headers: { 'Accept': 'application/json' } })
- .then(function (r) {
- if (!r.ok) throw new Error('HTTP ' + r.status);
- return r.json();
- })
- .then(function (data) {
- render((data && data.items) ? data.items : []);
- })
- .catch(function (err) {
- if (err && err.name === 'AbortError') return;
- setStatus('Fehler bei der Suche.');
- });
- }
-
- var debounced = debounce(function () {
- var q = (input.value || '').trim();
- if (q.length < 1) {
- close();
- return;
- }
- fetchResults(q);
- }, 180);
-
- input.addEventListener('input', debounced);
-
- input.addEventListener('focus', function () {
- var q = (input.value || '').trim();
- if (q.length >= 1) {
- fetchResults(q);
- }
- });
-
- input.addEventListener('keydown', function (e) {
- if (dropdown.hidden) return;
-
- if (e.key === 'Escape') {
- close();
- return;
- }
-
- var max = items.length - 1;
- if (max < 0) return;
-
- if (e.key === 'ArrowDown') {
- e.preventDefault();
- var next = activeIndex + 1;
- if (next > max) next = 0;
- setActive(next);
- } else if (e.key === 'ArrowUp') {
- e.preventDefault();
- var prev = activeIndex - 1;
- if (prev < 0) prev = max;
- setActive(prev);
- } else if (e.key === 'Enter') {
- if (activeIndex >= 0) {
- var links = dropdown.querySelectorAll('.searchDropdown__item');
- if (links[activeIndex]) {
- e.preventDefault();
- window.location.href = links[activeIndex].href;
- }
- }
- }
- });
-
- document.addEventListener('click', function (e) {
- if (wrapper.contains(e.target)) return;
- close();
- });
-
- // prevent blur-close when clicking inside dropdown until navigation happens
- dropdown.addEventListener('mousedown', function (e) {
- e.preventDefault();
- });
- }
-
- function init() {
- var inputs = document.querySelectorAll('input.nav__searchInput[name="search"]');
- for (var i = 0; i < inputs.length; i++) {
- attachToInput(inputs[i]);
- }
- }
-
- if (document.readyState === 'loading') {
- document.addEventListener('DOMContentLoaded', init);
- } else {
- init();
- }
-})();
-
diff --git a/attrbar.php b/attrbar.php
index 8c233c4..112cfc9 100644
--- a/attrbar.php
+++ b/attrbar.php
@@ -1,10 +1,40 @@
20,
'ipad' => 21,
@@ -13,14 +43,24 @@ $categoriesConfig = [
'accessories' => 24,
];
+// Überprüfen, ob die gewählte Kategorie im Konfigurations-Array vorhanden ist.
if (isset($categoriesConfig[$currentCategory])) {
+ // Falls vorhanden, wird die entsprechende Kategorie-ID für weitere Abfragen gesetzt.
$catId = $categoriesConfig[$currentCategory];
}
-// Fetch available attributes for the category if selected
+/**
+ * @var array $attributes
+ * @brief Speichert die verfügbaren Attribute (z.B. Farbe, Speichergröße) für die selektierte Kategorie.
+ */
$attributes = [];
+
+// Attribute nur laden, wenn eine gültige Kategorie-ID gefunden wurde.
if ($catId) {
- // Show attributes related to the active category
+ /**
+ * @details Holt alle Attribute, die mit der aktiven Kategorie verknüpft sind,
+ * sortiert nach ihrem Namen, um eine geordnete Filterliste anzuzeigen.
+ */
$stmtAttr = $conn->prepare("
SELECT a.attributeID, a.name, a.unit, a.dataType
FROM attributes a
@@ -31,30 +71,51 @@ if ($catId) {
$stmtAttr->bind_param("i", $catId);
$stmtAttr->execute();
$resAttr = $stmtAttr->get_result();
+
+ // Iteriere über das Result-Set und speichere jedes gefundene Attribut im $attributes-Array.
while ($row = $resAttr->fetch_assoc()) {
$attributes[] = $row;
}
+ // Statement schließen, um Ressourcen freizugeben.
$stmtAttr->close();
}
?>
-
+