refactor: centralize brand, company info, assets and Persian strings

Phase 1 of turning the game into the company landing page.

- Add src/config/company.js with name, tagline, contact, about and the
  product line, plus a brand palette built around #00a693 (contrast
  checked); placeholders are marked TODO(company).
- Add src/config/assets.js listing every loaded file with its expected
  format; placeholders are marked TODO(asset).
- Move all Persian interface text, dialogue, missions and scenery text
  into src/i18n/fa.js; components and game code read from it.
- Expose the palette as CSS variables (--color-primary and shades) and
  Tailwind colours, and use it for 3D materials and map drawings.
- Fix the arrival line that still called the manager's car a white
  pickup.
- Add tests that block hard-coded Persian text and low-contrast colours.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-31 08:13:00 +03:30
co-authored by Claude Opus 5.5
parent e02347142b
commit 8dbaf3d97e
54 changed files with 1184 additions and 486 deletions
+4 -6
View File
@@ -4,6 +4,7 @@ import { resolveCircle } from '../world/collision.js';
import { stepCar } from '../systems/vehicle.js';
import { EXIT_MAX_SPEED } from '../../constants/vehicles.js';
import { collideDriving, nearestRide, takeRide, walkAroundVehicles } from './garage.js';
import { strings } from '../../i18n/fa.js';
/**
* Runs the look operation for this subsystem.
* @param {*} context - context.
@@ -39,7 +40,7 @@ export // ---------- Car ----------
function toggleCar(context) {
if (context.payment.phase !== 'idle' || context.state.busy) return;
if (context.real.on) {
context.say('father', 'توی حالت دنیای واقعی پیاده راه می‌ریم؛ ماشین مجازی لازم نیست.');
context.say('father', strings.real.noVirtualCar);
return;
}
if (context.mode === 'walk') getIn(context);
@@ -58,10 +59,7 @@ function getIn(context) {
context.mode = 'drive';
context.cam.distance = Math.max(context.cam.distance, kind === 'bike' ? 6 : 8);
context.tone(320, 0.2, 0.05, 'square');
const line =
kind === 'bike'
? 'کلاه ایمنی سرت باشه! با موتور می‌شه از لای ماشین‌ها رد شد.'
: 'کمربندها رو ببندیم! مسیر طلایی روی زمین راه رو نشون می‌ده.';
const line = kind === 'bike' ? strings.dialogue.bikeOn : strings.dialogue.carOn;
context.say('father', line);
}
@@ -72,7 +70,7 @@ function getIn(context) {
function getOut(context) {
const { state, spec } = context.vehicle;
if (Math.abs(state.speed) > EXIT_MAX_SPEED) {
context.say('father', 'اول نگه دار، بعد پیاده می‌شویم.');
context.say('father', strings.dialogue.stopFirst);
return;
}
const left = { x: -Math.sin(state.heading), z: -Math.cos(state.heading) };