@titan-design/react-ui 0.10.0 → 0.11.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.d.mts +399 -4
- package/dist/index.d.ts +399 -4
- package/dist/index.js +2295 -895
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +2279 -905
- 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 +1974 -1740
- package/dist/pages.js.map +1 -1
- package/dist/pages.mjs +1975 -1741
- package/dist/pages.mjs.map +1 -1
- package/package.json +8 -3
- 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/Workout/DualVelocityStrip.test.tsx +685 -0
- package/src/components/custom/Workout/ExerciseCard.test.tsx +4 -4
- package/src/components/custom/Workout/ExerciseCard.tsx +3 -1
- package/src/components/custom/Workout/ExerciseDetailPage.tsx +7 -1
- package/src/components/custom/Workout/README.md +62 -16
- package/src/components/custom/Workout/SegmentedBar.stories.tsx +3 -3
- package/src/components/custom/Workout/SessionRail.test.tsx +43 -0
- package/src/components/custom/Workout/SessionRail.tsx +50 -13
- package/src/components/custom/Workout/SetRow.test.tsx +6 -6
- package/src/components/custom/Workout/SetRow.tsx +9 -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 +196 -79
- package/src/components/custom/Workout/VelocityStrip.tsx +953 -609
- package/src/components/custom/Workout/index.ts +3 -0
- 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/test/raw-color-patterns.test.ts +71 -0
- package/src/theme/ColorPalettes.stories.tsx +871 -0
- package/src/theme/ColorPrimitives.stories.tsx +300 -0
- package/src/theme/bar-paper.ts +59 -0
- package/src/theme/color-stories.coverage.test.ts +130 -0
- package/src/theme/color-story-kit.tsx +170 -0
- 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
|
@@ -27,6 +27,9 @@ export {
|
|
|
27
27
|
getVelocityZoneName,
|
|
28
28
|
calculateVelocityLoss,
|
|
29
29
|
calculateMeanVelocity,
|
|
30
|
+
DualVelocityStrip,
|
|
31
|
+
type DualVelocityStripProps,
|
|
32
|
+
type DualVelocityStream,
|
|
30
33
|
} from './VelocityStrip'
|
|
31
34
|
export { MuscleGroupChip, type MuscleGroupChipProps, type VolumeStatus } from './MuscleGroupChip'
|
|
32
35
|
export { Sparkline, type SparklineProps } from './Sparkline'
|
|
@@ -0,0 +1,438 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared fixtures and framing for the VelocityStrip story groups.
|
|
3
|
+
*
|
|
4
|
+
* The strip's stories are split by VIEW (compact / expanded / hero), and each
|
|
5
|
+
* group walks the SAME scenario set against the SAME datasets. That only proves
|
|
6
|
+
* anything if every group is genuinely showing the same data — so the datasets,
|
|
7
|
+
* the single-above-dual pairing, and the set-type board all live here rather
|
|
8
|
+
* than being re-typed per file.
|
|
9
|
+
*
|
|
10
|
+
* Dual is not a fourth view. It is the same strip when the exercise used two
|
|
11
|
+
* Voltras, so every scenario renders single ABOVE dual on one dataset and the
|
|
12
|
+
* pairing itself carries the point: dual = single + a lower half.
|
|
13
|
+
*
|
|
14
|
+
* Not a `.stories` file on purpose — it exports no stories, and keeping it out
|
|
15
|
+
* of the story glob means the chrome below is held to the same token rules as
|
|
16
|
+
* component code.
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
import type { ReactNode } from 'react'
|
|
20
|
+
import type { Decorator } from '@storybook/react-vite'
|
|
21
|
+
import { View, Text } from 'react-native'
|
|
22
|
+
import { VelocityStrip, DualVelocityStrip, type VelocitySet } from './VelocityStrip'
|
|
23
|
+
import { surfaceRampDark } from '../../../theme/tokens/primitives'
|
|
24
|
+
import { getSemanticColors } from '../../../theme/tokens/semantic'
|
|
25
|
+
|
|
26
|
+
const t = getSemanticColors('dark')
|
|
27
|
+
|
|
28
|
+
/** The real dark surface-background plane — stories sit on the token, not an ad-hoc grey. */
|
|
29
|
+
export const SURFACE_BG = surfaceRampDark.background
|
|
30
|
+
|
|
31
|
+
export const VIEW_LABEL = t['text-secondary']
|
|
32
|
+
export const VIEW_NOTE = t['text-tertiary']
|
|
33
|
+
export const VIEW_HEADING = t['text-primary']
|
|
34
|
+
|
|
35
|
+
export const LEFT_SLOT = 'Left'
|
|
36
|
+
export const RIGHT_SLOT = 'Right'
|
|
37
|
+
|
|
38
|
+
// ── Datasets ────────────────────────────────────────────────────────────────
|
|
39
|
+
// One representative set reused across every view, so a drift in one view is
|
|
40
|
+
// visible as a mismatch against the others rather than hiding behind new data.
|
|
41
|
+
|
|
42
|
+
/** The representative set: a clean, mildly-declining working set. */
|
|
43
|
+
export const REP_SET = [0.95, 0.9, 0.86, 0.8, 0.72]
|
|
44
|
+
/** The lagging partner — 4 reps to REP_SET's 5, so the index-lock shows an empty. */
|
|
45
|
+
export const REP_SET_LAGGING = [0.9, 0.83, 0.75, 0.66]
|
|
46
|
+
|
|
47
|
+
/** A hard fatigue decline — the loss colouring and VL bands have something to say. */
|
|
48
|
+
export const FATIGUE_SET = [0.96, 0.91, 0.83, 0.72, 0.61, 0.5]
|
|
49
|
+
export const FATIGUE_SET_LAGGING = [0.94, 0.87, 0.76, 0.63, 0.48]
|
|
50
|
+
|
|
51
|
+
/** Mid-set, still going: 4 done of 8 planned, newest bar live. */
|
|
52
|
+
export const IN_PROGRESS_SET = [0.82, 0.79, 0.81, 0.76]
|
|
53
|
+
export const IN_PROGRESS_LAGGING = [0.8, 0.77, 0.75]
|
|
54
|
+
|
|
55
|
+
export const dualOf = (velocities: number[], label: string) => ({ velocities, label })
|
|
56
|
+
|
|
57
|
+
// ── Framing ─────────────────────────────────────────────────────────────────
|
|
58
|
+
|
|
59
|
+
export function ViewLabel({ text }: { text: string }) {
|
|
60
|
+
return (
|
|
61
|
+
<Text
|
|
62
|
+
style={{
|
|
63
|
+
color: VIEW_LABEL,
|
|
64
|
+
fontSize: 10,
|
|
65
|
+
fontWeight: '700',
|
|
66
|
+
letterSpacing: 1,
|
|
67
|
+
textTransform: 'uppercase',
|
|
68
|
+
}}
|
|
69
|
+
>
|
|
70
|
+
{text}
|
|
71
|
+
</Text>
|
|
72
|
+
)
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
export function Note({ children }: { children: ReactNode }) {
|
|
76
|
+
return <Text style={{ color: VIEW_NOTE, fontSize: 10, lineHeight: 15 }}>{children}</Text>
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/** Page frame every scenario story sits in — one surface, one rhythm. */
|
|
80
|
+
export function Sheet({ children, width = 640 }: { children: ReactNode; width?: number }) {
|
|
81
|
+
return (
|
|
82
|
+
<View style={{ padding: 24, backgroundColor: SURFACE_BG, gap: 26, width }}>{children}</View>
|
|
83
|
+
)
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Wall frame for hero stories. The eyebrow is ORGANISM chrome — the north-star
|
|
88
|
+
* live page renders it, the primitive does not. It sits here only so the hero
|
|
89
|
+
* is judged in the context it will live in.
|
|
90
|
+
*/
|
|
91
|
+
export const wallDecorator: Decorator = (Story) => (
|
|
92
|
+
<View style={{ width: 620, padding: 28, backgroundColor: SURFACE_BG }}>
|
|
93
|
+
<Text
|
|
94
|
+
style={{
|
|
95
|
+
color: VIEW_NOTE,
|
|
96
|
+
fontSize: 10,
|
|
97
|
+
fontWeight: '700',
|
|
98
|
+
letterSpacing: 1,
|
|
99
|
+
marginBottom: 12,
|
|
100
|
+
}}
|
|
101
|
+
>
|
|
102
|
+
CONCENTRIC VELOCITY · THIS SET · (organism chrome — not the component)
|
|
103
|
+
</Text>
|
|
104
|
+
<Story />
|
|
105
|
+
</View>
|
|
106
|
+
)
|
|
107
|
+
|
|
108
|
+
// ── The single-above-dual pairing ───────────────────────────────────────────
|
|
109
|
+
|
|
110
|
+
/** Per-view heights. Dual expanded is taller because value-height genuinely needs 2×. */
|
|
111
|
+
export const VIEW_HEIGHT: Record<StripView, { single: number; dual: number }> = {
|
|
112
|
+
compact: { single: 11.5, dual: 11.5 },
|
|
113
|
+
expanded: { single: 60, dual: 64 },
|
|
114
|
+
hero: { single: 180, dual: 200 },
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
export type StripView = 'compact' | 'expanded' | 'hero'
|
|
118
|
+
|
|
119
|
+
/** The dual renderer for a view. `expanded`'s dual is the lean `rail` (a verified misnomer). */
|
|
120
|
+
export const dualVariantFor = (view: StripView) => (view === 'expanded' ? 'rail' : view)
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* One scenario, rendered single above dual on the same dataset.
|
|
124
|
+
*
|
|
125
|
+
* Takes either raw velocities or full `VelocitySet` descriptors, because the
|
|
126
|
+
* set-type scenarios need the descriptor form and the rest do not.
|
|
127
|
+
*/
|
|
128
|
+
export function ScenarioPair({
|
|
129
|
+
view,
|
|
130
|
+
title,
|
|
131
|
+
note,
|
|
132
|
+
single,
|
|
133
|
+
left,
|
|
134
|
+
right,
|
|
135
|
+
singleSet,
|
|
136
|
+
leftSet,
|
|
137
|
+
rightSet,
|
|
138
|
+
scale = 'peak',
|
|
139
|
+
width,
|
|
140
|
+
...rest
|
|
141
|
+
}: {
|
|
142
|
+
view: StripView
|
|
143
|
+
title: string
|
|
144
|
+
note?: string
|
|
145
|
+
single?: number[]
|
|
146
|
+
left?: number[]
|
|
147
|
+
right?: number[]
|
|
148
|
+
singleSet?: VelocitySet
|
|
149
|
+
leftSet?: VelocitySet
|
|
150
|
+
rightSet?: VelocitySet
|
|
151
|
+
scale?: 'peak' | 'fixed'
|
|
152
|
+
/** Constrains the pair, for the width-degrade ladders. Unset = fill the sheet. */
|
|
153
|
+
width?: number
|
|
154
|
+
targetReps?: number
|
|
155
|
+
liveRepIndex?: number
|
|
156
|
+
barColor?: 'zone' | 'loss'
|
|
157
|
+
}) {
|
|
158
|
+
const h = VIEW_HEIGHT[view]
|
|
159
|
+
const heroProps = view === 'hero' ? { label: 'Set' } : {}
|
|
160
|
+
const bareExpanded = view === 'expanded' ? { showNumbers: false, showInfo: false } : {}
|
|
161
|
+
|
|
162
|
+
return (
|
|
163
|
+
<View style={{ gap: 10, ...(width == null ? null : { width }) }}>
|
|
164
|
+
<View style={{ gap: 3 }}>
|
|
165
|
+
<ViewLabel text={title} />
|
|
166
|
+
{note ? <Note>{note}</Note> : null}
|
|
167
|
+
</View>
|
|
168
|
+
|
|
169
|
+
<ViewLabel text="single" />
|
|
170
|
+
<VelocityStrip
|
|
171
|
+
{...(singleSet ? { set: singleSet } : { velocities: single ?? [] })}
|
|
172
|
+
variant={view}
|
|
173
|
+
height={h.single}
|
|
174
|
+
scale={scale}
|
|
175
|
+
{...heroProps}
|
|
176
|
+
{...bareExpanded}
|
|
177
|
+
{...rest}
|
|
178
|
+
/>
|
|
179
|
+
|
|
180
|
+
<ViewLabel text="dual" />
|
|
181
|
+
<DualVelocityStrip
|
|
182
|
+
left={leftSet ? { set: leftSet, label: LEFT_SLOT } : dualOf(left ?? [], LEFT_SLOT)}
|
|
183
|
+
right={rightSet ? { set: rightSet, label: RIGHT_SLOT } : dualOf(right ?? [], RIGHT_SLOT)}
|
|
184
|
+
variant={dualVariantFor(view)}
|
|
185
|
+
height={h.dual}
|
|
186
|
+
scale={scale}
|
|
187
|
+
{...rest}
|
|
188
|
+
/>
|
|
189
|
+
</View>
|
|
190
|
+
)
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
// ── Set-type board ──────────────────────────────────────────────────────────
|
|
194
|
+
// The full set-type vocabulary, one row per type, each paired single-above-dual.
|
|
195
|
+
// Generalised over `view` so compact / expanded / hero all walk the same board
|
|
196
|
+
// instead of the hero owning a private copy.
|
|
197
|
+
|
|
198
|
+
export type BoardRow = {
|
|
199
|
+
type: string
|
|
200
|
+
note: string
|
|
201
|
+
single: VelocitySet
|
|
202
|
+
left: VelocitySet
|
|
203
|
+
right: VelocitySet
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
export const SET_TYPE_BOARD: BoardRow[] = [
|
|
207
|
+
{
|
|
208
|
+
type: 'straight',
|
|
209
|
+
note: 'done reps + solid to-do remainder to the planned count',
|
|
210
|
+
single: { type: 'straight', velocities: [0.9, 0.86, 0.82, 0.78], planned: 6 },
|
|
211
|
+
left: { type: 'straight', velocities: [0.9, 0.86, 0.82, 0.78], planned: 6 },
|
|
212
|
+
right: { type: 'straight', velocities: [0.84, 0.79], planned: 6 },
|
|
213
|
+
},
|
|
214
|
+
{
|
|
215
|
+
type: 'range',
|
|
216
|
+
note: 'committed rep bars + solid floor + the cyan variable window (floor..max)',
|
|
217
|
+
single: { type: 'range', velocities: [0.9, 0.86, 0.82, 0.8], floor: 6, max: 8 },
|
|
218
|
+
left: { type: 'range', velocities: [0.9, 0.86, 0.82, 0.8], floor: 6, max: 8 },
|
|
219
|
+
right: { type: 'range', velocities: [0.83, 0.78], floor: 6, max: 8 },
|
|
220
|
+
},
|
|
221
|
+
{
|
|
222
|
+
type: 'amrap',
|
|
223
|
+
note: 'performed reps + the trailing cyan-outline "continue" window',
|
|
224
|
+
single: { type: 'amrap', velocities: [0.9, 0.85, 0.8, 0.75] },
|
|
225
|
+
left: { type: 'amrap', velocities: [0.9, 0.85, 0.8, 0.75] },
|
|
226
|
+
right: { type: 'amrap', velocities: [0.82, 0.76] },
|
|
227
|
+
},
|
|
228
|
+
{
|
|
229
|
+
type: 'drop',
|
|
230
|
+
note: 'one bar per rep with WIDE chunk-notch gaps splitting each sub-load',
|
|
231
|
+
single: {
|
|
232
|
+
type: 'drop',
|
|
233
|
+
subloads: [
|
|
234
|
+
[0.95, 0.9],
|
|
235
|
+
[0.82, 0.76],
|
|
236
|
+
[0.7, 0.64],
|
|
237
|
+
],
|
|
238
|
+
},
|
|
239
|
+
left: {
|
|
240
|
+
type: 'drop',
|
|
241
|
+
subloads: [
|
|
242
|
+
[0.95, 0.9],
|
|
243
|
+
[0.82, 0.76],
|
|
244
|
+
[0.7, 0.64],
|
|
245
|
+
],
|
|
246
|
+
},
|
|
247
|
+
right: { type: 'drop', subloads: [[0.88, 0.83], [0.75, 0.68], [0.62]] },
|
|
248
|
+
},
|
|
249
|
+
]
|
|
250
|
+
|
|
251
|
+
export function SetTypeBoard({ view }: { view: StripView }) {
|
|
252
|
+
const h = VIEW_HEIGHT[view]
|
|
253
|
+
return (
|
|
254
|
+
<View style={{ gap: 30 }}>
|
|
255
|
+
{SET_TYPE_BOARD.map((row) => (
|
|
256
|
+
<View key={row.type} style={{ gap: 8 }}>
|
|
257
|
+
<View style={{ flexDirection: 'row', alignItems: 'baseline', gap: 10, flexWrap: 'wrap' }}>
|
|
258
|
+
<Text
|
|
259
|
+
style={{
|
|
260
|
+
color: VIEW_HEADING,
|
|
261
|
+
fontSize: 13,
|
|
262
|
+
fontWeight: '800',
|
|
263
|
+
letterSpacing: 1,
|
|
264
|
+
textTransform: 'uppercase',
|
|
265
|
+
}}
|
|
266
|
+
>
|
|
267
|
+
{row.type}
|
|
268
|
+
</Text>
|
|
269
|
+
<Note>{row.note}</Note>
|
|
270
|
+
</View>
|
|
271
|
+
|
|
272
|
+
<ViewLabel text="single" />
|
|
273
|
+
<VelocityStrip
|
|
274
|
+
variant={view}
|
|
275
|
+
set={row.single}
|
|
276
|
+
height={h.single}
|
|
277
|
+
scale="fixed"
|
|
278
|
+
{...(view === 'hero' ? { label: 'This Set' } : {})}
|
|
279
|
+
{...(view === 'expanded' ? { showNumbers: false, showInfo: false } : {})}
|
|
280
|
+
/>
|
|
281
|
+
|
|
282
|
+
<ViewLabel text="dual · symmetric structure, right logs fewer → aligned empties" />
|
|
283
|
+
<DualVelocityStrip
|
|
284
|
+
left={{ set: row.left, label: LEFT_SLOT }}
|
|
285
|
+
right={{ set: row.right, label: RIGHT_SLOT }}
|
|
286
|
+
variant={dualVariantFor(view)}
|
|
287
|
+
height={h.dual}
|
|
288
|
+
scale="fixed"
|
|
289
|
+
/>
|
|
290
|
+
</View>
|
|
291
|
+
))}
|
|
292
|
+
</View>
|
|
293
|
+
)
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
// ── Rep-type coverage ───────────────────────────────────────────────────────
|
|
297
|
+
// Every state a single rep column can be in, in one board. The velocity zones
|
|
298
|
+
// are only three of them; the rest come from the set descriptor (planned
|
|
299
|
+
// remainder, the repeatable windows, drop sub-load splits) or from the dual's
|
|
300
|
+
// index-lock (a rep one side logged and the other did not).
|
|
301
|
+
//
|
|
302
|
+
// Generalised over `view` for the same reason the set-type board is: a state
|
|
303
|
+
// that renders on hero but not compact is exactly the class of bug this is
|
|
304
|
+
// meant to surface.
|
|
305
|
+
|
|
306
|
+
export type RepTypeRow = {
|
|
307
|
+
key: string
|
|
308
|
+
label: string
|
|
309
|
+
note: string
|
|
310
|
+
single: VelocitySet
|
|
311
|
+
left: VelocitySet
|
|
312
|
+
right: VelocitySet
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
/** Velocities chosen to land in each zone: fast, moderate, slow, grinding. */
|
|
316
|
+
const ZONE_SET = [1.15, 0.88, 0.66, 0.42]
|
|
317
|
+
|
|
318
|
+
export const REP_TYPE_ROWS: RepTypeRow[] = [
|
|
319
|
+
{
|
|
320
|
+
key: 'zones',
|
|
321
|
+
label: 'Logged reps · the velocity zones',
|
|
322
|
+
note: 'fast → moderate → slow → grinding. One bar per rep, coloured by the zone it landed in.',
|
|
323
|
+
single: { type: 'straight', velocities: ZONE_SET },
|
|
324
|
+
left: { type: 'straight', velocities: ZONE_SET },
|
|
325
|
+
right: { type: 'straight', velocities: ZONE_SET },
|
|
326
|
+
},
|
|
327
|
+
{
|
|
328
|
+
key: 'upcoming',
|
|
329
|
+
label: 'Upcoming · the planned remainder',
|
|
330
|
+
note: 'Two logged of six planned. The remainder holds its columns so the set never reflows as reps land.',
|
|
331
|
+
single: { type: 'straight', velocities: [0.9, 0.86], planned: 6 },
|
|
332
|
+
left: { type: 'straight', velocities: [0.9, 0.86], planned: 6 },
|
|
333
|
+
right: { type: 'straight', velocities: [0.88, 0.84], planned: 6 },
|
|
334
|
+
},
|
|
335
|
+
{
|
|
336
|
+
key: 'missed-bottom',
|
|
337
|
+
label: 'Missed rep · lower slot',
|
|
338
|
+
note: 'Both slots logged four, but the LOWER one missed rep 3. Its column stays as an index-locked empty, fainter than a planned rep — so the gap reads as missing, not upcoming.',
|
|
339
|
+
single: { type: 'straight', velocities: [0.9, 0.86, 0.82, 0.78] },
|
|
340
|
+
left: { type: 'straight', velocities: [0.9, 0.86, 0.82, 0.78] },
|
|
341
|
+
right: { type: 'straight', velocities: [0.88, 0.84, 0.76] },
|
|
342
|
+
},
|
|
343
|
+
{
|
|
344
|
+
key: 'missed-top',
|
|
345
|
+
label: 'Missed rep · upper slot',
|
|
346
|
+
note: 'The mirror case — the UPPER slot is the one short a rep. The empty sits above the axis instead of below it.',
|
|
347
|
+
single: { type: 'straight', velocities: [0.9, 0.86, 0.82, 0.78] },
|
|
348
|
+
left: { type: 'straight', velocities: [0.9, 0.86, 0.82] },
|
|
349
|
+
right: { type: 'straight', velocities: [0.88, 0.84, 0.8, 0.76] },
|
|
350
|
+
},
|
|
351
|
+
{
|
|
352
|
+
key: 'variable',
|
|
353
|
+
label: 'Variable · a rep range',
|
|
354
|
+
note: 'A range set: committed reps to the floor, then the variable window up to the max. Outlined, never filled — a rep that MIGHT happen must not read as one that did.',
|
|
355
|
+
single: { type: 'range', velocities: [0.9, 0.86, 0.82], floor: 4, max: 6 },
|
|
356
|
+
left: { type: 'range', velocities: [0.9, 0.86, 0.82], floor: 4, max: 6 },
|
|
357
|
+
right: { type: 'range', velocities: [0.88, 0.84, 0.8], floor: 4, max: 6 },
|
|
358
|
+
},
|
|
359
|
+
{
|
|
360
|
+
key: 'repeatable',
|
|
361
|
+
label: 'Repeatable · AMRAP / myo continue',
|
|
362
|
+
note: 'No planned ceiling — the trailing continue window says another rep is allowed at any time.',
|
|
363
|
+
single: { type: 'amrap', velocities: [0.9, 0.85, 0.8] },
|
|
364
|
+
left: { type: 'amrap', velocities: [0.9, 0.85, 0.8] },
|
|
365
|
+
right: { type: 'amrap', velocities: [0.88, 0.83, 0.78] },
|
|
366
|
+
},
|
|
367
|
+
{
|
|
368
|
+
key: 'drop',
|
|
369
|
+
label: 'Drop · sub-load splits',
|
|
370
|
+
note: 'One bar per rep, with a wide chunk-notch separating each sub-load. The notch is a gap in the columns, so it survives even at compact height.',
|
|
371
|
+
single: {
|
|
372
|
+
type: 'drop',
|
|
373
|
+
subloads: [
|
|
374
|
+
[0.95, 0.9],
|
|
375
|
+
[0.82, 0.76],
|
|
376
|
+
[0.7, 0.64],
|
|
377
|
+
],
|
|
378
|
+
},
|
|
379
|
+
left: {
|
|
380
|
+
type: 'drop',
|
|
381
|
+
subloads: [
|
|
382
|
+
[0.95, 0.9],
|
|
383
|
+
[0.82, 0.76],
|
|
384
|
+
[0.7, 0.64],
|
|
385
|
+
],
|
|
386
|
+
},
|
|
387
|
+
right: {
|
|
388
|
+
type: 'drop',
|
|
389
|
+
subloads: [
|
|
390
|
+
[0.93, 0.88],
|
|
391
|
+
[0.8, 0.74],
|
|
392
|
+
[0.68, 0.62],
|
|
393
|
+
],
|
|
394
|
+
},
|
|
395
|
+
},
|
|
396
|
+
]
|
|
397
|
+
|
|
398
|
+
export function RepTypeBoard({ view }: { view: StripView }) {
|
|
399
|
+
const h = VIEW_HEIGHT[view]
|
|
400
|
+
return (
|
|
401
|
+
<View style={{ gap: 30 }}>
|
|
402
|
+
{REP_TYPE_ROWS.map((row) => (
|
|
403
|
+
<View key={row.key} style={{ gap: 8 }}>
|
|
404
|
+
<Text
|
|
405
|
+
style={{
|
|
406
|
+
color: VIEW_HEADING,
|
|
407
|
+
fontSize: 13,
|
|
408
|
+
fontWeight: '800',
|
|
409
|
+
letterSpacing: 0.5,
|
|
410
|
+
}}
|
|
411
|
+
>
|
|
412
|
+
{row.label}
|
|
413
|
+
</Text>
|
|
414
|
+
<Note>{row.note}</Note>
|
|
415
|
+
|
|
416
|
+
<ViewLabel text="single" />
|
|
417
|
+
<VelocityStrip
|
|
418
|
+
variant={view}
|
|
419
|
+
set={row.single}
|
|
420
|
+
height={h.single}
|
|
421
|
+
scale="fixed"
|
|
422
|
+
{...(view === 'hero' ? { label: 'Set' } : {})}
|
|
423
|
+
{...(view === 'expanded' ? { showNumbers: false, showInfo: false } : {})}
|
|
424
|
+
/>
|
|
425
|
+
|
|
426
|
+
<ViewLabel text="dual" />
|
|
427
|
+
<DualVelocityStrip
|
|
428
|
+
left={{ set: row.left, label: LEFT_SLOT }}
|
|
429
|
+
right={{ set: row.right, label: RIGHT_SLOT }}
|
|
430
|
+
variant={dualVariantFor(view)}
|
|
431
|
+
height={h.dual}
|
|
432
|
+
scale="fixed"
|
|
433
|
+
/>
|
|
434
|
+
</View>
|
|
435
|
+
))}
|
|
436
|
+
</View>
|
|
437
|
+
)
|
|
438
|
+
}
|