diff --git a/README.md b/README.md index 4ea3114..bbaa9fb 100644 --- a/README.md +++ b/README.md @@ -30,7 +30,7 @@ Or scan the QR code with **Expo Go** on your phone. ``` assets/exercises.json # the free-tier bundle (400 exercises), imported as a module -assets/images/flat/*.webp # 745 flat webp (start/peak pairs + single-pose "main") +assets/images/flat/*.webp # 741 flat webp (start/peak pairs + single-pose "main") assets/images/muscles/*.webp # 27 muscle icons assets/images/equipment/*.webp # 46 equipment icons assets/images/samples/*.webp # 1 paid-tier looping animation (Standard-tier preview) @@ -43,7 +43,7 @@ LICENSE # MIT for the example code React Native's Metro bundler resolves `require()` of static assets at build time. Dynamic paths like `require('./assets/' + name)` won't work. We generate -`lib/images.ts` once with one explicit `require()` per file (745 flat + samples +`lib/images.ts` once with one explicit `require()` per file (741 flat + samples + icons), so `getImage(slug, variant)` returns a usable module ID at runtime. The paid-tier preview gallery is **derived** from the animation files in diff --git a/app/_layout.tsx b/app/_layout.tsx index bf47b85..8d486b5 100644 --- a/app/_layout.tsx +++ b/app/_layout.tsx @@ -20,7 +20,9 @@ export default function RootLayout() { headerShadowVisible: false, contentStyle: { backgroundColor: t.bg }, }} - /> + > + + ); diff --git a/app/index.tsx b/app/index.tsx index 8eae1b1..d963a76 100644 --- a/app/index.tsx +++ b/app/index.tsx @@ -45,7 +45,6 @@ export default function ListScreen() { )} ListFooterComponent={ <> - Exercise data by RepDB (repdb.co) — free tier, attribution required. @@ -60,6 +59,9 @@ export default function ListScreen() { } /> + + + ); } @@ -75,7 +77,15 @@ const styles = StyleSheet.create({ }, title: { fontSize: 24, fontWeight: '700', letterSpacing: -0.3 }, subtitle: { fontSize: 13, marginTop: 4 }, - list: { paddingHorizontal: 16, paddingTop: 8, paddingBottom: 16 }, + list: { paddingHorizontal: 16, paddingTop: 8, paddingBottom: 180 }, + promoOverlay: { + position: 'absolute', + left: 16, + right: 16, + bottom: 16, + zIndex: 10, + alignItems: 'flex-end', + }, footer: { paddingTop: 16, marginTop: 16, diff --git a/assets/exercises.json b/assets/exercises.json index b93a51c..3c1df2b 100644 --- a/assets/exercises.json +++ b/assets/exercises.json @@ -1,6 +1,6 @@ { "schema_version": 3, - "generated_at": "2026-07-16T19:41:37Z", + "generated_at": "2026-07-27T13:53:50Z", "locales": [ "de", "en", @@ -12294,7 +12294,7 @@ "dumbbell pistol squat" ], "instructions_en": [ - "Stand tall, holding a dumbbell in front of your chest with both hands or in one hand on the same side as the working leg.", + "Stand tall, holding a dumbbell in each hand with your arms hanging at your sides.", "Lift one leg straight out in front of you, keeping it extended throughout the movement.", "Lower your body by bending the knee of your standing leg, keeping your chest up and back straight.", "Descend until your glutes are close to your heel or your thigh is parallel to the floor, maintaining balance.", @@ -12302,8 +12302,8 @@ "Complete all reps on one side, then switch legs and repeat for the other side per set." ], "tips_en": [ - "Start with bodyweight pistol squats to master the movement pattern before adding a dumbbell.", - "Use a counterweight (dumbbell) held out in front to help maintain balance during the descent.", + "Start with bodyweight pistol squats to master the movement pattern before adding dumbbells.", + "Let the dumbbells hang straight down at your sides and keep your chest up to stay balanced over the working foot.", "Focus on controlling the eccentric (lowering) phase to improve stability and strength.", "Elevate your heel slightly on a small plate if ankle mobility is a limiting factor." ], @@ -12318,7 +12318,7 @@ "name_de": "Kurzhantel-Pistol-Squat", "description_de": "Der Kurzhantel-Pistol-Squat ist eine fortgeschrittene einbeinige Kniebeugen-Variante, die erhebliche Unterkörperkraft, Gleichgewicht und Beweglichkeit aufbaut, hauptsächlich für Quadrizeps und Gesäßmuskulatur.", "instructions_de": [ - "Stehe aufrecht und halte eine Kurzhantel mit beiden Händen vor der Brust oder in einer Hand auf der Seite des Arbeitsbeins.", + "Stehe aufrecht und halte in jeder Hand eine Kurzhantel, die Arme hängen seitlich am Körper.", "Hebe ein Bein gerade nach vorne an und halte es während der gesamten Bewegung gestreckt.", "Senke deinen Körper, indem du das Knie deines Standbeins beugst, Brust oben und Rücken gerade halten.", "Gehe so tief, bis dein Gesäß nahe an deiner Ferse ist oder dein Oberschenkel parallel zum Boden ist, und halte das Gleichgewicht.", @@ -12326,15 +12326,15 @@ "Absolviere alle Wiederholungen auf einer Seite, wechsle dann das Bein und wiederhole pro Satz für die andere Seite." ], "tips_de": [ - "Beginne mit Pistol-Squats ohne Gewicht, um das Bewegungsmuster zu meistern, bevor du eine Kurzhantel hinzufügst.", - "Nutze ein Gegengewicht (Kurzhantel), das du nach vorne hältst, um das Gleichgewicht während der Abwärtsbewegung zu halten.", - "Konzentriere dich auf die Kontrolle der exzentrischen (senkenden) Phase, um Stabilität und Kraft zu verbessern.", - "Erhöhe deine Ferse leicht auf einer kleinen Platte, wenn die Sprunggelenksbeweglichkeit ein limitierender Faktor ist." + "Beginne mit Pistol Squats ohne Gewicht, um das Bewegungsmuster zu beherrschen, bevor du Kurzhanteln hinzunimmst.", + "Lass die Kurzhanteln seitlich gerade nach unten hängen und halte die Brust aufrecht, um über dem Arbeitsfuß im Gleichgewicht zu bleiben.", + "Konzentriere dich auf die kontrollierte exzentrische (absenkende) Phase, um Stabilität und Kraft zu verbessern.", + "Erhöhe die Ferse leicht auf einer kleinen Scheibe, falls die Sprunggelenksbeweglichkeit ein limitierender Faktor ist." ], "name_es": "Sentadilla Pistola con Mancuerna", "description_es": "La Sentadilla Pistola con Mancuerna es una variación avanzada de sentadilla a una pierna que desarrolla fuerza significativa en la parte inferior del cuerpo, equilibrio y movilidad, apuntando principalmente a los cuádriceps y glúteos.", "instructions_es": [ - "Ponte de pie, sosteniendo una mancuerna frente a tu pecho con ambas manos o en una mano del mismo lado que la pierna que trabaja.", + "Ponte de pie, sosteniendo una mancuerna en cada mano con los brazos colgando a los lados.", "Levanta una pierna recta hacia adelante, manteniéndola extendida durante todo el movimiento.", "Baja tu cuerpo doblando la rodilla de tu pierna de apoyo, manteniendo el pecho erguido y la espalda recta.", "Desciende hasta que tus glúteos estén cerca de tu talón o tu muslo esté paralelo al suelo, manteniendo el equilibrio.", @@ -12342,10 +12342,10 @@ "Completa todas las repeticiones de un lado, luego cambia de pierna y repite para el otro lado por serie." ], "tips_es": [ - "Comienza con sentadillas pistola sin peso corporal para dominar el patrón de movimiento antes de añadir una mancuerna.", - "Usa un contrapeso (mancuerna) sostenido al frente para ayudar a mantener el equilibrio durante el descenso.", - "Concéntrate en controlar la fase excéntrica (bajada) para mejorar la estabilidad y la fuerza.", - "Eleva ligeramente tu talón sobre un pequeño disco si la movilidad del tobillo es un factor limitante." + "Empieza con sentadillas pistola sin peso para dominar el patrón de movimiento antes de añadir mancuernas.", + "Deja que las mancuernas cuelguen rectas a los lados y mantén el pecho erguido para conservar el equilibrio sobre el pie que trabaja.", + "Concéntrate en controlar la fase excéntrica (de descenso) para mejorar la estabilidad y la fuerza.", + "Eleva ligeramente el talón sobre un disco pequeño si la movilidad del tobillo es un factor limitante." ], "images": { "flat": [ @@ -13782,6 +13782,7 @@ "forearm_flexors", "trapezius" ], + "image_alias": "romanian-deadlift", "name_de": "SZ-Stange Romanian Deadlift", "description_de": "Ein Romanian Deadlift mit der SZ-Stange. Der angewinkelte Griff schont die Handgelenke und eignet sich für leichtere Hinge-Arbeit oder handgelenkempfindliche Athleten.", "instructions_de": [ @@ -28517,6 +28518,7 @@ "secondary_muscles": [ "soleus" ], + "image_alias": "machine-calf-raise", "name_de": "Stehende Wadenhebeübung", "description_de": "Eine im Stand ausgeführte Wadenübung, die den Gastrocnemius durch einen vollen Bewegungsumfang kräftigt.", "instructions_de": [ diff --git a/assets/images/flat/ab-wheel-rollout-peak.webp b/assets/images/flat/ab-wheel-rollout-peak.webp index 1810b0e..08c319a 100644 Binary files a/assets/images/flat/ab-wheel-rollout-peak.webp and b/assets/images/flat/ab-wheel-rollout-peak.webp differ diff --git a/assets/images/flat/ab-wheel-rollout-start.webp b/assets/images/flat/ab-wheel-rollout-start.webp index c171e11..888ae52 100644 Binary files a/assets/images/flat/ab-wheel-rollout-start.webp and b/assets/images/flat/ab-wheel-rollout-start.webp differ diff --git a/assets/images/flat/bench-leg-pull-in-peak.webp b/assets/images/flat/bench-leg-pull-in-peak.webp index 7d2d6d5..7c7dd91 100644 Binary files a/assets/images/flat/bench-leg-pull-in-peak.webp and b/assets/images/flat/bench-leg-pull-in-peak.webp differ diff --git a/assets/images/flat/bench-leg-pull-in-start.webp b/assets/images/flat/bench-leg-pull-in-start.webp index 82ac3b7..6da401e 100644 Binary files a/assets/images/flat/bench-leg-pull-in-start.webp and b/assets/images/flat/bench-leg-pull-in-start.webp differ diff --git a/assets/images/flat/ez-bar-romanian-deadlift-peak.webp b/assets/images/flat/ez-bar-romanian-deadlift-peak.webp deleted file mode 100644 index c3e0dce..0000000 Binary files a/assets/images/flat/ez-bar-romanian-deadlift-peak.webp and /dev/null differ diff --git a/assets/images/flat/ez-bar-romanian-deadlift-start.webp b/assets/images/flat/ez-bar-romanian-deadlift-start.webp deleted file mode 100644 index f27c93f..0000000 Binary files a/assets/images/flat/ez-bar-romanian-deadlift-start.webp and /dev/null differ diff --git a/assets/images/flat/pull-up-peak.webp b/assets/images/flat/pull-up-peak.webp index 0e5e335..29b67a4 100644 Binary files a/assets/images/flat/pull-up-peak.webp and b/assets/images/flat/pull-up-peak.webp differ diff --git a/assets/images/flat/pull-up-start.webp b/assets/images/flat/pull-up-start.webp index 652f0db..5529bd8 100644 Binary files a/assets/images/flat/pull-up-start.webp and b/assets/images/flat/pull-up-start.webp differ diff --git a/assets/images/flat/standing-calf-raise-peak.webp b/assets/images/flat/standing-calf-raise-peak.webp deleted file mode 100644 index 612559c..0000000 Binary files a/assets/images/flat/standing-calf-raise-peak.webp and /dev/null differ diff --git a/assets/images/flat/standing-calf-raise-start.webp b/assets/images/flat/standing-calf-raise-start.webp deleted file mode 100644 index f93502d..0000000 Binary files a/assets/images/flat/standing-calf-raise-start.webp and /dev/null differ diff --git a/assets/images/samples/README.md b/assets/images/samples/README.md new file mode 100644 index 0000000..02a4474 --- /dev/null +++ b/assets/images/samples/README.md @@ -0,0 +1,7 @@ +# premium-samples/ + +A Standard-tier exercise — bent-over-db-row — at full paid-tier quality: +classic start/peak stills plus the looping transparent-background animation, +the same clip shown on https://repdb.co. The Standard tier adds animations +like this for most exercises. Current tiers and pricing: +https://repdb.co/pricing diff --git a/assets/images/samples/bent-over-db-row-peak.webp b/assets/images/samples/bent-over-db-row-peak.webp new file mode 100644 index 0000000..ba2f1d1 Binary files /dev/null and b/assets/images/samples/bent-over-db-row-peak.webp differ diff --git a/assets/images/samples/bent-over-db-row-start.webp b/assets/images/samples/bent-over-db-row-start.webp new file mode 100644 index 0000000..74b3e15 Binary files /dev/null and b/assets/images/samples/bent-over-db-row-start.webp differ diff --git a/components/SampleGallery.tsx b/components/SampleGallery.tsx index 6b1f073..8e69f12 100644 --- a/components/SampleGallery.tsx +++ b/components/SampleGallery.tsx @@ -14,30 +14,41 @@ export function SampleGallery({ locale }: { locale: Locale }) { if (SAMPLE_SLUGS.length === 0) return null; return ( - - - Paid tier preview - - - STANDARD TIER PREVIEW - + + + + Paid tier preview + + STANDARD TIER + - - - The Standard tier adds looping animations like these for most exercises — the - clips shown on repdb.co.{' '} - Linking.openURL('https://repdb.co/pricing')} - > - See pricing → + + Looping animations for most exercises.{' '} + Linking.openURL('https://repdb.co/pricing')} + > + See pricing → + - + {SAMPLE_SLUGS.map((slug) => { @@ -74,11 +85,21 @@ function prettySlug(slug: string): string { const styles = StyleSheet.create({ section: { - marginTop: 16, - paddingTop: 16, - borderTopWidth: StyleSheet.hairlineWidth, - gap: 10, + width: '100%', + maxWidth: 620, + minHeight: 118, + padding: 12, + borderWidth: 1, + borderRadius: 16, + flexDirection: 'row', + alignItems: 'center', + gap: 12, + shadowOffset: { width: 0, height: 8 }, + shadowOpacity: 0.22, + shadowRadius: 20, + elevation: 12, }, + copy: { flex: 1, minWidth: 0, gap: 7 }, headerRow: { flexDirection: 'row', alignItems: 'center', flexWrap: 'wrap', gap: 8 }, title: { fontSize: 16, fontWeight: '700', letterSpacing: -0.2 }, badge: { @@ -89,15 +110,16 @@ const styles = StyleSheet.create({ }, badgeText: { fontSize: 9.5, fontWeight: '700', letterSpacing: 0.5 }, note: { fontSize: 12, lineHeight: 17 }, - row: { gap: 12, paddingVertical: 2, paddingRight: 4 }, - tile: { width: 132, gap: 6 }, + scroller: { flexGrow: 0, flexShrink: 1, maxWidth: 204 }, + row: { gap: 10 }, + tile: { width: 92, gap: 4 }, frame: { - width: 132, - height: 132, - borderRadius: 12, + width: 92, + height: 92, + borderRadius: 10, borderWidth: StyleSheet.hairlineWidth, overflow: 'hidden', }, - image: { width: '100%', height: '100%', padding: 10 }, - tileLabel: { fontSize: 12, fontWeight: '500', textAlign: 'center' }, + image: { width: '100%', height: '100%', padding: 7 }, + tileLabel: { fontSize: 10.5, fontWeight: '500', textAlign: 'center' }, }); diff --git a/components/StartPeakViewer.tsx b/components/StartPeakViewer.tsx index 374c303..9104c1d 100644 --- a/components/StartPeakViewer.tsx +++ b/components/StartPeakViewer.tsx @@ -110,6 +110,9 @@ export function StartPeakViewer({ slug, alt, variants, interval = 1600 }: Props) const styles = StyleSheet.create({ frame: { + width: '100%', + maxWidth: 560, + alignSelf: 'center', aspectRatio: 4 / 3, borderRadius: 12, borderWidth: StyleSheet.hairlineWidth, diff --git a/lib/images.ts b/lib/images.ts index d480e94..f16aa04 100644 --- a/lib/images.ts +++ b/lib/images.ts @@ -333,8 +333,6 @@ export const IMAGES: Record = { 'ez-bar-reverse-curl-start': require('../assets/images/flat/ez-bar-reverse-curl-start.webp'), 'ez-bar-reverse-grip-row-peak': require('../assets/images/flat/ez-bar-reverse-grip-row-peak.webp'), 'ez-bar-reverse-grip-row-start': require('../assets/images/flat/ez-bar-reverse-grip-row-start.webp'), - 'ez-bar-romanian-deadlift-peak': require('../assets/images/flat/ez-bar-romanian-deadlift-peak.webp'), - 'ez-bar-romanian-deadlift-start': require('../assets/images/flat/ez-bar-romanian-deadlift-start.webp'), 'ez-bar-shrug-peak': require('../assets/images/flat/ez-bar-shrug-peak.webp'), 'ez-bar-shrug-start': require('../assets/images/flat/ez-bar-shrug-start.webp'), 'ez-bar-spider-curl-peak': require('../assets/images/flat/ez-bar-spider-curl-peak.webp'), @@ -683,8 +681,6 @@ export const IMAGES: Record = { 'stability-ball-knee-tuck-start': require('../assets/images/flat/stability-ball-knee-tuck-start.webp'), 'stability-ball-push-up-peak': require('../assets/images/flat/stability-ball-push-up-peak.webp'), 'stability-ball-push-up-start': require('../assets/images/flat/stability-ball-push-up-start.webp'), - 'standing-calf-raise-peak': require('../assets/images/flat/standing-calf-raise-peak.webp'), - 'standing-calf-raise-start': require('../assets/images/flat/standing-calf-raise-start.webp'), 'step-ups-peak': require('../assets/images/flat/step-ups-peak.webp'), 'step-ups-start': require('../assets/images/flat/step-ups-start.webp'), 'stiff-leg-deadlift-peak': require('../assets/images/flat/stiff-leg-deadlift-peak.webp'), diff --git a/package-lock.json b/package-lock.json index c772a73..1c4ec90 100644 --- a/package-lock.json +++ b/package-lock.json @@ -19,7 +19,8 @@ "react-native": "0.81.5", "react-native-gesture-handler": "~2.28.0", "react-native-safe-area-context": "~5.6.0", - "react-native-screens": "~4.16.0" + "react-native-screens": "~4.16.0", + "react-native-web": "~0.21.0" }, "devDependencies": { "@types/react": "~19.1.0", @@ -4523,6 +4524,15 @@ "url": "https://opencollective.com/core-js" } }, + "node_modules/cross-fetch": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/cross-fetch/-/cross-fetch-3.2.0.tgz", + "integrity": "sha512-Q+xVJLoGOeIMXZmbUK4HYk+69cQH6LudR0Vu/pRm2YlU/hDV9CiS0gKUMaWY5f2NeUH9C1nV3bsTlCo0FsTV1Q==", + "license": "MIT", + "dependencies": { + "node-fetch": "^2.7.0" + } + }, "node_modules/cross-spawn": { "version": "7.0.6", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", @@ -4537,6 +4547,15 @@ "node": ">= 8" } }, + "node_modules/css-in-js-utils": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/css-in-js-utils/-/css-in-js-utils-3.1.0.tgz", + "integrity": "sha512-fJAcud6B3rRu+KHYk+Bwf+WFL2MDCJJ1XG9x137tJQ0xYxor7XziQtuGFbWNdqrvF4Tk26O3H73nfVqXt/fW1A==", + "license": "MIT", + "dependencies": { + "hyphenate-style-name": "^1.0.3" + } + }, "node_modules/csstype": { "version": "3.2.3", "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz", @@ -5752,6 +5771,36 @@ "bser": "2.1.1" } }, + "node_modules/fbjs": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/fbjs/-/fbjs-3.0.5.tgz", + "integrity": "sha512-ztsSx77JBtkuMrEypfhgc3cI0+0h+svqeie7xHbh1k/IKdcydnvadp/mUaGgjAOXQmQSxsqgaRhS3q9fy+1kxg==", + "license": "MIT", + "dependencies": { + "cross-fetch": "^3.1.5", + "fbjs-css-vars": "^1.0.0", + "loose-envify": "^1.0.0", + "object-assign": "^4.1.0", + "promise": "^7.1.1", + "setimmediate": "^1.0.5", + "ua-parser-js": "^1.0.35" + } + }, + "node_modules/fbjs-css-vars": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/fbjs-css-vars/-/fbjs-css-vars-1.0.2.tgz", + "integrity": "sha512-b2XGFAFdWZWg0phtAWLHCk836A1Xann+I+Dgd3Gk64MHKZO44FfoD1KxyvbSh0qZsIoXQGGlVztIY+oitJPpRQ==", + "license": "MIT" + }, + "node_modules/fbjs/node_modules/promise": { + "version": "7.3.1", + "resolved": "https://registry.npmjs.org/promise/-/promise-7.3.1.tgz", + "integrity": "sha512-nolQXZ/4L+bP/UGlkfaIujX9BKxGwmQ9OT4mOt5yvy8iK1h3wqTEJCijzGANTCCl9nWjY41juyAn2K3Q1hLLTg==", + "license": "MIT", + "dependencies": { + "asap": "~2.0.3" + } + }, "node_modules/fill-range": { "version": "7.1.1", "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", @@ -6057,6 +6106,12 @@ "node": ">= 14" } }, + "node_modules/hyphenate-style-name": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/hyphenate-style-name/-/hyphenate-style-name-1.1.0.tgz", + "integrity": "sha512-WDC/ui2VVRrz3jOVi+XtjqkDjiVjTtFaAGiW37k6b+ohyQ5wYDOGkvCZa8+H0nx3gyvv0+BST9xuOgIyGQ00gw==", + "license": "BSD-3-Clause" + }, "node_modules/ieee754": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", @@ -6133,6 +6188,15 @@ "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==", "license": "ISC" }, + "node_modules/inline-style-prefixer": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/inline-style-prefixer/-/inline-style-prefixer-7.0.1.tgz", + "integrity": "sha512-lhYo5qNTQp3EvSSp3sRvXMbVQTLrvGV6DycRMJ5dm2BLMiJ30wpXKdDdgX+GmJZ5uQMucwRKHamXSst3Sj/Giw==", + "license": "MIT", + "dependencies": { + "css-in-js-utils": "^3.1.0" + } + }, "node_modules/invariant": { "version": "2.2.4", "resolved": "https://registry.npmjs.org/invariant/-/invariant-2.2.4.tgz", @@ -7615,6 +7679,26 @@ "integrity": "sha512-SrQrok4CATudVzBS7coSz26QRSmlK9TzzoFbeKfcPBUFPjcQM9Rqvr/DlJkOrwI/0KcgvMub1n1g5Jt9EgRn4A==", "license": "MIT" }, + "node_modules/node-fetch": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz", + "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==", + "license": "MIT", + "dependencies": { + "whatwg-url": "^5.0.0" + }, + "engines": { + "node": "4.x || >=6.0.0" + }, + "peerDependencies": { + "encoding": "^0.1.0" + }, + "peerDependenciesMeta": { + "encoding": { + "optional": true + } + } + }, "node_modules/node-forge": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/node-forge/-/node-forge-1.4.0.tgz", @@ -8003,6 +8087,12 @@ "node": "^10 || ^12 || >=14" } }, + "node_modules/postcss-value-parser": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz", + "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==", + "license": "MIT" + }, "node_modules/pretty-bytes": { "version": "5.6.0", "resolved": "https://registry.npmjs.org/pretty-bytes/-/pretty-bytes-5.6.0.tgz", @@ -8323,6 +8413,39 @@ "react-native": "*" } }, + "node_modules/react-native-web": { + "version": "0.21.2", + "resolved": "https://registry.npmjs.org/react-native-web/-/react-native-web-0.21.2.tgz", + "integrity": "sha512-SO2t9/17zM4iEnFvlu2DA9jqNbzNhoUP+AItkoCOyFmDMOhUnBBznBDCYN92fGdfAkfQlWzPoez6+zLxFNsZEg==", + "license": "MIT", + "peer": true, + "dependencies": { + "@babel/runtime": "^7.18.6", + "@react-native/normalize-colors": "^0.74.1", + "fbjs": "^3.0.4", + "inline-style-prefixer": "^7.0.1", + "memoize-one": "^6.0.0", + "nullthrows": "^1.1.1", + "postcss-value-parser": "^4.2.0", + "styleq": "^0.1.3" + }, + "peerDependencies": { + "react": "^18.0.0 || ^19.0.0", + "react-dom": "^18.0.0 || ^19.0.0" + } + }, + "node_modules/react-native-web/node_modules/@react-native/normalize-colors": { + "version": "0.74.89", + "resolved": "https://registry.npmjs.org/@react-native/normalize-colors/-/normalize-colors-0.74.89.tgz", + "integrity": "sha512-qoMMXddVKVhZ8PA1AbUCk83trpd6N+1nF2A6k1i6LsQObyS92fELuk8kU/lQs6M7BsMHwqyLCpQJ1uFgNvIQXg==", + "license": "MIT" + }, + "node_modules/react-native-web/node_modules/memoize-one": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/memoize-one/-/memoize-one-6.0.0.tgz", + "integrity": "sha512-rkpe71W0N0c0Xz6QD0eJETuWAJGnJ9afsl1srmwPrI+yBCkge5EycXXbYRyvL29zZVUWQCY7InPRCv3GDXuZNw==", + "license": "MIT" + }, "node_modules/react-native/node_modules/@react-native/virtualized-lists": { "version": "0.81.5", "resolved": "https://registry.npmjs.org/@react-native/virtualized-lists/-/virtualized-lists-0.81.5.tgz", @@ -8869,6 +8992,12 @@ "integrity": "sha512-qepMx2JxAa5jjfzxG79yPPq+8BuFToHd1hm7kI+Z4zAq1ftQiP7HcxMhDDItrbtwVeLg/cY2JnKnrcFkmiswNA==", "license": "MIT" }, + "node_modules/setimmediate": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/setimmediate/-/setimmediate-1.0.5.tgz", + "integrity": "sha512-MATJdZp8sLqDl/68LfQmbP8zKPLQNV6BIZoIgrscFDQ+RsvK/BxeDQOgyxKKoh0y/8h3BqVFnCqQ/gd+reiIXA==", + "license": "MIT" + }, "node_modules/setprototypeof": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", @@ -9138,6 +9267,12 @@ "integrity": "sha512-0MP/Cxx5SzeeZ10p/bZI0S6MpgD+yxAhi1BOQ34jgnMXsCq3j1t6tQnZu+KdlL7dvJTLT3g9xN8tl10TqgFMcg==", "license": "MIT" }, + "node_modules/styleq": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/styleq/-/styleq-0.1.3.tgz", + "integrity": "sha512-3ZUifmCDCQanjeej1f6kyl/BeP/Vae5EYkQ9iJfUm/QwZvlgnZzyflqAsAWYURdtea8Vkvswu2GrC57h3qffcA==", + "license": "MIT" + }, "node_modules/sucrase": { "version": "3.35.1", "resolved": "https://registry.npmjs.org/sucrase/-/sucrase-3.35.1.tgz", @@ -9455,6 +9590,12 @@ "node": ">=0.6" } }, + "node_modules/tr46": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", + "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==", + "license": "MIT" + }, "node_modules/ts-interface-checker": { "version": "0.1.13", "resolved": "https://registry.npmjs.org/ts-interface-checker/-/ts-interface-checker-0.1.13.tgz", @@ -9500,6 +9641,32 @@ "node": ">=14.17" } }, + "node_modules/ua-parser-js": { + "version": "1.0.41", + "resolved": "https://registry.npmjs.org/ua-parser-js/-/ua-parser-js-1.0.41.tgz", + "integrity": "sha512-LbBDqdIC5s8iROCUjMbW1f5dJQTEFB1+KO9ogbvlb3nm9n4YHa5p4KTvFPWvh2Hs8gZMBuiB1/8+pdfe/tDPug==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/ua-parser-js" + }, + { + "type": "paypal", + "url": "https://paypal.me/faisalman" + }, + { + "type": "github", + "url": "https://github.com/sponsors/faisalman" + } + ], + "license": "MIT", + "bin": { + "ua-parser-js": "script/cli.js" + }, + "engines": { + "node": "*" + } + }, "node_modules/undici": { "version": "6.27.0", "resolved": "https://registry.npmjs.org/undici/-/undici-6.27.0.tgz", @@ -9941,6 +10108,16 @@ "integrity": "sha512-EqhiFU6daOA8kpjOWTL0olhVOF3i7OrFzSYiGsEMB8GcXS+RrzauAERX65xMeNWVqxA6HXH2m69Z9LaKKdisfg==", "license": "MIT" }, + "node_modules/whatwg-url": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", + "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", + "license": "MIT", + "dependencies": { + "tr46": "~0.0.3", + "webidl-conversions": "^3.0.0" + } + }, "node_modules/whatwg-url-without-unicode": { "version": "8.0.0-3", "resolved": "https://registry.npmjs.org/whatwg-url-without-unicode/-/whatwg-url-without-unicode-8.0.0-3.tgz", @@ -9955,6 +10132,12 @@ "node": ">=10" } }, + "node_modules/whatwg-url/node_modules/webidl-conversions": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", + "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==", + "license": "BSD-2-Clause" + }, "node_modules/which": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", diff --git a/package.json b/package.json index 5ec802d..ca92600 100644 --- a/package.json +++ b/package.json @@ -22,7 +22,8 @@ "react-native": "0.81.5", "react-native-gesture-handler": "~2.28.0", "react-native-safe-area-context": "~5.6.0", - "react-native-screens": "~4.16.0" + "react-native-screens": "~4.16.0", + "react-native-web": "~0.21.0" }, "devDependencies": { "@types/react": "~19.1.0", diff --git a/scripts/gen-images.cjs b/scripts/gen-images.cjs index 4336c0e..39483e0 100644 --- a/scripts/gen-images.cjs +++ b/scripts/gen-images.cjs @@ -20,13 +20,15 @@ const webps = (dir) => // Build a `'key': require('/'),` block. `key` is the filename // without the .webp extension (slug-variant for flat stills, bare slug for the // sample animations, icon basename for muscles/equipment). -const block = (dir) => - webps(dir) +const block = (dir, files = webps(dir)) => + files .map((f) => ` '${f.replace(/\.webp$/, '')}': require('../${dir}/${f}'),`) .join('\n'); const flat = webps('assets/images/flat'); -const samples = webps('assets/images/samples'); +const samples = webps('assets/images/samples').filter( + (file) => !/-(start|peak|main)\.webp$/.test(file), +); const muscles = webps('assets/images/muscles'); const equipment = webps('assets/images/equipment'); @@ -49,7 +51,7 @@ ${block('assets/images/flat')} // exact clips shown on repdb.co. One animation per slug; the paid-tier preview // gallery is DERIVED from these keys (see SAMPLE_SLUGS). export const SAMPLES: Record = { -${block('assets/images/samples')} +${block('assets/images/samples', samples)} }; // icon basename (e.g. 'anterior-deltoid') -> module ID.