ycxc
All checks were successful
Deploy / deploy (push) Successful in 51s

This commit is contained in:
2026-07-24 21:00:47 +02:00
parent d507ae7bc7
commit 5256dd4080
14 changed files with 471 additions and 19 deletions

View File

@@ -61,6 +61,16 @@ $sectionLinks = [
<p class="muted">Hier pflegst du deine persönlichen Angaben für den Mitgliederbereich.</p>
</div>
<div class="account-panel__body">
<div class="profile-avatar-panel">
<div class="profile-avatar-panel__preview">
<?= \App\Avatar::render($profile, $profile['display_name'] ?: 'Papa', 'xl') ?>
</div>
<div class="profile-avatar-panel__content">
<h3>Dein Profilbild</h3>
<p class="muted">Stell dir mit wenigen Klicks ein wiedererkennbares Community-Bild zusammen. Du kannst Haut, Haare, Augen, Bart, Brille und Kopfform direkt anpassen.</p>
<button class="btn" type="button" data-modal-open="modalAvatar">Profilbild gestalten</button>
</div>
</div>
<dl class="account-kv">
<div><dt>Name</dt><dd><?= htmlspecialchars(trim($profile['first_name'] . ' ' . $profile['last_name']), ENT_QUOTES) ?></dd></div>
<div><dt>Anzeigename</dt><dd><?= htmlspecialchars($profile['display_name'], ENT_QUOTES) ?></dd></div>
@@ -279,6 +289,103 @@ $sectionLinks = [
</div>
<!-- Modals -->
<div class="modal" id="modalAvatar">
<div class="panel">
<div class="head flex between center-y">
<h3>Profilbild gestalten</h3>
<button class="btn ghost" type="button" data-modal-close>✕</button>
</div>
<form method="post" class="stack gap-12 avatar-builder" data-avatar-builder style="margin-top:12px;">
<input type="hidden" name="action" value="avatar_update">
<div class="avatar-builder__layout">
<div class="avatar-builder__preview">
<div data-avatar-preview>
<?= \App\Avatar::render($profile, $profile['display_name'] ?: 'Papa', 'xxl') ?>
</div>
<p class="muted small">Die Vorschau wird sofort aktualisiert.</p>
</div>
<div class="avatar-builder__controls">
<div class="stack gap-6">
<label class="label" for="avatarHeadShape">Kopfform</label>
<select id="avatarHeadShape" name="avatar_head_shape" class="select" data-avatar-field="avatar_head_shape">
<?php foreach ($avatarOptions['avatar_head_shape'] as $value => $label): ?>
<option value="<?= htmlspecialchars((string)$value, ENT_QUOTES) ?>" <?= $profile['avatar_head_shape'] === $value ? 'selected' : '' ?>><?= htmlspecialchars((string)$label, ENT_QUOTES) ?></option>
<?php endforeach; ?>
</select>
</div>
<div class="stack gap-6">
<label class="label" for="avatarHairStyle">Haarform</label>
<select id="avatarHairStyle" name="avatar_hair_style" class="select" data-avatar-field="avatar_hair_style">
<?php foreach ($avatarOptions['avatar_hair_style'] as $value => $label): ?>
<option value="<?= htmlspecialchars((string)$value, ENT_QUOTES) ?>" <?= $profile['avatar_hair_style'] === $value ? 'selected' : '' ?>><?= htmlspecialchars((string)$label, ENT_QUOTES) ?></option>
<?php endforeach; ?>
</select>
</div>
<div class="stack gap-6">
<label class="label" for="avatarBeardStyle">Bart</label>
<select id="avatarBeardStyle" name="avatar_beard_style" class="select" data-avatar-field="avatar_beard_style">
<?php foreach ($avatarOptions['avatar_beard_style'] as $value => $label): ?>
<option value="<?= htmlspecialchars((string)$value, ENT_QUOTES) ?>" <?= $profile['avatar_beard_style'] === $value ? 'selected' : '' ?>><?= htmlspecialchars((string)$label, ENT_QUOTES) ?></option>
<?php endforeach; ?>
</select>
</div>
<div class="stack gap-6">
<label class="label" for="avatarGlassesStyle">Brille</label>
<select id="avatarGlassesStyle" name="avatar_glasses_style" class="select" data-avatar-field="avatar_glasses_style">
<?php foreach ($avatarOptions['avatar_glasses_style'] as $value => $label): ?>
<option value="<?= htmlspecialchars((string)$value, ENT_QUOTES) ?>" <?= $profile['avatar_glasses_style'] === $value ? 'selected' : '' ?>><?= htmlspecialchars((string)$label, ENT_QUOTES) ?></option>
<?php endforeach; ?>
</select>
</div>
<div class="stack gap-6">
<span class="label">Hautfarbe</span>
<div class="avatar-swatch-group">
<?php foreach ($avatarOptions['avatar_skin_tone'] as $value => $meta): ?>
<label class="avatar-swatch">
<input type="radio" name="avatar_skin_tone" value="<?= htmlspecialchars((string)$value, ENT_QUOTES) ?>" data-avatar-field="avatar_skin_tone" data-avatar-color="<?= htmlspecialchars((string)$meta['color'], ENT_QUOTES) ?>" <?= $profile['avatar_skin_tone'] === $value ? 'checked' : '' ?>>
<span class="avatar-swatch__color" style="--swatch-color: <?= htmlspecialchars((string)$meta['color'], ENT_QUOTES) ?>;"></span>
<span><?= htmlspecialchars((string)$meta['label'], ENT_QUOTES) ?></span>
</label>
<?php endforeach; ?>
</div>
</div>
<div class="stack gap-6">
<span class="label">Haarfarbe</span>
<div class="avatar-swatch-group">
<?php foreach ($avatarOptions['avatar_hair_color'] as $value => $meta): ?>
<label class="avatar-swatch">
<input type="radio" name="avatar_hair_color" value="<?= htmlspecialchars((string)$value, ENT_QUOTES) ?>" data-avatar-field="avatar_hair_color" data-avatar-color="<?= htmlspecialchars((string)$meta['color'], ENT_QUOTES) ?>" <?= $profile['avatar_hair_color'] === $value ? 'checked' : '' ?>>
<span class="avatar-swatch__color" style="--swatch-color: <?= htmlspecialchars((string)$meta['color'], ENT_QUOTES) ?>;"></span>
<span><?= htmlspecialchars((string)$meta['label'], ENT_QUOTES) ?></span>
</label>
<?php endforeach; ?>
</div>
</div>
<div class="stack gap-6">
<span class="label">Augenfarbe</span>
<div class="avatar-swatch-group">
<?php foreach ($avatarOptions['avatar_eye_color'] as $value => $meta): ?>
<label class="avatar-swatch">
<input type="radio" name="avatar_eye_color" value="<?= htmlspecialchars((string)$value, ENT_QUOTES) ?>" data-avatar-field="avatar_eye_color" data-avatar-color="<?= htmlspecialchars((string)$meta['color'], ENT_QUOTES) ?>" <?= $profile['avatar_eye_color'] === $value ? 'checked' : '' ?>>
<span class="avatar-swatch__color" style="--swatch-color: <?= htmlspecialchars((string)$meta['color'], ENT_QUOTES) ?>;"></span>
<span><?= htmlspecialchars((string)$meta['label'], ENT_QUOTES) ?></span>
</label>
<?php endforeach; ?>
</div>
</div>
</div>
</div>
<div class="flex gap-12">
<button class="btn ghost" type="button" data-modal-close>Abbrechen</button>
<button class="btn" type="submit">Profilbild speichern</button>
</div>
</form>
</div>
</div>
<div class="modal" id="modalProfile">
<div class="panel">
<div class="head flex between center-y">

View File

@@ -115,7 +115,7 @@ $threads = $community
?>
<article class="forum-row">
<div class="forum-row__topic">
<div class="forum-row__icon" aria-hidden="true">💬</div>
<div class="forum-row__avatar"><?= \App\Avatar::render($t, (string)($t['display_name'] ?: 'Mitglied'), 'forum-list') ?></div>
<div>
<h3><a href="/community_thread?id=<?= (int)$t['id'] ?>"><?= htmlspecialchars((string)$t['title'], ENT_QUOTES) ?></a></h3>
<div class="forum-row__meta">
@@ -206,7 +206,7 @@ $threads = $community
?>
<article class="forum-row">
<div class="forum-row__topic">
<div class="forum-row__icon" aria-hidden="true">💬</div>
<div class="forum-row__avatar"><?= \App\Avatar::render($t, (string)($t['display_name'] ?: 'Mitglied'), 'forum-list') ?></div>
<div>
<h3><a href="/community_thread?id=<?= (int)$t['id'] ?>"><?= htmlspecialchars((string)$t['title'], ENT_QUOTES) ?></a></h3>
<div class="forum-row__meta">

View File

@@ -154,7 +154,7 @@ $userVotes = ($access && $userId) ? $access->getUserPostVotes((int)$userId, $pos
<article class="forum-post forum-post--lead">
<aside class="forum-post__author">
<div class="forum-post__avatar" aria-hidden="true"><?= strtoupper(mb_substr($threadAuthor, 0, 1)) ?></div>
<div class="forum-post__avatar"><?= \App\Avatar::render($thread, $threadAuthor, 'forum') ?></div>
<strong><?= htmlspecialchars($threadAuthor, ENT_QUOTES) ?></strong>
<span><?= htmlspecialchars(trim((string)($threadLevel['icon'] ?? '') . ' ' . (string)($threadLevel['label'] ?? '')), ENT_QUOTES) ?></span>
<span><?= number_format($threadPoints, 1) ?> Punkte</span>
@@ -203,7 +203,7 @@ $userVotes = ($access && $userId) ? $access->getUserPostVotes((int)$userId, $pos
?>
<article class="forum-post<?= $isHighlighted ? ' forum-post--highlighted' : '' ?>">
<aside class="forum-post__author">
<div class="forum-post__avatar" aria-hidden="true"><?= strtoupper(mb_substr($postAuthor, 0, 1)) ?></div>
<div class="forum-post__avatar"><?= \App\Avatar::render($p, $postAuthor, 'forum') ?></div>
<strong><?= htmlspecialchars($postAuthor, ENT_QUOTES) ?></strong>
<?php if ($isHighlighted): ?>
<span class="forum-highlight-badge">Hilfreich hervorgehoben</span>

View File

@@ -9,14 +9,12 @@ if ($isLoggedIn) {
try {
$pdo = $app->pdo();
if ($pdo) {
$stmt = $pdo->prepare('SELECT display_name, first_name FROM user_profiles WHERE user_id = :id LIMIT 1');
$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->execute(['id' => (int)$_SESSION['user_id']]);
$profileRow = $stmt->fetch(PDO::FETCH_ASSOC) ?: [];
$displayName = trim((string)($profileRow['display_name'] ?? '')) ?: trim((string)($profileRow['first_name'] ?? '')) ?: 'Profil';
$firstChar = mb_substr($displayName, 0, 1);
if ($firstChar !== '') {
$profileInitial = mb_strtoupper($firstChar);
}
$communityCfg = dirname(__DIR__, 2) . '/config/community.php';
if (file_exists($communityCfg)) {
@@ -50,7 +48,7 @@ if ($isLoggedIn) {
<?php if ($isLoggedIn): ?>
<div class="profile-menu" data-profile-menu>
<button class="profile-menu__trigger" type="button" aria-haspopup="menu" aria-expanded="false" data-profile-menu-trigger>
<span class="profile-menu__avatar" aria-hidden="true"><?= htmlspecialchars($profileInitial, ENT_QUOTES) ?></span>
<span class="profile-menu__avatar" aria-hidden="true"><?= \App\Avatar::render($profileRow ?? [], $displayName, 'nav') ?></span>
<span class="profile-menu__name"><?= htmlspecialchars($displayName, ENT_QUOTES) ?></span>
</button>
<div class="profile-menu__dropdown" role="menu">