concept4-reticle.jsx
1 // Concept 4 — RADAR RETICLE DEALER 2 // Circular radar-scope centerpiece. Amber-on-black arcade war-room. 3 // Ship floats at the center of a sweeping radar; surrounding sectors 4 // hold specs like quadrants of a targeting scope. 5 6 function Concept4_Reticle({ seed = "C4-default" }) { 7 const ship = makeShip(seed); 8 const bg = "#080604"; 9 const amber = "#ffbe2e"; 10 const amberDim = "#8c6610"; 11 const red = "#ff4d4d"; 12 const green = "#8fff6a"; 13 const paper = "#fff3d0"; 14 15 const Tag = ({ children, danger, ok }) => ( 16 <span style={{ 17 border:`1px solid ${danger?red:ok?green:amber}`, 18 color: danger?red:ok?green:amber, 19 padding:"2px 8px", fontFamily:"'IBM Plex Mono',monospace", fontSize:10, letterSpacing:2, 20 background: "#0d0907", 21 }}>{children}</span> 22 ); 23 24 const SpecLine = ({ k, v, u }) => ( 25 <div style={{display:"flex", justifyContent:"space-between", fontFamily:"'IBM Plex Mono',monospace", fontSize:11, color:paper, padding:"3px 0", borderBottom:`1px solid ${amberDim}44`}}> 26 <span style={{color:amberDim, letterSpacing:1.5}}>{k}</span> 27 <span>{v} <span style={{color:amberDim, fontSize:9}}>{u}</span></span> 28 </div> 29 ); 30 31 return ( 32 <div style={{ 33 width:1280, height:880, background: `radial-gradient(ellipse at center, #120c05 0%, ${bg} 70%)`, 34 position:"relative", overflow:"hidden", color:amber, fontFamily:"'IBM Plex Mono', monospace", 35 }}> 36 {/* faint grid */} 37 <div style={{position:"absolute", inset:0, backgroundImage:`linear-gradient(${amberDim}22 1px, transparent 1px), linear-gradient(90deg, ${amberDim}22 1px, transparent 1px)`, backgroundSize:"60px 60px"}}/> 38 39 {/* TOP CHROME */} 40 <div style={{display:"flex", alignItems:"center", justifyContent:"space-between", padding:"14px 28px", borderBottom:`1px solid ${amber}`, background:"rgba(0,0,0,0.5)", position:"relative", zIndex:2}}> 41 <div style={{display:"flex", alignItems:"center", gap:20}}> 42 <div style={{fontFamily:"Orbitron", fontWeight:900, fontSize:18, letterSpacing:5, color:amber}}>◉ WARROOM.VI</div> 43 <div style={{fontSize:10, letterSpacing:2, color:amberDim}}>FLEET PROCUREMENT · TERMINAL 07-G</div> 44 </div> 45 <div style={{display:"flex", gap:18, fontSize:10, letterSpacing:2, color:amberDim}}> 46 <span style={{color:green}}>◉ LINK 98%</span> 47 <span>CHR 2396.114.08:41</span> 48 <span>OBS RINGS · 8 CONTACTS</span> 49 <span>★★★★☆ BROKER 4.6</span> 50 </div> 51 </div> 52 53 {/* TOP TABS */} 54 <div style={{display:"flex", borderBottom:`1px solid ${amber}`, background:"rgba(0,0,0,0.3)"}}> 55 {["Fighters","Haulers","Yachts","Industrial","Capital","Science"].map((t,i) => ( 56 <div key={t} style={{ 57 padding:"8px 20px", fontSize:11, letterSpacing:3, textTransform:"uppercase", 58 borderRight:`1px solid ${amberDim}44`, color: i===0 ? amber : amberDim, 59 background: i===0 ? `${amber}15` : "transparent", 60 borderBottom: i===0 ? `2px solid ${amber}` : "none", 61 }}>{t}</div> 62 ))} 63 </div> 64 65 {/* MAIN GRID */} 66 <div style={{display:"grid", gridTemplateColumns:"1fr 560px 1fr", height:"calc(100% - 100px)"}}> 67 68 {/* LEFT QUADRANTS */} 69 <div style={{padding:"20px 24px", display:"flex", flexDirection:"column", gap:16, borderRight:`1px solid ${amberDim}66`}}> 70 {/* Header */} 71 <div> 72 <div style={{fontSize:10, letterSpacing:3, color:amberDim}}>DESIGNATION / {ship.cls.code}</div> 73 <div style={{fontFamily:"Orbitron", fontWeight:700, fontSize:28, color:paper, letterSpacing:1, marginTop:4, lineHeight:1.05}}>{ship.model}</div> 74 <div style={{fontSize:11, letterSpacing:2, color:amber, marginTop:4}}>{ship.serial}</div> 75 <div style={{display:"flex", gap:6, marginTop:10, flexWrap:"wrap"}}> 76 <Tag>{ship.cls.name.toUpperCase()}</Tag> 77 <Tag>{ship.mfg.code}</Tag> 78 <Tag ok>{ship.avail}</Tag> 79 <Tag danger>CLASS-3 EXPORT</Tag> 80 </div> 81 </div> 82 83 {/* Q1 - Hull */} 84 <div> 85 <QuadHdr n="Q1" title="HULL"/> 86 <SpecLine k="LENGTH" v={ship.length_m} u="m"/> 87 <SpecLine k="MASS" v={ship.mass.toLocaleString()} u="t"/> 88 <SpecLine k="ARMOR" v={ship.armor.toLocaleString()} u="mm"/> 89 <SpecLine k="SHIELD" v={ship.shields.toLocaleString()} u="GJ"/> 90 <SpecLine k="CREW" v={ship.crew} u="pax"/> 91 </div> 92 93 {/* Q2 - Drive */} 94 <div> 95 <QuadHdr n="Q2" title="DRIVE"/> 96 <SpecLine k="TYPE" v={ship.drive} u=""/> 97 <SpecLine k="TOP SPD" v={ship.topSpeed.toLocaleString()} u="m/s"/> 98 <SpecLine k="THRUST" v={ship.thrust} u="g"/> 99 <SpecLine k="JUMP" v={ship.jumpRange} u="ly"/> 100 </div> 101 102 {/* Q3 - Combat */} 103 <div> 104 <QuadHdr n="Q3" title="ARMAMENT"/> 105 <SpecLine k="HARDPT" v={ship.hardpoints} u="mt"/> 106 <SpecLine k="MANEUV" v={ship.maneuver} u="%"/> 107 <SpecLine k="SIG" v={ship.signature} u="dB"/> 108 </div> 109 </div> 110 111 {/* CENTER — RADAR SCOPE */} 112 <div style={{position:"relative", display:"flex", flexDirection:"column", alignItems:"center", padding:"16px"}}> 113 {/* scope */} 114 <div style={{position:"relative", width:520, height:520, marginTop:10}}> 115 <svg viewBox="0 0 520 520" width="520" height="520" style={{filter:`drop-shadow(0 0 30px ${amber}55)`}}> 116 <defs> 117 <radialGradient id="scopeBg" cx="50%" cy="50%" r="50%"> 118 <stop offset="0%" stopColor={amber} stopOpacity="0.08"/> 119 <stop offset="70%" stopColor={amber} stopOpacity="0.03"/> 120 <stop offset="100%" stopColor={amber} stopOpacity="0"/> 121 </radialGradient> 122 <linearGradient id="sweep" x1="0" y1="0" x2="1" y2="0"> 123 <stop offset="0%" stopColor={amber} stopOpacity="0.3"/> 124 <stop offset="100%" stopColor={amber} stopOpacity="0"/> 125 </linearGradient> 126 </defs> 127 <circle cx="260" cy="260" r="250" fill="url(#scopeBg)"/> 128 {/* rings */} 129 {[60, 120, 180, 240].map(r => <circle key={r} cx="260" cy="260" r={r} fill="none" stroke={amber} strokeOpacity="0.35" strokeWidth="1"/>)} 130 {/* crosshairs */} 131 <line x1="10" y1="260" x2="510" y2="260" stroke={amber} strokeOpacity="0.3"/> 132 <line x1="260" y1="10" x2="260" y2="510" stroke={amber} strokeOpacity="0.3"/> 133 {/* cardinal ticks */} 134 {Array.from({length:36}).map((_,i) => { 135 const a = (i*10)*Math.PI/180; 136 const r1 = 240, r2 = i%9===0 ? 256 : 248; 137 return <line key={i} x1={260+Math.cos(a)*r1} y1={260+Math.sin(a)*r1} 138 x2={260+Math.cos(a)*r2} y2={260+Math.sin(a)*r2} stroke={amber} strokeWidth={i%9===0?1.5:0.8} opacity={i%9===0?0.9:0.4}/>; 139 })} 140 {/* bearing labels */} 141 {[[260,26,"000"],[494,260,"090"],[260,504,"180"],[26,260,"270"]].map(([x,y,t]) => ( 142 <text key={t} x={x} y={y} textAnchor="middle" dominantBaseline="middle" fontFamily="IBM Plex Mono" fontSize="11" fill={amber} letterSpacing="2">{t}</text> 143 ))} 144 {/* sweep arc */} 145 <path d="M 260 260 L 500 260 A 240 240 0 0 0 430 90 Z" fill="url(#sweep)"/> 146 {/* contacts / blips */} 147 {Array.from({length:6}).map((_,i) => { 148 const rng = mulberry32(hashStr(seed+"blip"+i)); 149 const a = rng()*2*Math.PI; 150 const r = 80+rng()*150; 151 const x = 260+Math.cos(a)*r, y=260+Math.sin(a)*r; 152 return <g key={i}> 153 <circle cx={x} cy={y} r={3} fill={amber}/> 154 <circle cx={x} cy={y} r={8} fill="none" stroke={amber} strokeOpacity={0.4}/> 155 </g>; 156 })} 157 </svg> 158 159 {/* Ship at center */} 160 <div style={{position:"absolute", left:"50%", top:"50%", width:260, height:140, transform:"translate(-50%, -50%)"}}> 161 <ShipSilhouette ship={ship} stroke={amber} fill="rgba(255,190,46,0.12)" glow strokeWidth={1.4}/> 162 </div> 163 {/* reticle */} 164 <div style={{position:"absolute", left:"50%", top:"50%", width:300, height:300, transform:"translate(-50%,-50%)", pointerEvents:"none"}}> 165 <div style={{position:"absolute", inset:0, border:`1px solid ${red}`, borderRadius:"50%", opacity:0.7}}/> 166 {["tl","tr","bl","br"].map(c => { 167 const st = {position:"absolute", width:24, height:24, borderColor:red}; 168 if (c==="tl") Object.assign(st, {top:-4, left:-4, borderTop:`2px solid ${red}`, borderLeft:`2px solid ${red}`}); 169 if (c==="tr") Object.assign(st, {top:-4, right:-4, borderTop:`2px solid ${red}`, borderRight:`2px solid ${red}`}); 170 if (c==="bl") Object.assign(st, {bottom:-4, left:-4, borderBottom:`2px solid ${red}`, borderLeft:`2px solid ${red}`}); 171 if (c==="br") Object.assign(st, {bottom:-4, right:-4, borderBottom:`2px solid ${red}`, borderRight:`2px solid ${red}`}); 172 return <div key={c} style={st}/>; 173 })} 174 </div> 175 </div> 176 177 {/* status line below scope */} 178 <div style={{marginTop:12, width:"100%", padding:"10px 16px", border:`1px solid ${amber}`, display:"flex", justifyContent:"space-between", fontSize:10, letterSpacing:2, background:"rgba(0,0,0,0.5)"}}> 179 <span>SCAN · 420 m</span> 180 <span style={{color:green}}>LOCK · TIER-2</span> 181 <span>TTL 00:02:14</span> 182 <span>RNG {ship.length_m}m</span> 183 </div> 184 </div> 185 186 {/* RIGHT QUADRANTS */} 187 <div style={{padding:"20px 24px", display:"flex", flexDirection:"column", gap:16, borderLeft:`1px solid ${amberDim}66`}}> 188 189 {/* price */} 190 <div> 191 <div style={{fontSize:10, letterSpacing:3, color:amberDim}}>ASSESSMENT / LOT VALUE</div> 192 <div style={{fontFamily:"Orbitron", fontWeight:900, fontSize:44, color:paper, letterSpacing:0, marginTop:2, lineHeight:1}}> 193 ₵{formatCred(ship.price)} 194 </div> 195 <div style={{fontSize:10, letterSpacing:2, color:amberDim, marginTop:4}}>↑ 2.3% vs 30-CYC AVG · {ship.mfg.loc.toUpperCase()} EXW</div> 196 </div> 197 198 {/* Broker */} 199 <div> 200 <QuadHdr n="Q4" title="BROKER"/> 201 <SpecLine k="FIRM" v={ship.mfg.name} u=""/> 202 <SpecLine k="RATING" v="4.6 / 5.0" u="★"/> 203 <SpecLine k="CLOSINGS" v="1,240" u="units"/> 204 <SpecLine k="ESCROW" v="STELLAR BANK" u=""/> 205 </div> 206 207 {/* Terms */} 208 <div> 209 <QuadHdr n="Q5" title="TERMS"/> 210 <SpecLine k="DELIVERY" v={`${ship.delivery} cyc`} u=""/> 211 <SpecLine k="LEASE" v={`₵${formatCred(Math.round(ship.price/120))}`} u="/mo"/> 212 <SpecLine k="WARRANTY" v={ship.warranty} u=""/> 213 <SpecLine k="HULL COND" v={ship.cond} u=""/> 214 </div> 215 216 {/* Actions */} 217 <div style={{marginTop:"auto", display:"flex", flexDirection:"column", gap:8}}> 218 <button style={{ 219 padding:"16px", background:amber, color:bg, border:"none", 220 fontFamily:"Orbitron", fontWeight:900, fontSize:13, letterSpacing:4, cursor:"pointer", 221 boxShadow:`0 0 20px ${amber}88`, 222 }}>◉ ACQUIRE UNIT</button> 223 <div style={{display:"grid", gridTemplateColumns:"1fr 1fr", gap:6}}> 224 <button style={{padding:"12px", background:"transparent", color:amber, border:`1px solid ${amber}`, fontFamily:"Orbitron", fontSize:10, letterSpacing:2, cursor:"pointer"}}>BID</button> 225 <button style={{padding:"12px", background:"transparent", color:amber, border:`1px solid ${amber}`, fontFamily:"Orbitron", fontSize:10, letterSpacing:2, cursor:"pointer"}}>WATCH</button> 226 </div> 227 <div style={{padding:8, border:`1px solid ${red}`, color:red, fontSize:9, letterSpacing:2, textAlign:"center", background:"rgba(255,77,77,0.08)"}}> 228 ⚠ 3 OTHER BIDDERS ACTIVE · AUTH COOLDOWN 14s 229 </div> 230 </div> 231 </div> 232 </div> 233 234 {/* BOTTOM TICKER */} 235 <div style={{position:"absolute", bottom:0, left:0, right:0, padding:"6px 28px", borderTop:`1px solid ${amber}`, background:"rgba(0,0,0,0.6)", fontSize:10, letterSpacing:2, color:amberDim, display:"flex", gap:32, overflow:"hidden"}}> 236 <span style={{color:green}}>▲ KRV-IC +3.2%</span> 237 <span style={{color:red}}>▼ OBS-DR -1.1%</span> 238 <span>HEL-YT HOLD</span> 239 <span>VNT-CH +0.4%</span> 240 <span style={{color:green}}>▲ NXS-EX +5.6%</span> 241 <span>IOTA-SC HOLD</span> 242 <span>AUR-FR +0.9%</span> 243 </div> 244 </div> 245 ); 246 } 247 248 function QuadHdr({ n, title }) { 249 return ( 250 <div style={{display:"flex", alignItems:"baseline", gap:10, borderBottom:`1px solid #ffbe2e`, paddingBottom:4, marginBottom:6}}> 251 <span style={{fontSize:9, letterSpacing:2, color:"#8c6610"}}>{n}</span> 252 <span style={{fontFamily:"Orbitron", fontWeight:700, fontSize:12, letterSpacing:3, color:"#ffbe2e"}}>{title}</span> 253 </div> 254 ); 255 } 256 257 Object.assign(window, { Concept4_Reticle });