concept5-terminal.jsx
1 // Concept 5 — DATA-DENSE TERMINAL GRID 2 // Bloomberg-meets-Elite-Dangerous. Mono-everything, ultra-dense info grid. 3 // Looks like a real trader's procurement terminal: tables, mini-sparklines, 4 // green-on-black with market chatter scroll. Every pixel earns its place. 5 6 function Concept5_Terminal({ seed = "C5-default" }) { 7 const ship = makeShip(seed); 8 const bg = "#000906"; 9 const panel = "#031510"; 10 const fg = "#d6ffe8"; 11 const g1 = "#4dff9a"; // up/active 12 const g2 = "#1e7a4a"; // dim 13 const y1 = "#ffe066"; // highlight 14 const r1 = "#ff5a5a"; // down/alert 15 const cyan = "#5de8ff"; 16 const mute = "#4a6b60"; 17 18 // sparkline 19 const Spark = ({ seedStr, color = g1, w = 90, h = 22, up = true }) => { 20 const rng = mulberry32(hashStr(seedStr)); 21 const pts = []; 22 let y = 0.5; 23 for (let i = 0; i < 24; i++) { 24 y += (rng() - (up ? 0.4 : 0.55)) * 0.15; 25 y = Math.max(0.05, Math.min(0.95, y)); 26 pts.push([i/23*w, h - y*h]); 27 } 28 return ( 29 <svg width={w} height={h} style={{display:"block"}}> 30 <polyline points={pts.map(p=>p.join(",")).join(" ")} fill="none" stroke={color} strokeWidth={1.2}/> 31 <circle cx={pts[pts.length-1][0]} cy={pts[pts.length-1][1]} r={1.8} fill={color}/> 32 </svg> 33 ); 34 }; 35 36 const Cell = ({ children, col = fg, right, bold }) => ( 37 <td style={{padding:"3px 8px", color:col, textAlign: right?"right":"left", fontWeight: bold?700:400, whiteSpace:"nowrap", borderRight:`1px solid ${g2}33`}}>{children}</td> 38 ); 39 40 // Similar units row data 41 const similar = Array.from({length: 7}).map((_,i) => makeShip(seed+"-alt-"+i)); 42 43 return ( 44 <div style={{ 45 width:1280, height:880, background: bg, color: fg, 46 fontFamily:"'IBM Plex Mono', 'Menlo', monospace", fontSize:11, position:"relative", overflow:"hidden", 47 }}> 48 {/* TOP BAR — Bloomberg-ish */} 49 <div style={{display:"flex", alignItems:"center", background:"#000", borderBottom:`1px solid ${g1}`, padding:"5px 12px", gap:16, fontSize:10}}> 50 <span style={{color:g1, fontWeight:700, letterSpacing:3}}>FLEET//PROCURE</span> 51 <span style={{color:mute}}>F1 CATALOG</span> 52 <span style={{color:mute}}>F2 BROKERS</span> 53 <span style={{color:mute}}>F3 ORDERS</span> 54 <span style={{color:mute}}>F4 WATCH</span> 55 <span style={{color:mute}}>F5 ESCROW</span> 56 <span style={{color:mute}}>F6 FLEET</span> 57 <span style={{marginLeft:"auto", color:g1}}>◉ LIVE</span> 58 <span style={{color:mute}}>CMD:{seed.slice(0,6).toUpperCase()}</span> 59 <span style={{color:y1}}>2396.114.0841z</span> 60 </div> 61 62 {/* COMMAND LINE */} 63 <div style={{padding:"6px 12px", background:"#021008", borderBottom:`1px solid ${g2}66`, color:g1, fontSize:11, display:"flex", alignItems:"center", gap:8}}> 64 <span style={{color:mute}}>›</span> 65 <span>DESC {ship.serial}</span> 66 <span style={{color:mute}}>—</span> 67 <span style={{color:fg}}>{ship.mfg.name} · {ship.cls.name}</span> 68 <span style={{marginLeft:"auto", color:y1, fontSize:10}}>[TAB] switch panel · [/] search · [P] procure</span> 69 </div> 70 71 {/* MAIN GRID */} 72 <div style={{display:"grid", gridTemplateColumns:"380px 1fr 340px", gridTemplateRows:"auto 1fr auto", height:"calc(100% - 58px - 26px)"}}> 73 74 {/* HEADER STRIP — spans */} 75 <div style={{gridColumn:"1 / -1", borderBottom:`1px solid ${g2}66`, padding:"10px 14px", display:"flex", alignItems:"center", justifyContent:"space-between", background:panel}}> 76 <div style={{display:"flex", alignItems:"center", gap:14}}> 77 <span style={{fontFamily:"Orbitron", fontSize:22, fontWeight:700, color:fg, letterSpacing:1}}>{ship.model}</span> 78 <span style={{color:g1, fontSize:12, letterSpacing:2}}>[{ship.serial}]</span> 79 <span style={{color:g2, fontSize:10}}>·</span> 80 <span style={{color:mute, fontSize:10, letterSpacing:1.5}}>{ship.cls.name.toUpperCase()} / {ship.cls.role.toUpperCase()}</span> 81 <span style={{border:`1px solid ${g1}`, color:g1, padding:"1px 6px", fontSize:9, letterSpacing:1.5}}>{ship.avail}</span> 82 <span style={{border:`1px solid ${y1}`, color:y1, padding:"1px 6px", fontSize:9, letterSpacing:1.5}}>{ship.cond}</span> 83 <span style={{border:`1px solid ${cyan}`, color:cyan, padding:"1px 6px", fontSize:9, letterSpacing:1.5}}>{ship.faction.toUpperCase()}</span> 84 </div> 85 <div style={{display:"flex", alignItems:"baseline", gap:10}}> 86 <span style={{color:mute, fontSize:10, letterSpacing:2}}>LAST</span> 87 <span style={{fontFamily:"Orbitron", fontWeight:700, color:g1, fontSize:26}}>₵{formatCred(ship.price)}</span> 88 <span style={{color:g1, fontSize:11}}>+2.3%</span> 89 <Spark seedStr={seed+"-hdr"} color={g1} w={120} h={20} up={true}/> 90 </div> 91 </div> 92 93 {/* LEFT COLUMN — KeyVal dump */} 94 <div style={{borderRight:`1px solid ${g2}66`, background:panel, padding:"10px 0", overflow:"hidden"}}> 95 <KV title="IDENTITY"> 96 <KVRow k="MFG" v={ship.mfg.name}/> 97 <KVRow k="YARD" v={ship.mfg.loc}/> 98 <KVRow k="MODEL" v={ship.model}/> 99 <KVRow k="SERIAL" v={ship.serial} c={g1}/> 100 <KVRow k="TYPE" v={`${ship.cls.code} · ${ship.cls.name}`}/> 101 <KVRow k="ERA" v={`${ship.year} CMN`}/> 102 <KVRow k="FACTION" v={ship.faction}/> 103 <KVRow k="FLAG" v="COALITION BONDED"/> 104 </KV> 105 <KV title="DIMENSIONAL"> 106 <KVRow k="LENGTH" v={`${ship.length_m} m`}/> 107 <KVRow k="MASS" v={`${ship.mass.toLocaleString()} t`}/> 108 <KVRow k="CARGO" v={`${ship.cargo_t.toLocaleString()} t`}/> 109 <KVRow k="CREW" v={`${ship.crew} pax`}/> 110 <KVRow k="LIFE SUP" v={`${ship.crew * 6} pax-days`}/> 111 </KV> 112 <KV title="PROPULSION"> 113 <KVRow k="DRIVE" v={ship.drive} c={cyan}/> 114 <KVRow k="TOP" v={`${ship.topSpeed.toLocaleString()} m/s`}/> 115 <KVRow k="THRUST" v={`${ship.thrust} g`}/> 116 <KVRow k="JUMP" v={`${ship.jumpRange} ly`}/> 117 <KVRow k="MANEUV" v={`${ship.maneuver}%`}/> 118 </KV> 119 <KV title="COMBAT"> 120 <KVRow k="ARMOR" v={`${ship.armor.toLocaleString()} mm`}/> 121 <KVRow k="SHIELD" v={`${ship.shields.toLocaleString()} GJ`}/> 122 <KVRow k="HARDPT" v={ship.hardpoints}/> 123 <KVRow k="SIG" v={`${ship.signature} dB`} c={y1}/> 124 </KV> 125 </div> 126 127 {/* CENTER — viewport + chart + similar */} 128 <div style={{borderRight:`1px solid ${g2}66`, display:"flex", flexDirection:"column"}}> 129 {/* Ortho viewport */} 130 <div style={{padding:"10px 14px", borderBottom:`1px solid ${g2}66`, background:bg}}> 131 <HdrBar left="◢ ORTHO VIEW" right={`SCALE 1:${Math.round(ship.length_m/8)} · ${ship.length_m}m × ${Math.round(ship.length_m*0.45)}m`}/> 132 <div style={{position:"relative", height:260, border:`1px solid ${g2}66`, background:"#01090a", 133 backgroundImage:`linear-gradient(${g2}22 1px, transparent 1px), linear-gradient(90deg, ${g2}22 1px, transparent 1px)`, 134 backgroundSize:"20px 20px"}}> 135 <div style={{position:"absolute", inset:"10% 8%"}}> 136 <ShipSilhouette ship={ship} stroke={g1} fill="rgba(77,255,154,0.05)" glow strokeWidth={1.2}/> 137 </div> 138 {/* corner labels */} 139 <div style={{position:"absolute", top:4, left:6, color:mute, fontSize:9, letterSpacing:1.5}}>[0,0]</div> 140 <div style={{position:"absolute", top:4, right:6, color:mute, fontSize:9, letterSpacing:1.5}}>VW-A · PORT</div> 141 <div style={{position:"absolute", bottom:4, left:6, color:mute, fontSize:9, letterSpacing:1.5}}>◉ RENDER · WIRE · 60fps</div> 142 <div style={{position:"absolute", bottom:4, right:6, color:g1, fontSize:9, letterSpacing:1.5}}>HUD OVERLAY ON</div> 143 </div> 144 <div style={{display:"flex", marginTop:6, gap:4}}> 145 {["ORTHO","TOP","BOW","STERN","SECTION","SYSTEMS"].map((v,i) => ( 146 <div key={v} style={{padding:"3px 10px", fontSize:10, letterSpacing:2, border:`1px solid ${i===0?g1:g2}66`, color: i===0?g1:mute, background: i===0?`${g1}11`:"transparent"}}>{v}</div> 147 ))} 148 </div> 149 </div> 150 151 {/* Price chart */} 152 <div style={{padding:"10px 14px", borderBottom:`1px solid ${g2}66`, background:bg}}> 153 <HdrBar left="◢ PRICE · 90 CYCLE" right={`LAST ₵${formatCred(ship.price)} · VOL 412`}/> 154 <div style={{position:"relative", height:90, border:`1px solid ${g2}66`}}> 155 <PriceChart seed={seed} color={g1} yellow={y1} red={r1} mute={mute}/> 156 </div> 157 <div style={{display:"flex", justifyContent:"space-between", fontSize:9, color:mute, marginTop:4, letterSpacing:1}}> 158 <span>−90c</span><span>−60c</span><span>−30c</span><span style={{color:g1}}>NOW</span> 159 </div> 160 </div> 161 162 {/* Similar units table */} 163 <div style={{padding:"10px 14px", background:bg, flex:1, overflow:"hidden"}}> 164 <HdrBar left="◢ COMPARABLE UNITS" right="8 MATCHES · SORT: PRICE"/> 165 <table style={{width:"100%", borderCollapse:"collapse", fontSize:10, marginTop:4}}> 166 <thead> 167 <tr style={{color:mute, borderBottom:`1px solid ${g2}66`, textAlign:"left"}}> 168 <th style={{padding:"3px 8px", fontWeight:400, letterSpacing:1.5}}>SERIAL</th> 169 <th style={{padding:"3px 8px", fontWeight:400, letterSpacing:1.5}}>MODEL</th> 170 <th style={{padding:"3px 8px", fontWeight:400, letterSpacing:1.5}}>CLS</th> 171 <th style={{padding:"3px 8px", fontWeight:400, letterSpacing:1.5, textAlign:"right"}}>MASS</th> 172 <th style={{padding:"3px 8px", fontWeight:400, letterSpacing:1.5, textAlign:"right"}}>JUMP</th> 173 <th style={{padding:"3px 8px", fontWeight:400, letterSpacing:1.5, textAlign:"right"}}>PRICE</th> 174 <th style={{padding:"3px 8px", fontWeight:400, letterSpacing:1.5}}>CHART</th> 175 <th style={{padding:"3px 8px", fontWeight:400, letterSpacing:1.5, textAlign:"right"}}>Δ</th> 176 </tr> 177 </thead> 178 <tbody> 179 {similar.map((s,i) => { 180 const delta = ((hashStr(s.seed) % 200) - 100) / 10; 181 const up = delta >= 0; 182 return ( 183 <tr key={i} style={{borderBottom:`1px dotted ${g2}33`, background: i===0 ? `${g1}11` : "transparent"}}> 184 <Cell col={g1}>{s.serial}</Cell> 185 <Cell>{s.model}</Cell> 186 <Cell col={cyan}>{s.cls.code}</Cell> 187 <Cell right>{s.mass.toLocaleString()}</Cell> 188 <Cell right>{s.jumpRange}</Cell> 189 <Cell right bold>₵{formatCred(s.price)}</Cell> 190 <td style={{padding:"1px 8px", borderRight:`1px solid ${g2}33`}}><Spark seedStr={s.seed} color={up?g1:r1} w={70} h={16} up={up}/></td> 191 <Cell right col={up?g1:r1}>{up?"+":""}{delta.toFixed(1)}%</Cell> 192 </tr> 193 ); 194 })} 195 </tbody> 196 </table> 197 </div> 198 </div> 199 200 {/* RIGHT COLUMN — procurement + order book + news */} 201 <div style={{display:"flex", flexDirection:"column", background:panel}}> 202 <div style={{padding:"10px 14px", borderBottom:`1px solid ${g2}66`}}> 203 <HdrBar left="◢ PROCUREMENT" right=""/> 204 <div style={{fontSize:10, color:mute, letterSpacing:1.5, marginTop:6}}>UNIT PRICE</div> 205 <div style={{fontFamily:"Orbitron", fontWeight:900, fontSize:28, color:g1}}>₵{formatCred(ship.price)}</div> 206 <div style={{display:"grid", gridTemplateColumns:"1fr 1fr", gap:6, marginTop:8, fontSize:10}}> 207 <KVSmall k="LEASE/MO" v={`₵${formatCred(Math.round(ship.price/120))}`}/> 208 <KVSmall k="INSURE" v="1.8%/y"/> 209 <KVSmall k="DELIVERY" v={`${ship.delivery} cyc`}/> 210 <KVSmall k="WARRANTY" v={ship.warranty}/> 211 </div> 212 <div style={{marginTop:10, display:"grid", gridTemplateColumns:"1fr 1fr", gap:6}}> 213 <button style={{padding:"10px 0", background:g1, color:"#001208", border:"none", fontFamily:"Orbitron", fontWeight:700, fontSize:11, letterSpacing:3, cursor:"pointer"}}>BUY</button> 214 <button style={{padding:"10px 0", background:"transparent", color:y1, border:`1px solid ${y1}`, fontFamily:"Orbitron", fontWeight:700, fontSize:11, letterSpacing:3, cursor:"pointer"}}>BID</button> 215 </div> 216 </div> 217 218 {/* order book */} 219 <div style={{padding:"10px 14px", borderBottom:`1px solid ${g2}66`}}> 220 <HdrBar left="◢ ORDER BOOK" right="DEPTH 5"/> 221 <table style={{width:"100%", borderCollapse:"collapse", fontSize:10, marginTop:4}}> 222 <tbody> 223 {[[r1,"ASK","12.8M","₵428.0M",2], 224 [r1,"ASK","4.2M", "₵425.1M",1], 225 [r1,"ASK","1.0M", "₵423.3M",1], 226 [g1,"BID","2.1M", "₵421.9M",1], 227 [g1,"BID","6.6M", "₵419.4M",3], 228 [g1,"BID","18.0M","₵414.2M",4], 229 ].map((r,i) => ( 230 <tr key={i} style={{borderBottom:`1px dotted ${g2}33`}}> 231 <td style={{padding:"3px 6px", color:r[0], width:36, fontWeight:700}}>{r[1]}</td> 232 <td style={{padding:"3px 6px", textAlign:"right", color:mute}}>{r[2]}</td> 233 <td style={{padding:"3px 6px", textAlign:"right", color:fg, fontWeight:600}}>{r[3]}</td> 234 <td style={{padding:"3px 6px", textAlign:"right", color:r[0]}}>×{r[4]}</td> 235 </tr> 236 ))} 237 </tbody> 238 </table> 239 </div> 240 241 {/* news feed */} 242 <div style={{padding:"10px 14px", flex:1, overflow:"hidden"}}> 243 <HdrBar left="◢ WIRE · FLEET DESK" right="LIVE"/> 244 <div style={{fontSize:10, color:fg, lineHeight:1.55, marginTop:6}}> 245 {[ 246 [y1,"08:39","KRV Yards expand Ceres-II dry-dock. Throughput +18%."], 247 [g1,"08:21","Coalition lifts export tariff on Tier-2 interceptors."], 248 [r1,"07:58","Obsidian Forge recall · MN-class drive bolts."], 249 [cyan,"07:32","Syndicate fleet liquidation · 41 lots entering market."], 250 [g1,"06:44","Grav-slip drive certification standard ratified."], 251 [fg,"06:11","Pleasure yacht index stable at 112.4."], 252 ].map((row,i) => ( 253 <div key={i} style={{display:"flex", gap:8, padding:"2px 0", borderBottom:`1px dotted ${g2}33`}}> 254 <span style={{color:mute, width:36}}>{row[1]}</span> 255 <span style={{color:row[0], flex:1}}>{row[2]}</span> 256 </div> 257 ))} 258 </div> 259 </div> 260 </div> 261 262 {/* BOTTOM STATUS — spans all */} 263 <div style={{gridColumn:"1 / -1", borderTop:`1px solid ${g2}66`, padding:"5px 14px", background:"#000", display:"flex", justifyContent:"space-between", fontSize:9, color:mute, letterSpacing:1.5}}> 264 <span>◉ 14,208 SIMILAR UNITS IDX</span> 265 <span>HEARTBEAT 22ms</span> 266 <span>AUTH · CMDR VEYR · COMMODORE TIER</span> 267 <span>ENCRYPT · Q-KEY 4096</span> 268 <span style={{color:g1}}>SESSION 04:12:08</span> 269 </div> 270 </div> 271 </div> 272 ); 273 } 274 275 function KV({ title, children }) { 276 return ( 277 <div style={{padding:"6px 14px", borderBottom:`1px solid #1e7a4a33`}}> 278 <div style={{color:"#4a6b60", fontSize:9, letterSpacing:2, marginBottom:4}}>▸ {title}</div> 279 <div>{children}</div> 280 </div> 281 ); 282 } 283 function KVRow({ k, v, c = "#d6ffe8" }) { 284 return ( 285 <div style={{display:"flex", justifyContent:"space-between", fontSize:11, padding:"1px 0"}}> 286 <span style={{color:"#4a6b60", letterSpacing:1.5}}>{k}</span> 287 <span style={{color:c, fontWeight:500}}>{v}</span> 288 </div> 289 ); 290 } 291 function KVSmall({ k, v }) { 292 return ( 293 <div> 294 <div style={{color:"#4a6b60", fontSize:9, letterSpacing:1.5}}>{k}</div> 295 <div style={{color:"#d6ffe8", fontWeight:600, fontSize:12}}>{v}</div> 296 </div> 297 ); 298 } 299 function HdrBar({ left, right }) { 300 return ( 301 <div style={{display:"flex", justifyContent:"space-between", fontSize:10, letterSpacing:2, color:"#4dff9a", paddingBottom:4, borderBottom:"1px solid #1e7a4a66"}}> 302 <span>{left}</span> 303 <span style={{color:"#4a6b60"}}>{right}</span> 304 </div> 305 ); 306 } 307 function PriceChart({ seed, color, yellow, red, mute }) { 308 const rng = mulberry32(hashStr(seed+"chart")); 309 const w = 600, h = 90; 310 const N = 90; 311 const pts = []; 312 let y = 0.5; 313 for (let i = 0; i < N; i++) { 314 y += (rng() - 0.48) * 0.07; 315 y = Math.max(0.1, Math.min(0.9, y)); 316 pts.push([i/(N-1)*w, h - y*h]); 317 } 318 const area = `M 0,${h} ${pts.map(p=>`L ${p[0]},${p[1]}`).join(" ")} L ${w},${h} Z`; 319 const line = `M ${pts.map(p=>`${p[0]},${p[1]}`).join(" L ")}`; 320 return ( 321 <svg viewBox={`0 0 ${w} ${h}`} preserveAspectRatio="none" style={{width:"100%", height:"100%"}}> 322 {/* gridlines */} 323 {[0.25, 0.5, 0.75].map(f => <line key={f} x1={0} y1={h*f} x2={w} y2={h*f} stroke={mute} strokeOpacity={0.25} strokeDasharray="2,4"/>)} 324 <path d={area} fill={color} fillOpacity={0.15}/> 325 <path d={line} fill="none" stroke={color} strokeWidth={1.4}/> 326 <circle cx={pts[pts.length-1][0]-2} cy={pts[pts.length-1][1]} r={3} fill={yellow}/> 327 {/* avg line */} 328 <line x1={0} y1={h*0.55} x2={w} y2={h*0.55} stroke={yellow} strokeOpacity={0.5} strokeDasharray="4,4"/> 329 </svg> 330 ); 331 } 332 333 Object.assign(window, { Concept5_Terminal });