Commit b117f332 authored by Mahmoud Aglan's avatar Mahmoud Aglan

fix(branding): the game's look is locked; the manager only shows it

Owner decision 2026-10-26: the manager must not change the game's visual
identity. The app colours tab is read-only (current value or the game's
default from its tokens.css), the image slots are read-only, and the write
paths are removed: branding/game-color, branding/game-color/reset and
branding/upload-asset (routes and methods). updateColor and the palette reset
now touch only the manager's own palette keys that the game never reads, so
no request can reach a platform_theme key the game reads; nothing in the
manager writes platform_assets or theme_settings.
Co-Authored-By: 's avatarClaude Opus 5.5 <noreply@anthropic.com>
parent 3a5ae9a4
......@@ -208,9 +208,6 @@ return [
'branding/assets' => ['module' => 'branding', 'action' => 'assets'],
'branding/update-color' => ['module' => 'branding', 'action' => 'updateColor'],
'branding/reset-colors' => ['module' => 'branding', 'action' => 'resetColors'],
'branding/upload-asset' => ['module' => 'branding', 'action' => 'uploadAsset'],
'branding/game-color' => ['module' => 'branding', 'action' => 'updateGameColor'],
'branding/game-color/reset' => ['module' => 'branding', 'action' => 'resetGameColor'],
// Analytics
'analytics' => ['module' => 'analytics', 'action' => 'index'],
......
This diff is collapsed.
......@@ -13,8 +13,8 @@
<!-- Game Colors Tab -->
<div id="gameColorsTab" class="tab-content">
<div class="card mb-4 p-4">
<p class="text-sm">هذه هي الألوان التي يسمح التطبيق بتغييرها (تصميم Night Arcade). اللون غير المحدد يأخذ قيمته من تصميم التطبيق. يرى اللاعبون التغيير عند فتح التطبيق.</p>
<p class="text-sm text-muted mt-2">يمكن أيضًا تعديلها من لوحة الهوية في التطبيق نفسه: <a href="<?= View::e($gameAdminUrl) ?>" target="_blank" rel="noopener" class="text-link" dir="ltr"><?= View::e($gameAdminUrl) ?></a> — الطرفان يكتبان القيم نفسها.</p>
<p class="text-sm">هوية التطبيق البصرية مقفلة (قرار المالك، 26 أكتوبر 2026): هذه ألوان التطبيق الحالية للاطلاع فقط، ولا تُعدّل من لوحة الإدارة.</p>
<p class="text-sm text-muted mt-2">اللون "الافتراضي" يأتي من تصميم التطبيق نفسه؛ "مخصص" يعني قيمة محفوظة يقرؤها التطبيق.</p>
</div>
<?php foreach ($gameColors as $group => $items): ?>
<div class="card mb-4">
......@@ -34,19 +34,7 @@
</div>
</div>
<div class="flex items-center gap-2 mt-3">
<form method="POST" action="/branding/game-color" class="flex items-center gap-2">
<input type="hidden" name="_csrf" value="<?= Auth::csrfToken() ?>">
<input type="hidden" name="id" value="<?= View::e($c['id']) ?>">
<input type="color" name="value" value="<?= View::e($shown) ?>" aria-label="<?= View::e($c['label']) ?>" style="width: 40px; height: 32px; border: none; cursor: pointer;">
<button type="submit" class="btn btn-primary btn-sm">حفظ</button>
</form>
<?php if ($c['value']): ?>
<form method="POST" action="/branding/game-color/reset">
<input type="hidden" name="_csrf" value="<?= Auth::csrfToken() ?>">
<input type="hidden" name="id" value="<?= View::e($c['id']) ?>">
<button type="submit" class="btn btn-ghost btn-sm">الافتراضي</button>
</form>
<?php endif; ?>
<input type="text" class="form-input" style="width: 100px; padding: var(--space-1) var(--space-2); font-size: 12px;" value="<?= View::e($shown) ?>" dir="ltr" readonly aria-label="<?= View::e($c['label']) ?>">
</div>
</div>
<?php endforeach; ?>
......@@ -323,7 +311,7 @@
<div class="card"><div class="empty-state"><h3 class="empty-state-title">لا توجد ألوان محفوظة</h3><p class="empty-state-text">أضف ألوان العلامة التجارية من جدول platform_theme</p></div></div>
<?php else: ?>
<div class="flex items-center justify-between mb-4">
<p class="text-sm text-secondary">ألوان لوحة الإدارة هذه فقط. التطبيق لا يقرأ هذه المفاتيح؛ ألوانه في تبويب "ألوان التطبيق".</p>
<p class="text-sm text-secondary">ألوان لوحة الإدارة هذه فقط. التطبيق لا يقرأ هذه المفاتيح؛ أي مفتاح يقرؤه التطبيق مقفل هنا.</p>
<form method="POST" action="/branding/reset-colors" style="margin:0;">
<input type="hidden" name="_csrf" value="<?= Auth::csrfToken() ?>">
<button type="submit" class="btn btn-ghost btn-sm" onclick="return confirm('هل أنت متأكد؟ سيتم إعادة جميع الألوان إلى الافتراضي.')">
......@@ -342,6 +330,12 @@
<div class="text-xs text-muted" dir="ltr"><?= View::e($color['id']) ?></div>
</div>
</div>
<?php if (isset(BrandingController::GAME_COLORS[$color['id']])): ?>
<div class="flex items-center gap-2 mt-3">
<input type="text" class="form-input" style="width: 100px; padding: var(--space-1) var(--space-2); font-size: 12px;" value="<?= View::e($color['value']) ?>" dir="ltr" readonly>
<span class="text-xs text-muted">يقرؤه التطبيق أيضًا؛ مقفل</span>
</div>
<?php else: ?>
<form method="POST" action="/branding/update-color" class="flex items-center gap-2 mt-3">
<input type="hidden" name="_csrf" value="<?= Auth::csrfToken() ?>">
<input type="hidden" name="id" value="<?= View::e($color['id']) ?>">
......@@ -349,6 +343,7 @@
<input type="text" class="form-input" style="width: 100px; padding: var(--space-1) var(--space-2); font-size: 12px;" value="<?= View::e($color['value']) ?>" dir="ltr" readonly>
<button type="submit" class="btn btn-primary btn-sm">حفظ</button>
</form>
<?php endif; ?>
</div>
<?php endforeach; ?>
</div>
......@@ -359,17 +354,8 @@
<div id="assetsTab" class="tab-content hidden">
<?php $stateLabels = ['live' => ['يقرؤه التطبيق', 'badge-success'], 'conditional' => ['بشرط', 'badge-warning'], 'ignored' => ['لا يقرؤه التطبيق', 'badge-default']]; ?>
<div class="card mb-4 p-4">
<p class="text-sm">الخانات التي يقرؤها التطبيق: قطع الشطرنج (chess_piece_wK ...)، صور البوتات (chess_bot_المعرف)، أي أيقونة (icon_الاسم، مثل icon_coin و icon_crown)، صور التفاعلات (icon_emote_...)، وجوه نرد اللودو (emoji_dice_1 إلى 6)، والأصوات (sfx_...). الشعار وأيقونات الشريط السفلي (tab_...) تُقرأ فقط إن رُفعت بعد 19 أكتوبر 2026. باقي الخانات القديمة، ومنها خانات الرموز التعبيرية، لا يقرؤها التطبيق.</p>
<form method="POST" action="/branding/upload-asset" enctype="multipart/form-data" class="flex gap-2 items-end flex-wrap mt-3">
<input type="hidden" name="_csrf" value="<?= Auth::csrfToken() ?>">
<div class="form-group" style="margin-bottom:0;">
<label class="form-label">رفع لخانة جديدة</label>
<input type="text" name="id" class="form-input" required dir="ltr" placeholder="icon_coin" pattern="[a-zA-Z0-9_-]+">
</div>
<input type="file" name="asset" accept="image/png,image/jpeg,image/svg+xml,image/webp" class="form-input" style="font-size: 12px; max-width: 260px;" required>
<button type="submit" class="btn btn-primary btn-sm">رفع</button>
</form>
<p class="text-xs text-muted mt-2">الأصوات تُرفع من لوحة الهوية في التطبيق لأنها ليست صورًا.</p>
<p class="text-sm">صور التطبيق وأيقوناته مقفلة (قرار المالك، 26 أكتوبر 2026): تُعرض هنا للاطلاع فقط، ولا يُرفع ولا يُستبدل شيء منها من لوحة الإدارة.</p>
<p class="text-xs text-muted mt-2">الخانات التي يقرؤها التطبيق: قطع الشطرنج (chess_piece_wK ...)، صور البوتات (chess_bot_المعرف)، الأيقونات (icon_الاسم)، صور التفاعلات (icon_emote_...)، وجوه نرد اللودو (emoji_dice_1 إلى 6)، والأصوات (sfx_...). الشعار وأيقونات الشريط السفلي (tab_...) تُقرأ فقط إن رُفعت بعد 19 أكتوبر 2026. باقي الخانات القديمة لا يقرؤها التطبيق.</p>
</div>
<?php if (empty($assets)): ?>
<div class="card"><div class="empty-state"><h3 class="empty-state-title">لا توجد أصول مرئية</h3></div></div>
......@@ -390,13 +376,7 @@
<span class="badge <?= $st[1] ?> badge-sm"><?= $st[0] ?></span>
</div>
<p class="text-xs text-muted" dir="ltr"><?= View::e($asset['id']) ?></p>
<p class="text-xs text-secondary mb-3"><?= View::e($asset['game_note']) ?><?php if (!empty($asset['updated_at'])): ?> — آخر رفع <?= Time::show($asset['updated_at'], 'Y/m/d') ?><?php endif; ?></p>
<form method="POST" action="/branding/upload-asset" enctype="multipart/form-data">
<input type="hidden" name="_csrf" value="<?= Auth::csrfToken() ?>">
<input type="hidden" name="id" value="<?= View::e($asset['id']) ?>">
<input type="file" name="asset" accept="image/*" class="form-input" style="font-size: 12px;">
<button type="submit" class="btn btn-primary btn-sm mt-2 w-full">رفع</button>
</form>
<p class="text-xs text-secondary"><?= View::e($asset['game_note']) ?><?php if (!empty($asset['updated_at'])): ?> — آخر رفع <?= Time::show($asset['updated_at'], 'Y/m/d') ?><?php endif; ?></p>
</div>
<?php endforeach; ?>
</div>
......
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