Commit 5c00e8d4 authored by Mahmoud Aglan's avatar Mahmoud Aglan

fix(admin): a branding save no longer pins every default over tokens.css

The form posted every field, pre-filled with the Cafe Deck defaults, and
the save stored each one in platform_theme, where stored values beat
tokens.css: one save would have turned the app indigo again. It now sends
only the fields that changed, and its defaults are the Night Arcade
values. Pawn uploads go to the ludo_token_* slots the board now reads.
Co-Authored-By: 's avatarClaude Opus 5.5 <noreply@anthropic.com>
parent 32bfa830
......@@ -129,10 +129,10 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_FILES['asset'])) {
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>EL3AB Branding</title>
<link href="https://fonts.googleapis.com/css2?family=Alexandria:wght@400;600;700;800&display=swap" rel="stylesheet">
<link rel="stylesheet" href="/public/css/tokens.css">
<style>
* { box-sizing: border-box; margin: 0; padding: 0; }
body { background: #0E1540; color: #F3F0E8; font-family: 'Alexandria', system-ui, sans-serif; line-height: 1.6; }
body { background: #14121A; color: #FFFFFF; font-family: 'Rubik', system-ui, sans-serif; line-height: 1.6; }
.container { max-width: 900px; margin: 0 auto; padding: 24px 16px; }
h1 { color: #E4AC38; font-size: 24px; margin-bottom: 8px; }
h2 { color: #B7BCE0; font-size: 18px; margin: 32px 0 12px; border-bottom: 1px solid #151F52; padding-bottom: 8px; }
......@@ -174,45 +174,45 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_FILES['asset'])) {
<?php if (isset($_POST['save_theme'])): ?><div class="saved">Changes saved</div><?php endif; ?>
<form method="POST">
<form method="POST" id="theme-form">
<input type="hidden" name="save_theme" value="1">
<!-- THE CAFE DECK: COLOURS -->
<h2>The Cafe Deck: colours</h2>
<p class="note">The app is the deck on a cafe table. The ground is the indigo card back; cards are white stock printed in black and red ink; gold is foil. These are the defaults in public/css/tokens.css. Change one only on purpose.</p>
<!-- NIGHT ARCADE: COLOURS -->
<h2>Night Arcade: colours</h2>
<p class="note">A modern, minimal game: a deep ink stage, flat ink tiles, white game cards, flat colour buttons on a lip. These are the defaults in public/css/tokens.css. Only the fields you change are saved; leave a field alone and tokens.css keeps it.</p>
<?php
$deckGroups = [
'Card back (the ground)' => [
['key' => 'back_950', 'label' => 'Deepest', 'default' => '#090E2C', 'hint' => 'Top bar, tab bar, insets'],
['key' => 'back_900', 'label' => 'Ground', 'default' => '#0E1540', 'hint' => 'Every screen sits on this'],
['key' => 'back_800', 'label' => 'Raised', 'default' => '#151F52', 'hint' => 'Panels and purse chips'],
['key' => 'back_700', 'label' => 'Face-down card', 'default' => '#1D2963', 'hint' => 'Secondary buttons, highlighted rows'],
['key' => 'back_600', 'label' => 'Card back print', 'default' => '#283679', 'hint' => 'Face-down cards, pressed states'],
['key' => 'back_500', 'label' => 'Strong line', 'default' => '#3A4A9C', 'hint' => 'Rare emphasis on the ground'],
'Ink (the stage and its tiles)' => [
['key' => 'back_950', 'label' => 'Deepest', 'default' => '#0D0C12', 'hint' => 'Top bar, tab bar, insets'],
['key' => 'back_900', 'label' => 'Ground', 'default' => '#14121A', 'hint' => 'Every screen sits on this'],
['key' => 'back_800', 'label' => 'Raised', 'default' => '#1D1A25', 'hint' => 'Panels and purse chips'],
['key' => 'back_700', 'label' => 'Face-down card', 'default' => '#262230', 'hint' => 'Secondary buttons, highlighted rows'],
['key' => 'back_600', 'label' => 'Card back print', 'default' => '#302B3C', 'hint' => 'Face-down cards, pressed states'],
['key' => 'back_500', 'label' => 'Strong line', 'default' => '#3D3749', 'hint' => 'Rare emphasis on the ground'],
],
'Card stock and inks' => [
['key' => 'card', 'label' => 'Card stock', 'default' => '#F7F4EC', 'hint' => 'Card faces, sheets, toasts'],
['key' => 'card_2', 'label' => 'Card shade', 'default' => '#EEE9DD', 'hint' => 'Pressed card faces'],
['key' => 'card_line', 'label' => 'Inner frame', 'default' => '#D6CEBC', 'hint' => 'The printed frame inside every card'],
['key' => 'ink', 'label' => 'Black ink', 'default' => '#17150F', 'hint' => 'Text and marks on cards'],
['key' => 'ink_2', 'label' => 'Black ink, soft', 'default' => '#4A463C', 'hint' => 'Secondary text on cards'],
['key' => 'ink_3', 'label' => 'Black ink, faint', 'default' => '#6F6858', 'hint' => 'Small print on cards'],
['key' => 'red_ink', 'label' => 'Red ink', 'default' => '#C41E2E', 'hint' => 'Primary buttons, red suits, live'],
['key' => 'red_ink_deep', 'label' => 'Red ink, pressed', 'default' => '#A01624', 'hint' => 'Primary button pressed and edge'],
'White cards and their ink' => [
['key' => 'card', 'label' => 'Card stock', 'default' => '#FFFFFF', 'hint' => 'Card faces, sheets, toasts'],
['key' => 'card_2', 'label' => 'Card shade', 'default' => '#F4F2F8', 'hint' => 'Pressed card faces'],
['key' => 'card_line', 'label' => 'Inner frame', 'default' => '#E3DFEA', 'hint' => 'The printed frame inside every card'],
['key' => 'ink', 'label' => 'Black ink', 'default' => '#1A1722', 'hint' => 'Text and marks on cards'],
['key' => 'ink_2', 'label' => 'Black ink, soft', 'default' => '#4B4558', 'hint' => 'Secondary text on cards'],
['key' => 'ink_3', 'label' => 'Black ink, faint', 'default' => '#7D768C', 'hint' => 'Small print on cards'],
['key' => 'red_ink', 'label' => 'Red ink', 'default' => '#F0384B', 'hint' => 'Primary buttons, red suits, live'],
['key' => 'red_ink_deep', 'label' => 'Red ink, pressed', 'default' => '#C21F33', 'hint' => 'Primary button pressed and edge'],
],
'Foil and signals' => [
['key' => 'gold', 'label' => 'Gold foil', 'default' => '#E4AC38', 'hint' => 'Crown, coins, first place. Keep rare'],
['key' => 'gold_soft', 'label' => 'Gold, light', 'default' => '#F2C766', 'hint' => 'Gold on very dark areas'],
['key' => 'gold_dark', 'label' => 'Gold, edge', 'default' => '#B8841F', 'hint' => 'Gold button edge'],
['key' => 'red', 'label' => 'Red on the ground', 'default' => '#EF4D55', 'hint' => 'Red text and gems on indigo'],
['key' => 'win', 'label' => 'Win', 'default' => '#3DBE7E', 'hint' => 'Wins, online dots'],
['key' => 'loss', 'label' => 'Loss', 'default' => '#EF4D55', 'hint' => 'Losses'],
['key' => 'sky', 'label' => 'Links', 'default' => '#8FA2FA', 'hint' => 'Links and information on indigo'],
'Gold and signals' => [
['key' => 'gold', 'label' => 'Gold foil', 'default' => '#FFC531', 'hint' => 'Crown, coins, first place. Keep rare'],
['key' => 'gold_soft', 'label' => 'Gold, light', 'default' => '#FFD866', 'hint' => 'Gold on very dark areas'],
['key' => 'gold_dark', 'label' => 'Gold, edge', 'default' => '#D18D08', 'hint' => 'Gold button edge'],
['key' => 'red', 'label' => 'Red on the ground', 'default' => '#FF4D5E', 'hint' => 'Red text and gems on indigo'],
['key' => 'win', 'label' => 'Win', 'default' => '#2ED47A', 'hint' => 'Wins, online dots'],
['key' => 'loss', 'label' => 'Loss', 'default' => '#FF4D5E', 'hint' => 'Losses'],
['key' => 'sky', 'label' => 'Links', 'default' => '#4DA3FF', 'hint' => 'Links and information on indigo'],
],
'Text on the ground' => [
['key' => 'text_primary', 'label' => 'Text', 'default' => '#F3F0E8', 'hint' => 'Main text'],
['key' => 'text_secondary', 'label' => 'Secondary text', 'default' => '#B7BCE0', 'hint' => 'Supporting text'],
['key' => 'text_muted', 'label' => 'Muted text', 'default' => '#8C92C4', 'hint' => 'Meta and captions (keep 4.5:1 on the ground)'],
['key' => 'text_primary', 'label' => 'Text', 'default' => '#FFFFFF', 'hint' => 'Main text'],
['key' => 'text_secondary', 'label' => 'Secondary text', 'default' => '#C8C3D4', 'hint' => 'Supporting text'],
['key' => 'text_muted', 'label' => 'Muted text', 'default' => '#8E889E', 'hint' => 'Meta and captions (keep 4.5:1 on the ground)'],
],
];
foreach ($deckGroups as $groupName => $colors): ?>
......@@ -239,15 +239,15 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_FILES['asset'])) {
<div class="grid">
<?php
$chrome = [
['key' => 'hud_height', 'label' => 'Top bar height', 'default' => '56', 'min' => 48, 'max' => 72, 'hint' => 'px'],
['key' => 'hud_avatar_size', 'label' => 'Avatar size', 'default' => '36', 'min' => 28, 'max' => 48, 'hint' => 'px'],
['key' => 'hud_stat_font', 'label' => 'Purse numbers', 'default' => '14', 'min' => 11, 'max' => 18, 'hint' => 'px, never below 11'],
['key' => 'hud_height', 'label' => 'Top bar height', 'default' => '64', 'min' => 48, 'max' => 72, 'hint' => 'px'],
['key' => 'hud_avatar_size', 'label' => 'Avatar size', 'default' => '46', 'min' => 28, 'max' => 48, 'hint' => 'px'],
['key' => 'hud_stat_font', 'label' => 'Purse numbers', 'default' => '16', 'min' => 11, 'max' => 18, 'hint' => 'px, never below 11'],
['key' => 'hud_btn_size', 'label' => 'Bell button', 'default' => '44', 'min' => 44, 'max' => 56, 'hint' => 'px, 44 is the minimum tap size'],
['key' => 'hud_padding_x', 'label' => 'Side padding', 'default' => '12', 'min' => 8, 'max' => 24, 'hint' => 'px'],
['key' => 'tab_height', 'label' => 'Tab bar height', 'default' => '64', 'min' => 56, 'max' => 80, 'hint' => 'px'],
['key' => 'tab_icon_size', 'label' => 'Tab icon size', 'default' => '24', 'min' => 20, 'max' => 32, 'hint' => 'px'],
['key' => 'tab_label_font', 'label' => 'Tab label size', 'default' => '11', 'min' => 11, 'max' => 14, 'hint' => 'px, never below 11'],
['key' => 'tab_item_min_size', 'label' => 'Tab touch target', 'default' => '48', 'min' => 44, 'max' => 64, 'hint' => 'px'],
['key' => 'tab_height', 'label' => 'Tab bar height', 'default' => '84', 'min' => 56, 'max' => 80, 'hint' => 'px'],
['key' => 'tab_icon_size', 'label' => 'Tab icon size', 'default' => '32', 'min' => 20, 'max' => 32, 'hint' => 'px'],
['key' => 'tab_label_font', 'label' => 'Tab label size', 'default' => '12', 'min' => 11, 'max' => 14, 'hint' => 'px, never below 11'],
['key' => 'tab_item_min_size', 'label' => 'Tab touch target', 'default' => '52', 'min' => 44, 'max' => 64, 'hint' => 'px'],
];
foreach ($chrome as $s): $val = $theme[$s['key']] ?? $s['default']; ?>
<div class="field">
......@@ -268,11 +268,11 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_FILES['asset'])) {
<div class="grid">
<?php
$boardColors = [
['key' => 'board_light', 'label' => 'Light Square', 'default' => '#EFEADF', 'hint' => 'Light squares on the board'],
['key' => 'board_dark', 'label' => 'Dark Square', 'default' => '#7684BF', 'hint' => 'Dark squares on the board'],
['key' => 'board_highlight_light', 'label' => 'Move Highlight (Light)', 'default' => '#F3DC98', 'hint' => 'Last move — light square'],
['key' => 'board_highlight_dark', 'label' => 'Move Highlight (Dark)', 'default' => '#C7A65A', 'hint' => 'Last move — dark square'],
['key' => 'board_check', 'label' => 'King in Check', 'default' => '#C41E2E', 'hint' => 'When the king is under attack'],
['key' => 'board_light', 'label' => 'Light Square', 'default' => '#E9E6F1', 'hint' => 'Light squares on the board'],
['key' => 'board_dark', 'label' => 'Dark Square', 'default' => '#8B80AC', 'hint' => 'Dark squares on the board'],
['key' => 'board_highlight_light', 'label' => 'Move Highlight (Light)', 'default' => '#F6E58C', 'hint' => 'Last move — light square'],
['key' => 'board_highlight_dark', 'label' => 'Move Highlight (Dark)', 'default' => '#D3C060', 'hint' => 'Last move — dark square'],
['key' => 'board_check', 'label' => 'King in Check', 'default' => '#FF4D5E', 'hint' => 'When the king is under attack'],
];
foreach ($boardColors as $c):
$val = $theme[$c['key']] ?? $c['default'];
......@@ -295,10 +295,10 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_FILES['asset'])) {
<div class="grid">
<?php
$ludoBoardColors = [
['key' => 'ludo_red', 'label' => 'Ludo Red', 'default' => '#C41E2E', 'hint' => 'Red player zone'],
['key' => 'ludo_blue', 'label' => 'Ludo Blue', 'default' => '#2447B8', 'hint' => 'Blue player zone'],
['key' => 'ludo_green', 'label' => 'Ludo Green', 'default' => '#1F7A4D', 'hint' => 'Green player zone'],
['key' => 'ludo_yellow', 'label' => 'Ludo Yellow', 'default' => '#E0A526', 'hint' => 'Yellow player zone'],
['key' => 'ludo_red', 'label' => 'Ludo Red', 'default' => '#FF4D5E', 'hint' => 'Red player zone'],
['key' => 'ludo_blue', 'label' => 'Ludo Blue', 'default' => '#3B86FF', 'hint' => 'Blue player zone'],
['key' => 'ludo_green', 'label' => 'Ludo Green', 'default' => '#2BCF76', 'hint' => 'Green player zone'],
['key' => 'ludo_yellow', 'label' => 'Ludo Yellow', 'default' => '#FFC531', 'hint' => 'Yellow player zone'],
['key' => 'ludo_board_bg', 'label' => 'Board Background', 'default' => '#FAFAFA', 'hint' => 'Main board surface'],
['key' => 'ludo_path', 'label' => 'Path Color', 'default' => '#FFFFFF', 'hint' => 'Walking path tiles'],
['key' => 'ludo_safe', 'label' => 'Safe Spot', 'default' => '#FFD700', 'hint' => 'Star/safe zone marker'],
......@@ -326,20 +326,20 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_FILES['asset'])) {
<div class="grid">
<?php
$bgBoardColors = [
['key' => 'bg_board_border', 'label' => 'Board Border', 'default' => '#0B1035', 'hint' => 'Outer frame'],
['key' => 'bg_board_light', 'label' => 'Board Light', 'default' => '#EFEADF', 'hint' => 'Printed board field'],
['key' => 'bg_board_dark', 'label' => 'Board Dark', 'default' => '#E6DFD0', 'hint' => 'Printed board field, shade'],
['key' => 'bg_bar_dark', 'label' => 'Bar Dark', 'default' => '#151F52', 'hint' => 'Center bar dark'],
['key' => 'bg_bar_light', 'label' => 'Bar Light', 'default' => '#1D2963', 'hint' => 'Center bar light'],
['key' => 'bg_bearoff', 'label' => 'Bear-off Area', 'default' => '#0B1035', 'hint' => 'Bear-off tray'],
['key' => 'bg_point_dark', 'label' => 'Dark Point', 'default' => '#17150F', 'hint' => 'Dark triangle base'],
['key' => 'bg_point_dark_tip', 'label' => 'Dark Point Tip', 'default' => '#17150F', 'hint' => 'Dark triangle tip'],
['key' => 'bg_point_light', 'label' => 'Light Point', 'default' => '#C41E2E', 'hint' => 'Light triangle base'],
['key' => 'bg_point_light_tip', 'label' => 'Light Point Tip', 'default' => '#C41E2E', 'hint' => 'Light triangle tip'],
['key' => 'bg_checker_white', 'label' => 'White Checker', 'default' => '#F7F4EC', 'hint' => 'White player pieces'],
['key' => 'bg_checker_black', 'label' => 'Black Checker', 'default' => '#17150F', 'hint' => 'Black player pieces'],
['key' => 'bg_highlight', 'label' => 'Move Highlight', 'default' => '#3DBE7E', 'hint' => 'Valid move indicator'],
['key' => 'bg_selected', 'label' => 'Selected Piece', 'default' => '#E4AC38', 'hint' => 'Selected checker glow'],
['key' => 'bg_board_border', 'label' => 'Board Border', 'default' => '#14121A', 'hint' => 'Outer frame'],
['key' => 'bg_board_light', 'label' => 'Board Light', 'default' => '#22A57E', 'hint' => 'Printed board field'],
['key' => 'bg_board_dark', 'label' => 'Board Dark', 'default' => '#1B9070', 'hint' => 'Printed board field, shade'],
['key' => 'bg_bar_dark', 'label' => 'Bar Dark', 'default' => '#14121A', 'hint' => 'Center bar dark'],
['key' => 'bg_bar_light', 'label' => 'Bar Light', 'default' => '#1D1A25', 'hint' => 'Center bar light'],
['key' => 'bg_bearoff', 'label' => 'Bear-off Area', 'default' => '#1D1A25', 'hint' => 'Bear-off tray'],
['key' => 'bg_point_dark', 'label' => 'Dark Point', 'default' => '#FF4458', 'hint' => 'Dark triangle base'],
['key' => 'bg_point_dark_tip', 'label' => 'Dark Point Tip', 'default' => '#FF4458', 'hint' => 'Dark triangle tip'],
['key' => 'bg_point_light', 'label' => 'Light Point', 'default' => '#F4F2F8', 'hint' => 'Light triangle base'],
['key' => 'bg_point_light_tip', 'label' => 'Light Point Tip', 'default' => '#F4F2F8', 'hint' => 'Light triangle tip'],
['key' => 'bg_checker_white', 'label' => 'White Checker', 'default' => '#FFFFFF', 'hint' => 'White player pieces'],
['key' => 'bg_checker_black', 'label' => 'Black Checker', 'default' => '#1A1722', 'hint' => 'Black player pieces'],
['key' => 'bg_highlight', 'label' => 'Move Highlight', 'default' => '#2ED47A', 'hint' => 'Valid move indicator'],
['key' => 'bg_selected', 'label' => 'Selected Piece', 'default' => '#FFC531', 'hint' => 'Selected checker glow'],
];
foreach ($bgBoardColors as $c):
$val = $theme[$c['key']] ?? $c['default'];
......@@ -467,7 +467,7 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_FILES['asset'])) {
['key' => 'anim_speed', 'label' => 'Transition Speed (ms)', 'default' => '240', 'hint' => 'Default transition duration', 'type' => 'number'],
['key' => 'anim_fast', 'label' => 'Fast Speed (ms)', 'default' => '140', 'hint' => 'Buttons, feedback', 'type' => 'number'],
['key' => 'anim_slow', 'label' => 'Slow Speed (ms)', 'default' => '420', 'hint' => 'Scene transitions', 'type' => 'number'],
['key' => 'card_radius', 'label' => 'Card Radius (px)', 'default' => '14', 'hint' => 'Card corner radius', 'type' => 'number'],
['key' => 'card_radius', 'label' => 'Card Radius (px)', 'default' => '22', 'hint' => 'Card corner radius', 'type' => 'number'],
];
foreach ($anims as $a):
$val = $theme[$a['key']] ?? $a['default'];
......@@ -525,15 +525,15 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_FILES['asset'])) {
<!-- LUDO PAWNS -->
<h2>Ludo Pawns</h2>
<p style="color:#64748b;font-size:12px;margin-bottom:16px;">Upload PNG or SVG for Ludo pawns — they animate with the same bounce on the board. Default is geometric shape.</p>
<p style="color:#8E889E;font-size:12px;margin-bottom:16px;">Upload a PNG or SVG to replace a pawn; it animates with the same hop on the board. Empty slots use the drawn flat pawns. (The old ludo_pawn_* uploads are no longer read.)</p>
<div class="section">
<div class="grid">
<?php
$ludoPawns = [
['slot' => 'ludo_pawn_red', 'label' => 'Red Pawn', 'w' => 64, 'h' => 64, 'hint' => 'Player 1 — Red pawn sprite'],
['slot' => 'ludo_pawn_green', 'label' => 'Green Pawn', 'w' => 64, 'h' => 64, 'hint' => 'Player 2 — Green pawn sprite'],
['slot' => 'ludo_pawn_yellow', 'label' => 'Yellow Pawn', 'w' => 64, 'h' => 64, 'hint' => 'Player 3 — Yellow pawn sprite'],
['slot' => 'ludo_pawn_blue', 'label' => 'Blue Pawn', 'w' => 64, 'h' => 64, 'hint' => 'Player 4 — Blue pawn sprite'],
['slot' => 'ludo_token_red', 'label' => 'Red Pawn', 'w' => 64, 'h' => 64, 'hint' => 'Player 1, red pawn. Leave empty for the drawn flat pawn'],
['slot' => 'ludo_token_green', 'label' => 'Green Pawn', 'w' => 64, 'h' => 64, 'hint' => 'Player 2, green pawn. Leave empty for the drawn flat pawn'],
['slot' => 'ludo_token_yellow', 'label' => 'Yellow Pawn', 'w' => 64, 'h' => 64, 'hint' => 'Player 3, yellow pawn. Leave empty for the drawn flat pawn'],
['slot' => 'ludo_token_blue', 'label' => 'Blue Pawn', 'w' => 64, 'h' => 64, 'hint' => 'Player 4, blue pawn. Leave empty for the drawn flat pawn'],
];
foreach ($ludoPawns as $a):
$current = $theme['assets'][$a['slot']] ?? null;
......@@ -799,5 +799,21 @@ window.addEventListener('beforeunload', (e) => {
if (hasChanges) { e.preventDefault(); e.returnValue = ''; }
});
</script>
<script>
// Only the fields you change are sent. Every field is pre-filled (from the
// stored value or the tokens.css default), and sending them all would store
// every default and pin it over tokens.css, so a later palette change to
// tokens.css would never reach the app.
(function () {
const form = document.getElementById('theme-form');
if (!form) return;
const fields = [...form.querySelectorAll('input[name^="theme["]')];
const initial = new Map(fields.map(f => [f, f.value]));
form.addEventListener('submit', () => {
const changed = new Set(fields.filter(f => f.value !== initial.get(f)).map(f => f.name));
for (const f of fields) if (!changed.has(f.name)) f.disabled = true;
});
})();
</script>
</body>
</html>
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment