Files
Space-Game/js/pages/economy.js
2026-05-25 13:00:20 -04:00

1051 lines
77 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
window.GDD = window.GDD || {};
function EconomyPage() {
const [activeSection, setActiveSection] = React.useState('overview');
const refiningTable = [
{ ore: 'Veldspar', mineral: 'Tritanium', yield: 415, batch: 333, time: '45s' },
{ ore: 'Scordite', mineral: 'Pyerite', yield: 171, batch: 333, time: '45s' },
{ ore: 'Pyroxeres', mineral: 'Nocxium', yield: 8, batch: 333, time: '60s' },
{ ore: 'Kernite', mineral: 'Isogen', yield: 107, batch: 200, time: '60s' },
{ ore: 'Omber', mineral: 'Isogen', yield: 86, batch: 500, time: '75s' },
{ ore: 'Jaspet', mineral: 'Zydrine', yield: 8, batch: 500, time: '75s' },
{ ore: 'Hemorphite', mineral: 'Nocxium', yield: 21, batch: 500, time: '90s' },
{ ore: 'Arkonor', mineral: 'Megacyte', yield: 18, batch: 200, time: '120s' },
];
const manufacturingRecipes = [
{ product: 'Mining Laser I', minerals: 'Tritanium ×200, Pyerite ×80', time: '5m', station: 'Any', skill: 'Industry I' },
{ product: '150mm Railgun', minerals: 'Tritanium ×400, Pyerite ×150, Nocxium ×20', time: '15m', station: 'Any', skill: 'Industry II' },
{ product: 'Shield Booster I', minerals: 'Tritanium ×300, Isogen ×50', time: '10m', station: 'Any', skill: 'Industry II' },
{ product: 'Frigate Hull', minerals: 'Tritanium ×2000, Pyerite ×800, Nocxium ×100', time: '30m', station: 'Factory', skill: 'Industry III' },
{ product: 'Cruiser Hull', minerals: 'Tritanium ×8000, Pyerite ×3000, Isogen ×500, Nocxium ×200', time: '2h', station: 'Factory', skill: 'Industry IV' },
{ product: '1MN Afterburner', minerals: 'Tritanium ×150, Pyerite ×50, Isogen ×20', time: '8m', station: 'Any', skill: 'Industry II' },
];
const faucetsAndSinks = [
{ type: 'faucet', name: 'NPC Buy Orders', description: 'Station NPCs buy basic ores at floor prices. Guarantees new players can always earn ISK.', rate: '~2,000 ISK/min for rookie miner' },
{ type: 'faucet', name: 'Insurance Payout', description: 'Ship destruction triggers insurance payout (30120s delay). Tier-dependent: 4095% of hull value. See Gameplay → Insurance tab.', rate: 'Varies by ship value and tier' },
{ type: 'faucet', name: 'Mission Rewards', description: 'NPC agents pay ISK for completed missions. Scaling rewards based on mission tier and standing. See Gameplay → Missions tab.', rate: '~5,000300,000 ₢ per mission (level 14)' },
{ type: 'faucet', name: 'Bounty Prizes', description: 'NPC pirates drop bounty ISK when destroyed. Higher in low-sec space.', rate: '~1,00020,000 ISK per kill' },
{ type: 'faucet', name: 'Loyalty Point Store', description: 'Mission LP can be exchanged for faction items at below-market prices, creating indirect ISK value. See Gameplay → Missions tab.', rate: '~500 LP per mission, redeemable for items worth 150₢/LP' },
{ type: 'sink', name: 'Market Tax', description: '2% tax on all market transactions. Higher in NPC stations, lower in player stations (post-MVP).', rate: '2% of transaction value' },
{ type: 'sink', name: 'Manufacturing Fees', description: 'Station takes a cut of mineral value as a manufacturing fee.', rate: '15% of output value' },
{ type: 'sink', name: 'Ship Purchase', description: 'Buying ships and modules from market or manufacturing. Largest single sink.', rate: 'Variable' },
{ type: 'sink', name: 'Insurance Premiums', description: 'Players pay 1050% of hull value for insurance coverage. See Gameplay → Insurance tab.', rate: '1050% of hull value per 30-day policy' },
{ type: 'sink', name: 'Blueprint Research', description: 'ME/TE research costs ISK and time. See Economy → Manufacturing tab.', rate: '50,0005,000,000 ₢ per research level' },
{ type: 'sink', name: 'Crew Wages', description: 'AI crew members require wage payments. Higher rank = higher cost. (Post-MVP)', rate: '~5005,000 ISK/cycle' },
];
return (
<div className="content-inner">
<h1 style={{ marginBottom: '8px' }}>Economy & Industry</h1>
<p style={{ color: 'var(--fg-dim)', fontSize: '0.95rem', maxWidth: '680px' }}>
Player-led economy with NPC support as an underlying demand/supply buffer. Players mine, refine,
manufacture, and trade. NPCs provide a price floor and basic market liquidity so new players
always have a way to earn and spend.
</p>
<div className="stat-grid" style={{ marginTop: 'var(--sp-5)' }}>
<div className="stat-card">
<div className="stat-value" style={{ color: 'var(--accent)' }}>Player-led</div>
<div className="stat-label">Economy Model</div>
</div>
<div className="stat-card">
<div className="stat-value" style={{ color: 'var(--cyan)' }}>8 8</div>
<div className="stat-label">Ore Types Minerals</div>
</div>
<div className="stat-card">
<div className="stat-value" style={{ color: 'var(--green)' }}>8+</div>
<div className="stat-label">Manufacturable Items</div>
</div>
<div className="stat-card">
<div className="stat-value" style={{ color: 'var(--purple)' }}>2%</div>
<div className="stat-label">Market Tax</div>
</div>
</div>
{/* Tab navigation */}
<div style={{ display: 'flex', gap: 'var(--sp-2)', marginBottom: 'var(--sp-6)', flexWrap: 'wrap' }}>
{[
{ id: 'overview', label: 'Flow Overview' },
{ id: 'first30', label: '🚀 First 30 Minutes' },
{ id: 'diffusion', label: '📡 Info Diffusion' },
{ id: 'refining', label: 'Refining' },
{ id: 'manufacturing', label: 'Manufacturing' },
{ id: 'npc-pricing', label: '💹 NPC Pricing' },
{ id: 'faucets', label: 'Faucets & Sinks' },
].map(t => (
<button key={t.id} className={`btn btn-sm${activeSection === t.id ? ' btn-primary' : ''}`}
onClick={() => setActiveSection(t.id)}>{t.label}</button>
))}
</div>
{/* FIRST 30 MINUTES */}
{activeSection === 'first30' && (
<>
<div className="section-header">
<span className="section-num">ECON-30</span>
<h2 style={{ margin: 0 }}>First 30 Minutes of Economy</h2>
</div>
<div className="callout callout-info" style={{ marginBottom: 'var(--sp-5)' }}>
<strong>Exploration and commerce are the core pillars.</strong> This walkthrough shows what a new player sees, decides,
and experiences in their first 30 minutes from undocking with an empty cargo hold to discovering their first price
difference and making a profitable trade. The goal: by minute 30, the player <em>understands</em> that information asymmetry
is the game, and they've profited from it at least once.
</div>
<div style={{ marginTop: 'var(--sp-6)' }}>
{[
{ time: '0:00', title: 'Spawn at Home Station', desc: 'Player connects to SpacetimeDB, spawns in a rookie frigate at their home station. Station Mode UI is active: Market Panel, Inventory, Fitting Screen, and Agent Panel are visible in the sidebar. Wallet shows 0 ISK.', color: 'var(--cyan)' },
{ time: '0:30', title: 'First NPC Agent Mission', desc: 'The Agent Panel glows with an available mission. Player talks to Agent "Welcome, pilot. I need 500 units of Veldspar delivered to this station. I\'ll pay 1,500 ISK." Accept mission. Objective marker appears on System Map.', color: 'var(--green)' },
{ time: '1:00', title: 'Undock into Flight Mode', desc: '3D viewport fades in. Ship floats near the station. Asteroid belts visible as icons in the overview panel. Target the nearest belt and click "Warp To". Ship enters warp.', color: 'var(--accent)' },
{ time: '2:00', title: 'Mining Cycle', desc: 'Arrive at belt. Three asteroids in view. Target a Veldspar asteroid, click "Mine". Ship approaches automatically. Mining laser activates progress bar fills. Cargo counter ticks up: 0/150, 10/150, 25/150... The mining laser hums. Zora chirps: "Cargo at 33%."', color: 'var(--purple)' },
{ time: '5:00', title: 'Cargo Full Return to Station', desc: 'Cargo hits 150/150 units. HUD flashes "CARGO FULL". Player warps back to station. Docks. Station Mode transitions in. Inventory shows 150 Veldspar.', color: 'var(--cyan)' },
{ time: '6:00', title: 'First Sale The NPC Price', desc: 'Open Market Panel. Veldspar shows: NPC Buy Price = 2.50\u2262/unit. Sell 150 units wallet shows 375 ISK. Not enough for a module. Zora notes: "You could earn more if you refine first — your Industry skill gives 60% yield."', color: 'var(--green)' },
{ time: '8:00', title: 'The Discovery Second Station Prices', desc: 'Player warps to a second station in the same system (or an adjacent system). Opens Market Panel. Veldspar NPC Buy Price here = 3.10\u2262/unit. That\'s 24% more. The price is different! This is the aha moment — geography matters.', color: 'var(--red)' },
{ time: '10:00', title: 'The Decision', desc: 'Mine more Veldspar at the nearby belt. Cargo fills to 150 again. Two choices visible: (A) Sell at Station 1 for 2.50\u2262 = 375 ISK, or (B) Fly to Station 2 and sell for 3.10\u2262 = 465 ISK. Option B earns 90 ISK more — but takes 2 minutes of travel time. Is it worth it?', color: 'var(--accent)' },
{ time: '12:00', title: 'Executing the Trade Route', desc: 'Player chooses Station 2. Warps there, docks, sells. Wallet: 840 ISK. They notice the Market Panel also shows Scordite at 5.20\u2262 here vs. 4.00\u2262 at Station 1. A second price gap. The loop beckons.', color: 'var(--cyan)' },
{ time: '15:00', title: 'Agent Mission Turn-In', desc: 'Return to home station. Turn in the 500 Veldspar mission (already over-delivered). Agent pays 1,500 ISK + 50 LP. Wallet: 2,340 ISK + 150 units excess ore. Standing +0.10 with agent.', color: 'var(--green)' },
{ time: '18:00', title: 'First Refining Decision', desc: 'Refining tab shows: 200 Veldspar at 60% yield = 249 Tritanium (worth ~797\u2262 vs. raw ore at 500\u2262). Refine. Minerals appear in hangar. The production chain becomes visible.', color: 'var(--purple)' },
{ time: '22:00', title: 'Zora Flags an Opportunity', desc: 'Zora\'s market module fires: "I\'ve tracked your trades. Veldspar prices at Station 2 have been above average for 3 cycles. This looks like sustained demand — possibly a manufacturing cluster buying raw materials. I\'d recommend filling your cargo before heading there next time."', color: 'var(--accent)' },
{ time: '25:00', title: 'Manufacturing Tab Peek', desc: 'Player opens Manufacturing tab. Mining Laser I requires: 200 Tritanium + 80 Pyerite (which they partially have). The blueprint shows a 5-minute job. They\'re 40 Pyerite short. They know Scordite refines to Pyerite. A new mining target appears.', color: 'var(--cyan)' },
{ time: '30:00', title: 'The Loop Is Set', desc: 'Player has 2,340 ISK, 249 Tritanium, a standing relationship with an NPC agent, and two known price differences between stations. They understand: mine → choose where to sell → reinvest → manufacture. The economy is no longer abstract — it\'s a map of opportunities they can see and act on.', color: 'var(--green)' },
].map((step, i) => (
<div key={i} className="phase-item">
<div className="phase-marker">
<div className="phase-dot" style={{ background: step.color, boxShadow: '0 0 8px ' + step.color + '40' }} />
{i < 13 && <div className="phase-line" />}
</div>
<div className="phase-content">
<div style={{ display: 'flex', alignItems: 'baseline', gap: 'var(--sp-3)', marginBottom: 'var(--sp-1)' }}>
<span style={{ fontFamily: 'var(--font-mono)', fontSize: '0.7rem', color: step.color }}>MIN {step.time}</span>
<h4 style={{ margin: 0 }}>{step.title}</h4>
</div>
<p style={{ color: 'var(--fg-dim)', fontSize: '0.85rem', margin: 0 }}>{step.desc}</p>
</div>
</div>
))}
</div>
<div className="callout callout-warn" style={{ marginTop: 'var(--sp-6)' }}>
<strong>Design intent:</strong> By minute 8, the player has discovered that prices differ between stations.
By minute 12, they\'ve acted on that discovery and profited. By minute 30, they have a mental model of the
economy as a landscape of opportunities, not a single "sell" button. This is the hook. If the price discovery
moment doesn\'t feel exciting, the entire game falls flat.
</div>
</>
)}
{/* DIFFUSION */}
{activeSection === 'diffusion' && (
<>
<div className="section-header">
<span className="section-num">ECON-📡</span>
<h2 style={{ margin: 0 }}>Information Diffusion Between Systems</h2>
</div>
<div className="callout callout-info" style={{ marginBottom: 'var(--sp-5)' }}>
<strong>This IS the game.</strong> In EVE Online, the richest players aren't the best pilots they're the best
informed. Information about market prices, supply disruptions, and trade opportunities propagates at the speed
of player travel, not the speed of light. A player who knows that Veldspar spiked 40% in Amarr while it's still
cheap in Jita has a window of opportunity that closes as other traders make the same discovery. This is the
core loop of a spreadsheet simulator: <strong>information asymmetry profitable action market correction</strong>.
</div>
<div className="card card-accent" style={{ padding: 'var(--sp-5) var(--sp-6)', marginBottom: 'var(--sp-5)' }}>
<h4 style={{ color: 'var(--accent)', marginBottom: 'var(--sp-4)' }}>Information Propagation Model</h4>
<div style={{ fontFamily: 'var(--font-mono)', fontSize: '0.82rem', color: 'var(--fg-dim)', lineHeight: 2.2 }}>
<span style={{ color: 'var(--cyan)' }}>Event occurs</span> (belt depleted, station sold out, pirate attack) {' '}
<span style={{ color: 'var(--accent)' }}>Local chat sees it immediately</span> {' '}
<span style={{ color: 'var(--green)' }}>Adjacent systems learn in ~2 min</span> {' '}
<span style={{ color: 'var(--purple)' }}>Hub markets update in ~5 min</span> {' '}
<span style={{ color: 'var(--fg)' }}>Full region in ~15 min</span> {' '}
<span style={{ color: 'var(--muted)' }}>Galaxy-wide in ~30 min</span> <span style={{ color: 'var(--accent)', fontSize: '0.7rem' }}>(single galaxy propagation)</span>
</div>
</div>
<h3 style={{ marginBottom: 'var(--sp-4)' }}>Information Channels</h3>
<table className="data-table">
<thead>
<tr><th>Channel</th><th>Propagation</th><th>Latency</th><th>Reliability</th><th>Example</th></tr>
</thead>
<tbody>
<tr>
<td><span className="pill pill-cyan">Local Market</span></td>
<td>Instant, system-only</td>
<td>0s</td>
<td>100% live order book</td>
<td>You see every buy/sell order in your current station</td>
</tr>
<tr>
<td><span className="pill pill-green">Local Chat</span></td>
<td>Instant, system-only</td>
<td>0s</td>
<td>Unverified players lie</td>
<td>"Scordite prices are crashing in Amarr" could be true, could be market manipulation</td>
</tr>
<tr>
<td><span className="pill pill-amber">Ship AI Reports</span></td>
<td>Aggregated, player-specific</td>
<td>Real-time</td>
<td>High computed from data your ship has seen</td>
<td>Zora says: "Veldspar is 18% below its 7-day average here. I've logged 3 similar price dips that corrected within 2 hours."</td>
</tr>
<tr>
<td><span className="pill pill-purple">Region Market Data</span></td>
<td>Delayed by jump distance</td>
<td>210 min</td>
<td>Stale snapshot, not live</td>
<td>The price you see for a system 5 jumps away is from 5 minutes ago. It may have moved.</td>
</tr>
<tr>
<td><span className="pill pill-red">News Feed</span></td>
<td>Galaxy-wide events</td>
<td>1530 min</td>
<td>Factual but delayed</td>
<td>"Major fleet engagement in PF-346. Megacyte supply disruptions expected." Includes world events: faction wars, anomalies, migrations.</td>
</tr>
<tr>
<td><span className="pill pill-cyan">Scout Reports</span></td>
<td>Manual, player-to-player</td>
<td>Variable</td>
<td>Trust-based</td>
<td>A corpmate tells you they saw a Tritanium shortage in Rens. First-hand intel.</td>
</tr>
</tbody>
</table>
<h3 style={{ marginTop: 'var(--sp-6)', marginBottom: 'var(--sp-4)' }}>Why This Matters</h3>
<div className="grid-2">
<div className="card" style={{ borderLeft: '3px solid var(--accent)' }}>
<h4 style={{ color: 'var(--accent)' }}>Information Asymmetry = Gameplay</h4>
<p style={{ color: 'var(--fg-dim)', fontSize: '0.85rem', margin: '0 0 var(--sp-3) 0' }}>
The player who <em>knows</em> a price discrepancy exists <em>first</em> can fill a hauler and profit.
The player who arrives late finds the gap already closed. This is why speed of information matters,
and why players will pay for better intel, scout networks, and fast ships for information running.
</p>
</div>
<div className="card" style={{ borderLeft: '3px solid var(--cyan)' }}>
<h4 style={{ color: 'var(--cyan)' }}>Delayed Data = Speculation</h4>
<p style={{ color: 'var(--fg-dim)', fontSize: '0.85rem', margin: '0 0 var(--sp-3) 0' }}>
Because market data from other systems is delayed, traders must <em>predict</em> where prices are heading.
They see a snapshot from 5 minutes ago and must decide: is the trend still moving, or has it reversed?
This creates genuine risk and reward from pure information work no combat required.
</p>
</div>
<div className="card" style={{ borderLeft: '3px solid var(--green)' }}>
<h4 style={{ color: 'var(--green)' }}>Geography = Strategy</h4>
<p style={{ color: 'var(--fg-dim)', fontSize: '0.85rem', margin: '0 0 var(--sp-3) 0' }}>
Systems farther from trade hubs have more stale data and wider spreads. Deep null-sec markets are
almost blind the player who establishes a reliable supply chain out there controls the local economy.
Distance is not just travel time; it's an information barrier.
</p>
</div>
<div className="card" style={{ borderLeft: '3px solid var(--purple)' }}>
<h4 style={{ color: 'var(--purple)' }}>Zora as Information Edge</h4>
<p style={{ color: 'var(--fg-dim)', fontSize: '0.85rem', margin: '0 0 var(--sp-3) 0' }}>
The ship AI (Zora) is a force multiplier for information. She remembers every price the player has seen,
spots patterns, flags anomalies, and can continue monitoring markets while the player is offline.
A high-phase Zora is worth more than any single module — she's a living spreadsheet that
<em> thinks</em> about your portfolio.
</p>
</div>
</div>
<h3 style={{ marginTop: 'var(--sp-6)', marginBottom: 'var(--sp-4)' }}>Implementation: Market Data Pipeline</h3>
<div className="card" style={{ padding: 0, overflow: 'hidden' }}>
<div style={{ padding: 'var(--sp-3) var(--sp-4)', background: 'var(--surface-raised)', borderBottom: '1px solid var(--border)' }}>
<span style={{ fontFamily: 'var(--font-mono)', fontSize: '0.7rem', color: 'var(--accent)', textTransform: 'uppercase', letterSpacing: '0.05em' }}>
SpacetimeDB Market Diffusion Tables
</span>
</div>
<div className="code-block" style={{ margin: 0, borderRadius: 0 }}>
<code>
<span className="cm">// Each system has its own view of market reality</span><br/>
<span className="kw">table</span> <span className="type">system_market_view</span> {'{'}<br/>
&nbsp;&nbsp;system_id: <span className="type">u64</span>,<br/>
&nbsp;&nbsp;commodity_id: <span className="type">u64</span>,<br/>
&nbsp;&nbsp;best_bid: <span className="type">f64</span>,<br/>
&nbsp;&nbsp;best_ask: <span className="type">f64</span>,<br/>
&nbsp;&nbsp;last_trade_price: <span className="type">f64</span>,<br/>
&nbsp;&nbsp;volume_24h: <span className="type">u64</span>,<br/>
&nbsp;&nbsp;snapshot_time: <span className="type">timestamp</span>,<br/>
&nbsp;&nbsp;<span className="cm">// ^ How stale is this data? Key field.</span><br/>
{'}'}<br/>
<br/>
<span className="cm">// Diffusion scheduler — propagates data between connected systems</span><br/>
<span className="kw">reducer</span> <span className="fn">propagate_market_data</span>(ctx) {'{'}<br/>
&nbsp;&nbsp;<span className="cm">// Each tick, for each system pair connected by a gate:</span><br/>
&nbsp;&nbsp;<span className="cm">// 1. Compare local prices vs neighbor's snapshot</span><br/>
&nbsp;&nbsp;<span className="cm">// 2. If snapshot_age > propagation_delay, push updated prices</span><br/>
&nbsp;&nbsp;<span className="cm">// 3. Add random noise proportional to distance from source</span><br/>
&nbsp;&nbsp;<span className="cm">// 4. Diffusion speed ~1 system per 2 minutes per jump</span><br/>
{'}'}<br/>
</code>
</div>
</div>
</>
)}
{/* OVERVIEW */}
{activeSection === 'overview' && (
<>
<div className="card card-accent" style={{ padding: 'var(--sp-6) var(--sp-8)' }}>
<h4 style={{ marginBottom: 'var(--sp-4)' }}>Economy Pipeline</h4>
<div style={{ fontFamily: 'var(--font-mono)', fontSize: '0.85rem', color: 'var(--fg-dim)', lineHeight: 2.2 }}>
<span style={{ color: 'var(--accent)' }}>Mine Ore</span> {' '}
<span style={{ color: 'var(--fg-bright)' }}>Choose:</span><br/>
&nbsp;&nbsp; <span style={{ color: 'var(--green)' }}>Sell Raw</span> <span style={{ color: 'var(--cyan)' }}>Market</span> (fast, lower price)<br/>
&nbsp;&nbsp; <span style={{ color: 'var(--purple)' }}>Refine</span> <span style={{ color: 'var(--fg-bright)' }}>Minerals</span> {' '}
<span style={{ color: 'var(--fg-bright)' }}>Choose:</span><br/>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <span style={{ color: 'var(--green)' }}>Sell Minerals</span> <span style={{ color: 'var(--cyan)' }}>Market</span> (higher value)<br/>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <span style={{ color: 'var(--red)' }}>Manufacture</span> <span style={{ color: 'var(--accent)' }}>Ships/Modules</span> {' '}
<span style={{ color: 'var(--cyan)' }}>Use or Sell</span>
</div>
</div>
<div className="section-header" style={{ marginTop: 'var(--sp-6)' }}>
<span className="section-num">ECON-PHI</span>
<h2 style={{ margin: 0 }}>Design Philosophy</h2>
</div>
<div className="grid-2">
<div className="card">
<h4 style={{ color: 'var(--accent)' }}>Player agency first</h4>
<p style={{ color: 'var(--fg-dim)', fontSize: '0.85rem', margin: 0 }}>
Players set prices, build supply chains, and create market dynamics. NPC orders are a
safety net, not the primary economy. The goal is emergent trade routes, regional price
differences, and player-driven scarcity.
</p>
</div>
<div className="card">
<h4 style={{ color: 'var(--cyan)' }}>NPC as buffer</h4>
<p style={{ color: 'var(--fg-dim)', fontSize: '0.85rem', margin: 0 }}>
NPC buy orders create a price floor so mining is always worth something. NPC sell orders
provide basic items at a premium so new players aren't stuck. As the player economy
matures, NPCs step back.
</p>
</div>
<div className="card">
<h4 style={{ color: 'var(--green)' }}>Geographic trade</h4>
<p style={{ color: 'var(--fg-dim)', fontSize: '0.85rem', margin: 0 }}>
Different systems produce different ores. Null-sec has rarer minerals but higher risk.
This creates natural trade corridors and hauler opportunities. Region matters.
</p>
</div>
<div className="card">
<h4 style={{ color: 'var(--purple)' }}>Sinks balance faucets</h4>
<p style={{ color: 'var(--fg-dim)', fontSize: '0.85rem', margin: 0 }}>
Every ISK entering the economy must eventually leave. Ship destruction is the primary
sink — it's fun, dramatic, and removes value. Taxes and fees are the steady drain.
Inflation is the enemy.
</p>
</div>
</div>
<div className="section-header" style={{ marginTop: 'var(--sp-8)' }}>
<span className="section-num">ECON-MKT</span>
<h2 style={{ margin: 0 }}>Market Surface The Primary Game Interface</h2>
</div>
<div className="callout callout-info" style={{ marginBottom: 'var(--sp-5)' }}>
<strong>The Market Panel is where players spend most of their time.</strong> It is the richest, most complex UI surface
in the game. The Market demo (<code>js/demos/market.js</code>) validates a feature set that goes beyond the original
Economy spec. This section brings the spec up to what the demo actually implements.
</div>
<div className="grid-2" style={{ marginBottom: 'var(--sp-6)' }}>
<div className="card" style={{ borderLeft: '3px solid var(--cyan)' }}>
<h4 style={{ color: 'var(--cyan)' }}>Order Book & Depth</h4>
<p style={{ color: 'var(--fg-dim)', fontSize: '0.85rem', margin: '0 0 var(--sp-3) 0' }}>
Every commodity has a live order book with <strong style={{ color: 'var(--fg)' }}>bid/ask spread</strong> visualization.
Buy orders (bids) stack on the left, sell orders (asks) on the right. Depth chart shows cumulative volume at each price level.
Players can see exactly where their order sits in the queue.
</p>
<div style={{ fontFamily: 'var(--font-mono)', fontSize: '0.72rem', color: 'var(--muted)' }}>
Order types: Market (instant fill), Limit (price-specified), Stop (trigger-based)
</div>
</div>
<div className="card" style={{ borderLeft: '3px solid var(--accent)' }}>
<h4 style={{ color: 'var(--accent)' }}>Price History & Charts</h4>
<p style={{ color: 'var(--fg-dim)', fontSize: '0.85rem', margin: '0 0 var(--sp-3) 0' }}>
<strong style={{ color: 'var(--fg)' }}>Candlestick charts</strong> with configurable timeframes (1h, 6h, 24h, 7d, 30d).
Volume bars overlaid. Moving averages (7-period, 20-period). Players can annotate charts and share screenshots.
Historical data is per-station, per-commodity no galaxy-wide aggregation (information is local).
</p>
<div style={{ fontFamily: 'var(--font-mono)', fontSize: '0.72rem', color: 'var(--muted)' }}>
Data source: SpacetimeDB market_orders table + computed aggregations
</div>
</div>
<div className="card" style={{ borderLeft: '3px solid var(--green)' }}>
<h4 style={{ color: 'var(--green)' }}>Contract Specifications</h4>
<p style={{ color: 'var(--fg-dim)', fontSize: '0.85rem', margin: '0 0 var(--sp-3) 0' }}>
Each commodity is traded as a <strong style={{ color: 'var(--fg)' }}>contract</strong> with standardized lot sizes.
Contracts specify: commodity type, lot size, tick size (minimum price increment), and settlement rules.
This enables the order book to aggregate orders efficiently and provides a clean abstraction for the market engine.
</p>
<div style={{ fontFamily: 'var(--font-mono)', fontSize: '0.72rem', color: 'var(--muted)' }}>
Example: Tritanium contract = 1,000 units/lot, tick = 0.01
</div>
</div>
<div className="card" style={{ borderLeft: '3px solid var(--purple)' }}>
<h4 style={{ color: 'var(--purple)' }}>Margin Accounts & Positions</h4>
<p style={{ color: 'var(--fg-dim)', fontSize: '0.85rem', margin: '0 0 var(--sp-3) 0' }}>
<strong style={{ color: 'var(--fg)' }}>Era 2 feature.</strong> Players can open <strong style={{ color: 'var(--fg)' }}>long and short positions</strong>
on commodity contracts using a margin account. Margin requires collateral (ISK + assets). Maintenance margin enforced
if position moves against the player beyond the margin, the position is force-liquidated.
This adds speculative depth for advanced traders.
</p>
<div style={{ fontFamily: 'var(--font-mono)', fontSize: '0.72rem', color: 'var(--muted)' }}>
Margin ratio: 20% initial, 10% maintenance · Force liquidation at 5%
</div>
</div>
<div className="card" style={{ borderLeft: '3px solid var(--red)' }}>
<h4 style={{ color: 'var(--red)' }}>Commodity Ticker</h4>
<p style={{ color: 'var(--fg-dim)', fontSize: '0.85rem', margin: '0 0 var(--sp-3) 0' }}>
Scrolling price ticker across all active contracts. Real-time price updates filtered by player's current station
or region. Category filters (Ore, Minerals, Modules, Ships). Sparkline mini-charts next to each ticker entry.
The ticker runs in both Flight Mode (bottom bar) and Station Mode (market panel sidebar).
</p>
<div style={{ fontFamily: 'var(--font-mono)', fontSize: '0.72rem', color: 'var(--muted)' }}>
Update rate: 5s in-station, 30s regional (info diffusion applies)
</div>
</div>
<div className="card" style={{ borderLeft: '3px solid var(--cyan)' }}>
<h4 style={{ color: 'var(--cyan)' }}>Station-Filtered View</h4>
<p style={{ color: 'var(--fg-dim)', fontSize: '0.85rem', margin: '0 0 var(--sp-3) 0' }}>
Market data is <strong style={{ color: 'var(--fg)' }}>station-local by default</strong>. Players see the full order book
for their current station. To see prices at other stations, they must use the Region Market Data channel
(delayed, see Info Diffusion tab) or travel there physically. No global market view exists — information geography is real.
</p>
<div style={{ fontFamily: 'var(--font-mono)', fontSize: '0.72rem', color: 'var(--muted)' }}>
Players at trade hubs see more orders, tighter spreads, and faster updates
</div>
</div>
</div>
<div className="callout callout-info" style={{ marginTop: 'var(--sp-5)' }}>
<strong>Priority callout:</strong> Economy feel + social/multiplayer feel are the two most
important things to get right. If the economy doesn't feel rewarding and dynamic, the game
falls flat regardless of how good combat or graphics are.
</div>
<div className="callout callout-warn" style={{ marginTop: 'var(--sp-4)' }}>
<strong>Currency naming:</strong> "ISK" (symbol \u2262) is a <em>temporary placeholder</em>. The final in-game currency name
will be chosen during development. All references to ISK throughout the GDD should be understood as subject to renaming.
</div>
</>
)}
{/* REFINING */}
{activeSection === 'refining' && (
<>
<div className="section-header">
<span className="section-num">ECON-REF</span>
<h2 style={{ margin: 0 }}>Ore Mineral Refining</h2>
</div>
<p style={{ color: 'var(--fg-dim)', fontSize: '0.9rem', maxWidth: '680px', marginBottom: 'var(--sp-5)' }}>
Raw ore can be sold directly or refined into minerals at a station with reprocessing
facilities. Refined minerals are worth more per but require batch sizes and processing
time. Refining efficiency improves with the player's Industry skill level.
</p>
<div style={{ overflowX: 'auto' }}>
<table className="data-table">
<thead>
<tr>
<th>Ore</th>
<th>Yields Mineral</th>
<th>Units per Batch</th>
<th>Base Yield</th>
<th>Process Time</th>
</tr>
</thead>
<tbody>
{refiningTable.map((row, i) => (
<tr key={i}>
<td style={{ color: 'var(--accent)' }}>{row.ore}</td>
<td style={{ color: 'var(--cyan)' }}>{row.mineral}</td>
<td className="mono">{row.batch}</td>
<td className="mono">{row.yield}</td>
<td className="mono">{row.time}</td>
</tr>
))}
</tbody>
</table>
</div>
<div className="grid-2" style={{ marginTop: 'var(--sp-5)' }}>
<div className="card">
<h4 style={{ color: 'var(--accent)' }}>Refining Efficiency</h4>
<div style={{ fontFamily: 'var(--font-mono)', fontSize: '0.82rem', color: 'var(--fg-dim)', lineHeight: 2 }}>
<strong style={{ color: 'var(--fg)' }}>Base efficiency:</strong> 50%<br/>
<strong style={{ color: 'var(--fg)' }}>Industry I:</strong> 60%<br/>
<strong style={{ color: 'var(--fg)' }}>Industry II:</strong> 70%<br/>
<strong style={{ color: 'var(--fg)' }}>Industry III:</strong> 80%<br/>
<strong style={{ color: 'var(--fg)' }}>Industry IV:</strong> 87.5%<br/>
<strong style={{ color: 'var(--fg)' }}>Industry V:</strong> 95%<br/>
<span style={{ color: 'var(--muted)' }}>Lost material is destroyed (not recovered).</span>
</div>
</div>
<div className="card">
<h4 style={{ color: 'var(--cyan)' }}>Decision: Sell Raw vs Refine?</h4>
<p style={{ color: 'var(--fg-dim)', fontSize: '0.85rem', margin: '0 0 var(--sp-3) 0' }}>
At 50% efficiency, selling raw ore is usually better ISK/m³. At 80%+ efficiency,
refining is almost always more profitable. This creates a natural skill gate:
new players sell raw, veterans refine and manufacture.
</p>
<div className="callout callout-warn" style={{ fontSize: '0.8rem' }}>
<strong>Strategic choice:</strong> Do you invest XP into Industry early for better margins,
or into combat skills for safer mining in low-sec?
</div>
</div>
</div>
</>
)}
{/* MANUFACTURING */}
{activeSection === 'manufacturing' && (
<>
<div className="section-header">
<span className="section-num">ECON-MFG</span>
<h2 style={{ margin: 0 }}>Manufacturing</h2>
</div>
<p style={{ color: 'var(--fg-dim)', fontSize: '0.9rem', maxWidth: '680px', marginBottom: 'var(--sp-5)' }}>
Players can manufacture ships, modules, and ammunition from refined minerals. Manufacturing
requires a station with factory facilities, the correct minerals, and the appropriate Industry
skill level. Manufacturing time creates natural production queues and opportunity cost.
</p>
<div style={{ overflowX: 'auto' }}>
<table className="data-table">
<thead>
<tr>
<th>Product</th>
<th>Mineral Cost</th>
<th>Time</th>
<th>Station</th>
<th>Skill Req.</th>
</tr>
</thead>
<tbody>
{manufacturingRecipes.map((row, i) => (
<tr key={i}>
<td style={{ color: 'var(--accent)' }}>{row.product}</td>
<td style={{ color: 'var(--fg-dim)', fontSize: '0.75rem' }}>{row.minerals}</td>
<td className="mono">{row.time}</td>
<td>{row.station}</td>
<td><span className="pill pill-cyan">{row.skill}</span></td>
</tr>
))}
</tbody>
</table>
</div>
<div className="section-header" style={{ marginTop: 'var(--sp-8)' }}>
<span className="section-num">ECON-MFG.1</span>
<h2 style={{ margin: 0 }}>Full Production Chain</h2>
</div>
<div className="callout callout-info" style={{ marginBottom: 'var(--sp-5)' }}>
<strong>Manufacturing is not one step — it's a chain.</strong>
Raw ore refines into minerals. Minerals can be directly manufactured into basic modules, but advanced items
require intermediate components first. This multi-stage chain creates specialization opportunities:
a player who focuses on component manufacturing sells to ship builders who have never mined a single asteroid.
</div>
<div className="card card-accent" style={{ padding: 'var(--sp-5) var(--sp-6)', marginBottom: 'var(--sp-5)' }}>
<h4 style={{ color: 'var(--accent)', marginBottom: 'var(--sp-4)' }}>Production Chain Tiers</h4>
<div style={{ fontFamily: 'var(--font-mono)', fontSize: '0.82rem', color: 'var(--fg-dim)', lineHeight: 2.2 }}>
<span style={{ color: 'var(--accent)' }}>Tier 0 Ore</span> (mined from asteroids): Veldspar, Scordite, Pyroxeres, Kernite, Omber, Jaspet, Hemorphite, Arkonor<br/>
<span style={{ color: 'var(--cyan)' }}>Tier 1 Mineral</span> (refined from ore): Tritanium, Pyerite, Nocxium, Isogen, Zydrine, Megacyte, Mexallon, Morphite<br/>
<span style={{ color: 'var(--green)' }}>Tier 2 Component</span> (manufactured from minerals): Capacitor Unit, Shield Emitter, Armor Plate, Thruster Assembly, Electronics Cluster, Weapon Housing<br/>
<span style={{ color: 'var(--purple)' }}>Tier 3 Module</span> (manufactured from components + minerals): Mining Laser, Railgun, Shield Booster, Afterburner, Warp Scrambler<br/>
<span style={{ color: 'var(--red)' }}>Tier 4 Ship</span> (manufactured from components + modules + minerals): Frigate Hull, Destroyer Hull, Cruiser Hull, Industrial Hull, Battlecruiser Hull
</div>
</div>
<div style={{ overflowX: 'auto', marginBottom: 'var(--sp-6)' }}>
<table className="data-table">
<thead>
<tr><th>Stage</th><th>Input</th><th>Output</th><th>Example</th><th>Industry Skill</th></tr>
</thead>
<tbody>
{[
{ stage: 'Refining', input: '333 Veldspar', output: '415 Tritanium', example: 'Ore → Mineral', skill: 'None (base 50%)' },
{ stage: 'Component Mfg', input: '200 Tritanium + 80 Pyerite', output: '1 Capacitor Unit', example: 'Mineral → Component', skill: 'Industry I' },
{ stage: 'Module Mfg', input: '2 Capacitor Units + 150 Tritanium', output: '1 Mining Laser I', example: 'Component → Module', skill: 'Industry II' },
{ stage: 'Ship Mfg', input: '5 Component packs + 2000 Tritanium + 800 Pyerite + 100 Nocxium', output: '1 Frigate Hull', example: 'Component+Mineral → Ship', skill: 'Industry III' },
{ stage: 'Advanced Ship', input: '15 Component packs + 8000 Tritanium + 3000 Pyerite + 500 Isogen + 200 Nocxium', output: '1 Cruiser Hull', example: 'Multi-tier chain', skill: 'Industry IV' },
].map((row, i) => (
<tr key={i}>
<td style={{ fontWeight: 600, color: ['var(--accent)', 'var(--green)', 'var(--purple)', 'var(--red)', 'var(--cyan)'][i] }}>{row.stage}</td>
<td style={{ color: 'var(--fg-dim)', fontSize: '0.75rem' }}>{row.input}</td>
<td style={{ color: 'var(--fg-dim)', fontSize: '0.75rem' }}>{row.output}</td>
<td style={{ color: 'var(--muted)', fontSize: '0.75rem' }}>{row.example}</td>
<td><span className="pill pill-cyan" style={{ fontSize: '0.65rem' }}>{row.skill}</span></td>
</tr>
))}
</tbody>
</table>
</div>
<div className="section-header" style={{ marginTop: 'var(--sp-8)' }}>
<span className="section-num">ECON-MFG.2</span>
<h2 style={{ margin: 0 }}>Blueprint Research (ME / TE)</h2>
</div>
<p style={{ color: 'var(--fg-dim)', fontSize: '0.9rem', maxWidth: '680px', marginBottom: 'var(--sp-5)' }}>
Every manufacturable item has a <strong>Blueprint Original (BPO)</strong>. BPOs define the base recipe, but they can be
<strong> researched</strong> to improve two attributes: <strong>Material Efficiency (ME)</strong> reduces mineral waste,
and <strong>Time Efficiency (TE)</strong> reduces manufacturing time. Research costs time and ISK but permanently improves the BPO.
</p>
<div className="grid-2" style={{ marginBottom: 'var(--sp-6)' }}>
<div className="card" style={{ borderLeft: '3px solid var(--accent)' }}>
<h4 style={{ color: 'var(--accent)' }}>Material Efficiency (ME)</h4>
<p style={{ color: 'var(--fg-dim)', fontSize: '0.85rem', margin: '0 0 var(--sp-3) 0' }}>
Each ME level reduces material waste by 1%. At ME 0, the recipe uses the base material list (includes 10% waste).
At ME 10, waste is reduced to 0%. Research time increases exponentially.
</p>
<div style={{ fontFamily: 'var(--font-mono)', fontSize: '0.75rem', color: 'var(--fg-dim)', lineHeight: 1.8 }}>
ME 0: 10% waste (base recipe)<br/>
ME 1: 9% waste Research: 30 min + 50,000<br/>
ME 2: 8% waste Research: 1h + 100,000<br/>
ME 5: 5% waste Research: 8h + 500,000<br/>
ME 10: 0% waste (perfect) Research: 48h + 5,000,000<br/>
</div>
<div style={{ fontFamily: 'var(--font-mono)', fontSize: '0.72rem', color: 'var(--muted)', marginTop: 'var(--sp-2)' }}>
Formula: actual_materials = base × (1.0 ME × 0.01)<br/>
Skill gate: Blueprint Research I ME 3 max · II ME 5 · III ME 8 · IV ME 10
</div>
</div>
<div className="card" style={{ borderLeft: '3px solid var(--cyan)' }}>
<h4 style={{ color: 'var(--cyan)' }}>Time Efficiency (TE)</h4>
<p style={{ color: 'var(--fg-dim)', fontSize: '0.85rem', margin: '0 0 var(--sp-3) 0' }}>
Each TE level reduces manufacturing time by 2%. At TE 0, the recipe takes the base time.
At TE 20, manufacturing time is reduced by 40%. TE research is cheaper and faster than ME research.
</p>
<div style={{ fontFamily: 'var(--font-mono)', fontSize: '0.75rem', color: 'var(--fg-dim)', lineHeight: 1.8 }}>
TE 0: 0% time reduction (base)<br/>
TE 5: 10% faster Research: 15 min + 25,000<br/>
TE 10: 20% faster Research: 2h + 200,000<br/>
TE 15: 30% faster Research: 12h + 1,000,000<br/>
TE 20: 40% faster (max) Research: 24h + 2,500,000<br/>
</div>
<div style={{ fontFamily: 'var(--font-mono)', fontSize: '0.72rem', color: 'var(--muted)', marginTop: 'var(--sp-2)' }}>
Formula: actual_time = base_time × (1.0 TE × 0.02)<br/>
Skill gate: Same skill levels as ME · TE and ME can be researched independently
</div>
</div>
</div>
<div className="section-header" style={{ marginTop: 'var(--sp-8)' }}>
<span className="section-num">ECON-MFG.3</span>
<h2 style={{ margin: 0 }}>Production Queues & Job Management</h2>
</div>
<div className="grid-2" style={{ marginBottom: 'var(--sp-6)' }}>
<div className="card" style={{ borderLeft: '3px solid var(--green)' }}>
<h4 style={{ color: 'var(--green)' }}>Job Queue System</h4>
<p style={{ color: 'var(--fg-dim)', fontSize: '0.85rem', margin: '0 0 var(--sp-3) 0' }}>
Each station has a limited number of <strong style={{ color: 'var(--fg)' }}>manufacturing slots</strong>.
Players queue jobs and they execute in order. A player can have multiple jobs running simultaneously,
limited by their Industry skill level.
</p>
<div style={{ fontFamily: 'var(--font-mono)', fontSize: '0.75rem', color: 'var(--fg-dim)', lineHeight: 1.8 }}>
Industry I: 1 concurrent job<br/>
Industry II: 2 concurrent jobs<br/>
Industry III: 3 concurrent jobs<br/>
Industry IV: 5 concurrent jobs<br/>
Industry V: 8 concurrent jobs (max)
</div>
</div>
<div className="card" style={{ borderLeft: '3px solid var(--accent)' }}>
<h4 style={{ color: 'var(--accent)' }}>Station Facilities</h4>
<p style={{ color: 'var(--fg-dim)', fontSize: '0.85rem', margin: '0 0 var(--sp-3) 0' }}>
Not all stations can manufacture everything. Station facility level determines what can be built:
</p>
<div style={{ fontFamily: 'var(--font-mono)', fontSize: '0.75rem', color: 'var(--fg-dim)', lineHeight: 1.8 }}>
<span style={{ color: 'var(--green)' }}>Basic Factory:</span> Modules, ammo, components (any station)<br/>
<span style={{ color: 'var(--cyan)' }}>Advanced Factory:</span> Frigates, destroyers, industrials<br/>
<span style={{ color: 'var(--accent)' }}>Capital Yard:</span> Cruisers, battlecruisers<br/>
<span style={{ color: 'var(--red)' }}>Shipyard:</span> All ships including capitals (null-sec outposts only, post-MVP)
</div>
</div>
</div>
<div className="grid-2" style={{ marginBottom: 'var(--sp-6)' }}>
<div className="card" style={{ borderLeft: '3px solid var(--cyan)' }}>
<h4 style={{ color: 'var(--cyan)' }}>Blueprint Copies (BPC)</h4>
<p style={{ color: 'var(--fg-dim)', fontSize: '0.85rem', margin: '0 0 var(--sp-3) 0' }}>
BPOs can be copied to create Blueprint Copies (BPCs) with a limited number of runs.
BPCs are tradable a manufacturer can sell BPCs on the market without giving up the original BPO.
Copies inherit ME/TE levels from the original.
</p>
<div style={{ fontFamily: 'var(--font-mono)', fontSize: '0.72rem', color: 'var(--muted)' }}>
Copy time: 50% of manufacturing time per run · Max runs: 10 per copy · BPCs stack in inventory
</div>
</div>
<div className="card" style={{ borderLeft: '3px solid var(--purple)' }}>
<h4 style={{ color: 'var(--purple)' }}>Invention (Post-MVP)</h4>
<p style={{ color: 'var(--fg-dim)', fontSize: '0.85rem', margin: '0 0 var(--sp-3) 0' }}>
<strong>Tech 2 items</strong> are created through invention a research process that consumes a BPC, datacores,
and a decryptor to produce a T2 BPC with a probability of success. T2 items are significantly more powerful
than T1, making invention a high-value, high-risk specialization.
</p>
<div style={{ fontFamily: 'var(--font-mono)', fontSize: '0.72rem', color: 'var(--muted)' }}>
Success rate: 2060% (skill-dependent) · Requires: Science skills + datacores · T2 items: 1.53× T1 stats
</div>
</div>
</div>
<div className="section-header" style={{ marginTop: 'var(--sp-8)' }}>
<span className="section-num">ECON-MFG.4</span>
<h2 style={{ margin: 0 }}>Manufacturing Economics</h2>
</div>
<div className="grid-2" style={{ marginBottom: 'var(--sp-6)' }}>
<div className="card">
<h4 style={{ color: 'var(--green)' }}>Blueprints</h4>
<p style={{ color: 'var(--fg-dim)', fontSize: '0.85rem', margin: '0 0 var(--sp-3) 0' }}>
Each manufacturable item has a Blueprint Original (BPO) that defines the recipe. BPOs can
be researched to reduce mineral waste (Material Efficiency) and production time (Time Efficiency).
</p>
<div style={{ fontSize: '0.8rem', color: 'var(--fg-dim)' }}>
<strong>ME levels:</strong> 0% → 10% waste reduction per level<br/>
<strong>TE levels:</strong> 0% 20% time reduction per level
</div>
</div>
<div className="card">
<h4 style={{ color: 'var(--purple)' }}>Production Chains</h4>
<p style={{ color: 'var(--fg-dim)', fontSize: '0.85rem', margin: '0 0 var(--sp-3) 0' }}>
Advanced items require intermediate components (not just raw minerals). This creates
multi-step production chains where different players can specialize in different stages.
</p>
<div style={{ fontSize: '0.8rem', color: 'var(--muted)', fontFamily: 'var(--font-mono)' }}>
Ore Mineral Component Module Fitted Ship
</div>
</div>
</div>
<div className="callout callout-warn">
<strong>Strategic depth:</strong> A player with a fully researched BPO (ME 10, TE 20) manufactures at 0% waste and 40% faster
than a player using an unresearched BPO. This is a permanent competitive advantage that rewards long-term investment.
A veteran industrialist with a researched Cruiser BPO can underprice a newcomer and still profit. The mineral component module ship
chain means that <strong>industrialists who control the full vertical chain capture the most margin</strong>, while those who buy
components from the market pay a premium for convenience.
</div>
</>
)}
{/* NPC PRICING */}
{activeSection === 'npc-pricing' && (
<>
<div className="section-header">
<span className="section-num">ECON-NPC</span>
<h2 style={{ margin: 0 }}>NPC Price Adjustment Algorithm</h2>
</div>
<div className="callout callout-info" style={{ marginBottom: 'var(--sp-5)' }}>
<strong>Why this matters:</strong> NPC buy/sell orders are the economy's floor and ceiling. If NPC prices never move,
players will find exploits (buy from NPC in system A, sell to NPC in system B for guaranteed profit). If NPC prices
swing too wildly, new players can't predict income and the game feels unfair. The algorithm must be deterministic,
transparent to the player through Zora analysis, and self-correcting without manual intervention.
</div>
{/* Price seed model */}
<h3 style={{ marginBottom: 'var(--sp-4)' }}>Price Seed Model</h3>
<p style={{ color: 'var(--fg-dim)', fontSize: '0.9rem', maxWidth: '680px', marginBottom: 'var(--sp-5)' }}>
Every commodity has a <strong>base price</strong> (the global reference), a <strong>regional modifier</strong>
(set at galaxy generation, reflects local abundance/scarcity), and a <strong>dynamic modifier</strong> that shifts
based on player activity. NPC prices are never random — they are a deterministic function of these three inputs.
</p>
<div className="card card-accent" style={{ padding: 'var(--sp-5) var(--sp-6)', marginBottom: 'var(--sp-5)' }}>
<h4 style={{ color: 'var(--accent)', marginBottom: 'var(--sp-4)' }}>NPC Price Formula</h4>
<div style={{ fontFamily: 'var(--font-mono)', fontSize: '0.82rem', color: 'var(--fg-dim)', lineHeight: 2.2 }}>
<span style={{ color: 'var(--cyan)' }}>npc_price</span>(commodity, station) =<br/>
&nbsp;&nbsp;<span style={{ color: 'var(--accent)' }}>base_price</span>(commodity)<br/>
&nbsp;&nbsp;× <span style={{ color: 'var(--green)' }}>regional_modifier</span>(commodity, region) &nbsp;&nbsp;<span style={{ color: 'var(--muted)' }}>// fixed at galaxy gen, range [0.7 1.5]</span><br/>
&nbsp;&nbsp;× <span style={{ color: 'var(--purple)' }}>demand_pressure</span>(commodity, station) &nbsp;&nbsp;<span style={{ color: 'var(--muted)' }}>// dynamic, range [0.8 1.4]</span><br/>
&nbsp;&nbsp;× <span style={{ color: 'var(--fg)' }}>station_type_factor</span>(station) &nbsp;&nbsp;<span style={{ color: 'var(--muted)' }}>// trade hub: 0.95 (cheaper), frontier: 1.15 (premium)</span><br/>
<br/>
<span style={{ color: 'var(--muted)' }}>// NPC BUY price (what NPCs pay you) is always below sell:</span><br/>
<span style={{ color: 'var(--cyan)' }}>npc_buy_price</span> = <span style={{ color: 'var(--cyan)' }}>npc_price</span> × <span style={{ color: 'var(--red)' }}>buy_spread</span> &nbsp;&nbsp;<span style={{ color: 'var(--muted)' }}>// buy_spread ∈ [0.65, 0.85]</span><br/>
<span style={{ color: 'var(--cyan)' }}>npc_sell_price</span> = <span style={{ color: 'var(--cyan)' }}>npc_price</span> × <span style={{ color: 'var(--green)' }}>sell_spread</span> &nbsp;&nbsp;<span style={{ color: 'var(--muted)' }}>// sell_spread ∈ [1.10, 1.35]</span><br/>
<span style={{ color: 'var(--muted)' }}>// Spread ensures NPCs always buy lower than they sell.</span>
</div>
</div>
{/* Base price table */}
<h3 style={{ marginBottom: 'var(--sp-4)' }}>Base Prices (Global Reference)</h3>
<div style={{ overflowX: 'auto', marginBottom: 'var(--sp-6)' }}>
<table className="data-table">
<thead>
<tr><th>Commodity</th><th>Base Price (₢/unit)</th><th>Type</th><th>NPC Buy Spread</th><th>NPC Sell Spread</th></tr>
</thead>
<tbody>
<tr><td style={{ color: 'var(--accent)' }}>Veldspar</td><td className="mono">2.50</td><td>Ore</td><td className="mono">0.70</td><td className="mono">1.25</td></tr>
<tr><td style={{ color: 'var(--accent)' }}>Scordite</td><td className="mono">4.00</td><td>Ore</td><td className="mono">0.70</td><td className="mono">1.25</td></tr>
<tr><td style={{ color: 'var(--accent)' }}>Pyroxeres</td><td className="mono">12.00</td><td>Ore</td><td className="mono">0.68</td><td className="mono">1.28</td></tr>
<tr><td style={{ color: 'var(--accent)' }}>Kernite</td><td className="mono">25.00</td><td>Ore</td><td className="mono">0.65</td><td className="mono">1.30</td></tr>
<tr><td style={{ color: 'var(--cyan)' }}>Tritanium</td><td className="mono">3.20</td><td>Mineral</td><td className="mono">0.75</td><td className="mono">1.20</td></tr>
<tr><td style={{ color: 'var(--cyan)' }}>Pyerite</td><td className="mono">8.00</td><td>Mineral</td><td className="mono">0.75</td><td className="mono">1.20</td></tr>
<tr><td style={{ color: 'var(--cyan)' }}>Nocxium</td><td className="mono">120.00</td><td>Mineral</td><td className="mono">0.72</td><td className="mono">1.22</td></tr>
<tr><td style={{ color: 'var(--cyan)' }}>Megacyte</td><td className="mono">450.00</td><td>Mineral</td><td className="mono">0.68</td><td className="mono">1.30</td></tr>
<tr><td style={{ color: 'var(--green)' }}>Mining Laser I</td><td className="mono">800</td><td>Module</td><td className="mono">—</td><td className="mono">1.30</td></tr>
<tr><td style={{ color: 'var(--green)' }}>150mm Railgun</td><td className="mono">2,500</td><td>Module</td><td className="mono">—</td><td className="mono">1.30</td></tr>
<tr><td style={{ color: 'var(--purple)' }}>Frigate Hull</td><td className="mono">15,000</td><td>Ship</td><td className="mono">—</td><td className="mono">1.35</td></tr>
<tr><td style={{ color: 'var(--purple)' }}>Cruiser Hull</td><td className="mono">80,000</td><td>Ship</td><td className="mono">—</td><td className="mono">1.35</td></tr>
</tbody>
</table>
</div>
{/* Demand pressure algorithm */}
<div className="section-header">
<span className="section-num">ECON-NPC.1</span>
<h2 style={{ margin: 0 }}>Demand Pressure — The Dynamic Modifier</h2>
</div>
<p style={{ color: 'var(--fg-dim)', fontSize: '0.9rem', maxWidth: '680px', marginBottom: 'var(--sp-5)' }}>
The <strong>demand_pressure</strong> multiplier is the only dynamic component of NPC prices.
It tracks recent buy/sell volume at each station and adjusts prices to simulate supply and demand.
This is what prevents infinite arbitrage and makes regional trade meaningful.
</p>
<div className="card card-accent" style={{ padding: 'var(--sp-5) var(--sp-6)', marginBottom: 'var(--sp-5)' }}>
<h4 style={{ color: 'var(--purple)', marginBottom: 'var(--sp-4)' }}>Demand Pressure Algorithm</h4>
<div style={{ fontFamily: 'var(--font-mono)', fontSize: '0.78rem', color: 'var(--fg-dim)', lineHeight: 2.2 }}>
<span style={{ color: 'var(--muted)' }}>// Each station tracks a rolling demand state per commodity</span><br/>
<span style={{ color: 'var(--cyan)' }}>demand_pressure</span>(commodity, station) computation:<br/>
<br/>
<span style={{ color: 'var(--green)' }}>// 1. Accumulate net flow</span><br/>
net_flow = <span style={{ color: 'var(--green)' }}>volume_sold_to_npc</span> <span style={{ color: 'var(--red)' }}>volume_bought_from_npc</span><br/>
<span style={{ color: 'var(--muted)' }}>// Positive = players dumping, NPC inventory filling → price should drop</span><br/>
<span style={{ color: 'var(--muted)' }}>// Negative = players buying out, NPC stock depleting → price should rise</span><br/>
<br/>
<span style={{ color: 'var(--green)' }}>// 2. Exponential moving average (EMA) — forgets old data</span><br/>
flow_ema = α × net_flow + (1 α) × prev_flow_ema &nbsp;&nbsp;<span style={{ color: 'var(--muted)' }}>// α = 0.3 (adjusts over ~3 ticks)</span><br/>
<br/>
<span style={{ color: 'var(--green)' }}>// 3. Normalize to pressure multiplier</span><br/>
demand_pressure = clamp(1.0 β × flow_ema, 0.8, 1.4) &nbsp;&nbsp;<span style={{ color: 'var(--muted)' }}>// β = 0.002 per unit</span><br/>
<br/>
<span style={{ color: 'var(--green)' }}>// 4. Decay toward 1.0 when idle (no trades)</span><br/>
demand_pressure = lerp(demand_pressure, 1.0, γ) &nbsp;&nbsp;<span style={{ color: 'var(--muted)' }}>// γ = 0.05 per tick (5% per 5-min tick)</span><br/>
<br/>
<span style={{ color: 'var(--muted)' }}>// Result: high player selling → NPC buy price drops toward floor.</span><br/>
<span style={{ color: 'var(--muted)' }}>// High player buying → NPC sell price rises toward ceiling.</span><br/>
<span style={{ color: 'var(--muted)' }}>// No activity → prices decay back to regional baseline.</span>
</div>
</div>
{/* Worked example */}
<h3 style={{ marginBottom: 'var(--sp-4)' }}>Worked Example: Tritanium at Jita IV</h3>
<div className="card" style={{ padding: 0, overflow: 'hidden', marginBottom: 'var(--sp-5)' }}>
<div style={{ padding: 'var(--sp-3) var(--sp-4)', background: 'var(--surface-raised)', borderBottom: '1px solid var(--border)' }}>
<span style={{ fontFamily: 'var(--font-mono)', fontSize: '0.7rem', color: 'var(--accent)', textTransform: 'uppercase', letterSpacing: '0.05em' }}>
Tick-by-Tick Price Trace
</span>
</div>
<div style={{ padding: 'var(--sp-4)', fontSize: '0.82rem', color: 'var(--fg-dim)' }}>
<div style={{ fontFamily: 'var(--font-mono)', fontSize: '0.78rem', lineHeight: 2 }}>
<strong>Inputs:</strong> base_price(Tritanium) = 3.20₢, regional_modifier(The Forge) = 1.05, station_type = Trade Hub (0.95)<br/>
<strong>Initial:</strong> demand_pressure = 1.0 → NPC price = 3.20 × 1.05 × 1.0 × 0.95 = <span style={{ color: 'var(--accent)' }}>3.19₢</span><br/>
<strong style={{ color: 'var(--green)' }}>Tick 1:</strong> 5 players sell 2000 units each. net_flow = +10,000. flow_ema = 3,000. demand_pressure = 1.0 0.002 × 3000 = <span style={{ color: 'var(--red)' }}>0.80</span> (clamped)<br/>
&nbsp;&nbsp;→ NPC buy price = 3.19 × 0.80 = <span style={{ color: 'var(--red)' }}>2.55₢</span> (floor hit — NPCs paying minimum)<br/>
<strong style={{ color: 'var(--cyan)' }}>Tick 2:</strong> Players stop selling. flow_ema decays: 0.3×0 + 0.7×3000 = 2,100. demand_pressure = 0.80 → 1.00.002×2100 = <span style={{ color: 'var(--amber)' }}>0.86</span><br/>
&nbsp;&nbsp;→ NPC buy price = 3.19 × 0.86 = <span style={{ color: 'var(--amber)' }}>2.74₢</span> (recovering)<br/>
<strong style={{ color: 'var(--purple)' }}>Tick 3:</strong> No trades. Decay: lerp(0.86, 1.0, 0.05) = <span style={{ color: 'var(--purple)' }}>0.867</span>. Plus EMA decay continues.<br/>
&nbsp;&nbsp;→ demand_pressure ≈ 0.89 → NPC buy price ≈ <span style={{ color: 'var(--purple)' }}>2.84₢</span><br/>
<strong style={{ color: 'var(--fg)' }}>Tick 6:</strong> Full recovery. demand_pressure → 1.0. NPC buy price = <span style={{ color: 'var(--fg)' }}>3.19₢</span> (baseline restored)
</div>
</div>
</div>
{/* Regional price seeds */}
<div className="section-header">
<span className="section-num">ECON-NPC.2</span>
<h2 style={{ margin: 0 }}>Regional Price Seeds</h2>
</div>
<p style={{ color: 'var(--fg-dim)', fontSize: '0.9rem', maxWidth: '680px', marginBottom: 'var(--sp-5)' }}>
Regional modifiers are set at galaxy generation and <strong>never change</strong>. They reflect the natural
abundance or scarcity of resources in a region. A region rich in Veldspar belts has a low modifier for
Veldspar (cheap locally, not worth importing) but might have a high modifier for Megacyte (rare locally,
profitable to import). This creates natural trade corridors.
</p>
<div style={{ overflowX: 'auto', marginBottom: 'var(--sp-6)' }}>
<table className="data-table">
<thead>
<tr><th>Region</th><th>Character</th><th>Veldspar Mod</th><th>Nocxium Mod</th><th>Megacyte Mod</th><th>Module Mod</th></tr>
</thead>
<tbody>
<tr><td style={{ color: 'var(--cyan)' }}>The Forge (Jita)</td><td>Trade Hub</td><td className="mono" style={{ color: 'var(--green)' }}>0.90</td><td className="mono" style={{ color: 'var(--green)' }}>0.85</td><td className="mono" style={{ color: 'var(--red)' }}>1.30</td><td className="mono" style={{ color: 'var(--green)' }}>0.95</td></tr>
<tr><td style={{ color: 'var(--accent)' }}>Domain (Amarr)</td><td>Manufacturing</td><td className="mono" style={{ color: 'var(--green)' }}>0.95</td><td className="mono" style={{ color: 'var(--green)' }}>0.90</td><td className="mono" style={{ color: 'var(--green)' }}>0.80</td><td className="mono" style={{ color: 'var(--green)' }}>0.90</td></tr>
<tr><td style={{ color: 'var(--green)' }}>Sinq Laison</td><td>High-sec Mixed</td><td className="mono">1.00</td><td className="mono">1.00</td><td className="mono" style={{ color: 'var(--red)' }}>1.20</td><td className="mono">1.05</td></tr>
<tr><td style={{ color: 'var(--amber)' }}>Metropolis</td><td>Border Zone</td><td className="mono" style={{ color: 'var(--red)' }}>1.15</td><td className="mono" style={{ color: 'var(--red)' }}>1.10</td><td className="mono" style={{ color: 'var(--red)' }}>1.35</td><td className="mono" style={{ color: 'var(--red)' }}>1.15</td></tr>
<tr><td style={{ color: 'var(--red)' }}>Pure Blind (Null)</td><td>Null-sec Frontier</td><td className="mono" style={{ color: 'var(--red)' }}>1.40</td><td className="mono" style={{ color: 'var(--green)' }}>0.70</td><td className="mono" style={{ color: 'var(--green)' }}>0.60</td><td className="mono" style={{ color: 'var(--red)' }}>1.35</td></tr>
<tr><td style={{ color: 'var(--purple)' }}>Fade (Low-sec)</td><td>Pirate Corridor</td><td className="mono" style={{ color: 'var(--red)' }}>1.30</td><td className="mono" style={{ color: 'var(--green)' }}>0.75</td><td className="mono" style={{ color: 'var(--green)' }}>0.75</td><td className="mono" style={{ color: 'var(--red)' }}>1.25</td></tr>
</tbody>
</table>
</div>
<div className="callout callout-warn" style={{ marginBottom: 'var(--sp-5)' }}>
<strong>Trade route implication:</strong> A player who mines Nocxium in Pure Blind (mod 0.70, cheap) and hauls
it to The Forge (mod 0.85) makes a modest profit from regional modifier alone — but the real margin comes from
<em> demand_pressure</em>. If Jita has been buying lots of Nocxium (manufacturing cruisers), the demand_pressure
there might be 1.15+, making the same haul much more profitable. The dynamic price is the opportunity;
the regional seed is the baseline.
</div>
{/* Anti-arbitrage safeguards */}
<div className="section-header">
<span className="section-num">ECON-NPC.3</span>
<h2 style={{ margin: 0 }}>Anti-Arbitrage Safeguards</h2>
</div>
<div className="grid-2" style={{ marginBottom: 'var(--sp-6)' }}>
<div className="card" style={{ borderLeft: '3px solid var(--red)' }}>
<h4 style={{ color: 'var(--red)' }}>Buy-Sell Spread Guarantee</h4>
<p style={{ color: 'var(--fg-dim)', fontSize: '0.85rem', margin: '0 0 var(--sp-3) 0' }}>
NPC buy price is always ≤ NPC sell price for the same commodity at the same station. The minimum
spread (buy_spread × sell_spread⁻¹) is 0.65/1.10 = 0.59 — meaning NPCs pay at most 59% of what
they charge. <strong>You cannot buy from an NPC and sell back to the same NPC at a profit.</strong>
</p>
</div>
<div className="card" style={{ borderLeft: '3px solid var(--accent)' }}>
<h4 style={{ color: 'var(--accent)' }}>Price Floor & Ceiling</h4>
<p style={{ color: 'var(--fg-dim)', fontSize: '0.85rem', margin: '0 0 var(--sp-3) 0' }}>
demand_pressure clamps to [0.8, 1.4]. Even at maximum demand, NPC prices only reach 1.4× baseline.
Even at maximum oversupply, they only drop to 0.8×. This prevents price spirals in either direction
and ensures new players can always estimate their income within a known range.
</p>
</div>
<div className="card" style={{ borderLeft: '3px solid var(--green)' }}>
<h4 style={{ color: 'var(--green)' }}>Station Stock Limits</h4>
<p style={{ color: 'var(--fg-dim)', fontSize: '0.85rem', margin: '0 0 var(--sp-3) 0' }}>
NPCs have finite order depth. If players flood a station with Veldspar, the NPC buy order eventually
fills and the price drops sharply (demand_pressure floor). This limits infinite farming of any single
station and encourages players to diversify or find remote stations with unfilled orders.
</p>
</div>
<div className="card" style={{ borderLeft: '3px solid var(--cyan)' }}>
<h4 style={{ color: 'var(--cyan)' }}>Cross-Station Price Independence</h4>
<p style={{ color: 'var(--fg-dim)', fontSize: '0.85rem', margin: '0 0 var(--sp-3) 0' }}>
Each station tracks its own demand_pressure independently. Selling Tritanium at Jita doesn't affect
Tritanium prices at Amarr until the diffusion pipeline propagates the information (see Info Diffusion tab).
Players who <em>travel</em> between stations can exploit this lag; players who don't, can't.
</p>
</div>
</div>
{/* Backend schema */}
<div className="section-header">
<span className="section-num">ECON-NPC.4</span>
<h2 style={{ margin: 0 }}>Backend Schema for NPC Pricing</h2>
</div>
<div className="card" style={{ padding: 0, overflow: 'hidden' }}>
<div style={{ padding: 'var(--sp-3) var(--sp-4)', background: 'var(--surface-raised)', borderBottom: '1px solid var(--border)' }}>
<span style={{ fontFamily: 'var(--font-mono)', fontSize: '0.7rem', color: 'var(--accent)', textTransform: 'uppercase', letterSpacing: '0.05em' }}>
SpacetimeDB NPC Pricing Tables
</span>
</div>
<div className="code-block" style={{ margin: 0, borderRadius: 0 }}>
<code>
<span className="cm">// Per-station per-commodity demand state</span><br/>
<span className="kw">table</span> <span className="type">station_commodity_demand</span> {'{'}<br/>
&nbsp;&nbsp;station_id: <span className="type">u64</span>,<br/>
&nbsp;&nbsp;commodity_id: <span className="type">u64</span>,<br/>
&nbsp;&nbsp;flow_ema: <span className="type">f64</span>, &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span className="cm">// rolling net flow EMA</span><br/>
&nbsp;&nbsp;demand_pressure: <span className="type">f64</span>, &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span className="cm">// current multiplier, clamped [0.8, 1.4]</span><br/>
&nbsp;&nbsp;volume_sold_to_npc: <span className="type">u64</span>, &nbsp;&nbsp;<span className="cm">// cumulative this tick</span><br/>
&nbsp;&nbsp;volume_bought_from_npc: <span className="type">u64</span>, <span className="cm">// cumulative this tick</span><br/>
&nbsp;&nbsp;npc_stock_remaining: <span className="type">u64</span>, &nbsp;&nbsp;&nbsp;<span className="cm">// stock limit for buy orders</span><br/>
&nbsp;&nbsp;last_tick: <span className="type">timestamp</span>,<br/>
{'}'}<br/>
<br/>
<span className="cm">// Per-commodity base prices and parameters</span><br/>
<span className="kw">table</span> <span className="type">commodity_price_params</span> {'{'}<br/>
&nbsp;&nbsp;commodity_id: <span className="type">u64</span>,<br/>
&nbsp;&nbsp;base_price: <span className="type">f64</span>,<br/>
&nbsp;&nbsp;buy_spread: <span className="type">f64</span>, &nbsp;&nbsp;&nbsp;&nbsp;<span className="cm">// [0.65, 0.85]</span><br/>
&nbsp;&nbsp;sell_spread: <span className="type">f64</span>, &nbsp;&nbsp;&nbsp;&nbsp;<span className="cm">// [1.10, 1.35]</span><br/>
&nbsp;&nbsp;ema_alpha: <span className="type">f64</span>, &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span className="cm">// smoothing factor (default 0.3)</span><br/>
&nbsp;&nbsp;pressure_beta: <span className="type">f64</span>, &nbsp;&nbsp;&nbsp;<span className="cm">// sensitivity per unit (default 0.002)</span><br/>
&nbsp;&nbsp;decay_gamma: <span className="type">f64</span>, &nbsp;&nbsp;&nbsp;&nbsp;<span className="cm">// idle decay rate (default 0.05)</span><br/>
{'}'}<br/>
<br/>
<span className="cm">// Per-region per-commodity static modifier</span><br/>
<span className="kw">table</span> <span className="type">regional_price_seeds</span> {'{'}<br/>
&nbsp;&nbsp;region_id: <span className="type">u64</span>,<br/>
&nbsp;&nbsp;commodity_id: <span className="type">u64</span>,<br/>
&nbsp;&nbsp;modifier: <span className="type">f64</span>, &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span className="cm">// fixed at galaxy gen, range [0.6, 1.5]</span><br/>
{'}'}<br/>
<br/>
<span className="cm">// Scheduled agent that ticks demand pressure</span><br/>
<span className="kw">reducer</span> <span className="fn">tick_npc_pricing</span>(ctx) {'{'}<br/>
&nbsp;&nbsp;<span className="cm">// Every 5 minutes, for each station_commodity_demand row:</span><br/>
&nbsp;&nbsp;<span className="cm">// 1. Compute net_flow = volume_sold_to_npc - volume_bought_from_npc</span><br/>
&nbsp;&nbsp;<span className="cm">// 2. Update flow_ema with EMA formula</span><br/>
&nbsp;&nbsp;<span className="cm">// 3. Recompute demand_pressure = clamp(1.0 - β × flow_ema, 0.8, 1.4)</span><br/>
&nbsp;&nbsp;<span className="cm">// 4. Apply idle decay: lerp(demand_pressure, 1.0, γ)</span><br/>
&nbsp;&nbsp;<span className="cm">// 5. Reset volume counters for next tick</span><br/>
{'}'}<br/>
</code>
</div>
</div>
<div className="callout callout-info" style={{ marginTop: 'var(--sp-5)' }}>
<strong>Tuning knobs:</strong> The three parameters (α, β, γ) are per-commodity so that high-value items like Megacyte
can be less volatile (lower β) while common ores like Veldspar can react faster. The initial values listed
above are defaults the actual values should be tuned during playtesting by observing how quickly arbitrage
opportunities close and whether new players can still estimate their mining income.
</div>
</>
)}
{/* FAUCETS & SINKS */}
{activeSection === 'faucets' && (
<>
<div className="section-header">
<span className="section-num">ECON-FS</span>
<h2 style={{ margin: 0 }}>Money Supply: Faucets & Sinks</h2>
</div>
<div className="callout callout-warn" style={{ marginBottom: 'var(--sp-5)' }}>
<strong>Critical balance:</strong> If faucets exceed sinks, the currency inflates and prices
spiral. If sinks exceed faucets, players feel poor and stop engaging. The economy needs
continuous monitoring no fixed formula survives contact with real players.
</div>
<div className="grid-2">
{faucetsAndSinks.map((item, i) => (
<div key={i} className="card" style={{ borderLeft: `3px solid ${item.type === 'faucet' ? 'var(--green)' : 'var(--red)'}` }}>
<div style={{ display: 'flex', alignItems: 'center', gap: 'var(--sp-3)', marginBottom: 'var(--sp-3)' }}>
<span className={`pill ${item.type === 'faucet' ? 'pill-green' : 'pill-red'}`}>
{item.type === 'faucet' ? '▲ FAUCET' : '▼ SINK'}
</span>
<h4 style={{ margin: 0 }}>{item.name}</h4>
</div>
<p style={{ color: 'var(--fg-dim)', fontSize: '0.85rem', margin: '0 0 var(--sp-2) 0' }}>{item.description}</p>
<div style={{ fontFamily: 'var(--font-mono)', fontSize: '0.75rem', color: 'var(--muted)' }}>
{item.rate}
</div>
</div>
))}
</div>
</>
)}
</div>
);
}
window.GDD.EconomyPage = EconomyPage;