br-fleet.jsx
1 // Brutalist COMMODORE BRIDGE / FLEET VIEW — 2 // operations dashboard for the hulls you own. Heavy data, naval-yard vibe. 3 4 function BRFleet({ onNav }) { 5 // Build a synthetic owned fleet 6 const owned = React.useMemo(() => [ 7 { seed:"KRV-IC-2849-AX", status:"READY", bay:"22-E", pilot:"CMDR O. VEYR", hrs:412, fuel:0.91, hull:0.98, drill:"03d", mission:"PATROL ε-7" }, 8 { seed:"VNT-CH-1044-BM", status:"DRY-DOCK", bay:"04-A", pilot:"LT S. TOREN", hrs:2188, fuel:0.44, hull:0.71, drill:"14d", mission:"REFIT · Q-drive" }, 9 { seed:"OBS-HL-4420-CP", status:"READY", bay:"17-C", pilot:"LT K. AMARA", hrs:8810, fuel:1.00, hull:0.88, drill:"01d", mission:"RESUPPLY Kepler-II" }, 10 { seed:"MRD-YT-0088-DR", status:"TRANSIT", bay:"—", pilot:"CIV R. OLEN", hrs:144, fuel:0.62, hull:0.95, drill:"—", mission:"CIVIL COURIER" }, 11 { seed:"CDN-IN-7712-ES", status:"STANDBY", bay:"33-B", pilot:"—", hrs:0, fuel:1.00, hull:1.00, drill:"—", mission:"UNCOMMISSIONED" }, 12 { seed:"KRV-CR-9901-FT", status:"FLAGGED", bay:"11-D", pilot:"CMDR T. RHEIN", hrs:1044, fuel:0.32, hull:0.55, drill:"—", mission:"§403 HOLD · audit" }, 13 ].map(s => ({ ...s, ship: makeShip(s.seed) })), []); 14 15 const totalValue = owned.reduce((a,x) => a + x.ship.price, 0); 16 const readyPct = Math.round(owned.filter(x => x.status === "READY").length / owned.length * 100); 17 18 return ( 19 <BRPage minHeight={2200}> 20 <BRNav active="fleet" cartCount={0} onNav={onNav}/> 21 22 {/* HEADER */} 23 <div style={{padding:"30px 40px 20px", borderBottom:`4px double ${BR.ink}`, display:"flex", justifyContent:"space-between", alignItems:"flex-end"}}> 24 <div> 25 <div style={{fontFamily:"IBM Plex Mono", fontSize:11, letterSpacing:3, color:BR.mute, textTransform:"uppercase"}}>FORM 22 · FLEET REGISTER · CMDR O. VEYR</div> 26 <div style={{fontFamily:"Oswald", fontWeight:900, fontSize:92, lineHeight:0.9, letterSpacing:-2, textTransform:"uppercase", marginTop:6}}>Commodore Bridge</div> 27 <div style={{fontFamily:"IBM Plex Mono", fontSize:12, color:BR.ink2, marginTop:8, letterSpacing:1.2}}> 28 7TH EXPEDITIONARY · SECTOR G-4 · CERES ORBITAL · STARDATE 2396.116.1100Z 29 </div> 30 </div> 31 <div style={{display:"flex", gap:8}}> 32 <BRChip>{owned.length} HULLS</BRChip> 33 <BRChip ok>READY {readyPct}%</BRChip> 34 <BRChip rust>1 FLAGGED</BRChip> 35 </div> 36 </div> 37 38 {/* KPI STRIP */} 39 <div style={{display:"grid", gridTemplateColumns:"repeat(6, 1fr)", borderBottom:`2px solid ${BR.ink}`, background:BR.paper2}}> 40 {[ 41 ["HULLS", owned.length], 42 ["VALUE AT REG", `₵${formatCred(totalValue)}`], 43 ["READINESS", `${readyPct}%`], 44 ["PATROL HRS", owned.reduce((a,x)=>a+x.hrs,0).toLocaleString()], 45 ["DRY-DOCK Q", owned.filter(x=>x.status==="DRY-DOCK").length], 46 ["FLAG/HOLD", owned.filter(x=>x.status==="FLAGGED").length], 47 ].map(([k,v], i) => ( 48 <div key={i} style={{padding:"16px 20px", borderRight: i<5 ? `1px solid ${BR.ink}` : "none"}}> 49 <div style={{fontFamily:"IBM Plex Mono", fontSize:10, letterSpacing:2, color:BR.mute, textTransform:"uppercase"}}>{k}</div> 50 <div style={{fontFamily:"Oswald", fontWeight:900, fontSize:32, letterSpacing:-0.5, marginTop:2}}>{v}</div> 51 </div> 52 ))} 53 </div> 54 55 {/* MAIN GRID */} 56 <div style={{display:"grid", gridTemplateColumns:"1.7fr 1fr"}}> 57 {/* LEFT — Fleet register */} 58 <div style={{borderRight:`1px solid ${BR.ink}`, padding:"22px 32px"}}> 59 <BRH n="01">Fleet Register</BRH> 60 61 {/* Table */} 62 <table style={{width:"100%", borderCollapse:"collapse", fontFamily:"IBM Plex Mono", fontSize:12}}> 63 <thead> 64 <tr style={{background:BR.paper2, borderBottom:`2px solid ${BR.ink}`}}> 65 {["Hull","Serial / Bay","Pilot","Hrs","Fuel","Hull %","Mission","Status"].map(h => ( 66 <th key={h} style={{padding:"8px 10px", textAlign:"left", fontFamily:"IBM Plex Mono", fontSize:10, letterSpacing:2, color:BR.mute, textTransform:"uppercase", fontWeight:500}}>{h}</th> 67 ))} 68 </tr> 69 </thead> 70 <tbody> 71 {owned.map((o, i) => ( 72 <tr key={i} style={{borderBottom:`1px dotted ${BR.ink}44`, background: i%2 ? BR.paper : BR.paper2}}> 73 <td style={{padding:"8px 10px"}}> 74 <div style={{width:90, height:34, border:`1px solid ${BR.ink}55`, position:"relative"}}> 75 <div style={{position:"absolute", inset:"8% 6%"}}> 76 <ShipSilhouette ship={o.ship} stroke={BR.ink} glow={false} strokeWidth={1} detail="min"/> 77 </div> 78 </div> 79 </td> 80 <td style={{padding:"8px 10px"}}> 81 <div style={{fontFamily:"Oswald", fontWeight:700, fontSize:13, letterSpacing:0.5, textTransform:"uppercase"}}>{o.ship.model}</div> 82 <div style={{color:BR.mute, fontSize:10, letterSpacing:1, marginTop:2}}>{o.seed} · BAY {o.bay}</div> 83 </td> 84 <td style={{padding:"8px 10px", fontSize:11}}>{o.pilot}</td> 85 <td style={{padding:"8px 10px"}}>{o.hrs.toLocaleString()}</td> 86 <td style={{padding:"8px 10px"}}> 87 <MiniBar v={o.fuel} color={o.fuel < 0.4 ? BR.rust : BR.ink}/> 88 </td> 89 <td style={{padding:"8px 10px"}}> 90 <MiniBar v={o.hull} color={o.hull < 0.6 ? BR.rust : o.hull < 0.85 ? "#c78f1a" : BR.green}/> 91 </td> 92 <td style={{padding:"8px 10px", fontSize:11, color:BR.ink2, maxWidth:180}}>{o.mission}</td> 93 <td style={{padding:"8px 10px"}}> 94 <StatusChip s={o.status}/> 95 </td> 96 </tr> 97 ))} 98 </tbody> 99 </table> 100 101 <div style={{height:28}}/> 102 {/* SECTOR MAP */} 103 <BRH n="02">Sector Disposition</BRH> 104 <div style={{border:`2px solid ${BR.ink}`, background:BR.paper2, position:"relative", height:340, overflow:"hidden"}}> 105 {/* grid */} 106 <svg viewBox="0 0 800 340" style={{width:"100%", height:"100%", display:"block"}}> 107 <defs> 108 <pattern id="grid" width="40" height="40" patternUnits="userSpaceOnUse"> 109 <path d="M 40 0 L 0 0 0 40" fill="none" stroke={BR.ink} strokeOpacity="0.12" strokeWidth="1"/> 110 </pattern> 111 </defs> 112 <rect width="800" height="340" fill="url(#grid)"/> 113 {/* concentric orbits */} 114 {[60,120,180].map(r => <circle key={r} cx="400" cy="170" r={r} fill="none" stroke={BR.ink} strokeOpacity="0.28" strokeDasharray="3 4"/>)} 115 {/* center star */} 116 <circle cx="400" cy="170" r="6" fill={BR.rust}/> 117 <text x="412" y="174" fontFamily="IBM Plex Mono" fontSize="10" fill={BR.ink} letterSpacing="1.5">CERES-II</text> 118 {/* hulls */} 119 {[ 120 [260,120,"KRV-IC","PATROL"], 121 [520,90, "VNT-CH","DRY-DOCK"], 122 [580,220,"OBS-HL","RESUPPLY"], 123 [140,230,"MRD-YT","TRANSIT"], 124 [670,150,"CDN-IN","STANDBY"], 125 [330,260,"KRV-CR","§403 HOLD"], 126 ].map(([x,y,code,mis],i) => ( 127 <g key={i}> 128 <line x1="400" y1="170" x2={x} y2={y} stroke={BR.ink} strokeOpacity="0.18"/> 129 <rect x={x-4} y={y-4} width="8" height="8" fill={mis==="§403 HOLD" ? BR.rust : BR.ink}/> 130 <text x={x+10} y={y-4} fontFamily="IBM Plex Mono" fontSize="9" fill={BR.ink} letterSpacing="1">{code}</text> 131 <text x={x+10} y={y+8} fontFamily="IBM Plex Mono" fontSize="8" fill={BR.mute} letterSpacing="1">{mis}</text> 132 </g> 133 ))} 134 {/* compass */} 135 <g transform="translate(40,40)"> 136 <circle r="18" fill="none" stroke={BR.ink} strokeWidth="1.5"/> 137 <text y="-24" fontFamily="Oswald" fontSize="10" fontWeight="700" fill={BR.ink} textAnchor="middle">G-4</text> 138 <line x1="0" y1="-14" x2="0" y2="14" stroke={BR.ink}/> 139 <line x1="-14" y1="0" x2="14" y2="0" stroke={BR.ink}/> 140 </g> 141 </svg> 142 <div style={{position:"absolute", bottom:10, right:12, fontFamily:"IBM Plex Mono", fontSize:9, letterSpacing:2, color:BR.mute, textTransform:"uppercase"}}>SECTOR G-4 · 6 HULLS · LIVE</div> 143 </div> 144 </div> 145 146 {/* RIGHT — Orders, readiness, alerts */} 147 <div style={{padding:"22px 28px", background:BR.paper2, display:"flex", flexDirection:"column"}}> 148 <BRH n="03">Flag Orders</BRH> 149 <div style={{fontFamily:"IBM Plex Mono", fontSize:11, color:BR.mute, letterSpacing:1.5, marginBottom:8, textTransform:"uppercase"}}>ISSUED 2396.116.0900Z · PRIORITY QUEUE</div> 150 {[ 151 ["URGENT", "KRV-CR-9901 to §403 hold — respond within 48h", "24h LEFT"], 152 ["HIGH", "VNT-CH-1044 refit delayed — confirm slip date", "2d"], 153 ["MED", "OBS-HL-4420 resupply — sign off BOM", "4d"], 154 ["LOW", "Annual drill — schedule for Q-crew", "14d"], 155 ].map(([p, t, eta], i) => ( 156 <div key={i} style={{display:"flex", gap:10, padding:"12px 14px", border:`2px solid ${BR.ink}`, background:BR.paper, marginBottom:8}}> 157 <div style={{width:44, textAlign:"center"}}> 158 <div style={{fontFamily:"Oswald", fontWeight:900, fontSize:11, letterSpacing:2, color: p==="URGENT" ? BR.rust : p==="HIGH" ? "#c78f1a" : BR.ink}}>{p}</div> 159 </div> 160 <div style={{flex:1}}> 161 <div style={{fontFamily:"IBM Plex Sans", fontSize:12, color:BR.ink}}>{t}</div> 162 <div style={{fontFamily:"IBM Plex Mono", fontSize:10, color:BR.mute, letterSpacing:1.5, marginTop:2, textTransform:"uppercase"}}>ACK {eta}</div> 163 </div> 164 </div> 165 ))} 166 167 <div style={{height:22}}/> 168 <BRH n="04">Pilot Roster</BRH> 169 {[ 170 ["CMDR O. VEYR", "Tier-3 · 8,800h", BR.green], 171 ["LT S. TOREN", "Tier-2 · 4,200h", BR.green], 172 ["LT K. AMARA", "Tier-2 · 3,900h", BR.green], 173 ["CIV R. OLEN", "Civ · 1,100h", "#c78f1a"], 174 ["— UNASSIGNED", "Apply via desk", BR.mute], 175 ].map(([n, rank, dot], i) => ( 176 <div key={i} style={{display:"flex", gap:10, alignItems:"center", padding:"8px 0", borderBottom:`1px dotted ${BR.ink}33`}}> 177 <div style={{width:10, height:10, background:dot, borderRadius:"50%"}}/> 178 <div style={{flex:1, fontFamily:"Oswald", fontWeight:700, fontSize:12, letterSpacing:1.5, textTransform:"uppercase"}}>{n}</div> 179 <div style={{fontFamily:"IBM Plex Mono", fontSize:10, color:BR.mute, letterSpacing:1}}>{rank}</div> 180 </div> 181 ))} 182 183 <div style={{height:22}}/> 184 <BRH n="05">Quick Orders</BRH> 185 <div style={{display:"grid", gridTemplateColumns:"1fr 1fr", gap:8}}> 186 {[ 187 ["DISPATCH", BR.ink, BR.paper], 188 ["RECALL", BR.paper, BR.ink], 189 ["DRY-DOCK", BR.paper, BR.ink], 190 ["RE-CREW", BR.paper, BR.ink], 191 ["BROKER", BR.paper, BR.ink], 192 ["DECOMM", BR.rust, BR.paper], 193 ].map(([l, bg, fg], i) => ( 194 <button key={i} onClick={() => onNav(l === "BROKER" ? "broker" : l === "DISPATCH" ? "fleet" : "fleet")} style={{ 195 padding:"14px 0", background:bg, color:fg, border:`2px solid ${BR.ink}`, 196 fontFamily:"Oswald", fontWeight:700, fontSize:12, letterSpacing:3, cursor:"pointer" 197 }}>{l}</button> 198 ))} 199 </div> 200 </div> 201 </div> 202 203 <BRFooter/> 204 </BRPage> 205 ); 206 } 207 208 function MiniBar({ v, color }) { 209 return ( 210 <div style={{display:"flex", alignItems:"center", gap:8}}> 211 <div style={{width:60, height:8, background:BR.ink + "22", border:`1px solid ${BR.ink}55`, position:"relative"}}> 212 <div style={{position:"absolute", inset:0, width:`${v*100}%`, background:color}}/> 213 </div> 214 <span style={{fontFamily:"IBM Plex Mono", fontSize:10, color:BR.ink}}>{Math.round(v*100)}%</span> 215 </div> 216 ); 217 } 218 219 function StatusChip({ s }) { 220 const map = { 221 READY: { c:BR.green, bg:"rgba(45,107,58,0.12)" }, 222 "DRY-DOCK":{ c:"#8a6a1a", bg:"rgba(199,143,26,0.12)" }, 223 TRANSIT: { c:BR.ink, bg:BR.paper2 }, 224 STANDBY: { c:BR.mute, bg:"transparent" }, 225 FLAGGED: { c:BR.rust, bg:"rgba(194,66,26,0.12)" }, 226 }; 227 const {c, bg} = map[s] || map.STANDBY; 228 return ( 229 <span style={{display:"inline-block", padding:"3px 8px", background:bg, color:c, border:`1.5px solid ${c}`, fontFamily:"Oswald", fontWeight:700, fontSize:10, letterSpacing:2, textTransform:"uppercase"}}>{s}</span> 230 ); 231 } 232 233 Object.assign(window, { BRFleet });