968 lines
25 KiB
TypeScript
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',
|
|
},
|
|
});
|