@titan-design/react-ui 0.10.0 → 0.12.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/bodymap.js +173 -343
- package/dist/bodymap.js.map +1 -1
- package/dist/bodymap.mjs +173 -343
- package/dist/bodymap.mjs.map +1 -1
- package/dist/index.d.mts +416 -18
- package/dist/index.d.ts +416 -18
- package/dist/index.js +2512 -1307
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +2480 -1299
- package/dist/index.mjs.map +1 -1
- package/dist/{pages-DyEx-lBf.d.ts → pages-B1hqnGIC.d.ts} +149 -15
- package/dist/{pages-D7hOD4Vj.d.mts → pages-CvsFqNtx.d.mts} +149 -15
- package/dist/pages.d.mts +1 -1
- package/dist/pages.d.ts +1 -1
- package/dist/pages.js +1644 -1575
- package/dist/pages.js.map +1 -1
- package/dist/pages.mjs +1645 -1576
- package/dist/pages.mjs.map +1 -1
- package/dist/theme/index.d.mts +409 -415
- package/dist/theme/index.d.ts +409 -415
- package/dist/theme/index.js +234 -408
- package/dist/theme/index.js.map +1 -1
- package/dist/theme/index.mjs +227 -399
- package/dist/theme/index.mjs.map +1 -1
- package/dist/theme/tokens-css.js +141 -140
- package/dist/theme/tokens-css.js.map +1 -1
- package/dist/theme/tokens-css.mjs +141 -140
- package/dist/theme/tokens-css.mjs.map +1 -1
- package/package.json +8 -3
- package/src/arch/Architecture.stories.tsx +9 -9
- package/src/components/custom/DateTime/DateTime.stories.tsx +1 -1
- package/src/components/custom/Fatigue/DualGhostSpark.stories.tsx +258 -0
- package/src/components/custom/Fatigue/DualGhostSpark.test.tsx +138 -0
- package/src/components/custom/Fatigue/DualGhostSpark.tsx +208 -0
- package/src/components/custom/Fatigue/FatigueLights.stories.tsx +74 -0
- package/src/components/custom/Fatigue/FatigueLights.test.tsx +33 -0
- package/src/components/custom/Fatigue/FatigueLights.tsx +83 -0
- package/src/components/custom/Fatigue/GhostBand.test.tsx +235 -0
- package/src/components/custom/Fatigue/GhostBand.tsx +246 -0
- package/src/components/custom/Fatigue/GhostBloom.tsx +132 -0
- package/src/components/custom/Fatigue/GhostSpark.stories.tsx +175 -0
- package/src/components/custom/Fatigue/GhostSpark.test.tsx +45 -0
- package/src/components/custom/Fatigue/GhostSpark.tsx +126 -0
- package/src/components/custom/Fatigue/LiveFatigueCard.stories.tsx +126 -0
- package/src/components/custom/Fatigue/LiveFatigueCard.test.tsx +28 -0
- package/src/components/custom/Fatigue/LiveFatigueCard.tsx +86 -0
- package/src/components/custom/Fatigue/LiveFatiguePanel.stories.tsx +95 -0
- package/src/components/custom/Fatigue/LiveFatiguePanel.tsx +151 -0
- package/src/components/custom/Fatigue/README.md +93 -0
- package/src/components/custom/Fatigue/RomProgressionChart.stories.tsx +94 -0
- package/src/components/custom/Fatigue/RomProgressionChart.test.tsx +31 -0
- package/src/components/custom/Fatigue/RomProgressionChart.tsx +132 -0
- package/src/components/custom/Fatigue/SilverRedPalette.stories.tsx +226 -0
- package/src/components/custom/Fatigue/VelocityHero.stories.tsx +70 -0
- package/src/components/custom/Fatigue/VelocityHero.test.tsx +26 -0
- package/src/components/custom/Fatigue/VelocityHero.tsx +46 -0
- package/src/components/custom/Fatigue/VerdictHero.stories.tsx +59 -0
- package/src/components/custom/Fatigue/VerdictHero.test.tsx +49 -0
- package/src/components/custom/Fatigue/VerdictHero.tsx +79 -0
- package/src/components/custom/Fatigue/fatigue-mock.test.ts +59 -0
- package/src/components/custom/Fatigue/fatigue-mock.ts +268 -0
- package/src/components/custom/Fatigue/fatigue-model.ts +129 -0
- package/src/components/custom/Fatigue/fatigue-tokens.test.ts +70 -0
- package/src/components/custom/Fatigue/fatigue-tokens.ts +180 -0
- package/src/components/custom/Fatigue/index.ts +41 -0
- package/src/components/custom/Fatigue/tempo-pacing.test.ts +176 -0
- package/src/components/custom/Fatigue/tempo-pacing.ts +112 -0
- package/src/components/custom/Sidebar/Sidebar.tsx +4 -4
- package/src/components/custom/Table/Table.tsx +2 -2
- package/src/components/custom/Workout/ActiveWorkoutPage.tsx +3 -3
- package/src/components/custom/Workout/BaseBadge.test.tsx +6 -1
- package/src/components/custom/Workout/BaseBadge.tsx +1 -1
- package/src/components/custom/Workout/BodyMap.tsx +2 -2
- package/src/components/custom/Workout/BodyMapDetailPanel.tsx +2 -2
- package/src/components/custom/Workout/DeviationBar.test.tsx +2 -1
- package/src/components/custom/Workout/DeviationBar.tsx +3 -2
- package/src/components/custom/Workout/DualVelocityStrip.test.tsx +685 -0
- package/src/components/custom/Workout/ExerciseCard.test.tsx +7 -7
- package/src/components/custom/Workout/ExerciseCard.tsx +5 -3
- package/src/components/custom/Workout/ExerciseDetailPage.tsx +11 -5
- package/src/components/custom/Workout/InputBar.tsx +2 -2
- package/src/components/custom/Workout/LiveAuraFrame.stories.tsx +1 -1
- package/src/components/custom/Workout/LiveAuraFrame.test.tsx +1 -1
- package/src/components/custom/Workout/LiveAuraFrame.tsx +3 -3
- package/src/components/custom/Workout/MesoCard.tsx +3 -1
- package/src/components/custom/Workout/MuscleGroupChip.tsx +4 -3
- package/src/components/custom/Workout/PrHistoryModal.tsx +2 -2
- package/src/components/custom/Workout/ProgramPlanningPage.tsx +1 -1
- package/src/components/custom/Workout/README.md +62 -16
- package/src/components/custom/Workout/ReadinessCheck.tsx +2 -2
- package/src/components/custom/Workout/RestTimer.tsx +2 -2
- package/src/components/custom/Workout/S3SetTypes.stories.tsx +3 -3
- package/src/components/custom/Workout/SegmentedBar.stories.tsx +3 -3
- package/src/components/custom/Workout/SessionHeader.stories.tsx +2 -2
- package/src/components/custom/Workout/SessionHeader.tsx +1 -1
- package/src/components/custom/Workout/SessionRail.stories.tsx +2 -2
- package/src/components/custom/Workout/SessionRail.test.tsx +43 -0
- package/src/components/custom/Workout/SessionRail.tsx +63 -18
- package/src/components/custom/Workout/SetBar.tsx +3 -3
- package/src/components/custom/Workout/SetRow.test.tsx +12 -9
- package/src/components/custom/Workout/SetRow.tsx +9 -2
- package/src/components/custom/Workout/StatusDot.tsx +5 -4
- package/src/components/custom/Workout/StrengthTrendChart.tsx +1 -1
- package/src/components/custom/Workout/TrainingStatusPage.tsx +2 -2
- package/src/components/custom/Workout/VelocityStrip.compact.stories.tsx +191 -0
- package/src/components/custom/Workout/VelocityStrip.dual.stories.tsx +186 -0
- package/src/components/custom/Workout/VelocityStrip.expanded.stories.tsx +296 -0
- package/src/components/custom/Workout/VelocityStrip.hero.stories.tsx +259 -0
- package/src/components/custom/Workout/VelocityStrip.stories.tsx +217 -250
- package/src/components/custom/Workout/VelocityStrip.test.tsx +199 -79
- package/src/components/custom/Workout/VelocityStrip.tsx +953 -609
- package/src/components/custom/Workout/VolumeLandmarkBar.tsx +3 -3
- package/src/components/custom/Workout/WeightBadge.test.tsx +6 -1
- package/src/components/custom/Workout/WeightBadge.tsx +2 -1
- package/src/components/custom/Workout/WorkoutCard.tsx +3 -1
- package/src/components/custom/Workout/WorkoutPill.tsx +5 -2
- package/src/components/custom/Workout/ZoneTrack.tsx +4 -4
- package/src/components/custom/Workout/index.ts +3 -0
- package/src/components/custom/Workout/setHeadingKit.tsx +14 -8
- package/src/components/custom/Workout/velocity-story-kit.tsx +438 -0
- package/src/components/custom/charts/SetBarChart.test.tsx +275 -0
- package/src/components/custom/charts/SetBarChart.tsx +651 -0
- package/src/components/custom/charts/live-rep-growth.ts +91 -0
- package/src/components/custom/index.ts +1 -0
- package/src/components/custom/stepper/Stepper.stories.tsx +1 -1
- package/src/components/custom/stepper/Stepper.tsx +3 -3
- package/src/components/shell/DeviceMenu.tsx +2 -2
- package/src/components/shell/SideNav.tsx +1 -1
- package/src/components/shell/TopBar.tsx +1 -1
- package/src/components/ui/autocomplete/Autocomplete.stories.tsx +1 -1
- package/src/components/ui/autocomplete/Autocomplete.tsx +1 -1
- package/src/components/ui/avatar/Avatar.tsx +2 -2
- package/src/components/ui/badge/Badge.tsx +2 -2
- package/src/components/ui/card/Card.tsx +3 -3
- package/src/components/ui/checkbox/Checkbox.tsx +1 -1
- package/src/components/ui/collapse/Collapse.tsx +1 -1
- package/src/components/ui/data-row/DataRow.stories.tsx +1 -1
- package/src/components/ui/drawer/Drawer.tsx +3 -3
- package/src/components/ui/form-field/FormField.tsx +1 -1
- package/src/components/ui/help-tip/HelpTip.tsx +1 -1
- package/src/components/ui/menu/Menu.tsx +2 -2
- package/src/components/ui/pill/Pill.tsx +2 -2
- package/src/components/ui/popover/Popover.stories.tsx +2 -2
- package/src/components/ui/popover/Popover.tsx +1 -1
- package/src/components/ui/progress/Progress.tsx +3 -3
- package/src/components/ui/radio/Radio.stories.tsx +1 -1
- package/src/components/ui/radio/Radio.tsx +4 -4
- package/src/components/ui/select/Select.tsx +3 -3
- package/src/components/ui/surface/Surface.stories.tsx +3 -3
- package/src/components/ui/surface/Surface.test.tsx +32 -19
- package/src/components/ui/surface/Surface.tsx +3 -3
- package/src/components/ui/surface/SurfaceContext.ts +19 -29
- package/src/components/ui/surface/surface.contract.test.ts +57 -44
- package/src/components/ui/switch/Switch.tsx +1 -1
- package/src/components/ui/tabs/Tabs.tsx +2 -2
- package/src/components/ui/toolbar-button/ToolbarButton.stories.tsx +5 -5
- package/src/components/ui/toolbar-button/ToolbarButton.test.tsx +1 -1
- package/src/components/ui/toolbar-button/ToolbarButton.tsx +36 -22
- package/src/stories/PresetShowcase.stories.tsx +21 -21
- package/src/test/raw-color-patterns.test.ts +71 -0
- package/src/theme/ColorPalettes.stories.tsx +874 -0
- package/src/theme/ColorPrimitives.stories.tsx +348 -0
- package/src/theme/Depth.stories.tsx +312 -0
- package/src/theme/DepthCalibration.stories.tsx +655 -0
- package/src/theme/ThemeProvider.tsx +1 -1
- package/src/theme/barrel.ts +7 -14
- package/src/theme/color-stories.coverage.test.ts +125 -0
- package/src/theme/color-story-kit.tsx +170 -0
- package/src/theme/color-utils.ts +197 -0
- package/src/theme/config.ts +0 -8
- package/src/theme/depth-calibration.test.tsx +97 -0
- package/src/theme/elevation.test.ts +62 -1
- package/src/theme/elevation.ts +34 -8
- package/src/theme/global.css +47 -53
- package/src/theme/grey-ramp.test.ts +166 -0
- package/src/theme/materials.test.ts +106 -0
- package/src/theme/materials.ts +211 -0
- package/src/theme/presets/audiobook.ts +4 -4
- package/src/theme/presets/types.ts +3 -3
- package/src/theme/semantic-contrast.test.ts +83 -0
- package/src/theme/tokens/primitives.ts +62 -115
- package/src/theme/tokens/semantic.ts +130 -82
- package/src/utils/colors.ts +1 -1
- package/tailwind.config.js +2 -5
- package/src/components/custom/Workout/VelocityStrip.modalities.stories.tsx +0 -254
- package/src/components/custom/Workout/VelocityStrip.responsive.stories.tsx +0 -145
- package/src/theme/ColorSystem.stories.tsx +0 -508
- package/src/theme/Colors.stories.tsx +0 -323
- package/src/theme/shadows.stories.tsx +0 -523
- package/src/theme/shadows.ts +0 -439
|
@@ -0,0 +1,348 @@
|
|
|
1
|
+
import type { Meta, StoryObj } from '@storybook/react-vite'
|
|
2
|
+
import { View, Text } from 'react-native'
|
|
3
|
+
import {
|
|
4
|
+
primitiveColors,
|
|
5
|
+
primitiveRamps,
|
|
6
|
+
greyRamp,
|
|
7
|
+
SURFACE_PLANE_STEPS,
|
|
8
|
+
categoricalPalette,
|
|
9
|
+
CATEGORICAL_CVD_SAFE_MAX,
|
|
10
|
+
} from './tokens/primitives'
|
|
11
|
+
import {
|
|
12
|
+
SWATCH_BORDER,
|
|
13
|
+
SectionIntro,
|
|
14
|
+
SectionTitle,
|
|
15
|
+
ScaleRow,
|
|
16
|
+
contrast,
|
|
17
|
+
CVD_MATRICES,
|
|
18
|
+
simulateCvd,
|
|
19
|
+
} from './color-story-kit'
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Foundations/Color/Primitives — the raw material, and how it is validated.
|
|
23
|
+
*
|
|
24
|
+
* This half holds the scales themselves and nothing about what they mean. The
|
|
25
|
+
* organisations OF these scales — surfaces, categorical, status, brand,
|
|
26
|
+
* fatigue and the rest — live in the sibling `Foundations/Color/Palettes`.
|
|
27
|
+
*
|
|
28
|
+
* This page is a MENU: everything on it is something you may legitimately reach
|
|
29
|
+
* for. It is now also the complete list — TD-07.14 deleted the five older
|
|
30
|
+
* `primitiveColors` scales (`charcoal`, `neutral`, `redVivid`, and the support
|
|
31
|
+
* `blue`/`red`) rather than leaving them shelved, so there is no longer a
|
|
32
|
+
* category of "real but don't pick these". The greys folded into `greyRamp`
|
|
33
|
+
* below; the chromatics into the OKLCH `primitiveRamps`.
|
|
34
|
+
*/
|
|
35
|
+
const meta: Meta = {
|
|
36
|
+
title: 'Foundations/Color/Primitives',
|
|
37
|
+
tags: ['autodocs'],
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export default meta
|
|
41
|
+
|
|
42
|
+
// ============================================================================
|
|
43
|
+
// 1. Ramps
|
|
44
|
+
// ============================================================================
|
|
45
|
+
|
|
46
|
+
const CHROMATIC_RAMPS: Array<[keyof typeof primitiveRamps, string]> = [
|
|
47
|
+
['red', 'Red'],
|
|
48
|
+
['orange', 'Orange'],
|
|
49
|
+
['amber', 'Amber'],
|
|
50
|
+
['green', 'Green'],
|
|
51
|
+
['cyan', 'Cyan'],
|
|
52
|
+
['blue', 'Blue'],
|
|
53
|
+
['magenta', 'Magenta'],
|
|
54
|
+
]
|
|
55
|
+
|
|
56
|
+
const KEYWORDS = [
|
|
57
|
+
{ name: 'white', value: primitiveColors.white },
|
|
58
|
+
{ name: 'black', value: primitiveColors.black },
|
|
59
|
+
] as const
|
|
60
|
+
|
|
61
|
+
/** Which surface plane each ramp step backs, for the annotation row. */
|
|
62
|
+
const PLANE_BY_STEP: Record<number, string> = Object.fromEntries(
|
|
63
|
+
Object.entries(SURFACE_PLANE_STEPS).map(([plane, step]) => [step, plane])
|
|
64
|
+
)
|
|
65
|
+
|
|
66
|
+
/** Steps whose value is byte-identical to what shipped in v0.10.0. */
|
|
67
|
+
const ANCHOR_STEPS = new Set<number>(Object.values(SURFACE_PLANE_STEPS))
|
|
68
|
+
|
|
69
|
+
const GREY_STEPS = Object.keys(greyRamp).map(Number).sort((a, b) => a - b)
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* The grey ramp, rendered as a numbered scale with its plane aliases.
|
|
73
|
+
*
|
|
74
|
+
* This story used to show six unnumbered plane NAMES here, which was the visible
|
|
75
|
+
* symptom of the greys not actually being a ramp. The numbers are the content:
|
|
76
|
+
* they are what let a border, a plane and a text colour be compared on one scale.
|
|
77
|
+
*/
|
|
78
|
+
function GreyRampRow() {
|
|
79
|
+
return (
|
|
80
|
+
<View style={{ flexDirection: 'row', gap: 4, marginBottom: 4 }}>
|
|
81
|
+
{GREY_STEPS.map((step) => {
|
|
82
|
+
const hex = greyRamp[step as keyof typeof greyRamp]
|
|
83
|
+
const plane = PLANE_BY_STEP[step]
|
|
84
|
+
return (
|
|
85
|
+
<View key={step} style={{ flex: 1, alignItems: 'stretch' }}>
|
|
86
|
+
<View
|
|
87
|
+
style={{
|
|
88
|
+
height: 76,
|
|
89
|
+
borderRadius: 6,
|
|
90
|
+
backgroundColor: hex,
|
|
91
|
+
borderWidth: ANCHOR_STEPS.has(step) ? 1.5 : 1,
|
|
92
|
+
borderColor: ANCHOR_STEPS.has(step) ? '#FF7900' : SWATCH_BORDER,
|
|
93
|
+
}}
|
|
94
|
+
/>
|
|
95
|
+
<Text className="text-text-primary" style={{ fontSize: 10, fontWeight: '600', marginTop: 3 }}>
|
|
96
|
+
{step}
|
|
97
|
+
{ANCHOR_STEPS.has(step) ? ' ★' : ''}
|
|
98
|
+
</Text>
|
|
99
|
+
<Text className="text-text-tertiary" style={{ fontSize: 8 }}>
|
|
100
|
+
{hex.toUpperCase()}
|
|
101
|
+
</Text>
|
|
102
|
+
{plane ? (
|
|
103
|
+
<Text style={{ fontSize: 8, color: '#FF9628', marginTop: 1 }}>{plane}</Text>
|
|
104
|
+
) : null}
|
|
105
|
+
</View>
|
|
106
|
+
)
|
|
107
|
+
})}
|
|
108
|
+
</View>
|
|
109
|
+
)
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
export const Ramps: StoryObj = {
|
|
113
|
+
name: '1. Ramps — Source of Truth',
|
|
114
|
+
render: () => (
|
|
115
|
+
<View style={{ padding: 24 }}>
|
|
116
|
+
<Text className="text-2xl font-bold text-text-primary mb-2">Ramps</Text>
|
|
117
|
+
<SectionIntro>
|
|
118
|
+
Every raw colour value titan ships, as ordered scales. Nothing here carries meaning — a
|
|
119
|
+
ramp step becomes a border or a status only when a semantic token or a palette references
|
|
120
|
+
it, which is what{' '}
|
|
121
|
+
<Text className="font-semibold">Foundations/Color/Palettes</Text> documents. Everything on
|
|
122
|
+
this page is current and safe to pick.
|
|
123
|
+
</SectionIntro>
|
|
124
|
+
|
|
125
|
+
<Text className="text-text-tertiary text-xs mb-6">
|
|
126
|
+
Looking for <Text className="font-semibold">charcoal</Text>,{' '}
|
|
127
|
+
<Text className="font-semibold">neutral</Text>,{' '}
|
|
128
|
+
<Text className="font-semibold">redVivid</Text> or the support{' '}
|
|
129
|
+
<Text className="font-semibold">blue</Text>/<Text className="font-semibold">red</Text>{' '}
|
|
130
|
+
scales? All five were DELETED in TD-07.14, not shelved. The two grey scales folded into
|
|
131
|
+
the warm ramp below — they were cold (neutral ran R−B down to −26) while the surfaces had
|
|
132
|
+
been warm since v0.10.0, and nothing ever reconciled the two. The chromatics moved to the
|
|
133
|
+
OKLCH ramps above. Spec and generator:
|
|
134
|
+
coordination/design-explorations/foundations/warm-grey-ramp/.
|
|
135
|
+
</Text>
|
|
136
|
+
|
|
137
|
+
<SectionTitle>Chromatic — OKLCH tonal ramps</SectionTitle>
|
|
138
|
+
<Text className="text-text-secondary text-xs mb-4">
|
|
139
|
+
Seven OKLCH-generated hue ramps, 11 perceptual steps each (50 → 950), built with
|
|
140
|
+
hue-torsion and a chroma arc anchored through the brand/semantic hexes. These are the
|
|
141
|
+
source of truth for chromatic hexes: the categorical, diverging, effort and fatigue
|
|
142
|
+
palettes are all references into these steps rather than duplicated values, so re-tuning a
|
|
143
|
+
ramp moves them together. Amber absorbs the former yellow (lemon → warm body) and cyan
|
|
144
|
+
absorbs the former steel.
|
|
145
|
+
</Text>
|
|
146
|
+
{CHROMATIC_RAMPS.map(([key, name]) => (
|
|
147
|
+
<ScaleRow key={key} name={name} scale={primitiveRamps[key]} />
|
|
148
|
+
))}
|
|
149
|
+
|
|
150
|
+
<SectionTitle>Achromatic — the warm grey ramp</SectionTitle>
|
|
151
|
+
<Text className="text-text-secondary text-xs mb-3">
|
|
152
|
+
The ONE grey scale. Fifteen steps, warm at every one (R−B never below +2), spaced by
|
|
153
|
+
perceptual lightness so a step is the same visual distance anywhere in the stack. It
|
|
154
|
+
replaced three separate scales — a cold <Text className="font-semibold">charcoal</Text>, a
|
|
155
|
+
cold <Text className="font-semibold">neutral</Text>, and a standalone surface ramp — which
|
|
156
|
+
is why the surface planes appear here as ordinary numbered steps rather than as a parallel
|
|
157
|
+
set of names.
|
|
158
|
+
</Text>
|
|
159
|
+
<Text className="text-text-secondary text-xs mb-4">
|
|
160
|
+
Two things about the numbering, both deliberate.{' '}
|
|
161
|
+
<Text className="font-semibold">975</Text> sits past the end of the shared grid: the
|
|
162
|
+
canonical L* ladder — the median of the seven chromatic ramps — stops at L*10.3, which is
|
|
163
|
+
step 950, and the bezel is L*3.8. There was no number left for it.{' '}
|
|
164
|
+
<Text className="font-semibold">850</Text> and <Text className="font-semibold">875</Text>{' '}
|
|
165
|
+
are quarter-steps because overlay, raised and elevated sit only ~2.7 L* apart and all three
|
|
166
|
+
collide on 900 at normal resolution. There is deliberately no{' '}
|
|
167
|
+
<Text className="font-semibold">inset</Text>: at ΔE 1.10 from the frame it was an
|
|
168
|
+
imperceptible duplicate, which is why <Text className="font-semibold"><Surface
|
|
169
|
+
pressed></Text> kept collapsing into it.
|
|
170
|
+
</Text>
|
|
171
|
+
<GreyRampRow />
|
|
172
|
+
<Text className="text-text-tertiary text-xs mb-4">
|
|
173
|
+
<Text style={{ color: '#FF9628' }}>★ / orange outline</Text> = a step that also names a
|
|
174
|
+
surface plane, byte-identical to the value that shipped in v0.10.0 — the surfaces did not
|
|
175
|
+
move, only their names. Reach for planes with{' '}
|
|
176
|
+
<Text className="font-semibold"><Surface level></Text> rather than by reading a step;
|
|
177
|
+
the depth semantics and per-plane token aliases are in the Palettes surface story.
|
|
178
|
+
</Text>
|
|
179
|
+
|
|
180
|
+
<SectionTitle>Keywords</SectionTitle>
|
|
181
|
+
<Text className="text-text-secondary text-xs mb-4">
|
|
182
|
+
Absolute values with no scale. `transparent` is the third and encodes absence — it has no
|
|
183
|
+
swatch because there is nothing to show.
|
|
184
|
+
</Text>
|
|
185
|
+
<View style={{ flexDirection: 'row', gap: 12 }}>
|
|
186
|
+
{KEYWORDS.map(({ name, value }) => (
|
|
187
|
+
<View key={name} style={{ alignItems: 'center' }}>
|
|
188
|
+
<View
|
|
189
|
+
style={{
|
|
190
|
+
width: 62,
|
|
191
|
+
height: 46,
|
|
192
|
+
borderRadius: 6,
|
|
193
|
+
backgroundColor: value,
|
|
194
|
+
borderWidth: 1,
|
|
195
|
+
borderColor: SWATCH_BORDER,
|
|
196
|
+
}}
|
|
197
|
+
/>
|
|
198
|
+
<Text className="text-text-secondary text-xs mt-1">{name}</Text>
|
|
199
|
+
<Text className="text-text-tertiary" style={{ fontSize: 8 }}>
|
|
200
|
+
{value.toUpperCase()}
|
|
201
|
+
</Text>
|
|
202
|
+
</View>
|
|
203
|
+
))}
|
|
204
|
+
</View>
|
|
205
|
+
</View>
|
|
206
|
+
),
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
// ============================================================================
|
|
210
|
+
// 2. Accessibility — contrast + CVD
|
|
211
|
+
// ============================================================================
|
|
212
|
+
|
|
213
|
+
/** The mid-step of each chromatic ramp — the band most fills and strokes land on. */
|
|
214
|
+
const RAMP_MIDS = CHROMATIC_RAMPS.map(([key, name]) => ({
|
|
215
|
+
name,
|
|
216
|
+
hex: primitiveRamps[key][500],
|
|
217
|
+
}))
|
|
218
|
+
|
|
219
|
+
/**
|
|
220
|
+
* Width of the mode-label gutter. The swatch rows are indented by the same
|
|
221
|
+
* amount so each simulation strip sits directly under the colour it simulates —
|
|
222
|
+
* the comparison is worthless if the reader has to count across to pair them.
|
|
223
|
+
*/
|
|
224
|
+
const CVD_LABEL_WIDTH = 96
|
|
225
|
+
|
|
226
|
+
function CvdStrips({ colors, width }: { colors: readonly string[]; width: number }) {
|
|
227
|
+
return (
|
|
228
|
+
<>
|
|
229
|
+
{(['deuteranopia', 'protanopia'] as const).map((mode) => (
|
|
230
|
+
<View key={mode} style={{ flexDirection: 'row', alignItems: 'center', marginTop: 3 }}>
|
|
231
|
+
<Text className="text-text-secondary" style={{ fontSize: 9, width: CVD_LABEL_WIDTH }}>
|
|
232
|
+
{mode}
|
|
233
|
+
</Text>
|
|
234
|
+
<View style={{ flexDirection: 'row', gap: 6 }}>
|
|
235
|
+
{colors.map((hex, i) => (
|
|
236
|
+
<View
|
|
237
|
+
key={i}
|
|
238
|
+
style={{
|
|
239
|
+
width,
|
|
240
|
+
height: 14,
|
|
241
|
+
borderRadius: 3,
|
|
242
|
+
backgroundColor: simulateCvd(CVD_MATRICES[mode], hex),
|
|
243
|
+
}}
|
|
244
|
+
/>
|
|
245
|
+
))}
|
|
246
|
+
</View>
|
|
247
|
+
</View>
|
|
248
|
+
))}
|
|
249
|
+
</>
|
|
250
|
+
)
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
export const Accessibility: StoryObj = {
|
|
254
|
+
name: '2. Accessibility — Contrast + CVD',
|
|
255
|
+
render: () => (
|
|
256
|
+
<View style={{ padding: 24 }}>
|
|
257
|
+
<Text className="text-2xl font-bold text-text-primary mb-2">Accessibility</Text>
|
|
258
|
+
<SectionIntro>
|
|
259
|
+
How the raw material behaves for viewers who cannot separate the hues, and under text.
|
|
260
|
+
Dichromacy is simulated with the Machado-2009 matrices at severity 1.0. Converging strips
|
|
261
|
+
across two swatches mean those two are the same colour to that viewer — which is a
|
|
262
|
+
problem only where hue is carrying the meaning on its own.
|
|
263
|
+
</SectionIntro>
|
|
264
|
+
|
|
265
|
+
<SectionTitle>Ramp mid-steps (500)</SectionTitle>
|
|
266
|
+
<Text className="text-text-secondary text-xs mb-3">
|
|
267
|
+
The seven hues at the step most fills land on. Red/orange/amber and green/cyan compress
|
|
268
|
+
toward each other under both dichromacies — which is exactly why the categorical palette
|
|
269
|
+
below fans its picks across lightness instead of taking all seven at 500, and why the
|
|
270
|
+
ordered scales in Palettes encode magnitude in lightness rather than hue.
|
|
271
|
+
</Text>
|
|
272
|
+
<View
|
|
273
|
+
style={{ flexDirection: 'row', gap: 6, marginBottom: 6, paddingLeft: CVD_LABEL_WIDTH }}
|
|
274
|
+
>
|
|
275
|
+
{RAMP_MIDS.map(({ name, hex }) => (
|
|
276
|
+
<View key={name} style={{ alignItems: 'center' }}>
|
|
277
|
+
<View
|
|
278
|
+
style={{
|
|
279
|
+
width: 76,
|
|
280
|
+
height: 40,
|
|
281
|
+
borderRadius: 6,
|
|
282
|
+
backgroundColor: hex,
|
|
283
|
+
borderWidth: 1,
|
|
284
|
+
borderColor: SWATCH_BORDER,
|
|
285
|
+
}}
|
|
286
|
+
/>
|
|
287
|
+
<Text className="text-text-secondary" style={{ fontSize: 9, marginTop: 2 }}>
|
|
288
|
+
{name}
|
|
289
|
+
</Text>
|
|
290
|
+
</View>
|
|
291
|
+
))}
|
|
292
|
+
</View>
|
|
293
|
+
<CvdStrips colors={RAMP_MIDS.map((r) => r.hex)} width={76} />
|
|
294
|
+
|
|
295
|
+
<View style={{ height: 28 }} />
|
|
296
|
+
|
|
297
|
+
<SectionTitle>Categorical series — in-place text contrast</SectionTitle>
|
|
298
|
+
<Text className="text-text-secondary text-xs mb-3">
|
|
299
|
+
The palette that has to survive this test in production. All pairs hold ΔE ≥ 8 through the
|
|
300
|
+
first {CATEGORICAL_CVD_SAFE_MAX}; the 7th is extended and wants a legend. The palette
|
|
301
|
+
itself is documented in Palettes — this is the validation of it.
|
|
302
|
+
</Text>
|
|
303
|
+
{(['default', 'dark'] as const).map((variant) => {
|
|
304
|
+
const colors = categoricalPalette[variant]
|
|
305
|
+
const textColor = variant === 'dark' ? primitiveColors.white : primitiveColors.black
|
|
306
|
+
return (
|
|
307
|
+
<View key={variant} style={{ marginBottom: 22 }}>
|
|
308
|
+
<Text
|
|
309
|
+
className="font-semibold text-text-primary text-sm mb-2"
|
|
310
|
+
style={{ textTransform: 'capitalize' }}
|
|
311
|
+
>
|
|
312
|
+
{variant}
|
|
313
|
+
</Text>
|
|
314
|
+
<View
|
|
315
|
+
style={{
|
|
316
|
+
flexDirection: 'row',
|
|
317
|
+
gap: 6,
|
|
318
|
+
marginBottom: 6,
|
|
319
|
+
paddingLeft: CVD_LABEL_WIDTH,
|
|
320
|
+
}}
|
|
321
|
+
>
|
|
322
|
+
{colors.map((hex, i) => (
|
|
323
|
+
<View key={i} style={{ alignItems: 'center' }}>
|
|
324
|
+
<View
|
|
325
|
+
style={{
|
|
326
|
+
width: 56,
|
|
327
|
+
height: 40,
|
|
328
|
+
borderRadius: 6,
|
|
329
|
+
backgroundColor: hex,
|
|
330
|
+
alignItems: 'center',
|
|
331
|
+
justifyContent: 'center',
|
|
332
|
+
}}
|
|
333
|
+
>
|
|
334
|
+
<Text style={{ color: textColor, fontSize: 11, fontWeight: '700' }}>Aa</Text>
|
|
335
|
+
</View>
|
|
336
|
+
<Text className="text-text-secondary" style={{ fontSize: 8, marginTop: 2 }}>
|
|
337
|
+
{contrast(hex, textColor).toFixed(1)}:1
|
|
338
|
+
</Text>
|
|
339
|
+
</View>
|
|
340
|
+
))}
|
|
341
|
+
</View>
|
|
342
|
+
<CvdStrips colors={colors} width={56} />
|
|
343
|
+
</View>
|
|
344
|
+
)
|
|
345
|
+
})}
|
|
346
|
+
</View>
|
|
347
|
+
),
|
|
348
|
+
}
|
|
@@ -0,0 +1,312 @@
|
|
|
1
|
+
import type { Meta, StoryObj } from '@storybook/react-vite'
|
|
2
|
+
import { View, Text } from 'react-native'
|
|
3
|
+
import { greyRamp } from './tokens/primitives'
|
|
4
|
+
import { getSemanticColors } from './tokens/semantic'
|
|
5
|
+
import {
|
|
6
|
+
getElevationSurface,
|
|
7
|
+
getElevationShadow,
|
|
8
|
+
getBaseSurfaceColor,
|
|
9
|
+
FLOATING_ELEVATION_MIN,
|
|
10
|
+
type ElevationLevel,
|
|
11
|
+
} from './elevation'
|
|
12
|
+
import { paperSheet, insetWell, barPaper } from './materials'
|
|
13
|
+
import { SectionIntro, SectionTitle } from './color-story-kit'
|
|
14
|
+
|
|
15
|
+
const t = getSemanticColors('dark')
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Foundations/Depth — how a surface says how far away it is.
|
|
19
|
+
*
|
|
20
|
+
* There are exactly three mechanisms, and which one applies is not a taste call:
|
|
21
|
+
*
|
|
22
|
+
* TONE how deep a plane sits — the grey ramp
|
|
23
|
+
* HAIRLINE where one plane ends — alpha, self-normalising
|
|
24
|
+
* MATERIAL what a plane is made of — paper, well
|
|
25
|
+
*
|
|
26
|
+
* Drop-shadow is deliberately not on that list for anything inline. This page
|
|
27
|
+
* exists because the system previously had a fourth mechanism — a neumorphic
|
|
28
|
+
* dual-opposing shadow — that could not work here and was removed in TD-07.16.
|
|
29
|
+
*/
|
|
30
|
+
const meta: Meta = {
|
|
31
|
+
title: 'Foundations/Depth',
|
|
32
|
+
tags: ['autodocs'],
|
|
33
|
+
}
|
|
34
|
+
export default meta
|
|
35
|
+
|
|
36
|
+
const CONTENT_LEVELS: ElevationLevel[] = [0, 1, 2, 3]
|
|
37
|
+
const FLOATING_LEVELS: ElevationLevel[] = [4, 5]
|
|
38
|
+
const base = getBaseSurfaceColor('dark')
|
|
39
|
+
|
|
40
|
+
function Swatch({
|
|
41
|
+
label,
|
|
42
|
+
note,
|
|
43
|
+
style,
|
|
44
|
+
}: {
|
|
45
|
+
label: string
|
|
46
|
+
note: string
|
|
47
|
+
style: Record<string, unknown>
|
|
48
|
+
}) {
|
|
49
|
+
return (
|
|
50
|
+
<View style={{ width: 190 }}>
|
|
51
|
+
<View
|
|
52
|
+
style={{
|
|
53
|
+
height: 92,
|
|
54
|
+
borderRadius: 10,
|
|
55
|
+
padding: 10,
|
|
56
|
+
justifyContent: 'flex-end',
|
|
57
|
+
...style,
|
|
58
|
+
}}
|
|
59
|
+
>
|
|
60
|
+
<Text className="text-text-primary" style={{ fontSize: 12, fontWeight: '600' }}>
|
|
61
|
+
{label}
|
|
62
|
+
</Text>
|
|
63
|
+
</View>
|
|
64
|
+
<Text className="text-text-tertiary" style={{ fontSize: 9, marginTop: 4, lineHeight: 13 }}>
|
|
65
|
+
{note}
|
|
66
|
+
</Text>
|
|
67
|
+
</View>
|
|
68
|
+
)
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
export const Mechanisms: StoryObj = {
|
|
72
|
+
name: '1. The three mechanisms',
|
|
73
|
+
render: () => (
|
|
74
|
+
<View style={{ padding: 24, backgroundColor: greyRamp[975] }}>
|
|
75
|
+
<Text className="text-2xl font-bold text-text-primary mb-2">Depth</Text>
|
|
76
|
+
<SectionIntro>
|
|
77
|
+
Three mechanisms, in the order you should reach for them. A surface that needs to read as
|
|
78
|
+
deeper takes a TONE; one that needs an edge takes a HAIRLINE; one that needs to feel
|
|
79
|
+
physical takes a MATERIAL. Drop-shadow is not on the list for anything that is not
|
|
80
|
+
floating — see section 3 for why.
|
|
81
|
+
</SectionIntro>
|
|
82
|
+
|
|
83
|
+
<SectionTitle>Tone — the grey ramp</SectionTitle>
|
|
84
|
+
<Text className="text-text-secondary text-xs mb-3">
|
|
85
|
+
The primary cue and the only one that works identically on web and native. Every plane is
|
|
86
|
+
a ramp step.
|
|
87
|
+
</Text>
|
|
88
|
+
<View style={{ flexDirection: 'row', gap: 10, marginBottom: 22 }}>
|
|
89
|
+
{[975, 950, 925, 900, 875, 850].map((step) => (
|
|
90
|
+
<Swatch
|
|
91
|
+
key={step}
|
|
92
|
+
label={`grey-${step}`}
|
|
93
|
+
note=""
|
|
94
|
+
style={{ backgroundColor: greyRamp[step as keyof typeof greyRamp] }}
|
|
95
|
+
/>
|
|
96
|
+
))}
|
|
97
|
+
</View>
|
|
98
|
+
|
|
99
|
+
<SectionTitle>Hairline — where a plane ends</SectionTitle>
|
|
100
|
+
<Text className="text-text-secondary text-xs mb-3">
|
|
101
|
+
Alpha-white, so it composites toward white by the same amount on any background and holds
|
|
102
|
+
a near-constant ΔL* wherever it lands. A solid border swings wildly across the ramp; this
|
|
103
|
+
does not. It is the default separator, and it is what replaced the solid dark border
|
|
104
|
+
tokens.
|
|
105
|
+
</Text>
|
|
106
|
+
<View style={{ flexDirection: 'row', gap: 10, marginBottom: 22 }}>
|
|
107
|
+
{(['hairline-subtle', 'hairline-default', 'hairline-strong'] as const).map((token) => (
|
|
108
|
+
<Swatch
|
|
109
|
+
key={token}
|
|
110
|
+
label={token.replace('hairline-', '')}
|
|
111
|
+
note={t[token]}
|
|
112
|
+
style={{
|
|
113
|
+
backgroundColor: greyRamp[925],
|
|
114
|
+
borderWidth: 1,
|
|
115
|
+
borderColor: t[token],
|
|
116
|
+
}}
|
|
117
|
+
/>
|
|
118
|
+
))}
|
|
119
|
+
</View>
|
|
120
|
+
|
|
121
|
+
<SectionTitle>Material — what a plane is made of</SectionTitle>
|
|
122
|
+
<Text className="text-text-secondary text-xs mb-3">
|
|
123
|
+
Used sparingly. Paper is for HERO surfaces only and takes muted tones only — vivid colour
|
|
124
|
+
is ink ON paper, never the paper itself. There are no levels of paper; one treatment, or
|
|
125
|
+
the texture stops meaning anything.
|
|
126
|
+
</Text>
|
|
127
|
+
<View style={{ flexDirection: 'row', gap: 10, marginBottom: 8 }}>
|
|
128
|
+
<Swatch
|
|
129
|
+
label="paperSheet"
|
|
130
|
+
note="tonal fill (ΔL* ≤ 3) + grain + dither + rim + contact shadow. Hero surfaces."
|
|
131
|
+
style={paperSheet(greyRamp[875]) as Record<string, unknown>}
|
|
132
|
+
/>
|
|
133
|
+
<Swatch
|
|
134
|
+
label="insetWell"
|
|
135
|
+
note="inner top cut + a light line on the floor. The floor rim is the half that says 'below'."
|
|
136
|
+
style={insetWell(greyRamp[950]) as Record<string, unknown>}
|
|
137
|
+
/>
|
|
138
|
+
<Swatch
|
|
139
|
+
label="barPaper"
|
|
140
|
+
note="the same grain curve, tuned for a small saturated element."
|
|
141
|
+
style={{ ...(barPaper(t['status-info']) as Record<string, unknown>), backgroundColor: t['status-info'] }}
|
|
142
|
+
/>
|
|
143
|
+
</View>
|
|
144
|
+
<Text className="text-text-tertiary text-xs">
|
|
145
|
+
Materials are web/RNW only — `backgroundImage` and multi-layer `boxShadow` are ignored on
|
|
146
|
+
native. Every material keeps a solid `backgroundColor` underneath so a surface never
|
|
147
|
+
depends on one, and a material must never be the only thing carrying meaning.
|
|
148
|
+
</Text>
|
|
149
|
+
</View>
|
|
150
|
+
),
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
export const Elevation: StoryObj = {
|
|
154
|
+
name: '2. Elevation — tone below, shadow only when floating',
|
|
155
|
+
render: () => (
|
|
156
|
+
<View style={{ padding: 24, backgroundColor: greyRamp[975] }}>
|
|
157
|
+
<Text className="text-2xl font-bold text-text-primary mb-2">Elevation</Text>
|
|
158
|
+
<SectionIntro>
|
|
159
|
+
Levels 0–3 are separated by TONE ALONE. They cast no shadow, because a dark-on-dark drop
|
|
160
|
+
shadow is inert — it costs a composite layer and shows nothing. From{' '}
|
|
161
|
+
<Text className="font-semibold">{FLOATING_ELEVATION_MIN}</Text> up the element is
|
|
162
|
+
genuinely floating over a backdrop, where a large soft shadow describes separation rather
|
|
163
|
+
than rank, and there the shadow returns.
|
|
164
|
+
</SectionIntro>
|
|
165
|
+
|
|
166
|
+
<SectionTitle>Content levels — tone only</SectionTitle>
|
|
167
|
+
<View style={{ flexDirection: 'row', gap: 10, marginBottom: 22 }}>
|
|
168
|
+
{CONTENT_LEVELS.map((lv) => (
|
|
169
|
+
<Swatch
|
|
170
|
+
key={lv}
|
|
171
|
+
label={`elevation ${lv}`}
|
|
172
|
+
note={`${getElevationSurface(base, lv, 'dark')} · no shadow`}
|
|
173
|
+
style={{
|
|
174
|
+
backgroundColor: getElevationSurface(base, lv, 'dark'),
|
|
175
|
+
borderWidth: 1,
|
|
176
|
+
borderColor: t['hairline-default'],
|
|
177
|
+
...(getElevationShadow(base, lv, 'dark') as Record<string, unknown>),
|
|
178
|
+
}}
|
|
179
|
+
/>
|
|
180
|
+
))}
|
|
181
|
+
</View>
|
|
182
|
+
|
|
183
|
+
<SectionTitle>Floating levels — shadow returns, always with a ring</SectionTitle>
|
|
184
|
+
<View style={{ flexDirection: 'row', gap: 24, marginBottom: 8 }}>
|
|
185
|
+
{FLOATING_LEVELS.map((lv) => (
|
|
186
|
+
<Swatch
|
|
187
|
+
key={lv}
|
|
188
|
+
label={`elevation ${lv}`}
|
|
189
|
+
note={`${getElevationSurface(base, lv, 'dark')} · casts a shadow`}
|
|
190
|
+
style={{
|
|
191
|
+
backgroundColor: getElevationSurface(base, lv, 'dark'),
|
|
192
|
+
borderWidth: 1,
|
|
193
|
+
borderColor: t['hairline-strong'],
|
|
194
|
+
...(getElevationShadow(base, lv, 'dark') as Record<string, unknown>),
|
|
195
|
+
}}
|
|
196
|
+
/>
|
|
197
|
+
))}
|
|
198
|
+
</View>
|
|
199
|
+
<Text className="text-text-tertiary text-xs">
|
|
200
|
+
A floating surface should carry a hairline ring as well as its shadow — the shadow places
|
|
201
|
+
it above the page, the ring gives it an edge that survives on a panel where the shadow is
|
|
202
|
+
washed out by glare.
|
|
203
|
+
</Text>
|
|
204
|
+
</View>
|
|
205
|
+
),
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
export const WhyNotNeumorphism: StoryObj = {
|
|
209
|
+
name: '3. Why not neumorphism',
|
|
210
|
+
render: () => (
|
|
211
|
+
<View style={{ padding: 24, backgroundColor: greyRamp[975] }}>
|
|
212
|
+
<Text className="text-2xl font-bold text-text-primary mb-2">Why not neumorphism</Text>
|
|
213
|
+
<SectionIntro>
|
|
214
|
+
The system used to ship a neumorphic builder — a dark shadow bottom-right paired with a
|
|
215
|
+
light rim top-left. It was removed rather than retuned, and the reason is a lightness
|
|
216
|
+
budget rather than a preference, so it is worth being able to see.
|
|
217
|
+
</SectionIntro>
|
|
218
|
+
|
|
219
|
+
<Text className="text-text-secondary text-xs mb-4">
|
|
220
|
+
Neumorphism needs a MID-TONE ground (roughly 75–90% lightness) so the dark half has
|
|
221
|
+
somewhere to fall and the light half has somewhere to rise. Our surfaces sit near 10%.
|
|
222
|
+
Below them there is almost nothing left — so the dark half disappears and the treatment
|
|
223
|
+
collapses to a one-sided rim, which reads as a smudge rather than as depth.
|
|
224
|
+
</Text>
|
|
225
|
+
|
|
226
|
+
<View style={{ flexDirection: 'row', gap: 24 }}>
|
|
227
|
+
<View>
|
|
228
|
+
<Text className="text-text-secondary text-xs mb-2">
|
|
229
|
+
On a mid-tone ground — both halves have room
|
|
230
|
+
</Text>
|
|
231
|
+
<View
|
|
232
|
+
style={{
|
|
233
|
+
backgroundColor: greyRamp[300],
|
|
234
|
+
padding: 26,
|
|
235
|
+
borderRadius: 12,
|
|
236
|
+
flexDirection: 'row',
|
|
237
|
+
gap: 16,
|
|
238
|
+
}}
|
|
239
|
+
>
|
|
240
|
+
<View
|
|
241
|
+
style={
|
|
242
|
+
{
|
|
243
|
+
width: 110,
|
|
244
|
+
height: 66,
|
|
245
|
+
borderRadius: 10,
|
|
246
|
+
backgroundColor: greyRamp[300],
|
|
247
|
+
boxShadow: '3px 3px 6px rgba(0,0,0,0.35), -3px -3px 6px rgba(255,255,255,0.55)',
|
|
248
|
+
} as Record<string, unknown>
|
|
249
|
+
}
|
|
250
|
+
/>
|
|
251
|
+
</View>
|
|
252
|
+
</View>
|
|
253
|
+
|
|
254
|
+
<View>
|
|
255
|
+
<Text className="text-text-secondary text-xs mb-2">
|
|
256
|
+
On our ground — the dark half has nowhere to go
|
|
257
|
+
</Text>
|
|
258
|
+
<View
|
|
259
|
+
style={{
|
|
260
|
+
backgroundColor: greyRamp[925],
|
|
261
|
+
padding: 26,
|
|
262
|
+
borderRadius: 12,
|
|
263
|
+
flexDirection: 'row',
|
|
264
|
+
gap: 16,
|
|
265
|
+
}}
|
|
266
|
+
>
|
|
267
|
+
<View
|
|
268
|
+
style={
|
|
269
|
+
{
|
|
270
|
+
width: 110,
|
|
271
|
+
height: 66,
|
|
272
|
+
borderRadius: 10,
|
|
273
|
+
backgroundColor: greyRamp[925],
|
|
274
|
+
boxShadow: '3px 3px 6px rgba(0,0,0,0.35), -3px -3px 6px rgba(255,255,255,0.12)',
|
|
275
|
+
} as Record<string, unknown>
|
|
276
|
+
}
|
|
277
|
+
/>
|
|
278
|
+
</View>
|
|
279
|
+
</View>
|
|
280
|
+
|
|
281
|
+
<View>
|
|
282
|
+
<Text className="text-text-secondary text-xs mb-2">What we do instead</Text>
|
|
283
|
+
<View
|
|
284
|
+
style={{
|
|
285
|
+
backgroundColor: greyRamp[925],
|
|
286
|
+
padding: 26,
|
|
287
|
+
borderRadius: 12,
|
|
288
|
+
flexDirection: 'row',
|
|
289
|
+
gap: 16,
|
|
290
|
+
}}
|
|
291
|
+
>
|
|
292
|
+
<View
|
|
293
|
+
style={{
|
|
294
|
+
width: 110,
|
|
295
|
+
height: 66,
|
|
296
|
+
borderRadius: 10,
|
|
297
|
+
backgroundColor: greyRamp[900],
|
|
298
|
+
borderWidth: 1,
|
|
299
|
+
borderColor: t['hairline-default'],
|
|
300
|
+
}}
|
|
301
|
+
/>
|
|
302
|
+
</View>
|
|
303
|
+
</View>
|
|
304
|
+
</View>
|
|
305
|
+
|
|
306
|
+
<Text className="text-text-tertiary text-xs mt-4">
|
|
307
|
+
Same treatment, same offsets, different ground. The middle panel is the one that shipped.
|
|
308
|
+
The right panel is one tone step plus a hairline — cheaper, and it survives glare.
|
|
309
|
+
</Text>
|
|
310
|
+
</View>
|
|
311
|
+
),
|
|
312
|
+
}
|