Initial commit
This commit is contained in:
525
js/demos/refining.js
Normal file
525
js/demos/refining.js
Normal file
@@ -0,0 +1,525 @@
|
||||
window.GDD = window.GDD || {};
|
||||
|
||||
const { useState, useEffect, useCallback, useRef } = React;
|
||||
|
||||
function RefiningDemo() {
|
||||
const [inventory, setInventory] = useState([]);
|
||||
const [orePrices, setOrePrices] = useState({});
|
||||
const [selectedOre, setSelectedOre] = useState(null);
|
||||
const [refineQty, setRefineQty] = useState(0);
|
||||
const [skillLevel, setSkillLevel] = useState(2);
|
||||
const [refining, setRefining] = useState(false);
|
||||
const [results, setResults] = useState([]);
|
||||
const [manufacturingTab, setManufacturingTab] = useState(false);
|
||||
const [manufacturingJobs, setManufacturingJobs] = useState([]);
|
||||
const [notifications, setNotifications] = useState([]);
|
||||
const timerRef = useRef(null);
|
||||
|
||||
const mineralData = {
|
||||
Veldspar: { mineral: 'Tritanium', yield: 415, batch: 333, time: 45 },
|
||||
Scordite: { mineral: 'Pyerite', yield: 171, batch: 333, time: 45 },
|
||||
Pyroxeres: { mineral: 'Nocxium', yield: 8, batch: 333, time: 60 },
|
||||
Kernite: { mineral: 'Isogen', yield: 107, batch: 200, time: 60 },
|
||||
Omber: { mineral: 'Isogen', yield: 86, batch: 500, time: 75 },
|
||||
Jaspet: { mineral: 'Zydrine', yield: 8, batch: 500, time: 75 },
|
||||
Hemorphite: { mineral: 'Nocxium', yield: 21, batch: 500, time: 90 },
|
||||
Arkonor: { mineral: 'Megacyte', yield: 18, batch: 200, time: 120 },
|
||||
};
|
||||
|
||||
const manufacturingRecipes = [
|
||||
{ id: 1, product: 'Mining Laser I', minerals: { Tritanium: 200, Pyerite: 80 }, time: 300, skill: 1 },
|
||||
{ id: 2, product: '150mm Railgun', minerals: { Tritanium: 400, Pyerite: 150, Nocxium: 20 }, time: 900, skill: 2 },
|
||||
{ id: 3, product: 'Shield Booster I', minerals: { Tritanium: 300, Isogen: 50 }, time: 600, skill: 2 },
|
||||
{ id: 4, product: 'Frigate Hull', minerals: { Tritanium: 2000, Pyerite: 800, Nocxium: 100 }, time: 1800, skill: 3 },
|
||||
{ id: 5, product: '1MN Afterburner', minerals: { Tritanium: 150, Pyerite: 50, Isogen: 20 }, time: 480, skill: 2 },
|
||||
];
|
||||
|
||||
const [playerMinerals, setPlayerMinerals] = useState({
|
||||
Tritanium: 0, Pyerite: 0, Nocxium: 0, Isogen: 0, Zydrine: 0, Megacyte: 0,
|
||||
});
|
||||
|
||||
const skillEfficiency = { 0: 0.50, 1: 0.60, 2: 0.70, 3: 0.80, 4: 0.875, 5: 0.95 };
|
||||
|
||||
useEffect(() => {
|
||||
window.GDD.api.getPlayerInventory().then(i => {
|
||||
setInventory(i);
|
||||
if (i.length > 0) { setSelectedOre(i[0].item); setRefineQty(i[0].quantity); }
|
||||
});
|
||||
window.GDD.api.getOrePrices().then(p => setOrePrices(p));
|
||||
}, []);
|
||||
|
||||
const addNotif = useCallback((msg, color) => {
|
||||
const id = Date.now();
|
||||
setNotifications(prev => [...prev, { id, msg, color }]);
|
||||
setTimeout(() => setNotifications(prev => prev.filter(n => n.id !== id)), 3500);
|
||||
}, []);
|
||||
|
||||
const handleRefine = useCallback(async () => {
|
||||
if (!selectedOre || refineQty <= 0) return;
|
||||
const data = mineralData[selectedOre];
|
||||
if (!data) return;
|
||||
if (refineQty < data.batch) {
|
||||
addNotif(`Need at least ${data.batch} units for a batch.`, 'var(--red)');
|
||||
return;
|
||||
}
|
||||
|
||||
setRefining(true);
|
||||
const inv = inventory.find(i => i.item === selectedOre);
|
||||
const batches = Math.floor(refineQty / data.batch);
|
||||
const used = batches * data.batch;
|
||||
const eff = skillEfficiency[skillLevel];
|
||||
const mineralYield = Math.floor(batches * data.yield * eff);
|
||||
const rawValue = used * (orePrices[selectedOre] || 0);
|
||||
const mineralValue = mineralYield * Math.floor((orePrices[selectedOre] || 0) * 2.5);
|
||||
|
||||
// Simulate delay
|
||||
await new Promise(r => setTimeout(r, data.time * 10));
|
||||
|
||||
setPlayerMinerals(prev => ({
|
||||
...prev,
|
||||
[data.mineral]: prev[data.mineral] + mineralYield,
|
||||
}));
|
||||
|
||||
setInventory(prev => prev.map(i =>
|
||||
i.item === selectedOre
|
||||
? { ...i, quantity: i.quantity - used }
|
||||
: i
|
||||
).filter(i => i.quantity > 0));
|
||||
|
||||
setResults(prev => [{
|
||||
ore: selectedOre,
|
||||
batches,
|
||||
used,
|
||||
mineral: data.mineral,
|
||||
yield: mineralYield,
|
||||
efficiency: eff,
|
||||
rawValue,
|
||||
mineralValue,
|
||||
better: mineralValue > rawValue,
|
||||
}, ...prev.slice(0, 9)]);
|
||||
|
||||
setRefining(false);
|
||||
addNotif(`Refined ${used.toLocaleString()} ${selectedOre} → ${mineralYield.toLocaleString()} ${data.mineral} (${(eff * 100).toFixed(0)}% eff)`, 'var(--green)');
|
||||
}, [selectedOre, refineQty, skillLevel, inventory, orePrices, addNotif]);
|
||||
|
||||
const handleManufacture = useCallback((recipe) => {
|
||||
if (skillLevel < recipe.skill) {
|
||||
addNotif(`Need Industry level ${recipe.skill} to manufacture ${recipe.product}.`, 'var(--red)');
|
||||
return;
|
||||
}
|
||||
// Check minerals
|
||||
for (const [mineral, qty] of Object.entries(recipe.minerals)) {
|
||||
if ((playerMinerals[mineral] || 0) < qty) {
|
||||
addNotif(`Not enough ${mineral}. Need ${qty}, have ${playerMinerals[mineral] || 0}.`, 'var(--red)');
|
||||
return;
|
||||
}
|
||||
}
|
||||
// Deduct minerals
|
||||
setPlayerMinerals(prev => {
|
||||
const next = { ...prev };
|
||||
for (const [mineral, qty] of Object.entries(recipe.minerals)) {
|
||||
next[mineral] -= qty;
|
||||
}
|
||||
return next;
|
||||
});
|
||||
|
||||
const job = {
|
||||
id: Date.now(),
|
||||
product: recipe.product,
|
||||
totalTime: recipe.time,
|
||||
remaining: recipe.time,
|
||||
started: Date.now(),
|
||||
};
|
||||
setManufacturingJobs(prev => [...prev, job]);
|
||||
addNotif(`Manufacturing job started: ${recipe.product}. ETA: ${Math.floor(recipe.time / 60)}m ${recipe.time % 60}s`, 'var(--cyan)');
|
||||
}, [skillLevel, playerMinerals, addNotif]);
|
||||
|
||||
// Manufacturing timer
|
||||
useEffect(() => {
|
||||
const interval = setInterval(() => {
|
||||
setManufacturingJobs(prev => {
|
||||
const updated = prev.map(j => ({
|
||||
...j,
|
||||
remaining: Math.max(0, j.remaining - 1),
|
||||
}));
|
||||
const completed = updated.filter(j => j.remaining <= 0 && prev.find(p => p.id === j.id && p.remaining > 0));
|
||||
completed.forEach(j => {
|
||||
addNotif(`Manufacturing complete: ${j.product}`, 'var(--green)');
|
||||
});
|
||||
return updated.filter(j => j.remaining > 0);
|
||||
});
|
||||
}, 1000);
|
||||
return () => clearInterval(interval);
|
||||
}, [addNotif]);
|
||||
|
||||
const formatTime = (s) => `${Math.floor(s / 60)}m ${String(s % 60).padStart(2, '0')}s`;
|
||||
|
||||
return (
|
||||
<div className="content-inner">
|
||||
<a href="#overview" style={{ display: 'inline-flex', alignItems: 'center', gap: '4px', fontSize: '0.75rem', fontFamily: 'var(--font-mono)', color: 'var(--muted)', textDecoration: 'none', marginBottom: 'var(--sp-3)', transition: 'color 0.15s' }} onMouseEnter={e => e.currentTarget.style.color='var(--fg-bright)'} onMouseLeave={e => e.currentTarget.style.color='var(--muted)'}>← Back to Docs</a>
|
||||
<h1 style={{ marginBottom: '8px' }}>Refining & Manufacturing Demo</h1>
|
||||
<p style={{ color: 'var(--fg-dim)', fontSize: '0.9rem' }}>
|
||||
Refine raw ore into minerals, then use minerals to manufacture ships and modules.
|
||||
Industry skill level determines refining efficiency — higher skill means more minerals per batch.
|
||||
</p>
|
||||
|
||||
{/* HUD-style industry strip */}
|
||||
<div style={{
|
||||
display: 'flex', alignItems: 'center', gap: 'var(--sp-4)',
|
||||
padding: 'var(--sp-3) var(--sp-4)', marginTop: 'var(--sp-4)', marginBottom: 'var(--sp-3)',
|
||||
background: 'rgba(15,22,35,0.88)', border: '1px solid var(--border)',
|
||||
borderRadius: 'var(--radius-lg)', backdropFilter: 'blur(8px)',
|
||||
fontFamily: 'var(--font-mono)', fontSize: '0.75rem',
|
||||
}}>
|
||||
<span style={{ color: 'var(--fg-bright)', fontWeight: 600, fontSize: '0.8rem' }}>INDUSTRY</span>
|
||||
<div style={{ width: 1, height: 18, background: 'var(--border-light)' }} />
|
||||
<span style={{ color: 'var(--muted)', fontSize: '0.65rem' }}>SKILL</span>
|
||||
<span style={{ color: 'var(--accent)', fontWeight: 600 }}>Lvl {skillLevel}</span>
|
||||
<div style={{ width: 1, height: 18, background: 'var(--border-light)' }} />
|
||||
<span style={{ color: 'var(--muted)', fontSize: '0.65rem' }}>EFFICIENCY</span>
|
||||
<span style={{ color: 'var(--cyan)', fontWeight: 600 }}>{(skillEfficiency[skillLevel] * 100).toFixed(0)}%</span>
|
||||
<div style={{ width: 1, height: 18, background: 'var(--border-light)' }} />
|
||||
<span style={{ color: 'var(--muted)', fontSize: '0.65rem' }}>MINERALS</span>
|
||||
<span style={{ color: 'var(--green)', fontWeight: 600 }}>{Object.values(playerMinerals).reduce((a, b) => a + b, 0).toLocaleString()}</span>
|
||||
<div style={{ width: 1, height: 18, background: 'var(--border-light)' }} />
|
||||
<span style={{ color: 'var(--muted)', fontSize: '0.65rem' }}>JOBS</span>
|
||||
<span style={{ color: 'var(--purple)', fontWeight: 600 }}>{manufacturingJobs.length}</span>
|
||||
</div>
|
||||
|
||||
{/* Notifications */}
|
||||
<div style={{ position: 'fixed', top: 'var(--sp-4)', right: 'var(--sp-4)', zIndex: 1000, display: 'flex', flexDirection: 'column', gap: 'var(--sp-2)' }}>
|
||||
{notifications.map(n => (
|
||||
<div key={n.id} style={{
|
||||
background: 'var(--surface)', border: `1px solid ${n.color}40`,
|
||||
borderRadius: 'var(--radius-md)', padding: 'var(--sp-3) var(--sp-4)',
|
||||
fontSize: '0.8rem', color: n.color, boxShadow: 'var(--shadow-md)',
|
||||
}}>
|
||||
{n.msg}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
|
||||
{/* Tab toggle */}
|
||||
<div style={{ display: 'flex', gap: 'var(--sp-2)', marginBottom: 'var(--sp-5)' }}>
|
||||
<button className={`btn btn-sm${!manufacturingTab ? ' btn-primary' : ''}`} onClick={() => setManufacturingTab(false)}>
|
||||
Refining
|
||||
</button>
|
||||
<button className={`btn btn-sm${manufacturingTab ? ' btn-primary' : ''}`} onClick={() => setManufacturingTab(true)}>
|
||||
Manufacturing
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{/* Stats */}
|
||||
<div className="stat-grid" style={{ gridTemplateColumns: 'repeat(auto-fit, minmax(160px, 1fr))' }}>
|
||||
<div className="stat-card">
|
||||
<div className="stat-value" style={{ color: 'var(--accent)' }}>Lvl {skillLevel}</div>
|
||||
<div className="stat-label">Industry Skill</div>
|
||||
</div>
|
||||
<div className="stat-card">
|
||||
<div className="stat-value" style={{ color: 'var(--cyan)' }}>{(skillEfficiency[skillLevel] * 100).toFixed(0)}%</div>
|
||||
<div className="stat-label">Refine Efficiency</div>
|
||||
</div>
|
||||
<div className="stat-card">
|
||||
<div className="stat-value" style={{ color: 'var(--green)' }}>
|
||||
{Object.values(playerMinerals).reduce((a, b) => a + b, 0).toLocaleString()}
|
||||
</div>
|
||||
<div className="stat-label">Total Minerals</div>
|
||||
</div>
|
||||
<div className="stat-card">
|
||||
<div className="stat-value" style={{ color: 'var(--purple)' }}>{manufacturingJobs.length}</div>
|
||||
<div className="stat-label">Active Jobs</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{!manufacturingTab ? (
|
||||
/* ===== REFINING ===== */
|
||||
<>
|
||||
<div className="demo-container">
|
||||
<div className="demo-toolbar">
|
||||
<span className="demo-title">Reprocessing Plant</span>
|
||||
<span style={{ color: 'var(--muted)' }}>|</span>
|
||||
<span style={{ color: 'var(--fg-dim)', fontSize: '0.8rem' }}>Jita IV — Moon 4</span>
|
||||
{refining && (
|
||||
<span style={{ marginLeft: 'auto', color: 'var(--accent)', fontFamily: 'var(--font-mono)', fontSize: '0.7rem' }}>
|
||||
◌ REFINING...
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div style={{ display: 'flex', minHeight: '300px' }}>
|
||||
{/* Ore selection */}
|
||||
<div style={{ width: '280px', borderRight: '1px solid var(--border)', overflowY: 'auto' }}>
|
||||
<div style={{ padding: 'var(--sp-3) var(--sp-4)', borderBottom: '1px solid var(--border)' }}>
|
||||
<div style={{ fontFamily: 'var(--font-mono)', fontSize: '0.65rem', color: 'var(--accent)', textTransform: 'uppercase' }}>
|
||||
Your Ore
|
||||
</div>
|
||||
</div>
|
||||
{inventory.map(item => {
|
||||
const data = mineralData[item.item];
|
||||
return (
|
||||
<div key={item.item} style={{
|
||||
padding: 'var(--sp-3) var(--sp-4)',
|
||||
borderBottom: '1px solid var(--border)',
|
||||
cursor: 'pointer',
|
||||
background: selectedOre === item.item ? 'var(--accent-bg)' : 'transparent',
|
||||
}} onClick={() => { setSelectedOre(item.item); setRefineQty(item.quantity); }}>
|
||||
<div style={{ fontSize: '0.8rem', color: selectedOre === item.item ? 'var(--fg-bright)' : 'var(--fg-dim)' }}>
|
||||
{item.item}
|
||||
</div>
|
||||
<div style={{ fontFamily: 'var(--font-mono)', fontSize: '0.65rem', color: 'var(--muted)' }}>
|
||||
{item.quantity.toLocaleString()} units · → {data?.mineral || '?'}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
|
||||
{/* Refining panel */}
|
||||
<div style={{ flex: 1, padding: 'var(--sp-5)' }}>
|
||||
{selectedOre && mineralData[selectedOre] ? (
|
||||
<>
|
||||
<h3 style={{ marginBottom: 'var(--sp-4)' }}>{selectedOre}</h3>
|
||||
<div className="grid-2" style={{ marginBottom: 'var(--sp-5)' }}>
|
||||
<div>
|
||||
<div style={{ fontSize: '0.75rem', color: 'var(--muted)', marginBottom: 'var(--sp-1)' }}>Yields Mineral</div>
|
||||
<div style={{ color: 'var(--cyan)', fontWeight: 600 }}>{mineralData[selectedOre].mineral}</div>
|
||||
</div>
|
||||
<div>
|
||||
<div style={{ fontSize: '0.75rem', color: 'var(--muted)', marginBottom: 'var(--sp-1)' }}>Batch Size</div>
|
||||
<div style={{ fontFamily: 'var(--font-mono)' }}>{mineralData[selectedOre].batch.toLocaleString()} units</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div style={{ marginBottom: 'var(--sp-4)' }}>
|
||||
<div style={{ display: 'flex', justifyContent: 'space-between', marginBottom: 'var(--sp-1)' }}>
|
||||
<span style={{ fontSize: '0.75rem', color: 'var(--muted)' }}>Quantity to Refine</span>
|
||||
<span style={{ fontFamily: 'var(--font-mono)', fontSize: '0.75rem', color: 'var(--fg-dim)' }}>
|
||||
{Math.floor(refineQty / mineralData[selectedOre].batch)} batches
|
||||
</span>
|
||||
</div>
|
||||
<input type="range" min={0} max={inventory.find(i => i.item === selectedOre)?.quantity || 0}
|
||||
value={refineQty} onChange={e => setRefineQty(parseInt(e.target.value))}
|
||||
style={{ width: '100%', accentColor: 'var(--accent)' }}
|
||||
/>
|
||||
<div style={{ fontFamily: 'var(--font-mono)', fontSize: '0.8rem', color: 'var(--fg-bright)', textAlign: 'center' }}>
|
||||
{refineQty.toLocaleString()} units
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Skill selector */}
|
||||
<div style={{ marginBottom: 'var(--sp-5)' }}>
|
||||
<div style={{ fontSize: '0.75rem', color: 'var(--muted)', marginBottom: 'var(--sp-2)' }}>Industry Skill Level</div>
|
||||
<div style={{ display: 'flex', gap: 'var(--sp-1)' }}>
|
||||
{[0, 1, 2, 3, 4, 5].map(lvl => (
|
||||
<button key={lvl} className={`btn btn-sm${skillLevel === lvl ? ' btn-primary' : ''}`}
|
||||
style={{ minWidth: '36px' }} onClick={() => setSkillLevel(lvl)}>
|
||||
{lvl}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Preview */}
|
||||
{refineQty >= mineralData[selectedOre].batch && (
|
||||
<div className="card" style={{ marginBottom: 'var(--sp-4)' }}>
|
||||
<h4 style={{ marginBottom: 'var(--sp-3)' }}>Refining Preview</h4>
|
||||
{(() => {
|
||||
const data = mineralData[selectedOre];
|
||||
const batches = Math.floor(refineQty / data.batch);
|
||||
const used = batches * data.batch;
|
||||
const eff = skillEfficiency[skillLevel];
|
||||
const minYield = Math.floor(batches * data.yield * eff);
|
||||
const rawValue = used * (orePrices[selectedOre] || 0);
|
||||
const mineralValue = minYield * Math.floor((orePrices[selectedOre] || 0) * 2.5);
|
||||
return (
|
||||
<div style={{ fontFamily: 'var(--font-mono)', fontSize: '0.8rem' }}>
|
||||
<div style={{ display: 'flex', justifyContent: 'space-between', marginBottom: 'var(--sp-2)' }}>
|
||||
<span style={{ color: 'var(--muted)' }}>Ore consumed</span>
|
||||
<span>{used.toLocaleString()} {selectedOre}</span>
|
||||
</div>
|
||||
<div style={{ display: 'flex', justifyContent: 'space-between', marginBottom: 'var(--sp-2)' }}>
|
||||
<span style={{ color: 'var(--muted)' }}>Mineral yield</span>
|
||||
<span style={{ color: 'var(--cyan)' }}>{minYield.toLocaleString()} {data.mineral}</span>
|
||||
</div>
|
||||
<div style={{ display: 'flex', justifyContent: 'space-between', marginBottom: 'var(--sp-2)' }}>
|
||||
<span style={{ color: 'var(--muted)' }}>Efficiency</span>
|
||||
<span style={{ color: 'var(--accent)' }}>{(eff * 100).toFixed(0)}%</span>
|
||||
</div>
|
||||
<div style={{ borderTop: '1px solid var(--border)', paddingTop: 'var(--sp-2)', marginTop: 'var(--sp-2)' }}>
|
||||
<div style={{ display: 'flex', justifyContent: 'space-between', marginBottom: 'var(--sp-1)' }}>
|
||||
<span style={{ color: 'var(--muted)' }}>Sell raw value</span>
|
||||
<span>₢{rawValue.toLocaleString()}</span>
|
||||
</div>
|
||||
<div style={{ display: 'flex', justifyContent: 'space-between' }}>
|
||||
<span style={{ color: 'var(--muted)' }}>Refined value (est.)</span>
|
||||
<span style={{ color: mineralValue > rawValue ? 'var(--green)' : 'var(--red)' }}>
|
||||
₢{mineralValue.toLocaleString()} {mineralValue > rawValue ? '▲' : '▼'}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
})()}
|
||||
</div>
|
||||
)}
|
||||
|
||||
<button className="btn btn-primary" style={{ width: '100%' }}
|
||||
disabled={refining || refineQty < (mineralData[selectedOre]?.batch || 999)}
|
||||
onClick={handleRefine}>
|
||||
{refining ? 'Refining...' : 'Refine Ore'}
|
||||
</button>
|
||||
</>
|
||||
) : (
|
||||
<div style={{ textAlign: 'center', padding: 'var(--sp-8)', color: 'var(--muted)' }}>
|
||||
Select an ore type to begin refining
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Refining history */}
|
||||
{results.length > 0 && (
|
||||
<div style={{ marginTop: 'var(--sp-5)' }}>
|
||||
<h3>Refining History</h3>
|
||||
<table className="data-table" style={{ marginTop: 'var(--sp-3)' }}>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Ore</th>
|
||||
<th>Batches</th>
|
||||
<th>Mineral</th>
|
||||
<th>Yield</th>
|
||||
<th>Efficiency</th>
|
||||
<th>Raw Value</th>
|
||||
<th>Refined Value</th>
|
||||
<th>Verdict</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{results.map((r, i) => (
|
||||
<tr key={i}>
|
||||
<td style={{ color: 'var(--accent)' }}>{r.ore}</td>
|
||||
<td className="mono">{r.batches}</td>
|
||||
<td style={{ color: 'var(--cyan)' }}>{r.mineral}</td>
|
||||
<td className="mono">{r.yield.toLocaleString()}</td>
|
||||
<td className="mono">{(r.efficiency * 100).toFixed(0)}%</td>
|
||||
<td className="mono">₢{r.rawValue.toLocaleString()}</td>
|
||||
<td className="mono" style={{ color: r.better ? 'var(--green)' : 'var(--red)' }}>
|
||||
₢{r.mineralValue.toLocaleString()}
|
||||
</td>
|
||||
<td>
|
||||
<span className={`pill ${r.better ? 'pill-green' : 'pill-red'}`}>
|
||||
{r.better ? 'REFINE ▲' : 'SELL RAW ▼'}
|
||||
</span>
|
||||
</td>
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
) : (
|
||||
/* ===== MANUFACTURING ===== */
|
||||
<>
|
||||
<div className="grid-2">
|
||||
{/* Mineral inventory */}
|
||||
<div className="card">
|
||||
<h4 style={{ color: 'var(--cyan)', marginBottom: 'var(--sp-4)' }}>Mineral Inventory</h4>
|
||||
{Object.entries(playerMinerals).map(([mineral, qty]) => (
|
||||
<div key={mineral} style={{
|
||||
display: 'flex', justifyContent: 'space-between', alignItems: 'center',
|
||||
padding: 'var(--sp-2) 0', borderBottom: '1px solid var(--border)',
|
||||
}}>
|
||||
<span style={{ fontSize: '0.85rem', color: qty > 0 ? 'var(--fg-bright)' : 'var(--muted)' }}>{mineral}</span>
|
||||
<span style={{ fontFamily: 'var(--font-mono)', fontSize: '0.8rem', color: qty > 0 ? 'var(--cyan)' : 'var(--muted)' }}>
|
||||
{qty.toLocaleString()}
|
||||
</span>
|
||||
</div>
|
||||
))}
|
||||
<div className="callout callout-info" style={{ marginTop: 'var(--sp-4)', fontSize: '0.75rem' }}>
|
||||
Refine ore to accumulate minerals for manufacturing.
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Active jobs */}
|
||||
<div className="card">
|
||||
<h4 style={{ color: 'var(--accent)', marginBottom: 'var(--sp-4)' }}>Manufacturing Jobs</h4>
|
||||
{manufacturingJobs.length === 0 ? (
|
||||
<div style={{ color: 'var(--muted)', fontSize: '0.85rem', padding: 'var(--sp-4)', textAlign: 'center' }}>
|
||||
No active jobs. Start one from the recipe list below.
|
||||
</div>
|
||||
) : (
|
||||
manufacturingJobs.map(job => (
|
||||
<div key={job.id} style={{
|
||||
padding: 'var(--sp-3)', marginBottom: 'var(--sp-3)',
|
||||
background: 'var(--surface-raised)', borderRadius: 'var(--radius-md)',
|
||||
border: '1px solid var(--border)',
|
||||
}}>
|
||||
<div style={{ display: 'flex', justifyContent: 'space-between', marginBottom: 'var(--sp-2)' }}>
|
||||
<span style={{ fontSize: '0.85rem', color: 'var(--fg-bright)' }}>{job.product}</span>
|
||||
<span style={{ fontFamily: 'var(--font-mono)', fontSize: '0.7rem', color: 'var(--cyan)' }}>
|
||||
{formatTime(job.remaining)}
|
||||
</span>
|
||||
</div>
|
||||
<div className="progress-bar" style={{ height: '6px' }}>
|
||||
<div className="fill" style={{
|
||||
width: `${((job.totalTime - job.remaining) / job.totalTime) * 100}%`,
|
||||
background: 'var(--accent)',
|
||||
}} />
|
||||
</div>
|
||||
</div>
|
||||
))
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Recipe list */}
|
||||
<div style={{ marginTop: 'var(--sp-6)' }}>
|
||||
<h3>Blueprints</h3>
|
||||
<div style={{ display: 'grid', gridTemplateColumns: 'repeat(auto-fill, minmax(320px, 1fr))', gap: 'var(--sp-4)', marginTop: 'var(--sp-4)' }}>
|
||||
{manufacturingRecipes.map(recipe => {
|
||||
const canBuild = skillLevel >= recipe.skill &&
|
||||
Object.entries(recipe.minerals).every(([m, q]) => (playerMinerals[m] || 0) >= q);
|
||||
return (
|
||||
<div key={recipe.id} className="card" style={{ marginBottom: 0 }}>
|
||||
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'baseline', marginBottom: 'var(--sp-3)' }}>
|
||||
<h4 style={{ margin: 0, color: 'var(--accent)' }}>{recipe.product}</h4>
|
||||
<span className={`pill ${canBuild ? 'pill-green' : 'pill-red'}`}>
|
||||
{canBuild ? 'READY' : skillLevel < recipe.skill ? `LVL ${recipe.skill}` : 'NEED MATS'}
|
||||
</span>
|
||||
</div>
|
||||
<div style={{ fontSize: '0.8rem', color: 'var(--fg-dim)', marginBottom: 'var(--sp-3)' }}>
|
||||
{Object.entries(recipe.minerals).map(([m, q]) => {
|
||||
const have = playerMinerals[m] || 0;
|
||||
return (
|
||||
<div key={m} style={{ display: 'flex', justifyContent: 'space-between', marginBottom: '2px' }}>
|
||||
<span style={{ color: 'var(--muted)' }}>{m}</span>
|
||||
<span style={{ fontFamily: 'var(--font-mono)', color: have >= q ? 'var(--green)' : 'var(--red)' }}>
|
||||
{have.toLocaleString()} / {q.toLocaleString()}
|
||||
</span>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
|
||||
<span style={{ fontFamily: 'var(--font-mono)', fontSize: '0.7rem', color: 'var(--muted)' }}>
|
||||
Time: {formatTime(recipe.time)} · Skill: Lvl {recipe.skill}
|
||||
</span>
|
||||
<button className="btn btn-sm btn-primary" disabled={!canBuild}
|
||||
onClick={() => handleManufacture(recipe)}>
|
||||
Manufacture
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
window.GDD.RefiningDemo = RefiningDemo;
|
||||
Reference in New Issue
Block a user