Files
blb-throne-of-the-bone-king/src/screens/ToolkitScreen.tsx
T

968 lines
25 KiB
TypeScript

import { Pressable, StyleSheet, Text, View } from 'react-native';
import { useSafeAreaInsets } from 'react-native-safe-area-context';
import { buildVersionLabel } from '../config/buildInfo';
import type { CreditsContent } from '../data';
import {
routeDefinitions,
toolkitRoutes,
type RouteName,
type ToolkitRouteName,
} from '../navigation/routes';
import {
useToolkitContentStore,
useToolkitSocketStore,
type ToolkitContentStatus,
type ToolkitSocketStatus,
} from '../state';
import { defaultTextStyle, displayTextStyle } from '../styles/typography';
type ToolkitScreenProps = Readonly<{
goHome: () => void;
navigate: (routeName: RouteName) => void;
routeName: RouteName;
}>;
type ToolkitSectionContent = Readonly<{
futureFields: string[];
liableToChange: string;
purpose: string;
sources: string[];
}>;
const toolkitOverview: ToolkitSectionContent = {
futureFields: [
'section purpose map',
'wiki source references',
'future forms',
'tables',
'previews',
],
liableToChange:
'The Toolkit should follow whichever gameplay loops prove most volatile during early design.',
purpose:
'Select a toolkit area to inspect the systems most likely to change while the throne room loop stabilizes.',
sources: ['Home', 'Gameplay', 'Technical Design Specs'],
};
const toolkitSections: Record<ToolkitRouteName, ToolkitSectionContent> = {
toolkitAudio: {
futureFields: [
'throne room theme',
'world map theme',
'petitioner stings',
'BLB voice direction',
'comedy SFX rules',
],
liableToChange:
'Audio identity will shift as petitioner types, faction tone, and readable decision pacing evolve.',
purpose:
'Preview and catalog sound identity tied to throne room, map, petitioner, BLB voice, and comedy contexts.',
sources: ['Music', 'Gameplay', 'References'],
},
toolkitCredits: {
futureFields: [
'contributor names',
'roles',
'asset attribution',
'font licenses',
'music credits',
'special thanks',
],
liableToChange:
'Credits will change whenever art, audio, fonts, tooling, collaborators, or external inspirations are added.',
purpose:
'Track contributor credits, asset attribution, licenses, and acknowledgement copy for the game and toolkit.',
sources: ['Credits', 'References', 'Home'],
},
toolkitEndings: {
futureFields: [
'resource collapse',
'stability collapse',
'settlement collapse count',
'faction coup',
'ending summaries',
],
liableToChange:
'Failure thresholds and ending categories will change as the run length and kingdom identity model settle.',
purpose:
'Tune collapse states and outcome summaries so failure and victory reflect accumulated rulings.',
sources: ['Story', 'Technical Design Specs'],
},
toolkitFactions: {
futureFields: [
'faction ID',
'trust',
'power',
'agenda',
'likes',
'dislikes',
'coup risks',
],
liableToChange:
'Faction trust and power are expected to move as petitioner pressure and map consequence rules mature.',
purpose:
'Tune faction trust, power, agenda pressure, and the events attached to faction relationships.',
sources: ['Lore', 'Gameplay', 'Technical Design Specs'],
},
toolkitFlags: {
futureFields: [
'active flags',
'seen petitioners',
'rulings',
'daily summaries',
'unlocked events',
'chain triggers',
],
liableToChange:
'Delayed consequences and recurring petitioner chains depend on flags that will expand quickly.',
purpose:
'Debug delayed consequences, recurring petitioner chains, run history, and unlocked future events.',
sources: ['Gameplay', 'Story', 'Technical Design Specs'],
},
toolkitLore: {
futureFields: [
'settlements',
'factions',
'BLB origin variants',
'BLB limits',
'canon rules',
'joke rules',
],
liableToChange:
'Canon needs room to absorb absurd jokes while still preserving consequence and internal logic.',
purpose:
'Reference changing canon, world concepts, BLB rules, faction needs, and settlement identity.',
sources: ['Lore', 'Story', 'References'],
},
toolkitMap: {
futureFields: [
'settlements',
'owner',
'faction influence',
'production',
'stability',
'need',
'status effects',
],
liableToChange:
'The map is the consequence layer, so settlement state will change as ruling effects become visible.',
purpose:
'Support settlement and world-map iteration as throne room choices ripple into territory state.',
sources: ['Gameplay', 'Lore', 'Technical Design Specs'],
},
toolkitOpening: {
futureFields: [
'opening premise',
'first day setup',
'initial resources',
'first petitioners',
'first map action',
],
liableToChange:
'The first-run sequence will change as the tutorial learns how much of the loop to teach up front.',
purpose:
'Preview and sequence the first-run premise, Act I intro beats, and early tutorial framing.',
sources: ['Story', 'Gameplay', 'Technical Design Specs'],
},
toolkitPetitioners: {
futureFields: [
'petitioner ID',
'name',
'type',
'faction',
'settlement',
'tags',
'day range',
'weight',
'flags',
],
liableToChange:
'Petitioners are the primary content loop and will change as cases, factions, and map pressure evolve.',
purpose:
'Inspect and eventually author throne-room cases, the main variable content loop of each day.',
sources: ['Gameplay', 'Story', 'Technical Design Specs'],
},
toolkitReferences: {
futureFields: [
'game references',
'visual pillars',
'tone references',
'BLB asset links',
'repo links',
'build notes',
],
liableToChange:
'Reference material will grow as art, audio, design, and implementation sources become concrete.',
purpose:
'Keep design inspirations, source links, asset references, and build notes close to implementation.',
sources: ['References', 'Home'],
},
toolkitResources: {
futureFields: [
'current totals',
'daily production',
'upkeep',
'conversion rules',
'thresholds',
'warnings',
'collapse triggers',
],
liableToChange:
'The Bones, Minions, and Milk triangle is the core economy and will need repeated tuning.',
purpose:
'Tune the core Bones, Minions, and Milk economy plus production, upkeep, and collapse rules.',
sources: ['Gameplay', 'Technical Design Specs'],
},
toolkitRulings: {
futureFields: [
'label',
'tone',
'costs',
'resource effects',
'settlement effects',
'faction effects',
'future weights',
'result text',
],
liableToChange:
'Ruling effects need to be tuned separately from flavor text as consequence balance changes.',
purpose:
'Inspect and eventually tune choice outcomes independently from petitioner presentation.',
sources: ['Gameplay', 'Technical Design Specs'],
},
toolkitSeed: {
futureFields: [
'eligible pool',
'filters',
'weights',
'shortage modifiers',
'trust modifiers',
'selected IDs',
'day history',
],
liableToChange:
'Daily selection rules will change as shortages, unstable settlements, and faction pressure matter more.',
purpose:
'Debug why a day produced specific petitioners from deterministic seed and world-state weighting.',
sources: ['Gameplay', 'Technical Design Specs'],
},
toolkitStory: {
futureFields: [
'act definitions',
'recurring petitioner arcs',
'campaign pressure',
'kingdom signals',
'endgame hooks',
],
liableToChange:
'Story pressure should remain sandbox-first, so arcs will shift around emergent kingdom state.',
purpose:
'Organize Act I-IV pressure, recurring petitioner arcs, and long-term kingdom identity.',
sources: ['Story', 'Lore', 'Gameplay'],
},
};
const toolkitIcons: Record<ToolkitRouteName, string> = {
toolkitAudio: '♪',
toolkitCredits: '☆',
toolkitEndings: '!',
toolkitFactions: '◇',
toolkitFlags: '#',
toolkitLore: '✦',
toolkitMap: '⌖',
toolkitOpening: '▶',
toolkitPetitioners: '◉',
toolkitReferences: '※',
toolkitResources: '▦',
toolkitRulings: '§',
toolkitSeed: '◎',
toolkitStory: '★',
};
const socketStatusCopy: Record<ToolkitSocketStatus, string> = {
connected: 'Connected',
connecting: 'Connecting',
disconnected: 'Disconnected',
};
const creditsStatusCopy: Record<ToolkitContentStatus, string> = {
error: 'Using fallback credits',
idle: 'Bundled fallback credits',
loading: 'Loading credits',
ready: 'Live credits synced',
saving: 'Saving credits',
};
export function ToolkitScreen({
goHome,
navigate,
routeName,
}: ToolkitScreenProps) {
const safeAreaInsets = useSafeAreaInsets();
const activeRoute = routeDefinitions[routeName];
const credits = useToolkitContentStore(state => state.credits);
const creditsError = useToolkitContentStore(state => state.error);
const creditsRevision = useToolkitContentStore(state => state.revision);
const creditsStatus = useToolkitContentStore(state => state.status);
const socketStatus = useToolkitSocketStore(state => state.status);
const toolkitContent = getToolkitContent(routeName);
const isCreditsRoute = routeName === 'toolkitCredits';
return (
<View
style={[
styles.container,
{
paddingBottom: safeAreaInsets.bottom + 12,
paddingLeft: safeAreaInsets.left + 12,
paddingRight: safeAreaInsets.right + 12,
paddingTop: safeAreaInsets.top + 12,
},
]}>
<View style={styles.toolbar} testID="toolkit-toolbar">
<View style={styles.toolbarGroup} testID="toolkit-option-group">
{toolkitRoutes.map(toolkitRouteName => (
<ToolkitToolbarButton
key={toolkitRouteName}
active={toolkitRouteName === routeName}
icon={toolkitIcons[toolkitRouteName]}
label={routeDefinitions[toolkitRouteName].label}
onPress={() => navigate(toolkitRouteName)}
testID={`toolkit-${toolkitRouteName}`}
/>
))}
</View>
<View style={styles.homeSlot} testID="toolkit-home-slot">
<ToolkitToolbarButton
accessibilityLabel="Home"
icon="⌂"
iconOnly
label="Home"
onPress={goHome}
testID="toolkit-home"
/>
</View>
</View>
<View style={styles.contentPanel}>
<Text style={styles.eyebrow}>Bone Lord Bob</Text>
<Text style={styles.title}>{activeRoute.title}</Text>
<Text style={styles.readOnlyLabel}>Read-only planning panel</Text>
{isCreditsRoute ? (
<ToolkitCreditsCard
credits={credits}
error={creditsError}
revision={creditsRevision}
socketStatus={socketStatus}
status={creditsStatus}
/>
) : (
<>
<View style={styles.detailGrid}>
<ToolkitDetail
label="Purpose"
testID="toolkit-purpose"
value={toolkitContent.purpose}
/>
<ToolkitDetail
label="Liable to change because"
testID="toolkit-change-note"
value={toolkitContent.liableToChange}
/>
</View>
<View style={styles.sourcePanel} testID="toolkit-sources">
<Text style={styles.detailLabel}>Source wiki pages</Text>
<View style={styles.sourceRow}>
{toolkitContent.sources.map(source => (
<Text key={source} style={styles.sourcePill}>
{source}
</Text>
))}
</View>
</View>
<ToolkitDetail
label="Anticipated future fields / entities"
testID="toolkit-future-fields"
value={toolkitContent.futureFields.join(', ')}
/>
<View
style={styles.placeholderPanel}
testID="toolkit-future-placeholder">
<Text style={styles.placeholderTitle}>Future authoring area</Text>
<Text style={styles.placeholderCopy}>
Forms, tables, previews, and schema-backed validation will mount
here after the JSON and Markdown content formats stabilize.
</Text>
</View>
</>
)}
<ToolkitSocketIndicator status={socketStatus} />
</View>
</View>
);
}
function getToolkitContent(routeName: RouteName) {
if ((toolkitRoutes as readonly RouteName[]).includes(routeName)) {
return toolkitSections[routeName as ToolkitRouteName];
}
return toolkitOverview;
}
type ToolkitCreditsCardProps = Readonly<{
credits: CreditsContent;
error: string | null;
revision: string | null;
socketStatus: ToolkitSocketStatus;
status: ToolkitContentStatus;
}>;
function ToolkitCreditsCard({
credits,
error,
revision,
socketStatus,
status,
}: ToolkitCreditsCardProps) {
const syncCopy =
socketStatus === 'connected'
? creditsStatusCopy[status]
: 'Bundled fallback credits';
const revisionCopy = revision == null ? 'No live revision' : `Revision ${revision}`;
return (
<View style={styles.creditsCard} testID="toolkit-credits-card">
<View style={styles.creditsHero}>
<View style={styles.creditsLogoMark} testID="toolkit-credits-logo-mark">
<Text style={styles.creditsLogoInitials}>BLB</Text>
</View>
<View style={styles.creditsHeroCopy}>
<Text style={styles.creditsTitle}>{credits.mainLogo.title}</Text>
<Text style={styles.creditsSubtitle}>{credits.mainLogo.subtitle}</Text>
</View>
<View style={styles.creditsBuildBadge} testID="toolkit-credits-build">
<Text style={styles.creditsBuildLabel}>{buildVersionLabel}</Text>
<Text style={styles.creditsSyncLabel}>{syncCopy}</Text>
<Text style={styles.creditsRevisionLabel}>{revisionCopy}</Text>
</View>
</View>
{error == null ? null : (
<Text style={styles.creditsError} testID="toolkit-credits-error">
{error}
</Text>
)}
<View style={styles.creditsGrid}>
<View style={styles.creditsSection} testID="toolkit-credits-contributors">
<Text style={styles.creditsSectionTitle}>Contributors</Text>
{credits.contributors.map(contributor => (
<View key={`${contributor.name}-${contributor.role}`}>
<Text style={styles.creditsItemTitle}>{contributor.name}</Text>
<Text style={styles.creditsItemMeta}>{contributor.role}</Text>
{contributor.credit == null ? null : (
<Text style={styles.creditsItemCopy}>{contributor.credit}</Text>
)}
</View>
))}
</View>
<View style={styles.creditsSection} testID="toolkit-credits-technologies">
<Text style={styles.creditsSectionTitle}>Technologies</Text>
<View style={styles.creditsPillGrid}>
{credits.technologies.map(technology => (
<View key={technology.name} style={styles.creditsTechPill}>
<Text style={styles.creditsTechName}>{technology.name}</Text>
{technology.role == null ? null : (
<Text style={styles.creditsTechRole}>{technology.role}</Text>
)}
</View>
))}
</View>
</View>
</View>
<View style={styles.creditsThanks} testID="toolkit-credits-thanks">
<Text style={styles.creditsSectionTitle}>Special Thanks</Text>
<View style={styles.creditsThanksRow}>
{credits.specialThanks.map(thanks => (
<Text key={thanks} style={styles.creditsThanksItem}>
{thanks}
</Text>
))}
</View>
</View>
</View>
);
}
type ToolkitDetailProps = Readonly<{
label: string;
testID: string;
value: string;
}>;
function ToolkitDetail({ label, testID, value }: ToolkitDetailProps) {
return (
<View style={styles.detailPanel} testID={testID}>
<Text style={styles.detailLabel}>{label}</Text>
<Text style={styles.detailValue}>{value}</Text>
</View>
);
}
type ToolkitSocketIndicatorProps = Readonly<{
status: ToolkitSocketStatus;
}>;
function ToolkitSocketIndicator({ status }: ToolkitSocketIndicatorProps) {
return (
<View
accessibilityLabel={`Toolkit socket ${socketStatusCopy[status]}`}
style={styles.socketIndicator}
testID="toolkit-socket-indicator">
<View
style={[styles.socketDot, socketDotStyles[status]]}
testID="toolkit-socket-dot"
/>
<Text style={styles.socketLabel}>{socketStatusCopy[status]}</Text>
</View>
);
}
type ToolkitToolbarButtonProps = Readonly<{
active?: boolean;
accessibilityLabel?: string;
icon: string;
iconOnly?: boolean;
label: string;
onPress: () => void;
testID: string;
}>;
function ToolkitToolbarButton({
active = false,
accessibilityLabel,
icon,
iconOnly = false,
label,
onPress,
testID,
}: ToolkitToolbarButtonProps) {
return (
<Pressable
accessibilityLabel={accessibilityLabel ?? label}
accessibilityRole="button"
onPress={onPress}
style={({ pressed }) => [
styles.toolbarButton,
iconOnly ? styles.toolbarButtonIconOnly : null,
active ? styles.toolbarButtonActive : null,
pressed ? styles.toolbarButtonPressed : null,
]}
testID={testID}>
<View style={styles.toolbarButtonContent}>
<Text
style={[
styles.toolbarButtonIcon,
active ? styles.toolbarButtonLabelActive : null,
]}
testID={`${testID}-icon`}>
{icon}
</Text>
{iconOnly ? null : (
<Text
style={[
styles.toolbarButtonLabel,
active ? styles.toolbarButtonLabelActive : null,
]}
testID={`${testID}-label`}>
{label}
</Text>
)}
</View>
</Pressable>
);
}
const styles = StyleSheet.create({
container: {
alignItems: 'stretch',
backgroundColor: '#111315',
flex: 1,
gap: 8,
},
contentPanel: {
backgroundColor: '#191b1f',
borderColor: '#37342d',
borderRadius: 8,
borderWidth: 1,
flex: 1,
justifyContent: 'flex-start',
padding: 14,
position: 'relative',
},
creditsBuildBadge: {
alignItems: 'flex-end',
backgroundColor: '#24272b',
borderColor: '#4d4432',
borderRadius: 6,
borderWidth: 1,
marginLeft: 'auto',
paddingHorizontal: 10,
paddingVertical: 8,
},
creditsBuildLabel: {
...displayTextStyle,
color: '#f5d98d',
fontSize: 12,
letterSpacing: 0,
},
creditsCard: {
backgroundColor: '#131518',
borderColor: '#4d4432',
borderRadius: 8,
borderWidth: 1,
flex: 1,
gap: 10,
padding: 12,
},
creditsError: {
...defaultTextStyle,
color: '#d85a4d',
fontSize: 11,
lineHeight: 14,
},
creditsGrid: {
flex: 1,
flexDirection: 'row',
gap: 10,
},
creditsHero: {
alignItems: 'center',
flexDirection: 'row',
gap: 12,
},
creditsHeroCopy: {
flex: 1,
minWidth: 0,
},
creditsItemCopy: {
...defaultTextStyle,
color: '#80796c',
fontSize: 11,
lineHeight: 14,
},
creditsItemMeta: {
...defaultTextStyle,
color: '#d6b25e',
fontSize: 11,
fontWeight: '700',
letterSpacing: 0,
lineHeight: 14,
textTransform: 'uppercase',
},
creditsItemTitle: {
...displayTextStyle,
color: '#f6f1e7',
fontSize: 14,
letterSpacing: 0,
},
creditsLogoInitials: {
...displayTextStyle,
color: '#f5d98d',
fontSize: 18,
letterSpacing: 0,
},
creditsLogoMark: {
alignItems: 'center',
backgroundColor: '#3a2918',
borderColor: '#d6b25e',
borderRadius: 8,
borderWidth: 1,
height: 48,
justifyContent: 'center',
width: 58,
},
creditsRevisionLabel: {
...defaultTextStyle,
color: '#80796c',
fontSize: 10,
lineHeight: 13,
},
creditsSection: {
backgroundColor: '#191b1f',
borderColor: '#2d2f32',
borderRadius: 6,
borderWidth: 1,
flex: 1,
gap: 7,
padding: 10,
},
creditsSectionTitle: {
...defaultTextStyle,
color: '#d6b25e',
fontSize: 10,
fontWeight: '800',
letterSpacing: 0,
textTransform: 'uppercase',
},
creditsSubtitle: {
...defaultTextStyle,
color: '#c9c1b3',
fontSize: 12,
lineHeight: 16,
},
creditsSyncLabel: {
...defaultTextStyle,
color: '#c9c1b3',
fontSize: 10,
lineHeight: 13,
textTransform: 'uppercase',
},
creditsTechName: {
...displayTextStyle,
color: '#f6f1e7',
fontSize: 11,
letterSpacing: 0,
},
creditsTechPill: {
backgroundColor: '#24272b',
borderColor: '#4d4432',
borderRadius: 4,
borderWidth: 1,
paddingHorizontal: 8,
paddingVertical: 5,
},
creditsTechRole: {
...defaultTextStyle,
color: '#c9c1b3',
fontSize: 10,
lineHeight: 13,
},
creditsThanks: {
backgroundColor: '#191b1f',
borderColor: '#2d2f32',
borderRadius: 6,
borderWidth: 1,
gap: 7,
padding: 10,
},
creditsThanksItem: {
...defaultTextStyle,
color: '#c9c1b3',
flex: 1,
fontSize: 11,
lineHeight: 14,
minWidth: 150,
},
creditsThanksRow: {
flexDirection: 'row',
flexWrap: 'wrap',
gap: 8,
},
creditsTitle: {
...displayTextStyle,
color: '#f6f1e7',
fontSize: 24,
letterSpacing: 0,
lineHeight: 28,
},
detailGrid: {
flexDirection: 'row',
gap: 10,
marginBottom: 10,
},
detailLabel: {
...defaultTextStyle,
color: '#d6b25e',
fontSize: 10,
fontWeight: '800',
letterSpacing: 0,
marginBottom: 5,
textTransform: 'uppercase',
},
detailPanel: {
backgroundColor: '#131518',
borderColor: '#2d2f32',
borderRadius: 6,
borderWidth: 1,
flex: 1,
padding: 10,
},
detailValue: {
...defaultTextStyle,
color: '#c9c1b3',
fontSize: 12,
lineHeight: 17,
},
eyebrow: {
...defaultTextStyle,
color: '#d6b25e',
fontSize: 11,
fontWeight: '700',
letterSpacing: 0,
marginBottom: 10,
textTransform: 'uppercase',
},
title: {
...displayTextStyle,
color: '#f6f1e7',
fontSize: 30,
letterSpacing: 0,
lineHeight: 34,
marginBottom: 12,
},
placeholderCopy: {
...defaultTextStyle,
color: '#c9c1b3',
fontSize: 12,
lineHeight: 17,
},
placeholderPanel: {
backgroundColor: '#24272b',
borderColor: '#4d4432',
borderRadius: 6,
borderStyle: 'dashed',
borderWidth: 1,
marginTop: 10,
padding: 10,
},
placeholderTitle: {
...displayTextStyle,
color: '#f6f1e7',
fontSize: 12,
letterSpacing: 0,
marginBottom: 4,
},
readOnlyLabel: {
...defaultTextStyle,
color: '#80796c',
fontSize: 11,
fontWeight: '800',
letterSpacing: 0,
marginBottom: 10,
textTransform: 'uppercase',
},
sourcePanel: {
backgroundColor: '#131518',
borderColor: '#2d2f32',
borderRadius: 6,
borderWidth: 1,
marginBottom: 10,
padding: 10,
},
sourcePill: {
...defaultTextStyle,
backgroundColor: '#2a261d',
borderColor: '#4d4432',
borderRadius: 4,
borderWidth: 1,
color: '#f5d98d',
fontSize: 11,
fontWeight: '800',
letterSpacing: 0,
paddingHorizontal: 7,
paddingVertical: 4,
},
sourceRow: {
flexDirection: 'row',
flexWrap: 'wrap',
gap: 6,
},
socketDot: {
borderRadius: 5,
height: 10,
width: 10,
},
socketIndicator: {
alignItems: 'center',
bottom: 12,
flexDirection: 'row',
gap: 7,
position: 'absolute',
right: 12,
},
socketLabel: {
...defaultTextStyle,
color: '#c9c1b3',
fontSize: 11,
fontWeight: '700',
letterSpacing: 0,
textTransform: 'uppercase',
},
homeSlot: {
marginLeft: 'auto',
},
toolbar: {
alignItems: 'stretch',
flexDirection: 'row',
minHeight: 34,
},
toolbarButton: {
alignItems: 'center',
backgroundColor: '#24272b',
borderColor: '#4d4432',
borderRadius: 0,
borderWidth: 1,
justifyContent: 'center',
minHeight: 34,
paddingHorizontal: 8,
paddingVertical: 6,
},
toolbarButtonActive: {
backgroundColor: '#3a3120',
borderColor: '#d6b25e',
},
toolbarButtonContent: {
alignItems: 'center',
flexDirection: 'row',
gap: 4,
justifyContent: 'center',
},
toolbarButtonIcon: {
color: '#f5d98d',
fontSize: 12,
fontWeight: '800',
letterSpacing: 0,
lineHeight: 14,
textAlign: 'center',
},
toolbarButtonIconOnly: {
minWidth: 38,
paddingHorizontal: 9,
},
toolbarButtonLabel: {
...displayTextStyle,
color: '#f6f1e7',
fontSize: 10,
letterSpacing: 0,
textAlign: 'center',
},
toolbarButtonLabelActive: {
color: '#f5d98d',
},
toolbarButtonPressed: {
backgroundColor: '#33302a',
},
toolbarGroup: {
flexDirection: 'row',
flexShrink: 1,
flexWrap: 'wrap',
},
});
const socketDotStyles = StyleSheet.create({
connected: {
backgroundColor: '#3fbf6f',
},
connecting: {
backgroundColor: '#d6b25e',
},
disconnected: {
backgroundColor: '#d85a4d',
},
});