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
+21 -18
View File
@@ -1,40 +1,43 @@
import { COMPANY } from '../config/company.js';
import { strings } from '../i18n/fa.js';
// Robot Market machines in the playable Park Mellat area. Positions are path points taken from
// the OpenStreetMap data (local metres, see geo.js); the machine is placed beside that path.
export const CATEGORIES = {
factory: {
label: 'کارخانه',
label: strings.categories.factory,
icon: '🏭',
},
park: {
label: 'پارک',
label: strings.categories.park,
icon: '🌳',
},
university: {
label: 'دانشگاه',
label: strings.categories.university,
icon: '🎓',
},
school: {
label: 'مدرسه',
label: strings.categories.school,
icon: '🏫',
},
metro: {
label: 'ایستگاه مترو',
label: strings.categories.metro,
icon: 'Ⓜ',
},
mall: {
label: 'مرکز خرید',
label: strings.categories.mall,
icon: '🛍',
},
test: {
label: 'نقطهٔ آزمایشی GPS',
label: strings.categories.test,
icon: '🧪',
},
};
const ALL_DAY = [7 * 60, 23 * 60];
export const FACTORY = {
name: 'شرکت روبات مارکت',
tagline: 'سازندهٔ دستگاه‌های وندینگ هوشمند',
address: 'بزرگراه مشهد–چناران، خیابان کاله، کنار پارک علم و فناوری خراسان',
name: COMPANY.name,
tagline: COMPANY.tagline,
address: COMPANY.contact.address,
// Centre of the compound and the direction of its driveway (towards the gate on Kaleh Street).
center: {
x: -10308.4,
@@ -48,14 +51,14 @@ export const FACTORY = {
export const machines = [
{
id: 'factory',
name: 'دستگاه دفتر فنی مهندسی',
name: strings.places.officeMachine,
category: 'factory',
address: `${FACTORY.address}، ساختمان اداری، دفتر فنی مهندسی`,
address: strings.places.officeAddress(FACTORY.address),
anchor: null,
hours: [0, 24 * 60],
priceFactor: 0.5,
products: ['water', 'juice', 'tea', 'biscuit', 'chocolate'],
note: 'دستگاه دفتر برنامه‌نویس‌ها؛ نصف قیمت برای مهمان‌های کارخانه',
note: strings.places.officeNote,
},
{
id: 'mellat-park',
@@ -168,10 +171,10 @@ export const machines = [
*/
const testPoint = (n, lat, lon) => ({
id: `test-${n}`,
name: `نقطهٔ آزمایشی ${['۱', '۲', '۳'][n - 1]}`,
name: strings.places.testPoint(n.toLocaleString('fa-IR')),
category: 'test',
test: true,
address: `کنار کارخانهٔ روبات مارکت · ${lat.toFixed(6)}, ${lon.toFixed(6)}`,
address: strings.places.testAddress(`${lat.toFixed(6)}, ${lon.toFixed(6)}`),
anchor: null,
spot: {
lat,
@@ -180,7 +183,7 @@ const testPoint = (n, lat, lon) => ({
hours: [0, 24 * 60],
priceFactor: 0.5,
products: ['water', 'biscuit'],
note: 'برای آزمایش حالت دنیای واقعی: با گوشی تا اینجا راه بروید',
note: strings.places.testNote,
});
machines.push(
testPoint(1, 36.450276, 59.423431),
@@ -192,11 +195,11 @@ machines.push(
export const lockedPlaces = [
{
name: 'پارک کوهسنگی',
note: 'خارج از محدودهٔ فعلی · به‌زودی',
note: strings.places.comingSoon,
},
{
name: 'حرم و مرکز شهر',
note: 'خارج از محدودهٔ فعلی · به‌زودی',
note: strings.places.comingSoon,
},
];
export const START = {
+4 -46
View File
@@ -1,47 +1,5 @@
/** Display-only machine prices in rial, independent of the consumable economy. */
export const SHOWROOM_CATALOG = [
{
id: 'rm-35',
category: 'warm-vending',
name: 'روبات مارکت ۳۵ کانال',
shortLabel: '۳۵ کانال',
channels: 35,
priceLabel: '4,680,000,000 ریال',
priceNumeric: 4680000000,
description:
'یخچالدار، ۳۵ کانال اختصاصی، سیستم خنک‌کننده قدرتمند، تحویل نرم، مدیریت آنلاین ۲۴ ساعته.',
highlights: ['یخچالدار', '۳۵ کانال اختصاصی', 'تحویل نرم', 'مدیریت آنلاین ۲۴ ساعته'],
image: '/images/machine-35-base.webp',
imageAlt: '/images/machine-35-white.webp',
gif: '/gifs/machine-35.gif',
},
{
id: 'rm-48',
category: 'warm-vending',
name: 'روبات مارکت ۴۸ کانال',
shortLabel: '۴۸ کانال',
channels: 48,
priceLabel: '5,480,000,000 ریال',
priceNumeric: 5480000000,
description: 'یخچالدار، ۴۸ کانال اختصاصی، قابلیت‌های پیشرفته مدیریت آنلاین و تحویل کالا.',
highlights: ['یخچالدار', '۴۸ کانال اختصاصی', 'مدیریت آنلاین پیشرفته', 'تحویل هوشمند کالا'],
image: '/images/machine-48-base.webp',
imageAlt: '/images/machine-48-white.webp',
gif: '/gifs/machine-48.gif',
},
{
id: 'rm-60',
category: 'warm-vending',
name: 'روبات مارکت ۶۰ کانال',
shortLabel: '۶۰ کانال',
channels: 60,
priceLabel: '5,980,000,000 ریال',
priceNumeric: 5980000000,
description: 'یخچالدار، ۶۰ کانال اختصاصی برای کسب‌وکارهای پرتردد.',
highlights: ['یخچالدار', '۶۰ کانال اختصاصی', 'مناسب اماکن پرتردد', 'ظرفیت بالا'],
image: '/images/machine-60-base.webp',
imageAlt: '/images/machine-60-white.webp',
gif: '/gifs/machine-60.gif',
},
];
import { COMPANY } from '../config/company.js';
/** Display-only machine models; the product line itself lives in config/company.js. */
export const SHOWROOM_CATALOG = COMPANY.products;
export const SHOWROOM_REACH = 4;
+4 -10
View File
@@ -1,3 +1,5 @@
import { strings } from '../i18n/fa.js';
// Every vehicle in the factory yard is painted this gloss black.
export const VEHICLE_BLACK = '#0c0d0f';
@@ -7,16 +9,8 @@ export const ENTER_DISTANCE = 4.5;
// Above this speed (m/s) the player has to stop before getting off.
export const EXIT_MAX_SPEED = 3;
// Persian names shown in prompts. `bmw` is the family car parked beside the start point.
export const VEHICLE_LABELS = {
bmw: 'بی‌ام‌و',
bugatti: 'بوگاتی',
lamborghini: 'لامبورگینی',
mazda: 'مزدا',
nissan: 'نیسان',
apache: 'موتور آپاچی',
traffic: 'ماشین',
};
// Names shown in prompts, from the strings file. `bmw` is the family car beside the start point.
export const VEHICLE_LABELS = strings.vehicles;
// Brands parked in the staff bays, in bay order. Mr Mohammadi arrives in the Bugatti.
export const STAFF_CAR_BRANDS = ['lamborghini', 'mazda', 'nissan'];