Commit 8aa32432 authored by Mahmoud Aglan's avatar Mahmoud Aglan

feat: complete theming system — 67 emoji replacements, juice settings, full asset registry

ASSET_REGISTRY.json: machine-readable sweep of entire codebase
- 67 emojis with exact file:line locations
- 80 unique colors
- 14 gradients
- 24 animations
- 56 CSS variables

Admin panel new sections:
- 🧃 Juice Settings: particles on/off, shake intensity, haptic, confetti count,
  coin fly count, bounce scale, slam scale, float amount
- 😀 Emoji Replacements: 27 most visible emojis as upload slots
  Each with: current emoji preview, size hint, usage description
  Upload SVG/PNG → replaces emoji throughout the app

theme.js updated:
- emoji() helper: returns uploaded image OR fallback emoji at exact size
- image-rendering: -webkit-optimize-contrast (fixes pixelation on non-retina)
- All images use object-fit:contain (never overflow their expected box)
Co-Authored-By: 's avatarClaude Opus 4.6 <noreply@anthropic.com>
parent b2c350bc
This diff is collapsed.
...@@ -235,6 +235,93 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_FILES['asset'])) { ...@@ -235,6 +235,93 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_FILES['asset'])) {
<button type="submit">💾 حفظ كل التغييرات</button> <button type="submit">💾 حفظ كل التغييرات</button>
</form> </form>
<!-- JUICE SETTINGS -->
<h2>🧃 إعدادات الـ Juice (التأثيرات)</h2>
<div class="section">
<div class="grid">
<?php
$juiceSettings = [
['key' => 'juice_particles', 'label' => 'تفعيل الجسيمات (Particles)', 'default' => '1', 'hint' => '1=مفعل, 0=معطل', 'type' => 'number'],
['key' => 'juice_shake_intensity', 'label' => 'شدة الاهتزاز', 'default' => '4', 'hint' => '0=بلا, 2=خفيف, 4=عادي, 8=قوي', 'type' => 'number'],
['key' => 'juice_haptic', 'label' => 'تفعيل الاهتزاز (Haptic)', 'default' => '1', 'hint' => '1=مفعل, 0=معطل', 'type' => 'number'],
['key' => 'juice_confetti_count', 'label' => 'عدد جسيمات الاحتفال', 'default' => '30', 'hint' => 'عند الفوز — 0 لإلغاء', 'type' => 'number'],
['key' => 'juice_coin_fly_count', 'label' => 'عدد العملات الطائرة', 'default' => '5', 'hint' => 'عملات تطير للـ HUD', 'type' => 'number'],
['key' => 'juice_bounce_scale', 'label' => 'حجم الارتداد (Bounce)', 'default' => '1.08', 'hint' => '1.0=بلا, 1.05=خفيف, 1.15=مبالغ', 'type' => 'text'],
['key' => 'juice_slam_scale', 'label' => 'حجم الاصطدام (Slam)', 'default' => '1.5', 'hint' => 'عند ظهور النتائج', 'type' => 'text'],
['key' => 'juice_float_amount', 'label' => 'مسافة الطفو (px)', 'default' => '5', 'hint' => 'حركة المربعات في الصفحة الرئيسية', 'type' => 'number'],
];
foreach ($juiceSettings as $j):
$val = $theme[$j['key']] ?? $j['default'];
?>
<div class="field">
<label><?= $j['label'] ?></label>
<input type="<?= $j['type'] ?>" name="theme[<?= $j['key'] ?>]" value="<?= $val ?>">
<div class="hint"><?= $j['hint'] ?></div>
</div>
<?php endforeach; ?>
</div>
</div>
<!-- EMOJI REPLACEMENTS -->
<h2>😀 استبدال الرموز التعبيرية</h2>
<p style="color:#64748b;font-size:12px;margin-bottom:16px;">استبدل أي رمز تعبيري بصورة SVG أو PNG — تظهر بنفس الحجم بلا تشويه</p>
<div class="section">
<div class="grid">
<?php
$emojiSlots = [
['key' => 'trophy', 'emoji' => '🏆', 'label' => 'كأس الفوز', 'size' => 72, 'hint' => 'شاشة نتيجة الفوز'],
['key' => 'skull', 'emoji' => '💀', 'label' => 'أيقونة الخسارة', 'size' => 72, 'hint' => 'شاشة نتيجة الخسارة'],
['key' => 'handshake', 'emoji' => '🤝', 'label' => 'أيقونة التعادل', 'size' => 72, 'hint' => 'شاشة نتيجة التعادل'],
['key' => 'gift', 'emoji' => '🎁', 'label' => 'صندوق المكافأة', 'size' => 48, 'hint' => 'شاشة المكافأة اليومية'],
['key' => 'robot', 'emoji' => '🤖', 'label' => 'أيقونة البوت', 'size' => 32, 'hint' => 'بجانب اسم الخصم الآلي'],
['key' => 'star', 'emoji' => '⭐', 'label' => 'نجمة التقييم', 'size' => 20, 'hint' => 'شاشة المراجعة'],
['key' => 'dice', 'emoji' => '🎲', 'label' => 'أيقونة النرد', 'size' => 48, 'hint' => 'مربع اللودو + زر الرمي'],
['key' => 'gamepad', 'emoji' => '🎮', 'label' => 'لاعب واحد', 'size' => 24, 'hint' => 'قائمة اللعبة — لاعب واحد'],
['key' => 'swords', 'emoji' => '⚔️', 'label' => 'متعدد اللاعبين', 'size' => 24, 'hint' => 'قائمة اللعبة — أونلاين'],
['key' => 'medal_gold', 'emoji' => '🥇', 'label' => 'ميدالية ذهبية', 'size' => 20, 'hint' => 'المركز الأول في الترتيب'],
['key' => 'medal_silver', 'emoji' => '🥈', 'label' => 'ميدالية فضية', 'size' => 20, 'hint' => 'المركز الثاني'],
['key' => 'medal_bronze', 'emoji' => '🥉', 'label' => 'ميدالية برونزية', 'size' => 20, 'hint' => 'المركز الثالث'],
['key' => 'coin', 'emoji' => '🪙', 'label' => 'عملة', 'size' => 16, 'hint' => 'بجانب أسعار المتجر'],
['key' => 'gem', 'emoji' => '💎', 'label' => 'جوهرة', 'size' => 16, 'hint' => 'بجانب أسعار الجواهر'],
['key' => 'person', 'emoji' => '👤', 'label' => 'صورة لاعب افتراضية', 'size' => 32, 'hint' => 'الأفاتار الافتراضي'],
['key' => 'people', 'emoji' => '👥', 'label' => 'أيقونة الأصدقاء', 'size' => 32, 'hint' => 'شاشة الأصدقاء الفارغة'],
['key' => 'bell', 'emoji' => '🔔', 'label' => 'جرس الإشعارات', 'size' => 18, 'hint' => 'أيقونة الإشعارات في الهيدر'],
['key' => 'speaker_on', 'emoji' => '🔊', 'label' => 'صوت مفعل', 'size' => 20, 'hint' => 'إعدادات الصوت'],
['key' => 'speaker_off', 'emoji' => '🔇', 'label' => 'صوت معطل', 'size' => 20, 'hint' => 'إعدادات الصوت'],
['key' => 'art', 'emoji' => '🎨', 'label' => 'أيقونة المتجر', 'size' => 24, 'hint' => 'بطاقات المتجر'],
['key' => 'puzzle', 'emoji' => '🧩', 'label' => 'أحجيات', 'size' => 16, 'hint' => 'زر الأحجيات في القائمة'],
['key' => 'chart', 'emoji' => '📊', 'label' => 'تحليل', 'size' => 16, 'hint' => 'زر التحليل'],
['key' => 'clipboard', 'emoji' => '📋', 'label' => 'مبارياتي', 'size' => 16, 'hint' => 'زر المباريات'],
['key' => 'share', 'emoji' => '📤', 'label' => 'مشاركة', 'size' => 16, 'hint' => 'زر المشاركة'],
['key' => 'checkmark', 'emoji' => '✅', 'label' => 'علامة صح', 'size' => 20, 'hint' => 'نجاح العملية'],
['key' => 'cross', 'emoji' => '❌', 'label' => 'علامة خطأ', 'size' => 20, 'hint' => 'فشل العملية'],
['key' => 'flag', 'emoji' => '⚐', 'label' => 'استسلام', 'size' => 16, 'hint' => 'زر الاستسلام في الشطرنج'],
['key' => 'book', 'emoji' => '📖', 'label' => 'كتاب/نظرية', 'size' => 14, 'hint' => 'تصنيف نقلة نظرية'],
];
foreach ($emojiSlots as $e):
$current = $theme['assets']['emoji_' . $e['key']] ?? null;
?>
<div class="field">
<form method="POST" enctype="multipart/form-data">
<input type="hidden" name="slot" value="emoji_<?= $e['key'] ?>">
<input type="hidden" name="expected_w" value="<?= $e['size'] ?>">
<input type="hidden" name="expected_h" value="<?= $e['size'] ?>">
<label><?= $e['emoji'] ?> <?= $e['label'] ?></label>
<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()">
<?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>
<?php else: ?>
<span style="font-size:24px;"><?= $e['emoji'] ?></span> → ارفع بديل
<?php endif; ?>
<div class="size-hint"><?= $e['size'] ?>×<?= $e['size'] ?>px — <?= $e['hint'] ?></div>
</div>
</form>
</div>
<?php endforeach; ?>
</div>
</div>
<!-- ASSET UPLOADS --> <!-- ASSET UPLOADS -->
<h2>📦 الأصول البصرية</h2> <h2>📦 الأصول البصرية</h2>
<p style="color:#64748b;font-size:12px;margin-bottom:16px;">ارفع صور SVG أو PNG — ستظهر فوراً كافتراضي لكل اللاعبين</p> <p style="color:#64748b;font-size:12px;margin-bottom:16px;">ارفع صور SVG أو PNG — ستظهر فوراً كافتراضي لكل اللاعبين</p>
......
...@@ -78,7 +78,15 @@ function applyAnimations() { ...@@ -78,7 +78,15 @@ function applyAnimations() {
export function assetImg(slot, fallbackEmoji, width, height) { export function assetImg(slot, fallbackEmoji, width, height) {
const url = getAsset(slot); const url = getAsset(slot);
if (url) { if (url) {
return `<img src="${url}" alt="" style="width:${width}px;height:${height}px;object-fit:contain;image-rendering:auto;" draggable="false">`; return `<img src="${url}" alt="" style="width:${width}px;height:${height}px;object-fit:contain;image-rendering:-webkit-optimize-contrast;" draggable="false">`;
} }
return `<span style="font-size:${Math.min(width, height) * 0.7}px;line-height:1;">${fallbackEmoji}</span>`; return `<span style="font-size:${Math.min(width, height) * 0.7}px;line-height:1;">${fallbackEmoji}</span>`;
} }
export function emoji(key, fallback, size = 20) {
const url = getAsset('emoji_' + key);
if (url) {
return `<img src="${url}" alt="" style="width:${size}px;height:${size}px;object-fit:contain;vertical-align:middle;image-rendering:-webkit-optimize-contrast;" draggable="false">`;
}
return fallback;
}
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