diff --git a/.projectstructure.txt b/.projectstructure.txt index c76267b..45300bf 100644 --- a/.projectstructure.txt +++ b/.projectstructure.txt @@ -48,7 +48,7 @@ Stand: 2026-07-24 - `src/App/ProfileSettings.php` Persistente Profileinstellungen für Standortfreigabe und Avatar-Merkmale. - `src/App/Avatar.php` - Avatar-Generator, Optionen und HTML-Rendering. + Avatar-Generator, Optionen und HTML-Rendering im realistischerem Comic-Stil. - `src/App/Search.php` Eventsuche inkl. Distanzfilter. @@ -67,7 +67,7 @@ Stand: 2026-07-24 - Kein eigener Top-Level-Punkt `Termine`; Event-Fokus liegt derzeit auf Startseite und Suche. - Community mit Kategorien, Boards, Thread-Liste, Thread-Ansicht, Rollen-/Moderationslogik. - Mitgliederbereich als Bereichslayout mit linker Navigation und separaten Seiten für Profil, Kinder, Termine, Community und Einstellungen. -- Profilbereich mit Avatar-Generator für Haut, Haare, Augen, Bart, Brille und Kopfform. +- Profilbereich mit Avatar-Generator für Haut, Haare, Augen, Bart, Brille, Kopfform, Mütze und Outfit-Farbe. - Community-Admin-Bereich für Bewerbungen, Meldungen, Rollen und Migration. - Impressum, Datenschutz-&-Cookies-Seite und Über-uns vorhanden. - Standortsortierung auf der Startseite für Treffen in der Nähe. diff --git a/PROJECT_CONTEXT.md b/PROJECT_CONTEXT.md index 752c2f7..dbd1317 100644 --- a/PROJECT_CONTEXT.md +++ b/PROJECT_CONTEXT.md @@ -19,13 +19,13 @@ Papa-Kind-Treff ist eine PHP-basierte Plattform für Väter. Kernbereiche sind l - Die Community ist hierarchisch aufgebaut: Kategorien -> Boards -> Threads -> Posts. - Moderation ist getrennt vom normalen Community-Frontend in einem eigenen Adminbereich. - Standortfunktionen sind relevant für lokale Treffen und werden künftig weiter ausgebaut. -- Profilbilder werden aktuell über einen integrierten Avatar-Generator statt über Bild-Uploads erzeugt. +- Profilbilder werden aktuell über einen integrierten Avatar-Generator im realistischeren Comic-Stil statt über Bild-Uploads erzeugt. ## Navigation / Konto - Eingeloggte Nutzer sehen rechts ein Profil-Menü statt einzelner `Dashboard`-/`Logout`-Buttons. - Das Profil-Menü enthält aktuell `Profil`, `Kinder`, `Termine`, `Community`, `Einstellungen`, `Abmelden`. - Der Mitgliederbereich selbst ist als Layout mit linker Bereichsnavigation aufgebaut. -- Im Profil kann der Nutzer sein Avatar-Bild aus festen Merkmalen zusammenstellen. +- Im Profil kann der Nutzer sein Avatar-Bild aus festen Merkmalen wie Gesicht, Haare, Bart, Brille, Mütze und Outfit zusammenstellen. - Debug ist kein Menüpunkt und erscheint nur als Floating-Käfer für `site_admin` bei aktivem Debug-Modus. ## Community und Rechte diff --git a/README.md b/README.md index d4d305c..3bb3389 100644 --- a/README.md +++ b/README.md @@ -17,7 +17,7 @@ Papa-Kind-Treff ist eine PHP-basierte Plattform für Väter mit Fokus auf lokale - Impressum, Datenschutz-&-Cookies-Seite und Über-uns - standortbasierte Sortierung für die neuesten Treffen - eigener Einstellungsbereich für Standortfreigabe, Browser-Hinweise und Consent-Verwaltung -- Avatar-Generator im Profil mit Haut-, Haar-, Augen-, Bart-, Brillen- und Kopfform-Auswahl +- Avatar-Generator im Profil mit realistischerem Comic-Stil, Haut-, Haar-, Augen-, Bart-, Brillen-, Kopfform-, Mützen- und Outfit-Auswahl - Consent-Manager für Analyse und externe Dienste - Navigation aktuell mit `Home`, `Suche`, `Community` - eingeloggte Nutzer sehen rechts ein Profil-Menü mit Direktlinks zu `Profil`, `Kinder`, `Termine`, `Community`, `Einstellungen` diff --git a/partials/landing/account/dashboard.php b/partials/landing/account/dashboard.php index 28fe499..f1807ff 100755 --- a/partials/landing/account/dashboard.php +++ b/partials/landing/account/dashboard.php @@ -337,6 +337,14 @@ $sectionLinks = [ +
+ + +
Hautfarbe @@ -376,6 +384,32 @@ $sectionLinks = [
+ +
+ Mützenfarbe +
+ $meta): ?> + + +
+
+ +
+ Hoodie / Shirt +
+ $meta): ?> + + +
+
diff --git a/partials/structure/nav.php b/partials/structure/nav.php index a202fee..b341b0b 100755 --- a/partials/structure/nav.php +++ b/partials/structure/nav.php @@ -11,7 +11,7 @@ if ($isLoggedIn) { if ($pdo) { $profileSettings = new \App\ProfileSettings($pdo); $profileSettings->ensureSchema(); - $stmt = $pdo->prepare('SELECT display_name, first_name, avatar_skin_tone, avatar_hair_style, avatar_hair_color, avatar_eye_color, avatar_beard_style, avatar_glasses_style, avatar_head_shape FROM user_profiles WHERE user_id = :id LIMIT 1'); + $stmt = $pdo->prepare('SELECT display_name, first_name, avatar_skin_tone, avatar_hair_style, avatar_hair_color, avatar_eye_color, avatar_beard_style, avatar_glasses_style, avatar_head_shape, avatar_hat_style, avatar_hat_color, avatar_outfit_color FROM user_profiles WHERE user_id = :id LIMIT 1'); $stmt->execute(['id' => (int)$_SESSION['user_id']]); $profileRow = $stmt->fetch(PDO::FETCH_ASSOC) ?: []; $displayName = trim((string)($profileRow['display_name'] ?? '')) ?: trim((string)($profileRow['first_name'] ?? '')) ?: 'Profil'; diff --git a/public/assets/css/app.css b/public/assets/css/app.css index e52dadc..b1d9bfb 100755 --- a/public/assets/css/app.css +++ b/public/assets/css/app.css @@ -114,39 +114,51 @@ body { .step { text-align: left; } .step__icon { width: 34px; height:34px; border-radius: 10px; background: var(--color-accent); color: #fff; display:flex; align-items:center; justify-content:center; font-weight:700; margin-bottom: 8px; } -.pkt-avatar { --avatar-size: 52px; --avatar-skin: #efc1a3; --avatar-hair: #5b392b; --avatar-eye: #5f4431; position: relative; display: inline-block; width: var(--avatar-size); height: var(--avatar-size); border-radius: 18px; overflow: hidden; background: linear-gradient(180deg, #fbf6ee, #efe2d2); border: 1px solid rgba(74, 52, 38, 0.12); box-shadow: inset 0 1px 0 rgba(255,255,255,0.65); } +.pkt-avatar { --avatar-size: 52px; --avatar-skin: #efc1a3; --avatar-hair: #5b392b; --avatar-eye: #5f4431; --avatar-hat: #38475f; --avatar-outfit: #5f7387; position: relative; display: inline-block; width: var(--avatar-size); height: var(--avatar-size); border-radius: 20px; overflow: hidden; background: radial-gradient(circle at 28% 22%, #ffffff, #f5ece2 34%, #dfd0c0 100%); border: 1px solid rgba(74, 52, 38, 0.14); box-shadow: inset 0 1px 0 rgba(255,255,255,0.85), 0 10px 22px rgba(39, 28, 21, 0.08); } .pkt-avatar--nav { --avatar-size: 34px; border-radius: 999px; } .pkt-avatar--forum-list { --avatar-size: 42px; border-radius: 14px; } .pkt-avatar--forum { --avatar-size: 52px; border-radius: 16px; } .pkt-avatar--xl { --avatar-size: 120px; border-radius: 28px; } .pkt-avatar--xxl { --avatar-size: 168px; border-radius: 36px; } .pkt-avatar__bg, -.pkt-avatar__shirt, +.pkt-avatar__shadow, +.pkt-avatar__hoodie, .pkt-avatar__neck, .pkt-avatar__ears, .pkt-avatar__head, +.pkt-avatar__cheek, .pkt-avatar__hair, +.pkt-avatar__hat, .pkt-avatar__eye, +.pkt-avatar__brow, .pkt-avatar__nose, .pkt-avatar__mouth, .pkt-avatar__beard, .pkt-avatar__glasses { position: absolute; display: block; } -.pkt-avatar__bg { inset: 0; background: radial-gradient(circle at top, rgba(255,255,255,0.82), rgba(255,255,255,0) 48%); } -.pkt-avatar__shirt { left: 16%; bottom: -4%; width: 68%; height: 34%; border-radius: 50% 50% 18% 18%; background: linear-gradient(180deg, #607d94, #44586b); } -.pkt-avatar__neck { left: 43%; bottom: 24%; width: 14%; height: 16%; border-radius: 10px; background: var(--avatar-skin); } +.pkt-avatar__bg { inset: 0; background: radial-gradient(circle at 30% 16%, rgba(255,255,255,0.92), rgba(255,255,255,0.04) 42%), radial-gradient(circle at 78% 82%, rgba(93,70,48,0.08), rgba(93,70,48,0) 34%); } +.pkt-avatar__shadow { left: 18%; top: 18%; width: 64%; height: 62%; border-radius: 44% 44% 42% 42%; background: radial-gradient(circle at 52% 56%, rgba(44, 29, 21, 0.20), rgba(44, 29, 21, 0) 68%); filter: blur(calc(var(--avatar-size) * 0.045)); } +.pkt-avatar__hoodie { left: 7%; bottom: -4%; width: 86%; height: 38%; border-radius: 42% 42% 16% 16%; background: linear-gradient(180deg, color-mix(in srgb, var(--avatar-outfit) 82%, white 18%), color-mix(in srgb, var(--avatar-outfit) 72%, black 28%)); } +.pkt-avatar__hoodie::before { content: ""; position: absolute; left: 29%; top: -18%; width: 42%; height: 54%; border-radius: 50% 50% 42% 42%; border: calc(var(--avatar-size) * 0.035) solid color-mix(in srgb, var(--avatar-outfit) 78%, white 22%); border-bottom: 0; opacity: .95; } +.pkt-avatar__hoodie::after { content: ""; position: absolute; left: 47%; top: 12%; width: 6%; height: 20%; border-radius: 999px; background: rgba(255,255,255,0.42); box-shadow: calc(var(--avatar-size) * -0.08) 0 0 rgba(255,255,255,0.42); } +.pkt-avatar__neck { left: 43%; bottom: 24%; width: 14%; height: 16%; border-radius: 10px; background: linear-gradient(180deg, color-mix(in srgb, var(--avatar-skin) 90%, white 10%), color-mix(in srgb, var(--avatar-skin) 80%, black 20%)); } .pkt-avatar__ears { left: 20%; top: 37%; width: 60%; height: 18%; } .pkt-avatar__ears::before, -.pkt-avatar__ears::after { content: ""; position: absolute; top: 0; width: 12%; height: 100%; border-radius: 999px; background: var(--avatar-skin); } +.pkt-avatar__ears::after { content: ""; position: absolute; top: 0; width: 12%; height: 100%; border-radius: 999px; background: linear-gradient(180deg, color-mix(in srgb, var(--avatar-skin) 90%, white 10%), color-mix(in srgb, var(--avatar-skin) 78%, black 22%)); } .pkt-avatar__ears::before { left: -6%; } .pkt-avatar__ears::after { right: -6%; } -.pkt-avatar__head { left: 22%; top: 19%; width: 56%; height: 54%; background: var(--avatar-skin); } +.pkt-avatar__head { left: 22%; top: 19%; width: 56%; height: 54%; background: radial-gradient(circle at 44% 26%, color-mix(in srgb, var(--avatar-skin) 88%, white 12%) 0, color-mix(in srgb, var(--avatar-skin) 92%, white 8%) 35%, color-mix(in srgb, var(--avatar-skin) 84%, black 16%) 100%); box-shadow: inset 0 calc(var(--avatar-size) * -0.02) calc(var(--avatar-size) * 0.03) rgba(255,255,255,0.22), inset 0 calc(var(--avatar-size) * -0.04) calc(var(--avatar-size) * 0.08) rgba(118, 75, 47, 0.12); } .pkt-avatar--head-round .pkt-avatar__head { border-radius: 48% 48% 42% 42%; } .pkt-avatar--head-oval .pkt-avatar__head { border-radius: 44% 44% 46% 46%; transform: scaleX(0.94); } .pkt-avatar--head-square .pkt-avatar__head { border-radius: 26% 26% 32% 32%; } -.pkt-avatar__hair { left: 18%; top: 10%; width: 64%; height: 27%; background: var(--avatar-hair); border-radius: 52% 52% 26% 26%; } +.pkt-avatar__cheek { top: 54%; width: 11%; height: 6%; border-radius: 999px; background: rgba(194, 109, 97, 0.16); filter: blur(calc(var(--avatar-size) * 0.018)); } +.pkt-avatar__cheek--left { left: 29%; } +.pkt-avatar__cheek--right { right: 29%; } +.pkt-avatar__hair { left: 17%; top: 8%; width: 66%; height: 30%; background: linear-gradient(180deg, color-mix(in srgb, var(--avatar-hair) 92%, white 8%), color-mix(in srgb, var(--avatar-hair) 72%, black 28%)); border-radius: 52% 52% 26% 26%; box-shadow: inset 0 calc(var(--avatar-size) * -0.015) calc(var(--avatar-size) * 0.04) rgba(255,255,255,0.14); } .pkt-avatar__hair::before, .pkt-avatar__hair::after { content: ""; position: absolute; background: var(--avatar-hair); } .pkt-avatar--hair-short .pkt-avatar__hair::before { left: 10%; right: 10%; top: 52%; height: 36%; border-radius: 0 0 40% 40%; } +.pkt-avatar--hair-fade .pkt-avatar__hair::before { left: 12%; right: 12%; top: 48%; height: 28%; border-radius: 0 0 38% 38%; } +.pkt-avatar--hair-fade .pkt-avatar__hair::after { left: 6%; right: 6%; top: 66%; height: 12%; border-radius: 0 0 50% 50%; opacity: .72; } .pkt-avatar--hair-parted .pkt-avatar__hair::before { left: 8%; width: 36%; top: 54%; height: 38%; border-radius: 0 0 46% 42%; } .pkt-avatar--hair-parted .pkt-avatar__hair::after { right: 8%; width: 28%; top: 44%; height: 44%; border-radius: 0 0 40% 40%; } .pkt-avatar--hair-curly .pkt-avatar__hair { height: 30%; border-radius: 52% 52% 38% 38%; } @@ -154,21 +166,31 @@ body { .pkt-avatar--hair-curly .pkt-avatar__hair::after { top: 56%; width: 26%; height: 44%; border-radius: 50%; } .pkt-avatar--hair-curly .pkt-avatar__hair::before { left: 8%; } .pkt-avatar--hair-curly .pkt-avatar__hair::after { right: 8%; } +.pkt-avatar--hair-waves .pkt-avatar__hair::before { left: 7%; width: 34%; top: 48%; height: 42%; border-radius: 48% 42% 38% 42%; } +.pkt-avatar--hair-waves .pkt-avatar__hair::after { right: 5%; width: 38%; top: 44%; height: 46%; border-radius: 46% 46% 40% 44%; } .pkt-avatar--hair-bald .pkt-avatar__hair { display: none; } -.pkt-avatar__eye { top: 42%; width: 10%; height: 10%; border-radius: 999px; background: var(--avatar-eye); box-shadow: inset 0 0 0 3px rgba(255,255,255,0.65); } +.pkt-avatar__hat { left: 11%; top: 7%; width: 78%; height: 26%; display: none; background: linear-gradient(180deg, color-mix(in srgb, var(--avatar-hat) 86%, white 14%), color-mix(in srgb, var(--avatar-hat) 70%, black 30%)); box-shadow: inset 0 calc(var(--avatar-size) * -0.015) calc(var(--avatar-size) * 0.035) rgba(255,255,255,0.14); } +.pkt-avatar--hat-beanie .pkt-avatar__hat { display: block; border-radius: 48% 48% 22% 22%; } +.pkt-avatar--hat-beanie .pkt-avatar__hat::after { content: ""; position: absolute; left: 0; right: 0; bottom: 0; height: 32%; border-radius: 12px; background: color-mix(in srgb, var(--avatar-hat) 88%, black 12%); } +.pkt-avatar--hat-cap .pkt-avatar__hat { display: block; top: 9%; height: 22%; border-radius: 52% 52% 18% 18%; } +.pkt-avatar--hat-cap .pkt-avatar__hat::before { content: ""; position: absolute; left: 42%; top: 68%; width: 42%; height: 24%; border-radius: 0 999px 999px 999px; background: color-mix(in srgb, var(--avatar-hat) 82%, black 18%); transform: skewX(-18deg); } +.pkt-avatar__eye { top: 44%; width: 9%; height: 9%; border-radius: 999px; background: radial-gradient(circle at 42% 42%, rgba(255,255,255,0.85) 0 18%, var(--avatar-eye) 22% 100%); box-shadow: 0 calc(var(--avatar-size) * 0.01) 0 rgba(39,27,19,0.14); } .pkt-avatar__eye--left { left: 35%; } .pkt-avatar__eye--right { right: 35%; } -.pkt-avatar__nose { left: 47%; top: 48%; width: 6%; height: 12%; border-radius: 999px; background: rgba(126, 85, 54, 0.18); } -.pkt-avatar__mouth { left: 40%; top: 60%; width: 20%; height: 8%; border-bottom: 2px solid rgba(103, 66, 43, 0.55); border-radius: 0 0 999px 999px; } -.pkt-avatar__beard { left: 27%; top: 56%; width: 46%; background: var(--avatar-hair); display: none; } +.pkt-avatar__brow { top: 38%; width: 14%; height: 4%; background: color-mix(in srgb, var(--avatar-hair) 85%, black 15%); border-radius: 999px; opacity: .82; } +.pkt-avatar__brow--left { left: 32%; transform: rotate(-8deg); } +.pkt-avatar__brow--right { right: 32%; transform: rotate(8deg); } +.pkt-avatar__nose { left: 47%; top: 49%; width: 6%; height: 12%; border-radius: 999px; background: linear-gradient(180deg, rgba(126, 85, 54, 0.08), rgba(126, 85, 54, 0.26)); } +.pkt-avatar__mouth { left: 39%; top: 62%; width: 22%; height: 8%; border-bottom: 2px solid rgba(103, 66, 43, 0.56); border-radius: 0 0 999px 999px; } +.pkt-avatar__beard { left: 26%; top: 55%; width: 48%; background: linear-gradient(180deg, color-mix(in srgb, var(--avatar-hair) 88%, white 12%), color-mix(in srgb, var(--avatar-hair) 70%, black 30%)); display: none; } .pkt-avatar--beard-mustache .pkt-avatar__beard { display: block; height: 7%; top: 57%; border-radius: 999px 999px 18px 18px; } -.pkt-avatar--beard-short .pkt-avatar__beard { display: block; height: 18%; top: 56%; border-radius: 14px 14px 38% 38%; } -.pkt-avatar--beard-full .pkt-avatar__beard { display: block; height: 24%; top: 54%; border-radius: 16px 16px 42% 42%; } +.pkt-avatar--beard-short .pkt-avatar__beard { display: block; height: 19%; top: 56%; border-radius: 16px 16px 40% 40%; } +.pkt-avatar--beard-full .pkt-avatar__beard { display: block; height: 25%; top: 54%; border-radius: 18px 18px 44% 44%; } .pkt-avatar__glasses { inset: 0; display: none; } .pkt-avatar__glasses::before, -.pkt-avatar__glasses::after { content: ""; position: absolute; top: 40%; width: 18%; height: 12%; border: 2px solid rgba(53, 53, 53, 0.75); background: rgba(255,255,255,0.18); } +.pkt-avatar__glasses::after { content: ""; position: absolute; top: 40%; width: 18%; height: 12%; border: 2px solid rgba(53, 53, 53, 0.78); background: rgba(255,255,255,0.18); } .pkt-avatar__glasses::before { left: 28%; border-radius: 999px; } -.pkt-avatar__glasses::after { right: 28%; border-radius: 999px; box-shadow: -24px 0 0 -22px rgba(53,53,53,0.75); } +.pkt-avatar__glasses::after { right: 28%; border-radius: 999px; box-shadow: calc(var(--avatar-size) * -0.145) 0 0 calc(var(--avatar-size) * -0.132) rgba(53,53,53,0.78); } .pkt-avatar--glasses-round .pkt-avatar__glasses, .pkt-avatar--glasses-square .pkt-avatar__glasses { display: block; } .pkt-avatar--glasses-square .pkt-avatar__glasses::before, diff --git a/public/assets/js/app.js b/public/assets/js/app.js index 38b9f2e..a7d0de5 100755 --- a/public/assets/js/app.js +++ b/public/assets/js/app.js @@ -731,6 +731,7 @@ document.addEventListener('DOMContentLoaded', () => { 'pkt-avatar--hair-', 'pkt-avatar--beard-', 'pkt-avatar--glasses-', + 'pkt-avatar--hat-', ]; const updateAvatarPreview = (form) => { @@ -758,9 +759,12 @@ document.addEventListener('DOMContentLoaded', () => { preview.classList.add(`pkt-avatar--hair-${readValue('avatar_hair_style')}`); preview.classList.add(`pkt-avatar--beard-${readValue('avatar_beard_style')}`); preview.classList.add(`pkt-avatar--glasses-${readValue('avatar_glasses_style')}`); + preview.classList.add(`pkt-avatar--hat-${readValue('avatar_hat_style')}`); preview.style.setProperty('--avatar-skin', readColor('avatar_skin_tone')); preview.style.setProperty('--avatar-hair', readColor('avatar_hair_color')); preview.style.setProperty('--avatar-eye', readColor('avatar_eye_color')); + preview.style.setProperty('--avatar-hat', readColor('avatar_hat_color')); + preview.style.setProperty('--avatar-outfit', readColor('avatar_outfit_color')); }; document.querySelectorAll('[data-avatar-builder]').forEach((form) => { diff --git a/schema.sql b/schema.sql index 6ec7e95..8a9f73f 100755 --- a/schema.sql +++ b/schema.sql @@ -37,6 +37,9 @@ CREATE TABLE user_profiles ( avatar_beard_style VARCHAR(24) NOT NULL DEFAULT 'none', avatar_glasses_style VARCHAR(24) NOT NULL DEFAULT 'none', avatar_head_shape VARCHAR(24) NOT NULL DEFAULT 'round', + avatar_hat_style VARCHAR(24) NOT NULL DEFAULT 'none', + avatar_hat_color VARCHAR(24) NOT NULL DEFAULT 'navy', + avatar_outfit_color VARCHAR(24) NOT NULL DEFAULT 'slate', contact_phone VARBINARY(512) NULL, contact_email VARBINARY(512) NULL, profession VARBINARY(512) NULL, diff --git a/src/App/AccountPages.php b/src/App/AccountPages.php index a81128e..2393b44 100755 --- a/src/App/AccountPages.php +++ b/src/App/AccountPages.php @@ -345,11 +345,14 @@ final class AccountPages 'avatar_beard_style' => 'none', 'avatar_glasses_style' => 'none', 'avatar_head_shape' => 'round', + 'avatar_hat_style' => 'none', + 'avatar_hat_color' => 'navy', + 'avatar_outfit_color' => 'slate', ]; if ($profileSettings) { $profileSettings->ensureSchema(); } - $stmt = $pdo?->prepare('SELECT u.email, u.status, p.display_name, p.first_name, p.last_name, p.zip, p.city, p.profession, p.languages, p.about, p.contact_phone, p.location_tracking_preference, p.avatar_skin_tone, p.avatar_hair_style, p.avatar_hair_color, p.avatar_eye_color, p.avatar_beard_style, p.avatar_glasses_style, p.avatar_head_shape FROM users u LEFT JOIN user_profiles p ON p.user_id = u.id WHERE u.id = :id LIMIT 1'); + $stmt = $pdo?->prepare('SELECT u.email, u.status, p.display_name, p.first_name, p.last_name, p.zip, p.city, p.profession, p.languages, p.about, p.contact_phone, p.location_tracking_preference, p.avatar_skin_tone, p.avatar_hair_style, p.avatar_hair_color, p.avatar_eye_color, p.avatar_beard_style, p.avatar_glasses_style, p.avatar_head_shape, p.avatar_hat_style, p.avatar_hat_color, p.avatar_outfit_color FROM users u LEFT JOIN user_profiles p ON p.user_id = u.id WHERE u.id = :id LIMIT 1'); $stmt?->execute(['id' => $userId]); $row = $stmt?->fetch(\PDO::FETCH_ASSOC); if ($row) { diff --git a/src/App/Avatar.php b/src/App/Avatar.php index 38bff51..1928762 100644 --- a/src/App/Avatar.php +++ b/src/App/Avatar.php @@ -15,6 +15,9 @@ final class Avatar 'avatar_beard_style' => 'none', 'avatar_glasses_style' => 'none', 'avatar_head_shape' => 'round', + 'avatar_hat_style' => 'none', + 'avatar_hat_color' => 'navy', + 'avatar_outfit_color' => 'slate', ]; } @@ -30,8 +33,10 @@ final class Avatar ], 'avatar_hair_style' => [ 'short' => 'Kurz', + 'fade' => 'Fade', 'parted' => 'Seitlich', 'curly' => 'Lockig', + 'waves' => 'Wellen', 'bald' => 'Kahl', ], 'avatar_hair_color' => [ @@ -63,6 +68,25 @@ final class Avatar 'oval' => 'Oval', 'square' => 'Eckig', ], + 'avatar_hat_style' => [ + 'none' => 'Ohne', + 'beanie' => 'Beanie', + 'cap' => 'Cap', + ], + 'avatar_hat_color' => [ + 'navy' => ['label' => 'Navy', 'color' => '#38475f'], + 'charcoal' => ['label' => 'Anthrazit', 'color' => '#3c3f45'], + 'forest' => ['label' => 'Waldgrün', 'color' => '#48614d'], + 'brick' => ['label' => 'Ziegel', 'color' => '#975244'], + 'sand' => ['label' => 'Sand', 'color' => '#b79e79'], + ], + 'avatar_outfit_color' => [ + 'slate' => ['label' => 'Schiefer', 'color' => '#5f7387'], + 'graphite' => ['label' => 'Graphit', 'color' => '#4c515a'], + 'olive' => ['label' => 'Oliv', 'color' => '#667155'], + 'rust' => ['label' => 'Rost', 'color' => '#9f6547'], + 'ocean' => ['label' => 'Ozean', 'color' => '#4e7897'], + ], ]; } @@ -100,6 +124,8 @@ final class Avatar $skin = $options['avatar_skin_tone'][$avatar['avatar_skin_tone']]['color']; $hair = $options['avatar_hair_color'][$avatar['avatar_hair_color']]['color']; $eyes = $options['avatar_eye_color'][$avatar['avatar_eye_color']]['color']; + $hat = $options['avatar_hat_color'][$avatar['avatar_hat_color']]['color']; + $outfit = $options['avatar_outfit_color'][$avatar['avatar_outfit_color']]['color']; $classes = [ 'pkt-avatar', @@ -108,13 +134,16 @@ final class Avatar 'pkt-avatar--hair-' . $avatar['avatar_hair_style'], 'pkt-avatar--beard-' . $avatar['avatar_beard_style'], 'pkt-avatar--glasses-' . $avatar['avatar_glasses_style'], + 'pkt-avatar--hat-' . $avatar['avatar_hat_style'], ]; $style = sprintf( - '--avatar-skin:%s;--avatar-hair:%s;--avatar-eye:%s;', + '--avatar-skin:%s;--avatar-hair:%s;--avatar-eye:%s;--avatar-hat:%s;--avatar-outfit:%s;', $skin, $hair, - $eyes + $eyes, + $hat, + $outfit ); $label = htmlspecialchars('Avatar von ' . $name, ENT_QUOTES); @@ -122,13 +151,19 @@ final class Avatar return sprintf( '' . '' . - '' . + '' . + '' . '' . '' . '' . + '' . + '' . '' . + '' . '' . '' . + '' . + '' . '' . '' . '' . diff --git a/src/App/Community.php b/src/App/Community.php index ade3fc4..49c7019 100755 --- a/src/App/Community.php +++ b/src/App/Community.php @@ -150,7 +150,7 @@ final class Community $where = $conditions ? ('AND ' . implode(' AND ', $conditions)) : ''; - $avatarSelect = $this->hasColumn('user_profiles', 'avatar_skin_tone') + $avatarSelect = $this->hasColumn('user_profiles', 'avatar_outfit_color') ? ', ' . Avatar::selectColumns('p') : ''; $sql = "SELECT ft.id, ft.title, ft.body, ft.created_at, ft.updated_at, @@ -206,7 +206,7 @@ final class Community public function getThread(int $id): ?array { $select = 'ft.*, p.display_name'; - if ($this->hasColumn('user_profiles', 'avatar_skin_tone')) { + if ($this->hasColumn('user_profiles', 'avatar_outfit_color')) { $select .= ', ' . Avatar::selectColumns('p'); } $join = ''; @@ -226,7 +226,7 @@ final class Community public function listPosts(int $threadId): array { $select = 'fp.*, p.display_name'; - if ($this->hasColumn('user_profiles', 'avatar_skin_tone')) { + if ($this->hasColumn('user_profiles', 'avatar_outfit_color')) { $select .= ', ' . Avatar::selectColumns('p'); } if ($this->hasColumn('forum_posts', 'highlighted_at')) { diff --git a/src/App/ProfileSettings.php b/src/App/ProfileSettings.php index 9fb4c4e..06b78b6 100644 --- a/src/App/ProfileSettings.php +++ b/src/App/ProfileSettings.php @@ -36,6 +36,9 @@ final class ProfileSettings 'avatar_beard_style' => "ALTER TABLE user_profiles ADD COLUMN avatar_beard_style VARCHAR(24) NOT NULL DEFAULT 'none' AFTER avatar_eye_color", 'avatar_glasses_style' => "ALTER TABLE user_profiles ADD COLUMN avatar_glasses_style VARCHAR(24) NOT NULL DEFAULT 'none' AFTER avatar_beard_style", 'avatar_head_shape' => "ALTER TABLE user_profiles ADD COLUMN avatar_head_shape VARCHAR(24) NOT NULL DEFAULT 'round' AFTER avatar_glasses_style", + 'avatar_hat_style' => "ALTER TABLE user_profiles ADD COLUMN avatar_hat_style VARCHAR(24) NOT NULL DEFAULT 'none' AFTER avatar_head_shape", + 'avatar_hat_color' => "ALTER TABLE user_profiles ADD COLUMN avatar_hat_color VARCHAR(24) NOT NULL DEFAULT 'navy' AFTER avatar_hat_style", + 'avatar_outfit_color' => "ALTER TABLE user_profiles ADD COLUMN avatar_outfit_color VARCHAR(24) NOT NULL DEFAULT 'slate' AFTER avatar_hat_color", ]; foreach ($avatarColumns as $column => $sql) { if (!$this->hasColumn('user_profiles', $column)) { @@ -92,6 +95,9 @@ final class ProfileSettings avatar_beard_style = :beardStyle, avatar_glasses_style = :glassesStyle, avatar_head_shape = :headShape, + avatar_hat_style = :hatStyle, + avatar_hat_color = :hatColor, + avatar_outfit_color = :outfitColor, updated_at = NOW() WHERE user_id = :id' ); @@ -103,6 +109,9 @@ final class ProfileSettings 'beardStyle' => $avatar['avatar_beard_style'], 'glassesStyle' => $avatar['avatar_glasses_style'], 'headShape' => $avatar['avatar_head_shape'], + 'hatStyle' => $avatar['avatar_hat_style'], + 'hatColor' => $avatar['avatar_hat_color'], + 'outfitColor' => $avatar['avatar_outfit_color'], 'id' => $userId, ]); }