// screens_crew.jsx : Crew Screen window.S = window.S || {}; (() => { const { useState } = React; const { SHIPS, FACTIONS, PORTS } = window.D; const L = window.L; const A = window.E.A; const { T, panelStyle, Bar, Pill, Btn, StatBlock, SectionTitle, EmptyState, TutorialPopup, BackButton, Tooltip, IconCheers,IconAnchor, IconCannon, IconHammer, IconChefHat, IconCompass, IconShield } = window.UI; const G = window.G; const { shouldShowTutorial, markTutorialSeen } = window.L; // Helper: returns an SVG icon element for the given crew role const getRoleIcon = (role) => { const iconProps = { size: 14, color: T.text }; switch (role) { case "deckhand": return ; case "gunner": return ; case "cook": return ; case "carpenter": return ; case "navigator": return ; case "quartermaster":return ; default: return ๐Ÿ‘ค; } }; function CrewScreen({ state, dispatch }) { const perk = L.getRepPerk(state.reputation[state.currentPort] ?? 50); if (perk.servicesBlocked) { return (
); } const open = SHIPS[state.ship.type].maxCrew - state.crew.roster.length; const [selectedMember, setSelectedMember] = React.useState(null); const [showTutorial, setShowTutorial] = React.useState(() => shouldShowTutorial(state,"crew")); return (
{showTutorial && ( { markTutorialSeen("crew", disableAll); setShowTutorial(false); }} >

Click any crew member to see their story. Over time, they earn:

  • Scars from battles and storms they survived
  • Traits that reveal hidden personalities
  • Loyalty status : veterans who've served 200+ days may pledge loyalty

Watch crew faction alignment. Attacking a crew member's home faction can make them upset and eventually desert.

)}
{/* โ”€โ”€ ROSTER PANEL โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ */}
ROSTER
60 ? T.greenBr : state.crew.morale > 30 ? T.gold : T.redBr} />
MORALE
60 ? T.greenBr : state.crew.morale > 30 ? T.gold : T.redBr} h={10} /> {state.crew.morale < 50 &&
โš  Low morale weakens combat effectiveness
} {(() => { const counts = {}; state.crew.roster.forEach(m => { counts[m.faction] = (counts[m.faction] || 0) + 1; }); return (
{Object.entries(counts).map(([faction, count]) => { const fac = FACTIONS[faction]; return ( {fac?.label || faction}: {count} ); })}
); })()}
dispatch({ type: A.RAISE_MORALE })} disabled={state.crew.roster.length === 0 || state.gold < state.crew.roster.length * 5 || state.crew.morale >= 100}> Buy Drinks ({state.crew.roster.length * 5}g) +5 Morale
{/* โ”€โ”€ HIRE PANEL โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ */}
HIRE

50g per sailor. Your {SHIPS[state.ship.type].name} holds {state.crew.max}.

{[1, 5, 10].map(n => dispatch({ type: A.HIRE_CREW, count: n })} disabled={open < n || state.gold < n * 50}>+{n} ({n * 50}g))}
{open === 0 && }
{/* โ”€โ”€ CREW DETAIL / LEGEND PANEL โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ */}
CREW DETAILS {selectedMember ? ( (() => { const visibleTags = (selectedMember.tags || []).filter(t => !t.startsWith("hidden_")); return (
{selectedMember.firstName} {selectedMember.lastName} setSelectedMember(null)}>โœ•
Faction: {FACTIONS[selectedMember.faction]?.label || selectedMember.faction}
Role: {selectedMember.role}
Days aboard: {selectedMember.daysAboard ?? 0}
{visibleTags.length > 0 && (
{visibleTags.map(tag => { const positiveTraits = ["loyal", "seasoned", "veteran"]; const isPositive = positiveTraits.includes(tag); const color = isPositive ? T.greenBr : T.redBr; const displayLabels = { loyal: "Loyal Officer", scar_battle: "Battle Scarred", scar_storm: "Storm Survivor", scar_shipwreck: "Shipwrecked", seasoned: "Seasoned", veteran: "Veteran", mutineer: "Mutineer", upset: "Upset", revealed_drunkard: "Drunkard", revealed_coward: "Coward", revealed_greedy: "Greedy", }; const label = displayLabels[tag] || tag .replace("revealed_", "") .replace(/_/g, " ") .replace(/\b\w/g, c => c.toUpperCase()); return ; })}
)}
{selectedMember.bio ? selectedMember.bio : typeof G.generateCrewBio === 'function' ? G.generateCrewBio(selectedMember, state) : `${selectedMember.firstName} is a crew member.`}
{!L.hasTag(selectedMember, "protected") ? ( { dispatch({ type: A.DISMISS_CREW, memberId: selectedMember.id }); setSelectedMember(null); }}> โœ• Dismiss ) : null}
); })() ) : (
Click on any crew member icon for details.
)}
{/* โ”€โ”€ MANIFEST โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ */}
MANIFEST
{state.crew.roster.map(member => { const visibleTags = (member.tags || []).filter(t => !t.startsWith("hidden_")); const factionColor = FACTIONS[member.faction]?.color || T.textDim; const isMutineer = L.hasTag(member, "mutineer"); const tagLabels = visibleTags.map(t => { if (t === "upset") return "Upset"; if (t === "mutineer") return "Mutineer"; if (t === "scar_shipwreck") return "Shipwreck Survivor"; if (t === "loyal") return "Loyal Officer"; if (t === "seasoned") return "Seasoned"; if (t === "veteran") return "Veteran"; if (t.startsWith("revealed_")) return t.replace("revealed_", "").replace(/_/g, " "); return t; }); const tooltipText = `${member.firstName} ${member.lastName} ยท ${member.role} ยท ${member.faction} ยท ${member.daysAboard}d aboard` + (tagLabels.length > 0 ? ` ยท ${tagLabels.join(", ")}` : ""); return (
setSelectedMember(selectedMember?.id === member.id ? null : member)} style={{ width: 34, height: 34, borderRadius: 3, display: "flex", alignItems: "center", justifyContent: "center", fontSize: T.heading2FontSize, cursor: "pointer", background: selectedMember?.id === member.id ? T.panelAlt : T.panel, border: `2px solid ${selectedMember?.id === member.id ? T.gold : T.border}`, position: "relative", }} > {/* Render role icon here */} {getRoleIcon(member.role)}
{isMutineer && ( โš  )}
); })}
); } Object.assign(window.S, { CrewScreen }); })();