@tastic/hud 0.21.0 → 0.22.0

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.
package/dist/index.js CHANGED
@@ -200,6 +200,7 @@ __export(index_exports, {
200
200
  StatSection: () => StatSection,
201
201
  TriggerGaugeHost: () => TriggerGaugeHost,
202
202
  UpdateDialog: () => UpdateDialog,
203
+ WELL_SHADOW: () => WELL_SHADOW,
203
204
  getColorPopoverId: () => getColorPopoverId,
204
205
  getCpuDifficultyPopoverId: () => getCpuDifficultyPopoverId,
205
206
  getInlineColorPickerContentSize: () => getInlineColorPickerContentSize,
@@ -215,6 +216,7 @@ __export(index_exports, {
215
216
  useOverlayCardColors: () => useOverlayCardColors,
216
217
  usePopoverHost: () => usePopoverHost,
217
218
  useQuitConfirmation: () => useQuitConfirmation,
219
+ useWellColors: () => useWellColors,
218
220
  useZoneClampedAlign: () => useZoneClampedAlign
219
221
  });
220
222
  module.exports = __toCommonJS(index_exports);
@@ -226,16 +228,25 @@ var import_react_native3 = require("react-native");
226
228
  var import_react_native_paper2 = require("react-native-paper");
227
229
 
228
230
  // src/AchievementRow.tsx
229
- var import_auto_paper = require("@rific/auto-paper");
230
231
  var import_react_native = require("react-native");
231
232
  var import_react_native_paper = require("react-native-paper");
233
+
234
+ // src/wellSurface.ts
235
+ var import_auto_paper = require("@rific/auto-paper");
236
+ var WELL_SHADOW = "0px 1px 2px rgba(0, 0, 0, 0.3), 0px 1px 3px 1px rgba(0, 0, 0, 0.15)";
237
+ function useWellColors() {
238
+ const { colors } = (0, import_auto_paper.useAutoPaperTheme)();
239
+ return { bg: colors.surface, fg: colors.onSurface, fgMuted: colors.onSurfaceVariant };
240
+ }
241
+
242
+ // src/AchievementRow.tsx
232
243
  var import_jsx_runtime = require("react/jsx-runtime");
233
244
  var LOCKED_BADGE_COLOR = "rgba(128,128,128,0.3)";
234
245
  function AchievementRow({ icon, title, description, badgeColor, unlockedLabel, checkColor, progress, deviceMarker, fg: fgOverride, fgMuted: fgMutedOverride, sectionBg: sectionBgOverride }) {
235
- const { dark } = (0, import_auto_paper.useAutoPaperTheme)();
236
- const fg = fgOverride ?? (dark ? "#FFFFFF" : "#000000");
237
- const fgMuted = fgMutedOverride ?? (dark ? "rgba(255,255,255,0.5)" : "rgba(0,0,0,0.5)");
238
- const sectionBg = sectionBgOverride ?? (dark ? "rgba(255,255,255,0.06)" : "rgba(0,0,0,0.04)");
246
+ const well = useWellColors();
247
+ const fg = fgOverride ?? well.fg;
248
+ const fgMuted = fgMutedOverride ?? well.fgMuted;
249
+ const sectionBg = sectionBgOverride ?? well.bg;
239
250
  const unlocked = unlockedLabel !== void 0;
240
251
  return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_react_native.View, { style: [styles.row, { backgroundColor: sectionBg }], children: [
241
252
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react_native.View, { style: [styles.badge, { backgroundColor: badgeColor }], children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react_native_paper.Icon, { source: icon, size: 20, color: unlocked ? "#000000" : fgMuted }) }),
@@ -278,6 +289,7 @@ var styles = import_react_native.StyleSheet.create({
278
289
  row: {
279
290
  alignItems: "center",
280
291
  borderRadius: 12,
292
+ boxShadow: WELL_SHADOW,
281
293
  flexDirection: "row",
282
294
  gap: 12,
283
295
  padding: 12
@@ -299,13 +311,13 @@ var MONO_FONT = import_react_native2.Platform.select({ ios: "Menlo", android: "m
299
311
 
300
312
  // src/AchievementCatalogSection.tsx
301
313
  var import_jsx_runtime2 = require("react/jsx-runtime");
302
- function AchievementCatalogSection({ rows, label = "ALL ACHIEVEMENTS", fg, fgMuted: fgMutedOverride }) {
314
+ function AchievementCatalogSection({ rows, label = "ALL ACHIEVEMENTS", fg, fgMuted: fgMutedOverride, sectionBg }) {
303
315
  const { dark } = (0, import_auto_paper2.useAutoPaperTheme)();
304
316
  const fgMuted = fgMutedOverride ?? (dark ? "rgba(255,255,255,0.5)" : "rgba(0,0,0,0.5)");
305
317
  const tierAccent = (tierColor) => dark ? tierColor : (0, import_auto_paper2.getBlendedColor)("#000000", tierColor, 0.5);
306
318
  return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_react.Fragment, { children: [
307
319
  /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_react_native_paper2.Text, { variant: "labelMedium", style: [styles2.label, { color: fgMuted, fontFamily: MONO_FONT }], children: label }),
308
- rows.map((row) => /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(AchievementRow, { icon: row.icon, title: row.title, description: row.description, badgeColor: row.unlockedAt !== void 0 ? row.tierColor : LOCKED_BADGE_COLOR, checkColor: tierAccent(row.tierColor), unlockedLabel: row.unlockedLabel, progress: row.progress, deviceMarker: row.deviceMarker, fg, fgMuted: fgMutedOverride }, row.id))
320
+ rows.map((row) => /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(AchievementRow, { icon: row.icon, title: row.title, description: row.description, badgeColor: row.unlockedAt !== void 0 ? row.tierColor : LOCKED_BADGE_COLOR, checkColor: tierAccent(row.tierColor), unlockedLabel: row.unlockedLabel, progress: row.progress, deviceMarker: row.deviceMarker, fg, fgMuted: fgMutedOverride, sectionBg }, row.id))
309
321
  ] });
310
322
  }
311
323
  var styles2 = import_react_native3.StyleSheet.create({
@@ -360,14 +372,13 @@ var styles3 = import_react_native4.StyleSheet.create({
360
372
  });
361
373
 
362
374
  // src/StatRow.tsx
363
- var import_auto_paper4 = require("@rific/auto-paper");
364
375
  var import_react_native5 = require("react-native");
365
376
  var import_react_native_paper4 = require("react-native-paper");
366
377
  var import_jsx_runtime4 = require("react/jsx-runtime");
367
378
  function StatRow({ label, value, fg: fgOverride, fgMuted: fgMutedOverride }) {
368
- const { dark } = (0, import_auto_paper4.useAutoPaperTheme)();
369
- const fg = fgOverride ?? (dark ? "#FFFFFF" : "#000000");
370
- const fgMuted = fgMutedOverride ?? (dark ? "rgba(255,255,255,0.5)" : "rgba(0,0,0,0.5)");
379
+ const well = useWellColors();
380
+ const fg = fgOverride ?? well.fg;
381
+ const fgMuted = fgMutedOverride ?? well.fgMuted;
371
382
  return /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(import_react_native5.View, { style: styles4.row, children: [
372
383
  /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_react_native_paper4.Text, { variant: "bodyMedium", style: { color: fgMuted }, children: label }),
373
384
  /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_react_native_paper4.Text, { variant: "bodyMedium", style: [styles4.value, { color: fg }], children: value })
@@ -385,14 +396,13 @@ var styles4 = import_react_native5.StyleSheet.create({
385
396
  });
386
397
 
387
398
  // src/StatSection.tsx
388
- var import_auto_paper5 = require("@rific/auto-paper");
389
399
  var import_react_native6 = require("react-native");
390
400
  var import_react_native_paper5 = require("react-native-paper");
391
401
  var import_jsx_runtime5 = require("react/jsx-runtime");
392
402
  function StatSection({ label, fg: fgOverride, sectionBg: sectionBgOverride, children }) {
393
- const { dark } = (0, import_auto_paper5.useAutoPaperTheme)();
394
- const fg = fgOverride ?? (dark ? "#FFFFFF" : "#000000");
395
- const sectionBg = sectionBgOverride ?? (dark ? "rgba(255,255,255,0.06)" : "rgba(0,0,0,0.04)");
403
+ const well = useWellColors();
404
+ const fg = fgOverride ?? well.fg;
405
+ const sectionBg = sectionBgOverride ?? well.bg;
396
406
  return /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(import_react_native6.View, { style: [styles5.section, { backgroundColor: sectionBg }], children: [
397
407
  /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(import_react_native_paper5.Text, { variant: "labelMedium", style: [styles5.label, { color: fg }], children: label }),
398
408
  children
@@ -409,6 +419,7 @@ var styles5 = import_react_native6.StyleSheet.create({
409
419
  },
410
420
  section: {
411
421
  borderRadius: 12,
422
+ boxShadow: WELL_SHADOW,
412
423
  gap: 8,
413
424
  padding: 16
414
425
  }
@@ -416,16 +427,16 @@ var styles5 = import_react_native6.StyleSheet.create({
416
427
 
417
428
  // src/ActivityStatSection.tsx
418
429
  var import_jsx_runtime6 = require("react/jsx-runtime");
419
- function ActivityStatSection({ stats }) {
420
- return /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(StatSection, { label: "ACTIVITY", children: [
421
- /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(StatRow, { label: "Days Played", value: String(stats.distinctDaysPlayed) }),
422
- /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(StatRow, { label: "Day Streak", value: String(stats.currentDayStreak) }),
423
- /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(StatRow, { label: "Best Day Streak", value: String(stats.bestDayStreak) })
430
+ function ActivityStatSection({ stats, fg, fgMuted, sectionBg }) {
431
+ return /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(StatSection, { label: "ACTIVITY", fg, sectionBg, children: [
432
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(StatRow, { label: "Days Played", value: String(stats.distinctDaysPlayed), fg, fgMuted }),
433
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(StatRow, { label: "Day Streak", value: String(stats.currentDayStreak), fg, fgMuted }),
434
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(StatRow, { label: "Best Day Streak", value: String(stats.bestDayStreak), fg, fgMuted })
424
435
  ] });
425
436
  }
426
437
 
427
438
  // src/BaseSettingsDialog.tsx
428
- var import_auto_paper9 = require("@rific/auto-paper");
439
+ var import_auto_paper7 = require("@rific/auto-paper");
429
440
  var import_feedback_press2 = require("@rific/feedback-press");
430
441
  var import_updater = require("@rific/updater");
431
442
  var import_core4 = require("@tastic/core");
@@ -435,7 +446,7 @@ var import_react_native_paper9 = require("react-native-paper");
435
446
  var import_react_native_safe_area_context2 = require("react-native-safe-area-context");
436
447
 
437
448
  // src/ConfirmDialog.tsx
438
- var import_auto_paper8 = require("@rific/auto-paper");
449
+ var import_auto_paper6 = require("@rific/auto-paper");
439
450
  var import_feedback_press = require("@rific/feedback-press");
440
451
  var import_core2 = require("@tastic/core");
441
452
  var import_react3 = require("react");
@@ -443,7 +454,7 @@ var import_react_native11 = require("react-native");
443
454
  var import_react_native_paper7 = require("react-native-paper");
444
455
 
445
456
  // src/DialogHeader.tsx
446
- var import_auto_paper6 = require("@rific/auto-paper");
457
+ var import_auto_paper4 = require("@rific/auto-paper");
447
458
  var import_react_native7 = require("react-native");
448
459
  var import_react_native_paper6 = require("react-native-paper");
449
460
  var import_jsx_runtime7 = require("react/jsx-runtime");
@@ -452,7 +463,7 @@ function isCompactDialogHeight(height) {
452
463
  return height < COMPACT_DIALOG_HEIGHT;
453
464
  }
454
465
  function DialogHeader({ icon, title, color, compact = false, style }) {
455
- const { colors } = (0, import_auto_paper6.useAutoPaperTheme)();
466
+ const { colors } = (0, import_auto_paper4.useAutoPaperTheme)();
456
467
  const accent = color ?? colors.primary;
457
468
  return /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(import_react_native7.View, { style: [compact ? styles6.compact : styles6.full, style], children: [
458
469
  /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(import_react_native_paper6.Icon, { source: icon, size: compact ? COMPACT_ICON_SIZE : ICON_SIZE, color: accent }),
@@ -549,11 +560,11 @@ var overlayActionStyles = import_react_native8.StyleSheet.create({
549
560
  });
550
561
 
551
562
  // src/OverlaySurface.tsx
552
- var import_auto_paper7 = require("@rific/auto-paper");
563
+ var import_auto_paper5 = require("@rific/auto-paper");
553
564
  var import_react_native9 = require("react-native");
554
565
  var import_jsx_runtime8 = require("react/jsx-runtime");
555
566
  function useOverlayCardColors() {
556
- const { colors } = (0, import_auto_paper7.useAutoPaperTheme)();
567
+ const { colors } = (0, import_auto_paper5.useAutoPaperTheme)();
557
568
  return { fg: colors.onSurface, fgMuted: colors.onSurfaceVariant, cardBorder: colors.outlineVariant, backdrop: colors.backdrop };
558
569
  }
559
570
  function OverlayBackdrop({ children, style, onLayout }) {
@@ -561,9 +572,9 @@ function OverlayBackdrop({ children, style, onLayout }) {
561
572
  return /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(import_react_native9.View, { onLayout, style: [overlayStyles.overlay, { backgroundColor: backdrop }, style], children });
562
573
  }
563
574
  function OverlayCard({ children, style, ...rest }) {
564
- const { isV3, roundness } = (0, import_auto_paper7.useAutoPaperTheme)();
565
- const blur = (0, import_auto_paper7.useBlur)();
566
- return /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(import_auto_paper7.BlurView, { blur, style: [overlayStyles.card, styles7.card, { borderRadius: (isV3 ? 7 : 1) * roundness }, style], ...rest, children });
575
+ const { isV3, roundness } = (0, import_auto_paper5.useAutoPaperTheme)();
576
+ const blur = (0, import_auto_paper5.useBlur)();
577
+ return /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(import_auto_paper5.BlurView, { blur, style: [overlayStyles.card, styles7.card, { borderRadius: (isV3 ? 7 : 1) * roundness }, style], ...rest, children });
567
578
  }
568
579
  var styles7 = import_react_native9.StyleSheet.create({
569
580
  card: { overflow: "hidden" }
@@ -589,7 +600,7 @@ function useCompactDialog(rotation = 0) {
589
600
  // src/ConfirmDialog.tsx
590
601
  var import_jsx_runtime9 = require("react/jsx-runtime");
591
602
  function ConfirmDialog({ visible, title, message, confirmLabel, cancelLabel, onConfirm, onCancel, rotation = 0, icon = "help-circle-outline", destructive = false }) {
592
- const { colors } = (0, import_auto_paper8.useAutoPaperTheme)();
603
+ const { colors } = (0, import_auto_paper6.useAutoPaperTheme)();
593
604
  const { fg } = useOverlayCardColors();
594
605
  const { compact, onLayout } = useCompactDialog(rotation);
595
606
  const onCancelRef = (0, import_react3.useRef)(onCancel);
@@ -694,7 +705,7 @@ function SettingIcon({ source, color, containerColor }) {
694
705
  return /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(import_react_native13.View, { style: [styles10.iconBadge, { backgroundColor: containerColor }], children: /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(import_react_native_paper9.Icon, { source, size: 18, color }) });
695
706
  }
696
707
  function BaseSettingsDialog({ visible, onDismiss, rotation: rotationOverride, version, lockOrientation, onLockOrientationChange, deferBottomEdgeGestures, onDeferBottomEdgeGestures, keepAwake, onKeepAwakeChange, hideSound = false, hideHaptics = false, hideAppearance = false, hideUpdateCheck = false, onUpdateError, onShowHowToPlay, children }) {
697
- const { colors } = (0, import_auto_paper9.useAutoPaperTheme)();
708
+ const { colors } = (0, import_auto_paper7.useAutoPaperTheme)();
698
709
  const ambientRotation = (0, import_core4.useRotation)();
699
710
  const rotation = rotationOverride ?? ambientRotation;
700
711
  const window = (0, import_react_native13.useWindowDimensions)();
@@ -707,8 +718,8 @@ function BaseSettingsDialog({ visible, onDismiss, rotation: rotationOverride, ve
707
718
  const { settings: soundSettings, set: setSoundSettings } = (0, import_feedback_press2.useSoundSettings)();
708
719
  const sound = (0, import_react6.useContext)(import_feedback_press2.SoundContext);
709
720
  const { forceShort: forceHapticFeedback } = (0, import_feedback_press2.useVibration)();
710
- const { settings: themeSettings, set: setThemeSettings } = (0, import_auto_paper9.useThemeSettings)();
711
- const expoBlur = (0, import_auto_paper9.useBlurModule)();
721
+ const { settings: themeSettings, set: setThemeSettings } = (0, import_auto_paper7.useThemeSettings)();
722
+ const expoBlur = (0, import_auto_paper7.useBlurModule)();
712
723
  const [infoMessage, setInfoMessage] = (0, import_react6.useState)(null);
713
724
  const { manifest: updateManifest, onConfirm: onUpdateConfirm, respond: respondToUpdateConfirm } = useUpdateConfirmBridge();
714
725
  const { check, checking, updateReady } = (0, import_updater.useUpdater)({
@@ -731,8 +742,8 @@ function BaseSettingsDialog({ visible, onDismiss, rotation: rotationOverride, ve
731
742
  const showUpdateCheck = import_react_native13.Platform.OS !== "web" && !hideUpdateCheck;
732
743
  if (!opened) return null;
733
744
  return /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(import_react_native_paper9.Portal, { children: /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(RotatedFrame, { rotation, rotatedSafeArea: true, portalHost: true, passThrough: true, children: [
734
- /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(import_auto_paper9.Dialog, { visible, onDismiss, style: [styles10.dialog, { width: sheetWidth }], children: [
735
- /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(import_auto_paper9.Dialog.ScrollArea, { style: styles10.scrollArea, children: /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(import_auto_paper9.FadingScrollView, { contentContainerStyle: styles10.scrollContent, showsVerticalScrollIndicator: false, children: [
745
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(import_auto_paper7.Dialog, { visible, onDismiss, style: [styles10.dialog, { width: sheetWidth }], children: [
746
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(import_auto_paper7.Dialog.ScrollArea, { style: styles10.scrollArea, children: /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(import_auto_paper7.FadingScrollView, { contentContainerStyle: styles10.scrollContent, showsVerticalScrollIndicator: false, children: [
736
747
  /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(DialogHeader, { icon: "cog", title: "Settings", compact: compactHeader }),
737
748
  /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(import_react_native13.View, { style: styles10.content, children: [
738
749
  /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(import_react_native13.View, { style: styles10.toggleGroup, children: [
@@ -814,7 +825,7 @@ function BaseSettingsDialog({ visible, onDismiss, rotation: rotationOverride, ve
814
825
  ] }),
815
826
  showAppearance && /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(import_react_native13.View, { style: styles10.section, children: [
816
827
  /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(import_react_native_paper9.Text, { variant: "labelMedium", style: [styles10.sectionLabel, { color: colors.onSurfaceVariant }], children: "APPEARANCE" }),
817
- /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(import_auto_paper9.AutoAppearancePicker, { showLabels: false }),
828
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(import_auto_paper7.AutoAppearancePicker, { showLabels: false }),
818
829
  showBlur && /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(import_feedback_press2.TouchableRipple, { onPress: () => setThemeSettings({ blur: !themeSettings.blur }), style: styles10.toggleButton, accessibilityLabel: `Blur ${themeSettings.blur ? "on" : "off"}`, children: /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(import_react_native13.View, { style: styles10.toggleContent, children: [
819
830
  /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(SettingIcon, { source: themeSettings.blur ? "blur" : "blur-off", color: themeSettings.blur ? colors.tertiary : colors.onSurfaceVariant, containerColor: themeSettings.blur ? colors.tertiaryContainer : colors.surfaceVariant }),
820
831
  /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(import_react_native13.View, { style: styles10.flexShrink, children: [
@@ -925,7 +936,7 @@ var styles10 = import_react_native13.StyleSheet.create({
925
936
  });
926
937
 
927
938
  // src/BaseStatsScreen.tsx
928
- var import_auto_paper10 = require("@rific/auto-paper");
939
+ var import_auto_paper8 = require("@rific/auto-paper");
929
940
  var import_feedback_press3 = require("@rific/feedback-press");
930
941
  var import_core5 = require("@tastic/core");
931
942
  var import_react7 = require("react");
@@ -933,7 +944,7 @@ var import_react_native14 = require("react-native");
933
944
  var import_react_native_paper10 = require("react-native-paper");
934
945
  var import_jsx_runtime12 = require("react/jsx-runtime");
935
946
  function BaseStatsScreen({ rotation: rotationOverride, title = "Achievements", titleVariant = "headlineSmall", onBack, backLabel = "Back", hitSlop, insets, onReset, resetLabel = "Reset All Stats", resetConfirmTitle = "Reset Everything?", resetConfirmBody = "This permanently erases all stats and achievements. This cannot be undone.", fg: fgOverride, bg: bgOverride, children }) {
936
- const { dark, colors } = (0, import_auto_paper10.useAutoPaperTheme)();
947
+ const { dark, colors } = (0, import_auto_paper8.useAutoPaperTheme)();
937
948
  const ambientRotation = (0, import_core5.useRotation)();
938
949
  const rotation = rotationOverride ?? ambientRotation;
939
950
  const [confirmResetVisible, setConfirmResetVisible] = (0, import_react7.useState)(false);
@@ -945,7 +956,7 @@ function BaseStatsScreen({ rotation: rotationOverride, title = "Achievements", t
945
956
  /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(import_feedback_press3.IconButton, { icon: "arrow-left", iconColor: fg, size: 24, hitSlop, onPress: onBack, accessibilityLabel: backLabel }),
946
957
  /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(import_react_native_paper10.Text, { variant: titleVariant, style: [styles11.title, { color: fg }], children: title })
947
958
  ] }),
948
- /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)(import_auto_paper10.FadingScrollView, { style: styles11.scrollView, contentContainerStyle: [styles11.content, { paddingBottom: 32 + insets.bottom }], showsVerticalScrollIndicator: false, children: [
959
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)(import_auto_paper8.FadingScrollView, { style: styles11.scrollView, contentContainerStyle: [styles11.content, { paddingBottom: 32 + insets.bottom }], showsVerticalScrollIndicator: false, children: [
949
960
  children,
950
961
  showReset && /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(import_feedback_press3.Button, { mode: "contained", onPress: () => setConfirmResetVisible(true), buttonColor: colors.danger, textColor: colors.onDanger, style: styles11.resetButton, children: resetLabel })
951
962
  ] }),
@@ -1028,7 +1039,7 @@ var import_core9 = require("@tastic/core");
1028
1039
  var import_react_native20 = require("react-native");
1029
1040
 
1030
1041
  // src/SectionedDropdown.tsx
1031
- var import_auto_paper11 = require("@rific/auto-paper");
1042
+ var import_auto_paper9 = require("@rific/auto-paper");
1032
1043
  var import_feedback_press4 = require("@rific/feedback-press");
1033
1044
  var import_core8 = require("@tastic/core");
1034
1045
  var import_react_native19 = require("react-native");
@@ -1189,8 +1200,8 @@ function SectionedDropdown({ id, host, icon, accessibilityLabel, sections, accen
1189
1200
  const ambientRotation = (0, import_core8.useRotation)();
1190
1201
  const rotation = rotationOverride ?? ambientRotation;
1191
1202
  const menuBg = dark ? "#000000" : "#FFFFFF";
1192
- const menuBorderColor = (0, import_auto_paper11.getBlendedColor)(mutedColor, menuBg, 0.5);
1193
- const resolvedOnAccentColor = onAccentColor ?? (0, import_auto_paper11.getColorRoles)(accentColor, menuBg).onColor;
1203
+ const menuBorderColor = (0, import_auto_paper9.getBlendedColor)(mutedColor, menuBg, 0.5);
1204
+ const resolvedOnAccentColor = onAccentColor ?? (0, import_auto_paper9.getColorRoles)(accentColor, menuBg).onColor;
1194
1205
  const fg = dark ? "#FFFFFF" : "#000000";
1195
1206
  const open = host.openId === id;
1196
1207
  const estimatedHeight = MENU_VERTICAL_PADDING + sections.reduce((sum, section, sectionIndex) => {
@@ -1431,7 +1442,7 @@ var styles17 = import_react_native22.StyleSheet.create({
1431
1442
  });
1432
1443
 
1433
1444
  // src/InlineColorPicker.tsx
1434
- var import_auto_paper12 = require("@rific/auto-paper");
1445
+ var import_auto_paper10 = require("@rific/auto-paper");
1435
1446
  var import_feedback_press6 = require("@rific/feedback-press");
1436
1447
  var import_core10 = require("@tastic/core");
1437
1448
  var import_react_native23 = require("react-native");
@@ -1457,7 +1468,7 @@ function getInlineColorPickerContentSize(swatchCount, windowWidth, columns) {
1457
1468
  const height = SWATCHES_BORDER_WIDTH * 2 + SWATCHES_PADDING * 2 + SWATCH_SIZE * rows + SWATCHES_GAP * (rows - 1);
1458
1469
  return { width, height };
1459
1470
  }
1460
- function InlineColorPicker({ id, host, value, onChange, previewValue, swatches = import_auto_paper12.defaultColors, takenValue, allowSwapTaken, dark, align: forcedAlign, alignOverride, rotation: rotationOverride, icon = "palette", tag, labelFontFamily = MONO_FONT, autoDismiss = true, columns, size = DEFAULT_SIZE }) {
1471
+ function InlineColorPicker({ id, host, value, onChange, previewValue, swatches = import_auto_paper10.defaultColors, takenValue, allowSwapTaken, dark, align: forcedAlign, alignOverride, rotation: rotationOverride, icon = "palette", tag, labelFontFamily = MONO_FONT, autoDismiss = true, columns, size = DEFAULT_SIZE }) {
1461
1472
  const ambientRotation = (0, import_core10.useRotation)();
1462
1473
  const rotation = rotationOverride ?? ambientRotation;
1463
1474
  const menuBg = dark ? "#000000" : "#FFFFFF";
@@ -1470,7 +1481,7 @@ function InlineColorPicker({ id, host, value, onChange, previewValue, swatches =
1470
1481
  const align = forcedAlign ?? placement.align;
1471
1482
  const displayFor = previewValue ?? ((hex) => hex);
1472
1483
  const displayValue = displayFor(value);
1473
- const contrastColor = (0, import_auto_paper12.getContrastColor)(displayValue);
1484
+ const contrastColor = (0, import_auto_paper10.getContrastColor)(displayValue);
1474
1485
  const tagFontSize = size * (tag && EMOJI_PATTERN.test(tag) ? 0.6 : 0.4);
1475
1486
  return /* @__PURE__ */ (0, import_jsx_runtime22.jsxs)(import_react_native23.View, { style: [styles18.anchor, open && styles18.anchorOpen], children: [
1476
1487
  /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(import_feedback_press6.TouchableRipple, { onPress: () => host.toggle(id), borderless: true, style: [styles18.trigger, { backgroundColor: displayValue, borderRadius: size / 2, height: size, width: size }], children: /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(import_react_native23.View, { ref: triggerRef, collapsable: false, style: styles18.triggerMeasure, children: tag ? /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(import_react_native_paper13.Text, { style: [styles18.tagLabel, { color: contrastColor, fontFamily: labelFontFamily, fontSize: tagFontSize }], numberOfLines: 1, adjustsFontSizeToFit: true, children: tag }) : /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(import_react_native_paper13.Icon, { source: icon, size: size * 0.6, color: contrastColor }) }) }),
@@ -1489,7 +1500,7 @@ function InlineColorPicker({ id, host, value, onChange, previewValue, swatches =
1489
1500
  },
1490
1501
  borderless: true,
1491
1502
  style: [styles18.swatch, { backgroundColor: swatchDisplay }, selected && styles18.swatchSelected, taken && !swappable && styles18.swatchTaken],
1492
- children: selected ? /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(import_react_native_paper13.Icon, { source: "check", size: 16, color: (0, import_auto_paper12.getContrastColor)(swatchDisplay) }) : swappable ? /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(import_react_native_paper13.Icon, { source: "swap-horizontal", size: 16, color: (0, import_auto_paper12.getContrastColor)(swatchDisplay) }) : taken ? /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(import_react_native_paper13.Icon, { source: "close", size: 16, color: (0, import_auto_paper12.getContrastColor)(swatchDisplay) }) : /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(import_react_native23.View, {})
1503
+ children: selected ? /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(import_react_native_paper13.Icon, { source: "check", size: 16, color: (0, import_auto_paper10.getContrastColor)(swatchDisplay) }) : swappable ? /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(import_react_native_paper13.Icon, { source: "swap-horizontal", size: 16, color: (0, import_auto_paper10.getContrastColor)(swatchDisplay) }) : taken ? /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(import_react_native_paper13.Icon, { source: "close", size: 16, color: (0, import_auto_paper10.getContrastColor)(swatchDisplay) }) : /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(import_react_native23.View, {})
1493
1504
  },
1494
1505
  swatch.value
1495
1506
  );
@@ -1568,7 +1579,7 @@ var styles18 = import_react_native23.StyleSheet.create({
1568
1579
  });
1569
1580
 
1570
1581
  // src/LabeledDropdown.tsx
1571
- var import_auto_paper13 = require("@rific/auto-paper");
1582
+ var import_auto_paper11 = require("@rific/auto-paper");
1572
1583
  var import_feedback_press7 = require("@rific/feedback-press");
1573
1584
  var import_react_native24 = require("react-native");
1574
1585
  var import_react_native_paper14 = require("react-native-paper");
@@ -1602,7 +1613,7 @@ function LabeledDropdown({ id, host, options, value, onChange, color, dark, alig
1602
1613
  ] }) }),
1603
1614
  /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(PopoverBody, { visible: open && measured, align, verticalAlign, caretColor: menuBg, caretBorderColor: color, caretBorderWidth: 2, triggerSize: TRIGGER_HEIGHT, children: /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(import_react_native24.ScrollView, { style: [styles19.menu, { backgroundColor: menuBg, borderColor: color, width: POPOVER_WIDTH, maxHeight }], contentContainerStyle: styles19.menuContent, showsVerticalScrollIndicator: false, children: options.map((option) => {
1604
1615
  const isSelected = option.value === value;
1605
- const onColor = (0, import_auto_paper13.getContrastColor)(color);
1616
+ const onColor = (0, import_auto_paper11.getContrastColor)(color);
1606
1617
  return /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(import_feedback_press7.TouchableRipple, { onPress: () => handleSelect(option), style: [styles19.row, isSelected && { backgroundColor: color }], children: /* @__PURE__ */ (0, import_jsx_runtime23.jsxs)(import_react_native24.View, { style: styles19.rowInner, children: [
1607
1618
  option.icon && /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(import_react_native_paper14.Icon, { source: option.icon, size: 18, color: isSelected ? onColor : fg }),
1608
1619
  /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(import_react_native_paper14.Text, { style: [styles19.rowLabel, { color: isSelected ? onColor : fg }], numberOfLines: 1, children: option.label })
@@ -1942,6 +1953,7 @@ function useQuitConfirmation(hasProgress, onConfirmedBack) {
1942
1953
  StatSection,
1943
1954
  TriggerGaugeHost,
1944
1955
  UpdateDialog,
1956
+ WELL_SHADOW,
1945
1957
  getColorPopoverId,
1946
1958
  getCpuDifficultyPopoverId,
1947
1959
  getInlineColorPickerContentSize,
@@ -1957,5 +1969,6 @@ function useQuitConfirmation(hasProgress, onConfirmedBack) {
1957
1969
  useOverlayCardColors,
1958
1970
  usePopoverHost,
1959
1971
  useQuitConfirmation,
1972
+ useWellColors,
1960
1973
  useZoneClampedAlign
1961
1974
  });
package/dist/index.mjs CHANGED
@@ -17,9 +17,11 @@ import {
17
17
  StatRow,
18
18
  StatSection,
19
19
  UpdateDialog,
20
+ WELL_SHADOW,
20
21
  getStaggeredWordDuration,
21
- useCompactDialog
22
- } from "./chunk-UOA35OWJ.mjs";
22
+ useCompactDialog,
23
+ useWellColors
24
+ } from "./chunk-N7A7W72S.mjs";
23
25
  import {
24
26
  COMPACT_DIALOG_HEIGHT,
25
27
  DialogHeader,
@@ -915,6 +917,7 @@ export {
915
917
  StatSection,
916
918
  TriggerGaugeHost,
917
919
  UpdateDialog,
920
+ WELL_SHADOW,
918
921
  getColorPopoverId,
919
922
  getCpuDifficultyPopoverId,
920
923
  getInlineColorPickerContentSize,
@@ -930,5 +933,6 @@ export {
930
933
  useOverlayCardColors,
931
934
  usePopoverHost,
932
935
  useQuitConfirmation,
936
+ useWellColors,
933
937
  useZoneClampedAlign
934
938
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tastic/hud",
3
- "version": "0.21.0",
3
+ "version": "0.22.0",
4
4
  "description": "Visual component kit for local-multiplayer React Native games: inline (non-modal) popovers, dropdowns, color pickers, gauges, ready buttons, and dialogs, built to stay scoped to one player's own zone on a shared screen",
5
5
  "keywords": [
6
6
  "react-native",
@@ -15,12 +15,13 @@ import { MONO_FONT } from './fonts'
15
15
  interface Props {
16
16
  rows: AchievementCatalogRow[]
17
17
  label?: string
18
- // Independently optional overrides for this section's own heading/row colors - default to the
19
- // same useAutoPaperTheme()-derived formula as AchievementRow/StatSection/StatRow/BaseStatsScreen
20
- // (see BaseStatsScreen's own doc for why) when omitted, so every existing caller renders
21
- // identically to before. Passed straight through to each <AchievementRow>.
18
+ // Independently optional overrides for the heading's and rows' colors. fg and sectionBg go to each
19
+ // <AchievementRow> (whose own defaults are the well's, see wellSurface.ts). fgMuted goes to the heading and
20
+ // the rows; omitted, the heading (drawn on the screen, not on a well) keeps its own screen-derived default
21
+ // and the rows theirs.
22
22
  fg?: string
23
23
  fgMuted?: string
24
+ sectionBg?: string
24
25
  }
25
26
 
26
27
  // The "ALL ACHIEVEMENTS" heading + one <AchievementRow> per row - extracted verbatim from every
@@ -42,7 +43,7 @@ interface Props {
42
43
  // about 1.4:1 and 1.8:1 against the light screen's white, so a bar or check in either barely shows;
43
44
  // mixed, they're about 5:1 and 6:1 (bronze goes from about 3:1 to 9:1). The badge keeps the true tier
44
45
  // color: its glyph is black, which reads on all three.
45
- export function AchievementCatalogSection({ rows, label = 'ALL ACHIEVEMENTS', fg, fgMuted: fgMutedOverride }: Props) {
46
+ export function AchievementCatalogSection({ rows, label = 'ALL ACHIEVEMENTS', fg, fgMuted: fgMutedOverride, sectionBg }: Props) {
46
47
  const { dark } = useAutoPaperTheme()
47
48
  const fgMuted = fgMutedOverride ?? (dark ? 'rgba(255,255,255,0.5)' : 'rgba(0,0,0,0.5)')
48
49
  const tierAccent = (tierColor: string) => (dark ? tierColor : getBlendedColor('#000000', tierColor, 0.5))
@@ -53,7 +54,7 @@ export function AchievementCatalogSection({ rows, label = 'ALL ACHIEVEMENTS', fg
53
54
  {label}
54
55
  </Text>
55
56
  {rows.map((row) => (
56
- <AchievementRow key={row.id} icon={row.icon} title={row.title} description={row.description} badgeColor={row.unlockedAt !== undefined ? row.tierColor : LOCKED_BADGE_COLOR} checkColor={tierAccent(row.tierColor)} unlockedLabel={row.unlockedLabel} progress={row.progress} deviceMarker={row.deviceMarker} fg={fg} fgMuted={fgMutedOverride} />
57
+ <AchievementRow key={row.id} icon={row.icon} title={row.title} description={row.description} badgeColor={row.unlockedAt !== undefined ? row.tierColor : LOCKED_BADGE_COLOR} checkColor={tierAccent(row.tierColor)} unlockedLabel={row.unlockedLabel} progress={row.progress} deviceMarker={row.deviceMarker} fg={fg} fgMuted={fgMutedOverride} sectionBg={sectionBg} />
57
58
  ))}
58
59
  </Fragment>
59
60
  )
@@ -1,7 +1,8 @@
1
- import { useAutoPaperTheme } from '@rific/auto-paper'
2
1
  import { StyleSheet, View } from 'react-native'
3
2
  import { Icon, ProgressBar, Text } from 'react-native-paper'
4
3
 
4
+ import { useWellColors, WELL_SHADOW } from './wellSurface'
5
+
5
6
  // A neutral gray for a locked badge's own background - exported so a caller computing its own
6
7
  // badge color (unlocked ? tierColor : LOCKED_BADGE_COLOR, matching LightCycles' own achievements.tsx)
7
8
  // doesn't need to invent or duplicate this exact tone.
@@ -32,10 +33,9 @@ interface Props {
32
33
  // from a specific profile's own tab - optional since only an app with per-profile stat views has
33
34
  // this concept at all.
34
35
  deviceMarker?: boolean
35
- // Independently optional overrides for this row's own text/background colors - each defaults to
36
- // the same dark-mode-derived formula as StatRow/StatSection/BaseStatsScreen (see that component's
37
- // own doc for why) when omitted, so every existing caller renders identically to before. A caller
38
- // with its own app-wide chrome palette passes whichever of the three it needs.
36
+ // Independently optional overrides for this row's own text/background colors - each defaults to the well's
37
+ // (wellSurface.ts: the theme's surface and its onSurface/onSurfaceVariant text), like StatSection/StatRow. A
38
+ // caller with its own app-wide chrome palette passes whichever of the three it needs.
39
39
  fg?: string
40
40
  fgMuted?: string
41
41
  sectionBg?: string
@@ -44,16 +44,15 @@ interface Props {
44
44
  // One row per achievement - a tier-colored (or locked-gray) badge, title/description, an optional
45
45
  // progress bar while locked, and either an unlocked check-mark+label or a lock icon. Extracted
46
46
  // verbatim from LightCycles' own achievements.tsx, where this exact row shape backed its entire
47
- // "ALL ACHIEVEMENTS" list. fg/fgMuted/its own section background default to the same
48
- // useAutoPaperTheme()-derived formula as StatRow/StatSection/BaseStatsScreen (see BaseStatsScreen's
49
- // own doc for why), each independently overridable via the matching prop for a caller with its own
50
- // chrome palette. Has no opinion on what an achievement actually IS (id, unlock predicate, catalog)
47
+ // "ALL ACHIEVEMENTS" list. Each row is its own well (wellSurface.ts: the theme's surface on MD3's level-1 shadow);
48
+ // fg/fgMuted/its own background default to the well's colors, each independently overridable via the matching
49
+ // prop for a caller with its own chrome palette. Has no opinion on what an achievement actually IS (id, unlock predicate, catalog)
51
50
  // - that stays entirely app-local; this is purely the presentational row.
52
51
  export function AchievementRow({ icon, title, description, badgeColor, unlockedLabel, checkColor, progress, deviceMarker, fg: fgOverride, fgMuted: fgMutedOverride, sectionBg: sectionBgOverride }: Props) {
53
- const { dark } = useAutoPaperTheme()
54
- const fg = fgOverride ?? (dark ? '#FFFFFF' : '#000000')
55
- const fgMuted = fgMutedOverride ?? (dark ? 'rgba(255,255,255,0.5)' : 'rgba(0,0,0,0.5)')
56
- const sectionBg = sectionBgOverride ?? (dark ? 'rgba(255,255,255,0.06)' : 'rgba(0,0,0,0.04)')
52
+ const well = useWellColors()
53
+ const fg = fgOverride ?? well.fg
54
+ const fgMuted = fgMutedOverride ?? well.fgMuted
55
+ const sectionBg = sectionBgOverride ?? well.bg
57
56
  const unlocked = unlockedLabel !== undefined
58
57
 
59
58
  return (
@@ -116,6 +115,7 @@ const styles = StyleSheet.create({
116
115
  row: {
117
116
  alignItems: 'center',
118
117
  borderRadius: 12,
118
+ boxShadow: WELL_SHADOW,
119
119
  flexDirection: 'row',
120
120
  gap: 12,
121
121
  padding: 12
@@ -9,6 +9,11 @@ interface ActivityStats {
9
9
 
10
10
  interface Props {
11
11
  stats: ActivityStats
12
+ // Optional overrides for the section's and its rows' colors, passed straight through to StatSection and each
13
+ // StatRow (whose own defaults are the well's, see wellSurface.ts) - for a caller with its own chrome palette.
14
+ fg?: string
15
+ fgMuted?: string
16
+ sectionBg?: string
12
17
  }
13
18
 
14
19
  // The "ACTIVITY" StatSection (Days Played / Day Streak / Best Day Streak) - extracted verbatim
@@ -18,12 +23,12 @@ interface Props {
18
23
  // currentDayStreak/bestDayStreak) rather than importing DayStreakState itself, so this component
19
24
  // has no dependency on @tastic/achievements at all: any caller's own stats/statsView object that
20
25
  // happens to carry these three fields already satisfies it structurally.
21
- export function ActivityStatSection({ stats }: Props) {
26
+ export function ActivityStatSection({ stats, fg, fgMuted, sectionBg }: Props) {
22
27
  return (
23
- <StatSection label='ACTIVITY'>
24
- <StatRow label='Days Played' value={String(stats.distinctDaysPlayed)} />
25
- <StatRow label='Day Streak' value={String(stats.currentDayStreak)} />
26
- <StatRow label='Best Day Streak' value={String(stats.bestDayStreak)} />
28
+ <StatSection label='ACTIVITY' fg={fg} sectionBg={sectionBg}>
29
+ <StatRow label='Days Played' value={String(stats.distinctDaysPlayed)} fg={fg} fgMuted={fgMuted} />
30
+ <StatRow label='Day Streak' value={String(stats.currentDayStreak)} fg={fg} fgMuted={fgMuted} />
31
+ <StatRow label='Best Day Streak' value={String(stats.bestDayStreak)} fg={fg} fgMuted={fgMuted} />
27
32
  </StatSection>
28
33
  )
29
34
  }
package/src/StatRow.tsx CHANGED
@@ -1,29 +1,28 @@
1
- import { useAutoPaperTheme } from '@rific/auto-paper'
2
1
  import { StyleSheet, View } from 'react-native'
3
2
  import { Text } from 'react-native-paper'
4
3
 
4
+ import { useWellColors } from './wellSurface'
5
+
5
6
  interface Props {
6
7
  label: string
7
8
  value: string
8
- // Independently optional overrides for this row's own text colors - default to the same
9
- // useAutoPaperTheme()-derived formula as BaseStatsScreen (see that component's own doc for why)
10
- // when omitted, so every existing caller renders identically to before.
9
+ // Independently optional overrides for this row's own text colors - default to the text colors of the well it
10
+ // sits in (wellSurface.ts: onSurface, onSurfaceVariant).
11
11
  fg?: string
12
12
  fgMuted?: string
13
13
  }
14
14
 
15
15
  // A single labeled stat - label on the left (muted), value on the right (bold, full-contrast).
16
16
  // Extracted from LightCycles' own achievements.tsx, where this exact row shape backed every
17
- // section (Overall/Vs CPU/Two Player/Activity). fg/fgMuted default to the same
18
- // useAutoPaperTheme()-derived formula as BaseStatsScreen (see that component's own doc for why),
19
- // each independently overridable via the matching prop for a caller with its own chrome palette.
17
+ // section (Overall/Vs CPU/Two Player/Activity). fg/fgMuted default to the text colors of the StatSection well it
18
+ // sits in, each independently overridable via the matching prop for a caller with its own chrome palette.
20
19
  // Deliberately just label+value text, not a color swatch or profile chip - an app needing a row
21
- // with a leading visual builds its own on top of this same dark-mode formula (see LightCycles' own
20
+ // with a leading visual builds its own on top of the same useWellColors() (see LightCycles' own
22
21
  // ColorStatRow/ProfileRankingRow for an example), since what that visual even is varies per game.
23
22
  export function StatRow({ label, value, fg: fgOverride, fgMuted: fgMutedOverride }: Props) {
24
- const { dark } = useAutoPaperTheme()
25
- const fg = fgOverride ?? (dark ? '#FFFFFF' : '#000000')
26
- const fgMuted = fgMutedOverride ?? (dark ? 'rgba(255,255,255,0.5)' : 'rgba(0,0,0,0.5)')
23
+ const well = useWellColors()
24
+ const fg = fgOverride ?? well.fg
25
+ const fgMuted = fgMutedOverride ?? well.fgMuted
27
26
 
28
27
  return (
29
28
  <View style={styles.row}>
@@ -1,29 +1,28 @@
1
- import { useAutoPaperTheme } from '@rific/auto-paper'
2
1
  import { ReactNode } from 'react'
3
2
  import { StyleSheet, View } from 'react-native'
4
3
  import { Text } from 'react-native-paper'
5
4
 
5
+ import { useWellColors, WELL_SHADOW } from './wellSurface'
6
+
6
7
  interface Props {
7
8
  label: string
8
- // Independently optional overrides for this section's own label color/background - default to
9
- // the same useAutoPaperTheme()-derived formula as StatRow/BaseStatsScreen (see BaseStatsScreen's
10
- // own doc for why) when omitted, so every existing caller renders identically to before.
9
+ // Independently optional overrides for this section's own label color/background - default to the well's
10
+ // (wellSurface.ts: the theme's surface and its onSurface text).
11
11
  fg?: string
12
12
  sectionBg?: string
13
13
  children: ReactNode
14
14
  }
15
15
 
16
- // A labeled, tinted-background grouping box - the "OVERALL"/"VS CPU"/"ACTIVITY"-style section
17
- // shape from LightCycles' own achievements.tsx, generalized to any content (typically a stack of
18
- // StatRow). fg/sectionBg default to the same useAutoPaperTheme()-derived formula as
19
- // StatRow/BaseStatsScreen (see BaseStatsScreen's own doc for why), each independently overridable
20
- // via the matching prop for a caller with its own chrome palette. The label itself carries the
21
- // section's own meaning (what it's a section OF), so this component has no opinion on that beyond
22
- // rendering the string it's given.
16
+ // A labeled grouping box, a well (wellSurface.ts: the theme's surface on MD3's level-1 shadow) - the
17
+ // "OVERALL"/"VS CPU"/"ACTIVITY"-style section shape from LightCycles' own achievements.tsx, generalized to any
18
+ // content (typically a stack of StatRow). fg/sectionBg default to the well's colors, each independently
19
+ // overridable via the matching prop for a caller with its own chrome palette. It was a faint tint until
20
+ // 2026-10-17 (6% white in dark mode, 4% black in light). The label itself carries the section's own meaning
21
+ // (what it's a section OF), so this component has no opinion on that beyond rendering the string it's given.
23
22
  export function StatSection({ label, fg: fgOverride, sectionBg: sectionBgOverride, children }: Props) {
24
- const { dark } = useAutoPaperTheme()
25
- const fg = fgOverride ?? (dark ? '#FFFFFF' : '#000000')
26
- const sectionBg = sectionBgOverride ?? (dark ? 'rgba(255,255,255,0.06)' : 'rgba(0,0,0,0.04)')
23
+ const well = useWellColors()
24
+ const fg = fgOverride ?? well.fg
25
+ const sectionBg = sectionBgOverride ?? well.bg
27
26
 
28
27
  return (
29
28
  <View style={[styles.section, { backgroundColor: sectionBg }]}>
@@ -46,6 +45,7 @@ const styles = StyleSheet.create({
46
45
  },
47
46
  section: {
48
47
  borderRadius: 12,
48
+ boxShadow: WELL_SHADOW,
49
49
  gap: 8,
50
50
  padding: 16
51
51
  }
@@ -23,3 +23,4 @@ export { StatRow } from '../StatRow'
23
23
  export { StatSection } from '../StatSection'
24
24
  export { UpdateDialog, type UpdateDialogProps } from '../UpdateDialog'
25
25
  export { useCompactDialog } from '../useCompactDialog'
26
+ export { useWellColors, WELL_SHADOW, type WellColors } from '../wellSurface'