/ brutalist / br-fleet.jsx
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 });