Commit 034fc175 authored by Mahmoud Aglan's avatar Mahmoud Aglan

feat(admin): branding edits The Cafe Deck tokens; theme saves need the admin

- The colour section now edits the deck tokens (card back, card stock and
  inks, foil and signals, text) with tokens.css values as defaults; the top
  and tab bar section only lists values theme.js still applies.
- Retired sections (home tiles, quick actions, game menu sheet, menu buttons,
  chips, bot cards, game gradients, pill radii) are gone; saving them did
  nothing after the redesign, and their old defaults could have overridden it.
- Emoji replacement slots become icon overrides (icon_<name>); slots that are
  now drawn icons are dropped from the asset list. Board defaults match the
  printed boards.
- POST /api/branding.php required no login at all: anyone could rewrite the
  theme for every player. It now requires the branding admin session.
Co-Authored-By: 's avatarClaude Opus 5.5 <noreply@anthropic.com>
parent 6b2507b8
...@@ -14,7 +14,7 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['logout'])) { ...@@ -14,7 +14,7 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['logout'])) {
if (!($_SESSION['branding_auth'] ?? false)) { if (!($_SESSION['branding_auth'] ?? false)) {
?><!DOCTYPE html><html lang="en" dir="ltr"><head><meta charset="UTF-8"><meta name="viewport" content="width=device-width,initial-scale=1"><title>EL3AB Branding Admin</title> ?><!DOCTYPE html><html lang="en" dir="ltr"><head><meta charset="UTF-8"><meta name="viewport" content="width=device-width,initial-scale=1"><title>EL3AB Branding Admin</title>
<style>body{background:#0a0a1a;color:#f8fafc;font-family:'IBM Plex Sans Arabic',sans-serif;display:flex;align-items:center;justify-content:center;height:100vh;margin:0}form{background:#1a1a2e;padding:40px;border-radius:16px;width:320px;text-align:center}input{width:100%;padding:12px;margin:8px 0;background:#2a2a4a;border:1px solid #333;border-radius:8px;color:#fff;font-size:14px}button{width:100%;padding:12px;margin-top:16px;background:linear-gradient(135deg,#E4AC38,#FFCC66);border:none;border-radius:8px;color:#1a1a1a;font-weight:700;font-size:15px;cursor:pointer}h2{color:#E4AC38;margin-bottom:16px}</style></head><body> <style>body{background:#0E1540;color:#F3F0E8;font-family:'IBM Plex Sans Arabic',sans-serif;display:flex;align-items:center;justify-content:center;height:100vh;margin:0}form{background:#151F52;padding:40px;border-radius:16px;width:320px;text-align:center}input{width:100%;padding:12px;margin:8px 0;background:#090E2C;border:1px solid #333;border-radius:8px;color:#fff;font-size:14px}button{width:100%;padding:12px;margin-top:16px;background:linear-gradient(135deg,#E4AC38,#FFCC66);border:none;border-radius:8px;color:#1a1a1a;font-weight:700;font-size:15px;cursor:pointer}h2{color:#E4AC38;margin-bottom:16px}</style></head><body>
<form method="POST"><h2>EL3AB Branding</h2><input name="username" placeholder="Username" required><input name="password" type="password" placeholder="Password" required><button name="login" value="1">Login</button></form></body></html><?php <form method="POST"><h2>EL3AB Branding</h2><input name="username" placeholder="Username" required><input name="password" type="password" placeholder="Password" required><button name="login" value="1">Login</button></form></body></html><?php
exit; exit;
} }
...@@ -128,23 +128,24 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_FILES['asset'])) { ...@@ -128,23 +128,24 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_FILES['asset'])) {
<head> <head>
<meta charset="UTF-8"> <meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1"> <meta name="viewport" content="width=device-width, initial-scale=1">
<title>EL3AB — Branding Admin</title> <title>EL3AB Branding</title>
<link href="https://fonts.googleapis.com/css2?family=Alexandria:wght@400;600;700;800&display=swap" rel="stylesheet">
<style> <style>
* { box-sizing: border-box; margin: 0; padding: 0; } * { box-sizing: border-box; margin: 0; padding: 0; }
body { background: #0a0a1a; color: #f8fafc; font-family: 'IBM Plex Sans Arabic', 'Inter', sans-serif; line-height: 1.6; } body { background: #0E1540; color: #F3F0E8; font-family: 'Alexandria', system-ui, sans-serif; line-height: 1.6; }
.container { max-width: 900px; margin: 0 auto; padding: 24px 16px; } .container { max-width: 900px; margin: 0 auto; padding: 24px 16px; }
h1 { color: #E4AC38; font-size: 24px; margin-bottom: 8px; } h1 { color: #E4AC38; font-size: 24px; margin-bottom: 8px; }
h2 { color: #94a3b8; font-size: 18px; margin: 32px 0 12px; border-bottom: 1px solid #1a1a2e; padding-bottom: 8px; } h2 { color: #B7BCE0; font-size: 18px; margin: 32px 0 12px; border-bottom: 1px solid #151F52; padding-bottom: 8px; }
h3 { color: #64748b; font-size: 14px; margin: 16px 0 8px; } h3 { color: #8C92C4; font-size: 14px; margin: 16px 0 8px; }
.logout { float: left; background: none; border: 1px solid #333; color: #94a3b8; padding: 6px 14px; border-radius: 6px; cursor: pointer; font-size: 12px; } .logout { float: right; background: none; border: 1px solid #333; color: #B7BCE0; padding: 6px 14px; border-radius: 6px; cursor: pointer; font-size: 12px; }
.section { background: #1a1a2e; border-radius: 12px; padding: 20px; margin-bottom: 16px; } .section { background: #151F52; border-radius: 12px; padding: 20px; margin-bottom: 16px; }
.grid { display: grid; grid-template-columns: 1fr 1fr; gap: 12px; } .grid { display: grid; grid-template-columns: 1fr 1fr; gap: 12px; }
@media(max-width:600px) { .grid { grid-template-columns: 1fr; } } @media(max-width:600px) { .grid { grid-template-columns: 1fr; } }
.field { margin-bottom: 12px; } .field { margin-bottom: 12px; }
.field label { display: block; font-size: 12px; color: #94a3b8; margin-bottom: 4px; font-weight: 600; } .field label { display: block; font-size: 12px; color: #B7BCE0; margin-bottom: 4px; font-weight: 600; }
.field .hint { font-size: 10px; color: #64748b; margin-top: 2px; } .field .hint { font-size: 10px; color: #8C92C4; margin-top: 2px; }
input[type="color"] { width: 48px; height: 36px; border: 1px solid #333; border-radius: 6px; background: #2a2a4a; cursor: pointer; } input[type="color"] { width: 48px; height: 36px; border: 1px solid #333; border-radius: 6px; background: #090E2C; cursor: pointer; }
input[type="text"], input[type="number"], select { width: 100%; padding: 8px 12px; background: #2a2a4a; border: 1px solid #333; border-radius: 6px; color: #fff; font-size: 13px; } input[type="text"], input[type="number"], select { width: 100%; padding: 8px 12px; background: #090E2C; border: 1px solid #333; border-radius: 6px; color: #fff; font-size: 13px; }
.color-row { display: flex; align-items: center; gap: 8px; } .color-row { display: flex; align-items: center; gap: 8px; }
.color-row input[type="text"] { flex: 1; } .color-row input[type="text"] { flex: 1; }
.upload-box { border: 2px dashed #333; border-radius: 8px; padding: 16px; text-align: center; cursor: pointer; transition: border-color 0.2s; } .upload-box { border: 2px dashed #333; border-radius: 8px; padding: 16px; text-align: center; cursor: pointer; transition: border-color 0.2s; }
...@@ -152,52 +153,77 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_FILES['asset'])) { ...@@ -152,52 +153,77 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_FILES['asset'])) {
.upload-box .size-hint { font-size: 11px; color: #E4AC38; font-weight: 600; margin-top: 4px; } .upload-box .size-hint { font-size: 11px; color: #E4AC38; font-weight: 600; margin-top: 4px; }
.upload-box .current { margin-top: 8px; } .upload-box .current { margin-top: 8px; }
.upload-box img { max-width: 80px; max-height: 80px; border-radius: 4px; } .upload-box img { max-width: 80px; max-height: 80px; border-radius: 4px; }
button[type="submit"] { background: linear-gradient(135deg, #E4AC38, #FFCC66); color: #1a1a1a; border: none; padding: 12px 32px; border-radius: 8px; font-weight: 700; font-size: 14px; cursor: pointer; margin-top: 16px; } button[type="submit"] { background: #C41E2E; color: #F7F4EC; border: none; padding: 12px 32px; border-radius: 8px; font-weight: 700; font-size: 14px; cursor: pointer; margin-top: 16px; }
.saved { background: #34D399; color: #000; padding: 8px 16px; border-radius: 6px; margin: 8px 0; font-size: 13px; font-weight: 600; } .saved { background: #34D399; color: #000; padding: 8px 16px; border-radius: 6px; margin: 8px 0; font-size: 13px; font-weight: 600; }
.range-row { display:flex; align-items:center; gap:8px; } .range-row { display:flex; align-items:center; gap:8px; }
.range-row input[type="range"] { flex:1; height:6px; -webkit-appearance:none; background:#2a2a4a; border-radius:3px; outline:none; } .range-row input[type="range"] { flex:1; height:6px; -webkit-appearance:none; background:#090E2C; border-radius:3px; outline:none; }
.range-row input[type="range"]::-webkit-slider-thumb { -webkit-appearance:none; width:16px; height:16px; border-radius:50%; background:#E4AC38; cursor:pointer; } .range-row input[type="range"]::-webkit-slider-thumb { -webkit-appearance:none; width:16px; height:16px; border-radius:50%; background:#E4AC38; cursor:pointer; }
.range-row .range-val { min-width:40px; text-align:right; font-size:12px; color:#E4AC38; font-family:'IBM Plex Mono',monospace; } .range-row .range-val { min-width:40px; text-align:right; font-size:12px; color:#E4AC38; font-variant-numeric:tabular-nums; }
</style> .note { color: #8C92C4; font-size: 13px; margin: -4px 0 16px; max-width: 70ch; }
.slot { color: #8C92C4; font-weight: 400; font-size: 11px; margin-left: 6px; }
.state { font-size: 11px; color: #3DBE7E; margin-top: 4px; }
input[type="color"] { border-color: rgba(150,165,240,.28); }
.section { border: 1px solid rgba(150,165,240,.14); }
</style>
</head> </head>
<body> <body>
<div class="container"> <div class="container">
<form method="POST" style="display:inline;"><button class="logout" name="logout" value="1">Logout</button></form> <form method="POST" style="display:inline;"><button class="logout" name="logout" value="1">Logout</button></form>
<h1>🎨 EL3AB Branding Admin</h1> <h1>EL3AB Branding</h1>
<p style="color:#64748b;font-size:13px;margin-bottom:24px;">Visual identity management — colors, icons, and assets appear as defaults for all players</p> <p style="color:#64748b;font-size:13px;margin-bottom:24px;">Visual identity management — colors, icons, and assets appear as defaults for all players</p>
<?php if (isset($_POST['save_theme'])): ?><div class="saved">✓ Changes saved</div><?php endif; ?> <?php if (isset($_POST['save_theme'])): ?><div class="saved">Changes saved</div><?php endif; ?>
<form method="POST"> <form method="POST">
<input type="hidden" name="save_theme" value="1"> <input type="hidden" name="save_theme" value="1">
<!-- COLORS --> <!-- THE CAFE DECK: COLOURS -->
<h2>🎨 Core Colors</h2> <h2>The Cafe Deck: colours</h2>
<div class="section"> <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>
<div class="grid">
<?php <?php
$colors = [ $deckGroups = [
['key' => 'bg_deep', 'label' => 'Deep Background', 'default' => '#050810', 'hint' => 'Darkest layer in the app'], 'Card back (the ground)' => [
['key' => 'bg_base', 'label' => 'Base Background', 'default' => '#0A1020', 'hint' => 'Screen background'], ['key' => 'back_950', 'label' => 'Deepest', 'default' => '#090E2C', 'hint' => 'Top bar, tab bar, insets'],
['key' => 'bg_card', 'label' => 'Card Background', 'default' => '#121A2E', 'hint' => 'Card surfaces'], ['key' => 'back_900', 'label' => 'Ground', 'default' => '#0E1540', 'hint' => 'Every screen sits on this'],
['key' => 'bg_elevated', 'label' => 'Elevated Background', 'default' => '#1A2440', 'hint' => 'Inputs & elevated elements'], ['key' => 'back_800', 'label' => 'Raised', 'default' => '#151F52', 'hint' => 'Panels and purse chips'],
['key' => 'gold', 'label' => 'Gold (Brand)', 'default' => '#E4AC38', 'hint' => 'Primary brand, buttons, rewards'], ['key' => 'back_700', 'label' => 'Face-down card', 'default' => '#1D2963', 'hint' => 'Secondary buttons, highlighted rows'],
['key' => 'gold_soft', 'label' => 'Gold Light', 'default' => '#FFCC66', 'hint' => 'Gold button gradient end'], ['key' => 'back_600', 'label' => 'Card back print', 'default' => '#283679', 'hint' => 'Face-down cards, pressed states'],
['key' => 'blue', 'label' => 'Blue', 'default' => '#2082F0', 'hint' => 'Links, competitive actions'], ['key' => 'back_500', 'label' => 'Strong line', 'default' => '#3A4A9C', 'hint' => 'Rare emphasis on the ground'],
['key' => 'cyan', 'label' => 'Cyan', 'default' => '#00FFFF', 'hint' => 'Glow effects'], ],
['key' => 'purple', 'label' => 'Purple', 'default' => '#6834BE', 'hint' => 'Rare/Legendary tier'], 'Card stock and inks' => [
['key' => 'success', 'label' => 'Success / Win', 'default' => '#34D399', 'hint' => 'Win & success states'], ['key' => 'card', 'label' => 'Card stock', 'default' => '#F7F4EC', 'hint' => 'Card faces, sheets, toasts'],
['key' => 'error', 'label' => 'Error / Loss', 'default' => '#EF4444', 'hint' => 'Loss & error states'], ['key' => 'card_2', 'label' => 'Card shade', 'default' => '#EEE9DD', 'hint' => 'Pressed card faces'],
['key' => 'text_primary', 'label' => 'Primary Text', 'default' => '#F8FAFC', 'hint' => 'Main text color'], ['key' => 'card_line', 'label' => 'Inner frame', 'default' => '#D6CEBC', 'hint' => 'The printed frame inside every card'],
['key' => 'text_secondary', 'label' => 'Secondary Text', 'default' => '#94A3B8', 'hint' => 'Muted/secondary text'], ['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'],
],
'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'],
],
'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)'],
],
]; ];
foreach ($colors as $c): foreach ($deckGroups as $groupName => $colors): ?>
$val = $theme[$c['key']] ?? $c['default']; <h3><?= $groupName ?></h3>
?> <div class="section">
<div class="grid">
<?php foreach ($colors as $c): $val = $theme[$c['key']] ?? $c['default']; ?>
<div class="field"> <div class="field">
<label><?= $c['label'] ?></label> <label><?= $c['label'] ?></label>
<div class="color-row"> <div class="color-row">
<input type="color" name="theme[<?= $c['key'] ?>]" value="<?= $val ?>"> <input type="color" value="<?= $val ?>">
<input type="text" name="theme[<?= $c['key'] ?>]" value="<?= $val ?>" pattern="#[0-9A-Fa-f]{6}"> <input type="text" name="theme[<?= $c['key'] ?>]" value="<?= $val ?>" pattern="#[0-9A-Fa-f]{6}">
</div> </div>
<div class="hint"><?= $c['hint'] ?></div> <div class="hint"><?= $c['hint'] ?></div>
...@@ -205,289 +231,25 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_FILES['asset'])) { ...@@ -205,289 +231,25 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_FILES['asset'])) {
<?php endforeach; ?> <?php endforeach; ?>
</div> </div>
</div> </div>
<!-- LAYOUT: HUD BAR -->
<h2>📐 HUD Bar Layout</h2>
<div class="section">
<div class="grid">
<?php
$hudLayout = [
['key' => 'hud_height', 'label' => 'HUD Height', 'default' => '56', 'min' => 40, 'max' => 80, 'hint' => 'Top bar height (px)'],
['key' => 'hud_avatar_size', 'label' => 'Avatar Size', 'default' => '34', 'min' => 24, 'max' => 56, 'hint' => 'Profile avatar circle'],
['key' => 'hud_avatar_border', 'label' => 'Avatar Border', 'default' => '2', 'min' => 0, 'max' => 6, 'hint' => 'Gold ring width'],
['key' => 'hud_icon_size', 'label' => 'Coin/Gem Icon', 'default' => '16', 'min' => 10, 'max' => 28, 'hint' => 'HUD emoji icon size'],
['key' => 'hud_bell_size', 'label' => 'Bell Icon', 'default' => '18', 'min' => 12, 'max' => 32, 'hint' => 'Notification bell'],
['key' => 'hud_stat_font', 'label' => 'Stat Font Size', 'default' => '14', 'min' => 10, 'max' => 22, 'hint' => 'Coin/gem numbers'],
['key' => 'hud_stat_gap', 'label' => 'Stats Gap', 'default' => '16', 'min' => 4, 'max' => 32, 'hint' => 'Space between coin & gem'],
['key' => 'hud_btn_size', 'label' => 'Action Button', 'default' => '44', 'min' => 32, 'max' => 64, 'hint' => 'Bell button size'],
['key' => 'hud_badge_size', 'label' => 'Badge Size', 'default' => '16', 'min' => 10, 'max' => 24, 'hint' => 'Notification dot'],
['key' => 'hud_badge_font', 'label' => 'Badge Font', 'default' => '9', 'min' => 7, 'max' => 14, 'hint' => 'Badge number size'],
['key' => 'hud_padding_x', 'label' => 'Horizontal Padding', 'default' => '16', 'min' => 8, 'max' => 32, 'hint' => 'Left/right padding'],
];
foreach ($hudLayout as $s):
$val = $theme[$s['key']] ?? $s['default'];
?>
<div class="field">
<label><?= $s['label'] ?></label>
<div class="range-row">
<input type="range" name="theme[<?= $s['key'] ?>]" value="<?= $val ?>" min="<?= $s['min'] ?>" max="<?= $s['max'] ?>" oninput="this.nextElementSibling.textContent=this.value+'px'">
<span class="range-val"><?= $val ?>px</span>
</div>
<div class="hint"><?= $s['hint'] ?></div>
</div>
<?php endforeach; ?>
</div>
</div>
<!-- LAYOUT: TAB BAR -->
<h2>📐 Tab Bar Layout</h2>
<div class="section">
<div class="grid">
<?php
$tabLayout = [
['key' => 'tab_height', 'label' => 'Tab Bar Height', 'default' => '64', 'min' => 48, 'max' => 88, 'hint' => 'Bottom bar height'],
['key' => 'tab_icon_size', 'label' => 'Tab Icon Size', 'default' => '24', 'min' => 16, 'max' => 36, 'hint' => 'Icon width/height'],
['key' => 'tab_label_font', 'label' => 'Tab Label Font', 'default' => '11', 'min' => 8, 'max' => 16, 'hint' => 'Text under icons'],
['key' => 'tab_item_gap', 'label' => 'Icon-Label Gap', 'default' => '3', 'min' => 0, 'max' => 12, 'hint' => 'Between icon & text'],
['key' => 'tab_item_padding_x', 'label' => 'Item Padding X', 'default' => '12', 'min' => 4, 'max' => 24, 'hint' => 'Horizontal padding'],
['key' => 'tab_item_padding_y', 'label' => 'Item Padding Y', 'default' => '8', 'min' => 2, 'max' => 20, 'hint' => 'Vertical padding'],
['key' => 'tab_item_min_size', 'label' => 'Touch Target', 'default' => '48', 'min' => 36, 'max' => 64, 'hint' => 'Min tap area'],
];
foreach ($tabLayout as $s):
$val = $theme[$s['key']] ?? $s['default'];
?>
<div class="field">
<label><?= $s['label'] ?></label>
<div class="range-row">
<input type="range" name="theme[<?= $s['key'] ?>]" value="<?= $val ?>" min="<?= $s['min'] ?>" max="<?= $s['max'] ?>" oninput="this.nextElementSibling.textContent=this.value+'px'">
<span class="range-val"><?= $val ?>px</span>
</div>
<div class="hint"><?= $s['hint'] ?></div>
</div>
<?php endforeach; ?>
</div>
</div>
<!-- LAYOUT: HOME PAGE -->
<h2>📐 Home Page Layout</h2>
<div class="section">
<div class="grid">
<?php
$homeLayout = [
['key' => 'home_padding', 'label' => 'Page Padding', 'default' => '16', 'min' => 8, 'max' => 32, 'hint' => 'Content edge spacing'],
['key' => 'home_max_width', 'label' => 'Content Max Width', 'default' => '340', 'min' => 280, 'max' => 500, 'hint' => 'Grid/widget max width'],
['key' => 'home_greeting_font', 'label' => 'Greeting Font', 'default' => '16', 'min' => 12, 'max' => 24, 'hint' => 'Welcome text size'],
['key' => 'home_greeting_margin', 'label' => 'Greeting Margin', 'default' => '12', 'min' => 4, 'max' => 32, 'hint' => 'Space below greeting'],
];
foreach ($homeLayout as $s):
$val = $theme[$s['key']] ?? $s['default'];
?>
<div class="field">
<label><?= $s['label'] ?></label>
<div class="range-row">
<input type="range" name="theme[<?= $s['key'] ?>]" value="<?= $val ?>" min="<?= $s['min'] ?>" max="<?= $s['max'] ?>" oninput="this.nextElementSibling.textContent=this.value+'px'">
<span class="range-val"><?= $val ?>px</span>
</div>
<div class="hint"><?= $s['hint'] ?></div>
</div>
<?php endforeach; ?>
</div>
</div>
<!-- LAYOUT: QUICK ACTIONS -->
<h2>📐 Quick Action Buttons</h2>
<div class="section">
<div class="grid">
<?php
$quickLayout = [
['key' => 'quick_btn_icon_size', 'label' => 'Icon Box Size', 'default' => '42', 'min' => 28, 'max' => 64, 'hint' => 'Circle icon container'],
['key' => 'quick_btn_icon_radius', 'label' => 'Icon Radius', 'default' => '12', 'min' => 4, 'max' => 32, 'hint' => 'Corner rounding'],
['key' => 'quick_btn_icon_font', 'label' => 'Icon Font Size', 'default' => '20', 'min' => 12, 'max' => 36, 'hint' => 'Emoji/icon in circle'],
['key' => 'quick_btn_label_font', 'label' => 'Label Font', 'default' => '10', 'min' => 7, 'max' => 16, 'hint' => 'Text under icon'],
['key' => 'quick_btn_gap', 'label' => 'Icon-Label Gap', 'default' => '8', 'min' => 0, 'max' => 16, 'hint' => 'Space inside button'],
['key' => 'quick_btn_padding', 'label' => 'Button Padding', 'default' => '6', 'min' => 0, 'max' => 16, 'hint' => 'Outer padding'],
['key' => 'quick_row_gap', 'label' => 'Row Gap', 'default' => '8', 'min' => 4, 'max' => 24, 'hint' => 'Between buttons'],
['key' => 'quick_row_margin', 'label' => 'Row Bottom Margin', 'default' => '16', 'min' => 4, 'max' => 32, 'hint' => 'Below quick actions'],
];
foreach ($quickLayout as $s):
$val = $theme[$s['key']] ?? $s['default'];
?>
<div class="field">
<label><?= $s['label'] ?></label>
<div class="range-row">
<input type="range" name="theme[<?= $s['key'] ?>]" value="<?= $val ?>" min="<?= $s['min'] ?>" max="<?= $s['max'] ?>" oninput="this.nextElementSibling.textContent=this.value+'px'">
<span class="range-val"><?= $val ?>px</span>
</div>
<div class="hint"><?= $s['hint'] ?></div>
</div>
<?php endforeach; ?>
</div>
</div>
<!-- LAYOUT: GAME TILES -->
<h2>📐 Game Tiles</h2>
<div class="section">
<div class="grid">
<?php
$tileLayout = [
['key' => 'game_tile_radius', 'label' => 'Tile Radius', 'default' => '18', 'min' => 0, 'max' => 40, 'hint' => 'Corner rounding'],
['key' => 'game_tile_gap', 'label' => 'Grid Gap', 'default' => '12', 'min' => 4, 'max' => 24, 'hint' => 'Space between tiles'],
['key' => 'game_tile_icon_size', 'label' => 'Icon Size (asset)', 'default' => '48', 'min' => 24, 'max' => 80, 'hint' => 'Game icon img size'],
['key' => 'game_tile_icon_font', 'label' => 'Icon Size (emoji)', 'default' => '42', 'min' => 20, 'max' => 72, 'hint' => 'Fallback emoji size'],
['key' => 'game_tile_name_font', 'label' => 'Name Font', 'default' => '16', 'min' => 10, 'max' => 24, 'hint' => 'Game title text'],
['key' => 'game_tile_content_gap', 'label' => 'Content Gap', 'default' => '8', 'min' => 0, 'max' => 20, 'hint' => 'Icon to name space'],
];
foreach ($tileLayout as $s):
$val = $theme[$s['key']] ?? $s['default'];
?>
<div class="field">
<label><?= $s['label'] ?></label>
<div class="range-row">
<input type="range" name="theme[<?= $s['key'] ?>]" value="<?= $val ?>" min="<?= $s['min'] ?>" max="<?= $s['max'] ?>" oninput="this.nextElementSibling.textContent=this.value+'px'">
<span class="range-val"><?= $val ?>px</span>
</div>
<div class="hint"><?= $s['hint'] ?></div>
</div>
<?php endforeach; ?>
<div class="field">
<label>Tile Aspect Ratio</label>
<div class="range-row">
<input type="range" name="theme[game_tile_aspect]" value="<?= $theme['game_tile_aspect'] ?? '1.1' ?>" min="0.7" max="1.6" step="0.05" oninput="this.nextElementSibling.textContent=this.value">
<span class="range-val"><?= $theme['game_tile_aspect'] ?? '1.1' ?></span>
</div>
<div class="hint">Width:height ratio (1.0 = square)</div>
</div>
</div>
</div>
<!-- LAYOUT: GAME MENU SHEET -->
<h2>📐 Game Menu (Bottom Sheet)</h2>
<div class="section">
<div class="grid">
<?php
$sheetLayout = [
['key' => 'sheet_radius', 'label' => 'Sheet Radius', 'default' => '28', 'min' => 0, 'max' => 48, 'hint' => 'Top corner rounding'],
['key' => 'sheet_padding_x', 'label' => 'Padding X', 'default' => '20', 'min' => 8, 'max' => 40, 'hint' => 'Left/right space'],
['key' => 'sheet_padding_top', 'label' => 'Padding Top', 'default' => '28', 'min' => 12, 'max' => 48, 'hint' => 'Top space'],
['key' => 'menu_close_size', 'label' => 'Close Button Size', 'default' => '36', 'min' => 24, 'max' => 56, 'hint' => 'X button circle'],
['key' => 'menu_close_font', 'label' => 'Close Icon Font', 'default' => '20', 'min' => 12, 'max' => 32, 'hint' => 'X character size'],
['key' => 'menu_header_icon_size', 'label' => 'Header Icon Size', 'default' => '42', 'min' => 28, 'max' => 64, 'hint' => 'Game icon box'],
['key' => 'menu_header_icon_radius', 'label' => 'Header Icon Radius', 'default' => '12', 'min' => 0, 'max' => 32, 'hint' => 'Icon corner rounding'],
['key' => 'menu_header_icon_font', 'label' => 'Header Icon Font', 'default' => '22', 'min' => 12, 'max' => 36, 'hint' => 'Emoji size in icon'],
['key' => 'menu_header_title_font', 'label' => 'Title Font', 'default' => '22', 'min' => 14, 'max' => 32, 'hint' => 'Game name size'],
['key' => 'menu_header_gap', 'label' => 'Header Gap', 'default' => '10', 'min' => 4, 'max' => 24, 'hint' => 'Icon to title space'],
['key' => 'menu_header_margin', 'label' => 'Header Margin', 'default' => '20', 'min' => 8, 'max' => 40, 'hint' => 'Below header space'],
];
foreach ($sheetLayout as $s):
$val = $theme[$s['key']] ?? $s['default'];
?>
<div class="field">
<label><?= $s['label'] ?></label>
<div class="range-row">
<input type="range" name="theme[<?= $s['key'] ?>]" value="<?= $val ?>" min="<?= $s['min'] ?>" max="<?= $s['max'] ?>" oninput="this.nextElementSibling.textContent=this.value+'px'">
<span class="range-val"><?= $val ?>px</span>
</div>
<div class="hint"><?= $s['hint'] ?></div>
</div>
<?php endforeach; ?>
<div class="field">
<label>Max Height</label>
<div class="range-row">
<input type="range" name="theme[sheet_max_height]" value="<?= $theme['sheet_max_height'] ?? '75' ?>" min="40" max="95" oninput="this.nextElementSibling.textContent=this.value+'vh'">
<span class="range-val"><?= $theme['sheet_max_height'] ?? '75' ?>vh</span>
</div>
<div class="hint">Sheet max viewport height</div>
</div>
<div class="field">
<label>Sheet Background</label>
<input type="color" name="theme[sheet_bg]" value="<?= $theme['sheet_bg'] ?? '#0f0f1e' ?>" class="color-input">
<div class="hint">Bottom sheet bg color</div>
</div>
</div>
</div>
<!-- LAYOUT: MENU BUTTONS -->
<h2>📐 Menu Play Buttons</h2>
<div class="section">
<div class="grid">
<?php
$gmBtnLayout = [
['key' => 'gm_btn_padding_y', 'label' => 'Button Padding Y', 'default' => '18', 'min' => 8, 'max' => 32, 'hint' => 'Top/bottom space'],
['key' => 'gm_btn_padding_x', 'label' => 'Button Padding X', 'default' => '16', 'min' => 8, 'max' => 32, 'hint' => 'Left/right space'],
['key' => 'gm_btn_gap', 'label' => 'Icon-Body Gap', 'default' => '14', 'min' => 4, 'max' => 28, 'hint' => 'Between icon & text'],
['key' => 'gm_btn_radius', 'label' => 'Button Radius', 'default' => '16', 'min' => 0, 'max' => 32, 'hint' => 'Corner rounding'],
['key' => 'gm_btn_margin_bottom', 'label' => 'Button Spacing', 'default' => '10', 'min' => 4, 'max' => 24, 'hint' => 'Between buttons'],
['key' => 'gm_btn_icon_size', 'label' => 'Icon Size', 'default' => '52', 'min' => 32, 'max' => 72, 'hint' => 'Gradient icon box'],
['key' => 'gm_btn_icon_radius', 'label' => 'Icon Radius', 'default' => '14', 'min' => 0, 'max' => 36, 'hint' => 'Icon corner rounding'],
['key' => 'gm_btn_icon_font', 'label' => 'Icon Font', 'default' => '26', 'min' => 14, 'max' => 40, 'hint' => 'Emoji in icon box'],
['key' => 'gm_btn_title_font', 'label' => 'Title Font', 'default' => '17', 'min' => 12, 'max' => 24, 'hint' => 'Button title text'],
['key' => 'gm_btn_sub_font', 'label' => 'Subtitle Font', 'default' => '11', 'min' => 8, 'max' => 16, 'hint' => 'Description text'],
['key' => 'gm_btn_arrow_font', 'label' => 'Arrow Font', 'default' => '22', 'min' => 12, 'max' => 32, 'hint' => 'Chevron size'],
];
foreach ($gmBtnLayout as $s):
$val = $theme[$s['key']] ?? $s['default'];
?>
<div class="field">
<label><?= $s['label'] ?></label>
<div class="range-row">
<input type="range" name="theme[<?= $s['key'] ?>]" value="<?= $val ?>" min="<?= $s['min'] ?>" max="<?= $s['max'] ?>" oninput="this.nextElementSibling.textContent=this.value+'px'">
<span class="range-val"><?= $val ?>px</span>
</div>
<div class="hint"><?= $s['hint'] ?></div>
</div>
<?php endforeach; ?>
</div>
</div>
<!-- LAYOUT: MENU CHIPS -->
<h2>📐 Menu Chips (Secondary)</h2>
<div class="section">
<div class="grid">
<?php
$chipLayout = [
['key' => 'gm_chip_padding_y', 'label' => 'Chip Padding Y', 'default' => '14', 'min' => 4, 'max' => 28, 'hint' => 'Top/bottom'],
['key' => 'gm_chip_padding_x', 'label' => 'Chip Padding X', 'default' => '8', 'min' => 4, 'max' => 24, 'hint' => 'Left/right'],
['key' => 'gm_chip_gap', 'label' => 'Icon-Label Gap', 'default' => '4', 'min' => 0, 'max' => 16, 'hint' => 'Inside chip'],
['key' => 'gm_chip_radius', 'label' => 'Chip Radius', 'default' => '12', 'min' => 0, 'max' => 24, 'hint' => 'Corner rounding'],
['key' => 'gm_chip_font', 'label' => 'Chip Font', 'default' => '11', 'min' => 8, 'max' => 16, 'hint' => 'Label text size'],
['key' => 'gm_chip_icon_font', 'label' => 'Chip Icon', 'default' => '18', 'min' => 12, 'max' => 32, 'hint' => 'Emoji size'],
['key' => 'gm_chips_grid_gap', 'label' => 'Grid Gap', 'default' => '8', 'min' => 4, 'max' => 20, 'hint' => 'Between chips'],
['key' => 'gm_chips_margin_top', 'label' => 'Top Margin', 'default' => '14', 'min' => 4, 'max' => 32, 'hint' => 'Above chip row'],
];
foreach ($chipLayout as $s):
$val = $theme[$s['key']] ?? $s['default'];
?>
<div class="field">
<label><?= $s['label'] ?></label>
<div class="range-row">
<input type="range" name="theme[<?= $s['key'] ?>]" value="<?= $val ?>" min="<?= $s['min'] ?>" max="<?= $s['max'] ?>" oninput="this.nextElementSibling.textContent=this.value+'px'">
<span class="range-val"><?= $val ?>px</span>
</div>
<div class="hint"><?= $s['hint'] ?></div>
</div>
<?php endforeach; ?> <?php endforeach; ?>
</div>
</div>
<!-- LAYOUT: BOT CARDS --> <!-- CHROME -->
<h2>📐 Bot Selection Cards</h2> <h2>Top bar and tab bar</h2>
<div class="section"> <div class="section">
<div class="grid"> <div class="grid">
<?php <?php
$botLayout = [ $chrome = [
['key' => 'bot_card_avatar_size', 'label' => 'Avatar Size', 'default' => '56', 'min' => 32, 'max' => 80, 'hint' => 'Bot portrait circle'], ['key' => 'hud_height', 'label' => 'Top bar height', 'default' => '56', 'min' => 48, 'max' => 72, 'hint' => 'px'],
['key' => 'bot_card_avatar_border', 'label' => 'Avatar Border', 'default' => '2', 'min' => 0, 'max' => 6, 'hint' => 'Color ring width'], ['key' => 'hud_avatar_size', 'label' => 'Avatar size', 'default' => '36', 'min' => 28, 'max' => 48, 'hint' => 'px'],
['key' => 'bot_card_name_font', 'label' => 'Name Font', 'default' => '14', 'min' => 10, 'max' => 20, 'hint' => 'Bot name size'], ['key' => 'hud_stat_font', 'label' => 'Purse numbers', 'default' => '14', 'min' => 11, 'max' => 18, 'hint' => 'px, never below 11'],
['key' => 'bot_card_sub_font', 'label' => 'Subtitle Font', 'default' => '11', 'min' => 8, 'max' => 16, 'hint' => 'Style & ELO text'], ['key' => 'hud_btn_size', 'label' => 'Bell button', 'default' => '44', 'min' => 44, 'max' => 56, 'hint' => 'px, 44 is the minimum tap size'],
['key' => 'bot_card_accent_height', 'label' => 'Top Accent Bar', 'default' => '3', 'min' => 0, 'max' => 8, 'hint' => 'Colored bar height'], ['key' => 'hud_padding_x', 'label' => 'Side padding', 'default' => '12', 'min' => 8, 'max' => 24, 'hint' => 'px'],
['key' => 'bot_grid_gap', 'label' => 'Grid Gap', 'default' => '12', 'min' => 4, 'max' => 24, 'hint' => 'Between cards'], ['key' => 'tab_height', 'label' => 'Tab bar height', 'default' => '64', 'min' => 56, 'max' => 80, 'hint' => 'px'],
['key' => 'bot_back_btn_size', 'label' => 'Back Button Size', 'default' => '44', 'min' => 32, 'max' => 56, 'hint' => 'Arrow button'], ['key' => 'tab_icon_size', 'label' => 'Tab icon size', 'default' => '24', 'min' => 20, 'max' => 32, 'hint' => 'px'],
['key' => 'bot_back_btn_font', 'label' => 'Back Button Font', 'default' => '18', 'min' => 12, 'max' => 28, 'hint' => 'Arrow character size'], ['key' => 'tab_label_font', 'label' => 'Tab label size', 'default' => '11', 'min' => 11, 'max' => 14, 'hint' => 'px, never below 11'],
['key' => 'bot_title_font', 'label' => 'Page Title Font', 'default' => '18', 'min' => 14, 'max' => 28, 'hint' => 'Select bot heading'], ['key' => 'tab_item_min_size', 'label' => 'Tab touch target', 'default' => '48', 'min' => 44, 'max' => 64, 'hint' => 'px'],
]; ];
foreach ($botLayout as $s): foreach ($chrome as $s): $val = $theme[$s['key']] ?? $s['default']; ?>
$val = $theme[$s['key']] ?? $s['default'];
?>
<div class="field"> <div class="field">
<label><?= $s['label'] ?></label> <label><?= $s['label'] ?></label>
<div class="range-row"> <div class="range-row">
...@@ -500,46 +262,17 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_FILES['asset'])) { ...@@ -500,46 +262,17 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_FILES['asset'])) {
</div> </div>
</div> </div>
<!-- GAME COLORS -->
<h2>🎮 Game Colors</h2>
<div class="section">
<div class="grid">
<?php
$gameColors = [
['key' => 'chess_primary', 'label' => 'Chess — Primary', 'default' => '#2563EB'],
['key' => 'chess_secondary', 'label' => 'Chess — Secondary', 'default' => '#F5B731'],
['key' => 'domino_primary', 'label' => 'Domino — Primary', 'default' => '#10B981'],
['key' => 'domino_secondary', 'label' => 'Domino — Secondary', 'default' => '#06B6D4'],
['key' => 'ludo_primary', 'label' => 'Ludo — Primary', 'default' => '#8B5CF6'],
['key' => 'ludo_secondary', 'label' => 'Ludo — Secondary', 'default' => '#EC4899'],
['key' => 'backgammon_primary', 'label' => 'Backgammon — Primary', 'default' => '#F59E0B'],
['key' => 'backgammon_secondary', 'label' => 'Backgammon — Secondary', 'default' => '#EF4444'],
];
foreach ($gameColors as $c):
$val = $theme[$c['key']] ?? $c['default'];
?>
<div class="field">
<label><?= $c['label'] ?></label>
<div class="color-row">
<input type="color" name="theme[<?= $c['key'] ?>]" value="<?= $val ?>">
<input type="text" name="theme[<?= $c['key'] ?>]" value="<?= $val ?>">
</div>
</div>
<?php endforeach; ?>
</div>
</div>
<!-- CHESS BOARD --> <!-- CHESS BOARD -->
<h2>♟ Chess Board Colors</h2> <h2>Chess Board Colors</h2>
<div class="section"> <div class="section">
<div class="grid"> <div class="grid">
<?php <?php
$boardColors = [ $boardColors = [
['key' => 'board_light', 'label' => 'Light Square', 'default' => '#F0D9B5', 'hint' => 'Light squares on the board'], ['key' => 'board_light', 'label' => 'Light Square', 'default' => '#EFEADF', 'hint' => 'Light squares on the board'],
['key' => 'board_dark', 'label' => 'Dark Square', 'default' => '#B58863', 'hint' => 'Dark 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' => '#CDD16A', 'hint' => 'Last move — light square'], ['key' => 'board_highlight_light', 'label' => 'Move Highlight (Light)', 'default' => '#F3DC98', 'hint' => 'Last move — light square'],
['key' => 'board_highlight_dark', 'label' => 'Move Highlight (Dark)', 'default' => '#AAA23A', 'hint' => 'Last move — dark square'], ['key' => 'board_highlight_dark', 'label' => 'Move Highlight (Dark)', 'default' => '#C7A65A', 'hint' => 'Last move — dark square'],
['key' => 'board_check', 'label' => 'King in Check', 'default' => '#FF6B6B', 'hint' => 'When the king is under attack'], ['key' => 'board_check', 'label' => 'King in Check', 'default' => '#C41E2E', 'hint' => 'When the king is under attack'],
]; ];
foreach ($boardColors as $c): foreach ($boardColors as $c):
$val = $theme[$c['key']] ?? $c['default']; $val = $theme[$c['key']] ?? $c['default'];
...@@ -557,15 +290,15 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_FILES['asset'])) { ...@@ -557,15 +290,15 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_FILES['asset'])) {
</div> </div>
<!-- LUDO BOARD COLORS --> <!-- LUDO BOARD COLORS -->
<h2>🎲 Ludo Board Colors</h2> <h2>Ludo Board Colors</h2>
<div class="section"> <div class="section">
<div class="grid"> <div class="grid">
<?php <?php
$ludoBoardColors = [ $ludoBoardColors = [
['key' => 'ludo_red', 'label' => 'Ludo Red', 'default' => '#E53935', 'hint' => 'Red player zone'], ['key' => 'ludo_red', 'label' => 'Ludo Red', 'default' => '#C41E2E', 'hint' => 'Red player zone'],
['key' => 'ludo_blue', 'label' => 'Ludo Blue', 'default' => '#1E88E5', 'hint' => 'Blue player zone'], ['key' => 'ludo_blue', 'label' => 'Ludo Blue', 'default' => '#2447B8', 'hint' => 'Blue player zone'],
['key' => 'ludo_green', 'label' => 'Ludo Green', 'default' => '#43A047', 'hint' => 'Green player zone'], ['key' => 'ludo_green', 'label' => 'Ludo Green', 'default' => '#1F7A4D', 'hint' => 'Green player zone'],
['key' => 'ludo_yellow', 'label' => 'Ludo Yellow', 'default' => '#FDD835', 'hint' => 'Yellow player zone'], ['key' => 'ludo_yellow', 'label' => 'Ludo Yellow', 'default' => '#E0A526', 'hint' => 'Yellow player zone'],
['key' => 'ludo_board_bg', 'label' => 'Board Background', 'default' => '#FAFAFA', 'hint' => 'Main board surface'], ['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_path', 'label' => 'Path Color', 'default' => '#FFFFFF', 'hint' => 'Walking path tiles'],
['key' => 'ludo_safe', 'label' => 'Safe Spot', 'default' => '#FFD700', 'hint' => 'Star/safe zone marker'], ['key' => 'ludo_safe', 'label' => 'Safe Spot', 'default' => '#FFD700', 'hint' => 'Star/safe zone marker'],
...@@ -588,25 +321,25 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_FILES['asset'])) { ...@@ -588,25 +321,25 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_FILES['asset'])) {
</div> </div>
<!-- BACKGAMMON BOARD COLORS --> <!-- BACKGAMMON BOARD COLORS -->
<h2>🎯 Backgammon Board Colors</h2> <h2>Backgammon Board Colors</h2>
<div class="section"> <div class="section">
<div class="grid"> <div class="grid">
<?php <?php
$bgBoardColors = [ $bgBoardColors = [
['key' => 'bg_board_border', 'label' => 'Board Border', 'default' => '#5C3A0E', 'hint' => 'Outer frame'], ['key' => 'bg_board_border', 'label' => 'Board Border', 'default' => '#0B1035', 'hint' => 'Outer frame'],
['key' => 'bg_board_light', 'label' => 'Board Light', 'default' => '#3D6B1E', 'hint' => 'Light felt area'], ['key' => 'bg_board_light', 'label' => 'Board Light', 'default' => '#EFEADF', 'hint' => 'Printed board field'],
['key' => 'bg_board_dark', 'label' => 'Board Dark', 'default' => '#2D5016', 'hint' => 'Dark felt area'], ['key' => 'bg_board_dark', 'label' => 'Board Dark', 'default' => '#E6DFD0', 'hint' => 'Printed board field, shade'],
['key' => 'bg_bar_dark', 'label' => 'Bar Dark', 'default' => '#2A1800', 'hint' => 'Center bar dark'], ['key' => 'bg_bar_dark', 'label' => 'Bar Dark', 'default' => '#151F52', 'hint' => 'Center bar dark'],
['key' => 'bg_bar_light', 'label' => 'Bar Light', 'default' => '#3D2B1A', 'hint' => 'Center bar light'], ['key' => 'bg_bar_light', 'label' => 'Bar Light', 'default' => '#1D2963', 'hint' => 'Center bar light'],
['key' => 'bg_bearoff', 'label' => 'Bear-off Area', 'default' => '#1A0E00', 'hint' => 'Bear-off tray'], ['key' => 'bg_bearoff', 'label' => 'Bear-off Area', 'default' => '#0B1035', 'hint' => 'Bear-off tray'],
['key' => 'bg_point_dark', 'label' => 'Dark Point', 'default' => '#8B4513', 'hint' => 'Dark triangle base'], ['key' => 'bg_point_dark', 'label' => 'Dark Point', 'default' => '#17150F', 'hint' => 'Dark triangle base'],
['key' => 'bg_point_dark_tip', 'label' => 'Dark Point Tip', 'default' => '#5C2D0A', 'hint' => 'Dark triangle tip'], ['key' => 'bg_point_dark_tip', 'label' => 'Dark Point Tip', 'default' => '#17150F', 'hint' => 'Dark triangle tip'],
['key' => 'bg_point_light', 'label' => 'Light Point', 'default' => '#D2691E', 'hint' => 'Light triangle base'], ['key' => 'bg_point_light', 'label' => 'Light Point', 'default' => '#C41E2E', 'hint' => 'Light triangle base'],
['key' => 'bg_point_light_tip', 'label' => 'Light Point Tip', 'default' => '#A0522D', 'hint' => 'Light triangle tip'], ['key' => 'bg_point_light_tip', 'label' => 'Light Point Tip', 'default' => '#C41E2E', 'hint' => 'Light triangle tip'],
['key' => 'bg_checker_white', 'label' => 'White Checker', 'default' => '#F5E6D3', 'hint' => 'White player pieces'], ['key' => 'bg_checker_white', 'label' => 'White Checker', 'default' => '#F7F4EC', 'hint' => 'White player pieces'],
['key' => 'bg_checker_black', 'label' => 'Black Checker', 'default' => '#2A2A2A', 'hint' => 'Black player pieces'], ['key' => 'bg_checker_black', 'label' => 'Black Checker', 'default' => '#17150F', 'hint' => 'Black player pieces'],
['key' => 'bg_highlight', 'label' => 'Move Highlight', 'default' => '#10B981', 'hint' => 'Valid move indicator'], ['key' => 'bg_highlight', 'label' => 'Move Highlight', 'default' => '#3DBE7E', 'hint' => 'Valid move indicator'],
['key' => 'bg_selected', 'label' => 'Selected Piece', 'default' => '#FFD700', 'hint' => 'Selected checker glow'], ['key' => 'bg_selected', 'label' => 'Selected Piece', 'default' => '#E4AC38', 'hint' => 'Selected checker glow'],
]; ];
foreach ($bgBoardColors as $c): foreach ($bgBoardColors as $c):
$val = $theme[$c['key']] ?? $c['default']; $val = $theme[$c['key']] ?? $c['default'];
...@@ -624,19 +357,19 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_FILES['asset'])) { ...@@ -624,19 +357,19 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_FILES['asset'])) {
</div> </div>
<!-- CHESS PIECES --> <!-- CHESS PIECES -->
<h2>♟ Chess Pieces</h2> <h2>Chess Pieces</h2>
<p style="color:#64748b;font-size:12px;margin-bottom:16px;">Upload PNG or SVG images for each piece — they appear on the board immediately</p> <p style="color:#64748b;font-size:12px;margin-bottom:16px;">Upload PNG or SVG images for each piece — they appear on the board immediately</p>
<div class="section"> <div class="section">
<h3 style="color:#f8fafc;margin-bottom:12px;">⬜ White Pieces</h3> <h3 style="color:#f8fafc;margin-bottom:12px;">⬜ White Pieces</h3>
<div class="grid"> <div class="grid">
<?php <?php
$whitePieces = [ $whitePieces = [
['slot' => 'chess_piece_wK', 'label' => 'White King ♔', 'w' => 128, 'h' => 128, 'hint' => 'White King'], ['slot' => 'chess_piece_wK', 'label' => 'White King', 'w' => 128, 'h' => 128, 'hint' => 'White King'],
['slot' => 'chess_piece_wQ', 'label' => 'White Queen ♕', 'w' => 128, 'h' => 128, 'hint' => 'White Queen'], ['slot' => 'chess_piece_wQ', 'label' => 'White Queen', 'w' => 128, 'h' => 128, 'hint' => 'White Queen'],
['slot' => 'chess_piece_wR', 'label' => 'White Rook ♖', 'w' => 128, 'h' => 128, 'hint' => 'White Rook'], ['slot' => 'chess_piece_wR', 'label' => 'White Rook', 'w' => 128, 'h' => 128, 'hint' => 'White Rook'],
['slot' => 'chess_piece_wB', 'label' => 'White Bishop ♗', 'w' => 128, 'h' => 128, 'hint' => 'White Bishop'], ['slot' => 'chess_piece_wB', 'label' => 'White Bishop', 'w' => 128, 'h' => 128, 'hint' => 'White Bishop'],
['slot' => 'chess_piece_wN', 'label' => 'White Knight ♘', 'w' => 128, 'h' => 128, 'hint' => 'White Knight'], ['slot' => 'chess_piece_wN', 'label' => 'White Knight', 'w' => 128, 'h' => 128, 'hint' => 'White Knight'],
['slot' => 'chess_piece_wP', 'label' => 'White Pawn ♙', 'w' => 128, 'h' => 128, 'hint' => 'White Pawn'], ['slot' => 'chess_piece_wP', 'label' => 'White Pawn', 'w' => 128, 'h' => 128, 'hint' => 'White Pawn'],
]; ];
foreach ($whitePieces as $a): foreach ($whitePieces as $a):
$current = $theme['assets'][$a['slot']] ?? null; $current = $theme['assets'][$a['slot']] ?? null;
...@@ -651,9 +384,9 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_FILES['asset'])) { ...@@ -651,9 +384,9 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_FILES['asset'])) {
<input type="file" name="asset" accept=".svg,.png,.webp" style="display:none" onchange="this.form.submit()"> <input type="file" name="asset" accept=".svg,.png,.webp" style="display:none" onchange="this.form.submit()">
<?php if ($current): ?> <?php if ($current): ?>
<div class="current"><img src="<?= $current ?>" style="width:64px;height:64px;object-fit:contain;background:#F0D9B5;border-radius:8px;padding:4px;"></div> <div class="current"><img src="<?= $current ?>" style="width:64px;height:64px;object-fit:contain;background:#F0D9B5;border-radius:8px;padding:4px;"></div>
<div style="font-size:10px;color:#34D399;margin-top:4px;">✓ Uploaded</div> <div class="state">Uploaded</div>
<?php else: ?> <?php else: ?>
📤 Click to upload Click to upload
<?php endif; ?> <?php endif; ?>
<div class="size-hint"><?= $a['w'] ?>×<?= $a['h'] ?>px — <?= $a['hint'] ?></div> <div class="size-hint"><?= $a['w'] ?>×<?= $a['h'] ?>px — <?= $a['hint'] ?></div>
</div> </div>
...@@ -666,12 +399,12 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_FILES['asset'])) { ...@@ -666,12 +399,12 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_FILES['asset'])) {
<div class="grid"> <div class="grid">
<?php <?php
$blackPieces = [ $blackPieces = [
['slot' => 'chess_piece_bK', 'label' => 'Black King ♚', 'w' => 128, 'h' => 128, 'hint' => 'Black King'], ['slot' => 'chess_piece_bK', 'label' => 'Black King', 'w' => 128, 'h' => 128, 'hint' => 'Black King'],
['slot' => 'chess_piece_bQ', 'label' => 'Black Queen ♛', 'w' => 128, 'h' => 128, 'hint' => 'Black Queen'], ['slot' => 'chess_piece_bQ', 'label' => 'Black Queen', 'w' => 128, 'h' => 128, 'hint' => 'Black Queen'],
['slot' => 'chess_piece_bR', 'label' => 'Black Rook ♜', 'w' => 128, 'h' => 128, 'hint' => 'Black Rook'], ['slot' => 'chess_piece_bR', 'label' => 'Black Rook', 'w' => 128, 'h' => 128, 'hint' => 'Black Rook'],
['slot' => 'chess_piece_bB', 'label' => 'Black Bishop ♝', 'w' => 128, 'h' => 128, 'hint' => 'Black Bishop'], ['slot' => 'chess_piece_bB', 'label' => 'Black Bishop', 'w' => 128, 'h' => 128, 'hint' => 'Black Bishop'],
['slot' => 'chess_piece_bN', 'label' => 'Black Knight ♞', 'w' => 128, 'h' => 128, 'hint' => 'Black Knight'], ['slot' => 'chess_piece_bN', 'label' => 'Black Knight', 'w' => 128, 'h' => 128, 'hint' => 'Black Knight'],
['slot' => 'chess_piece_bP', 'label' => 'Black Pawn ♟', 'w' => 128, 'h' => 128, 'hint' => 'Black Pawn'], ['slot' => 'chess_piece_bP', 'label' => 'Black Pawn', 'w' => 128, 'h' => 128, 'hint' => 'Black Pawn'],
]; ];
foreach ($blackPieces as $a): foreach ($blackPieces as $a):
$current = $theme['assets'][$a['slot']] ?? null; $current = $theme['assets'][$a['slot']] ?? null;
...@@ -686,9 +419,9 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_FILES['asset'])) { ...@@ -686,9 +419,9 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_FILES['asset'])) {
<input type="file" name="asset" accept=".svg,.png,.webp" style="display:none" onchange="this.form.submit()"> <input type="file" name="asset" accept=".svg,.png,.webp" style="display:none" onchange="this.form.submit()">
<?php if ($current): ?> <?php if ($current): ?>
<div class="current"><img src="<?= $current ?>" style="width:64px;height:64px;object-fit:contain;background:#B58863;border-radius:8px;padding:4px;"></div> <div class="current"><img src="<?= $current ?>" style="width:64px;height:64px;object-fit:contain;background:#B58863;border-radius:8px;padding:4px;"></div>
<div style="font-size:10px;color:#34D399;margin-top:4px;">✓ Uploaded</div> <div class="state">Uploaded</div>
<?php else: ?> <?php else: ?>
📤 Click to upload Click to upload
<?php endif; ?> <?php endif; ?>
<div class="size-hint"><?= $a['w'] ?>×<?= $a['h'] ?>px — <?= $a['hint'] ?></div> <div class="size-hint"><?= $a['w'] ?>×<?= $a['h'] ?>px — <?= $a['hint'] ?></div>
</div> </div>
...@@ -700,7 +433,7 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_FILES['asset'])) { ...@@ -700,7 +433,7 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_FILES['asset'])) {
<!-- BACKGAMMON SETTINGS --> <!-- BACKGAMMON SETTINGS -->
<h2>🎲 Backgammon Settings</h2> <h2>Backgammon Settings</h2>
<div class="section"> <div class="section">
<div class="grid"> <div class="grid">
<?php <?php
...@@ -726,17 +459,15 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_FILES['asset'])) { ...@@ -726,17 +459,15 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_FILES['asset'])) {
</div> </div>
<!-- ANIMATIONS --> <!-- ANIMATIONS -->
<h2>⚡ Animations & Motion</h2> <h2>Animations & Motion</h2>
<div class="section"> <div class="section">
<div class="grid"> <div class="grid">
<?php <?php
$anims = [ $anims = [
['key' => 'anim_speed', 'label' => 'Transition Speed (ms)', 'default' => '250', 'hint' => 'Default transition duration', 'type' => 'number'], ['key' => 'anim_speed', 'label' => 'Transition Speed (ms)', 'default' => '240', 'hint' => 'Default transition duration', 'type' => 'number'],
['key' => 'anim_fast', 'label' => 'Fast Speed (ms)', 'default' => '150', 'hint' => 'Buttons, feedback', 'type' => 'number'], ['key' => 'anim_fast', 'label' => 'Fast Speed (ms)', 'default' => '140', 'hint' => 'Buttons, feedback', 'type' => 'number'],
['key' => 'anim_slow', 'label' => 'Slow Speed (ms)', 'default' => '400', 'hint' => 'Scene transitions', 'type' => 'number'], ['key' => 'anim_slow', 'label' => 'Slow Speed (ms)', 'default' => '420', 'hint' => 'Scene transitions', 'type' => 'number'],
['key' => 'btn_scale', 'label' => 'Button Press Scale', 'default' => '0.92', 'hint' => '0.9 = strong press, 0.97 = soft press', 'type' => 'text'], ['key' => 'card_radius', 'label' => 'Card Radius (px)', 'default' => '14', 'hint' => 'Card corner radius', 'type' => 'number'],
['key' => 'border_radius', 'label' => 'Border Radius (px)', 'default' => '12', 'hint' => 'Button corner radius', 'type' => 'number'],
['key' => 'card_radius', 'label' => 'Card Radius (px)', 'default' => '20', 'hint' => 'Card & modal corner radius', 'type' => 'number'],
]; ];
foreach ($anims as $a): foreach ($anims as $a):
$val = $theme[$a['key']] ?? $a['default']; $val = $theme[$a['key']] ?? $a['default'];
...@@ -750,37 +481,23 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_FILES['asset'])) { ...@@ -750,37 +481,23 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_FILES['asset'])) {
</div> </div>
</div> </div>
<!-- BUTTON SHAPES --> <!-- BUTTON SHAPE -->
<h2>🔘 Button Shapes</h2> <h2>Button corners</h2>
<div class="section"> <div class="section">
<div class="grid"> <div class="grid">
<?php <?php $val = $theme['btn_radius_px'] ?? '10'; ?>
$shapes = [
['key' => 'btn_radius', 'label' => 'Primary Button Radius (px)', 'default' => '9999', 'hint' => '9999 = pill, 12 = soft rect, 0 = sharp', 'type' => 'number'],
['key' => 'btn_secondary_radius', 'label' => 'Secondary Button Radius (px)', 'default' => '9999', 'hint' => 'Cancel & option buttons', 'type' => 'number'],
['key' => 'btn_height', 'label' => 'Button Height (px)', 'default' => '44', 'hint' => 'Min button height — 44 for touch', 'type' => 'number'],
['key' => 'btn_font_weight', 'label' => 'Button Font Weight', 'default' => '700', 'hint' => '400=normal, 600=semi, 700=bold, 800=extra', 'type' => 'number'],
['key' => 'btn_shadow', 'label' => 'Primary Button Shadow', 'default' => '0 4px 16px rgba(228,172,56,0.3)', 'hint' => 'Full CSS box-shadow', 'type' => 'text'],
['key' => 'card_border_width', 'label' => 'Card Border Width (px)', 'default' => '1', 'hint' => '0 = none, 1 = subtle, 2 = visible', 'type' => 'number'],
['key' => 'input_radius', 'label' => 'Input Field Radius (px)', 'default' => '12', 'hint' => 'Text & password fields', 'type' => 'number'],
];
foreach ($shapes as $s):
$val = $theme[$s['key']] ?? $s['default'];
?>
<div class="field"> <div class="field">
<label><?= $s['label'] ?></label> <label>Button corner radius (px)</label>
<input type="<?= $s['type'] ?>" name="theme[<?= $s['key'] ?>]" value="<?= $val ?>"> <input type="number" name="theme[btn_radius_px]" value="<?= $val ?>">
<div class="hint"><?= $s['hint'] ?></div> <div class="hint">Printed card corners: 10 by default. Pills do not belong in this world.</div>
</div> </div>
<?php endforeach; ?>
</div> </div>
</div> </div>
<button type="submit">💾 Save All Changes</button>
</form> </form>
<!-- JUICE SETTINGS --> <!-- JUICE SETTINGS -->
<h2>🧃 Juice Effects</h2> <h2>Juice Effects</h2>
<div class="section"> <div class="section">
<div class="grid"> <div class="grid">
<?php <?php
...@@ -807,16 +524,16 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_FILES['asset'])) { ...@@ -807,16 +524,16 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_FILES['asset'])) {
</div> </div>
<!-- LUDO PAWNS --> <!-- LUDO PAWNS -->
<h2>🎯 Ludo Pawns</h2> <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:#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>
<div class="section"> <div class="section">
<div class="grid"> <div class="grid">
<?php <?php
$ludoPawns = [ $ludoPawns = [
['slot' => 'ludo_pawn_red', 'label' => '🔴 Red Pawn', 'w' => 64, 'h' => 64, 'hint' => 'Player 1 — Red pawn sprite'], ['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_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_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_pawn_blue', 'label' => 'Blue Pawn', 'w' => 64, 'h' => 64, 'hint' => 'Player 4 — Blue pawn sprite'],
]; ];
foreach ($ludoPawns as $a): foreach ($ludoPawns as $a):
$current = $theme['assets'][$a['slot']] ?? null; $current = $theme['assets'][$a['slot']] ?? null;
...@@ -831,9 +548,9 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_FILES['asset'])) { ...@@ -831,9 +548,9 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_FILES['asset'])) {
<input type="file" name="asset" accept=".svg,.png,.webp" style="display:none" onchange="this.form.submit()"> <input type="file" name="asset" accept=".svg,.png,.webp" style="display:none" onchange="this.form.submit()">
<?php if ($current): ?> <?php if ($current): ?>
<div class="current"><img src="<?= $current ?>" style="width:48px;height:48px;object-fit:contain;background:#2a2a4a;border-radius:8px;padding:4px;"></div> <div class="current"><img src="<?= $current ?>" style="width:48px;height:48px;object-fit:contain;background:#2a2a4a;border-radius:8px;padding:4px;"></div>
<div style="font-size:10px;color:#34D399;margin-top:4px;">✓ Uploaded</div> <div class="state">Uploaded</div>
<?php else: ?> <?php else: ?>
📤 Click to upload Click to upload
<?php endif; ?> <?php endif; ?>
<div class="size-hint"><?= $a['w'] ?>×<?= $a['h'] ?>px — <?= $a['hint'] ?></div> <div class="size-hint"><?= $a['w'] ?>×<?= $a['h'] ?>px — <?= $a['hint'] ?></div>
</div> </div>
...@@ -844,39 +561,39 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_FILES['asset'])) { ...@@ -844,39 +561,39 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_FILES['asset'])) {
</div> </div>
<!-- SOUND EFFECTS --> <!-- SOUND EFFECTS -->
<h2>🔊 Sound Effects</h2> <h2>Sound Effects</h2>
<p style="color:#64748b;font-size:12px;margin-bottom:16px;">Upload MP3/OGG/WAV to replace generated sounds. Default is synthesized electronic audio.</p> <p style="color:#64748b;font-size:12px;margin-bottom:16px;">Upload MP3/OGG/WAV to replace generated sounds. Default is synthesized electronic audio.</p>
<div class="section"> <div class="section">
<div class="grid"> <div class="grid">
<?php <?php
$soundSlots = [ $soundSlots = [
['slot' => 'sfx_click', 'label' => '🖱️ Click', 'hint' => 'UI buttons — very short'], ['slot' => 'sfx_click', 'label' => 'Click', 'hint' => 'UI buttons — very short'],
['slot' => 'sfx_move', 'label' => '♟ Move', 'hint' => 'Chess/Ludo piece move'], ['slot' => 'sfx_move', 'label' => 'Move', 'hint' => 'Chess/Ludo piece move'],
['slot' => 'sfx_capture', 'label' => '⚔️ Capture', 'hint' => 'Capturing a piece'], ['slot' => 'sfx_capture', 'label' => 'Capture', 'hint' => 'Capturing a piece'],
['slot' => 'sfx_check', 'label' => '⚠️ Check', 'hint' => 'King under attack'], ['slot' => 'sfx_check', 'label' => 'Check', 'hint' => 'King under attack'],
['slot' => 'sfx_castle', 'label' => '🏰 Castle', 'hint' => 'Chess castling move'], ['slot' => 'sfx_castle', 'label' => 'Castle', 'hint' => 'Chess castling move'],
['slot' => 'sfx_gameOver', 'label' => '🏁 Game Over', 'hint' => 'Match end'], ['slot' => 'sfx_gameOver', 'label' => 'Game Over', 'hint' => 'Match end'],
['slot' => 'sfx_win', 'label' => '🏆 Win', 'hint' => 'Victory sound'], ['slot' => 'sfx_win', 'label' => 'Win', 'hint' => 'Victory sound'],
['slot' => 'sfx_lose', 'label' => '💀 Lose', 'hint' => 'Defeat sound'], ['slot' => 'sfx_lose', 'label' => 'Lose', 'hint' => 'Defeat sound'],
['slot' => 'sfx_coin', 'label' => '🪙 Coin', 'hint' => 'Earn coins — short metallic'], ['slot' => 'sfx_coin', 'label' => 'Coin', 'hint' => 'Earn coins — short metallic'],
['slot' => 'sfx_levelUp', 'label' => '⬆️ Level Up', 'hint' => 'Level up fanfare'], ['slot' => 'sfx_levelUp', 'label' => '⬆️ Level Up', 'hint' => 'Level up fanfare'],
['slot' => 'sfx_notification', 'label' => '🔔 Notification', 'hint' => 'Incoming notification'], ['slot' => 'sfx_notification', 'label' => 'Notification', 'hint' => 'Incoming notification'],
['slot' => 'sfx_dice', 'label' => '🎲 Dice Roll', 'hint' => 'Ludo dice roll'], ['slot' => 'sfx_dice', 'label' => 'Dice Roll', 'hint' => 'Ludo dice roll'],
['slot' => 'sfx_place', 'label' => '📍 Place', 'hint' => 'Domino tile placed'], ['slot' => 'sfx_place', 'label' => 'Place', 'hint' => 'Domino tile placed'],
['slot' => 'sfx_reward', 'label' => '🎁 Reward', 'hint' => 'Daily reward open'], ['slot' => 'sfx_reward', 'label' => 'Reward', 'hint' => 'Daily reward open'],
['slot' => 'sfx_match_found', 'label' => '🎯 Match Found', 'hint' => 'Opponent paired'], ['slot' => 'sfx_match_found', 'label' => 'Match Found', 'hint' => 'Opponent paired'],
['slot' => 'sfx_dice_shake', 'label' => '🎲 Dice Shake', 'hint' => 'Shake before throw — Ludo'], ['slot' => 'sfx_dice_shake', 'label' => 'Dice Shake', 'hint' => 'Shake before throw — Ludo'],
['slot' => 'sfx_boost', 'label' => '🚀 Boost / Six', 'hint' => 'Rolled 6 — extra turn — Ludo'], ['slot' => 'sfx_boost', 'label' => 'Boost / Six', 'hint' => 'Rolled 6 — extra turn — Ludo'],
['slot' => 'sfx_piece_home', 'label' => '🏠 Piece Home', 'hint' => 'Piece reached center — Ludo'], ['slot' => 'sfx_piece_home', 'label' => 'Piece Home', 'hint' => 'Piece reached center — Ludo'],
['slot' => 'sfx_turn_start', 'label' => '🔔 Turn Start', 'hint' => 'Your turn alert — Ludo/Domino'], ['slot' => 'sfx_turn_start', 'label' => 'Turn Start', 'hint' => 'Your turn alert — Ludo/Domino'],
['slot' => 'sfx_emote', 'label' => '💬 Emote', 'hint' => 'Sending emoji/social phrase'], ['slot' => 'sfx_emote', 'label' => 'Emote', 'hint' => 'Sending emoji/social phrase'],
['slot' => 'sfx_bg_dice_roll', 'label' => '🎲 BG Dice Roll', 'hint' => 'Backgammon dice roll'], ['slot' => 'sfx_bg_dice_roll', 'label' => 'BG Dice Roll', 'hint' => 'Backgammon dice roll'],
['slot' => 'sfx_bg_piece_move', 'label' => '♟️ BG Piece Move', 'hint' => 'Move piece on board — Backgammon'], ['slot' => 'sfx_bg_piece_move', 'label' => 'BG Piece Move', 'hint' => 'Move piece on board — Backgammon'],
['slot' => 'sfx_bg_piece_hit', 'label' => '💥 BG Hit', 'hint' => 'Send opponent to bar — Backgammon'], ['slot' => 'sfx_bg_piece_hit', 'label' => 'BG Hit', 'hint' => 'Send opponent to bar — Backgammon'],
['slot' => 'sfx_bg_bear_off', 'label' => '✅ BG Bear Off', 'hint' => 'Bear off piece — Backgammon'], ['slot' => 'sfx_bg_bear_off', 'label' => 'BG Bear Off', 'hint' => 'Bear off piece — Backgammon'],
['slot' => 'sfx_bg_double', 'label' => '⬆️ BG Double', 'hint' => 'Doubling cube — Backgammon'], ['slot' => 'sfx_bg_double', 'label' => '⬆️ BG Double', 'hint' => 'Doubling cube — Backgammon'],
['slot' => 'sfx_bg_win_game', 'label' => '🎉 BG Win Game', 'hint' => 'Win a single game — Backgammon'], ['slot' => 'sfx_bg_win_game', 'label' => 'BG Win Game', 'hint' => 'Win a single game — Backgammon'],
['slot' => 'sfx_bg_win_match', 'label' => '🏆 BG Win Match', 'hint' => 'Win the match — Backgammon'], ['slot' => 'sfx_bg_win_match', 'label' => 'BG Win Match', 'hint' => 'Win the match — Backgammon'],
]; ];
foreach ($soundSlots as $s): foreach ($soundSlots as $s):
$current = $theme['assets'][$s['slot']] ?? null; $current = $theme['assets'][$s['slot']] ?? null;
...@@ -890,10 +607,10 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_FILES['asset'])) { ...@@ -890,10 +607,10 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_FILES['asset'])) {
<div class="upload-box" onclick="this.querySelector('input[type=file]').click()"> <div class="upload-box" onclick="this.querySelector('input[type=file]').click()">
<input type="file" name="asset" accept=".mp3,.ogg,.wav,.m4a,.webm" style="display:none" onchange="this.form.submit()"> <input type="file" name="asset" accept=".mp3,.ogg,.wav,.m4a,.webm" style="display:none" onchange="this.form.submit()">
<?php if ($current): ?> <?php if ($current): ?>
<div style="font-size:11px;color:#34D399;">✓ Uploaded</div> <div class="state">Uploaded</div>
<audio controls src="<?= $current ?>" style="width:100%;height:28px;margin-top:4px;"></audio> <audio controls src="<?= $current ?>" style="width:100%;height:28px;margin-top:4px;"></audio>
<?php else: ?> <?php else: ?>
🔊 Click to upload audio Click to upload audio
<?php endif; ?> <?php endif; ?>
<div class="size-hint"><?= $s['hint'] ?></div> <div class="size-hint"><?= $s['hint'] ?></div>
</div> </div>
...@@ -903,120 +620,49 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_FILES['asset'])) { ...@@ -903,120 +620,49 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_FILES['asset'])) {
</div> </div>
</div> </div>
<!-- EMOJI REPLACEMENTS --> <!-- ICON OVERRIDES -->
<h2>😀 Emoji Replacements</h2> <h2>Icon overrides</h2>
<p style="color:#64748b;font-size:12px;margin-bottom:16px;">Replace any emoji with SVG or PNG — shown at the same size without distortion</p> <p class="note">The app draws its own icon family (public/js/core/icons.js). Upload an SVG here only to replace one; it is shown at the icon's exact size. The old emoji slots are no longer read.</p>
<div class="section"> <div class="section">
<div class="grid"> <div class="grid">
<?php <?php
$emojiSlots = [ $iconSlots = [
['key' => 'trophy', 'emoji' => '🏆', 'label' => 'Win Trophy', 'size' => 72, 'hint' => 'Win result screen'], ['key' => 'crown', 'label' => 'Crown', 'hint' => 'Level, first place, splash'],
['key' => 'skull', 'emoji' => '💀', 'label' => 'Loss Icon', 'size' => 72, 'hint' => 'Loss result screen'], ['key' => 'coin', 'label' => 'Coin', 'hint' => 'Top bar purse, rewards, shop'],
['key' => 'handshake', 'emoji' => '🤝', 'label' => 'Draw Icon', 'size' => 72, 'hint' => 'Draw result screen'], ['key' => 'gem', 'label' => 'Gem', 'hint' => 'Top bar purse, shop'],
['key' => 'gift', 'emoji' => '🎁', 'label' => 'Reward Box', 'size' => 48, 'hint' => 'Daily reward screen'], ['key' => 'game-chess', 'label' => 'Chess mark', 'hint' => 'Home hand, leaderboard filter'],
['key' => 'robot', 'emoji' => '🤖', 'label' => 'Bot Icon', 'size' => 32, 'hint' => 'Next to bot opponent name'], ['key' => 'game-ludo', 'label' => 'Ludo mark', 'hint' => 'Home hand, leaderboard filter'],
['key' => 'star', 'emoji' => '⭐', 'label' => 'Rating Star', 'size' => 20, 'hint' => 'Review screen'], ['key' => 'game-domino', 'label' => 'Domino mark', 'hint' => 'Home hand, leaderboard filter'],
['key' => 'dice', 'emoji' => '🎲', 'label' => 'Dice Icon', 'size' => 48, 'hint' => 'Ludo tile + roll button'], ['key' => 'game-backgammon', 'label' => 'Backgammon mark', 'hint' => 'Home hand, leaderboard filter'],
['key' => 'gamepad', 'emoji' => '🎮', 'label' => 'Single Player', 'size' => 24, 'hint' => 'Game menu — vs bot'], ['key' => 'cards', 'label' => 'Tab: Play', 'hint' => 'Tab bar'],
['key' => 'swords', 'emoji' => '⚔️', 'label' => 'Multiplayer', 'size' => 24, 'hint' => 'Game menu — online'], ['key' => 'podium', 'label' => 'Tab: Rank', 'hint' => 'Tab bar'],
['key' => 'medal_gold', 'emoji' => '🥇', 'label' => 'Gold Medal', 'size' => 20, 'hint' => '#1 in leaderboard'], ['key' => 'users', 'label' => 'Tab: Friends', 'hint' => 'Tab bar'],
['key' => 'medal_silver', 'emoji' => '🥈', 'label' => 'Silver Medal', 'size' => 20, 'hint' => '#2 in leaderboard'], ['key' => 'trophy', 'label' => 'Tab: Tournaments', 'hint' => 'Tab bar, tournaments'],
['key' => 'medal_bronze', 'emoji' => '🥉', 'label' => 'Bronze Medal', 'size' => 20, 'hint' => '#3 in leaderboard'], ['key' => 'user', 'label' => 'Tab: Profile', 'hint' => 'Tab bar, avatars without a photo'],
['key' => 'coin', 'emoji' => '🪙', 'label' => 'Coin', 'size' => 16, 'hint' => 'Next to shop prices'], ['key' => 'bell', 'label' => 'Notifications', 'hint' => 'Top bar'],
['key' => 'gem', 'emoji' => '💎', 'label' => 'Gem', 'size' => 16, 'hint' => 'Next to gem prices'], ['key' => 'gift', 'label' => 'Daily reward', 'hint' => 'Home, daily reward'],
['key' => 'person', 'emoji' => '👤', 'label' => 'Default Avatar', 'size' => 32, 'hint' => 'Default player avatar'], ['key' => 'medal', 'label' => 'Achievements', 'hint' => 'Home, achievements'],
['key' => 'people', 'emoji' => '👥', 'label' => 'Friends Icon', 'size' => 32, 'hint' => 'Empty friends screen'], ['key' => 'swords', 'label' => 'Play online / challenge', 'hint' => 'Lobby buttons'],
['key' => 'bell', 'emoji' => '🔔', 'label' => 'Notification Bell', 'size' => 18, 'hint' => 'Header notifications icon'], ['key' => 'bot', 'label' => 'Play the computer', 'hint' => 'Lobby buttons'],
['key' => 'speaker_on', 'emoji' => '🔊', 'label' => 'Sound On', 'size' => 20, 'hint' => 'Sound settings'],
['key' => 'speaker_off', 'emoji' => '🔇', 'label' => 'Sound Off', 'size' => 20, 'hint' => 'Sound settings'],
['key' => 'art', 'emoji' => '🎨', 'label' => 'Shop Icon', 'size' => 24, 'hint' => 'Shop cards'],
['key' => 'puzzle', 'emoji' => '🧩', 'label' => 'Puzzles', 'size' => 16, 'hint' => 'Puzzles menu button'],
['key' => 'chart', 'emoji' => '📊', 'label' => 'Analysis', 'size' => 16, 'hint' => 'Analysis button'],
['key' => 'clipboard', 'emoji' => '📋', 'label' => 'My Games', 'size' => 16, 'hint' => 'Match history button'],
['key' => 'share', 'emoji' => '📤', 'label' => 'Share', 'size' => 16, 'hint' => 'Share button'],
['key' => 'checkmark', 'emoji' => '✅', 'label' => 'Success mark', 'size' => 20, 'hint' => 'Operation success feedback'],
['key' => 'cross', 'emoji' => '❌', 'label' => 'Error mark', 'size' => 20, 'hint' => 'Operation failure feedback'],
['key' => 'flag', 'emoji' => '⚐', 'label' => 'Resign flag', 'size' => 16, 'hint' => 'Chess resign button'],
['key' => 'book', 'emoji' => '📖', 'label' => 'Book/Theory', 'size' => 14, 'hint' => 'Move classification: book move'],
// HUD icons
['key' => 'hud_coin', 'emoji' => '●', 'label' => 'HUD coin dot', 'size' => 16, 'hint' => 'Coin indicator in top HUD bar'],
['key' => 'hud_gem', 'emoji' => '◆', 'label' => 'HUD gem diamond', 'size' => 16, 'hint' => 'Gem indicator in top HUD bar'],
['key' => 'hud_level', 'emoji' => '⬡', 'label' => 'HUD level hex', 'size' => 16, 'hint' => 'Level indicator in top HUD bar'],
// Game specific
['key' => 'lightning', 'emoji' => '⚡', 'label' => 'Puzzle rating bolt', 'size' => 16, 'hint' => 'Puzzle difficulty indicator'],
['key' => 'best_move_star', 'emoji' => '★', 'label' => 'Best move star', 'size' => 14, 'hint' => 'Move classification: best move'],
['key' => 'particle_star', 'emoji' => '✦', 'label' => 'Particle star', 'size' => 10, 'hint' => 'Star burst particle effect'],
['key' => 'backgammon_icon', 'emoji' => '◎', 'label' => 'Backgammon game icon', 'size' => 48, 'hint' => 'Home screen backgammon tile'],
['key' => 'domino_tile', 'emoji' => '⬚', 'label' => 'Domino game icon', 'size' => 48, 'hint' => 'Home screen domino tile'],
['key' => 'ludo_hex', 'emoji' => '⬡', 'label' => 'Ludo game icon', 'size' => 48, 'hint' => 'Home screen ludo tile (if not using dice)'],
['key' => 'chess_pawn', 'emoji' => '♟', 'label' => 'Chess pawn', 'size' => 48, 'hint' => 'Home screen chess tile'],
// Social
['key' => 'challenge_swords', 'emoji' => '⚔️', 'label' => 'Challenge swords', 'size' => 20, 'hint' => 'Challenge friend to game'],
['key' => 'search_icon', 'emoji' => '🔍', 'label' => 'Search icon', 'size' => 16, 'hint' => 'Search button in social'],
['key' => 'online_dot', 'emoji' => '🟢', 'label' => 'Online indicator', 'size' => 12, 'hint' => 'Online status dot'],
// Dice faces
['key' => 'dice_1', 'emoji' => '⚀', 'label' => 'Dice face 1', 'size' => 56, 'hint' => 'Ludo dice — shown when rolled 1'],
['key' => 'dice_2', 'emoji' => '⚁', 'label' => 'Dice face 2', 'size' => 56, 'hint' => 'Ludo dice — shown when rolled 2'],
['key' => 'dice_3', 'emoji' => '⚂', 'label' => 'Dice face 3', 'size' => 56, 'hint' => 'Ludo dice — shown when rolled 3'],
['key' => 'dice_4', 'emoji' => '⚃', 'label' => 'Dice face 4', 'size' => 56, 'hint' => 'Ludo dice — shown when rolled 4'],
['key' => 'dice_5', 'emoji' => '⚄', 'label' => 'Dice face 5', 'size' => 56, 'hint' => 'Ludo dice — shown when rolled 5'],
['key' => 'dice_6', 'emoji' => '⚅', 'label' => 'Dice face 6', 'size' => 56, 'hint' => 'Ludo dice — shown when rolled 6 (golden glow)'],
// Puzzle themes
['key' => 'theme_fork', 'emoji' => '🔱', 'label' => 'Fork theme', 'size' => 16, 'hint' => 'Puzzle theme: fork'],
['key' => 'theme_pin', 'emoji' => '📌', 'label' => 'Pin theme', 'size' => 16, 'hint' => 'Puzzle theme: pin'],
['key' => 'theme_castle', 'emoji' => '🏰', 'label' => 'Back rank theme', 'size' => 16, 'hint' => 'Puzzle theme: back rank mate'],
['key' => 'theme_crown', 'emoji' => '👑', 'label' => 'Promotion theme', 'size' => 16, 'hint' => 'Puzzle theme: promotion'],
['key' => 'theme_sacrifice', 'emoji' => '💥', 'label' => 'Sacrifice theme', 'size' => 16, 'hint' => 'Puzzle theme: sacrifice'],
// Tournaments
['key' => 'tournament_cup', 'emoji' => '🏆', 'label' => 'Tournament trophy', 'size' => 20, 'hint' => 'Tournament list & leaderboard button'],
['key' => 'money_bag', 'emoji' => '💰', 'label' => 'Entry fee', 'size' => 14, 'hint' => 'Tournament entry fee indicator'],
// Misc
['key' => 'thinking_dots', 'emoji' => '●●●', 'label' => 'Bot thinking dots', 'size' => 14, 'hint' => 'Chess bot thinking indicator'],
['key' => 'building', 'emoji' => '🏛️', 'label' => 'Organization', 'size' => 32, 'hint' => 'Organization/club icon'],
// Social & Navigation
['key' => 'crown', 'emoji' => '👑', 'label' => 'Crown/Owner', 'size' => 16, 'hint' => 'Group owner / top rank badge'],
['key' => 'mute', 'emoji' => '🔇', 'label' => 'Muted user', 'size' => 16, 'hint' => 'Muted player indicator'],
['key' => 'block', 'emoji' => '🚫', 'label' => 'Blocked', 'size' => 16, 'hint' => 'Blocked user indicator'],
['key' => 'hourglass', 'emoji' => '⏳', 'label' => 'Waiting', 'size' => 24, 'hint' => 'Matchmaking / waiting state'],
['key' => 'play', 'emoji' => '▶️', 'label' => 'Play button', 'size' => 24, 'hint' => 'Start/play action'],
['key' => 'exit', 'emoji' => '🚪', 'label' => 'Exit', 'size' => 20, 'hint' => 'Leave game / exit'],
['key' => 'clock', 'emoji' => '⏱️', 'label' => 'Timer', 'size' => 16, 'hint' => 'Turn timer / time control'],
['key' => 'live', 'emoji' => '🔴', 'label' => 'Live dot', 'size' => 12, 'hint' => 'Live match indicator'],
['key' => 'calendar', 'emoji' => '📅', 'label' => 'Calendar', 'size' => 16, 'hint' => 'Tournament date/schedule'],
['key' => 'news', 'emoji' => '📢', 'label' => 'News', 'size' => 20, 'hint' => 'Announcements/news feed'],
['key' => 'group', 'emoji' => '👥', 'label' => 'Group', 'size' => 20, 'hint' => 'Groups/team section'],
['key' => 'inbox', 'emoji' => '📥', 'label' => 'Inbox', 'size' => 20, 'hint' => 'Messages inbox'],
['key' => 'sleeping', 'emoji' => '💤', 'label' => 'Offline/Sleeping', 'size' => 14, 'hint' => 'Player is offline'],
['key' => 'send', 'emoji' => '✉️', 'label' => 'Send message', 'size' => 16, 'hint' => 'Send button in chat'],
['key' => 'wave', 'emoji' => '👋', 'label' => 'Wave/Hello', 'size' => 20, 'hint' => 'Friend request greeting'],
['key' => 'camera', 'emoji' => '📷', 'label' => 'Camera', 'size' => 20, 'hint' => 'Upload photo button'],
['key' => 'arrow_back', 'emoji' => '←', 'label' => 'Back arrow', 'size' => 16, 'hint' => 'Navigation back button'],
['key' => 'globe', 'emoji' => '🌐', 'label' => 'Globe/Language', 'size' => 16, 'hint' => 'Language selector'],
['key' => 'save', 'emoji' => '💾', 'label' => 'Save', 'size' => 16, 'hint' => 'Save settings button'],
['key' => 'lock', 'emoji' => '🔒', 'label' => 'Lock', 'size' => 16, 'hint' => 'Locked/premium content'],
['key' => 'document', 'emoji' => '📄', 'label' => 'Document', 'size' => 16, 'hint' => 'Terms / policy docs'],
['key' => 'target', 'emoji' => '🎯', 'label' => 'Target/Challenge', 'size' => 20, 'hint' => 'Daily challenge objective'],
['key' => 'green_circle', 'emoji' => '🟢', 'label' => 'Online dot', 'size' => 10, 'hint' => 'Player is online'],
['key' => 'gray_circle', 'emoji' => '⚫', 'label' => 'Offline dot', 'size' => 10, 'hint' => 'Player is offline'],
['key' => 'check', 'emoji' => '✓', 'label' => 'Checkmark', 'size' => 14, 'hint' => 'Selection/completion mark'],
]; ];
foreach ($emojiSlots as $e): foreach ($iconSlots as $e):
$current = $theme['assets']['emoji_' . $e['key']] ?? null; $current = $theme['assets']['icon_' . $e['key']] ?? null;
?> ?>
<div class="field"> <div class="field">
<form method="POST" enctype="multipart/form-data"> <form method="POST" enctype="multipart/form-data">
<input type="hidden" name="slot" value="emoji_<?= $e['key'] ?>"> <input type="hidden" name="slot" value="icon_<?= $e['key'] ?>">
<input type="hidden" name="expected_w" value="<?= $e['size'] ?>"> <input type="hidden" name="expected_w" value="24">
<input type="hidden" name="expected_h" value="<?= $e['size'] ?>"> <input type="hidden" name="expected_h" value="24">
<label><?= $e['emoji'] ?> <?= $e['label'] ?></label> <label><?= $e['label'] ?> <span class="slot">icon_<?= $e['key'] ?></span></label>
<div class="upload-box" onclick="this.querySelector('input[type=file]').click()"> <div class="upload-box" onclick="this.querySelector('input[type=file]').click()">
<input type="file" name="asset" accept=".svg,.png,.jpg,.webp" style="display:none" onchange="this.form.submit()"> <input type="file" name="asset" accept=".svg,.png,.webp" style="display:none" onchange="this.form.submit()">
<?php if ($current): ?> <?php if ($current): ?>
<div class="current"><img src="<?= $current ?>" style="width:<?= min($e['size'], 48) ?>px;height:<?= min($e['size'], 48) ?>px;object-fit:contain;"></div> <div class="current"><img src="<?= $current ?>" style="width:32px;height:32px;object-fit:contain;"></div>
<div class="state">Override uploaded</div>
<?php else: ?> <?php else: ?>
<span style="font-size:24px;"><?= $e['emoji'] ?></span> → Upload replacement Using the drawn icon. Click to upload a replacement
<?php endif; ?> <?php endif; ?>
<div class="size-hint"><?= $e['size'] ?>×<?= $e['size'] ?>px — <?= $e['hint'] ?></div> <div class="size-hint">24x24 SVG preferred: <?= $e['hint'] ?></div>
</div> </div>
</form> </form>
</div> </div>
...@@ -1025,7 +671,7 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_FILES['asset'])) { ...@@ -1025,7 +671,7 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_FILES['asset'])) {
</div> </div>
<!-- ASSET UPLOADS --> <!-- ASSET UPLOADS -->
<h2>📦 Visual Assets</h2> <h2>Visual Assets</h2>
<p style="color:#64748b;font-size:12px;margin-bottom:16px;">Upload SVG or PNG — appears immediately as default for all players</p> <p style="color:#64748b;font-size:12px;margin-bottom:16px;">Upload SVG or PNG — appears immediately as default for all players</p>
<div class="section"> <div class="section">
...@@ -1036,27 +682,7 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_FILES['asset'])) { ...@@ -1036,27 +682,7 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_FILES['asset'])) {
['slot' => 'logo_icon', 'label' => 'Logo Icon (Square)', 'w' => 64, 'h' => 64, 'hint' => 'Compact logo icon'], ['slot' => 'logo_icon', 'label' => 'Logo Icon (Square)', 'w' => 64, 'h' => 64, 'hint' => 'Compact logo icon'],
['slot' => 'splash_bg', 'label' => 'Splash Background', 'w' => 1080, 'h' => 1920, 'hint' => 'Full background on app launch'], ['slot' => 'splash_bg', 'label' => 'Splash Background', 'w' => 1080, 'h' => 1920, 'hint' => 'Full background on app launch'],
['slot' => 'favicon', 'label' => 'Favicon', 'w' => 32, 'h' => 32, 'hint' => 'Browser tab icon'], ['slot' => 'favicon', 'label' => 'Favicon', 'w' => 32, 'h' => 32, 'hint' => 'Browser tab icon'],
['slot' => 'coin_icon', 'label' => 'Coin Icon (HUD)', 'w' => 24, 'h' => 24, 'hint' => 'Next to coin count in header'],
['slot' => 'gem_icon', 'label' => 'Gem Icon (HUD)', 'w' => 24, 'h' => 24, 'hint' => 'Next to gem count in header'],
['slot' => 'xp_icon', 'label' => 'Level Icon (HUD)', 'w' => 24, 'h' => 24, 'hint' => 'Next to level number'],
['slot' => 'chess_icon', 'label' => 'Chess Icon', 'w' => 64, 'h' => 64, 'hint' => 'Chess tile on home screen'],
['slot' => 'domino_icon', 'label' => 'Domino Icon', 'w' => 64, 'h' => 64, 'hint' => 'Domino tile on home screen'],
['slot' => 'ludo_icon', 'label' => 'Ludo Icon', 'w' => 64, 'h' => 64, 'hint' => 'Ludo tile on home screen'],
['slot' => 'backgammon_icon', 'label' => 'Backgammon Icon', 'w' => 64, 'h' => 64, 'hint' => 'Backgammon tile on home screen'],
['slot' => 'win_trophy', 'label' => 'Win Trophy', 'w' => 128, 'h' => 128, 'hint' => 'Match result — win'],
['slot' => 'loss_icon', 'label' => 'Loss Icon', 'w' => 128, 'h' => 128, 'hint' => 'Match result — loss'],
['slot' => 'draw_icon', 'label' => 'Draw Icon', 'w' => 128, 'h' => 128, 'hint' => 'Match result — draw'],
['slot' => 'daily_reward', 'label' => 'Daily Reward Box', 'w' => 128, 'h' => 128, 'hint' => 'Daily reward screen'],
['slot' => 'rank_gold', 'label' => 'Gold Medal (#1)', 'w' => 32, 'h' => 32, 'hint' => 'Next to #1 in leaderboard'],
['slot' => 'rank_silver', 'label' => 'Silver Medal (#2)', 'w' => 32, 'h' => 32, 'hint' => 'Next to #2 in leaderboard'],
['slot' => 'rank_bronze', 'label' => 'Bronze Medal (#3)', 'w' => 32, 'h' => 32, 'hint' => 'Next to #3 in leaderboard'],
['slot' => 'default_avatar', 'label' => 'Default Player Avatar', 'w' => 128, 'h' => 128, 'hint' => 'Shown if player has no photo'], ['slot' => 'default_avatar', 'label' => 'Default Player Avatar', 'w' => 128, 'h' => 128, 'hint' => 'Shown if player has no photo'],
['slot' => 'notification_bell', 'label' => 'Notification Bell', 'w' => 24, 'h' => 24, 'hint' => 'Header notification icon'],
['slot' => 'tab_play', 'label' => 'Tab Icon: Play', 'w' => 24, 'h' => 24, 'hint' => 'Bottom navigation bar'],
['slot' => 'tab_rank', 'label' => 'Tab Icon: Leaderboard', 'w' => 24, 'h' => 24, 'hint' => 'Bottom navigation bar'],
['slot' => 'tab_social', 'label' => 'Tab Icon: Friends', 'w' => 24, 'h' => 24, 'hint' => 'Bottom navigation bar'],
['slot' => 'tab_shop', 'label' => 'Tab Icon: Shop', 'w' => 24, 'h' => 24, 'hint' => 'Bottom navigation bar'],
['slot' => 'tab_profile', 'label' => 'Tab Icon: Profile', 'w' => 24, 'h' => 24, 'hint' => 'Bottom navigation bar'],
]; ];
foreach ($assetSlots as $a): foreach ($assetSlots as $a):
$current = $theme['assets'][$a['slot']] ?? null; $current = $theme['assets'][$a['slot']] ?? null;
...@@ -1073,9 +699,9 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_FILES['asset'])) { ...@@ -1073,9 +699,9 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_FILES['asset'])) {
<input type="file" name="asset" accept=".svg,.png,.jpg,.jpeg,.webp,.gif,.ico" style="display:none" onchange="this.form.submit()"> <input type="file" name="asset" accept=".svg,.png,.jpg,.jpeg,.webp,.gif,.ico" style="display:none" onchange="this.form.submit()">
<?php if ($current): ?> <?php if ($current): ?>
<div class="current"><img src="<?= $current ?>" style="width:<?= $displayW ?>px;height:<?= $displayH ?>px;object-fit:contain;image-rendering:auto;"></div> <div class="current"><img src="<?= $current ?>" style="width:<?= $displayW ?>px;height:<?= $displayH ?>px;object-fit:contain;image-rendering:auto;"></div>
<div style="font-size:10px;color:#64748b;margin-top:4px;">✓ Uploaded</div> <div class="state">Uploaded</div>
<?php else: ?> <?php else: ?>
📤 Click to upload Click to upload
<?php endif; ?> <?php endif; ?>
<div class="size-hint"><?= $a['w'] ?>×<?= $a['h'] ?>px — <?= $a['hint'] ?></div> <div class="size-hint"><?= $a['w'] ?>×<?= $a['h'] ?>px — <?= $a['hint'] ?></div>
</div> </div>
...@@ -1090,7 +716,7 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_FILES['asset'])) { ...@@ -1090,7 +716,7 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_FILES['asset'])) {
<!-- Sticky save bar --> <!-- Sticky save bar -->
<div id="save-bar" style="position:fixed;bottom:0;left:0;right:0;background:#0f0f1e;border-top:1px solid rgba(255,255,255,0.1);padding:12px 20px;display:flex;align-items:center;justify-content:space-between;z-index:999;"> <div id="save-bar" style="position:fixed;bottom:0;left:0;right:0;background:#0f0f1e;border-top:1px solid rgba(255,255,255,0.1);padding:12px 20px;display:flex;align-items:center;justify-content:space-between;z-index:999;">
<span id="save-status" style="font-size:12px;color:#64748b;">No unsaved changes</span> <span id="save-status" style="font-size:12px;color:#64748b;">No unsaved changes</span>
<button id="save-btn" onclick="saveAll()" style="background:linear-gradient(135deg,#E4AC38,#FFCC66);color:#1a1a1a;border:none;padding:10px 28px;border-radius:8px;font-weight:700;font-size:14px;cursor:pointer;opacity:0.5;pointer-events:none;transition:opacity 0.2s;">💾 Save All</button> <button id="save-btn" onclick="saveAll()" style="background:#C41E2E;color:#F7F4EC;border:none;padding:10px 28px;border-radius:8px;font-weight:700;font-size:14px;cursor:pointer;opacity:0.5;pointer-events:none;transition:opacity 0.2s;">Save all</button>
</div> </div>
<script> <script>
...@@ -1103,7 +729,7 @@ function markDirty() { ...@@ -1103,7 +729,7 @@ function markDirty() {
hasChanges = true; hasChanges = true;
saveBtn.style.opacity = '1'; saveBtn.style.opacity = '1';
saveBtn.style.pointerEvents = 'auto'; saveBtn.style.pointerEvents = 'auto';
saveStatus.textContent = '⚠️ Unsaved changes'; saveStatus.textContent = 'Unsaved changes';
saveStatus.style.color = '#FBBF24'; saveStatus.style.color = '#FBBF24';
} }
} }
...@@ -1131,23 +757,23 @@ function saveAll() { ...@@ -1131,23 +757,23 @@ function saveAll() {
.then(data => { .then(data => {
if (data.success) { if (data.success) {
hasChanges = false; hasChanges = false;
saveBtn.textContent = '✓ Saved!'; saveBtn.textContent = 'Saved';
saveBtn.style.opacity = '0.5'; saveBtn.style.opacity = '0.5';
saveBtn.style.pointerEvents = 'none'; saveBtn.style.pointerEvents = 'none';
saveStatus.textContent = '✓ All changes saved to database'; saveStatus.textContent = 'All changes saved';
saveStatus.style.color = '#34D399'; saveStatus.style.color = '#34D399';
setTimeout(() => { setTimeout(() => {
saveBtn.textContent = '💾 Save All'; saveBtn.textContent = 'Save all';
saveStatus.textContent = 'No unsaved changes'; saveStatus.textContent = 'No unsaved changes';
saveStatus.style.color = '#64748b'; saveStatus.style.color = '#64748b';
}, 2000); }, 2000);
} else { } else {
saveBtn.textContent = '❌ Error: ' + (data.error || 'unknown'); saveBtn.textContent = 'Error: ' + (data.error || 'unknown');
saveBtn.style.opacity = '1'; saveBtn.style.opacity = '1';
} }
}) })
.catch((err) => { .catch((err) => {
saveBtn.textContent = '❌ Network error — retry'; saveBtn.textContent = 'Network error, retry';
saveBtn.style.opacity = '1'; saveBtn.style.opacity = '1';
}); });
} }
...@@ -1158,7 +784,7 @@ document.querySelectorAll('input[name^="theme["]').forEach(input => { ...@@ -1158,7 +784,7 @@ document.querySelectorAll('input[name^="theme["]').forEach(input => {
input.addEventListener('change', markDirty); input.addEventListener('change', markDirty);
}); });
// Sync color picker ↔ text input (no save, just sync visuals) // Sync color picker and text input (no save, just sync visuals)
document.querySelectorAll('.color-row').forEach(row => { document.querySelectorAll('.color-row').forEach(row => {
const colorInput = row.querySelector('input[type="color"]'); const colorInput = row.querySelector('input[type="color"]');
const textInput = row.querySelector('input[type="text"]'); const textInput = row.querySelector('input[type="text"]');
......
...@@ -41,7 +41,15 @@ if ($method === 'GET') { ...@@ -41,7 +41,15 @@ if ($method === 'GET') {
} }
if ($method === 'POST') { if ($method === 'POST') {
// Admin endpoint — saves theme values to Supabase // Admin endpoint: saves theme values to Supabase. Only the signed-in
// branding admin (/admin/branding.php, same origin, session cookie) may
// write; without this check anyone could repaint the app for every player.
session_start();
if (empty($_SESSION['branding_auth'])) {
http_response_code(401);
echo json_encode(['error' => 'Sign in to /admin/branding.php first']);
exit;
}
$input = getInput(); $input = getInput();
$action = $input['action'] ?? ''; $action = $input['action'] ?? '';
......
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