This commit is contained in:
@@ -8,7 +8,7 @@ Aktueller Projektstand
|
|||||||
- Der Avatar-Proxy liegt unter `public/api/avatar.php`.
|
- Der Avatar-Proxy liegt unter `public/api/avatar.php`.
|
||||||
- Die visuelle Avatar-Konfiguration liegt im Dashboard-Modal unter `partials/landing/account/dashboard.php` und rendert Komponenten-Vorschauen live nach.
|
- Die visuelle Avatar-Konfiguration liegt im Dashboard-Modal unter `partials/landing/account/dashboard.php` und rendert Komponenten-Vorschauen live nach.
|
||||||
- Der Generator speichert `avatar_style`, `avatar_seed` und `avatar_config_json` generisch im Profil.
|
- Der Generator speichert `avatar_style`, `avatar_seed` und `avatar_config_json` generisch im Profil.
|
||||||
- Der Generator zeigt pro Style auch die von DiceBear unterstützten Farbfelder inklusive Fuellung, Stopps und Winkel und speichert diese im gleichen Config-JSON.
|
- Der Generator zeigt pro Style die von DiceBear unterstützten Farbfelder direkt in den passenden Komponentenabschnitten und speichert diese im gleichen Config-JSON.
|
||||||
- Gespeicherte Benutzer-Avatare werden lokal unter `public/assets/avatars/users/ptk-<userId>.svg` abgelegt und bei Änderungen überschrieben.
|
- Gespeicherte Benutzer-Avatare werden lokal unter `public/assets/avatars/users/ptk-<userId>.svg` abgelegt und bei Änderungen überschrieben.
|
||||||
- Der Generator besitzt einen Button `Random Profilbild`, der Seed und Varianten des aktiven Styles zufällig neu setzt.
|
- Der Generator besitzt einen Button `Random Profilbild`, der Seed und Varianten des aktiven Styles zufällig neu setzt.
|
||||||
- Beim Oeffnen des Modals werden nur die Bilder des aktiven Styles und Tabs geladen; weitere Tabs laden ihre Varianten erst beim Wechsel nach.
|
- Beim Oeffnen des Modals werden nur die Bilder des aktiven Styles und Tabs geladen; weitere Tabs laden ihre Varianten erst beim Wechsel nach.
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ Papa-Kind-Treff ist eine PHP-basierte Plattform für Väter. Kernbereiche sind l
|
|||||||
- Standortfunktionen sind relevant für lokale Treffen und werden künftig weiter ausgebaut.
|
- Standortfunktionen sind relevant für lokale Treffen und werden künftig weiter ausgebaut.
|
||||||
- Profilbilder werden aktuell über die Mehr-Style-Architektur serverseitig erzeugt; aktiv sind derzeit `lorelei` und `croodles`.
|
- Profilbilder werden aktuell über die Mehr-Style-Architektur serverseitig erzeugt; aktiv sind derzeit `lorelei` und `croodles`.
|
||||||
- Stil, Seed und Varianten werden generisch im Profil gespeichert; das fertige SVG wird lokal pro Benutzer unter `ptk-<userId>.svg` abgelegt.
|
- Stil, Seed und Varianten werden generisch im Profil gespeichert; das fertige SVG wird lokal pro Benutzer unter `ptk-<userId>.svg` abgelegt.
|
||||||
- Der Avatar-Editor bietet visuelle Komponenten-Vorschauen statt reiner Text-Dropdowns, laedt nur den aktiven Style/Tab nach und zeigt pro Style die unterstützten Farb-, Fuellungs-, Stopps- und Winkelparameter an.
|
- Der Avatar-Editor bietet visuelle Komponenten-Vorschauen statt reiner Text-Dropdowns, laedt nur den aktiven Style/Tab nach und zeigt die Farbfelder direkt im passenden Komponentenabschnitt an.
|
||||||
|
|
||||||
## Navigation / Konto
|
## Navigation / Konto
|
||||||
- Eingeloggte Nutzer sehen rechts ein Profil-Menü statt einzelner `Dashboard`-/`Logout`-Buttons.
|
- Eingeloggte Nutzer sehen rechts ein Profil-Menü statt einzelner `Dashboard`-/`Logout`-Buttons.
|
||||||
|
|||||||
@@ -19,9 +19,9 @@ Papa-Kind-Treff ist eine PHP-basierte Plattform für Väter mit Fokus auf lokale
|
|||||||
- eigener Einstellungsbereich für Standortfreigabe, Browser-Hinweise und Consent-Verwaltung
|
- eigener Einstellungsbereich für Standortfreigabe, Browser-Hinweise und Consent-Verwaltung
|
||||||
- modernes Avatar-System mit zentraler Mehr-Style-Architektur auf Basis von DiceBear
|
- modernes Avatar-System mit zentraler Mehr-Style-Architektur auf Basis von DiceBear
|
||||||
- aktive Avatar-Styles aktuell: `lorelei` und `croodles`; weitere Styles können über `config/avatar.php` freigeschaltet werden
|
- aktive Avatar-Styles aktuell: `lorelei` und `croodles`; weitere Styles können über `config/avatar.php` freigeschaltet werden
|
||||||
- Profilbild-Generator mit visueller Komponenten-Auswahl, generischem Style-/Seed-/Config-Speichermodell, Style-Wechsel und vollständigen Farb-, Füllungs-, Stopps- und Winkeloptionen pro Style
|
- Profilbild-Generator mit visueller Komponenten-Auswahl, generischem Style-/Seed-/Config-Speichermodell, Style-Wechsel und komponentenbezogenen Farbfeldern pro Style
|
||||||
- jedes gespeicherte Profilbild wird als lokale Benutzer-SVG unter `public/assets/avatars/users/ptk-<userId>.svg` abgelegt
|
- jedes gespeicherte Profilbild wird als lokale Benutzer-SVG unter `public/assets/avatars/users/ptk-<userId>.svg` abgelegt
|
||||||
- `config/avatar.php` enthaelt jetzt pro Style auch explizite Beispielpaletten und Beispielprofile als Einbindungsreferenz
|
- `config/avatar.php` enthaelt jetzt pro Style auch explizite Beispielpaletten, Farbgruppen pro Komponente und Beispielprofile als Einbindungsreferenz
|
||||||
- Consent-Manager für Analyse und externe Dienste
|
- Consent-Manager für Analyse und externe Dienste
|
||||||
- Navigation aktuell mit `Home`, `Suche`, `Community`
|
- Navigation aktuell mit `Home`, `Suche`, `Community`
|
||||||
- eingeloggte Nutzer sehen rechts ein Profil-Menü mit Direktlinks zu `Profil`, `Kinder`, `Termine`, `Community`, `Einstellungen`
|
- eingeloggte Nutzer sehen rechts ein Profil-Menü mit Direktlinks zu `Profil`, `Kinder`, `Termine`, `Community`, `Einstellungen`
|
||||||
|
|||||||
@@ -23,6 +23,15 @@ return [
|
|||||||
'label' => 'Lorelei',
|
'label' => 'Lorelei',
|
||||||
'class' => \App\Avatar\Lorelei::class,
|
'class' => \App\Avatar\Lorelei::class,
|
||||||
'fixed_colors' => [],
|
'fixed_colors' => [],
|
||||||
|
'color_groups' => [
|
||||||
|
'eyesVariant' => ['eyesColor', 'skinColor', 'backgroundColor'],
|
||||||
|
'eyebrowsVariant' => ['eyebrowsColor'],
|
||||||
|
'mouthVariant' => ['mouthColor', 'noseColor', 'frecklesColor'],
|
||||||
|
'glassesVariant' => ['glassesColor'],
|
||||||
|
'hairVariant' => ['hairColor', 'hairAccessoriesColor'],
|
||||||
|
'beardVariant' => [],
|
||||||
|
'earringsVariant' => ['earringsColor'],
|
||||||
|
],
|
||||||
'component_controls' => [
|
'component_controls' => [
|
||||||
'eyesVariant',
|
'eyesVariant',
|
||||||
'eyebrowsVariant',
|
'eyebrowsVariant',
|
||||||
@@ -38,99 +47,66 @@ return [
|
|||||||
'default' => '#d6b36b',
|
'default' => '#d6b36b',
|
||||||
'samples' => ['#d6b36b', '#b08d57', '#d9d9d9', '#8c6c4a'],
|
'samples' => ['#d6b36b', '#b08d57', '#d9d9d9', '#8c6c4a'],
|
||||||
'allow_custom' => true,
|
'allow_custom' => true,
|
||||||
'allow_fill' => true,
|
|
||||||
'allow_fill_stops' => true,
|
|
||||||
'allow_angle' => true,
|
|
||||||
],
|
],
|
||||||
'eyebrowsColor' => [
|
'eyebrowsColor' => [
|
||||||
'label' => 'Augenbrauen',
|
'label' => 'Augenbrauen',
|
||||||
'default' => '#4a3227',
|
'default' => '#4a3227',
|
||||||
'samples' => ['#4a3227', '#6a4938', '#2c221d', '#8a6651'],
|
'samples' => ['#4a3227', '#6a4938', '#2c221d', '#8a6651'],
|
||||||
'allow_custom' => true,
|
'allow_custom' => true,
|
||||||
'allow_fill' => true,
|
|
||||||
'allow_fill_stops' => true,
|
|
||||||
'allow_angle' => true,
|
|
||||||
],
|
],
|
||||||
'eyesColor' => [
|
'eyesColor' => [
|
||||||
'label' => 'Augen',
|
'label' => 'Augen',
|
||||||
'default' => '#2a2522',
|
'default' => '#2a2522',
|
||||||
'samples' => ['#2a2522', '#3f4f66', '#6b4d35', '#1f3a5f'],
|
'samples' => ['#2a2522', '#3f4f66', '#6b4d35', '#1f3a5f'],
|
||||||
'allow_custom' => true,
|
'allow_custom' => true,
|
||||||
'allow_fill' => true,
|
|
||||||
'allow_fill_stops' => true,
|
|
||||||
'allow_angle' => true,
|
|
||||||
],
|
],
|
||||||
'frecklesColor' => [
|
'frecklesColor' => [
|
||||||
'label' => 'Sommersprossen',
|
'label' => 'Sommersprossen',
|
||||||
'default' => '#b67852',
|
'default' => '#b67852',
|
||||||
'samples' => ['#b67852', '#a16645', '#c48a66', '#8f5a3e'],
|
'samples' => ['#b67852', '#a16645', '#c48a66', '#8f5a3e'],
|
||||||
'allow_custom' => true,
|
'allow_custom' => true,
|
||||||
'allow_fill' => true,
|
|
||||||
'allow_fill_stops' => true,
|
|
||||||
'allow_angle' => true,
|
|
||||||
],
|
],
|
||||||
'glassesColor' => [
|
'glassesColor' => [
|
||||||
'label' => 'Brille',
|
'label' => 'Brille',
|
||||||
'default' => '#26313f',
|
'default' => '#26313f',
|
||||||
'samples' => ['#26313f', '#4d5f73', '#1b1f27', '#7a5d42'],
|
'samples' => ['#26313f', '#4d5f73', '#1b1f27', '#7a5d42'],
|
||||||
'allow_custom' => true,
|
'allow_custom' => true,
|
||||||
'allow_fill' => true,
|
|
||||||
'allow_fill_stops' => true,
|
|
||||||
'allow_angle' => true,
|
|
||||||
],
|
],
|
||||||
'hairColor' => [
|
'hairColor' => [
|
||||||
'label' => 'Haare',
|
'label' => 'Haare',
|
||||||
'default' => '#4b3128',
|
'default' => '#4b3128',
|
||||||
'samples' => ['#4b3128', '#6f4b38', '#221916', '#b38a52'],
|
'samples' => ['#4b3128', '#6f4b38', '#221916', '#b38a52'],
|
||||||
'allow_custom' => true,
|
'allow_custom' => true,
|
||||||
'allow_fill' => true,
|
|
||||||
'allow_fill_stops' => true,
|
|
||||||
'allow_angle' => true,
|
|
||||||
],
|
],
|
||||||
'hairAccessoriesColor' => [
|
'hairAccessoriesColor' => [
|
||||||
'label' => 'Haar-Accessoires',
|
'label' => 'Haar-Accessoires',
|
||||||
'default' => '#b96f54',
|
'default' => '#b96f54',
|
||||||
'samples' => ['#b96f54', '#c98664', '#8d5a9e', '#5e8aa8'],
|
'samples' => ['#b96f54', '#c98664', '#8d5a9e', '#5e8aa8'],
|
||||||
'allow_custom' => true,
|
'allow_custom' => true,
|
||||||
'allow_fill' => true,
|
|
||||||
'allow_fill_stops' => true,
|
|
||||||
'allow_angle' => true,
|
|
||||||
],
|
],
|
||||||
'mouthColor' => [
|
'mouthColor' => [
|
||||||
'label' => 'Mund',
|
'label' => 'Mund',
|
||||||
'default' => '#9f5a56',
|
'default' => '#9f5a56',
|
||||||
'samples' => ['#9f5a56', '#b66b66', '#804444', '#c98a7a'],
|
'samples' => ['#9f5a56', '#b66b66', '#804444', '#c98a7a'],
|
||||||
'allow_custom' => true,
|
'allow_custom' => true,
|
||||||
'allow_fill' => true,
|
|
||||||
'allow_fill_stops' => true,
|
|
||||||
'allow_angle' => true,
|
|
||||||
],
|
],
|
||||||
'noseColor' => [
|
'noseColor' => [
|
||||||
'label' => 'Nase',
|
'label' => 'Nase',
|
||||||
'default' => '#a56f58',
|
'default' => '#a56f58',
|
||||||
'samples' => ['#a56f58', '#bf8569', '#8d5f49', '#d3a17b'],
|
'samples' => ['#a56f58', '#bf8569', '#8d5f49', '#d3a17b'],
|
||||||
'allow_custom' => true,
|
'allow_custom' => true,
|
||||||
'allow_fill' => true,
|
|
||||||
'allow_fill_stops' => true,
|
|
||||||
'allow_angle' => true,
|
|
||||||
],
|
],
|
||||||
'skinColor' => [
|
'skinColor' => [
|
||||||
'label' => 'Haut',
|
'label' => 'Haut',
|
||||||
'default' => '#e7ba9a',
|
'default' => '#e7ba9a',
|
||||||
'samples' => ['#f4d2ba', '#e7ba9a', '#c98f69', '#8a5638'],
|
'samples' => ['#f4d2ba', '#e7ba9a', '#c98f69', '#8a5638'],
|
||||||
'allow_custom' => true,
|
'allow_custom' => true,
|
||||||
'allow_fill' => true,
|
|
||||||
'allow_fill_stops' => true,
|
|
||||||
'allow_angle' => true,
|
|
||||||
],
|
],
|
||||||
'backgroundColor' => [
|
'backgroundColor' => [
|
||||||
'label' => 'Hintergrund',
|
'label' => 'Hintergrund',
|
||||||
'default' => '#f5efe5',
|
'default' => '#f5efe5',
|
||||||
'samples' => ['#f5efe5', '#e9dfd1', '#d7e6ef', '#efe2d2'],
|
'samples' => ['#f5efe5', '#e9dfd1', '#d7e6ef', '#efe2d2'],
|
||||||
'allow_custom' => true,
|
'allow_custom' => true,
|
||||||
'allow_fill' => true,
|
|
||||||
'allow_fill_stops' => true,
|
|
||||||
'allow_angle' => true,
|
|
||||||
],
|
],
|
||||||
],
|
],
|
||||||
'example_profile' => [
|
'example_profile' => [
|
||||||
@@ -156,6 +132,15 @@ return [
|
|||||||
'label' => 'Croodles',
|
'label' => 'Croodles',
|
||||||
'class' => \App\Avatar\Croodles::class,
|
'class' => \App\Avatar\Croodles::class,
|
||||||
'fixed_colors' => [],
|
'fixed_colors' => [],
|
||||||
|
'color_groups' => [
|
||||||
|
'headVariant' => ['baseColor', 'backgroundColor'],
|
||||||
|
'eyesVariant' => ['eyepatchColor', 'glassesColor'],
|
||||||
|
'noseVariant' => [],
|
||||||
|
'mouthVariant' => [],
|
||||||
|
'topVariant' => ['topColor'],
|
||||||
|
'beardVariant' => [],
|
||||||
|
'mustacheVariant' => [],
|
||||||
|
],
|
||||||
'component_controls' => [
|
'component_controls' => [
|
||||||
'headVariant',
|
'headVariant',
|
||||||
'eyesVariant',
|
'eyesVariant',
|
||||||
@@ -171,45 +156,30 @@ return [
|
|||||||
'default' => '#e7ba9a',
|
'default' => '#e7ba9a',
|
||||||
'samples' => ['#f4d2ba', '#e7ba9a', '#c98f69', '#8a5638'],
|
'samples' => ['#f4d2ba', '#e7ba9a', '#c98f69', '#8a5638'],
|
||||||
'allow_custom' => true,
|
'allow_custom' => true,
|
||||||
'allow_fill' => true,
|
|
||||||
'allow_fill_stops' => true,
|
|
||||||
'allow_angle' => true,
|
|
||||||
],
|
],
|
||||||
'eyepatchColor' => [
|
'eyepatchColor' => [
|
||||||
'label' => 'Augenklappe',
|
'label' => 'Augenklappe',
|
||||||
'default' => '#283341',
|
'default' => '#283341',
|
||||||
'samples' => ['#283341', '#1a222d', '#4a5a6f', '#77583f'],
|
'samples' => ['#283341', '#1a222d', '#4a5a6f', '#77583f'],
|
||||||
'allow_custom' => true,
|
'allow_custom' => true,
|
||||||
'allow_fill' => true,
|
|
||||||
'allow_fill_stops' => true,
|
|
||||||
'allow_angle' => true,
|
|
||||||
],
|
],
|
||||||
'glassesColor' => [
|
'glassesColor' => [
|
||||||
'label' => 'Brille',
|
'label' => 'Brille',
|
||||||
'default' => '#283341',
|
'default' => '#283341',
|
||||||
'samples' => ['#283341', '#4a5a6f', '#1a222d', '#74614a'],
|
'samples' => ['#283341', '#4a5a6f', '#1a222d', '#74614a'],
|
||||||
'allow_custom' => true,
|
'allow_custom' => true,
|
||||||
'allow_fill' => true,
|
|
||||||
'allow_fill_stops' => true,
|
|
||||||
'allow_angle' => true,
|
|
||||||
],
|
],
|
||||||
'topColor' => [
|
'topColor' => [
|
||||||
'label' => 'Top-Farbe',
|
'label' => 'Top-Farbe',
|
||||||
'default' => '#4f6ea8',
|
'default' => '#4f6ea8',
|
||||||
'samples' => ['#4f6ea8', '#6d8fc2', '#36507a', '#6f8c55'],
|
'samples' => ['#4f6ea8', '#6d8fc2', '#36507a', '#6f8c55'],
|
||||||
'allow_custom' => true,
|
'allow_custom' => true,
|
||||||
'allow_fill' => true,
|
|
||||||
'allow_fill_stops' => true,
|
|
||||||
'allow_angle' => true,
|
|
||||||
],
|
],
|
||||||
'backgroundColor' => [
|
'backgroundColor' => [
|
||||||
'label' => 'Hintergrund',
|
'label' => 'Hintergrund',
|
||||||
'default' => '#f2eadf',
|
'default' => '#f2eadf',
|
||||||
'samples' => ['#f2eadf', '#e6dccd', '#d9e8ef', '#efe0d0'],
|
'samples' => ['#f2eadf', '#e6dccd', '#d9e8ef', '#efe0d0'],
|
||||||
'allow_custom' => true,
|
'allow_custom' => true,
|
||||||
'allow_fill' => true,
|
|
||||||
'allow_fill_stops' => true,
|
|
||||||
'allow_angle' => true,
|
|
||||||
],
|
],
|
||||||
],
|
],
|
||||||
'example_profile' => [
|
'example_profile' => [
|
||||||
|
|||||||
@@ -378,110 +378,64 @@ $sectionLinks = [
|
|||||||
</label>
|
</label>
|
||||||
<?php endforeach; ?>
|
<?php endforeach; ?>
|
||||||
</div>
|
</div>
|
||||||
|
<?php $componentColors = $component['colors'] ?? []; ?>
|
||||||
|
<?php if ($componentColors): ?>
|
||||||
|
<div class="form-grid" style="margin-top:18px;">
|
||||||
|
<?php foreach ($componentColors as $colorField => $colorMeta): ?>
|
||||||
|
<?php
|
||||||
|
$colorValue = (string)($colorMeta['value'] ?? '');
|
||||||
|
$colorDefault = (string)($colorMeta['default'] ?? '#c4a484');
|
||||||
|
$samples = is_array($colorMeta['samples'] ?? null) ? $colorMeta['samples'] : [];
|
||||||
|
?>
|
||||||
|
<div class="stack gap-6">
|
||||||
|
<label class="label" for="avatarColor<?= htmlspecialchars($styleKey . $colorField, ENT_QUOTES) ?>"><?= htmlspecialchars((string)($colorMeta['label'] ?? $colorField), ENT_QUOTES) ?></label>
|
||||||
|
<input
|
||||||
|
type="hidden"
|
||||||
|
name="avatar_config[<?= htmlspecialchars($styleKey, ENT_QUOTES) ?>][<?= htmlspecialchars((string)$colorField, ENT_QUOTES) ?>]"
|
||||||
|
value="<?= htmlspecialchars($colorValue, ENT_QUOTES) ?>"
|
||||||
|
data-avatar-style="<?= htmlspecialchars($styleKey, ENT_QUOTES) ?>"
|
||||||
|
data-avatar-color-input="<?= htmlspecialchars((string)$colorField, ENT_QUOTES) ?>"
|
||||||
|
>
|
||||||
|
<div class="flex gap-12 center-y" style="flex-wrap:wrap;">
|
||||||
|
<input
|
||||||
|
id="avatarColor<?= htmlspecialchars($styleKey . $colorField, ENT_QUOTES) ?>"
|
||||||
|
type="color"
|
||||||
|
value="<?= htmlspecialchars($colorValue !== '' ? $colorValue : $colorDefault, ENT_QUOTES) ?>"
|
||||||
|
data-avatar-style="<?= htmlspecialchars($styleKey, ENT_QUOTES) ?>"
|
||||||
|
data-avatar-color-picker="<?= htmlspecialchars((string)$colorField, ENT_QUOTES) ?>"
|
||||||
|
data-avatar-color-default="<?= htmlspecialchars($colorDefault, ENT_QUOTES) ?>"
|
||||||
|
style="width:64px; height:44px; padding:4px;"
|
||||||
|
>
|
||||||
|
<button
|
||||||
|
class="btn ghost"
|
||||||
|
type="button"
|
||||||
|
data-avatar-style="<?= htmlspecialchars($styleKey, ENT_QUOTES) ?>"
|
||||||
|
data-avatar-color-reset="<?= htmlspecialchars((string)$colorField, ENT_QUOTES) ?>"
|
||||||
|
>Standard</button>
|
||||||
|
</div>
|
||||||
|
<?php if ($samples): ?>
|
||||||
|
<div class="flex gap-8 center-y" style="flex-wrap:wrap;">
|
||||||
|
<?php foreach ($samples as $sampleColor): ?>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
class="avatar-color-sample"
|
||||||
|
title="<?= htmlspecialchars((string)$sampleColor, ENT_QUOTES) ?>"
|
||||||
|
aria-label="<?= htmlspecialchars((string)$sampleColor, ENT_QUOTES) ?>"
|
||||||
|
data-avatar-style="<?= htmlspecialchars($styleKey, ENT_QUOTES) ?>"
|
||||||
|
data-avatar-color-sample-field="<?= htmlspecialchars((string)$colorField, ENT_QUOTES) ?>"
|
||||||
|
data-avatar-color-sample-value="<?= htmlspecialchars((string)$sampleColor, ENT_QUOTES) ?>"
|
||||||
|
style="width:32px; height:32px; border-radius:10px; border:1px solid var(--color-border); background: <?= htmlspecialchars((string)$sampleColor, ENT_QUOTES) ?>;"
|
||||||
|
></button>
|
||||||
|
<?php endforeach; ?>
|
||||||
|
</div>
|
||||||
|
<?php endif; ?>
|
||||||
|
</div>
|
||||||
|
<?php endforeach; ?>
|
||||||
|
</div>
|
||||||
|
<?php endif; ?>
|
||||||
</section>
|
</section>
|
||||||
<?php endforeach; ?>
|
<?php endforeach; ?>
|
||||||
</div>
|
</div>
|
||||||
<?php $styleColors = $styleMeta['colors'] ?? []; ?>
|
|
||||||
<?php if ($styleColors): ?>
|
|
||||||
<div class="account-panel" style="margin-top:18px;">
|
|
||||||
<div class="account-panel__head">
|
|
||||||
<h3>Farben</h3>
|
|
||||||
<p class="muted small">Alle aktuell für diesen DiceBear-Style unterstützten Farbfelder.</p>
|
|
||||||
</div>
|
|
||||||
<div class="account-panel__body">
|
|
||||||
<div class="form-grid">
|
|
||||||
<?php foreach ($styleColors as $colorField => $colorMeta): ?>
|
|
||||||
<?php
|
|
||||||
$colorValue = (string)($colorMeta['value'] ?? '');
|
|
||||||
$colorDefault = (string)($colorMeta['default'] ?? '#c4a484');
|
|
||||||
?>
|
|
||||||
<div class="stack gap-6">
|
|
||||||
<label class="label" for="avatarColor<?= htmlspecialchars($styleKey . $colorField, ENT_QUOTES) ?>"><?= htmlspecialchars((string)($colorMeta['label'] ?? $colorField), ENT_QUOTES) ?></label>
|
|
||||||
<input
|
|
||||||
type="hidden"
|
|
||||||
name="avatar_config[<?= htmlspecialchars($styleKey, ENT_QUOTES) ?>][<?= htmlspecialchars((string)$colorField, ENT_QUOTES) ?>]"
|
|
||||||
value="<?= htmlspecialchars($colorValue, ENT_QUOTES) ?>"
|
|
||||||
data-avatar-style="<?= htmlspecialchars($styleKey, ENT_QUOTES) ?>"
|
|
||||||
data-avatar-color-input="<?= htmlspecialchars((string)$colorField, ENT_QUOTES) ?>"
|
|
||||||
>
|
|
||||||
<div class="flex gap-12 center-y" style="flex-wrap:wrap;">
|
|
||||||
<input
|
|
||||||
id="avatarColor<?= htmlspecialchars($styleKey . $colorField, ENT_QUOTES) ?>"
|
|
||||||
type="color"
|
|
||||||
value="<?= htmlspecialchars($colorValue !== '' ? $colorValue : $colorDefault, ENT_QUOTES) ?>"
|
|
||||||
data-avatar-style="<?= htmlspecialchars($styleKey, ENT_QUOTES) ?>"
|
|
||||||
data-avatar-color-picker="<?= htmlspecialchars((string)$colorField, ENT_QUOTES) ?>"
|
|
||||||
data-avatar-color-default="<?= htmlspecialchars($colorDefault, ENT_QUOTES) ?>"
|
|
||||||
style="width:64px; height:44px; padding:4px;"
|
|
||||||
>
|
|
||||||
<button
|
|
||||||
class="btn ghost"
|
|
||||||
type="button"
|
|
||||||
data-avatar-style="<?= htmlspecialchars($styleKey, ENT_QUOTES) ?>"
|
|
||||||
data-avatar-color-reset="<?= htmlspecialchars((string)$colorField, ENT_QUOTES) ?>"
|
|
||||||
>Standard</button>
|
|
||||||
<span class="muted small" data-avatar-color-label="<?= htmlspecialchars($styleKey . ':' . (string)$colorField, ENT_QUOTES) ?>">
|
|
||||||
<?= $colorValue !== '' ? htmlspecialchars($colorValue, ENT_QUOTES) : 'DiceBear-Standard' ?>
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
<div class="form-grid" style="margin-top:6px;">
|
|
||||||
<div class="stack gap-6">
|
|
||||||
<label class="label" for="avatarFill<?= htmlspecialchars($styleKey . $colorField, ENT_QUOTES) ?>">Füllung</label>
|
|
||||||
<select
|
|
||||||
id="avatarFill<?= htmlspecialchars($styleKey . $colorField, ENT_QUOTES) ?>"
|
|
||||||
name="avatar_config[<?= htmlspecialchars($styleKey, ENT_QUOTES) ?>][<?= htmlspecialchars((string)$colorField, ENT_QUOTES) ?>Fill]"
|
|
||||||
class="select"
|
|
||||||
data-avatar-style="<?= htmlspecialchars($styleKey, ENT_QUOTES) ?>"
|
|
||||||
data-avatar-color-meta="<?= htmlspecialchars((string)$colorField, ENT_QUOTES) ?>"
|
|
||||||
data-avatar-color-meta-type="fill"
|
|
||||||
>
|
|
||||||
<option value="" <?= (($colorMeta['fill'] ?? '') === '') ? 'selected' : '' ?>>Standard</option>
|
|
||||||
<option value="solid" <?= (($colorMeta['fill'] ?? '') === 'solid') ? 'selected' : '' ?>>Solid</option>
|
|
||||||
<option value="linear" <?= (($colorMeta['fill'] ?? '') === 'linear') ? 'selected' : '' ?>>Linear</option>
|
|
||||||
<option value="radial" <?= (($colorMeta['fill'] ?? '') === 'radial') ? 'selected' : '' ?>>Radial</option>
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
<div class="stack gap-6">
|
|
||||||
<label class="label" for="avatarStops<?= htmlspecialchars($styleKey . $colorField, ENT_QUOTES) ?>">Farb-Stopps</label>
|
|
||||||
<input
|
|
||||||
id="avatarStops<?= htmlspecialchars($styleKey . $colorField, ENT_QUOTES) ?>"
|
|
||||||
type="number"
|
|
||||||
min="2"
|
|
||||||
max="8"
|
|
||||||
step="1"
|
|
||||||
name="avatar_config[<?= htmlspecialchars($styleKey, ENT_QUOTES) ?>][<?= htmlspecialchars((string)$colorField, ENT_QUOTES) ?>FillStops]"
|
|
||||||
class="input"
|
|
||||||
value="<?= htmlspecialchars((string)($colorMeta['fill_stops'] ?? ''), ENT_QUOTES) ?>"
|
|
||||||
placeholder="Standard"
|
|
||||||
data-avatar-style="<?= htmlspecialchars($styleKey, ENT_QUOTES) ?>"
|
|
||||||
data-avatar-color-meta="<?= htmlspecialchars((string)$colorField, ENT_QUOTES) ?>"
|
|
||||||
data-avatar-color-meta-type="fillStops"
|
|
||||||
>
|
|
||||||
</div>
|
|
||||||
<div class="stack gap-6">
|
|
||||||
<label class="label" for="avatarAngle<?= htmlspecialchars($styleKey . $colorField, ENT_QUOTES) ?>">Winkel</label>
|
|
||||||
<input
|
|
||||||
id="avatarAngle<?= htmlspecialchars($styleKey . $colorField, ENT_QUOTES) ?>"
|
|
||||||
type="number"
|
|
||||||
min="-360"
|
|
||||||
max="360"
|
|
||||||
step="1"
|
|
||||||
name="avatar_config[<?= htmlspecialchars($styleKey, ENT_QUOTES) ?>][<?= htmlspecialchars((string)$colorField, ENT_QUOTES) ?>Angle]"
|
|
||||||
class="input"
|
|
||||||
value="<?= htmlspecialchars((string)($colorMeta['angle'] ?? ''), ENT_QUOTES) ?>"
|
|
||||||
placeholder="Standard"
|
|
||||||
data-avatar-style="<?= htmlspecialchars($styleKey, ENT_QUOTES) ?>"
|
|
||||||
data-avatar-color-meta="<?= htmlspecialchars((string)$colorField, ENT_QUOTES) ?>"
|
|
||||||
data-avatar-color-meta-type="angle"
|
|
||||||
>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<?php endforeach; ?>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<?php endif; ?>
|
|
||||||
</div>
|
</div>
|
||||||
<?php endforeach; ?>
|
<?php endforeach; ?>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -800,13 +800,6 @@ document.addEventListener('DOMContentLoaded', () => {
|
|||||||
return input ? input.value : '';
|
return input ? input.value : '';
|
||||||
};
|
};
|
||||||
|
|
||||||
const readAvatarColorMetaValue = (form, style, field, type) => {
|
|
||||||
const panel = getAvatarStylePanel(form, style);
|
|
||||||
if (!panel) return '';
|
|
||||||
const input = panel.querySelector(`[data-avatar-color-meta="${field}"][data-avatar-color-meta-type="${type}"]`);
|
|
||||||
return input ? input.value : '';
|
|
||||||
};
|
|
||||||
|
|
||||||
const getAvatarStyleConfig = (style) => {
|
const getAvatarStyleConfig = (style) => {
|
||||||
if (avatarStyleConfigs[style]) return avatarStyleConfigs[style];
|
if (avatarStyleConfigs[style]) return avatarStyleConfigs[style];
|
||||||
return avatarStyleConfigs.lorelei;
|
return avatarStyleConfigs.lorelei;
|
||||||
@@ -845,18 +838,6 @@ document.addEventListener('DOMContentLoaded', () => {
|
|||||||
: readAvatarColorValue(form, style, field);
|
: readAvatarColorValue(form, style, field);
|
||||||
if (value) {
|
if (value) {
|
||||||
params.set(field, value);
|
params.set(field, value);
|
||||||
const fill = Object.prototype.hasOwnProperty.call(overrides, `${field}Fill`)
|
|
||||||
? overrides[`${field}Fill`]
|
|
||||||
: readAvatarColorMetaValue(form, style, field, 'fill');
|
|
||||||
const fillStops = Object.prototype.hasOwnProperty.call(overrides, `${field}FillStops`)
|
|
||||||
? overrides[`${field}FillStops`]
|
|
||||||
: readAvatarColorMetaValue(form, style, field, 'fillStops');
|
|
||||||
const angle = Object.prototype.hasOwnProperty.call(overrides, `${field}Angle`)
|
|
||||||
? overrides[`${field}Angle`]
|
|
||||||
: readAvatarColorMetaValue(form, style, field, 'angle');
|
|
||||||
if (fill) params.set(`${field}Fill`, fill);
|
|
||||||
if (fillStops) params.set(`${field}FillStops`, fillStops);
|
|
||||||
if (angle) params.set(`${field}Angle`, angle);
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -937,13 +918,9 @@ document.addEventListener('DOMContentLoaded', () => {
|
|||||||
const panel = getAvatarStylePanel(form, style);
|
const panel = getAvatarStylePanel(form, style);
|
||||||
if (!panel || !field) return;
|
if (!panel || !field) return;
|
||||||
const hidden = panel.querySelector(`[data-avatar-color-input="${field}"]`);
|
const hidden = panel.querySelector(`[data-avatar-color-input="${field}"]`);
|
||||||
const label = panel.querySelector(`[data-avatar-color-label="${style}:${field}"]`);
|
|
||||||
if (hidden) {
|
if (hidden) {
|
||||||
hidden.value = picker.value;
|
hidden.value = picker.value;
|
||||||
}
|
}
|
||||||
if (label) {
|
|
||||||
label.textContent = picker.value;
|
|
||||||
}
|
|
||||||
ensureAvatarBuilderLoaded(form, style);
|
ensureAvatarBuilderLoaded(form, style);
|
||||||
updateAvatarPreview(form);
|
updateAvatarPreview(form);
|
||||||
updateAvatarOptionPreviews(form, style, getActiveAvatarField(form, style));
|
updateAvatarOptionPreviews(form, style, getActiveAvatarField(form, style));
|
||||||
@@ -961,10 +938,6 @@ document.addEventListener('DOMContentLoaded', () => {
|
|||||||
if (!panel || !field) return;
|
if (!panel || !field) return;
|
||||||
const hidden = panel.querySelector(`[data-avatar-color-input="${field}"]`);
|
const hidden = panel.querySelector(`[data-avatar-color-input="${field}"]`);
|
||||||
const picker = panel.querySelector(`[data-avatar-color-picker="${field}"]`);
|
const picker = panel.querySelector(`[data-avatar-color-picker="${field}"]`);
|
||||||
const label = panel.querySelector(`[data-avatar-color-label="${style}:${field}"]`);
|
|
||||||
const fill = panel.querySelector(`[data-avatar-color-meta="${field}"][data-avatar-color-meta-type="fill"]`);
|
|
||||||
const fillStops = panel.querySelector(`[data-avatar-color-meta="${field}"][data-avatar-color-meta-type="fillStops"]`);
|
|
||||||
const angle = panel.querySelector(`[data-avatar-color-meta="${field}"][data-avatar-color-meta-type="angle"]`);
|
|
||||||
const defaultValue = picker?.getAttribute('data-avatar-color-default') || '#c4a484';
|
const defaultValue = picker?.getAttribute('data-avatar-color-default') || '#c4a484';
|
||||||
if (hidden) {
|
if (hidden) {
|
||||||
hidden.value = '';
|
hidden.value = '';
|
||||||
@@ -972,28 +945,27 @@ document.addEventListener('DOMContentLoaded', () => {
|
|||||||
if (picker) {
|
if (picker) {
|
||||||
picker.value = defaultValue;
|
picker.value = defaultValue;
|
||||||
}
|
}
|
||||||
if (label) {
|
|
||||||
label.textContent = 'DiceBear-Standard';
|
|
||||||
}
|
|
||||||
if (fill) fill.value = '';
|
|
||||||
if (fillStops) fillStops.value = '';
|
|
||||||
if (angle) angle.value = '';
|
|
||||||
ensureAvatarBuilderLoaded(form, style);
|
ensureAvatarBuilderLoaded(form, style);
|
||||||
updateAvatarPreview(form);
|
updateAvatarPreview(form);
|
||||||
updateAvatarOptionPreviews(form, style, getActiveAvatarField(form, style));
|
updateAvatarOptionPreviews(form, style, getActiveAvatarField(form, style));
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
form.querySelectorAll('[data-avatar-color-meta]').forEach((input) => {
|
form.querySelectorAll('[data-avatar-color-sample-field]').forEach((button) => {
|
||||||
const syncMeta = () => {
|
button.addEventListener('click', () => {
|
||||||
const style = input.getAttribute('data-avatar-style') || getCurrentAvatarStyle(form);
|
const style = button.getAttribute('data-avatar-style') || getCurrentAvatarStyle(form);
|
||||||
|
const field = button.getAttribute('data-avatar-color-sample-field') || '';
|
||||||
|
const value = button.getAttribute('data-avatar-color-sample-value') || '';
|
||||||
|
const panel = getAvatarStylePanel(form, style);
|
||||||
|
if (!panel || !field || !value) return;
|
||||||
|
const hidden = panel.querySelector(`[data-avatar-color-input="${field}"]`);
|
||||||
|
const picker = panel.querySelector(`[data-avatar-color-picker="${field}"]`);
|
||||||
|
if (hidden) hidden.value = value;
|
||||||
|
if (picker) picker.value = value;
|
||||||
ensureAvatarBuilderLoaded(form, style);
|
ensureAvatarBuilderLoaded(form, style);
|
||||||
updateAvatarPreview(form);
|
updateAvatarPreview(form);
|
||||||
updateAvatarOptionPreviews(form, style, getActiveAvatarField(form, style));
|
updateAvatarOptionPreviews(form, style, getActiveAvatarField(form, style));
|
||||||
};
|
});
|
||||||
|
|
||||||
input.addEventListener('input', syncMeta);
|
|
||||||
input.addEventListener('change', syncMeta);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
const setActiveComponentPanel = (style, field) => {
|
const setActiveComponentPanel = (style, field) => {
|
||||||
@@ -1018,7 +990,7 @@ document.addEventListener('DOMContentLoaded', () => {
|
|||||||
panel.querySelectorAll('[data-avatar-field]').forEach((input) => {
|
panel.querySelectorAll('[data-avatar-field]').forEach((input) => {
|
||||||
input.disabled = !active;
|
input.disabled = !active;
|
||||||
});
|
});
|
||||||
panel.querySelectorAll('[data-avatar-color-input], [data-avatar-color-picker], [data-avatar-color-reset]').forEach((input) => {
|
panel.querySelectorAll('[data-avatar-color-input], [data-avatar-color-picker], [data-avatar-color-reset], [data-avatar-color-sample-field]').forEach((input) => {
|
||||||
input.disabled = !active;
|
input.disabled = !active;
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -209,6 +209,9 @@ final class AvatarManager
|
|||||||
$styleClass = (string)$meta['class'];
|
$styleClass = (string)$meta['class'];
|
||||||
$styleConfig = $currentStyle === $meta['key'] ? $currentConfig : $styleClass::defaultConfig();
|
$styleConfig = $currentStyle === $meta['key'] ? $currentConfig : $styleClass::defaultConfig();
|
||||||
$components = [];
|
$components = [];
|
||||||
|
$colorDefinitions = $styleClass::colorDefinitions();
|
||||||
|
$colorControls = is_array($meta['color_controls'] ?? null) ? $meta['color_controls'] : [];
|
||||||
|
$colorGroups = is_array($meta['color_groups'] ?? null) ? $meta['color_groups'] : [];
|
||||||
|
|
||||||
foreach ($styleClass::componentDefinitions() as $field => $definition) {
|
foreach ($styleClass::componentDefinitions() as $field => $definition) {
|
||||||
$options = [];
|
$options = [];
|
||||||
@@ -221,24 +224,28 @@ final class AvatarManager
|
|||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$componentColors = [];
|
||||||
|
foreach (($colorGroups[$field] ?? []) as $colorField) {
|
||||||
|
if (!isset($colorDefinitions[$colorField])) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
$componentColors[$colorField] = [
|
||||||
|
'field' => $colorField,
|
||||||
|
'label' => (string)($colorControls[$colorField]['label'] ?? $colorDefinitions[$colorField]['label'] ?? $colorField),
|
||||||
|
'value' => (string)($styleConfig[$colorField] ?? ''),
|
||||||
|
'default' => (string)($colorControls[$colorField]['default'] ?? $colorDefinitions[$colorField]['default'] ?? '#c4a484'),
|
||||||
|
'samples' => is_array($colorControls[$colorField]['samples'] ?? null) ? $colorControls[$colorField]['samples'] : [],
|
||||||
|
'allow_custom' => (bool)($colorControls[$colorField]['allow_custom'] ?? true),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
$components[$field] = [
|
$components[$field] = [
|
||||||
'field' => $field,
|
'field' => $field,
|
||||||
'label' => (string)($definition['label'] ?? $field),
|
'label' => (string)($definition['label'] ?? $field),
|
||||||
'count' => count(array_filter($options, static fn(array $option): bool => $option['value'] !== '')),
|
'count' => count(array_filter($options, static fn(array $option): bool => $option['value'] !== '')),
|
||||||
'options' => $options,
|
'options' => $options,
|
||||||
];
|
'colors' => $componentColors,
|
||||||
}
|
|
||||||
|
|
||||||
$colors = [];
|
|
||||||
foreach ($styleClass::colorDefinitions() as $field => $definition) {
|
|
||||||
$colors[$field] = [
|
|
||||||
'field' => $field,
|
|
||||||
'label' => (string)($definition['label'] ?? $field),
|
|
||||||
'value' => (string)($styleConfig[$field] ?? ''),
|
|
||||||
'default' => (string)($definition['default'] ?? '#c4a484'),
|
|
||||||
'fill' => (string)($styleConfig[$field . 'Fill'] ?? ''),
|
|
||||||
'fill_stops' => (string)($styleConfig[$field . 'FillStops'] ?? ''),
|
|
||||||
'angle' => (string)($styleConfig[$field . 'Angle'] ?? ''),
|
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -246,7 +253,6 @@ final class AvatarManager
|
|||||||
'key' => $meta['key'],
|
'key' => $meta['key'],
|
||||||
'label' => $meta['label'],
|
'label' => $meta['label'],
|
||||||
'components' => $components,
|
'components' => $components,
|
||||||
'colors' => $colors,
|
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user