@buoy-gg/core 7.0.26 → 7.0.27

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (40) hide show
  1. package/bin/buoy.js +6 -0
  2. package/lib/commonjs/floatingMenu/DevToolsSettingsModal.js +524 -1520
  3. package/lib/commonjs/floatingMenu/DevToolsSettingsModal.web.js +541 -566
  4. package/lib/commonjs/floatingMenu/FloatingDevTools.web.js +5 -1
  5. package/lib/commonjs/floatingMenu/ToolHost.js +2 -1
  6. package/lib/commonjs/floatingMenu/dial/DialDevTools.js +50 -21
  7. package/lib/commonjs/floatingMenu/dial/DialHyperspace.js +177 -0
  8. package/lib/commonjs/floatingMenu/dial/DialSnow.js +92 -0
  9. package/lib/commonjs/version.js +1 -1
  10. package/lib/module/floatingMenu/DevToolsSettingsModal.js +525 -1524
  11. package/lib/module/floatingMenu/DevToolsSettingsModal.web.js +542 -569
  12. package/lib/module/floatingMenu/FloatingDevTools.web.js +5 -1
  13. package/lib/module/floatingMenu/ToolHost.js +2 -1
  14. package/lib/module/floatingMenu/dial/DialDevTools.js +52 -23
  15. package/lib/module/floatingMenu/dial/DialHyperspace.js +173 -0
  16. package/lib/module/floatingMenu/dial/DialSnow.js +90 -0
  17. package/lib/module/version.js +1 -1
  18. package/lib/typescript/commonjs/floatingMenu/DevToolsSettingsModal.d.ts +19 -13
  19. package/lib/typescript/commonjs/floatingMenu/DevToolsSettingsModal.d.ts.map +1 -1
  20. package/lib/typescript/commonjs/floatingMenu/DevToolsSettingsModal.web.d.ts +3 -3
  21. package/lib/typescript/commonjs/floatingMenu/DevToolsSettingsModal.web.d.ts.map +1 -1
  22. package/lib/typescript/commonjs/floatingMenu/FloatingDevTools.web.d.ts.map +1 -1
  23. package/lib/typescript/commonjs/floatingMenu/dial/DialDevTools.d.ts.map +1 -1
  24. package/lib/typescript/commonjs/floatingMenu/dial/DialHyperspace.d.ts +24 -0
  25. package/lib/typescript/commonjs/floatingMenu/dial/DialHyperspace.d.ts.map +1 -0
  26. package/lib/typescript/commonjs/floatingMenu/dial/DialSnow.d.ts +23 -0
  27. package/lib/typescript/commonjs/floatingMenu/dial/DialSnow.d.ts.map +1 -0
  28. package/lib/typescript/commonjs/version.d.ts +1 -1
  29. package/lib/typescript/module/floatingMenu/DevToolsSettingsModal.d.ts +19 -13
  30. package/lib/typescript/module/floatingMenu/DevToolsSettingsModal.d.ts.map +1 -1
  31. package/lib/typescript/module/floatingMenu/DevToolsSettingsModal.web.d.ts +3 -3
  32. package/lib/typescript/module/floatingMenu/DevToolsSettingsModal.web.d.ts.map +1 -1
  33. package/lib/typescript/module/floatingMenu/FloatingDevTools.web.d.ts.map +1 -1
  34. package/lib/typescript/module/floatingMenu/dial/DialDevTools.d.ts.map +1 -1
  35. package/lib/typescript/module/floatingMenu/dial/DialHyperspace.d.ts +24 -0
  36. package/lib/typescript/module/floatingMenu/dial/DialHyperspace.d.ts.map +1 -0
  37. package/lib/typescript/module/floatingMenu/dial/DialSnow.d.ts +23 -0
  38. package/lib/typescript/module/floatingMenu/dial/DialSnow.d.ts.map +1 -0
  39. package/lib/typescript/module/version.d.ts +1 -1
  40. package/package.json +5 -5
@@ -4,14 +4,12 @@ import { useState, useEffect, useCallback, useMemo } from "react";
4
4
  import { View, Text, StyleSheet, ScrollView, TouchableOpacity, Dimensions, Platform } from "react-native";
5
5
  import { settingsBus } from "./settingsBus.js";
6
6
  import { useDesktopSyncStatus } from "./useDesktopSyncStatus.js";
7
- import { SentryBugIcon, WifiCircuitIcon, ImageOverlayIcon, RenderCountIcon, Globe, Info, Layers, ChevronRightIcon, ChevronDown, getStorageBackendType, persistentStorage, Database, Trash2, CheckCircle2, AlertTriangle, Zap, FileText, HardDrive, Copy, FileCode, copyToClipboard, LicenseEntryModal, SectionHeader } from "@buoy-gg/shared-ui";
7
+ import { SentryBugIcon, WifiCircuitIcon, ImageOverlayIcon, RenderCountIcon, Info, Layers, ChevronRightIcon, ChevronDown, getStorageBackendType, persistentStorage, Trash2, CheckCircle2, LicenseEntryModal, BackgroundSwitcher, night, NightCard, NightRows, NightSectionLabel, NightFootnote, NightSwitch, NightButton, NightSegmented, NightBadge, NightSeparator } from "@buoy-gg/shared-ui";
8
8
  import { EnvIcon, StorageIcon, RoutesIcon, NetworkIcon, QueryIcon, HighlighterIcon, ReduxIcon, EventsIcon } from "@buoy-gg/floating-tools-core";
9
9
  import { useDefaultConfig } from "./DefaultConfigContext.js";
10
+ import { renderAppIcon } from "./AppIcon.js";
10
11
  import { JsModal, devToolsStorageKeys } from "@buoy-gg/shared-ui";
11
12
  import { useSafeAreaInsets } from "@buoy-gg/shared-ui";
12
- import { ModalHeader } from "@buoy-gg/shared-ui";
13
- import { TabSelector } from "@buoy-gg/shared-ui";
14
- import { buoyColors } from "@buoy-gg/shared-ui";
15
13
  import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
16
14
  const STORAGE_KEY = "@react_buoy_dev_tools_settings";
17
15
  const MAX_DIAL_SLOTS = 6;
@@ -88,6 +86,12 @@ const mergeWithDefaults = (defaults, stored, options) => {
88
86
  * via AsyncStorage so teams can tailor which tools appear in the dial or floating row.
89
87
  */
90
88
 
89
+ /**
90
+ * The subset of InstalledApp the settings modal needs per tool. `icon`/`color`
91
+ * let rows fall back to the tool's own glyph when getToolIcon() has no
92
+ * night-tuned override for it.
93
+ */
94
+
91
95
  /**
92
96
  * Generate default settings based on available apps and optional team default configuration.
93
97
  *
@@ -139,6 +143,10 @@ const generateDefaultSettings = (availableApps = [], defaultFloatingTools, defau
139
143
  /**
140
144
  * Configurable modal surface that lets engineers pick which dev tools appear in the dial and
141
145
  * floating row. Persists preferences and enforces slot limits for a consistent UX.
146
+ *
147
+ * Styled in the "night" language (Everlights port — see shared-ui's
148
+ * ui/night): section labels outside near-black cards, hairline-separated
149
+ * rows, real switches, and a transparent header the star field runs through.
142
150
  */
143
151
  export const DevToolsSettingsModal = ({
144
152
  visible,
@@ -155,11 +163,27 @@ export const DevToolsSettingsModal = ({
155
163
  const defaultSettings = useMemo(() => generateDefaultSettings(availableApps, defaultFloatingTools, defaultDialTools), [availableApps, defaultFloatingTools, defaultDialTools]);
156
164
  const allowedDialKeys = useMemo(() => Object.keys(defaultSettings.dialTools), [defaultSettings]);
157
165
  const allowedFloatingKeys = useMemo(() => Object.keys(defaultSettings.floatingTools).filter(key => key !== "environment"), [defaultSettings]);
158
- const [settings, setSettings] = useState(initialSettings || defaultSettings);
159
- const [activeTab, setActiveTab] = useState("floating");
160
- const [activeTabLoaded, setActiveTabLoaded] = useState(false);
166
+ const [settings, setSettings] = useState(initialSettings || cachedSettings || defaultSettings);
167
+ const [activeTab, setActiveTab] = useState(cachedActiveTab ?? "floating");
168
+ const [activeTabLoaded, setActiveTabLoaded] = useState(cachedActiveTab !== null);
161
169
  const [expandedSettings, setExpandedSettings] = useState(new Set());
162
170
  const [showLicenseModal, setShowLicenseModal] = useState(false);
171
+ /**
172
+ * Tool icons are the expensive half of this modal: ~20 of them, each a
173
+ * data-driven glyph that mounts one view per shape. Rendering them in the
174
+ * first pass pushed the modal's first paint out to ~240ms, so the tap felt
175
+ * dead. Holding them back a single frame lets the chrome and the tool rows
176
+ * paint at ~100ms and the glyphs land during the entrance fade.
177
+ *
178
+ * There is no layout shift: `styles.toolIconSlot` is a fixed box, so it
179
+ * reads as the icon's placeholder until filled.
180
+ */
181
+ const [iconsReady, setIconsReady] = useState(false);
182
+ useEffect(() => {
183
+ const frame = requestAnimationFrame(() => setIconsReady(true));
184
+ return () => cancelAnimationFrame(frame);
185
+ }, []);
186
+
163
187
  // null when external-sync isn't installed (or predates the status store);
164
188
  // "idle" until AutoExternalSync initializes — the row hides in both cases.
165
189
  const desktopSyncStatus = useDesktopSyncStatus();
@@ -170,7 +194,9 @@ export const DevToolsSettingsModal = ({
170
194
  try {
171
195
  const savedTab = await persistentStorage.getItem(devToolsStorageKeys.settings.activeTab());
172
196
  if (savedTab && ["floating", "settings", "pro"].includes(savedTab)) {
173
- setActiveTab(savedTab);
197
+ const tab = savedTab;
198
+ cachedActiveTab = tab;
199
+ setActiveTab(prev => prev === tab ? prev : tab);
174
200
  }
175
201
  } catch (error) {
176
202
  // Failed to load active tab - use default
@@ -195,18 +221,13 @@ export const DevToolsSettingsModal = ({
195
221
  const useLicenseHook = getUseLicense();
196
222
  const license = useLicenseHook?.();
197
223
  const isPro = license?.isPro ?? false;
198
- const [storageBackend, setStorageBackend] = useState(null);
224
+ const [storageBackend, setStorageBackend] = useState(cachedStorageBackend);
199
225
  const [isClearing, setIsClearing] = useState(false);
200
226
  const [clearSuccess, setClearSuccess] = useState(false);
201
227
  const [isStorageExpanded, setIsStorageExpanded] = useState(false);
202
228
  const [savedKeys, setSavedKeys] = useState([]);
203
229
  const [savedKeysLoading, setSavedKeysLoading] = useState(false);
204
- const [copySuccess, setCopySuccess] = useState(false);
205
- const [isExportExpanded, setIsExportExpanded] = useState(false);
206
230
  const insets = useSafeAreaInsets();
207
-
208
- // Check if in development mode - used to show dev-only features like Export Config
209
- const isDevelopmentMode = typeof __DEV__ !== "undefined" && __DEV__;
210
231
  const screenHeight = Dimensions.get("window").height;
211
232
  const screenWidth = Dimensions.get("window").width;
212
233
  const modalHeight = Math.floor(screenHeight * 0.33); // 1/3 of screen height
@@ -221,10 +242,12 @@ export const DevToolsSettingsModal = ({
221
242
  allowedDialKeys,
222
243
  allowedFloatingKeys
223
244
  });
224
- setSettings(merged);
245
+ cachedSettings = merged;
246
+ setSettings(prev => sameSettings(prev, merged) ? prev : merged);
225
247
  return;
226
248
  }
227
- setSettings(defaultSettings);
249
+ cachedSettings = defaultSettings;
250
+ setSettings(prev => sameSettings(prev, defaultSettings) ? prev : defaultSettings);
228
251
  } catch (error) {
229
252
  console.error("Failed to load dev tools settings:", error);
230
253
  setSettings(defaultSettings);
@@ -236,7 +259,10 @@ export const DevToolsSettingsModal = ({
236
259
 
237
260
  // Load storage backend type
238
261
  useEffect(() => {
239
- getStorageBackendType().then(setStorageBackend);
262
+ getStorageBackendType().then(backend => {
263
+ cachedStorageBackend = backend;
264
+ setStorageBackend(prev => prev === backend ? prev : backend);
265
+ });
240
266
  }, []);
241
267
 
242
268
  // Load saved keys when storage card is expanded
@@ -259,6 +285,7 @@ export const DevToolsSettingsModal = ({
259
285
  dialTools: enforceDialLimit(newSettings.dialTools)
260
286
  };
261
287
  await persistentStorage.setItem(STORAGE_KEY, JSON.stringify(limitedSettings));
288
+ cachedSettings = limitedSettings;
262
289
  setSettings(limitedSettings);
263
290
  onSettingsChange?.(limitedSettings);
264
291
  // Notify listeners (e.g., floating bubble) to refresh immediately
@@ -305,49 +332,6 @@ export const DevToolsSettingsModal = ({
305
332
  }
306
333
  };
307
334
 
308
- // Get the enabled tools as arrays for display and copy
309
- const enabledConfig = useMemo(() => {
310
- const enabledFloating = Object.entries(settings.floatingTools).filter(([_, enabled]) => enabled).map(([id]) => id);
311
- const enabledDial = Object.entries(settings.dialTools).filter(([_, enabled]) => enabled).map(([id]) => id);
312
- return {
313
- floating: enabledFloating,
314
- dial: enabledDial
315
- };
316
- }, [settings]);
317
-
318
- // Generate exportable code snippet from current settings
319
- // Only outputs the defaultFloatingTools and defaultDialTools props
320
- // so users can add them to their existing FloatingDevTools config
321
- const generateConfigCode = useCallback(() => {
322
- const {
323
- floating,
324
- dial
325
- } = enabledConfig;
326
-
327
- // Build only the default config props (not the full component)
328
- const props = [];
329
- if (floating.length > 0) {
330
- const floatingStr = floating.map(id => `'${id}'`).join(', ');
331
- props.push(`defaultFloatingTools={[${floatingStr}]}`);
332
- }
333
- if (dial.length > 0) {
334
- const dialStr = dial.map(id => `'${id}'`).join(', ');
335
- props.push(`defaultDialTools={[${dialStr}]}`);
336
- }
337
- if (props.length === 0) {
338
- return `// No tools enabled`;
339
- }
340
- return props.join('\n');
341
- }, [enabledConfig]);
342
- const handleCopyConfig = async () => {
343
- const code = generateConfigCode();
344
- const success = await copyToClipboard(code);
345
- if (success) {
346
- setCopySuccess(true);
347
- setTimeout(() => setCopySuccess(false), 2000);
348
- }
349
- };
350
-
351
335
  // Modal is fixed to bottom sheet mode
352
336
  const handleModeChange = useCallback(_mode => {
353
337
  // Mode changes handled by JsModal
@@ -365,153 +349,161 @@ export const DevToolsSettingsModal = ({
365
349
  };
366
350
  const getToolLabel = tool => {
367
351
  if (tool === "environment") {
368
- return "ENV BADGE";
352
+ return "Env Badge";
369
353
  }
370
354
  const app = availableApps.find(a => a.id === tool);
371
355
  return app?.name ?? tool.toUpperCase().replace(/_/g, " ");
372
356
  };
373
-
374
- // Clean tool card renderer - unified Buoy theme
375
- const renderToolCard = (keyName, value, disabled, onToggle) => {
376
- const getToolIcon = tool => {
377
- switch (tool) {
378
- case "query":
379
- return /*#__PURE__*/_jsx(QueryIcon, {
380
- size: 16
381
- });
382
- case "env":
383
- return /*#__PURE__*/_jsx(EnvIcon, {
384
- size: 16
385
- });
386
- case "sentry":
387
- return /*#__PURE__*/_jsx(SentryBugIcon, {
388
- size: 16,
389
- colorPreset: "red",
390
- noBackground: true
391
- });
392
- case "storage":
393
- return /*#__PURE__*/_jsx(StorageIcon, {
394
- size: 16
395
- });
396
- case "wifi":
397
- case "query-wifi-toggle":
398
- // Support both IDs for wifi toggle
399
- return /*#__PURE__*/_jsx(WifiCircuitIcon, {
400
- size: 16,
401
- colorPreset: "green",
402
- strength: 4,
403
- noBackground: true
404
- });
405
- case "route-events":
406
- return /*#__PURE__*/_jsx(RoutesIcon, {
407
- size: 16
408
- });
409
- case "network":
410
- return /*#__PURE__*/_jsx(NetworkIcon, {
411
- size: 16
412
- });
413
- case "environment":
414
- return /*#__PURE__*/_jsx(EnvIcon, {
415
- size: 16
416
- });
417
- case "debug-borders":
418
- return /*#__PURE__*/_jsx(Layers, {
419
- size: 16,
420
- color: buoyColors.primary
421
- });
422
- case "highlight-updates":
423
- return /*#__PURE__*/_jsx(RenderCountIcon, {
424
- size: 16,
425
- color: buoyColors.primary
426
- });
427
- case "highlight-updates-modal":
428
- return /*#__PURE__*/_jsx(HighlighterIcon, {
429
- size: 16
357
+ const getToolIcon = tool => {
358
+ if (!iconsReady) return null;
359
+ switch (tool) {
360
+ case "query":
361
+ return /*#__PURE__*/_jsx(QueryIcon, {
362
+ size: 18
363
+ });
364
+ case "env":
365
+ return /*#__PURE__*/_jsx(EnvIcon, {
366
+ size: 18
367
+ });
368
+ case "sentry":
369
+ return /*#__PURE__*/_jsx(SentryBugIcon, {
370
+ size: 18,
371
+ colorPreset: "red",
372
+ noBackground: true
373
+ });
374
+ case "storage":
375
+ return /*#__PURE__*/_jsx(StorageIcon, {
376
+ size: 18,
377
+ bgColor: night.color.surface
378
+ });
379
+ case "wifi":
380
+ case "query-wifi-toggle":
381
+ // Support both IDs for wifi toggle
382
+ return /*#__PURE__*/_jsx(WifiCircuitIcon, {
383
+ size: 18,
384
+ colorPreset: "green",
385
+ strength: 4,
386
+ noBackground: true
387
+ });
388
+ case "route-events":
389
+ return /*#__PURE__*/_jsx(RoutesIcon, {
390
+ size: 18,
391
+ bgColor: night.color.surface
392
+ });
393
+ case "network":
394
+ return /*#__PURE__*/_jsx(NetworkIcon, {
395
+ size: 18
396
+ });
397
+ case "environment":
398
+ return /*#__PURE__*/_jsx(EnvIcon, {
399
+ size: 18
400
+ });
401
+ case "debug-borders":
402
+ return /*#__PURE__*/_jsx(Layers, {
403
+ size: 18,
404
+ color: night.color.accent
405
+ });
406
+ case "highlight-updates":
407
+ return /*#__PURE__*/_jsx(RenderCountIcon, {
408
+ size: 18,
409
+ color: night.color.accent
410
+ });
411
+ case "highlight-updates-modal":
412
+ return /*#__PURE__*/_jsx(HighlighterIcon, {
413
+ size: 18
414
+ });
415
+ case "redux":
416
+ return /*#__PURE__*/_jsx(ReduxIcon, {
417
+ size: 18
418
+ });
419
+ case "image-overlay":
420
+ return /*#__PURE__*/_jsx(ImageOverlayIcon, {
421
+ size: 18
422
+ });
423
+ case "events":
424
+ return /*#__PURE__*/_jsx(EventsIcon, {
425
+ size: 18
426
+ });
427
+ case "zustand":
428
+ try {
429
+ // @ts-ignore - Optional package
430
+ const {
431
+ ZustandIcon
432
+ } = require("@buoy-gg/zustand");
433
+ return /*#__PURE__*/_jsx(ZustandIcon, {
434
+ size: 18
430
435
  });
431
- case "redux":
432
- return /*#__PURE__*/_jsx(ReduxIcon, {
433
- size: 16
436
+ } catch {
437
+ return /*#__PURE__*/_jsx(Info, {
438
+ size: 18,
439
+ color: night.color.textSecondary
434
440
  });
435
- case "image-overlay":
436
- return /*#__PURE__*/_jsx(ImageOverlayIcon, {
437
- size: 16
441
+ }
442
+ case "jotai":
443
+ try {
444
+ // @ts-ignore - Optional package
445
+ const {
446
+ JotaiIcon
447
+ } = require("@buoy-gg/jotai");
448
+ return /*#__PURE__*/_jsx(JotaiIcon, {
449
+ size: 18
438
450
  });
439
- case "events":
440
- return /*#__PURE__*/_jsx(EventsIcon, {
441
- size: 16
451
+ } catch {
452
+ return /*#__PURE__*/_jsx(Info, {
453
+ size: 18,
454
+ color: night.color.textSecondary
442
455
  });
443
- case "zustand":
444
- try {
445
- // @ts-ignore - Optional package
446
- const {
447
- ZustandIcon
448
- } = require("@buoy-gg/zustand");
449
- return /*#__PURE__*/_jsx(ZustandIcon, {
450
- size: 16
451
- });
452
- } catch {
453
- return /*#__PURE__*/_jsx(Info, {
454
- size: 16,
455
- color: buoyColors.textSecondary
456
- });
457
- }
458
- case "jotai":
459
- try {
460
- // @ts-ignore - Optional package
461
- const {
462
- JotaiIcon
463
- } = require("@buoy-gg/jotai");
464
- return /*#__PURE__*/_jsx(JotaiIcon, {
465
- size: 16
466
- });
467
- } catch {
468
- return /*#__PURE__*/_jsx(Info, {
469
- size: 16,
470
- color: buoyColors.textSecondary
456
+ }
457
+ default:
458
+ {
459
+ // Tools without a night-tuned override (console, images, time
460
+ // machine, perf monitor, custom toolkit tools, …) render their own
461
+ // app icon — the same artwork the dial shows.
462
+ const app = availableApps.find(a => a.id === tool);
463
+ if (app?.icon != null) {
464
+ return renderAppIcon(app.icon, {
465
+ slot: "row",
466
+ size: 18,
467
+ color: app.color
471
468
  });
472
469
  }
473
- default:
474
470
  return /*#__PURE__*/_jsx(Info, {
475
- size: 16,
476
- color: buoyColors.textSecondary
471
+ size: 18,
472
+ color: night.color.textSecondary
477
473
  });
478
- }
479
- };
480
- return /*#__PURE__*/_jsx(TouchableOpacity, {
481
- activeOpacity: disabled ? 1 : 0.85,
474
+ }
475
+ }
476
+ };
477
+
478
+ /**
479
+ * One tool row: glyph, name + one-line description, switch. Lives inside a
480
+ * NightCard's hairline-separated rows.
481
+ */
482
+ const renderToolRow = (keyName, value, disabled, onToggle) => {
483
+ const description = getToolDescription(keyName);
484
+ return /*#__PURE__*/_jsxs(TouchableOpacity, {
485
+ activeOpacity: disabled ? 1 : 0.7,
482
486
  onPress: () => !disabled && onToggle(),
483
- style: {
484
- marginBottom: 8,
485
- opacity: disabled ? 0.5 : 1
486
- },
487
- children: /*#__PURE__*/_jsx(View, {
488
- style: [styles.glassCard, value && styles.glassCardActive],
489
- children: /*#__PURE__*/_jsxs(View, {
490
- style: styles.glassCardInner,
491
- children: [/*#__PURE__*/_jsx(View, {
492
- style: [styles.iconContainer, value && styles.iconContainerActive],
493
- children: getToolIcon(keyName)
494
- }), /*#__PURE__*/_jsxs(View, {
495
- style: styles.toolInfo,
496
- children: [/*#__PURE__*/_jsxs(Text, {
497
- style: [styles.toolName, value && styles.toolNameActive],
498
- children: [getToolLabel(keyName), disabled ? " (MAX 6)" : ""]
499
- }), /*#__PURE__*/_jsx(Text, {
500
- style: styles.toolDescription,
501
- numberOfLines: 1,
502
- children: getToolDescription(keyName)
503
- })]
504
- }), /*#__PURE__*/_jsx(View, {
505
- style: [styles.pillToggle, value ? styles.pillToggleOn : styles.pillToggleOff, disabled && {
506
- opacity: 0.5
507
- }],
508
- children: /*#__PURE__*/_jsx(Text, {
509
- style: [styles.pillToggleText, value ? styles.pillToggleTextOn : styles.pillToggleTextOff],
510
- children: value ? "ON" : "OFF"
511
- })
512
- })]
513
- })
514
- })
487
+ accessibilityLabel: getToolLabel(keyName),
488
+ style: [styles.toolRow, disabled && styles.rowDisabled],
489
+ children: [/*#__PURE__*/_jsx(View, {
490
+ style: styles.toolIconSlot,
491
+ children: getToolIcon(keyName)
492
+ }), /*#__PURE__*/_jsxs(View, {
493
+ style: styles.rowInfo,
494
+ children: [/*#__PURE__*/_jsxs(Text, {
495
+ style: styles.rowLabel,
496
+ children: [getToolLabel(keyName), disabled ? " · max 6" : ""]
497
+ }), description ? /*#__PURE__*/_jsx(Text, {
498
+ style: styles.rowCaption,
499
+ numberOfLines: 1,
500
+ children: description
501
+ }) : null]
502
+ }), /*#__PURE__*/_jsx(NightSwitch, {
503
+ value: value,
504
+ disabled: disabled,
505
+ onValueChange: () => onToggle()
506
+ })]
515
507
  }, keyName);
516
508
  };
517
509
 
@@ -528,609 +520,252 @@ export const DevToolsSettingsModal = ({
528
520
  });
529
521
  };
530
522
 
531
- // Render a global setting toggle card with expandable description
532
- const renderGlobalSettingCard = (settingKey, label, category, shortDescription, fullDescription, recommendation) => {
523
+ /**
524
+ * One global-setting row: name + one-line hint, switch, and a tap-to-expand
525
+ * body holding the full description and recommendation.
526
+ */
527
+ const renderGlobalSettingRow = (settingKey, label, shortDescription, fullDescription, recommendation) => {
533
528
  const value = settings.globalSettings?.[settingKey] ?? false;
534
529
  const isExpanded = expandedSettings.has(settingKey);
535
- const color = buoyColors.primary;
536
- return /*#__PURE__*/_jsx(View, {
537
- style: {
538
- marginBottom: 10
539
- },
540
- children: /*#__PURE__*/_jsxs(TouchableOpacity, {
541
- activeOpacity: 0.85,
530
+ return /*#__PURE__*/_jsxs(View, {
531
+ children: [/*#__PURE__*/_jsxs(TouchableOpacity, {
532
+ activeOpacity: 0.7,
542
533
  onPress: () => toggleSettingExpanded(settingKey),
543
- style: [styles.expandableCard, isExpanded && {
544
- borderColor: color,
545
- borderWidth: 2,
546
- shadowColor: color,
547
- shadowOpacity: 0.8,
548
- shadowRadius: 20,
549
- shadowOffset: {
550
- width: 0,
551
- height: 0
552
- },
553
- elevation: 10,
554
- transform: [{
555
- scale: 1.01
556
- }]
557
- }],
534
+ accessibilityLabel: label,
535
+ style: styles.settingRow,
558
536
  children: [/*#__PURE__*/_jsxs(View, {
559
- style: styles.expandableCardHeader,
560
- children: [/*#__PURE__*/_jsx(View, {
561
- style: styles.expandableCardCategory,
562
- children: /*#__PURE__*/_jsx(Text, {
563
- style: styles.expandableCardCategoryText,
564
- children: category
565
- })
566
- }), /*#__PURE__*/_jsxs(View, {
567
- style: styles.expandableCardTitle,
568
- children: [/*#__PURE__*/_jsx(Text, {
569
- style: styles.expandableCardTitleText,
570
- children: label
571
- }), !isExpanded && /*#__PURE__*/_jsx(Text, {
572
- style: styles.expandableCardSubtitle,
573
- numberOfLines: 1,
574
- children: shortDescription
575
- })]
576
- }), /*#__PURE__*/_jsxs(View, {
577
- style: styles.expandableCardActions,
578
- children: [/*#__PURE__*/_jsx(TouchableOpacity, {
579
- onPress: () => toggleGlobalSetting(settingKey),
580
- activeOpacity: 0.8,
581
- style: [styles.pillToggle, {
582
- backgroundColor: value ? `${buoyColors.success}33` : buoyColors.hover,
583
- borderColor: value ? `${buoyColors.success}88` : buoyColors.border,
584
- shadowColor: value ? buoyColors.success : "transparent",
585
- shadowOffset: {
586
- width: 0,
587
- height: 0
588
- },
589
- shadowOpacity: value ? 0.4 : 0,
590
- shadowRadius: value ? 8 : 0
591
- }],
592
- children: /*#__PURE__*/_jsx(Text, {
593
- style: [styles.pillToggleText, {
594
- color: value ? buoyColors.success : buoyColors.textMuted
595
- }],
596
- children: value ? "ON" : "OFF"
597
- })
598
- }), isExpanded ? /*#__PURE__*/_jsx(ChevronDown, {
599
- size: 18,
600
- color: "#7F91B2"
601
- }) : /*#__PURE__*/_jsx(ChevronRightIcon, {
602
- size: 18,
603
- color: "#7F91B2"
604
- })]
605
- })]
606
- }), isExpanded && /*#__PURE__*/_jsxs(View, {
607
- style: styles.expandableCardBody,
608
- children: [/*#__PURE__*/_jsxs(View, {
609
- style: styles.expandableCardSection,
610
- children: [/*#__PURE__*/_jsx(Text, {
611
- style: styles.expandableCardSectionTitle,
612
- children: "DESCRIPTION"
613
- }), /*#__PURE__*/_jsx(Text, {
614
- style: styles.expandableCardSectionText,
615
- children: fullDescription
616
- })]
617
- }), /*#__PURE__*/_jsxs(View, {
618
- style: styles.expandableCardSection,
619
- children: [/*#__PURE__*/_jsx(Text, {
620
- style: styles.expandableCardSectionTitle,
621
- children: "RECOMMENDATION"
622
- }), /*#__PURE__*/_jsx(Text, {
623
- style: styles.expandableCardSectionText,
624
- children: recommendation
625
- })]
537
+ style: styles.rowInfo,
538
+ children: [/*#__PURE__*/_jsx(Text, {
539
+ style: styles.rowLabel,
540
+ children: label
541
+ }), /*#__PURE__*/_jsx(Text, {
542
+ style: styles.rowCaption,
543
+ numberOfLines: isExpanded ? 0 : 1,
544
+ children: shortDescription
626
545
  })]
546
+ }), /*#__PURE__*/_jsx(NightSwitch, {
547
+ value: value,
548
+ onValueChange: () => toggleGlobalSetting(settingKey)
627
549
  })]
628
- })
550
+ }), isExpanded && /*#__PURE__*/_jsxs(View, {
551
+ style: styles.settingRowBody,
552
+ children: [/*#__PURE__*/_jsx(Text, {
553
+ style: styles.settingRowBodyText,
554
+ children: fullDescription
555
+ }), /*#__PURE__*/_jsx(Text, {
556
+ style: styles.settingRowBodyHint,
557
+ children: recommendation
558
+ })]
559
+ })]
629
560
  }, settingKey);
630
561
  };
562
+
563
+ /** Section = uppercase label, card, optional footnote(s) under it. */
564
+ const renderSection = (label, card, footnotes) => /*#__PURE__*/_jsxs(View, {
565
+ children: [/*#__PURE__*/_jsx(NightSectionLabel, {
566
+ children: label
567
+ }), card, footnotes]
568
+ });
569
+ const storageBadge = storageBackend === "filesystem" ? /*#__PURE__*/_jsx(NightBadge, {
570
+ label: "File System",
571
+ tone: "accent"
572
+ }) : storageBackend === "asyncstorage" ? /*#__PURE__*/_jsx(NightBadge, {
573
+ label: "Async Storage",
574
+ tone: "warning"
575
+ }) : storageBackend === "memory" ? /*#__PURE__*/_jsx(NightBadge, {
576
+ label: "Memory",
577
+ tone: "danger"
578
+ }) : /*#__PURE__*/_jsx(NightBadge, {
579
+ label: "Loading\u2026",
580
+ tone: "neutral"
581
+ });
631
582
  const renderContent = () => /*#__PURE__*/_jsxs(View, {
632
583
  style: styles.container,
633
584
  children: [/*#__PURE__*/_jsxs(ScrollView, {
634
585
  style: styles.scrollContent,
635
586
  showsVerticalScrollIndicator: false,
636
587
  contentContainerStyle: styles.scrollContainer,
637
- children: [activeTab === "floating" && /*#__PURE__*/_jsx(View, {
638
- style: styles.section,
639
- children: Object.entries(settings.floatingTools).map(([key, value]) => renderToolCard(key, value, false, () => toggleFloatingTool(key)))
640
- }), activeTab === "settings" && /*#__PURE__*/_jsxs(View, {
641
- style: styles.section,
642
- children: [/*#__PURE__*/_jsxs(View, {
643
- style: [styles.storageStatusCard, isStorageExpanded && {
644
- borderColor: storageBackend === "filesystem" ? buoyColors.success + "60" : storageBackend === "asyncstorage" ? buoyColors.warning + "60" : buoyColors.error + "60"
645
- }],
646
- children: [/*#__PURE__*/_jsx(TouchableOpacity, {
647
- activeOpacity: 0.8,
588
+ children: [activeTab === "floating" && /*#__PURE__*/_jsx(_Fragment, {
589
+ children: renderSection("Floating Tools", /*#__PURE__*/_jsx(NightCard, {
590
+ children: /*#__PURE__*/_jsx(NightRows, {
591
+ children: Object.entries(settings.floatingTools).map(([key, value]) => renderToolRow(key, value, false, () => toggleFloatingTool(key)))
592
+ })
593
+ }), /*#__PURE__*/_jsx(NightFootnote, {
594
+ children: "Tools switched on here appear in the floating bubble row."
595
+ }))
596
+ }), activeTab === "settings" && /*#__PURE__*/_jsxs(_Fragment, {
597
+ children: [renderSection("Background", /*#__PURE__*/_jsx(NightCard, {
598
+ padded: true,
599
+ children: /*#__PURE__*/_jsx(BackgroundSwitcher, {
600
+ embedded: true
601
+ })
602
+ })), renderSection("Storage", /*#__PURE__*/_jsxs(NightCard, {
603
+ children: [/*#__PURE__*/_jsxs(TouchableOpacity, {
604
+ activeOpacity: 0.7,
648
605
  onPress: () => setIsStorageExpanded(!isStorageExpanded),
649
- children: /*#__PURE__*/_jsxs(View, {
650
- style: styles.storageStatusHeader,
651
- children: [/*#__PURE__*/_jsx(View, {
652
- style: [styles.storageStatusIcon, {
653
- backgroundColor: storageBackend === "filesystem" ? buoyColors.success + "15" : storageBackend === "asyncstorage" ? buoyColors.warning + "15" : buoyColors.error + "15"
654
- }],
655
- children: /*#__PURE__*/_jsx(Database, {
656
- size: 18,
657
- color: storageBackend === "filesystem" ? buoyColors.success : storageBackend === "asyncstorage" ? buoyColors.warning : buoyColors.error
658
- })
659
- }), /*#__PURE__*/_jsxs(View, {
660
- style: styles.storageStatusInfo,
661
- children: [/*#__PURE__*/_jsx(Text, {
662
- style: styles.storageStatusLabel,
663
- children: "STORAGE TYPE"
664
- }), /*#__PURE__*/_jsxs(View, {
665
- style: styles.storageStatusBadgeContainer,
666
- children: [/*#__PURE__*/_jsx(View, {
667
- style: [styles.storageStatusBadge, {
668
- backgroundColor: storageBackend === "filesystem" ? buoyColors.success + "20" : storageBackend === "asyncstorage" ? buoyColors.warning + "20" : buoyColors.error + "20",
669
- borderColor: storageBackend === "filesystem" ? buoyColors.success + "60" : storageBackend === "asyncstorage" ? buoyColors.warning + "60" : buoyColors.error + "60"
670
- }],
671
- children: /*#__PURE__*/_jsx(Text, {
672
- style: [styles.storageStatusBadgeText, {
673
- color: storageBackend === "filesystem" ? buoyColors.success : storageBackend === "asyncstorage" ? buoyColors.warning : buoyColors.error
674
- }],
675
- children: storageBackend === "filesystem" ? "FILE SYSTEM" : storageBackend === "asyncstorage" ? "ASYNC STORAGE" : storageBackend === "memory" ? "MEMORY" : "LOADING..."
676
- })
677
- }), storageBackend === "filesystem" && /*#__PURE__*/_jsx(CheckCircle2, {
678
- size: 14,
679
- color: buoyColors.success
680
- }), storageBackend === "asyncstorage" && /*#__PURE__*/_jsx(AlertTriangle, {
681
- size: 14,
682
- color: buoyColors.warning
683
- }), storageBackend === "memory" && /*#__PURE__*/_jsx(AlertTriangle, {
684
- size: 14,
685
- color: buoyColors.error
686
- })]
687
- })]
688
- }), isStorageExpanded ? /*#__PURE__*/_jsx(ChevronDown, {
689
- size: 18,
690
- color: buoyColors.textMuted
691
- }) : /*#__PURE__*/_jsx(ChevronRightIcon, {
692
- size: 18,
693
- color: buoyColors.textMuted
694
- })]
695
- })
696
- }), /*#__PURE__*/_jsx(Text, {
697
- style: styles.storageStatusDescription,
698
- children: storageBackend === "filesystem" ? "Settings persist independently and survive AsyncStorage.clear() calls during logout." : storageBackend === "asyncstorage" ? "Settings may be lost if AsyncStorage is cleared during logout." : storageBackend === "memory" ? "Settings are stored in memory only and will be lost on app restart." : "Checking storage backend..."
699
- }), storageBackend === "asyncstorage" && /*#__PURE__*/_jsxs(View, {
700
- style: styles.adviceHint,
701
- children: [/*#__PURE__*/_jsx(Zap, {
702
- size: 14,
703
- color: buoyColors.warning
704
- }), /*#__PURE__*/_jsxs(Text, {
705
- style: styles.adviceHintText,
706
- children: [/*#__PURE__*/_jsx(Text, {
707
- style: styles.adviceHintBold,
708
- children: "Tip:"
709
- }), " Install", " ", /*#__PURE__*/_jsx(Text, {
710
- style: styles.adviceHintCode,
711
- children: "expo-file-system"
712
- }), " to persist settings through logout flows."]
606
+ accessibilityLabel: "Storage Type",
607
+ style: styles.settingRow,
608
+ children: [/*#__PURE__*/_jsx(View, {
609
+ style: styles.rowInfo,
610
+ children: /*#__PURE__*/_jsx(Text, {
611
+ style: styles.rowLabel,
612
+ children: "Storage Type"
613
+ })
614
+ }), storageBadge, isStorageExpanded ? /*#__PURE__*/_jsx(ChevronDown, {
615
+ size: 16,
616
+ color: night.color.textTertiary
617
+ }) : /*#__PURE__*/_jsx(ChevronRightIcon, {
618
+ size: 16,
619
+ color: night.color.textTertiary
713
620
  })]
714
- }), storageBackend === "memory" && /*#__PURE__*/_jsxs(View, {
715
- style: styles.adviceHintsContainer,
621
+ }), isStorageExpanded && /*#__PURE__*/_jsxs(_Fragment, {
716
622
  children: [/*#__PURE__*/_jsxs(View, {
717
- style: [styles.adviceHint, {
718
- backgroundColor: buoyColors.error + "10"
719
- }],
720
- children: [/*#__PURE__*/_jsx(AlertTriangle, {
721
- size: 14,
722
- color: buoyColors.error
723
- }), /*#__PURE__*/_jsxs(Text, {
724
- style: [styles.adviceHintText, {
725
- color: buoyColors.error
726
- }],
727
- children: [/*#__PURE__*/_jsx(Text, {
728
- style: styles.adviceHintBold,
729
- children: "No persistent storage available!"
730
- }), " Settings will reset on every app restart."]
731
- })]
732
- }), /*#__PURE__*/_jsxs(View, {
733
- style: styles.adviceHint,
734
- children: [/*#__PURE__*/_jsx(Zap, {
735
- size: 14,
736
- color: buoyColors.primary
737
- }), /*#__PURE__*/_jsxs(Text, {
738
- style: styles.adviceHintText,
739
- children: [/*#__PURE__*/_jsx(Text, {
740
- style: styles.adviceHintBold,
741
- children: "Best:"
742
- }), " Install", " ", /*#__PURE__*/_jsx(Text, {
743
- style: styles.adviceHintCode,
744
- children: "expo-file-system"
745
- }), " for logout-safe persistence."]
623
+ style: styles.settingRowBody,
624
+ children: [/*#__PURE__*/_jsx(Text, {
625
+ style: styles.settingRowBodyText,
626
+ children: storageBackend === "filesystem" ? "Settings persist independently and survive AsyncStorage.clear() calls during logout." : storageBackend === "asyncstorage" ? "Settings may be lost if AsyncStorage is cleared during logout." : storageBackend === "memory" ? "Settings are stored in memory only and will be lost on app restart." : "Checking storage backend…"
627
+ }), storageBackend === "asyncstorage" && /*#__PURE__*/_jsx(Text, {
628
+ style: [styles.settingRowBodyText, styles.bodyWarning],
629
+ children: "Install expo-file-system to persist settings through logout flows."
630
+ }), storageBackend === "memory" && /*#__PURE__*/_jsx(Text, {
631
+ style: [styles.settingRowBodyText, styles.bodyDanger],
632
+ children: "No persistent storage available \u2014 install expo-file-system (best) or @react-native-async-storage/async-storage."
746
633
  })]
747
- }), /*#__PURE__*/_jsxs(View, {
748
- style: styles.adviceHint,
749
- children: [/*#__PURE__*/_jsx(HardDrive, {
750
- size: 14,
751
- color: buoyColors.textMuted
752
- }), /*#__PURE__*/_jsxs(Text, {
753
- style: styles.adviceHintText,
634
+ }), /*#__PURE__*/_jsx(NightSeparator, {}), /*#__PURE__*/_jsxs(View, {
635
+ style: styles.storageExpanded,
636
+ children: [/*#__PURE__*/_jsxs(View, {
637
+ style: styles.storageExpandedHeader,
754
638
  children: [/*#__PURE__*/_jsx(Text, {
755
- style: styles.adviceHintBold,
756
- children: "Alternative:"
757
- }), " Install", " ", /*#__PURE__*/_jsx(Text, {
758
- style: styles.adviceHintCode,
759
- children: "@react-native-async-storage/async-storage"
760
- }), " for basic persistence."]
761
- })]
762
- })]
763
- }), isStorageExpanded && /*#__PURE__*/_jsxs(View, {
764
- style: styles.storageExpandedContent,
765
- children: [/*#__PURE__*/_jsxs(View, {
766
- style: styles.storageExpandedHeader,
767
- children: [/*#__PURE__*/_jsx(FileText, {
768
- size: 14,
769
- color: buoyColors.primary
770
- }), /*#__PURE__*/_jsx(Text, {
771
- style: styles.storageExpandedTitle,
772
- children: "SAVED SETTINGS"
773
- }), /*#__PURE__*/_jsx(Text, {
774
- style: styles.storageExpandedCount,
775
- children: savedKeysLoading ? "..." : `${savedKeys.length} keys`
776
- })]
777
- }), savedKeysLoading ? /*#__PURE__*/_jsx(Text, {
778
- style: styles.storageKeyItem,
779
- children: "Loading..."
780
- }) : savedKeys.length === 0 ? /*#__PURE__*/_jsx(Text, {
781
- style: styles.storageKeyItemEmpty,
782
- children: "No settings saved yet"
783
- }) : /*#__PURE__*/_jsx(ScrollView, {
784
- style: styles.storageKeysList,
785
- nestedScrollEnabled: true,
786
- showsVerticalScrollIndicator: true,
787
- children: savedKeys.map(key => /*#__PURE__*/_jsx(View, {
788
- style: styles.storageKeyItem,
789
- children: /*#__PURE__*/_jsx(Text, {
790
- style: styles.storageKeyText,
791
- numberOfLines: 1,
792
- children: key.replace("@react_buoy_", "")
793
- })
794
- }, key))
795
- })]
796
- }), /*#__PURE__*/_jsxs(TouchableOpacity, {
797
- style: [styles.clearStorageButton, isClearing && styles.clearStorageButtonDisabled, clearSuccess && styles.clearStorageButtonSuccess],
798
- onPress: handleClearStorage,
799
- disabled: isClearing,
800
- activeOpacity: 0.7,
801
- children: [clearSuccess ? /*#__PURE__*/_jsx(CheckCircle2, {
802
- size: 14,
803
- color: buoyColors.success
804
- }) : /*#__PURE__*/_jsx(Trash2, {
805
- size: 14,
806
- color: isClearing ? buoyColors.textMuted : buoyColors.error
807
- }), /*#__PURE__*/_jsx(Text, {
808
- style: [styles.clearStorageButtonText, clearSuccess && {
809
- color: buoyColors.success
810
- }, isClearing && {
811
- color: buoyColors.textMuted
812
- }],
813
- children: clearSuccess ? "CLEARED" : isClearing ? "CLEARING..." : "CLEAR ALL SETTINGS"
814
- })]
815
- })]
816
- }), desktopSyncStatus && desktopSyncStatus.state !== "idle" && /*#__PURE__*/_jsxs(View, {
817
- style: [styles.storageStatusCard, {
818
- borderColor: desktopSyncStatus.state === "connected" ? buoyColors.success + "60" : buoyColors.warning + "60"
819
- }],
820
- children: [/*#__PURE__*/_jsxs(View, {
821
- style: styles.storageStatusHeader,
822
- children: [/*#__PURE__*/_jsx(View, {
823
- style: [styles.storageStatusIcon, {
824
- backgroundColor: desktopSyncStatus.state === "connected" ? buoyColors.success + "15" : buoyColors.warning + "15"
825
- }],
826
- children: /*#__PURE__*/_jsx(Globe, {
827
- size: 18,
828
- color: desktopSyncStatus.state === "connected" ? buoyColors.success : buoyColors.warning
829
- })
830
- }), /*#__PURE__*/_jsxs(View, {
831
- style: styles.storageStatusInfo,
832
- children: [/*#__PURE__*/_jsx(Text, {
833
- style: styles.storageStatusLabel,
834
- children: "DESKTOP SYNC"
835
- }), /*#__PURE__*/_jsxs(View, {
836
- style: styles.storageStatusBadgeContainer,
837
- children: [/*#__PURE__*/_jsx(View, {
838
- style: [styles.storageStatusBadge, {
839
- backgroundColor: desktopSyncStatus.state === "connected" ? buoyColors.success + "20" : buoyColors.warning + "20",
840
- borderColor: desktopSyncStatus.state === "connected" ? buoyColors.success + "60" : buoyColors.warning + "60"
841
- }],
639
+ style: styles.storageExpandedTitle,
640
+ children: "SAVED SETTINGS"
641
+ }), /*#__PURE__*/_jsx(Text, {
642
+ style: styles.storageExpandedCount,
643
+ children: savedKeysLoading ? "…" : `${savedKeys.length} keys`
644
+ })]
645
+ }), savedKeysLoading ? /*#__PURE__*/_jsx(Text, {
646
+ style: styles.storageKeyEmpty,
647
+ children: "Loading\u2026"
648
+ }) : savedKeys.length === 0 ? /*#__PURE__*/_jsx(Text, {
649
+ style: styles.storageKeyEmpty,
650
+ children: "No settings saved yet"
651
+ }) : /*#__PURE__*/_jsx(ScrollView, {
652
+ style: styles.storageKeysList,
653
+ nestedScrollEnabled: true,
654
+ showsVerticalScrollIndicator: true,
655
+ children: savedKeys.map(key => /*#__PURE__*/_jsx(View, {
656
+ style: styles.storageKeyItem,
842
657
  children: /*#__PURE__*/_jsx(Text, {
843
- style: [styles.storageStatusBadgeText, {
844
- color: desktopSyncStatus.state === "connected" ? buoyColors.success : buoyColors.warning
845
- }],
846
- children: desktopSyncStatus.state === "connected" ? "CONNECTED" : desktopSyncStatus.state === "connecting" ? "CONNECTING..." : "RETRYING"
658
+ style: styles.storageKeyText,
659
+ numberOfLines: 1,
660
+ children: key.replace("@react_buoy_", "")
847
661
  })
848
- }), desktopSyncStatus.state === "connected" ? /*#__PURE__*/_jsx(CheckCircle2, {
849
- size: 14,
850
- color: buoyColors.success
851
- }) : /*#__PURE__*/_jsx(AlertTriangle, {
852
- size: 14,
853
- color: buoyColors.warning
854
- })]
855
- })]
856
- })]
857
- }), /*#__PURE__*/_jsxs(Text, {
858
- style: styles.storageStatusDescription,
859
- children: [desktopSyncStatus.state === "connected" ? "Streaming tool data to Buoy Desktop at " : "Trying to reach Buoy Desktop at ", /*#__PURE__*/_jsx(Text, {
860
- style: styles.adviceHintCode,
861
- children: desktopSyncStatus.targetURL ?? "(unknown URL)"
862
- }), desktopSyncStatus.state !== "connected" && " — is the desktop app running? Override with the externalSync socketURL option if it runs elsewhere."]
863
- }), desktopSyncStatus.state === "retrying" && desktopSyncStatus.lastError && /*#__PURE__*/_jsxs(View, {
864
- style: styles.adviceHint,
865
- children: [/*#__PURE__*/_jsx(AlertTriangle, {
866
- size: 14,
867
- color: buoyColors.warning
868
- }), /*#__PURE__*/_jsxs(Text, {
869
- style: styles.adviceHintText,
870
- children: [/*#__PURE__*/_jsx(Text, {
871
- style: styles.adviceHintBold,
872
- children: "Last error:"
873
- }), " ", desktopSyncStatus.lastError]
874
- })]
875
- })]
876
- }), renderGlobalSettingCard("enableSharedModalDimensions", "SHARED MODAL SIZE", "MODAL", "Sync dimensions across all tools", "When enabled, all tool modals will share the same size and position. Resizing one modal will affect all others. When disabled, each tool remembers its own size and position independently.", "Keep OFF for the best experience. This allows you to customize each tool's modal size separately. Enable only if you prefer uniform modal sizes across all dev tools."), Platform.OS !== "web" && renderGlobalSettingCard("expandableWindowControls", "EXPAND CONTROLS", "MODAL", "iPad-style expandable window buttons", "When enabled, the window control buttons (minimize, toggle mode, close) start as small dots and expand into larger, easy-to-tap buttons when pressed — similar to iPad window controls. When disabled, buttons are directly tappable at their small size.", "Keep ON for touch devices where the small buttons are hard to press. Turn OFF if you prefer direct single-tap access (e.g. when using a mouse or simulator)."), isDevelopmentMode && /*#__PURE__*/_jsxs(View, {
877
- style: styles.exportConfigCard,
878
- children: [/*#__PURE__*/_jsxs(TouchableOpacity, {
879
- activeOpacity: 0.85,
880
- onPress: () => setIsExportExpanded(!isExportExpanded),
881
- style: styles.exportConfigHeader,
882
- children: [/*#__PURE__*/_jsx(View, {
883
- style: styles.exportConfigIconContainer,
884
- children: /*#__PURE__*/_jsx(FileCode, {
885
- size: 18,
886
- color: buoyColors.success
887
- })
888
- }), /*#__PURE__*/_jsxs(View, {
889
- style: styles.exportConfigInfo,
890
- children: [/*#__PURE__*/_jsx(Text, {
891
- style: styles.exportConfigLabel,
892
- children: "EXPORT CONFIG"
893
- }), /*#__PURE__*/_jsx(Text, {
894
- style: styles.exportConfigHint,
895
- children: "Save your settings to code"
662
+ }, key))
896
663
  })]
897
664
  }), /*#__PURE__*/_jsx(View, {
898
- style: styles.exportConfigActions,
899
- children: isExportExpanded ? /*#__PURE__*/_jsx(ChevronDown, {
900
- size: 18,
901
- color: buoyColors.textMuted
902
- }) : /*#__PURE__*/_jsx(ChevronRightIcon, {
903
- size: 18,
904
- color: buoyColors.textMuted
665
+ style: styles.cardButtonWrap,
666
+ children: /*#__PURE__*/_jsx(NightButton, {
667
+ label: clearSuccess ? "Cleared" : isClearing ? "Clearing…" : "Clear All Settings",
668
+ variant: clearSuccess ? "secondary" : "destructive",
669
+ size: "md",
670
+ disabled: isClearing,
671
+ onPress: handleClearStorage,
672
+ icon: clearSuccess ? /*#__PURE__*/_jsx(CheckCircle2, {
673
+ size: 15,
674
+ color: night.color.accent
675
+ }) : /*#__PURE__*/_jsx(Trash2, {
676
+ size: 15,
677
+ color: isClearing ? night.color.textTertiary : night.color.danger
678
+ })
905
679
  })
906
680
  })]
907
- }), /*#__PURE__*/_jsxs(View, {
908
- style: styles.exportHintBanner,
909
- children: [/*#__PURE__*/_jsx(Zap, {
910
- size: 14,
911
- color: buoyColors.textMuted
912
- }), /*#__PURE__*/_jsx(Text, {
913
- style: styles.exportHintText,
914
- children: "Configure tools, then export as props for your team."
915
- })]
916
- }), isExportExpanded && /*#__PURE__*/_jsxs(View, {
917
- style: styles.exportCodeContainer,
681
+ })]
682
+ })), desktopSyncStatus && desktopSyncStatus.state !== "idle" && renderSection("Desktop Sync", /*#__PURE__*/_jsxs(NightCard, {
683
+ children: [/*#__PURE__*/_jsxs(TouchableOpacity, {
684
+ activeOpacity: 0.7,
685
+ onPress: () => toggleSettingExpanded("desktopSync"),
686
+ accessibilityLabel: "Buoy Desktop",
687
+ style: styles.settingRow,
918
688
  children: [/*#__PURE__*/_jsx(View, {
919
- style: styles.exportCodeHeader,
920
- children: /*#__PURE__*/_jsx(Text, {
921
- style: styles.exportCodeTitle,
922
- children: "PROPS TO ADD"
923
- })
924
- }), enabledConfig.floating.length > 0 && /*#__PURE__*/_jsxs(View, {
925
- style: styles.exportJsonBlock,
926
- children: [/*#__PURE__*/_jsx(Text, {
927
- style: styles.exportJsonProp,
928
- children: "defaultFloatingTools"
929
- }), /*#__PURE__*/_jsx(Text, {
930
- style: styles.exportJsonEquals,
931
- children: "="
932
- }), /*#__PURE__*/_jsx(Text, {
933
- style: styles.exportJsonBracket,
934
- children: "{"
935
- }), /*#__PURE__*/_jsx(Text, {
936
- style: styles.exportJsonArrayBracket,
937
- children: "["
938
- }), /*#__PURE__*/_jsx(View, {
939
- style: styles.exportJsonArrayContent,
940
- children: enabledConfig.floating.map((id, index) => /*#__PURE__*/_jsxs(View, {
941
- style: styles.exportJsonArrayItem,
942
- children: [/*#__PURE__*/_jsxs(Text, {
943
- style: styles.exportJsonString,
944
- children: ["'", id, "'"]
945
- }), index < enabledConfig.floating.length - 1 && /*#__PURE__*/_jsx(Text, {
946
- style: styles.exportJsonComma,
947
- children: ","
948
- })]
949
- }, id))
950
- }), /*#__PURE__*/_jsx(Text, {
951
- style: styles.exportJsonArrayBracket,
952
- children: "]"
953
- }), /*#__PURE__*/_jsx(Text, {
954
- style: styles.exportJsonBracket,
955
- children: "}"
956
- })]
957
- }), enabledConfig.dial.length > 0 && /*#__PURE__*/_jsxs(View, {
958
- style: [styles.exportJsonBlock, enabledConfig.floating.length > 0 && {
959
- marginTop: 8
960
- }],
961
- children: [/*#__PURE__*/_jsx(Text, {
962
- style: styles.exportJsonProp,
963
- children: "defaultDialTools"
964
- }), /*#__PURE__*/_jsx(Text, {
965
- style: styles.exportJsonEquals,
966
- children: "="
967
- }), /*#__PURE__*/_jsx(Text, {
968
- style: styles.exportJsonBracket,
969
- children: "{"
970
- }), /*#__PURE__*/_jsx(Text, {
971
- style: styles.exportJsonArrayBracket,
972
- children: "["
973
- }), /*#__PURE__*/_jsx(View, {
974
- style: styles.exportJsonArrayContent,
975
- children: enabledConfig.dial.map((id, index) => /*#__PURE__*/_jsxs(View, {
976
- style: styles.exportJsonArrayItem,
977
- children: [/*#__PURE__*/_jsxs(Text, {
978
- style: styles.exportJsonString,
979
- children: ["'", id, "'"]
980
- }), index < enabledConfig.dial.length - 1 && /*#__PURE__*/_jsx(Text, {
981
- style: styles.exportJsonComma,
982
- children: ","
983
- })]
984
- }, id))
985
- }), /*#__PURE__*/_jsx(Text, {
986
- style: styles.exportJsonArrayBracket,
987
- children: "]"
988
- }), /*#__PURE__*/_jsx(Text, {
989
- style: styles.exportJsonBracket,
990
- children: "}"
991
- })]
992
- }), enabledConfig.floating.length === 0 && enabledConfig.dial.length === 0 && /*#__PURE__*/_jsx(View, {
993
- style: styles.exportJsonBlock,
689
+ style: styles.rowInfo,
994
690
  children: /*#__PURE__*/_jsx(Text, {
995
- style: styles.exportJsonComment,
996
- children: "// No tools enabled"
691
+ style: styles.rowLabel,
692
+ children: "Buoy Desktop"
997
693
  })
998
- }), /*#__PURE__*/_jsxs(Text, {
999
- style: styles.exportCodeDescription,
1000
- children: ["Add these props to your existing", " ", /*#__PURE__*/_jsx(Text, {
1001
- style: styles.exportCodeInline,
1002
- children: "<FloatingDevTools />"
1003
- }), " ", "component to set team defaults."]
694
+ }), desktopSyncStatus.state === "connected" ? /*#__PURE__*/_jsx(NightBadge, {
695
+ label: "Connected",
696
+ tone: "accent"
697
+ }) : desktopSyncStatus.state === "connecting" ? /*#__PURE__*/_jsx(NightBadge, {
698
+ label: "Connecting\u2026",
699
+ tone: "neutral"
700
+ }) : /*#__PURE__*/_jsx(NightBadge, {
701
+ label: "Retrying",
702
+ tone: "warning"
703
+ }), expandedSettings.has("desktopSync") ? /*#__PURE__*/_jsx(ChevronDown, {
704
+ size: 16,
705
+ color: night.color.textTertiary
706
+ }) : /*#__PURE__*/_jsx(ChevronRightIcon, {
707
+ size: 16,
708
+ color: night.color.textTertiary
1004
709
  })]
1005
- }), /*#__PURE__*/_jsxs(TouchableOpacity, {
1006
- style: [styles.exportCopyButton, copySuccess && styles.exportCopyButtonSuccess],
1007
- onPress: handleCopyConfig,
1008
- activeOpacity: 0.7,
1009
- children: [copySuccess ? /*#__PURE__*/_jsx(CheckCircle2, {
1010
- size: 14,
1011
- color: buoyColors.success
1012
- }) : /*#__PURE__*/_jsx(Copy, {
1013
- size: 14,
1014
- color: buoyColors.success
1015
- }), /*#__PURE__*/_jsx(Text, {
1016
- style: [styles.exportCopyButtonText, copySuccess && {
1017
- color: buoyColors.success
1018
- }],
1019
- children: copySuccess ? "COPIED!" : "COPY CONFIG TO CLIPBOARD"
710
+ }), expandedSettings.has("desktopSync") && /*#__PURE__*/_jsxs(View, {
711
+ style: styles.settingRowBody,
712
+ children: [/*#__PURE__*/_jsxs(Text, {
713
+ style: styles.settingRowBodyText,
714
+ children: [desktopSyncStatus.state === "connected" ? "Streaming tool data to Buoy Desktop at " : "Trying to reach Buoy Desktop at ", /*#__PURE__*/_jsx(Text, {
715
+ style: styles.footnoteMono,
716
+ children: desktopSyncStatus.targetURL ?? "(unknown URL)"
717
+ }), desktopSyncStatus.state !== "connected" && " — is the desktop app running? Override with the externalSync socketURL option if it runs elsewhere."]
718
+ }), desktopSyncStatus.state === "retrying" && desktopSyncStatus.lastError && /*#__PURE__*/_jsxs(Text, {
719
+ style: [styles.settingRowBodyText, styles.bodyWarning],
720
+ children: ["Last error: ", desktopSyncStatus.lastError]
1020
721
  })]
1021
722
  })]
1022
- })]
1023
- }), activeTab === "pro" && /*#__PURE__*/_jsx(View, {
1024
- style: styles.proContainer,
1025
- children: isPro ? /*#__PURE__*/_jsx(_Fragment, {
723
+ })), renderSection("Modal Behavior", /*#__PURE__*/_jsx(NightCard, {
724
+ children: /*#__PURE__*/_jsxs(NightRows, {
725
+ children: [renderGlobalSettingRow("enableSharedModalDimensions", "Shared Modal Size", "Sync dimensions across all tools", "When enabled, all tool modals will share the same size and position. Resizing one modal will affect all others. When disabled, each tool remembers its own size and position independently.", "Keep OFF for the best experience. This allows you to customize each tool's modal size separately."), Platform.OS !== "web" ? renderGlobalSettingRow("expandableWindowControls", "Expand Controls", "iPad-style expandable window buttons", "When enabled, the window control buttons (minimize, toggle mode, close) start as small dots and expand into larger, easy-to-tap buttons when pressed. When disabled, buttons are directly tappable at their small size.", "Keep ON for touch devices where the small buttons are hard to press. Turn OFF if you prefer direct single-tap access.") : null]
726
+ })
727
+ }))]
728
+ }), activeTab === "pro" && /*#__PURE__*/_jsxs(_Fragment, {
729
+ children: [renderSection("License", /*#__PURE__*/_jsx(NightCard, {
1026
730
  children: /*#__PURE__*/_jsxs(View, {
1027
- style: styles.proSection,
1028
- children: [/*#__PURE__*/_jsxs(SectionHeader, {
1029
- children: [/*#__PURE__*/_jsx(SectionHeader.Icon, {
1030
- icon: Zap,
1031
- color: buoyColors.primary,
1032
- size: 12
1033
- }), /*#__PURE__*/_jsx(SectionHeader.Title, {
1034
- children: "LICENSE STATUS"
1035
- }), /*#__PURE__*/_jsx(SectionHeader.Badge, {
1036
- count: "Active",
1037
- color: buoyColors.success
1038
- })]
1039
- }), /*#__PURE__*/_jsx(View, {
1040
- style: styles.proSectionContent,
731
+ style: styles.settingRow,
732
+ children: [/*#__PURE__*/_jsx(View, {
733
+ style: styles.rowInfo,
1041
734
  children: /*#__PURE__*/_jsx(Text, {
1042
- style: styles.proActiveDescription,
1043
- children: "You have full access to all Buoy DevTools features."
735
+ style: styles.rowLabel,
736
+ children: "License Status"
1044
737
  })
738
+ }), isPro ? /*#__PURE__*/_jsx(NightBadge, {
739
+ label: "Active",
740
+ tone: "accent"
741
+ }) : /*#__PURE__*/_jsx(NightBadge, {
742
+ label: "Free",
743
+ tone: "neutral"
1045
744
  })]
1046
745
  })
1047
- }) : /*#__PURE__*/_jsxs(_Fragment, {
1048
- children: [/*#__PURE__*/_jsxs(View, {
1049
- style: styles.proSection,
1050
- children: [/*#__PURE__*/_jsxs(SectionHeader, {
1051
- children: [/*#__PURE__*/_jsx(SectionHeader.Icon, {
1052
- icon: Zap,
1053
- color: buoyColors.textMuted,
1054
- size: 12
1055
- }), /*#__PURE__*/_jsx(SectionHeader.Title, {
1056
- children: "LICENSE STATUS"
1057
- }), /*#__PURE__*/_jsx(SectionHeader.Badge, {
1058
- count: "Free",
1059
- color: buoyColors.textMuted
1060
- })]
1061
- }), /*#__PURE__*/_jsx(View, {
1062
- style: styles.proSectionContent,
1063
- children: /*#__PURE__*/_jsx(Text, {
1064
- style: styles.proFreeDescription,
1065
- children: "Upgrade to Pro to unlock all features and support development."
1066
- })
1067
- })]
1068
- }), /*#__PURE__*/_jsxs(View, {
1069
- style: styles.proSection,
1070
- children: [/*#__PURE__*/_jsxs(SectionHeader, {
1071
- children: [/*#__PURE__*/_jsx(SectionHeader.Icon, {
1072
- icon: CheckCircle2,
1073
- color: buoyColors.primary,
1074
- size: 12
1075
- }), /*#__PURE__*/_jsx(SectionHeader.Title, {
1076
- children: "PRO FEATURES"
1077
- })]
1078
- }), /*#__PURE__*/_jsx(View, {
1079
- style: styles.proSectionContent,
1080
- children: /*#__PURE__*/_jsxs(View, {
1081
- style: styles.proFeaturesList,
1082
- children: [/*#__PURE__*/_jsxs(View, {
1083
- style: styles.proFeatureItem,
1084
- children: [/*#__PURE__*/_jsx(CheckCircle2, {
1085
- size: 14,
1086
- color: buoyColors.primary
1087
- }), /*#__PURE__*/_jsx(Text, {
1088
- style: styles.proFeatureText,
1089
- children: "Advanced Settings"
1090
- })]
1091
- }), /*#__PURE__*/_jsxs(View, {
1092
- style: styles.proFeatureItem,
1093
- children: [/*#__PURE__*/_jsx(CheckCircle2, {
1094
- size: 14,
1095
- color: buoyColors.primary
1096
- }), /*#__PURE__*/_jsx(Text, {
1097
- style: styles.proFeatureText,
1098
- children: "Export Configuration"
1099
- })]
1100
- }), /*#__PURE__*/_jsxs(View, {
1101
- style: styles.proFeatureItem,
1102
- children: [/*#__PURE__*/_jsx(CheckCircle2, {
1103
- size: 14,
1104
- color: buoyColors.primary
1105
- }), /*#__PURE__*/_jsx(Text, {
1106
- style: styles.proFeatureText,
1107
- children: "Team Defaults"
1108
- })]
1109
- }), /*#__PURE__*/_jsxs(View, {
1110
- style: styles.proFeatureItem,
1111
- children: [/*#__PURE__*/_jsx(CheckCircle2, {
1112
- size: 14,
1113
- color: buoyColors.primary
1114
- }), /*#__PURE__*/_jsx(Text, {
1115
- style: styles.proFeatureText,
1116
- children: "Priority Support"
1117
- })]
746
+ }), /*#__PURE__*/_jsx(NightFootnote, {
747
+ children: isPro ? "You have full access to all Buoy DevTools features." : "Upgrade to Pro to unlock all features and support development."
748
+ })), !isPro && /*#__PURE__*/_jsxs(_Fragment, {
749
+ children: [renderSection("Pro Features", /*#__PURE__*/_jsx(NightCard, {
750
+ children: /*#__PURE__*/_jsx(NightRows, {
751
+ children: ["Advanced Settings", "Priority Support"].map(feature => /*#__PURE__*/_jsxs(View, {
752
+ style: styles.featureRow,
753
+ children: [/*#__PURE__*/_jsx(CheckCircle2, {
754
+ size: 16,
755
+ color: night.color.accent
756
+ }), /*#__PURE__*/_jsx(Text, {
757
+ style: styles.rowLabel,
758
+ children: feature
1118
759
  })]
1119
- })
1120
- })]
1121
- }), /*#__PURE__*/_jsxs(TouchableOpacity, {
1122
- style: styles.proUpgradeBtn,
1123
- onPress: () => setShowLicenseModal(true),
1124
- activeOpacity: 0.8,
1125
- children: [/*#__PURE__*/_jsx(Zap, {
1126
- size: 16,
1127
- color: buoyColors.base
1128
- }), /*#__PURE__*/_jsx(Text, {
1129
- style: styles.proUpgradeBtnText,
1130
- children: "Upgrade to Pro"
1131
- })]
760
+ }, feature))
761
+ })
762
+ })), /*#__PURE__*/_jsx(NightButton, {
763
+ label: "Upgrade to Pro",
764
+ variant: "primary",
765
+ size: "lg",
766
+ onPress: () => setShowLicenseModal(true)
1132
767
  })]
1133
- })
768
+ })]
1134
769
  })]
1135
770
  }), /*#__PURE__*/_jsx(LicenseEntryModal, {
1136
771
  visible: showLicenseModal,
@@ -1141,27 +776,25 @@ export const DevToolsSettingsModal = ({
1141
776
  return /*#__PURE__*/_jsx(JsModal, {
1142
777
  visible: visible,
1143
778
  onClose: onClose,
779
+ variant: "night",
1144
780
  header: {
1145
781
  showToggleButton: false,
1146
- customContent: /*#__PURE__*/_jsxs(ModalHeader, {
1147
- children: [/*#__PURE__*/_jsx(ModalHeader.Content, {
1148
- title: "",
1149
- noMargin: true,
1150
- children: /*#__PURE__*/_jsx(TabSelector, {
1151
- tabs: [{
1152
- key: "floating",
1153
- label: "FLOATING"
1154
- }, {
1155
- key: "settings",
1156
- label: "SETTINGS"
1157
- }, {
1158
- key: "pro",
1159
- label: "PRO"
1160
- }],
1161
- activeTab: activeTab,
1162
- onTabChange: tab => setActiveTab(tab)
1163
- })
1164
- }), /*#__PURE__*/_jsx(ModalHeader.Actions, {})]
782
+ customContent: /*#__PURE__*/_jsx(View, {
783
+ style: styles.headerRow,
784
+ children: /*#__PURE__*/_jsx(NightSegmented, {
785
+ tabs: [{
786
+ key: "floating",
787
+ label: "Floating"
788
+ }, {
789
+ key: "settings",
790
+ label: "Settings"
791
+ }, {
792
+ key: "pro",
793
+ label: "Pro"
794
+ }],
795
+ activeKey: activeTab,
796
+ onChange: tab => setActiveTab(tab)
797
+ })
1165
798
  })
1166
799
  },
1167
800
  initialMode: "bottomSheet",
@@ -1236,6 +869,30 @@ const createDefaultsFromConfig = (defaultFloatingTools, defaultDialTools) => {
1236
869
  * When no saved settings exist, the hook uses the default configuration from
1237
870
  * the DefaultConfigProvider (if available) to determine initial tool states.
1238
871
  */
872
+ /**
873
+ * Session cache for the values the settings modal loads asynchronously on mount.
874
+ *
875
+ * Without it every open renders three times: once with defaults, then again as
876
+ * each of the persisted reads (settings, active tab, storage backend) resolves —
877
+ * and each of those re-renders rebuilds the whole tool list. The values cannot
878
+ * change behind our back (writes go through this module, and settingsBus
879
+ * broadcasts them), so after the first open we can render the real values
880
+ * immediately and only re-render if a read actually came back different.
881
+ */
882
+ let cachedSettings = null;
883
+ let cachedActiveTab = null;
884
+ let cachedStorageBackend = null;
885
+
886
+ /** Cheap structural equality — these objects are small and JSON-safe. */
887
+ const sameSettings = (a, b) => JSON.stringify(a) === JSON.stringify(b);
888
+
889
+ // Every surface that changes settings broadcasts on the bus, so listening here
890
+ // keeps the cache authoritative rather than merely last-seen — otherwise a
891
+ // change made while the modal was unmounted would show stale for one frame.
892
+ // Registered for the app's lifetime on purpose; there is nothing to unsubscribe.
893
+ settingsBus.addListener(payload => {
894
+ if (payload) cachedSettings = payload;
895
+ });
1239
896
  export const useDevToolsSettings = () => {
1240
897
  // Get team default configuration from context
1241
898
  const {
@@ -1296,800 +953,144 @@ export const useDevToolsSettings = () => {
1296
953
  const styles = StyleSheet.create({
1297
954
  container: {
1298
955
  flex: 1,
1299
- backgroundColor: buoyColors.base
956
+ // Transparent so JsModal's night star field shows through everything.
957
+ backgroundColor: "transparent"
1300
958
  },
1301
- // Header styles matching React Query modal exactly
1302
- headerContainer: {
1303
- flex: 1,
959
+ // Header — the segmented control, inset so it clears the absolutely
960
+ // positioned window controls on the right.
961
+ headerRow: {
1304
962
  flexDirection: "row",
1305
963
  alignItems: "center",
1306
- justifyContent: "space-between",
1307
- paddingHorizontal: 12
1308
- },
1309
- tabNavigationContainer: {
1310
- flex: 1,
1311
- flexDirection: "row",
1312
- backgroundColor: buoyColors.hover,
1313
- borderRadius: 6,
1314
- padding: 2,
1315
- borderWidth: 1,
1316
- borderColor: buoyColors.border,
1317
- justifyContent: "space-evenly"
1318
- },
1319
- tabButton: {
1320
- paddingHorizontal: 8,
1321
- paddingVertical: 5,
1322
- borderRadius: 4,
1323
- alignItems: "center",
1324
- justifyContent: "center",
1325
- flex: 1,
1326
- marginHorizontal: 1
1327
- },
1328
- tabButtonActive: {
1329
- backgroundColor: buoyColors.primary + "20",
1330
- borderWidth: 1,
1331
- borderColor: buoyColors.primary
1332
- },
1333
- tabButtonInactive: {
1334
- backgroundColor: "transparent"
964
+ paddingLeft: night.gutter,
965
+ paddingRight: 64,
966
+ paddingBottom: 8
1335
967
  },
1336
- tabButtonText: {
1337
- fontSize: 12,
1338
- fontWeight: "600",
1339
- letterSpacing: 0.5,
1340
- fontFamily: "monospace",
1341
- textTransform: "uppercase"
1342
- },
1343
- tabButtonTextActive: {
1344
- color: buoyColors.primary
1345
- },
1346
- tabButtonTextInactive: {
1347
- color: buoyColors.textMuted
1348
- },
1349
- // Scroll content
968
+ // Scroll content — labeled groups on a 24pt rhythm (Everlights create screen)
1350
969
  scrollContent: {
1351
970
  flex: 1
1352
971
  },
1353
972
  scrollContainer: {
1354
- paddingTop: 16,
1355
- paddingBottom: 24
1356
- },
1357
- // Sections
1358
- section: {
1359
- marginHorizontal: 16,
1360
- marginBottom: 24
973
+ paddingHorizontal: night.gutter,
974
+ paddingTop: 8,
975
+ paddingBottom: 24,
976
+ gap: night.groupGap
1361
977
  },
1362
- sectionHeader: {
978
+ // Rows
979
+ toolRow: {
1363
980
  flexDirection: "row",
1364
981
  alignItems: "center",
1365
- marginBottom: 12,
1366
- paddingHorizontal: 4
1367
- },
1368
- sectionIndicator: {
1369
- width: 3,
1370
- height: 16,
1371
- borderRadius: 2,
1372
- backgroundColor: buoyColors.primary,
1373
- marginRight: 8,
1374
- shadowColor: buoyColors.primary,
1375
- shadowOffset: {
1376
- width: 0,
1377
- height: 0
1378
- },
1379
- shadowOpacity: 0.6,
1380
- shadowRadius: 4
1381
- },
1382
- sectionTitle: {
1383
- flex: 1,
1384
- color: buoyColors.primary,
1385
- fontSize: 13,
1386
- fontWeight: "700",
1387
- letterSpacing: 1.2
1388
- },
1389
- sectionCount: {
1390
- color: buoyColors.textSecondary,
1391
- fontSize: 11,
1392
- opacity: 0.7
1393
- },
1394
- // Tool Cards - Clean unified Buoy theme
1395
- glassCard: {
1396
- borderRadius: 12,
1397
- paddingVertical: 12,
1398
- paddingHorizontal: 14,
1399
- backgroundColor: buoyColors.card,
1400
- borderWidth: 1,
1401
- borderColor: buoyColors.border
1402
- },
1403
- glassCardActive: {
1404
- borderColor: buoyColors.primary + "50",
1405
- backgroundColor: buoyColors.primary + "08"
982
+ gap: 12,
983
+ paddingHorizontal: night.rowPadH,
984
+ paddingVertical: 12
1406
985
  },
1407
- glassCardInner: {
986
+ settingRow: {
1408
987
  flexDirection: "row",
1409
988
  alignItems: "center",
1410
- gap: 12
1411
- },
1412
- iconContainer: {
1413
- width: 32,
1414
- height: 32,
1415
- borderRadius: 8,
1416
- backgroundColor: buoyColors.hover,
1417
- alignItems: "center",
1418
- justifyContent: "center"
1419
- },
1420
- iconContainerActive: {
1421
- backgroundColor: buoyColors.primary + "20"
1422
- },
1423
- toolInfo: {
1424
- flex: 1
1425
- },
1426
- toolName: {
1427
- color: buoyColors.text,
1428
- fontWeight: "600",
1429
- fontSize: 13
1430
- },
1431
- toolNameActive: {
1432
- color: buoyColors.text
1433
- },
1434
- toolDescription: {
1435
- color: buoyColors.textMuted,
1436
- fontSize: 11,
1437
- marginTop: 2
1438
- },
1439
- pillToggle: {
1440
- paddingHorizontal: 14,
1441
- paddingVertical: 6,
1442
- borderRadius: 6,
1443
- borderWidth: 1,
1444
- minWidth: 48,
1445
- alignItems: "center"
1446
- },
1447
- pillToggleOn: {
1448
- backgroundColor: buoyColors.primary + "20",
1449
- borderColor: buoyColors.primary
1450
- },
1451
- pillToggleOff: {
1452
- backgroundColor: buoyColors.hover,
1453
- borderColor: buoyColors.border
1454
- },
1455
- pillToggleText: {
1456
- fontWeight: "600",
1457
- fontSize: 11,
1458
- letterSpacing: 0.5
1459
- },
1460
- pillToggleTextOn: {
1461
- color: buoyColors.primary
1462
- },
1463
- pillToggleTextOff: {
1464
- color: buoyColors.textMuted
1465
- },
1466
- closeButton: {
1467
- marginLeft: 8,
1468
- paddingHorizontal: 10,
1469
- paddingVertical: 6,
1470
- borderRadius: 6,
1471
- backgroundColor: buoyColors.error + "1A",
1472
- borderWidth: 1,
1473
- borderColor: buoyColors.error + "33"
1474
- },
1475
- closeButtonText: {
1476
- color: buoyColors.error,
1477
- fontSize: 14,
1478
- fontWeight: "700",
1479
- letterSpacing: 0.5
1480
- },
1481
- // Expandable Settings Card styles
1482
- expandableCard: {
1483
- backgroundColor: buoyColors.card,
1484
- borderRadius: 8,
1485
- borderWidth: 1,
1486
- borderColor: buoyColors.border,
1487
- padding: 12
989
+ gap: 10,
990
+ paddingHorizontal: night.rowPadH,
991
+ paddingVertical: night.rowPadV,
992
+ minHeight: 52
1488
993
  },
1489
- expandableCardHeader: {
994
+ featureRow: {
1490
995
  flexDirection: "row",
1491
996
  alignItems: "center",
1492
- gap: 12
997
+ gap: 10,
998
+ paddingHorizontal: night.rowPadH,
999
+ paddingVertical: 12
1493
1000
  },
1494
- expandableCardCategory: {
1495
- backgroundColor: buoyColors.primary + "20",
1496
- borderWidth: 1,
1497
- borderColor: buoyColors.primary + "40",
1498
- borderRadius: 4,
1499
- paddingHorizontal: 8,
1500
- paddingVertical: 4
1001
+ rowDisabled: {
1002
+ opacity: 0.4
1501
1003
  },
1502
- expandableCardCategoryText: {
1503
- fontSize: 10,
1504
- fontWeight: "700",
1505
- color: buoyColors.primary,
1506
- letterSpacing: 0.5
1004
+ toolIconSlot: {
1005
+ width: 24,
1006
+ alignItems: "center"
1507
1007
  },
1508
- expandableCardTitle: {
1008
+ rowInfo: {
1509
1009
  flex: 1,
1510
- paddingHorizontal: 4
1511
- },
1512
- expandableCardTitleText: {
1513
- fontFamily: "monospace",
1514
- fontSize: 12,
1515
- fontWeight: "700",
1516
- color: buoyColors.text
1517
- },
1518
- expandableCardSubtitle: {
1519
- fontSize: 10,
1520
- color: buoyColors.textMuted,
1521
- marginTop: 2
1522
- },
1523
- expandableCardActions: {
1524
- flexDirection: "row",
1525
- alignItems: "center",
1526
- gap: 8
1527
- },
1528
- expandableCardBody: {
1529
- marginTop: 12,
1530
- paddingTop: 12,
1531
- borderTopWidth: 1,
1532
- borderTopColor: buoyColors.border + "50"
1533
- },
1534
- expandableCardSection: {
1535
- marginBottom: 12
1536
- },
1537
- expandableCardSectionTitle: {
1538
- fontSize: 10,
1539
- fontWeight: "700",
1540
- color: buoyColors.primary,
1541
- letterSpacing: 1,
1542
- marginBottom: 6
1543
- },
1544
- expandableCardSectionText: {
1545
- fontSize: 12,
1546
- color: buoyColors.textSecondary,
1547
- lineHeight: 18
1548
- },
1549
- // Storage Status Card styles
1550
- storageStatusCard: {
1551
- backgroundColor: buoyColors.card,
1552
- borderRadius: 8,
1553
- borderWidth: 1,
1554
- borderColor: buoyColors.border,
1555
- padding: 12,
1556
- marginBottom: 10
1557
- },
1558
- storageStatusHeader: {
1559
- flexDirection: "row",
1560
- alignItems: "center",
1561
- gap: 12,
1562
- marginBottom: 8
1563
- },
1564
- storageStatusIcon: {
1565
- width: 36,
1566
- height: 36,
1567
- borderRadius: 8,
1568
- backgroundColor: buoyColors.hover,
1569
- alignItems: "center",
1570
- justifyContent: "center"
1010
+ gap: 2
1571
1011
  },
1572
- storageStatusInfo: {
1573
- flex: 1
1012
+ rowLabel: {
1013
+ color: night.color.text,
1014
+ fontSize: night.font.row,
1015
+ fontWeight: "500"
1574
1016
  },
1575
- storageStatusLabel: {
1576
- fontSize: 10,
1577
- fontWeight: "700",
1578
- color: buoyColors.textMuted,
1579
- letterSpacing: 1,
1580
- marginBottom: 4
1017
+ rowCaption: {
1018
+ color: night.color.textTertiary,
1019
+ fontSize: night.font.label
1581
1020
  },
1582
- storageStatusBadgeContainer: {
1583
- flexDirection: "row",
1584
- alignItems: "center",
1021
+ // Expanded body under a global-setting row
1022
+ settingRowBody: {
1023
+ paddingHorizontal: night.rowPadH,
1024
+ paddingBottom: night.rowPadV,
1585
1025
  gap: 6
1586
1026
  },
1587
- storageStatusBadge: {
1588
- paddingHorizontal: 8,
1589
- paddingVertical: 4,
1590
- borderRadius: 4,
1591
- borderWidth: 1
1592
- },
1593
- storageStatusBadgeText: {
1594
- fontSize: 11,
1595
- fontWeight: "700",
1596
- letterSpacing: 0.5
1027
+ settingRowBodyText: {
1028
+ color: night.color.textSecondary,
1029
+ fontSize: night.font.caption,
1030
+ lineHeight: night.font.caption * 1.45
1597
1031
  },
1598
- storageStatusDescription: {
1599
- fontSize: 11,
1600
- color: buoyColors.textMuted,
1601
- lineHeight: 16,
1602
- marginBottom: 12
1603
- },
1604
- clearStorageButton: {
1605
- flexDirection: "row",
1606
- alignItems: "center",
1607
- justifyContent: "center",
1608
- gap: 8,
1609
- paddingVertical: 10,
1610
- paddingHorizontal: 16,
1611
- borderRadius: 6,
1612
- backgroundColor: buoyColors.error + "15",
1613
- borderWidth: 1,
1614
- borderColor: buoyColors.error + "40"
1032
+ settingRowBodyHint: {
1033
+ color: night.color.textTertiary,
1034
+ fontSize: night.font.caption,
1035
+ lineHeight: night.font.caption * 1.45
1615
1036
  },
1616
- clearStorageButtonDisabled: {
1617
- opacity: 0.5
1037
+ bodyWarning: {
1038
+ color: night.color.warning
1618
1039
  },
1619
- clearStorageButtonSuccess: {
1620
- backgroundColor: buoyColors.success + "15",
1621
- borderColor: buoyColors.success + "40"
1040
+ bodyDanger: {
1041
+ color: night.color.danger
1622
1042
  },
1623
- clearStorageButtonText: {
1624
- fontSize: 12,
1625
- fontWeight: "700",
1626
- color: buoyColors.error,
1627
- letterSpacing: 0.5
1628
- },
1629
- // Advice hints
1630
- adviceHintsContainer: {
1631
- gap: 8,
1632
- marginBottom: 12
1633
- },
1634
- adviceHint: {
1635
- flexDirection: "row",
1636
- alignItems: "flex-start",
1637
- gap: 8,
1638
- backgroundColor: buoyColors.warning + "10",
1639
- borderRadius: 6,
1640
- padding: 10,
1641
- marginBottom: 8
1642
- },
1643
- adviceHintText: {
1644
- flex: 1,
1645
- fontSize: 11,
1646
- color: buoyColors.textSecondary,
1647
- lineHeight: 16
1043
+ // Buttons that live inside a card
1044
+ cardButtonWrap: {
1045
+ paddingHorizontal: night.cardPad,
1046
+ paddingTop: 10,
1047
+ paddingBottom: night.cardPad
1648
1048
  },
1649
- adviceHintBold: {
1650
- fontWeight: "700",
1651
- color: buoyColors.text
1652
- },
1653
- adviceHintCode: {
1654
- fontFamily: "monospace",
1655
- fontSize: 10,
1656
- color: buoyColors.info,
1657
- backgroundColor: buoyColors.info + "15",
1658
- paddingHorizontal: 4,
1659
- borderRadius: 3
1660
- },
1661
- // Expanded storage content
1662
- storageExpandedContent: {
1663
- marginTop: 12,
1664
- paddingTop: 12,
1665
- borderTopWidth: 1,
1666
- borderTopColor: buoyColors.border + "50"
1049
+ // Storage expanded key list
1050
+ storageExpanded: {
1051
+ paddingHorizontal: night.rowPadH,
1052
+ paddingVertical: 12,
1053
+ gap: 8
1667
1054
  },
1668
1055
  storageExpandedHeader: {
1669
1056
  flexDirection: "row",
1670
- alignItems: "center",
1671
- gap: 8,
1672
- marginBottom: 10
1057
+ alignItems: "center"
1673
1058
  },
1674
1059
  storageExpandedTitle: {
1675
- fontSize: 10,
1676
- fontWeight: "700",
1677
- color: buoyColors.primary,
1678
- letterSpacing: 1,
1679
- flex: 1
1060
+ flex: 1,
1061
+ color: night.color.textSecondary,
1062
+ fontSize: night.font.micro,
1063
+ fontWeight: "600",
1064
+ letterSpacing: 0.5
1680
1065
  },
1681
1066
  storageExpandedCount: {
1682
- fontSize: 10,
1683
- color: buoyColors.textMuted
1067
+ color: night.color.textTertiary,
1068
+ fontSize: night.font.micro
1684
1069
  },
1685
1070
  storageKeysList: {
1686
- gap: 4,
1687
1071
  maxHeight: 150
1688
1072
  },
1689
1073
  storageKeyItem: {
1690
- backgroundColor: buoyColors.hover,
1691
- borderRadius: 4,
1692
- paddingHorizontal: 8,
1693
- paddingVertical: 6,
1694
- borderWidth: 1,
1695
- borderColor: buoyColors.border + "50"
1696
- },
1697
- storageKeyItemEmpty: {
1698
- fontSize: 11,
1699
- color: buoyColors.textMuted,
1700
- fontStyle: "italic",
1701
- paddingVertical: 8
1074
+ backgroundColor: night.color.surfaceElevated,
1075
+ borderRadius: night.radius.segment,
1076
+ paddingHorizontal: 10,
1077
+ paddingVertical: 7,
1078
+ marginBottom: 4
1702
1079
  },
1703
1080
  storageKeyText: {
1704
- fontSize: 11,
1705
- color: buoyColors.textSecondary,
1081
+ color: night.color.textSecondary,
1082
+ fontSize: night.font.micro,
1706
1083
  fontFamily: "monospace"
1707
1084
  },
1708
- // Export Config Card styles
1709
- exportConfigCard: {
1710
- backgroundColor: buoyColors.card,
1711
- borderRadius: 8,
1712
- borderWidth: 1,
1713
- borderColor: buoyColors.success + "40",
1714
- padding: 12,
1715
- marginTop: 10
1716
- },
1717
- exportConfigHeader: {
1718
- flexDirection: "row",
1719
- alignItems: "center",
1720
- gap: 12
1721
- },
1722
- exportConfigIconContainer: {
1723
- width: 36,
1724
- height: 36,
1725
- borderRadius: 8,
1726
- backgroundColor: buoyColors.success + "15",
1727
- alignItems: "center",
1728
- justifyContent: "center"
1729
- },
1730
- exportConfigInfo: {
1731
- flex: 1
1732
- },
1733
- exportConfigLabel: {
1734
- fontSize: 12,
1735
- fontWeight: "700",
1736
- color: buoyColors.success,
1737
- letterSpacing: 0.5
1738
- },
1739
- exportConfigHint: {
1740
- fontSize: 10,
1741
- color: buoyColors.textMuted,
1742
- marginTop: 2
1743
- },
1744
- exportConfigActions: {
1745
- flexDirection: "row",
1746
- alignItems: "center"
1747
- },
1748
- exportHintBanner: {
1749
- flexDirection: "row",
1750
- alignItems: "flex-start",
1751
- gap: 8,
1752
- backgroundColor: buoyColors.warning + "10",
1753
- borderRadius: 6,
1754
- padding: 10,
1755
- marginTop: 12
1756
- },
1757
- exportHintText: {
1758
- flex: 1,
1759
- fontSize: 11,
1760
- color: buoyColors.textSecondary,
1761
- lineHeight: 16
1762
- },
1763
- exportHintBold: {
1764
- fontWeight: "700",
1765
- color: buoyColors.warning
1766
- },
1767
- exportCodeContainer: {
1768
- marginTop: 12,
1769
- paddingTop: 12,
1770
- borderTopWidth: 1,
1771
- borderTopColor: buoyColors.border + "50"
1772
- },
1773
- exportCodeHeader: {
1774
- flexDirection: "row",
1775
- alignItems: "center",
1776
- gap: 8,
1777
- marginBottom: 8
1778
- },
1779
- exportCodeTitle: {
1780
- fontSize: 10,
1781
- fontWeight: "700",
1782
- color: buoyColors.success,
1783
- letterSpacing: 1
1784
- },
1785
- exportCodeBlock: {
1786
- backgroundColor: buoyColors.hover,
1787
- borderRadius: 6,
1788
- padding: 12,
1789
- borderWidth: 1,
1790
- borderColor: buoyColors.border + "50"
1791
- },
1792
- exportCodeText: {
1793
- fontSize: 11,
1794
- color: buoyColors.text,
1795
- fontFamily: "monospace",
1796
- lineHeight: 18
1797
- },
1798
- exportCodeDescription: {
1799
- fontSize: 10,
1800
- color: buoyColors.textMuted,
1801
- marginTop: 8,
1802
- lineHeight: 14
1803
- },
1804
- exportCodeInline: {
1805
- fontFamily: "monospace",
1806
- fontSize: 10,
1807
- color: buoyColors.info,
1808
- backgroundColor: buoyColors.info + "15",
1809
- paddingHorizontal: 4,
1810
- borderRadius: 3
1811
- },
1812
- exportCopyButton: {
1813
- flexDirection: "row",
1814
- alignItems: "center",
1815
- justifyContent: "center",
1816
- gap: 8,
1817
- paddingVertical: 10,
1818
- paddingHorizontal: 16,
1819
- borderRadius: 6,
1820
- backgroundColor: buoyColors.success + "15",
1821
- borderWidth: 1,
1822
- borderColor: buoyColors.success + "40",
1823
- marginTop: 12
1824
- },
1825
- exportCopyButtonSuccess: {
1826
- backgroundColor: buoyColors.success + "25",
1827
- borderColor: buoyColors.success + "60"
1828
- },
1829
- exportCopyButtonText: {
1830
- fontSize: 12,
1831
- fontWeight: "700",
1832
- color: buoyColors.success,
1833
- letterSpacing: 0.5
1834
- },
1835
- // JSON-like viewer styles (syntax highlighting)
1836
- exportJsonBlock: {
1837
- backgroundColor: buoyColors.hover,
1838
- borderRadius: 6,
1839
- padding: 12,
1840
- borderWidth: 1,
1841
- borderColor: buoyColors.border + "50",
1842
- flexDirection: "row",
1843
- flexWrap: "wrap",
1844
- alignItems: "center",
1845
- gap: 4
1846
- },
1847
- exportJsonProp: {
1848
- fontSize: 12,
1849
- fontFamily: "monospace",
1850
- color: buoyColors.info,
1851
- // Cyan for prop names
1852
- fontWeight: "600"
1853
- },
1854
- exportJsonEquals: {
1855
- fontSize: 12,
1856
- fontFamily: "monospace",
1857
- color: buoyColors.textMuted
1858
- },
1859
- exportJsonBracket: {
1860
- fontSize: 12,
1861
- fontFamily: "monospace",
1862
- color: buoyColors.warning // Yellow for JSX brackets
1863
- },
1864
- exportJsonArrayBracket: {
1865
- fontSize: 12,
1866
- fontFamily: "monospace",
1867
- color: buoyColors.text
1868
- },
1869
- exportJsonArrayContent: {
1870
- flexDirection: "row",
1871
- flexWrap: "wrap",
1872
- alignItems: "center",
1873
- gap: 2
1874
- },
1875
- exportJsonArrayItem: {
1876
- flexDirection: "row",
1877
- alignItems: "center"
1878
- },
1879
- exportJsonString: {
1880
- fontSize: 12,
1881
- fontFamily: "monospace",
1882
- color: buoyColors.success // Green for strings
1883
- },
1884
- exportJsonComma: {
1885
- fontSize: 12,
1886
- fontFamily: "monospace",
1887
- color: buoyColors.textMuted,
1888
- marginRight: 4
1889
- },
1890
- exportJsonComment: {
1891
- fontSize: 12,
1892
- fontFamily: "monospace",
1893
- color: buoyColors.textMuted,
1894
- fontStyle: "italic"
1895
- },
1896
- // Pro tab styles - using buoyColors for consistency with other modals
1897
- proContainer: {
1898
- paddingHorizontal: 16,
1899
- paddingTop: 16,
1900
- paddingBottom: 24
1901
- },
1902
- proSection: {
1903
- backgroundColor: buoyColors.card,
1904
- borderRadius: 8,
1905
- borderWidth: 1,
1906
- borderColor: buoyColors.border,
1907
- marginBottom: 12,
1908
- overflow: "hidden"
1909
- },
1910
- proSectionContent: {
1911
- paddingHorizontal: 16,
1912
- paddingTop: 8,
1913
- paddingBottom: 16
1914
- },
1915
- proStatsRow: {
1916
- flexDirection: "row",
1917
- justifyContent: "center",
1918
- alignItems: "center",
1919
- paddingVertical: 8
1920
- },
1921
- proStatItem: {
1922
- alignItems: "center",
1923
- paddingHorizontal: 20
1924
- },
1925
- proStatValue: {
1926
- fontSize: 20,
1927
- fontWeight: "700",
1928
- color: buoyColors.text
1929
- },
1930
- proStatValueDanger: {
1931
- color: buoyColors.error
1932
- },
1933
- proStatLabel: {
1934
- fontSize: 10,
1935
- color: buoyColors.textMuted,
1936
- letterSpacing: 0.5,
1937
- marginTop: 2
1938
- },
1939
- proStatDivider: {
1940
- width: 1,
1941
- height: 24,
1942
- backgroundColor: buoyColors.border
1943
- },
1944
- proEmptyState: {
1945
- alignItems: "center",
1946
- paddingVertical: 20,
1947
- gap: 8
1948
- },
1949
- proEmptyStateText: {
1950
- fontSize: 11,
1951
- color: buoyColors.textMuted,
1085
+ storageKeyEmpty: {
1086
+ color: night.color.textTertiary,
1087
+ fontSize: night.font.caption,
1952
1088
  fontStyle: "italic"
1953
1089
  },
1954
- proErrorState: {
1955
- flexDirection: "row",
1956
- alignItems: "center",
1957
- gap: 8,
1958
- paddingVertical: 12,
1959
- paddingHorizontal: 12,
1960
- backgroundColor: buoyColors.error + "15",
1961
- borderRadius: 6
1962
- },
1963
- proErrorStateText: {
1964
- fontSize: 11,
1965
- color: buoyColors.error,
1966
- flex: 1
1967
- },
1968
- proDevicesList: {
1969
- gap: 6
1970
- },
1971
- proDeviceRow: {
1972
- flexDirection: "row",
1973
- alignItems: "center",
1974
- paddingVertical: 8,
1975
- paddingHorizontal: 10,
1976
- backgroundColor: buoyColors.input,
1977
- borderRadius: 6,
1978
- borderWidth: 1,
1979
- borderColor: buoyColors.border
1980
- },
1981
- proDeviceRowCurrent: {
1982
- borderColor: buoyColors.success + "50",
1983
- backgroundColor: buoyColors.success + "08"
1984
- },
1985
- proDeviceIcon: {
1986
- width: 28,
1987
- height: 28,
1988
- borderRadius: 14,
1989
- backgroundColor: buoyColors.hover,
1990
- alignItems: "center",
1991
- justifyContent: "center"
1992
- },
1993
- proDeviceInfo: {
1994
- flex: 1,
1995
- marginLeft: 10
1996
- },
1997
- proDeviceNameRow: {
1998
- flexDirection: "row",
1999
- alignItems: "center",
2000
- gap: 6
2001
- },
2002
- proDeviceName: {
2003
- fontSize: 12,
2004
- fontWeight: "600",
2005
- color: buoyColors.text,
2006
- flexShrink: 1
2007
- },
2008
- proDeviceBadge: {
2009
- backgroundColor: buoyColors.success + "20",
2010
- paddingHorizontal: 5,
2011
- paddingVertical: 1,
2012
- borderRadius: 3
2013
- },
2014
- proDeviceBadgeText: {
2015
- fontSize: 8,
2016
- fontWeight: "700",
2017
- color: buoyColors.success,
2018
- letterSpacing: 0.3
2019
- },
2020
- proDeviceDetails: {
2021
- fontSize: 10,
2022
- color: buoyColors.textMuted,
2023
- marginTop: 2
2024
- },
2025
- proDeviceDate: {
2026
- fontSize: 9,
2027
- color: buoyColors.textMuted,
2028
- marginTop: 2
2029
- },
2030
- proDeviceRemoveBtn: {
2031
- padding: 6,
2032
- marginLeft: 4
2033
- },
2034
- proRegisterDeviceBtn: {
2035
- flexDirection: "row",
2036
- alignItems: "center",
2037
- justifyContent: "center",
2038
- gap: 8,
2039
- paddingVertical: 10,
2040
- paddingHorizontal: 16,
2041
- borderRadius: 8,
2042
- backgroundColor: buoyColors.primary + "15",
2043
- borderWidth: 1,
2044
- borderColor: buoyColors.primary + "40",
2045
- marginTop: 10
2046
- },
2047
- proRegisterDeviceBtnText: {
2048
- fontSize: 12,
2049
- fontWeight: "600",
2050
- color: buoyColors.primary
2051
- },
2052
- proFreeDescription: {
2053
- fontSize: 11,
2054
- color: buoyColors.textSecondary,
2055
- lineHeight: 16
2056
- },
2057
- proActiveDescription: {
2058
- fontSize: 11,
2059
- color: buoyColors.textSecondary,
2060
- lineHeight: 16
2061
- },
2062
- proFeaturesList: {
2063
- gap: 8
2064
- },
2065
- proFeatureItem: {
2066
- flexDirection: "row",
2067
- alignItems: "center",
2068
- gap: 10
2069
- },
2070
- proFeatureText: {
2071
- fontSize: 12,
2072
- color: buoyColors.text
2073
- },
2074
- proUpgradeBtn: {
2075
- flexDirection: "row",
2076
- alignItems: "center",
2077
- justifyContent: "center",
2078
- gap: 8,
2079
- backgroundColor: buoyColors.primary,
2080
- borderRadius: 10,
2081
- paddingVertical: 14,
2082
- shadowColor: buoyColors.primary,
2083
- shadowOffset: {
2084
- width: 0,
2085
- height: 0
2086
- },
2087
- shadowOpacity: 0.4,
2088
- shadowRadius: 8
2089
- },
2090
- proUpgradeBtnText: {
2091
- fontSize: 14,
2092
- fontWeight: "700",
2093
- color: buoyColors.base
1090
+ // Footnote inline code
1091
+ footnoteMono: {
1092
+ fontFamily: "monospace",
1093
+ fontSize: night.font.label,
1094
+ color: night.color.textSecondary
2094
1095
  }
2095
1096
  });