Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ npm run start

```
src/data/exercises.json # the free-tier bundle (400 exercises), imported as a module
public/images/flat/*.webp # 745 flat webp (start/peak pairs + single-pose "main")
public/images/flat/*.webp # 741 flat webp (start/peak pairs + single-pose "main")
public/images/muscles/*.webp # 27 muscle icons
public/images/equipment/*.webp # 46 equipment icons
public/images/samples/*.webp # 1 paid-tier looping animation (Standard-tier preview)
Expand Down
Binary file modified public/images/flat/ab-wheel-rollout-peak.webp
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified public/images/flat/ab-wheel-rollout-start.webp
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified public/images/flat/bench-leg-pull-in-peak.webp
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified public/images/flat/bench-leg-pull-in-start.webp
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file not shown.
Binary file not shown.
Binary file modified public/images/flat/pull-up-peak.webp
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified public/images/flat/pull-up-start.webp
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file removed public/images/flat/standing-calf-raise-peak.webp
Binary file not shown.
Binary file removed public/images/flat/standing-calf-raise-start.webp
Binary file not shown.
7 changes: 7 additions & 0 deletions public/images/samples/README.md
Original file line number Diff line number Diff line change
@@ -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
Binary file added public/images/samples/bent-over-db-row-peak.webp
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added public/images/samples/bent-over-db-row-start.webp
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
4 changes: 2 additions & 2 deletions src/app/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,8 @@ export default async function Home({ searchParams }: PageProps) {
<LocaleSwitcher current={locale} />
</div>

<SamplePreview />

<FilterBar
bodyParts={BODY_PARTS}
equipments={EQUIPMENTS}
Expand All @@ -70,8 +72,6 @@ export default async function Home({ searchParams }: PageProps) {
))}
</ul>
)}

<SamplePreview />
</div>
);
}
5 changes: 3 additions & 2 deletions src/components/ExerciseCard.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import Link from 'next/link';
import Image from 'next/image';
import {
type Exercise,
type Locale,
Expand All @@ -18,7 +17,9 @@ export function ExerciseCard({ exercise, locale }: { exercise: Exercise; locale:
>
<div className="aspect-[4/3] bg-sky-50 dark:bg-[color-mix(in_srgb,var(--surface-2)_92%,#dbeafe_8%)] flex items-center justify-center overflow-hidden">
{thumb ? (
<Image
/* The bundle timestamp in `thumb` prevents stale vendored images after a refresh. */
// eslint-disable-next-line @next/next/no-img-element
<img

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Preserve lazy loading for catalog thumbnails

When the unfiltered catalog renders all 400 exercises, replacing next/image with a plain <img> without loading="lazy" removes the previous component's default lazy loading. The browser can therefore request all 394 unique thumbnail assets—about 6.2 MiB—and decode hundreds of offscreen images on every initial visit, substantially increasing network usage and memory pressure. Add native lazy loading (and preferably asynchronous decoding) or retain the optimized image component with supported cache versioning.

Useful? React with 👍 / 👎.

src={thumb}
alt={name}
width={344}
Expand Down
4 changes: 2 additions & 2 deletions src/components/ExerciseFrames.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import type { ImageVariant } from '@/lib/bundle';
import { assetUrl, type ImageVariant } from '@/lib/bundle';

const VARIANT_LABELS: Record<ImageVariant, string> = {
start: 'Start',
Expand Down Expand Up @@ -33,7 +33,7 @@ export function ExerciseFrames({
<Frame
key={v}
label={VARIANT_LABELS[v] ?? v}
src={`/images/flat/${slug}-${v}.webp`}
src={assetUrl(`/images/flat/${slug}-${v}.webp`)}
alt={`${name} — ${VARIANT_LABELS[v] ?? v}`}
/>
))}
Expand Down
30 changes: 16 additions & 14 deletions src/data/exercises.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"schema_version": 3,
"generated_at": "2026-07-16T19:41:37Z",
"generated_at": "2026-07-27T13:53:50Z",
"locales": [
"de",
"en",
Expand Down Expand Up @@ -12294,16 +12294,16 @@
"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.",
"Push through your heel to return to the starting position, extending your standing leg fully.",
"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."
],
Expand All @@ -12318,34 +12318,34 @@
"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.",
"Drücke dich durch die Ferse zurück in die Ausgangsposition und strecke dein Standbein vollständig.",
"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.",
"Empuja a través de tu talón para volver a la posición inicial, extendiendo completamente tu pierna de apoyo.",
"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": [
Expand Down Expand Up @@ -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": [
Expand Down Expand Up @@ -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": [
Expand Down
13 changes: 10 additions & 3 deletions src/lib/bundle.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ export interface TaxonomyEntry {

export interface Bundle {
schema_version: number;
generated_at?: string;
locales: Locale[];
exercises: Exercise[];
muscles?: Record<string, TaxonomyEntry>;
Expand All @@ -46,6 +47,12 @@ export const EXERCISES = BUNDLE.exercises;
export const LOCALES: Locale[] = BUNDLE.locales;
export const MUSCLES = BUNDLE.muscles ?? {};
export const EQUIPMENT = BUNDLE.equipment ?? {};
export const ASSET_VERSION = BUNDLE.generated_at ?? 'dev';

/** Bust browser/Next caches when a newly generated bundle replaces vendored assets. */
export function assetUrl(path: string): string {
return `${path}?v=${encodeURIComponent(ASSET_VERSION)}`;
}

/** Base slug for image files — aliased exercises borrow another slug's renders. */
export function imageBase(ex: Exercise): string {
Expand All @@ -62,7 +69,7 @@ export function exerciseThumb(ex: Exercise): string | null {
const variants = flatVariants(ex);
const pick =
variants.find((v) => v === 'peak') ?? variants.find((v) => v === 'main') ?? variants[0];
return pick ? `/images/flat/${imageBase(ex)}-${pick}.webp` : null;
return pick ? assetUrl(`/images/flat/${imageBase(ex)}-${pick}.webp`) : null;
}

function localized(entry: TaxonomyEntry | undefined, locale: Locale, fallback: string): string {
Expand All @@ -78,7 +85,7 @@ export function muscleLabel(key: string, locale: Locale): string {

export function muscleIcon(key: string): string | null {
const img = MUSCLES[key]?.image;
return img ? `/images/muscles/${img}` : null;
return img ? assetUrl(`/images/muscles/${img}`) : null;
}

export function equipmentLabel(key: string, locale: Locale): string {
Expand All @@ -87,7 +94,7 @@ export function equipmentLabel(key: string, locale: Locale): string {

export function equipmentIcon(key: string): string | null {
const img = EQUIPMENT[key]?.image;
return img ? `/images/equipment/${img}` : null;
return img ? assetUrl(`/images/equipment/${img}`) : null;
}

export function exerciseName(ex: Exercise, locale: Locale): string {
Expand Down
9 changes: 7 additions & 2 deletions src/lib/samples.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import fs from 'node:fs';
import path from 'node:path';
import { assetUrl } from '@/lib/bundle';

/**
* Paid-tier preview animations.
Expand All @@ -22,11 +23,15 @@ function readSampleAnimations(): SampleAnimation[] {
try {
return fs
.readdirSync(SAMPLES_DIR)
.filter((file) => file.endsWith('.webp'))
.filter(
(file) =>
file.endsWith('.webp') &&
!/-(start|peak|main)\.webp$/.test(file),
)
.sort()
.map((file) => ({
slug: file.replace(/\.webp$/, ''),
src: `/images/samples/${file}`,
src: assetUrl(`/images/samples/${file}`),
}));
} catch {
return [];
Expand Down
Loading