ycydcsd
All checks were successful
Deploy / deploy (push) Successful in 1m0s

This commit is contained in:
2026-07-29 21:09:51 +02:00
parent 37bea188d5
commit ae34348b29
3 changed files with 44 additions and 31 deletions

View File

@@ -294,40 +294,46 @@ $sectionLinks = [
<!-- Modals -->
<div class="modal" id="modalAvatar">
<div class="panel">
<div class="panel panel--avatar">
<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;">
<form method="post" class="avatar-builder" data-avatar-builder style="margin-top:12px;">
<input type="hidden" name="action" value="avatar_update">
<?php if (count($avatarStyleKeys) <= 1): ?>
<input type="hidden" name="avatar_style" value="<?= htmlspecialchars($currentAvatarStyle, ENT_QUOTES) ?>" data-avatar-style-select>
<?php endif; ?>
<input type="hidden" name="avatar_seed" value="<?= htmlspecialchars((string)$profile['avatar_seed'], ENT_QUOTES) ?>" data-avatar-seed>
<div class="avatar-builder__layout">
<div class="avatar-builder__preview">
<div data-avatar-preview>
<?= \App\Avatar\AvatarManager::render($profile, $profile['display_name'] ?: 'Papa', 'xxl', $dashboardUserId) ?>
</div>
<p class="muted small">Wähle eine Komponente und passe dein neues Profilbild Schritt für Schritt an. Gespeichert wird erst nach Klick auf den Button unten.</p>
<?php if (count($avatarStyleKeys) > 1): ?>
<div class="stack gap-6" style="max-width:320px; margin:0 auto;">
<label class="label" for="avatarStyleSelect">Stil</label>
<select id="avatarStyleSelect" name="avatar_style" class="select" data-avatar-style-select>
<?php foreach ($avatarBuilder as $styleKey => $styleMeta): ?>
<option value="<?= htmlspecialchars($styleKey, ENT_QUOTES) ?>" <?= $styleKey === $currentAvatarStyle ? 'selected' : '' ?>>
<?= htmlspecialchars((string)$styleMeta['label'], ENT_QUOTES) ?>
</option>
<?php endforeach; ?>
</select>
<div class="avatar-builder__shell">
<div class="avatar-builder__top">
<div class="avatar-builder__hero">
<div class="avatar-builder__hero-preview" data-avatar-preview>
<?= \App\Avatar\AvatarManager::render($profile, $profile['display_name'] ?: 'Papa', 'xxl', $dashboardUserId) ?>
</div>
<div class="avatar-builder__hero-content">
<p class="muted small">Wähle eine Komponente und passe dein neues Profilbild Schritt für Schritt an. Gespeichert wird erst nach Klick auf den Button unten.</p>
<?php if (count($avatarStyleKeys) > 1): ?>
<div class="stack gap-6" style="max-width:320px;">
<label class="label" for="avatarStyleSelect">Stil</label>
<select id="avatarStyleSelect" name="avatar_style" class="select" data-avatar-style-select>
<?php foreach ($avatarBuilder as $styleKey => $styleMeta): ?>
<option value="<?= htmlspecialchars($styleKey, ENT_QUOTES) ?>" <?= $styleKey === $currentAvatarStyle ? 'selected' : '' ?>>
<?= htmlspecialchars((string)$styleMeta['label'], ENT_QUOTES) ?>
</option>
<?php endforeach; ?>
</select>
</div>
<?php endif; ?>
<div class="flex gap-12 avatar-builder__actions" style="flex-wrap:wrap;">
<button class="btn ghost" type="button" data-avatar-random>Random Profilbild</button>
<button class="btn ghost" type="button" data-modal-close>Abbrechen</button>
<button class="btn" type="submit">Profilbild speichern</button>
</div>
</div>
<?php endif; ?>
<div class="flex gap-12" style="flex-wrap:wrap; justify-content:center;">
<button class="btn ghost" type="button" data-avatar-random>Random Profilbild</button>
<button class="btn ghost" type="button" data-modal-close>Abbrechen</button>
<button class="btn" type="submit">Profilbild speichern</button>
</div>
</div>
<div class="avatar-builder__body">
<?php foreach ($avatarBuilder as $styleKey => $styleMeta): ?>
<?php $styleComponents = $styleMeta['components'] ?? []; ?>
<div class="avatar-style-group <?= $styleKey === $currentAvatarStyle ? 'is-active' : '' ?>" data-avatar-style-panel="<?= htmlspecialchars($styleKey, ENT_QUOTES) ?>" <?= $styleKey === $currentAvatarStyle ? '' : 'hidden' ?>>

View File

@@ -247,8 +247,14 @@ body {
.profile-avatar-panel__preview { display: flex; justify-content: center; }
.profile-avatar-panel__content h3 { margin: 0 0 8px; }
.profile-avatar-panel__content p { margin: 0 0 14px; }
.avatar-builder__layout { display: grid; grid-template-columns: 1fr; gap: 20px; align-items: start; }
.avatar-builder__preview { display: grid; justify-items: center; gap: 14px; padding: 18px; background: linear-gradient(180deg, #fffdfa, #f8f4ec); border: 1px solid var(--color-border); border-radius: 18px; }
.avatar-builder { height: 100%; min-height: 0; }
.avatar-builder__shell { height: 100%; min-height: 0; display: grid; grid-template-rows: auto minmax(0, 1fr); gap: 18px; }
.avatar-builder__top { display: grid; gap: 16px; padding: 18px; background: linear-gradient(180deg, #fffdfa, #f8f4ec); border: 1px solid var(--color-border); border-radius: 18px; }
.avatar-builder__hero { display: grid; grid-template-columns: 180px minmax(0, 1fr); gap: 22px; align-items: center; }
.avatar-builder__hero-preview { display: flex; align-items: center; justify-content: center; }
.avatar-builder__hero-content { display: grid; gap: 14px; min-width: 0; }
.avatar-builder__actions { justify-content: flex-start; }
.avatar-builder__body { min-height: 0; overflow: auto; padding-right: 6px; display: grid; gap: 16px; }
.avatar-component-nav { display: flex; flex-wrap: wrap; gap: 8px; justify-content: center; }
.avatar-component-nav__button { border: 1px solid var(--color-border); background: #fff; color: var(--color-text); border-radius: 999px; padding: 8px 12px; font: inherit; font-size: 13px; font-weight: 700; cursor: pointer; }
.avatar-component-nav__button.is-active { background: var(--color-primary); color: var(--color-primary-contrast); border-color: var(--color-primary); }
@@ -260,7 +266,7 @@ body {
.avatar-component-panel { display: none; width: 100%; gap: 10px; }
.avatar-component-panel.is-active { display: grid; }
.avatar-component-panel__head { display: flex; justify-content: space-between; gap: 10px; align-items: baseline; }
.avatar-component-grid { display: grid; grid-template-columns: repeat(4, minmax(0, 1fr)); gap: 12px; max-height: 420px; overflow: auto; padding-right: 4px; }
.avatar-component-grid { display: grid; grid-template-columns: repeat(4, minmax(0, 1fr)); gap: 12px; }
.avatar-component-card { position: relative; display: grid; gap: 6px; padding: 8px; background: #fff; border: 1px solid var(--color-border); border-radius: 16px; cursor: pointer; }
.avatar-component-card input { position: absolute; opacity: 0; pointer-events: none; }
.avatar-component-card__thumb { display: block; aspect-ratio: 1; border-radius: 14px; overflow: hidden; background: linear-gradient(180deg, #f7efe6, #f0e4d7); border: 1px solid rgba(74, 52, 38, 0.08); }
@@ -268,7 +274,7 @@ body {
.avatar-component-card__label { font-size: 12px; font-weight: 700; color: var(--color-text); text-align: center; }
.avatar-component-card:has(input:checked) { border-color: var(--color-primary); box-shadow: 0 0 0 3px rgba(52,72,90,0.14); }
@media (max-width: 980px){ .avatar-component-grid { grid-template-columns: repeat(3, minmax(0, 1fr)); } }
@media (max-width: 860px){ .avatar-component-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); } }
@media (max-width: 860px){ .avatar-builder__hero { grid-template-columns: 1fr; } .avatar-builder__actions { justify-content: center; } .avatar-component-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); } }
.avatar-swatch-group { display: grid; grid-template-columns: repeat(auto-fit, minmax(120px, 1fr)); gap: 10px; }
.avatar-swatch { display: grid; grid-template-columns: 20px 18px minmax(0, 1fr); gap: 10px; align-items: center; padding: 10px 12px; border: 1px solid var(--color-border); border-radius: 14px; background: #fff; cursor: pointer; }
.avatar-swatch__color { width: 18px; height: 18px; border-radius: 999px; background: var(--swatch-color); border: 1px solid rgba(0,0,0,0.12); }
@@ -349,9 +355,7 @@ body {
.forum-row__activity { grid-auto-flow: column; justify-content: start; gap: 10px; align-items: baseline; }
.forum-post { grid-template-columns: 1fr; }
.forum-post__author { border-right: 0; border-bottom: 1px solid var(--color-border); }
.profile-avatar-panel,
.avatar-builder__layout { grid-template-columns: 1fr; }
.avatar-builder__preview { position: static; }
.profile-avatar-panel { grid-template-columns: 1fr; }
}
@media (max-width: 720px){
@@ -389,10 +393,10 @@ body {
.form-grid.single { grid-template-columns: 1fr; }
@media (max-width: 900px){ .auth-grid { grid-template-columns: 1fr; } .form-grid { grid-template-columns: 1fr; } }
.modal .panel { max-height: 90vh; overflow: auto; }
.modal { position: fixed; inset: 0; display: none; align-items: flex-start; justify-content: center; padding: 70px 16px 24px; background: rgba(0,0,0,0.4); z-index: 200; overflow: auto; }
.modal.open { display: flex; }
.modal .panel { background:#fff; border-radius: var(--radius-md); padding: 16px; box-shadow: 0 18px 50px rgba(0,0,0,0.25); width: min(960px, 100%); }
.modal .panel--avatar { width: min(1120px, 100%); height: calc(100vh - 96px); max-height: calc(100vh - 96px); overflow: hidden; display: grid; grid-template-rows: auto minmax(0, 1fr); }
.dash-grid { display:grid; grid-template-columns: repeat(3, minmax(0,1fr)); gap: 16px; }
.dash-grid-2 { display:grid; grid-template-columns: repeat(2, minmax(0,1fr)); gap: 16px; }

View File

@@ -54,6 +54,7 @@ final class AvatarManager
'label' => (string)($styles[$key]['label'] ?? $class::label()),
'class' => $class,
'fixed_colors' => is_array($styles[$key]['fixed_colors'] ?? null) ? $styles[$key]['fixed_colors'] : [],
'color_groups' => is_array($styles[$key]['color_groups'] ?? null) ? $styles[$key]['color_groups'] : [],
'color_controls' => is_array($styles[$key]['color_controls'] ?? null) ? $styles[$key]['color_controls'] : [],
];
}
@@ -64,6 +65,7 @@ final class AvatarManager
'label' => Lorelei::label(),
'class' => Lorelei::class,
'fixed_colors' => [],
'color_groups' => [],
'color_controls' => [],
];
$result['croodles'] = [
@@ -71,6 +73,7 @@ final class AvatarManager
'label' => Croodles::label(),
'class' => Croodles::class,
'fixed_colors' => [],
'color_groups' => [],
'color_controls' => [],
];
}