diff --git a/backend/engine.py b/backend/engine.py
index 16848d8..19897b7 100644
--- a/backend/engine.py
+++ b/backend/engine.py
@@ -1,7 +1,6 @@
import math
import time
-# Drug Catalog mirroring config.js
# Drug Catalog mirroring config.js
DRUG_CONFIGS = {
'weed': {'rawCost': 10, 'sellPrice': 16, 'produceRate': 1.0, 'sellRate': 1.0, 'supplyRate': 1.0, 'supplierCost': 75, 'workerCost': 100, 'dealerCost': 150, 'unlockCost': 0},
@@ -28,23 +27,70 @@ SKILL_COSTS = {
'smug_1': 2000, 'smug_2': 50000, 'smug_3': 1000000
}
+UNIT_MILESTONES = [1, 5, 10, 25, 50, 100, 200, 300, 400, 500, 750, 1000, 1500, 2000]
+
+OFFSHORE_PERK_COSTS = {
+ 'swiss_vault': [1, 3, 8, 20, 50],
+ 'auto_contracts': [2, 5, 12, 30],
+ 'corrupt_judiciary': [2, 6, 15, 40],
+ 'bulk_discount': [5, 15, 35, 80],
+ 'compound_interest': [4, 10, 25, 60, 150]
+}
+
+ACHIEVEMENT_TIER_REQS = {
+ 'dirty_empire': [10000, 250000, 5000000, 100000000, 1000000000],
+ 'master_synthesizer': [100, 1000, 15000, 200000, 2500000],
+ 'street_monopoly': [100, 1500, 25000, 300000, 3000000],
+ 'supply_baron': [250, 3000, 50000, 500000],
+ 'vault_hoarder': [50000, 500000, 10000000, 250000000],
+ 'syndicate_army': [25, 100, 500, 2500],
+ 'purity_legend': [5, 25, 100, 500],
+ 'shadow_cartel': [3, 10, 30],
+ 'swiss_kingpin': [1, 3, 10, 25],
+ 'formula_expansion': [3, 6, 8, 10]
+}
+
def clean_round(val: float) -> float:
"""Rounds float gracefully to 1 decimal place without floating point anomalies."""
if math.isnan(val) or math.isinf(val) or val < 0:
return 0.0
return round(float(val), 1)
+def get_unit_milestone_multiplier(count: int) -> float:
+ """Calculates 2x multiplier for each milestone threshold achieved."""
+ reached = sum(1 for m in UNIT_MILESTONES if count >= m)
+ return float(math.pow(2.0, reached))
+
+def get_all_tier_milestone_multiplier(state: dict, unit_key: str) -> float:
+ """Calculates 2x global set multiplier when all unlocked drugs reach a milestone."""
+ unlocked = [d for d in state.get('drugs', {}).values() if d.get('unlocked')]
+ if not unlocked:
+ return 1.0
+ min_count = min(d.get(unit_key, 0) for d in unlocked)
+ reached = sum(1 for m in UNIT_MILESTONES if min_count >= m)
+ return float(math.pow(2.0, reached))
+
def get_initial_state() -> dict:
state = {
'cash': 25.0,
'cleanBonds': 0,
+ 'totalCleanBonds': 0,
'heat': 0.0,
'totalEarned': 0.0,
- 'lastTick': time.time(),
- 'drugs': {},
+ 'stats': {
+ 'totalProduced': 0.0,
+ 'totalSold': 0.0,
+ 'totalSupplied': 0.0,
+ 'purityProcs': 0,
+ 'raidsSurvived': 0,
+ 'prestigeCount': 0
+ },
+ 'achievements': {}, # { ach_id: claimed_tier_int }
+ 'offshorePerks': {}, # { perk_id: level_int }
'skills': {s: False for s in SKILL_COSTS},
- 'achievements': {},
- 'synthesis': {r: 0 for r in SYNTHESIS_RECIPES}
+ 'synthesis': {r: 0 for r in SYNTHESIS_RECIPES},
+ 'drugs': {},
+ 'lastTick': time.time()
}
for drug_id, cfg in DRUG_CONFIGS.items():
state['drugs'][drug_id] = {
@@ -58,26 +104,100 @@ def get_initial_state() -> dict:
return state
def get_multipliers(state: dict):
- bonds = state.get('cleanBonds', 0)
+ # Total lifetime bonds provide permanent base boost
+ total_bonds = max(state.get('totalCleanBonds', 0), state.get('cleanBonds', 0))
skills = state.get('skills', {})
- achievements = state.get('achievements', {})
+ ach = state.get('achievements', {})
+ perks = state.get('offshorePerks', {})
- prod_m = 1.0 + (bonds * 0.03)
+ # Compound interest perk raises bond sale value
+ compound_lvl = perks.get('compound_interest', 0)
+ bond_sell_rate = 0.04 + (compound_lvl * 0.02)
+ bond_prod_rate = 0.03
+
+ prod_m = 1.0 + (total_bonds * bond_prod_rate)
if skills.get('chem_1'): prod_m += 0.15
- if achievements.get('millionaire'): prod_m += 0.20
- sell_m = 1.0 + (bonds * 0.04)
+ # Achievement bonuses for production
+ synth_tier = ach.get('master_synthesizer', 0)
+ prod_m += [0.0, 0.05, 0.10, 0.15, 0.25, 0.50][min(5, synth_tier)]
+ formula_tier = ach.get('formula_expansion', 0)
+ prod_m += [0.0, 0.05, 0.10, 0.15, 0.30][min(4, formula_tier)]
+
+ # Sales multiplier
+ sell_m = 1.0 + (total_bonds * bond_sell_rate)
if skills.get('king_2'): sell_m += 0.25
- if achievements.get('first_hit'): sell_m += 0.05
- if achievements.get('cartel_tier'): sell_m += 0.15
+ empire_tier = ach.get('dirty_empire', 0)
+ sell_m += [0.0, 0.05, 0.10, 0.15, 0.25, 0.50][min(5, empire_tier)]
+ # Raw material cost multiplier
raw_m = 0.85 if skills.get('chem_2') else 1.0
+ vault_tier = ach.get('vault_hoarder', 0)
+ raw_discount = [0.0, 0.05, 0.10, 0.15, 0.25][min(4, vault_tier)]
+ raw_m = max(0.1, raw_m - raw_discount)
+ # Dealer speed multiplier
dealer_m = 1.0
if skills.get('king_1'): dealer_m += 0.20
if skills.get('king_3'): dealer_m += 0.50
+ street_tier = ach.get('street_monopoly', 0)
+ dealer_m += [0.0, 0.05, 0.10, 0.15, 0.25, 0.50][min(5, street_tier)]
- return prod_m, sell_m, raw_m, dealer_m
+ # Supplier speed multiplier
+ supply_tier = ach.get('supply_baron', 0)
+ supplier_m = 1.0 + [0.0, 0.05, 0.10, 0.15, 0.25][min(4, supply_tier)]
+
+ # Syndicate Army all speed multiplier
+ army_tier = ach.get('syndicate_army', 0)
+ all_unit_m = 1.0 + [0.0, 0.05, 0.10, 0.15, 0.25][min(4, army_tier)]
+
+ dealer_m *= all_unit_m
+ supplier_m *= all_unit_m
+ worker_m = all_unit_m
+
+ return prod_m, sell_m, raw_m, dealer_m, supplier_m, worker_m
+
+def get_current_stat_value(state: dict, stat_key: str) -> float:
+ """Helper to query current progress for an achievement category."""
+ if stat_key == 'totalEarned':
+ return float(state.get('totalEarned', 0.0))
+ elif stat_key == 'cash':
+ return float(state.get('cash', 0.0))
+ elif stat_key == 'totalPersonnel':
+ total = 0
+ for d in state.get('drugs', {}).values():
+ total += d.get('suppliers', 0) + d.get('workers', 0) + d.get('dealers', 0)
+ return float(total)
+ elif stat_key == 'unlockedCount':
+ return float(sum(1 for d in state.get('drugs', {}).values() if d.get('unlocked')))
+ else:
+ return float(state.get('stats', {}).get(stat_key, 0.0))
+
+def validate_achievement_claim(state: dict, ach_id: str, tier: int) -> bool:
+ reqs = ACHIEVEMENT_TIER_REQS.get(ach_id)
+ if not reqs or tier < 1 or tier > len(reqs):
+ return False
+
+ current_claimed = state.get('achievements', {}).get(ach_id, 0)
+ if tier != current_claimed + 1:
+ return False
+
+ req_val = reqs[tier - 1]
+ stat_key_map = {
+ 'dirty_empire': 'totalEarned',
+ 'master_synthesizer': 'totalProduced',
+ 'street_monopoly': 'totalSold',
+ 'supply_baron': 'totalSupplied',
+ 'vault_hoarder': 'cash',
+ 'syndicate_army': 'totalPersonnel',
+ 'purity_legend': 'purityProcs',
+ 'shadow_cartel': 'raidsSurvived',
+ 'swiss_kingpin': 'prestigeCount',
+ 'formula_expansion': 'unlockedCount'
+ }
+ stat_key = stat_key_map.get(ach_id, 'totalEarned')
+ current_val = get_current_stat_value(state, stat_key)
+ return current_val >= req_val
def process_offline_and_ticks(state: dict) -> dict:
now = time.time()
@@ -88,7 +208,14 @@ def process_offline_and_ticks(state: dict) -> dict:
if delta <= 0:
return sanitize_state(state)
- prod_m, sell_m, raw_m, dealer_m = get_multipliers(state)
+ prod_m, sell_m, raw_m, dealer_m, supplier_m, worker_m = get_multipliers(state)
+
+ # All-tier milestone multipliers
+ all_tier_sup = get_all_tier_milestone_multiplier(state, 'suppliers')
+ all_tier_work = get_all_tier_milestone_multiplier(state, 'workers')
+ all_tier_deal = get_all_tier_milestone_multiplier(state, 'dealers')
+
+ stats = state.setdefault('stats', {})
for drug_id, data in state.get('drugs', {}).items():
cfg = DRUG_CONFIGS.get(drug_id)
@@ -98,54 +225,48 @@ def process_offline_and_ticks(state: dict) -> dict:
# Suppliers automatic raw cultivation / procurement
suppliers = data.get('suppliers', 0)
if suppliers > 0:
- supply_speed = suppliers * cfg.get('supplyRate', 1.0) * prod_m
- data['raw'] = data.get('raw', 0.0) + (supply_speed * delta)
+ unit_milestone_m = get_unit_milestone_multiplier(suppliers)
+ supply_speed = suppliers * cfg.get('supplyRate', 1.0) * prod_m * supplier_m * unit_milestone_m * all_tier_sup
+ added_raw = supply_speed * delta
+ data['raw'] = data.get('raw', 0.0) + added_raw
+ stats['totalSupplied'] = stats.get('totalSupplied', 0.0) + added_raw
# Workers production (Refining raw -> product)
workers = data.get('workers', 0)
raw = data.get('raw', 0.0)
if workers > 0 and raw > 0:
- potential_prod = workers * cfg['produceRate'] * prod_m * delta
+ unit_milestone_m = get_unit_milestone_multiplier(workers)
+ potential_prod = workers * cfg['produceRate'] * prod_m * worker_m * unit_milestone_m * all_tier_work * delta
actual_prod = min(raw, potential_prod)
data['raw'] = max(0.0, raw - actual_prod)
data['product'] = data.get('product', 0.0) + actual_prod
+ stats['totalProduced'] = stats.get('totalProduced', 0.0) + actual_prod
# Dealers selling (Product -> Cash)
dealers = data.get('dealers', 0)
product = data.get('product', 0.0)
if dealers > 0 and product > 0:
- potential_sell = dealers * cfg['sellRate'] * dealer_m * delta
+ unit_milestone_m = get_unit_milestone_multiplier(dealers)
+ potential_sell = dealers * cfg['sellRate'] * dealer_m * unit_milestone_m * all_tier_deal * delta
actual_sell = min(product, potential_sell)
data['product'] = max(0.0, product - actual_sell)
rev = actual_sell * cfg['sellPrice'] * sell_m
state['cash'] = state.get('cash', 0.0) + rev
state['totalEarned'] = state.get('totalEarned', 0.0) + rev
+ stats['totalSold'] = stats.get('totalSold', 0.0) + actual_sell
# Heat decay
state['heat'] = max(0.0, state.get('heat', 0.0) - (0.5 * delta))
- check_server_achievements(state)
-
return sanitize_state(state)
-def check_server_achievements(state: dict):
- ach = state.setdefault('achievements', {})
- if state.get('totalEarned', 0) > 0:
- ach['first_hit'] = True
- if state.get('drugs', {}).get('weed', {}).get('workers', 0) >= 10:
- ach['grower_op'] = True
- if state.get('drugs', {}).get('meth', {}).get('unlocked'):
- ach['blue_sky'] = True
- if state.get('drugs', {}).get('cocaine', {}).get('unlocked'):
- ach['cartel_tier'] = True
- if state.get('cash', 0) >= 1000000:
- ach['millionaire'] = True
- if state.get('cleanBonds', 0) >= 1:
- ach['laundered'] = True
-
def apply_actions(state: dict, actions: list) -> dict:
"""Applies and authorizes player actions sequentially against server authority."""
- prod_m, sell_m, raw_m, dealer_m = get_multipliers(state)
+ prod_m, sell_m, raw_m, dealer_m, supplier_m, worker_m = get_multipliers(state)
+
+ # Unit price scaling factor (1.15 down to 1.11 via bulk_discount perk)
+ discount_lvl = state.get('offshorePerks', {}).get('bulk_discount', 0)
+ scaling_r = max(1.10, 1.15 - (discount_lvl * 0.01))
for act in actions:
action_type = act.get('type')
@@ -159,6 +280,7 @@ def apply_actions(state: dict, actions: list) -> dict:
if state['cash'] >= total_cost:
state['cash'] -= total_cost
state['drugs'][drug_id]['raw'] = state['drugs'][drug_id].get('raw', 0.0) + amount
+ state.setdefault('stats', {})['totalSupplied'] = state['stats'].get('totalSupplied', 0.0) + amount
elif action_type == 'manufacture' and drug_id in DRUG_CONFIGS:
data = state['drugs'][drug_id]
@@ -168,10 +290,16 @@ def apply_actions(state: dict, actions: list) -> dict:
data['raw'] -= actual
yield_count = actual
if state.get('skills', {}).get('chem_3'):
- # Purity proc bonus
+ # 10% chance on client for 3x yield
yield_count *= 1.3
+ state.setdefault('stats', {})['purityProcs'] = state['stats'].get('purityProcs', 0) + 1
data['product'] = data.get('product', 0.0) + yield_count
- state['heat'] = min(100.0, state.get('heat', 0.0) + (0.5 * actual))
+ state.setdefault('stats', {})['totalProduced'] = state['stats'].get('totalProduced', 0.0) + yield_count
+
+ # Swiss vault secrecy perk reduces heat generation
+ vault_lvl = state.get('offshorePerks', {}).get('swiss_vault', 0)
+ heat_rise = max(0.2, (0.5 * actual) * (1.0 - vault_lvl * 0.12))
+ state['heat'] = min(100.0, state.get('heat', 0.0) + heat_rise)
elif action_type == 'sell_manual' and drug_id in DRUG_CONFIGS:
data = state['drugs'][drug_id]
@@ -183,7 +311,11 @@ def apply_actions(state: dict, actions: list) -> dict:
profit = actual * cfg['sellPrice'] * sell_m
state['cash'] += profit
state['totalEarned'] += profit
- state['heat'] = min(100.0, state.get('heat', 0.0) + (0.8 * actual))
+ state.setdefault('stats', {})['totalSold'] = state['stats'].get('totalSold', 0.0) + actual
+
+ vault_lvl = state.get('offshorePerks', {}).get('swiss_vault', 0)
+ heat_rise = max(0.3, (0.8 * actual) * (1.0 - vault_lvl * 0.12))
+ state['heat'] = min(100.0, state.get('heat', 0.0) + heat_rise)
elif action_type == 'hire_supplier' and drug_id in DRUG_CONFIGS:
cfg = DRUG_CONFIGS[drug_id]
@@ -191,7 +323,7 @@ def apply_actions(state: dict, actions: list) -> dict:
base_cost = cfg.get('supplierCost', 75)
for _ in range(hire_qty):
count = state['drugs'][drug_id].get('suppliers', 0)
- cost = math.floor(base_cost * math.pow(1.15, count))
+ cost = math.floor(base_cost * math.pow(scaling_r, count))
if state['cash'] >= cost:
state['cash'] -= cost
state['drugs'][drug_id]['suppliers'] = count + 1
@@ -204,7 +336,7 @@ def apply_actions(state: dict, actions: list) -> dict:
base_cost = cfg['workerCost']
for _ in range(hire_qty):
count = state['drugs'][drug_id].get('workers', 0)
- cost = math.floor(base_cost * math.pow(1.15, count))
+ cost = math.floor(base_cost * math.pow(scaling_r, count))
if state['cash'] >= cost:
state['cash'] -= cost
state['drugs'][drug_id]['workers'] = count + 1
@@ -217,7 +349,7 @@ def apply_actions(state: dict, actions: list) -> dict:
base_cost = cfg['dealerCost']
for _ in range(hire_qty):
count = state['drugs'][drug_id].get('dealers', 0)
- cost = math.floor(base_cost * math.pow(1.15, count))
+ cost = math.floor(base_cost * math.pow(scaling_r, count))
if state['cash'] >= cost:
state['cash'] -= cost
state['drugs'][drug_id]['dealers'] = count + 1
@@ -230,6 +362,12 @@ def apply_actions(state: dict, actions: list) -> dict:
if state['cash'] >= cost and not state['drugs'][drug_id].get('unlocked'):
state['cash'] -= cost
state['drugs'][drug_id]['unlocked'] = True
+
+ # Procurement pipeline perk gives free starter units!
+ starter_units = state.get('offshorePerks', {}).get('auto_contracts', 0)
+ if starter_units > 0:
+ state['drugs'][drug_id]['suppliers'] = state['drugs'][drug_id].get('suppliers', 0) + starter_units
+ state['drugs'][drug_id]['workers'] = state['drugs'][drug_id].get('workers', 0) + starter_units
elif action_type == 'blend_recipe':
recipe_id = act.get('recipe_id')
@@ -255,28 +393,72 @@ def apply_actions(state: dict, actions: list) -> dict:
state['cash'] -= cost
state.setdefault('skills', {})[skill_id] = True
+ elif action_type == 'claim_achievement':
+ ach_id = act.get('ach_id')
+ tier = int(act.get('tier', 1))
+ if validate_achievement_claim(state, ach_id, tier):
+ state.setdefault('achievements', {})[ach_id] = tier
+
+ elif action_type == 'buy_offshore_perk':
+ perk_id = act.get('perk_id')
+ if perk_id in OFFSHORE_PERK_COSTS:
+ costs = OFFSHORE_PERK_COSTS[perk_id]
+ current_lvl = state.setdefault('offshorePerks', {}).get(perk_id, 0)
+ if current_lvl < len(costs):
+ cost = costs[current_lvl]
+ if state.get('cleanBonds', 0) >= cost:
+ state['cleanBonds'] -= cost
+ state['offshorePerks'][perk_id] = current_lvl + 1
+
elif action_type == 'bribe_police':
- bribe_cost = max(100.0, state['cash'] * 0.15)
+ base_bribe = max(100.0, state['cash'] * 0.15)
+ judiciary_lvl = state.get('offshorePerks', {}).get('corrupt_judiciary', 0)
+ bribe_cost = max(25.0, base_bribe * (1.0 - judiciary_lvl * 0.20))
if state['cash'] >= bribe_cost:
state['cash'] -= bribe_cost
state['heat'] = 0.0
+ state.setdefault('stats', {})['raidsSurvived'] = state['stats'].get('raidsSurvived', 0) + 1
elif action_type == 'flush_stash':
state['heat'] = 20.0
+ state.setdefault('stats', {})['raidsSurvived'] = state['stats'].get('raidsSurvived', 0) + 1
+
+ elif action_type == 'breakout_raid':
+ success = bool(act.get('success', False))
+ if success:
+ state['heat'] = 0.0
+ state.setdefault('stats', {})['raidsSurvived'] = state['stats'].get('raidsSurvived', 0) + 1
elif action_type == 'launder_prestige':
bonds_to_earn = math.floor(math.sqrt(max(0.0, state.get('totalEarned', 0.0)) / 25000.0))
- gain = max(0, bonds_to_earn - state.get('cleanBonds', 0))
+ gain = max(0, bonds_to_earn - state.get('totalCleanBonds', 0))
if gain >= 1:
- state['cleanBonds'] += gain
- state['cash'] = 50.0
+ state['totalCleanBonds'] = state.get('totalCleanBonds', 0) + gain
+ state['cleanBonds'] = state.get('cleanBonds', 0) + gain
+
+ stats = state.setdefault('stats', {})
+ stats['prestigeCount'] = stats.get('prestigeCount', 0) + 1
+
+ # Calculate start cash with bond and swiss kingpin bonuses
+ start_cash = 50.0 + (state['totalCleanBonds'] * 25.0)
+ swiss_tier = state.get('achievements', {}).get('swiss_kingpin', 0)
+ if swiss_tier >= 1: start_cash += 250.0
+ if swiss_tier >= 2: start_cash += 2500.0
+ if swiss_tier >= 3: start_cash += 25000.0
+ if swiss_tier >= 4: start_cash += 250000.0
+
+ state['cash'] = start_cash
state['heat'] = 0.0
+
+ # Starter units from auto_contracts perk
+ free_starter = state.get('offshorePerks', {}).get('auto_contracts', 0)
+
for d_id, d_data in state['drugs'].items():
d_data['unlocked'] = (d_id == 'weed')
d_data['raw'] = 0.0
d_data['product'] = 0.0
- d_data['suppliers'] = 0
- d_data['workers'] = 0
+ d_data['suppliers'] = free_starter if d_data['unlocked'] else 0
+ d_data['workers'] = free_starter if d_data['unlocked'] else 0
d_data['dealers'] = 0
return sanitize_state(state)
@@ -286,7 +468,17 @@ def sanitize_state(state: dict) -> dict:
state['cash'] = clean_round(state.get('cash', 0.0))
state['heat'] = clean_round(min(100.0, max(0.0, state.get('heat', 0.0))))
state['totalEarned'] = clean_round(state.get('totalEarned', 0.0))
- state['cleanBonds'] = int(state.get('cleanBonds', 0))
+ state['cleanBonds'] = max(0, int(state.get('cleanBonds', 0)))
+ state['totalCleanBonds'] = max(state['cleanBonds'], int(state.get('totalCleanBonds', 0)))
+
+ stats = state.setdefault('stats', {})
+ for k in ['totalProduced', 'totalSold', 'totalSupplied']:
+ stats[k] = clean_round(stats.get(k, 0.0))
+ for k in ['purityProcs', 'raidsSurvived', 'prestigeCount']:
+ stats[k] = int(stats.get(k, 0))
+
+ state.setdefault('achievements', {})
+ state.setdefault('offshorePerks', {})
for d_id, data in state.get('drugs', {}).items():
data['raw'] = clean_round(data.get('raw', 0.0))
diff --git a/frontend/css/style.css b/frontend/css/style.css
index 589a455..e3284e3 100644
--- a/frontend/css/style.css
+++ b/frontend/css/style.css
@@ -258,6 +258,215 @@ html, body {
font-weight: 700;
}
+/* Buy Multiplier Selector Bar */
+#buy-multiplier-bar {
+ background: #090c0f;
+ border-top: 1px solid rgba(36, 45, 54, 0.5);
+ padding: 6px 14px;
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+}
+
+.buy-multi-label {
+ font-family: var(--font-mono);
+ font-size: 0.65rem;
+ color: var(--text-secondary);
+ font-weight: 700;
+ letter-spacing: 0.5px;
+}
+
+.buy-multi-group {
+ display: flex;
+ gap: 4px;
+}
+
+.btn-multi-pill {
+ background: #141b22;
+ color: var(--text-secondary);
+ border: 1px solid #283442;
+ border-radius: 6px;
+ padding: 4px 10px;
+ font-family: var(--font-mono);
+ font-size: 0.7rem;
+ font-weight: 700;
+ cursor: pointer;
+ transition: all 0.15s;
+}
+
+.btn-multi-pill:hover {
+ color: var(--text-primary);
+ border-color: var(--border-accent);
+}
+
+.btn-multi-pill.active {
+ background: #252814;
+ border-color: var(--brand-gold);
+ color: var(--brand-gold);
+ box-shadow: 0 0 8px rgba(234, 179, 8, 0.25);
+}
+
+/* Nav Badge for unclaimed Trophies */
+.nav-badge {
+ position: absolute;
+ top: 4px;
+ right: 22%;
+ background: var(--neon-red);
+ color: #fff;
+ font-size: 0.6rem;
+ font-weight: 800;
+ padding: 1px 5px;
+ border-radius: 10px;
+ min-width: 16px;
+ text-align: center;
+ box-shadow: 0 0 8px rgba(239, 68, 68, 0.8);
+ animation: pulse 1.8s infinite;
+}
+
+/* Milestone Indicator */
+.milestone-badge {
+ display: inline-flex;
+ align-items: center;
+ gap: 3px;
+ font-family: var(--font-mono);
+ font-size: 0.65rem;
+ background: #121d26;
+ border: 1px solid #233546;
+ border-radius: 4px;
+ padding: 1px 6px;
+ color: var(--neon-cyan);
+}
+
+/* Trophy Cabinet & Achievements */
+.trophy-card {
+ background: var(--bg-card);
+ border: 1px solid var(--border-color);
+ border-radius: 12px;
+ padding: 12px 14px;
+ display: flex;
+ flex-direction: column;
+ gap: 8px;
+ position: relative;
+ overflow: hidden;
+}
+
+.trophy-card.completed {
+ border-color: #22c55e;
+ background: #0d1a14;
+}
+
+.trophy-card.claimable {
+ border-color: var(--brand-gold);
+ box-shadow: 0 0 12px rgba(234, 179, 8, 0.3);
+ animation: glow-gold 2s infinite alternate;
+}
+
+@keyframes glow-gold {
+ from { box-shadow: 0 0 6px rgba(234, 179, 8, 0.2); }
+ to { box-shadow: 0 0 16px rgba(234, 179, 8, 0.5); }
+}
+
+.trophy-header {
+ display: flex;
+ justify-content: space-between;
+ align-items: flex-start;
+}
+
+.trophy-stars {
+ color: var(--brand-gold);
+ font-size: 0.85rem;
+ letter-spacing: 2px;
+}
+
+.trophy-progress-track {
+ width: 100%;
+ height: 6px;
+ background: #161e27;
+ border-radius: 3px;
+ overflow: hidden;
+}
+
+.trophy-progress-fill {
+ height: 100%;
+ background: linear-gradient(90deg, #06b6d4, #22c55e);
+ transition: width 0.3s ease;
+}
+
+.btn-claim-reward {
+ background: linear-gradient(135deg, #eab308 0%, #ca8a04 100%);
+ color: #000;
+ font-weight: 800;
+ font-family: var(--font-mono);
+ border: none;
+ border-radius: 8px;
+ padding: 8px 12px;
+ cursor: pointer;
+ text-align: center;
+ box-shadow: 0 0 12px rgba(234, 179, 8, 0.6);
+ transition: transform 0.1s, filter 0.15s;
+}
+
+.btn-claim-reward:active {
+ transform: scale(0.96);
+}
+
+/* Swiss Offshore Perk Shop */
+.perk-grid {
+ display: grid;
+ grid-template-columns: 1fr;
+ gap: 10px;
+ margin-top: 10px;
+}
+
+.perk-card {
+ background: #0d1217;
+ border: 1px solid #1f2a36;
+ border-radius: 10px;
+ padding: 12px;
+ display: flex;
+ flex-direction: column;
+ gap: 8px;
+}
+
+.perk-card-header {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+}
+
+.perk-lvl-badge {
+ font-family: var(--font-mono);
+ font-size: 0.7rem;
+ font-weight: bold;
+ color: var(--neon-cyan);
+ background: #101c26;
+ padding: 2px 8px;
+ border-radius: 4px;
+ border: 1px solid #1a3246;
+}
+
+.btn-perk-buy {
+ background: #122332;
+ border: 1px solid #234562;
+ color: var(--neon-cyan);
+ padding: 8px;
+ border-radius: 6px;
+ font-family: var(--font-mono);
+ font-weight: 700;
+ font-size: 0.75rem;
+ cursor: pointer;
+ transition: background 0.15s;
+}
+
+.btn-perk-buy:hover:not(:disabled) {
+ background: #18334a;
+}
+
+.btn-perk-buy:disabled {
+ opacity: 0.4;
+ cursor: not-allowed;
+}
+
/* Main Viewport Content */
#main-viewport {
flex: 1;
diff --git a/frontend/index.html b/frontend/index.html
index 8b4438c..bb7f3b7 100644
--- a/frontend/index.html
+++ b/frontend/index.html
@@ -71,6 +71,18 @@
+
+
+
+
BUY QTY:
+
+
+
+
+
+
+
+
@@ -91,6 +103,11 @@
Streets
+