diff --git a/.pm-checkpoint.json b/.pm-checkpoint.json index 80fce39..d1cbedd 100644 --- a/.pm-checkpoint.json +++ b/.pm-checkpoint.json @@ -1,26 +1,40 @@ { - "lastRun": "2026-03-02T19:37:00Z", - "status": "unblocked", - "unblockedReason": "OpenCode API configured as fallback for Gemini quota", - "currentPhase": "05", - "currentTask": "05-03", - "result": "Fallback system implemented: Gemini (primary) → OpenCode (fallback)", - "nextTask": "05-03: Frontend integration for research display (can now proceed with OpenCode fallback)", - - "apiConfiguration": { - "primary": { - "provider": "Gemini", - "status": "quota-limited", - "notes": "Free tier has daily limits" + "lastRun": "2026-03-03T02:47:00+01:00", + "status": "completed", + "result": "Phase 06-01 COMPLETED: Backend exercise recommendation endpoint + frontend components implemented. API contract finalized with fallback chain. Ready for integration testing.", + "phase": "06-01", + "phaseStarted": "2026-03-03T02:47:00+01:00", + "phaseCompleted": "2026-03-03T02:47:00+01:00", + "work": { + "backend": { + "status": "completed", + "created": "backend/src/routes/exerciseRecommendations.js", + "modified": "backend/src/index.js", + "features": [ + "POST /api/exercises/recommend endpoint", + "Input validation (fitness_level, goals, available_time, equipment, focus_muscles, limit)", + "AI fallback chain: Ollama → Gemini → OpenRouter", + "Heuristic fallback when all providers fail", + "Coach persona integration", + "JSON parsing + error handling", + "Logging + metrics" + ] }, - "fallback": { - "provider": "OpenCode", - "baseUrl": "https://api.opencode.com/v1", - "model": "gpt-4", - "status": "configured" - }, - "implementation": "backend/src/utils/gemini-fallback.js" + "frontend": { + "status": "completed", + "created": [ + "frontend/src/components/exercises/ExerciseCard.jsx", + "frontend/src/components/exercises/RecommendationPanel.jsx", + "frontend/src/components/exercises/ProgressionTracker.jsx", + "frontend/src/components/exercises/exerciseRecommendations.css", + "frontend/src/types/exerciseRecommendations.ts" + ] + } }, - - "action": "READY TO RESUME: PM can continue with 05-03 using fallback" + "verification": { + "gitStatus": "New files: exerciseRecommendations.js, exercise*.jsx, exerciseRecommendations.css, exerciseRecommendations.ts; Modified: index.js", + "lastCommit": "f580fa8 feat(05-03): Implement API fallback handling for research display", + "uncommittedChanges": "Ready for review and commit" + }, + "nextCheck": "PHASE 06-02: Integration testing - Verify API responses, test fallback chain, validate component rendering. Target: E2E flow validation." } diff --git a/backend/package-lock.json b/backend/package-lock.json index 8a89240..df47662 100644 --- a/backend/package-lock.json +++ b/backend/package-lock.json @@ -15,7 +15,31 @@ "pg": "^8.11.3" }, "devDependencies": { - "nodemon": "^3.0.2" + "nodemon": "^3.0.2", + "supertest": "^6.3.3" + } + }, + "node_modules/@noble/hashes": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.8.0.tgz", + "integrity": "sha512-jCs9ldd7NwzpgXDIf6P3+NrHh9/sD6CQdxHyjQI+h/6rDNo88ypBxxz45UDuZHz9r3tNz7N/VInSVoVdtXEI4A==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^14.21.3 || >=16" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@paralleldrive/cuid2": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/@paralleldrive/cuid2/-/cuid2-2.3.1.tgz", + "integrity": "sha512-XO7cAxhnTZl0Yggq6jOgjiOHhbgcO4NqFqwSmQpjK3b6TEE6Uj/jfSk6wzYyemh3+I0sHirKSetjQwn5cZktFw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@noble/hashes": "^1.1.5" } }, "node_modules/accepts": { @@ -51,6 +75,20 @@ "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==", "license": "MIT" }, + "node_modules/asap": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/asap/-/asap-2.0.6.tgz", + "integrity": "sha512-BSHWgDSAiKs50o2Re8ppvp3seVHXSRM44cdSsT9FfNEUUZLOGWVCsiWaRPWM1Znn+mqZ1OfVZ3z3DWEzSp7hRA==", + "dev": true, + "license": "MIT" + }, + "node_modules/asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", + "dev": true, + "license": "MIT" + }, "node_modules/balanced-match": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", @@ -194,6 +232,29 @@ "fsevents": "~2.3.2" } }, + "node_modules/combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "dev": true, + "license": "MIT", + "dependencies": { + "delayed-stream": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/component-emitter": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.3.1.tgz", + "integrity": "sha512-T0+barUSQRTUQASh8bx02dl+DhF54GtIDY13Y3m9oWTklKbb3Wv974meRpeZ3lp1JpLVECWWNHC4vaG2XHXouQ==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/concat-map": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", @@ -237,6 +298,13 @@ "integrity": "sha512-NXdYc3dLr47pBkpUCHtKSwIOQXLVn8dZEuywboCOJY/osA0wFSLlSawr3KN8qXJEyX66FcONTH8EIlVuK0yyFA==", "license": "MIT" }, + "node_modules/cookiejar": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/cookiejar/-/cookiejar-2.1.4.tgz", + "integrity": "sha512-LDx6oHrK+PhzLKJU9j5S7/Y3jM/mUHvD/DeI1WQmJn652iPC5Y4TBzC9l+5OMOXlyTTA+SmVUPm0HQUwpD5Jqw==", + "dev": true, + "license": "MIT" + }, "node_modules/cors": { "version": "2.8.6", "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.6.tgz", @@ -263,6 +331,16 @@ "ms": "2.0.0" } }, + "node_modules/delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.4.0" + } + }, "node_modules/depd": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", @@ -282,6 +360,17 @@ "npm": "1.2.8000 || >= 1.4.16" } }, + "node_modules/dezalgo": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/dezalgo/-/dezalgo-1.0.4.tgz", + "integrity": "sha512-rXSP0bf+5n0Qonsb+SVVfNfIsimO4HEtmnIpPHY8Q1UCzKlQrDMfdobr8nJOOsRgWCyMRqeSBQzmWUMq7zvVig==", + "dev": true, + "license": "ISC", + "dependencies": { + "asap": "^2.0.0", + "wrappy": "1" + } + }, "node_modules/dunder-proto": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", @@ -350,6 +439,22 @@ "node": ">= 0.4" } }, + "node_modules/es-set-tostringtag": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz", + "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/escape-html": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", @@ -411,6 +516,13 @@ "url": "https://opencollective.com/express" } }, + "node_modules/fast-safe-stringify": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/fast-safe-stringify/-/fast-safe-stringify-2.1.1.tgz", + "integrity": "sha512-W+KJc2dmILlPplD/H4K9l9LcAHAfPtP6BY84uVLXQ6Evcz9Lcg33Y2z1IVblT6xdY54PXYVHEv+0Wpq8Io6zkA==", + "dev": true, + "license": "MIT" + }, "node_modules/fill-range": { "version": "7.1.1", "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", @@ -442,6 +554,39 @@ "node": ">= 0.8" } }, + "node_modules/form-data": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.5.tgz", + "integrity": "sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w==", + "dev": true, + "license": "MIT", + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "es-set-tostringtag": "^2.1.0", + "hasown": "^2.0.2", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/formidable": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/formidable/-/formidable-2.1.5.tgz", + "integrity": "sha512-Oz5Hwvwak/DCaXVVUtPn4oLMLLy1CdclLKO1LFgU7XzDpVMUU5UjlSLpGMocyQNNk8F6IJW9M/YdooSn2MRI+Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@paralleldrive/cuid2": "^2.2.2", + "dezalgo": "^1.0.4", + "once": "^1.4.0", + "qs": "^6.11.0" + }, + "funding": { + "url": "https://ko-fi.com/tunnckoCore/commissions" + } + }, "node_modules/forwarded": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", @@ -568,6 +713,22 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/has-tostringtag": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", + "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-symbols": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/hasown": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", @@ -965,6 +1126,16 @@ "node": ">= 0.8" } }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "dev": true, + "license": "ISC", + "dependencies": { + "wrappy": "1" + } + }, "node_modules/parseurl": { "version": "1.3.3", "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", @@ -1385,6 +1556,82 @@ "node": ">= 0.8" } }, + "node_modules/superagent": { + "version": "8.1.2", + "resolved": "https://registry.npmjs.org/superagent/-/superagent-8.1.2.tgz", + "integrity": "sha512-6WTxW1EB6yCxV5VFOIPQruWGHqc3yI7hEmZK6h+pyk69Lk/Ut7rLUY6W/ONF2MjBuGjvmMiIpsrVJ2vjrHlslA==", + "deprecated": "Please upgrade to superagent v10.2.2+, see release notes at https://github.com/forwardemail/superagent/releases/tag/v10.2.2 - maintenance is supported by Forward Email @ https://forwardemail.net", + "dev": true, + "license": "MIT", + "dependencies": { + "component-emitter": "^1.3.0", + "cookiejar": "^2.1.4", + "debug": "^4.3.4", + "fast-safe-stringify": "^2.1.1", + "form-data": "^4.0.0", + "formidable": "^2.1.2", + "methods": "^1.1.2", + "mime": "2.6.0", + "qs": "^6.11.0", + "semver": "^7.3.8" + }, + "engines": { + "node": ">=6.4.0 <13 || >=14" + } + }, + "node_modules/superagent/node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/superagent/node_modules/mime": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-2.6.0.tgz", + "integrity": "sha512-USPkMeET31rOMiarsBNIHZKLGgvKc/LrjofAnBlOttf5ajRvqiRA8QsenbcooctK6d6Ts6aqZXBA+XbkKthiQg==", + "dev": true, + "license": "MIT", + "bin": { + "mime": "cli.js" + }, + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/superagent/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true, + "license": "MIT" + }, + "node_modules/supertest": { + "version": "6.3.4", + "resolved": "https://registry.npmjs.org/supertest/-/supertest-6.3.4.tgz", + "integrity": "sha512-erY3HFDG0dPnhw4U+udPfrzXa4xhSG+n4rxfRuZWCUvjFWwKl+OxWf/7zk50s84/fAAs7vf5QAb9uRa0cCykxw==", + "deprecated": "Please upgrade to supertest v7.1.3+, see release notes at https://github.com/forwardemail/supertest/releases/tag/v7.1.3 - maintenance is supported by Forward Email @ https://forwardemail.net", + "dev": true, + "license": "MIT", + "dependencies": { + "methods": "^1.1.2", + "superagent": "^8.1.2" + }, + "engines": { + "node": ">=6.4.0" + } + }, "node_modules/supports-color": { "version": "5.5.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", @@ -1477,6 +1724,13 @@ "node": ">= 0.8" } }, + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", + "dev": true, + "license": "ISC" + }, "node_modules/xtend": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", diff --git a/backend/src/index.js b/backend/src/index.js index f8ac86d..bfffad4 100644 --- a/backend/src/index.js +++ b/backend/src/index.js @@ -4,6 +4,7 @@ const { Pool } = require('pg'); const bcrypt = require('bcryptjs'); const jwt = require('jsonwebtoken'); const { createExerciseResearchRouter } = require('./routes/exerciseResearch'); +const { createExerciseRecommendationRouter } = require('./routes/exerciseRecommendations'); const { searchExerciseResearch } = require('./services/exaSearch'); const app = express(); @@ -21,6 +22,7 @@ const pool = new Pool({ app.use(cors()); app.use(express.json()); app.use('/api/exercises', createExerciseResearchRouter({ pool, exaSearch: searchExerciseResearch })); +app.use('/api/exercises', createExerciseRecommendationRouter()); const authMiddleware = (req, res, next) => { const token = req.headers.authorization?.split(' ')[1]; diff --git a/backend/src/routes/exerciseRecommendations.js b/backend/src/routes/exerciseRecommendations.js new file mode 100644 index 0000000..91035ab --- /dev/null +++ b/backend/src/routes/exerciseRecommendations.js @@ -0,0 +1,407 @@ +const express = require('express'); + +const exercisesData = require('../../../agents/coach/exercises.json'); + +const OLLAMA_URL = process.env.OLLAMA_URL || 'http://localhost:11434'; +const OLLAMA_MODEL = process.env.OLLAMA_MODEL || 'deepseek-v3.2:cloud'; +const GEMINI_API_KEY = process.env.GOOGLE_API_KEY; +const OPENROUTER_API_KEY = process.env.OPENROUTER_API_KEY; +const OPENROUTER_BASE_URL = process.env.OPENROUTER_BASE_URL || 'https://openrouter.ai/api/v1'; + +const VALID_FITNESS_LEVELS = ['beginner', 'intermediate', 'advanced']; +const VALID_GOALS = ['strength', 'hypertrophy', 'fat_loss', 'endurance', 'mobility', 'general_fitness']; + +const difficultyRank = { + beginner: 1, + intermediate: 2, + advanced: 3 +}; + +const normalizeGoals = (goals) => { + if (!goals) return []; + if (Array.isArray(goals)) { + return goals.map((goal) => String(goal).trim()).filter(Boolean); + } + if (typeof goals === 'string') { + return goals.split(',').map((goal) => goal.trim()).filter(Boolean); + } + return []; +}; + +const normalizeList = (value) => { + if (!value) return []; + if (Array.isArray(value)) { + return value.map((item) => String(item).trim()).filter(Boolean); + } + if (typeof value === 'string') { + return value.split(',').map((item) => item.trim()).filter(Boolean); + } + return []; +}; + +const validatePayload = (payload) => { + const errors = []; + const fitnessLevel = payload?.fitness_level; + const goals = normalizeGoals(payload?.goals); + const availableTime = Number(payload?.available_time); + + if (!fitnessLevel || typeof fitnessLevel !== 'string' || !VALID_FITNESS_LEVELS.includes(fitnessLevel)) { + errors.push('fitness_level is required and must be beginner, intermediate, or advanced'); + } + if (!goals.length) { + errors.push('goals is required and must be a non-empty array or comma-separated string'); + } else { + const invalidGoals = goals.filter((goal) => !VALID_GOALS.includes(goal)); + if (invalidGoals.length) { + errors.push(`goals contains invalid values: ${invalidGoals.join(', ')}`); + } + } + if (!Number.isFinite(availableTime) || availableTime <= 0) { + errors.push('available_time is required and must be a positive number (minutes)'); + } + + return { errors, goals, availableTime }; +}; + +const buildPrompt = ({ fitnessLevel, goals, availableTime, equipment, focusMuscles, limit, exercises }) => { + const coachPersona = `Du är Coach, en erfaren styrke- och konditionscoach (15+ års erfarenhet).\n` + + `- Direkt och tydlig, inga fluff.\n- Anpassar språk efter nivå.\n- Prioritera säkerhet.\n- Ge alltid alternativ.\n` + + `Svara på svenska.`; + + const requestContext = { + fitness_level: fitnessLevel, + goals, + available_time_minutes: availableTime, + equipment, + focus_muscles: focusMuscles, + limit + }; + + const exerciseCatalog = exercises.map((exercise) => ({ + id: exercise.id, + name: exercise.name, + name_en: exercise.name_en, + category: exercise.category, + primary_muscles: exercise.primary_muscles, + secondary_muscles: exercise.secondary_muscles, + equipment: exercise.equipment, + difficulty: exercise.difficulty, + alternatives: exercise.alternatives + })); + + return `${coachPersona}\n\n` + + `Uppgift: Rekommendera övningar för användaren baserat på kontexten nedan.\n` + + `- Välj endast från katalogen.\n- Anpassa set/reps/rest till mål och nivå.\n- Motivera kort varför varje övning passar.\n- Svara med exakt JSON enligt schema.\n\n` + + `KONTEKST:\n${JSON.stringify(requestContext)}\n\n` + + `KATALOG:\n${JSON.stringify(exerciseCatalog)}\n\n` + + `SCHEMA:\n` + + `{"recommendations":[{"id":"","sets":0,"reps":"","rest_seconds":0,"reason":"","alternatives":[]}],"notes":""}`; +}; + +const extractJsonPayload = (text) => { + if (!text || typeof text !== 'string') { + throw new Error('No response text to parse'); + } + + const start = text.indexOf('{'); + const end = text.lastIndexOf('}'); + if (start === -1 || end === -1 || end <= start) { + throw new Error('No JSON object found in response'); + } + + const jsonString = text.slice(start, end + 1); + return JSON.parse(jsonString); +}; + +const parseRecommendations = (payload, exerciseMap) => { + if (!payload || !Array.isArray(payload.recommendations)) { + throw new Error('Invalid recommendations payload'); + } + + const recommendations = payload.recommendations + .map((rec) => { + const exercise = exerciseMap.get(rec.id); + if (!exercise) return null; + return { + id: exercise.id, + name: exercise.name, + name_en: exercise.name_en, + sets: Number(rec.sets) || 3, + reps: rec.reps || '8-12', + rest_seconds: Number(rec.rest_seconds) || 90, + reason: rec.reason || 'Bra match för ditt mål och din nivå.', + alternatives: Array.isArray(rec.alternatives) && rec.alternatives.length + ? rec.alternatives + : exercise.alternatives || [] + }; + }) + .filter(Boolean); + + if (!recommendations.length) { + throw new Error('No valid recommendations after parsing'); + } + + return { + recommendations, + notes: payload.notes || '' + }; +}; + +const buildHeuristicRecommendations = ({ fitnessLevel, goals, availableTime, equipment, focusMuscles, limit }) => { + const maxDifficulty = difficultyRank[fitnessLevel] || 2; + const equipmentSet = new Set((equipment || []).map((item) => item.toLowerCase())); + const focusSet = new Set((focusMuscles || []).map((item) => item.toLowerCase())); + + const goalWeights = { + strength: { compound: 3, isolation: 1 }, + hypertrophy: { compound: 2, isolation: 2 }, + fat_loss: { compound: 2, isolation: 1 }, + endurance: { compound: 1, isolation: 2 }, + mobility: { compound: 1, isolation: 2 }, + general_fitness: { compound: 2, isolation: 1 } + }; + + const filteredExercises = exercisesData.exercises.filter((exercise) => { + const diffOk = (difficultyRank[exercise.difficulty] || 2) <= maxDifficulty; + if (!diffOk) return false; + + if (equipmentSet.size === 0) return true; + + if (!exercise.equipment || exercise.equipment.length === 0) return true; + return exercise.equipment.some((item) => equipmentSet.has(item.toLowerCase())); + }); + + const exercises = filteredExercises.length ? filteredExercises : exercisesData.exercises; + + const scored = exercises.map((exercise) => { + let score = 0; + goals.forEach((goal) => { + const weights = goalWeights[goal] || goalWeights.general_fitness; + score += weights[exercise.category] || 0; + }); + + if (focusSet.size) { + if (exercise.primary_muscles?.some((muscle) => focusSet.has(muscle.toLowerCase()))) { + score += 3; + } else if (exercise.secondary_muscles?.some((muscle) => focusSet.has(muscle.toLowerCase()))) { + score += 1; + } + } + + if (!exercise.equipment || exercise.equipment.length === 0) { + score += 1; + } + + return { exercise, score }; + }); + + scored.sort((a, b) => b.score - a.score); + + const timeBasedLimit = availableTime <= 20 + ? 3 + : availableTime <= 35 + ? 4 + : availableTime <= 50 + ? 6 + : 8; + + const finalLimit = Math.min(limit || timeBasedLimit, 10); + const selected = scored.slice(0, finalLimit); + + return selected.map(({ exercise }) => ({ + id: exercise.id, + name: exercise.name, + name_en: exercise.name_en, + sets: exercise.category === 'compound' ? 4 : 3, + reps: goals.includes('strength') ? '4-6' : '8-12', + rest_seconds: exercise.category === 'compound' ? 120 : 60, + reason: `Passar ${goals.join(', ')} med fokus på ${exercise.primary_muscles.join(', ')}.`, + alternatives: exercise.alternatives || [] + })); +}; + +const extractProviderText = (provider, data) => { + if (provider === 'ollama') { + return data?.response || ''; + } + if (provider === 'gemini') { + return data?.candidates?.[0]?.content?.parts?.[0]?.text || ''; + } + if (provider === 'openrouter') { + return data?.choices?.[0]?.message?.content || ''; + } + return ''; +}; + +const generateRecommendationsWithFallback = async ({ prompt }) => { + if (typeof fetch !== 'function') { + throw new Error('Fetch API not available in this runtime'); + } + + // Tier 1: Ollama + try { + console.log(`📍 [Recommend] Tier 1: Ollama (${OLLAMA_MODEL})`); + const response = await fetch(`${OLLAMA_URL}/api/generate`, { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ + model: OLLAMA_MODEL, + prompt, + stream: false, + temperature: 0.6 + }), + timeout: 30000 + }); + + if (response.ok) { + const data = await response.json(); + console.log('✅ [Recommend] Ollama success'); + return { provider: 'ollama', data }; + } + + console.warn(`⚠️ [Recommend] Ollama error: ${response.status}`); + } catch (err) { + console.warn(`⚠️ [Recommend] Ollama failed: ${err.message}`); + } + + // Tier 2: Gemini + if (GEMINI_API_KEY) { + try { + console.log('📍 [Recommend] Tier 2: Gemini'); + const response = await fetch( + `https://generativelanguage.googleapis.com/v1/models/gemini-pro:generateContent?key=${GEMINI_API_KEY}`, + { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ + contents: [{ parts: [{ text: prompt }] }], + generationConfig: { temperature: 0.6 } + }) + } + ); + + if (response.ok) { + const data = await response.json(); + console.log('✅ [Recommend] Gemini success'); + return { provider: 'gemini', data }; + } + + if (response.status === 429 || response.status === 403) { + console.warn('⚠️ [Recommend] Gemini quota exceeded'); + } else { + console.warn(`⚠️ [Recommend] Gemini error: ${response.status}`); + } + } catch (err) { + console.warn(`⚠️ [Recommend] Gemini failed: ${err.message}`); + } + } + + // Tier 3: OpenRouter + if (OPENROUTER_API_KEY) { + try { + console.log('📍 [Recommend] Tier 3: OpenRouter'); + const response = await fetch(`${OPENROUTER_BASE_URL}/chat/completions`, { + method: 'POST', + headers: { + 'Authorization': `Bearer ${OPENROUTER_API_KEY}`, + 'Content-Type': 'application/json', + 'HTTP-Referer': 'https://gravl.app' + }, + body: JSON.stringify({ + model: 'openai/gpt-4', + messages: [{ role: 'user', content: prompt }], + temperature: 0.6, + max_tokens: 1200 + }) + }); + + if (response.ok) { + const data = await response.json(); + console.log('✅ [Recommend] OpenRouter success'); + return { provider: 'openrouter', data }; + } + + console.warn(`⚠️ [Recommend] OpenRouter error: ${response.status}`); + } catch (err) { + console.warn(`⚠️ [Recommend] OpenRouter failed: ${err.message}`); + } + } + + throw new Error('All recommendation providers failed (Ollama → Gemini → OpenRouter)'); +}; + +const createExerciseRecommendationRouter = () => { + const router = express.Router(); + const exerciseMap = new Map(exercisesData.exercises.map((exercise) => [exercise.id, exercise])); + + /** + * POST /api/exercises/recommend + * Request body: + * { + * "fitness_level": "beginner" | "intermediate" | "advanced", + * "goals": ["strength" | "hypertrophy" | "fat_loss" | "endurance" | "mobility" | "general_fitness"], + * "available_time": 30, + * "equipment": ["barbell", "dumbbells"], + * "focus_muscles": ["chest", "back"], + * "limit": 6 + * } + */ + router.post('/recommend', async (req, res) => { + const { errors, goals, availableTime } = validatePayload(req.body); + if (errors.length) { + return res.status(400).json({ error: 'Validation failed', details: errors }); + } + + const fitnessLevel = req.body.fitness_level; + const equipment = normalizeList(req.body.equipment); + const focusMuscles = normalizeList(req.body.focus_muscles); + const limit = Number.isFinite(Number(req.body.limit)) ? Math.min(Number(req.body.limit), 10) : null; + + const prompt = buildPrompt({ + fitnessLevel, + goals, + availableTime, + equipment, + focusMuscles, + limit, + exercises: exercisesData.exercises + }); + + try { + const { provider, data } = await generateRecommendationsWithFallback({ prompt }); + const text = extractProviderText(provider, data); + const parsedPayload = extractJsonPayload(text); + const aiRecommendations = parseRecommendations(parsedPayload, exerciseMap); + + return res.json({ + recommendations: aiRecommendations.recommendations, + notes: aiRecommendations.notes, + provider, + status: 'success' + }); + } catch (err) { + console.warn(`⚠️ [Recommend] Falling back to heuristic recommendations: ${err.message}`); + const fallbackRecommendations = buildHeuristicRecommendations({ + fitnessLevel, + goals, + availableTime, + equipment, + focusMuscles, + limit + }); + + return res.json({ + recommendations: fallbackRecommendations, + notes: 'Fallback recommendations generated without AI provider.', + provider: 'fallback', + status: 'degraded' + }); + } + }); + + return router; +}; + +module.exports = { + createExerciseRecommendationRouter +}; diff --git a/frontend/dist/assets/index-hhKetRGz.js b/frontend/dist/assets/index-hhKetRGz.js deleted file mode 100644 index 64d4d5d..0000000 --- a/frontend/dist/assets/index-hhKetRGz.js +++ /dev/null @@ -1,67 +0,0 @@ -function ud(e,t){for(var n=0;nr[l]})}}}return Object.freeze(Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}))}(function(){const t=document.createElement("link").relList;if(t&&t.supports&&t.supports("modulepreload"))return;for(const l of document.querySelectorAll('link[rel="modulepreload"]'))r(l);new MutationObserver(l=>{for(const s of l)if(s.type==="childList")for(const o of s.addedNodes)o.tagName==="LINK"&&o.rel==="modulepreload"&&r(o)}).observe(document,{childList:!0,subtree:!0});function n(l){const s={};return l.integrity&&(s.integrity=l.integrity),l.referrerPolicy&&(s.referrerPolicy=l.referrerPolicy),l.crossOrigin==="use-credentials"?s.credentials="include":l.crossOrigin==="anonymous"?s.credentials="omit":s.credentials="same-origin",s}function r(l){if(l.ep)return;l.ep=!0;const s=n(l);fetch(l.href,s)}})();function cd(e){return e&&e.__esModule&&Object.prototype.hasOwnProperty.call(e,"default")?e.default:e}var $a={exports:{}},El={},Ba={exports:{}},D={};/** - * @license React - * react.production.min.js - * - * Copyright (c) Facebook, Inc. and its affiliates. - * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - */var mr=Symbol.for("react.element"),dd=Symbol.for("react.portal"),fd=Symbol.for("react.fragment"),pd=Symbol.for("react.strict_mode"),hd=Symbol.for("react.profiler"),md=Symbol.for("react.provider"),vd=Symbol.for("react.context"),gd=Symbol.for("react.forward_ref"),yd=Symbol.for("react.suspense"),xd=Symbol.for("react.memo"),kd=Symbol.for("react.lazy"),ho=Symbol.iterator;function jd(e){return e===null||typeof e!="object"?null:(e=ho&&e[ho]||e["@@iterator"],typeof e=="function"?e:null)}var Ua={isMounted:function(){return!1},enqueueForceUpdate:function(){},enqueueReplaceState:function(){},enqueueSetState:function(){}},Wa=Object.assign,Aa={};function kn(e,t,n){this.props=e,this.context=t,this.refs=Aa,this.updater=n||Ua}kn.prototype.isReactComponent={};kn.prototype.setState=function(e,t){if(typeof e!="object"&&typeof e!="function"&&e!=null)throw Error("setState(...): takes an object of state variables to update or a function which returns an object of state variables.");this.updater.enqueueSetState(this,e,t,"setState")};kn.prototype.forceUpdate=function(e){this.updater.enqueueForceUpdate(this,e,"forceUpdate")};function Va(){}Va.prototype=kn.prototype;function hi(e,t,n){this.props=e,this.context=t,this.refs=Aa,this.updater=n||Ua}var mi=hi.prototype=new Va;mi.constructor=hi;Wa(mi,kn.prototype);mi.isPureReactComponent=!0;var mo=Array.isArray,Ha=Object.prototype.hasOwnProperty,vi={current:null},Qa={key:!0,ref:!0,__self:!0,__source:!0};function Ka(e,t,n){var r,l={},s=null,o=null;if(t!=null)for(r in t.ref!==void 0&&(o=t.ref),t.key!==void 0&&(s=""+t.key),t)Ha.call(t,r)&&!Qa.hasOwnProperty(r)&&(l[r]=t[r]);var a=arguments.length-2;if(a===1)l.children=n;else if(1>>1,X=L[H];if(0>>1;Hl(se,R))Oel(wr,se)?(L[H]=wr,L[Oe]=R,H=Oe):(L[H]=se,L[I]=R,H=I);else if(Oel(wr,R))L[H]=wr,L[Oe]=R,H=Oe;else break e}}return M}function l(L,M){var R=L.sortIndex-M.sortIndex;return R!==0?R:L.id-M.id}if(typeof performance=="object"&&typeof performance.now=="function"){var s=performance;e.unstable_now=function(){return s.now()}}else{var o=Date,a=o.now();e.unstable_now=function(){return o.now()-a}}var u=[],c=[],h=1,p=null,v=3,g=!1,k=!1,x=!1,S=typeof setTimeout=="function"?setTimeout:null,m=typeof clearTimeout=="function"?clearTimeout:null,d=typeof setImmediate<"u"?setImmediate:null;typeof navigator<"u"&&navigator.scheduling!==void 0&&navigator.scheduling.isInputPending!==void 0&&navigator.scheduling.isInputPending.bind(navigator.scheduling);function f(L){for(var M=n(c);M!==null;){if(M.callback===null)r(c);else if(M.startTime<=L)r(c),M.sortIndex=M.expirationTime,t(u,M);else break;M=n(c)}}function y(L){if(x=!1,f(L),!k)if(n(u)!==null)k=!0,Xe(N);else{var M=n(c);M!==null&&Kt(y,M.startTime-L)}}function N(L,M){k=!1,x&&(x=!1,m(E),E=-1),g=!0;var R=v;try{for(f(M),p=n(u);p!==null&&(!(p.expirationTime>M)||L&&!q());){var H=p.callback;if(typeof H=="function"){p.callback=null,v=p.priorityLevel;var X=H(p.expirationTime<=M);M=e.unstable_now(),typeof X=="function"?p.callback=X:p===n(u)&&r(u),f(M)}else r(u);p=n(u)}if(p!==null)var z=!0;else{var I=n(c);I!==null&&Kt(y,I.startTime-M),z=!1}return z}finally{p=null,v=R,g=!1}}var w=!1,C=null,E=-1,O=5,T=-1;function q(){return!(e.unstable_now()-TL||125H?(L.sortIndex=R,t(c,L),n(u)===null&&L===n(c)&&(x?(m(E),E=-1):x=!0,Kt(y,R-H))):(L.sortIndex=X,t(u,L),k||g||(k=!0,Xe(N))),L},e.unstable_shouldYield=q,e.unstable_wrapCallback=function(L){var M=v;return function(){var R=v;v=M;try{return L.apply(this,arguments)}finally{v=R}}}})(qa);Za.exports=qa;var Rd=Za.exports;/** - * @license React - * react-dom.production.min.js - * - * Copyright (c) Facebook, Inc. and its affiliates. - * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - */var Od=j,Ce=Rd;function _(e){for(var t="https://reactjs.org/docs/error-decoder.html?invariant="+e,n=1;n"u"||typeof window.document>"u"||typeof window.document.createElement>"u"),xs=Object.prototype.hasOwnProperty,Dd=/^[:A-Z_a-z\u00C0-\u00D6\u00D8-\u00F6\u00F8-\u02FF\u0370-\u037D\u037F-\u1FFF\u200C-\u200D\u2070-\u218F\u2C00-\u2FEF\u3001-\uD7FF\uF900-\uFDCF\uFDF0-\uFFFD][:A-Z_a-z\u00C0-\u00D6\u00D8-\u00F6\u00F8-\u02FF\u0370-\u037D\u037F-\u1FFF\u200C-\u200D\u2070-\u218F\u2C00-\u2FEF\u3001-\uD7FF\uF900-\uFDCF\uFDF0-\uFFFD\-.0-9\u00B7\u0300-\u036F\u203F-\u2040]*$/,go={},yo={};function Id(e){return xs.call(yo,e)?!0:xs.call(go,e)?!1:Dd.test(e)?yo[e]=!0:(go[e]=!0,!1)}function Fd(e,t,n,r){if(n!==null&&n.type===0)return!1;switch(typeof t){case"function":case"symbol":return!0;case"boolean":return r?!1:n!==null?!n.acceptsBooleans:(e=e.toLowerCase().slice(0,5),e!=="data-"&&e!=="aria-");default:return!1}}function $d(e,t,n,r){if(t===null||typeof t>"u"||Fd(e,t,n,r))return!0;if(r)return!1;if(n!==null)switch(n.type){case 3:return!t;case 4:return t===!1;case 5:return isNaN(t);case 6:return isNaN(t)||1>t}return!1}function ve(e,t,n,r,l,s,o){this.acceptsBooleans=t===2||t===3||t===4,this.attributeName=r,this.attributeNamespace=l,this.mustUseProperty=n,this.propertyName=e,this.type=t,this.sanitizeURL=s,this.removeEmptyString=o}var ae={};"children dangerouslySetInnerHTML defaultValue defaultChecked innerHTML suppressContentEditableWarning suppressHydrationWarning style".split(" ").forEach(function(e){ae[e]=new ve(e,0,!1,e,null,!1,!1)});[["acceptCharset","accept-charset"],["className","class"],["htmlFor","for"],["httpEquiv","http-equiv"]].forEach(function(e){var t=e[0];ae[t]=new ve(t,1,!1,e[1],null,!1,!1)});["contentEditable","draggable","spellCheck","value"].forEach(function(e){ae[e]=new ve(e,2,!1,e.toLowerCase(),null,!1,!1)});["autoReverse","externalResourcesRequired","focusable","preserveAlpha"].forEach(function(e){ae[e]=new ve(e,2,!1,e,null,!1,!1)});"allowFullScreen async autoFocus autoPlay controls default defer disabled disablePictureInPicture disableRemotePlayback formNoValidate hidden loop noModule noValidate open playsInline readOnly required reversed scoped seamless itemScope".split(" ").forEach(function(e){ae[e]=new ve(e,3,!1,e.toLowerCase(),null,!1,!1)});["checked","multiple","muted","selected"].forEach(function(e){ae[e]=new ve(e,3,!0,e,null,!1,!1)});["capture","download"].forEach(function(e){ae[e]=new ve(e,4,!1,e,null,!1,!1)});["cols","rows","size","span"].forEach(function(e){ae[e]=new ve(e,6,!1,e,null,!1,!1)});["rowSpan","start"].forEach(function(e){ae[e]=new ve(e,5,!1,e.toLowerCase(),null,!1,!1)});var yi=/[\-:]([a-z])/g;function xi(e){return e[1].toUpperCase()}"accent-height alignment-baseline arabic-form baseline-shift cap-height clip-path clip-rule color-interpolation color-interpolation-filters color-profile color-rendering dominant-baseline enable-background fill-opacity fill-rule flood-color flood-opacity font-family font-size font-size-adjust font-stretch font-style font-variant font-weight glyph-name glyph-orientation-horizontal glyph-orientation-vertical horiz-adv-x horiz-origin-x image-rendering letter-spacing lighting-color marker-end marker-mid marker-start overline-position overline-thickness paint-order panose-1 pointer-events rendering-intent shape-rendering stop-color stop-opacity strikethrough-position strikethrough-thickness stroke-dasharray stroke-dashoffset stroke-linecap stroke-linejoin stroke-miterlimit stroke-opacity stroke-width text-anchor text-decoration text-rendering underline-position underline-thickness unicode-bidi unicode-range units-per-em v-alphabetic v-hanging v-ideographic v-mathematical vector-effect vert-adv-y vert-origin-x vert-origin-y word-spacing writing-mode xmlns:xlink x-height".split(" ").forEach(function(e){var t=e.replace(yi,xi);ae[t]=new ve(t,1,!1,e,null,!1,!1)});"xlink:actuate xlink:arcrole xlink:role xlink:show xlink:title xlink:type".split(" ").forEach(function(e){var t=e.replace(yi,xi);ae[t]=new ve(t,1,!1,e,"http://www.w3.org/1999/xlink",!1,!1)});["xml:base","xml:lang","xml:space"].forEach(function(e){var t=e.replace(yi,xi);ae[t]=new ve(t,1,!1,e,"http://www.w3.org/XML/1998/namespace",!1,!1)});["tabIndex","crossOrigin"].forEach(function(e){ae[e]=new ve(e,1,!1,e.toLowerCase(),null,!1,!1)});ae.xlinkHref=new ve("xlinkHref",1,!1,"xlink:href","http://www.w3.org/1999/xlink",!0,!1);["src","href","action","formAction"].forEach(function(e){ae[e]=new ve(e,1,!1,e.toLowerCase(),null,!0,!0)});function ki(e,t,n,r){var l=ae.hasOwnProperty(t)?ae[t]:null;(l!==null?l.type!==0:r||!(2a||l[o]!==s[a]){var u=` -`+l[o].replace(" at new "," at ");return e.displayName&&u.includes("")&&(u=u.replace("",e.displayName)),u}while(1<=o&&0<=a);break}}}finally{Kl=!1,Error.prepareStackTrace=n}return(e=e?e.displayName||e.name:"")?In(e):""}function Bd(e){switch(e.tag){case 5:return In(e.type);case 16:return In("Lazy");case 13:return In("Suspense");case 19:return In("SuspenseList");case 0:case 2:case 15:return e=Gl(e.type,!1),e;case 11:return e=Gl(e.type.render,!1),e;case 1:return e=Gl(e.type,!0),e;default:return""}}function Ss(e){if(e==null)return null;if(typeof e=="function")return e.displayName||e.name||null;if(typeof e=="string")return e;switch(e){case Xt:return"Fragment";case Yt:return"Portal";case ks:return"Profiler";case ji:return"StrictMode";case js:return"Suspense";case ws:return"SuspenseList"}if(typeof e=="object")switch(e.$$typeof){case tu:return(e.displayName||"Context")+".Consumer";case eu:return(e._context.displayName||"Context")+".Provider";case wi:var t=e.render;return e=e.displayName,e||(e=t.displayName||t.name||"",e=e!==""?"ForwardRef("+e+")":"ForwardRef"),e;case Si:return t=e.displayName||null,t!==null?t:Ss(e.type)||"Memo";case at:t=e._payload,e=e._init;try{return Ss(e(t))}catch{}}return null}function Ud(e){var t=e.type;switch(e.tag){case 24:return"Cache";case 9:return(t.displayName||"Context")+".Consumer";case 10:return(t._context.displayName||"Context")+".Provider";case 18:return"DehydratedFragment";case 11:return e=t.render,e=e.displayName||e.name||"",t.displayName||(e!==""?"ForwardRef("+e+")":"ForwardRef");case 7:return"Fragment";case 5:return t;case 4:return"Portal";case 3:return"Root";case 6:return"Text";case 16:return Ss(t);case 8:return t===ji?"StrictMode":"Mode";case 22:return"Offscreen";case 12:return"Profiler";case 21:return"Scope";case 13:return"Suspense";case 19:return"SuspenseList";case 25:return"TracingMarker";case 1:case 0:case 17:case 2:case 14:case 15:if(typeof t=="function")return t.displayName||t.name||null;if(typeof t=="string")return t}return null}function Nt(e){switch(typeof e){case"boolean":case"number":case"string":case"undefined":return e;case"object":return e;default:return""}}function ru(e){var t=e.type;return(e=e.nodeName)&&e.toLowerCase()==="input"&&(t==="checkbox"||t==="radio")}function Wd(e){var t=ru(e)?"checked":"value",n=Object.getOwnPropertyDescriptor(e.constructor.prototype,t),r=""+e[t];if(!e.hasOwnProperty(t)&&typeof n<"u"&&typeof n.get=="function"&&typeof n.set=="function"){var l=n.get,s=n.set;return Object.defineProperty(e,t,{configurable:!0,get:function(){return l.call(this)},set:function(o){r=""+o,s.call(this,o)}}),Object.defineProperty(e,t,{enumerable:n.enumerable}),{getValue:function(){return r},setValue:function(o){r=""+o},stopTracking:function(){e._valueTracker=null,delete e[t]}}}}function Cr(e){e._valueTracker||(e._valueTracker=Wd(e))}function lu(e){if(!e)return!1;var t=e._valueTracker;if(!t)return!0;var n=t.getValue(),r="";return e&&(r=ru(e)?e.checked?"true":"false":e.value),e=r,e!==n?(t.setValue(e),!0):!1}function tl(e){if(e=e||(typeof document<"u"?document:void 0),typeof e>"u")return null;try{return e.activeElement||e.body}catch{return e.body}}function Ns(e,t){var n=t.checked;return G({},t,{defaultChecked:void 0,defaultValue:void 0,value:void 0,checked:n??e._wrapperState.initialChecked})}function ko(e,t){var n=t.defaultValue==null?"":t.defaultValue,r=t.checked!=null?t.checked:t.defaultChecked;n=Nt(t.value!=null?t.value:n),e._wrapperState={initialChecked:r,initialValue:n,controlled:t.type==="checkbox"||t.type==="radio"?t.checked!=null:t.value!=null}}function su(e,t){t=t.checked,t!=null&&ki(e,"checked",t,!1)}function Cs(e,t){su(e,t);var n=Nt(t.value),r=t.type;if(n!=null)r==="number"?(n===0&&e.value===""||e.value!=n)&&(e.value=""+n):e.value!==""+n&&(e.value=""+n);else if(r==="submit"||r==="reset"){e.removeAttribute("value");return}t.hasOwnProperty("value")?_s(e,t.type,n):t.hasOwnProperty("defaultValue")&&_s(e,t.type,Nt(t.defaultValue)),t.checked==null&&t.defaultChecked!=null&&(e.defaultChecked=!!t.defaultChecked)}function jo(e,t,n){if(t.hasOwnProperty("value")||t.hasOwnProperty("defaultValue")){var r=t.type;if(!(r!=="submit"&&r!=="reset"||t.value!==void 0&&t.value!==null))return;t=""+e._wrapperState.initialValue,n||t===e.value||(e.value=t),e.defaultValue=t}n=e.name,n!==""&&(e.name=""),e.defaultChecked=!!e._wrapperState.initialChecked,n!==""&&(e.name=n)}function _s(e,t,n){(t!=="number"||tl(e.ownerDocument)!==e)&&(n==null?e.defaultValue=""+e._wrapperState.initialValue:e.defaultValue!==""+n&&(e.defaultValue=""+n))}var Fn=Array.isArray;function on(e,t,n,r){if(e=e.options,t){t={};for(var l=0;l"+t.valueOf().toString()+"",t=_r.firstChild;e.firstChild;)e.removeChild(e.firstChild);for(;t.firstChild;)e.appendChild(t.firstChild)}});function Zn(e,t){if(t){var n=e.firstChild;if(n&&n===e.lastChild&&n.nodeType===3){n.nodeValue=t;return}}e.textContent=t}var Wn={animationIterationCount:!0,aspectRatio:!0,borderImageOutset:!0,borderImageSlice:!0,borderImageWidth:!0,boxFlex:!0,boxFlexGroup:!0,boxOrdinalGroup:!0,columnCount:!0,columns:!0,flex:!0,flexGrow:!0,flexPositive:!0,flexShrink:!0,flexNegative:!0,flexOrder:!0,gridArea:!0,gridRow:!0,gridRowEnd:!0,gridRowSpan:!0,gridRowStart:!0,gridColumn:!0,gridColumnEnd:!0,gridColumnSpan:!0,gridColumnStart:!0,fontWeight:!0,lineClamp:!0,lineHeight:!0,opacity:!0,order:!0,orphans:!0,tabSize:!0,widows:!0,zIndex:!0,zoom:!0,fillOpacity:!0,floodOpacity:!0,stopOpacity:!0,strokeDasharray:!0,strokeDashoffset:!0,strokeMiterlimit:!0,strokeOpacity:!0,strokeWidth:!0},Ad=["Webkit","ms","Moz","O"];Object.keys(Wn).forEach(function(e){Ad.forEach(function(t){t=t+e.charAt(0).toUpperCase()+e.substring(1),Wn[t]=Wn[e]})});function uu(e,t,n){return t==null||typeof t=="boolean"||t===""?"":n||typeof t!="number"||t===0||Wn.hasOwnProperty(e)&&Wn[e]?(""+t).trim():t+"px"}function cu(e,t){e=e.style;for(var n in t)if(t.hasOwnProperty(n)){var r=n.indexOf("--")===0,l=uu(n,t[n],r);n==="float"&&(n="cssFloat"),r?e.setProperty(n,l):e[n]=l}}var Vd=G({menuitem:!0},{area:!0,base:!0,br:!0,col:!0,embed:!0,hr:!0,img:!0,input:!0,keygen:!0,link:!0,meta:!0,param:!0,source:!0,track:!0,wbr:!0});function Ps(e,t){if(t){if(Vd[e]&&(t.children!=null||t.dangerouslySetInnerHTML!=null))throw Error(_(137,e));if(t.dangerouslySetInnerHTML!=null){if(t.children!=null)throw Error(_(60));if(typeof t.dangerouslySetInnerHTML!="object"||!("__html"in t.dangerouslySetInnerHTML))throw Error(_(61))}if(t.style!=null&&typeof t.style!="object")throw Error(_(62))}}function zs(e,t){if(e.indexOf("-")===-1)return typeof t.is=="string";switch(e){case"annotation-xml":case"color-profile":case"font-face":case"font-face-src":case"font-face-uri":case"font-face-format":case"font-face-name":case"missing-glyph":return!1;default:return!0}}var Ms=null;function Ni(e){return e=e.target||e.srcElement||window,e.correspondingUseElement&&(e=e.correspondingUseElement),e.nodeType===3?e.parentNode:e}var Ts=null,an=null,un=null;function No(e){if(e=yr(e)){if(typeof Ts!="function")throw Error(_(280));var t=e.stateNode;t&&(t=Tl(t),Ts(e.stateNode,e.type,t))}}function du(e){an?un?un.push(e):un=[e]:an=e}function fu(){if(an){var e=an,t=un;if(un=an=null,No(e),t)for(e=0;e>>=0,e===0?32:31-(ef(e)/tf|0)|0}var Er=64,Lr=4194304;function $n(e){switch(e&-e){case 1:return 1;case 2:return 2;case 4:return 4;case 8:return 8;case 16:return 16;case 32:return 32;case 64:case 128:case 256:case 512:case 1024:case 2048:case 4096:case 8192:case 16384:case 32768:case 65536:case 131072:case 262144:case 524288:case 1048576:case 2097152:return e&4194240;case 4194304:case 8388608:case 16777216:case 33554432:case 67108864:return e&130023424;case 134217728:return 134217728;case 268435456:return 268435456;case 536870912:return 536870912;case 1073741824:return 1073741824;default:return e}}function sl(e,t){var n=e.pendingLanes;if(n===0)return 0;var r=0,l=e.suspendedLanes,s=e.pingedLanes,o=n&268435455;if(o!==0){var a=o&~l;a!==0?r=$n(a):(s&=o,s!==0&&(r=$n(s)))}else o=n&~l,o!==0?r=$n(o):s!==0&&(r=$n(s));if(r===0)return 0;if(t!==0&&t!==r&&!(t&l)&&(l=r&-r,s=t&-t,l>=s||l===16&&(s&4194240)!==0))return t;if(r&4&&(r|=n&16),t=e.entangledLanes,t!==0)for(e=e.entanglements,t&=r;0n;n++)t.push(e);return t}function vr(e,t,n){e.pendingLanes|=t,t!==536870912&&(e.suspendedLanes=0,e.pingedLanes=0),e=e.eventTimes,t=31-Be(t),e[t]=n}function sf(e,t){var n=e.pendingLanes&~t;e.pendingLanes=t,e.suspendedLanes=0,e.pingedLanes=0,e.expiredLanes&=t,e.mutableReadLanes&=t,e.entangledLanes&=t,t=e.entanglements;var r=e.eventTimes;for(e=e.expirationTimes;0=Vn),Ro=" ",Oo=!1;function Tu(e,t){switch(e){case"keyup":return Of.indexOf(t.keyCode)!==-1;case"keydown":return t.keyCode!==229;case"keypress":case"mousedown":case"focusout":return!0;default:return!1}}function Ru(e){return e=e.detail,typeof e=="object"&&"data"in e?e.data:null}var Jt=!1;function If(e,t){switch(e){case"compositionend":return Ru(t);case"keypress":return t.which!==32?null:(Oo=!0,Ro);case"textInput":return e=t.data,e===Ro&&Oo?null:e;default:return null}}function Ff(e,t){if(Jt)return e==="compositionend"||!Ti&&Tu(e,t)?(e=zu(),Qr=Pi=ft=null,Jt=!1,e):null;switch(e){case"paste":return null;case"keypress":if(!(t.ctrlKey||t.altKey||t.metaKey)||t.ctrlKey&&t.altKey){if(t.char&&1=t)return{node:n,offset:t-e};e=r}e:{for(;n;){if(n.nextSibling){n=n.nextSibling;break e}n=n.parentNode}n=void 0}n=$o(n)}}function Fu(e,t){return e&&t?e===t?!0:e&&e.nodeType===3?!1:t&&t.nodeType===3?Fu(e,t.parentNode):"contains"in e?e.contains(t):e.compareDocumentPosition?!!(e.compareDocumentPosition(t)&16):!1:!1}function $u(){for(var e=window,t=tl();t instanceof e.HTMLIFrameElement;){try{var n=typeof t.contentWindow.location.href=="string"}catch{n=!1}if(n)e=t.contentWindow;else break;t=tl(e.document)}return t}function Ri(e){var t=e&&e.nodeName&&e.nodeName.toLowerCase();return t&&(t==="input"&&(e.type==="text"||e.type==="search"||e.type==="tel"||e.type==="url"||e.type==="password")||t==="textarea"||e.contentEditable==="true")}function Kf(e){var t=$u(),n=e.focusedElem,r=e.selectionRange;if(t!==n&&n&&n.ownerDocument&&Fu(n.ownerDocument.documentElement,n)){if(r!==null&&Ri(n)){if(t=r.start,e=r.end,e===void 0&&(e=t),"selectionStart"in n)n.selectionStart=t,n.selectionEnd=Math.min(e,n.value.length);else if(e=(t=n.ownerDocument||document)&&t.defaultView||window,e.getSelection){e=e.getSelection();var l=n.textContent.length,s=Math.min(r.start,l);r=r.end===void 0?s:Math.min(r.end,l),!e.extend&&s>r&&(l=r,r=s,s=l),l=Bo(n,s);var o=Bo(n,r);l&&o&&(e.rangeCount!==1||e.anchorNode!==l.node||e.anchorOffset!==l.offset||e.focusNode!==o.node||e.focusOffset!==o.offset)&&(t=t.createRange(),t.setStart(l.node,l.offset),e.removeAllRanges(),s>r?(e.addRange(t),e.extend(o.node,o.offset)):(t.setEnd(o.node,o.offset),e.addRange(t)))}}for(t=[],e=n;e=e.parentNode;)e.nodeType===1&&t.push({element:e,left:e.scrollLeft,top:e.scrollTop});for(typeof n.focus=="function"&&n.focus(),n=0;n=document.documentMode,Zt=null,$s=null,Qn=null,Bs=!1;function Uo(e,t,n){var r=n.window===n?n.document:n.nodeType===9?n:n.ownerDocument;Bs||Zt==null||Zt!==tl(r)||(r=Zt,"selectionStart"in r&&Ri(r)?r={start:r.selectionStart,end:r.selectionEnd}:(r=(r.ownerDocument&&r.ownerDocument.defaultView||window).getSelection(),r={anchorNode:r.anchorNode,anchorOffset:r.anchorOffset,focusNode:r.focusNode,focusOffset:r.focusOffset}),Qn&&rr(Qn,r)||(Qn=r,r=al($s,"onSelect"),0en||(e.current=Qs[en],Qs[en]=null,en--)}function U(e,t){en++,Qs[en]=e.current,e.current=t}var Ct={},fe=Et(Ct),xe=Et(!1),$t=Ct;function hn(e,t){var n=e.type.contextTypes;if(!n)return Ct;var r=e.stateNode;if(r&&r.__reactInternalMemoizedUnmaskedChildContext===t)return r.__reactInternalMemoizedMaskedChildContext;var l={},s;for(s in n)l[s]=t[s];return r&&(e=e.stateNode,e.__reactInternalMemoizedUnmaskedChildContext=t,e.__reactInternalMemoizedMaskedChildContext=l),l}function ke(e){return e=e.childContextTypes,e!=null}function cl(){A(xe),A(fe)}function Go(e,t,n){if(fe.current!==Ct)throw Error(_(168));U(fe,t),U(xe,n)}function Gu(e,t,n){var r=e.stateNode;if(t=t.childContextTypes,typeof r.getChildContext!="function")return n;r=r.getChildContext();for(var l in r)if(!(l in t))throw Error(_(108,Ud(e)||"Unknown",l));return G({},n,r)}function dl(e){return e=(e=e.stateNode)&&e.__reactInternalMemoizedMergedChildContext||Ct,$t=fe.current,U(fe,e),U(xe,xe.current),!0}function Yo(e,t,n){var r=e.stateNode;if(!r)throw Error(_(169));n?(e=Gu(e,t,$t),r.__reactInternalMemoizedMergedChildContext=e,A(xe),A(fe),U(fe,e)):A(xe),U(xe,n)}var Ze=null,Rl=!1,os=!1;function Yu(e){Ze===null?Ze=[e]:Ze.push(e)}function lp(e){Rl=!0,Yu(e)}function Lt(){if(!os&&Ze!==null){os=!0;var e=0,t=$;try{var n=Ze;for($=1;e>=o,l-=o,qe=1<<32-Be(t)+l|n<E?(O=C,C=null):O=C.sibling;var T=v(m,C,f[E],y);if(T===null){C===null&&(C=O);break}e&&C&&T.alternate===null&&t(m,C),d=s(T,d,E),w===null?N=T:w.sibling=T,w=T,C=O}if(E===f.length)return n(m,C),V&&Mt(m,E),N;if(C===null){for(;EE?(O=C,C=null):O=C.sibling;var q=v(m,C,T.value,y);if(q===null){C===null&&(C=O);break}e&&C&&q.alternate===null&&t(m,C),d=s(q,d,E),w===null?N=q:w.sibling=q,w=q,C=O}if(T.done)return n(m,C),V&&Mt(m,E),N;if(C===null){for(;!T.done;E++,T=f.next())T=p(m,T.value,y),T!==null&&(d=s(T,d,E),w===null?N=T:w.sibling=T,w=T);return V&&Mt(m,E),N}for(C=r(m,C);!T.done;E++,T=f.next())T=g(C,m,E,T.value,y),T!==null&&(e&&T.alternate!==null&&C.delete(T.key===null?E:T.key),d=s(T,d,E),w===null?N=T:w.sibling=T,w=T);return e&&C.forEach(function(Ye){return t(m,Ye)}),V&&Mt(m,E),N}function S(m,d,f,y){if(typeof f=="object"&&f!==null&&f.type===Xt&&f.key===null&&(f=f.props.children),typeof f=="object"&&f!==null){switch(f.$$typeof){case Nr:e:{for(var N=f.key,w=d;w!==null;){if(w.key===N){if(N=f.type,N===Xt){if(w.tag===7){n(m,w.sibling),d=l(w,f.props.children),d.return=m,m=d;break e}}else if(w.elementType===N||typeof N=="object"&&N!==null&&N.$$typeof===at&&Zo(N)===w.type){n(m,w.sibling),d=l(w,f.props),d.ref=zn(m,w,f),d.return=m,m=d;break e}n(m,w);break}else t(m,w);w=w.sibling}f.type===Xt?(d=Ft(f.props.children,m.mode,y,f.key),d.return=m,m=d):(y=br(f.type,f.key,f.props,null,m.mode,y),y.ref=zn(m,d,f),y.return=m,m=y)}return o(m);case Yt:e:{for(w=f.key;d!==null;){if(d.key===w)if(d.tag===4&&d.stateNode.containerInfo===f.containerInfo&&d.stateNode.implementation===f.implementation){n(m,d.sibling),d=l(d,f.children||[]),d.return=m,m=d;break e}else{n(m,d);break}else t(m,d);d=d.sibling}d=ms(f,m.mode,y),d.return=m,m=d}return o(m);case at:return w=f._init,S(m,d,w(f._payload),y)}if(Fn(f))return k(m,d,f,y);if(Cn(f))return x(m,d,f,y);Dr(m,f)}return typeof f=="string"&&f!==""||typeof f=="number"?(f=""+f,d!==null&&d.tag===6?(n(m,d.sibling),d=l(d,f),d.return=m,m=d):(n(m,d),d=hs(f,m.mode,y),d.return=m,m=d),o(m)):n(m,d)}return S}var vn=qu(!0),bu=qu(!1),hl=Et(null),ml=null,rn=null,Fi=null;function $i(){Fi=rn=ml=null}function Bi(e){var t=hl.current;A(hl),e._currentValue=t}function Ys(e,t,n){for(;e!==null;){var r=e.alternate;if((e.childLanes&t)!==t?(e.childLanes|=t,r!==null&&(r.childLanes|=t)):r!==null&&(r.childLanes&t)!==t&&(r.childLanes|=t),e===n)break;e=e.return}}function dn(e,t){ml=e,Fi=rn=null,e=e.dependencies,e!==null&&e.firstContext!==null&&(e.lanes&t&&(ye=!0),e.firstContext=null)}function Te(e){var t=e._currentValue;if(Fi!==e)if(e={context:e,memoizedValue:t,next:null},rn===null){if(ml===null)throw Error(_(308));rn=e,ml.dependencies={lanes:0,firstContext:e}}else rn=rn.next=e;return t}var Ot=null;function Ui(e){Ot===null?Ot=[e]:Ot.push(e)}function ec(e,t,n,r){var l=t.interleaved;return l===null?(n.next=n,Ui(t)):(n.next=l.next,l.next=n),t.interleaved=n,rt(e,r)}function rt(e,t){e.lanes|=t;var n=e.alternate;for(n!==null&&(n.lanes|=t),n=e,e=e.return;e!==null;)e.childLanes|=t,n=e.alternate,n!==null&&(n.childLanes|=t),n=e,e=e.return;return n.tag===3?n.stateNode:null}var ut=!1;function Wi(e){e.updateQueue={baseState:e.memoizedState,firstBaseUpdate:null,lastBaseUpdate:null,shared:{pending:null,interleaved:null,lanes:0},effects:null}}function tc(e,t){e=e.updateQueue,t.updateQueue===e&&(t.updateQueue={baseState:e.baseState,firstBaseUpdate:e.firstBaseUpdate,lastBaseUpdate:e.lastBaseUpdate,shared:e.shared,effects:e.effects})}function et(e,t){return{eventTime:e,lane:t,tag:0,payload:null,callback:null,next:null}}function xt(e,t,n){var r=e.updateQueue;if(r===null)return null;if(r=r.shared,F&2){var l=r.pending;return l===null?t.next=t:(t.next=l.next,l.next=t),r.pending=t,rt(e,n)}return l=r.interleaved,l===null?(t.next=t,Ui(r)):(t.next=l.next,l.next=t),r.interleaved=t,rt(e,n)}function Gr(e,t,n){if(t=t.updateQueue,t!==null&&(t=t.shared,(n&4194240)!==0)){var r=t.lanes;r&=e.pendingLanes,n|=r,t.lanes=n,_i(e,n)}}function qo(e,t){var n=e.updateQueue,r=e.alternate;if(r!==null&&(r=r.updateQueue,n===r)){var l=null,s=null;if(n=n.firstBaseUpdate,n!==null){do{var o={eventTime:n.eventTime,lane:n.lane,tag:n.tag,payload:n.payload,callback:n.callback,next:null};s===null?l=s=o:s=s.next=o,n=n.next}while(n!==null);s===null?l=s=t:s=s.next=t}else l=s=t;n={baseState:r.baseState,firstBaseUpdate:l,lastBaseUpdate:s,shared:r.shared,effects:r.effects},e.updateQueue=n;return}e=n.lastBaseUpdate,e===null?n.firstBaseUpdate=t:e.next=t,n.lastBaseUpdate=t}function vl(e,t,n,r){var l=e.updateQueue;ut=!1;var s=l.firstBaseUpdate,o=l.lastBaseUpdate,a=l.shared.pending;if(a!==null){l.shared.pending=null;var u=a,c=u.next;u.next=null,o===null?s=c:o.next=c,o=u;var h=e.alternate;h!==null&&(h=h.updateQueue,a=h.lastBaseUpdate,a!==o&&(a===null?h.firstBaseUpdate=c:a.next=c,h.lastBaseUpdate=u))}if(s!==null){var p=l.baseState;o=0,h=c=u=null,a=s;do{var v=a.lane,g=a.eventTime;if((r&v)===v){h!==null&&(h=h.next={eventTime:g,lane:0,tag:a.tag,payload:a.payload,callback:a.callback,next:null});e:{var k=e,x=a;switch(v=t,g=n,x.tag){case 1:if(k=x.payload,typeof k=="function"){p=k.call(g,p,v);break e}p=k;break e;case 3:k.flags=k.flags&-65537|128;case 0:if(k=x.payload,v=typeof k=="function"?k.call(g,p,v):k,v==null)break e;p=G({},p,v);break e;case 2:ut=!0}}a.callback!==null&&a.lane!==0&&(e.flags|=64,v=l.effects,v===null?l.effects=[a]:v.push(a))}else g={eventTime:g,lane:v,tag:a.tag,payload:a.payload,callback:a.callback,next:null},h===null?(c=h=g,u=p):h=h.next=g,o|=v;if(a=a.next,a===null){if(a=l.shared.pending,a===null)break;v=a,a=v.next,v.next=null,l.lastBaseUpdate=v,l.shared.pending=null}}while(!0);if(h===null&&(u=p),l.baseState=u,l.firstBaseUpdate=c,l.lastBaseUpdate=h,t=l.shared.interleaved,t!==null){l=t;do o|=l.lane,l=l.next;while(l!==t)}else s===null&&(l.shared.lanes=0);Wt|=o,e.lanes=o,e.memoizedState=p}}function bo(e,t,n){if(e=t.effects,t.effects=null,e!==null)for(t=0;tn?n:4,e(!0);var r=us.transition;us.transition={};try{e(!1),t()}finally{$=n,us.transition=r}}function yc(){return Re().memoizedState}function ap(e,t,n){var r=jt(e);if(n={lane:r,action:n,hasEagerState:!1,eagerState:null,next:null},xc(e))kc(t,n);else if(n=ec(e,t,n,r),n!==null){var l=he();Ue(n,e,r,l),jc(n,t,r)}}function up(e,t,n){var r=jt(e),l={lane:r,action:n,hasEagerState:!1,eagerState:null,next:null};if(xc(e))kc(t,l);else{var s=e.alternate;if(e.lanes===0&&(s===null||s.lanes===0)&&(s=t.lastRenderedReducer,s!==null))try{var o=t.lastRenderedState,a=s(o,n);if(l.hasEagerState=!0,l.eagerState=a,We(a,o)){var u=t.interleaved;u===null?(l.next=l,Ui(t)):(l.next=u.next,u.next=l),t.interleaved=l;return}}catch{}finally{}n=ec(e,t,l,r),n!==null&&(l=he(),Ue(n,e,r,l),jc(n,t,r))}}function xc(e){var t=e.alternate;return e===K||t!==null&&t===K}function kc(e,t){Kn=yl=!0;var n=e.pending;n===null?t.next=t:(t.next=n.next,n.next=t),e.pending=t}function jc(e,t,n){if(n&4194240){var r=t.lanes;r&=e.pendingLanes,n|=r,t.lanes=n,_i(e,n)}}var xl={readContext:Te,useCallback:ue,useContext:ue,useEffect:ue,useImperativeHandle:ue,useInsertionEffect:ue,useLayoutEffect:ue,useMemo:ue,useReducer:ue,useRef:ue,useState:ue,useDebugValue:ue,useDeferredValue:ue,useTransition:ue,useMutableSource:ue,useSyncExternalStore:ue,useId:ue,unstable_isNewReconciler:!1},cp={readContext:Te,useCallback:function(e,t){return He().memoizedState=[e,t===void 0?null:t],e},useContext:Te,useEffect:ta,useImperativeHandle:function(e,t,n){return n=n!=null?n.concat([e]):null,Xr(4194308,4,pc.bind(null,t,e),n)},useLayoutEffect:function(e,t){return Xr(4194308,4,e,t)},useInsertionEffect:function(e,t){return Xr(4,2,e,t)},useMemo:function(e,t){var n=He();return t=t===void 0?null:t,e=e(),n.memoizedState=[e,t],e},useReducer:function(e,t,n){var r=He();return t=n!==void 0?n(t):t,r.memoizedState=r.baseState=t,e={pending:null,interleaved:null,lanes:0,dispatch:null,lastRenderedReducer:e,lastRenderedState:t},r.queue=e,e=e.dispatch=ap.bind(null,K,e),[r.memoizedState,e]},useRef:function(e){var t=He();return e={current:e},t.memoizedState=e},useState:ea,useDebugValue:Xi,useDeferredValue:function(e){return He().memoizedState=e},useTransition:function(){var e=ea(!1),t=e[0];return e=op.bind(null,e[1]),He().memoizedState=e,[t,e]},useMutableSource:function(){},useSyncExternalStore:function(e,t,n){var r=K,l=He();if(V){if(n===void 0)throw Error(_(407));n=n()}else{if(n=t(),re===null)throw Error(_(349));Ut&30||sc(r,t,n)}l.memoizedState=n;var s={value:n,getSnapshot:t};return l.queue=s,ta(oc.bind(null,r,s,e),[e]),r.flags|=2048,dr(9,ic.bind(null,r,s,n,t),void 0,null),n},useId:function(){var e=He(),t=re.identifierPrefix;if(V){var n=be,r=qe;n=(r&~(1<<32-Be(r)-1)).toString(32)+n,t=":"+t+"R"+n,n=ur++,0<\/script>",e=e.removeChild(e.firstChild)):typeof r.is=="string"?e=o.createElement(n,{is:r.is}):(e=o.createElement(n),n==="select"&&(o=e,r.multiple?o.multiple=!0:r.size&&(o.size=r.size))):e=o.createElementNS(e,n),e[Qe]=t,e[ir]=r,Mc(e,t,!1,!1),t.stateNode=e;e:{switch(o=zs(n,r),n){case"dialog":W("cancel",e),W("close",e),l=r;break;case"iframe":case"object":case"embed":W("load",e),l=r;break;case"video":case"audio":for(l=0;lxn&&(t.flags|=128,r=!0,Mn(s,!1),t.lanes=4194304)}else{if(!r)if(e=gl(o),e!==null){if(t.flags|=128,r=!0,n=e.updateQueue,n!==null&&(t.updateQueue=n,t.flags|=4),Mn(s,!0),s.tail===null&&s.tailMode==="hidden"&&!o.alternate&&!V)return ce(t),null}else 2*J()-s.renderingStartTime>xn&&n!==1073741824&&(t.flags|=128,r=!0,Mn(s,!1),t.lanes=4194304);s.isBackwards?(o.sibling=t.child,t.child=o):(n=s.last,n!==null?n.sibling=o:t.child=o,s.last=o)}return s.tail!==null?(t=s.tail,s.rendering=t,s.tail=t.sibling,s.renderingStartTime=J(),t.sibling=null,n=Q.current,U(Q,r?n&1|2:n&1),t):(ce(t),null);case 22:case 23:return to(),r=t.memoizedState!==null,e!==null&&e.memoizedState!==null!==r&&(t.flags|=8192),r&&t.mode&1?we&1073741824&&(ce(t),t.subtreeFlags&6&&(t.flags|=8192)):ce(t),null;case 24:return null;case 25:return null}throw Error(_(156,t.tag))}function yp(e,t){switch(Di(t),t.tag){case 1:return ke(t.type)&&cl(),e=t.flags,e&65536?(t.flags=e&-65537|128,t):null;case 3:return gn(),A(xe),A(fe),Hi(),e=t.flags,e&65536&&!(e&128)?(t.flags=e&-65537|128,t):null;case 5:return Vi(t),null;case 13:if(A(Q),e=t.memoizedState,e!==null&&e.dehydrated!==null){if(t.alternate===null)throw Error(_(340));mn()}return e=t.flags,e&65536?(t.flags=e&-65537|128,t):null;case 19:return A(Q),null;case 4:return gn(),null;case 10:return Bi(t.type._context),null;case 22:case 23:return to(),null;case 24:return null;default:return null}}var Fr=!1,de=!1,xp=typeof WeakSet=="function"?WeakSet:Set,P=null;function ln(e,t){var n=e.ref;if(n!==null)if(typeof n=="function")try{n(null)}catch(r){Y(e,t,r)}else n.current=null}function ri(e,t,n){try{n()}catch(r){Y(e,t,r)}}var fa=!1;function kp(e,t){if(Us=il,e=$u(),Ri(e)){if("selectionStart"in e)var n={start:e.selectionStart,end:e.selectionEnd};else e:{n=(n=e.ownerDocument)&&n.defaultView||window;var r=n.getSelection&&n.getSelection();if(r&&r.rangeCount!==0){n=r.anchorNode;var l=r.anchorOffset,s=r.focusNode;r=r.focusOffset;try{n.nodeType,s.nodeType}catch{n=null;break e}var o=0,a=-1,u=-1,c=0,h=0,p=e,v=null;t:for(;;){for(var g;p!==n||l!==0&&p.nodeType!==3||(a=o+l),p!==s||r!==0&&p.nodeType!==3||(u=o+r),p.nodeType===3&&(o+=p.nodeValue.length),(g=p.firstChild)!==null;)v=p,p=g;for(;;){if(p===e)break t;if(v===n&&++c===l&&(a=o),v===s&&++h===r&&(u=o),(g=p.nextSibling)!==null)break;p=v,v=p.parentNode}p=g}n=a===-1||u===-1?null:{start:a,end:u}}else n=null}n=n||{start:0,end:0}}else n=null;for(Ws={focusedElem:e,selectionRange:n},il=!1,P=t;P!==null;)if(t=P,e=t.child,(t.subtreeFlags&1028)!==0&&e!==null)e.return=t,P=e;else for(;P!==null;){t=P;try{var k=t.alternate;if(t.flags&1024)switch(t.tag){case 0:case 11:case 15:break;case 1:if(k!==null){var x=k.memoizedProps,S=k.memoizedState,m=t.stateNode,d=m.getSnapshotBeforeUpdate(t.elementType===t.type?x:Ie(t.type,x),S);m.__reactInternalSnapshotBeforeUpdate=d}break;case 3:var f=t.stateNode.containerInfo;f.nodeType===1?f.textContent="":f.nodeType===9&&f.documentElement&&f.removeChild(f.documentElement);break;case 5:case 6:case 4:case 17:break;default:throw Error(_(163))}}catch(y){Y(t,t.return,y)}if(e=t.sibling,e!==null){e.return=t.return,P=e;break}P=t.return}return k=fa,fa=!1,k}function Gn(e,t,n){var r=t.updateQueue;if(r=r!==null?r.lastEffect:null,r!==null){var l=r=r.next;do{if((l.tag&e)===e){var s=l.destroy;l.destroy=void 0,s!==void 0&&ri(t,n,s)}l=l.next}while(l!==r)}}function Il(e,t){if(t=t.updateQueue,t=t!==null?t.lastEffect:null,t!==null){var n=t=t.next;do{if((n.tag&e)===e){var r=n.create;n.destroy=r()}n=n.next}while(n!==t)}}function li(e){var t=e.ref;if(t!==null){var n=e.stateNode;switch(e.tag){case 5:e=n;break;default:e=n}typeof t=="function"?t(e):t.current=e}}function Oc(e){var t=e.alternate;t!==null&&(e.alternate=null,Oc(t)),e.child=null,e.deletions=null,e.sibling=null,e.tag===5&&(t=e.stateNode,t!==null&&(delete t[Qe],delete t[ir],delete t[Hs],delete t[np],delete t[rp])),e.stateNode=null,e.return=null,e.dependencies=null,e.memoizedProps=null,e.memoizedState=null,e.pendingProps=null,e.stateNode=null,e.updateQueue=null}function Dc(e){return e.tag===5||e.tag===3||e.tag===4}function pa(e){e:for(;;){for(;e.sibling===null;){if(e.return===null||Dc(e.return))return null;e=e.return}for(e.sibling.return=e.return,e=e.sibling;e.tag!==5&&e.tag!==6&&e.tag!==18;){if(e.flags&2||e.child===null||e.tag===4)continue e;e.child.return=e,e=e.child}if(!(e.flags&2))return e.stateNode}}function si(e,t,n){var r=e.tag;if(r===5||r===6)e=e.stateNode,t?n.nodeType===8?n.parentNode.insertBefore(e,t):n.insertBefore(e,t):(n.nodeType===8?(t=n.parentNode,t.insertBefore(e,n)):(t=n,t.appendChild(e)),n=n._reactRootContainer,n!=null||t.onclick!==null||(t.onclick=ul));else if(r!==4&&(e=e.child,e!==null))for(si(e,t,n),e=e.sibling;e!==null;)si(e,t,n),e=e.sibling}function ii(e,t,n){var r=e.tag;if(r===5||r===6)e=e.stateNode,t?n.insertBefore(e,t):n.appendChild(e);else if(r!==4&&(e=e.child,e!==null))for(ii(e,t,n),e=e.sibling;e!==null;)ii(e,t,n),e=e.sibling}var ie=null,Fe=!1;function ot(e,t,n){for(n=n.child;n!==null;)Ic(e,t,n),n=n.sibling}function Ic(e,t,n){if(Ke&&typeof Ke.onCommitFiberUnmount=="function")try{Ke.onCommitFiberUnmount(Ll,n)}catch{}switch(n.tag){case 5:de||ln(n,t);case 6:var r=ie,l=Fe;ie=null,ot(e,t,n),ie=r,Fe=l,ie!==null&&(Fe?(e=ie,n=n.stateNode,e.nodeType===8?e.parentNode.removeChild(n):e.removeChild(n)):ie.removeChild(n.stateNode));break;case 18:ie!==null&&(Fe?(e=ie,n=n.stateNode,e.nodeType===8?is(e.parentNode,n):e.nodeType===1&&is(e,n),tr(e)):is(ie,n.stateNode));break;case 4:r=ie,l=Fe,ie=n.stateNode.containerInfo,Fe=!0,ot(e,t,n),ie=r,Fe=l;break;case 0:case 11:case 14:case 15:if(!de&&(r=n.updateQueue,r!==null&&(r=r.lastEffect,r!==null))){l=r=r.next;do{var s=l,o=s.destroy;s=s.tag,o!==void 0&&(s&2||s&4)&&ri(n,t,o),l=l.next}while(l!==r)}ot(e,t,n);break;case 1:if(!de&&(ln(n,t),r=n.stateNode,typeof r.componentWillUnmount=="function"))try{r.props=n.memoizedProps,r.state=n.memoizedState,r.componentWillUnmount()}catch(a){Y(n,t,a)}ot(e,t,n);break;case 21:ot(e,t,n);break;case 22:n.mode&1?(de=(r=de)||n.memoizedState!==null,ot(e,t,n),de=r):ot(e,t,n);break;default:ot(e,t,n)}}function ha(e){var t=e.updateQueue;if(t!==null){e.updateQueue=null;var n=e.stateNode;n===null&&(n=e.stateNode=new xp),t.forEach(function(r){var l=Pp.bind(null,e,r);n.has(r)||(n.add(r),r.then(l,l))})}}function De(e,t){var n=t.deletions;if(n!==null)for(var r=0;rl&&(l=o),r&=~s}if(r=l,r=J()-r,r=(120>r?120:480>r?480:1080>r?1080:1920>r?1920:3e3>r?3e3:4320>r?4320:1960*wp(r/1960))-r,10e?16:e,pt===null)var r=!1;else{if(e=pt,pt=null,wl=0,F&6)throw Error(_(331));var l=F;for(F|=4,P=e.current;P!==null;){var s=P,o=s.child;if(P.flags&16){var a=s.deletions;if(a!==null){for(var u=0;uJ()-bi?It(e,0):qi|=n),je(e,t)}function Hc(e,t){t===0&&(e.mode&1?(t=Lr,Lr<<=1,!(Lr&130023424)&&(Lr=4194304)):t=1);var n=he();e=rt(e,t),e!==null&&(vr(e,t,n),je(e,n))}function Lp(e){var t=e.memoizedState,n=0;t!==null&&(n=t.retryLane),Hc(e,n)}function Pp(e,t){var n=0;switch(e.tag){case 13:var r=e.stateNode,l=e.memoizedState;l!==null&&(n=l.retryLane);break;case 19:r=e.stateNode;break;default:throw Error(_(314))}r!==null&&r.delete(t),Hc(e,n)}var Qc;Qc=function(e,t,n){if(e!==null)if(e.memoizedProps!==t.pendingProps||xe.current)ye=!0;else{if(!(e.lanes&n)&&!(t.flags&128))return ye=!1,vp(e,t,n);ye=!!(e.flags&131072)}else ye=!1,V&&t.flags&1048576&&Xu(t,pl,t.index);switch(t.lanes=0,t.tag){case 2:var r=t.type;Jr(e,t),e=t.pendingProps;var l=hn(t,fe.current);dn(t,n),l=Ki(null,t,r,e,l,n);var s=Gi();return t.flags|=1,typeof l=="object"&&l!==null&&typeof l.render=="function"&&l.$$typeof===void 0?(t.tag=1,t.memoizedState=null,t.updateQueue=null,ke(r)?(s=!0,dl(t)):s=!1,t.memoizedState=l.state!==null&&l.state!==void 0?l.state:null,Wi(t),l.updater=Dl,t.stateNode=l,l._reactInternals=t,Js(t,r,e,n),t=bs(null,t,r,!0,s,n)):(t.tag=0,V&&s&&Oi(t),pe(null,t,l,n),t=t.child),t;case 16:r=t.elementType;e:{switch(Jr(e,t),e=t.pendingProps,l=r._init,r=l(r._payload),t.type=r,l=t.tag=Mp(r),e=Ie(r,e),l){case 0:t=qs(null,t,r,e,n);break e;case 1:t=ua(null,t,r,e,n);break e;case 11:t=oa(null,t,r,e,n);break e;case 14:t=aa(null,t,r,Ie(r.type,e),n);break e}throw Error(_(306,r,""))}return t;case 0:return r=t.type,l=t.pendingProps,l=t.elementType===r?l:Ie(r,l),qs(e,t,r,l,n);case 1:return r=t.type,l=t.pendingProps,l=t.elementType===r?l:Ie(r,l),ua(e,t,r,l,n);case 3:e:{if(Lc(t),e===null)throw Error(_(387));r=t.pendingProps,s=t.memoizedState,l=s.element,tc(e,t),vl(t,r,null,n);var o=t.memoizedState;if(r=o.element,s.isDehydrated)if(s={element:r,isDehydrated:!1,cache:o.cache,pendingSuspenseBoundaries:o.pendingSuspenseBoundaries,transitions:o.transitions},t.updateQueue.baseState=s,t.memoizedState=s,t.flags&256){l=yn(Error(_(423)),t),t=ca(e,t,r,n,l);break e}else if(r!==l){l=yn(Error(_(424)),t),t=ca(e,t,r,n,l);break e}else for(Se=yt(t.stateNode.containerInfo.firstChild),Ne=t,V=!0,$e=null,n=bu(t,null,r,n),t.child=n;n;)n.flags=n.flags&-3|4096,n=n.sibling;else{if(mn(),r===l){t=lt(e,t,n);break e}pe(e,t,r,n)}t=t.child}return t;case 5:return nc(t),e===null&&Gs(t),r=t.type,l=t.pendingProps,s=e!==null?e.memoizedProps:null,o=l.children,As(r,l)?o=null:s!==null&&As(r,s)&&(t.flags|=32),Ec(e,t),pe(e,t,o,n),t.child;case 6:return e===null&&Gs(t),null;case 13:return Pc(e,t,n);case 4:return Ai(t,t.stateNode.containerInfo),r=t.pendingProps,e===null?t.child=vn(t,null,r,n):pe(e,t,r,n),t.child;case 11:return r=t.type,l=t.pendingProps,l=t.elementType===r?l:Ie(r,l),oa(e,t,r,l,n);case 7:return pe(e,t,t.pendingProps,n),t.child;case 8:return pe(e,t,t.pendingProps.children,n),t.child;case 12:return pe(e,t,t.pendingProps.children,n),t.child;case 10:e:{if(r=t.type._context,l=t.pendingProps,s=t.memoizedProps,o=l.value,U(hl,r._currentValue),r._currentValue=o,s!==null)if(We(s.value,o)){if(s.children===l.children&&!xe.current){t=lt(e,t,n);break e}}else for(s=t.child,s!==null&&(s.return=t);s!==null;){var a=s.dependencies;if(a!==null){o=s.child;for(var u=a.firstContext;u!==null;){if(u.context===r){if(s.tag===1){u=et(-1,n&-n),u.tag=2;var c=s.updateQueue;if(c!==null){c=c.shared;var h=c.pending;h===null?u.next=u:(u.next=h.next,h.next=u),c.pending=u}}s.lanes|=n,u=s.alternate,u!==null&&(u.lanes|=n),Ys(s.return,n,t),a.lanes|=n;break}u=u.next}}else if(s.tag===10)o=s.type===t.type?null:s.child;else if(s.tag===18){if(o=s.return,o===null)throw Error(_(341));o.lanes|=n,a=o.alternate,a!==null&&(a.lanes|=n),Ys(o,n,t),o=s.sibling}else o=s.child;if(o!==null)o.return=s;else for(o=s;o!==null;){if(o===t){o=null;break}if(s=o.sibling,s!==null){s.return=o.return,o=s;break}o=o.return}s=o}pe(e,t,l.children,n),t=t.child}return t;case 9:return l=t.type,r=t.pendingProps.children,dn(t,n),l=Te(l),r=r(l),t.flags|=1,pe(e,t,r,n),t.child;case 14:return r=t.type,l=Ie(r,t.pendingProps),l=Ie(r.type,l),aa(e,t,r,l,n);case 15:return Cc(e,t,t.type,t.pendingProps,n);case 17:return r=t.type,l=t.pendingProps,l=t.elementType===r?l:Ie(r,l),Jr(e,t),t.tag=1,ke(r)?(e=!0,dl(t)):e=!1,dn(t,n),wc(t,r,l),Js(t,r,l,n),bs(null,t,r,!0,e,n);case 19:return zc(e,t,n);case 22:return _c(e,t,n)}throw Error(_(156,t.tag))};function Kc(e,t){return xu(e,t)}function zp(e,t,n,r){this.tag=e,this.key=n,this.sibling=this.child=this.return=this.stateNode=this.type=this.elementType=null,this.index=0,this.ref=null,this.pendingProps=t,this.dependencies=this.memoizedState=this.updateQueue=this.memoizedProps=null,this.mode=r,this.subtreeFlags=this.flags=0,this.deletions=null,this.childLanes=this.lanes=0,this.alternate=null}function ze(e,t,n,r){return new zp(e,t,n,r)}function ro(e){return e=e.prototype,!(!e||!e.isReactComponent)}function Mp(e){if(typeof e=="function")return ro(e)?1:0;if(e!=null){if(e=e.$$typeof,e===wi)return 11;if(e===Si)return 14}return 2}function wt(e,t){var n=e.alternate;return n===null?(n=ze(e.tag,t,e.key,e.mode),n.elementType=e.elementType,n.type=e.type,n.stateNode=e.stateNode,n.alternate=e,e.alternate=n):(n.pendingProps=t,n.type=e.type,n.flags=0,n.subtreeFlags=0,n.deletions=null),n.flags=e.flags&14680064,n.childLanes=e.childLanes,n.lanes=e.lanes,n.child=e.child,n.memoizedProps=e.memoizedProps,n.memoizedState=e.memoizedState,n.updateQueue=e.updateQueue,t=e.dependencies,n.dependencies=t===null?null:{lanes:t.lanes,firstContext:t.firstContext},n.sibling=e.sibling,n.index=e.index,n.ref=e.ref,n}function br(e,t,n,r,l,s){var o=2;if(r=e,typeof e=="function")ro(e)&&(o=1);else if(typeof e=="string")o=5;else e:switch(e){case Xt:return Ft(n.children,l,s,t);case ji:o=8,l|=8;break;case ks:return e=ze(12,n,t,l|2),e.elementType=ks,e.lanes=s,e;case js:return e=ze(13,n,t,l),e.elementType=js,e.lanes=s,e;case ws:return e=ze(19,n,t,l),e.elementType=ws,e.lanes=s,e;case nu:return $l(n,l,s,t);default:if(typeof e=="object"&&e!==null)switch(e.$$typeof){case eu:o=10;break e;case tu:o=9;break e;case wi:o=11;break e;case Si:o=14;break e;case at:o=16,r=null;break e}throw Error(_(130,e==null?e:typeof e,""))}return t=ze(o,n,t,l),t.elementType=e,t.type=r,t.lanes=s,t}function Ft(e,t,n,r){return e=ze(7,e,r,t),e.lanes=n,e}function $l(e,t,n,r){return e=ze(22,e,r,t),e.elementType=nu,e.lanes=n,e.stateNode={isHidden:!1},e}function hs(e,t,n){return e=ze(6,e,null,t),e.lanes=n,e}function ms(e,t,n){return t=ze(4,e.children!==null?e.children:[],e.key,t),t.lanes=n,t.stateNode={containerInfo:e.containerInfo,pendingChildren:null,implementation:e.implementation},t}function Tp(e,t,n,r,l){this.tag=t,this.containerInfo=e,this.finishedWork=this.pingCache=this.current=this.pendingChildren=null,this.timeoutHandle=-1,this.callbackNode=this.pendingContext=this.context=null,this.callbackPriority=0,this.eventTimes=Xl(0),this.expirationTimes=Xl(-1),this.entangledLanes=this.finishedLanes=this.mutableReadLanes=this.expiredLanes=this.pingedLanes=this.suspendedLanes=this.pendingLanes=0,this.entanglements=Xl(0),this.identifierPrefix=r,this.onRecoverableError=l,this.mutableSourceEagerHydrationData=null}function lo(e,t,n,r,l,s,o,a,u){return e=new Tp(e,t,n,a,u),t===1?(t=1,s===!0&&(t|=8)):t=0,s=ze(3,null,null,t),e.current=s,s.stateNode=e,s.memoizedState={element:r,isDehydrated:n,cache:null,transitions:null,pendingSuspenseBoundaries:null},Wi(s),e}function Rp(e,t,n){var r=3"u"||typeof __REACT_DEVTOOLS_GLOBAL_HOOK__.checkDCE!="function"))try{__REACT_DEVTOOLS_GLOBAL_HOOK__.checkDCE(Jc)}catch(e){console.error(e)}}Jc(),Ja.exports=_e;var $p=Ja.exports,wa=$p;ys.createRoot=wa.createRoot,ys.hydrateRoot=wa.hydrateRoot;/** - * @remix-run/router v1.23.2 - * - * Copyright (c) Remix Software Inc. - * - * This source code is licensed under the MIT license found in the - * LICENSE.md file in the root directory of this source tree. - * - * @license MIT - */function pr(){return pr=Object.assign?Object.assign.bind():function(e){for(var t=1;t"u")throw new Error(t)}function ao(e,t){if(!e){typeof console<"u"&&console.warn(t);try{throw new Error(t)}catch{}}}function Up(){return Math.random().toString(36).substr(2,8)}function Na(e,t){return{usr:e.state,key:e.key,idx:t}}function di(e,t,n,r){return n===void 0&&(n=null),pr({pathname:typeof e=="string"?e:e.pathname,search:"",hash:""},typeof t=="string"?Sn(t):t,{state:n,key:t&&t.key||r||Up()})}function Cl(e){let{pathname:t="/",search:n="",hash:r=""}=e;return n&&n!=="?"&&(t+=n.charAt(0)==="?"?n:"?"+n),r&&r!=="#"&&(t+=r.charAt(0)==="#"?r:"#"+r),t}function Sn(e){let t={};if(e){let n=e.indexOf("#");n>=0&&(t.hash=e.substr(n),e=e.substr(0,n));let r=e.indexOf("?");r>=0&&(t.search=e.substr(r),e=e.substr(0,r)),e&&(t.pathname=e)}return t}function Wp(e,t,n,r){r===void 0&&(r={});let{window:l=document.defaultView,v5Compat:s=!1}=r,o=l.history,a=ht.Pop,u=null,c=h();c==null&&(c=0,o.replaceState(pr({},o.state,{idx:c}),""));function h(){return(o.state||{idx:null}).idx}function p(){a=ht.Pop;let S=h(),m=S==null?null:S-c;c=S,u&&u({action:a,location:x.location,delta:m})}function v(S,m){a=ht.Push;let d=di(x.location,S,m);c=h()+1;let f=Na(d,c),y=x.createHref(d);try{o.pushState(f,"",y)}catch(N){if(N instanceof DOMException&&N.name==="DataCloneError")throw N;l.location.assign(y)}s&&u&&u({action:a,location:x.location,delta:1})}function g(S,m){a=ht.Replace;let d=di(x.location,S,m);c=h();let f=Na(d,c),y=x.createHref(d);o.replaceState(f,"",y),s&&u&&u({action:a,location:x.location,delta:0})}function k(S){let m=l.location.origin!=="null"?l.location.origin:l.location.href,d=typeof S=="string"?S:Cl(S);return d=d.replace(/ $/,"%20"),Z(m,"No window.location.(origin|href) available to create URL for href: "+d),new URL(d,m)}let x={get action(){return a},get location(){return e(l,o)},listen(S){if(u)throw new Error("A history only accepts one active listener");return l.addEventListener(Sa,p),u=S,()=>{l.removeEventListener(Sa,p),u=null}},createHref(S){return t(l,S)},createURL:k,encodeLocation(S){let m=k(S);return{pathname:m.pathname,search:m.search,hash:m.hash}},push:v,replace:g,go(S){return o.go(S)}};return x}var Ca;(function(e){e.data="data",e.deferred="deferred",e.redirect="redirect",e.error="error"})(Ca||(Ca={}));function Ap(e,t,n){return n===void 0&&(n="/"),Vp(e,t,n)}function Vp(e,t,n,r){let l=typeof t=="string"?Sn(t):t,s=uo(l.pathname||"/",n);if(s==null)return null;let o=Zc(e);Hp(o);let a=null;for(let u=0;a==null&&u{let u={relativePath:a===void 0?s.path||"":a,caseSensitive:s.caseSensitive===!0,childrenIndex:o,route:s};u.relativePath.startsWith("/")&&(Z(u.relativePath.startsWith(r),'Absolute route path "'+u.relativePath+'" nested under path '+('"'+r+'" is not valid. An absolute child route path ')+"must start with the combined path of all its parent routes."),u.relativePath=u.relativePath.slice(r.length));let c=St([r,u.relativePath]),h=n.concat(u);s.children&&s.children.length>0&&(Z(s.index!==!0,"Index routes must not have child routes. Please remove "+('all child routes from route path "'+c+'".')),Zc(s.children,t,h,c)),!(s.path==null&&!s.index)&&t.push({path:c,score:Zp(c,s.index),routesMeta:h})};return e.forEach((s,o)=>{var a;if(s.path===""||!((a=s.path)!=null&&a.includes("?")))l(s,o);else for(let u of qc(s.path))l(s,o,u)}),t}function qc(e){let t=e.split("/");if(t.length===0)return[];let[n,...r]=t,l=n.endsWith("?"),s=n.replace(/\?$/,"");if(r.length===0)return l?[s,""]:[s];let o=qc(r.join("/")),a=[];return a.push(...o.map(u=>u===""?s:[s,u].join("/"))),l&&a.push(...o),a.map(u=>e.startsWith("/")&&u===""?"/":u)}function Hp(e){e.sort((t,n)=>t.score!==n.score?n.score-t.score:qp(t.routesMeta.map(r=>r.childrenIndex),n.routesMeta.map(r=>r.childrenIndex)))}const Qp=/^:[\w-]+$/,Kp=3,Gp=2,Yp=1,Xp=10,Jp=-2,_a=e=>e==="*";function Zp(e,t){let n=e.split("/"),r=n.length;return n.some(_a)&&(r+=Jp),t&&(r+=Gp),n.filter(l=>!_a(l)).reduce((l,s)=>l+(Qp.test(s)?Kp:s===""?Yp:Xp),r)}function qp(e,t){return e.length===t.length&&e.slice(0,-1).every((r,l)=>r===t[l])?e[e.length-1]-t[t.length-1]:0}function bp(e,t,n){let{routesMeta:r}=e,l={},s="/",o=[];for(let a=0;a{let{paramName:v,isOptional:g}=h;if(v==="*"){let x=a[p]||"";o=s.slice(0,s.length-x.length).replace(/(.)\/+$/,"$1")}const k=a[p];return g&&!k?c[v]=void 0:c[v]=(k||"").replace(/%2F/g,"/"),c},{}),pathname:s,pathnameBase:o,pattern:e}}function th(e,t,n){t===void 0&&(t=!1),n===void 0&&(n=!0),ao(e==="*"||!e.endsWith("*")||e.endsWith("/*"),'Route path "'+e+'" will be treated as if it were '+('"'+e.replace(/\*$/,"/*")+'" because the `*` character must ')+"always follow a `/` in the pattern. To get rid of this warning, "+('please change the route path to "'+e.replace(/\*$/,"/*")+'".'));let r=[],l="^"+e.replace(/\/*\*?$/,"").replace(/^\/*/,"/").replace(/[\\.*+^${}|()[\]]/g,"\\$&").replace(/\/:([\w-]+)(\?)?/g,(o,a,u)=>(r.push({paramName:a,isOptional:u!=null}),u?"/?([^\\/]+)?":"/([^\\/]+)"));return e.endsWith("*")?(r.push({paramName:"*"}),l+=e==="*"||e==="/*"?"(.*)$":"(?:\\/(.+)|\\/*)$"):n?l+="\\/*$":e!==""&&e!=="/"&&(l+="(?:(?=\\/|$))"),[new RegExp(l,t?void 0:"i"),r]}function nh(e){try{return e.split("/").map(t=>decodeURIComponent(t).replace(/\//g,"%2F")).join("/")}catch(t){return ao(!1,'The URL path "'+e+'" could not be decoded because it is is a malformed URL segment. This is probably due to a bad percent '+("encoding ("+t+").")),e}}function uo(e,t){if(t==="/")return e;if(!e.toLowerCase().startsWith(t.toLowerCase()))return null;let n=t.endsWith("/")?t.length-1:t.length,r=e.charAt(n);return r&&r!=="/"?null:e.slice(n)||"/"}const rh=/^(?:[a-z][a-z0-9+.-]*:|\/\/)/i,lh=e=>rh.test(e);function sh(e,t){t===void 0&&(t="/");let{pathname:n,search:r="",hash:l=""}=typeof e=="string"?Sn(e):e,s;if(n)if(lh(n))s=n;else{if(n.includes("//")){let o=n;n=n.replace(/\/\/+/g,"/"),ao(!1,"Pathnames cannot have embedded double slashes - normalizing "+(o+" -> "+n))}n.startsWith("/")?s=Ea(n.substring(1),"/"):s=Ea(n,t)}else s=t;return{pathname:s,search:ah(r),hash:uh(l)}}function Ea(e,t){let n=t.replace(/\/+$/,"").split("/");return e.split("/").forEach(l=>{l===".."?n.length>1&&n.pop():l!=="."&&n.push(l)}),n.length>1?n.join("/"):"/"}function vs(e,t,n,r){return"Cannot include a '"+e+"' character in a manually specified "+("`to."+t+"` field ["+JSON.stringify(r)+"]. Please separate it out to the ")+("`to."+n+"` field. Alternatively you may provide the full path as ")+'a string in and the router will parse it for you.'}function ih(e){return e.filter((t,n)=>n===0||t.route.path&&t.route.path.length>0)}function co(e,t){let n=ih(e);return t?n.map((r,l)=>l===n.length-1?r.pathname:r.pathnameBase):n.map(r=>r.pathnameBase)}function fo(e,t,n,r){r===void 0&&(r=!1);let l;typeof e=="string"?l=Sn(e):(l=pr({},e),Z(!l.pathname||!l.pathname.includes("?"),vs("?","pathname","search",l)),Z(!l.pathname||!l.pathname.includes("#"),vs("#","pathname","hash",l)),Z(!l.search||!l.search.includes("#"),vs("#","search","hash",l)));let s=e===""||l.pathname==="",o=s?"/":l.pathname,a;if(o==null)a=n;else{let p=t.length-1;if(!r&&o.startsWith("..")){let v=o.split("/");for(;v[0]==="..";)v.shift(),p-=1;l.pathname=v.join("/")}a=p>=0?t[p]:"/"}let u=sh(l,a),c=o&&o!=="/"&&o.endsWith("/"),h=(s||o===".")&&n.endsWith("/");return!u.pathname.endsWith("/")&&(c||h)&&(u.pathname+="/"),u}const St=e=>e.join("/").replace(/\/\/+/g,"/"),oh=e=>e.replace(/\/+$/,"").replace(/^\/*/,"/"),ah=e=>!e||e==="?"?"":e.startsWith("?")?e:"?"+e,uh=e=>!e||e==="#"?"":e.startsWith("#")?e:"#"+e;function ch(e){return e!=null&&typeof e.status=="number"&&typeof e.statusText=="string"&&typeof e.internal=="boolean"&&"data"in e}const bc=["post","put","patch","delete"];new Set(bc);const dh=["get",...bc];new Set(dh);/** - * React Router v6.30.3 - * - * Copyright (c) Remix Software Inc. - * - * This source code is licensed under the MIT license found in the - * LICENSE.md file in the root directory of this source tree. - * - * @license MIT - */function hr(){return hr=Object.assign?Object.assign.bind():function(e){for(var t=1;t{a.current=!0}),j.useCallback(function(c,h){if(h===void 0&&(h={}),!a.current)return;if(typeof c=="number"){r.go(c);return}let p=fo(c,JSON.parse(o),s,h.relative==="path");e==null&&t!=="/"&&(p.pathname=p.pathname==="/"?t:St([t,p.pathname])),(h.replace?r.replace:r.push)(p,h.state,h)},[t,r,o,s,e])}function nd(e,t){let{relative:n}=t===void 0?{}:t,{future:r}=j.useContext(Pt),{matches:l}=j.useContext(zt),{pathname:s}=kr(),o=JSON.stringify(co(l,r.v7_relativeSplatPath));return j.useMemo(()=>fo(e,JSON.parse(o),s,n==="path"),[e,o,s,n])}function mh(e,t){return vh(e,t)}function vh(e,t,n,r){Nn()||Z(!1);let{navigator:l}=j.useContext(Pt),{matches:s}=j.useContext(zt),o=s[s.length-1],a=o?o.params:{};o&&o.pathname;let u=o?o.pathnameBase:"/";o&&o.route;let c=kr(),h;if(t){var p;let S=typeof t=="string"?Sn(t):t;u==="/"||(p=S.pathname)!=null&&p.startsWith(u)||Z(!1),h=S}else h=c;let v=h.pathname||"/",g=v;if(u!=="/"){let S=u.replace(/^\//,"").split("/");g="/"+v.replace(/^\//,"").split("/").slice(S.length).join("/")}let k=Ap(e,{pathname:g}),x=jh(k&&k.map(S=>Object.assign({},S,{params:Object.assign({},a,S.params),pathname:St([u,l.encodeLocation?l.encodeLocation(S.pathname).pathname:S.pathname]),pathnameBase:S.pathnameBase==="/"?u:St([u,l.encodeLocation?l.encodeLocation(S.pathnameBase).pathname:S.pathnameBase])})),s,n,r);return t&&x?j.createElement(Vl.Provider,{value:{location:hr({pathname:"/",search:"",hash:"",state:null,key:"default"},h),navigationType:ht.Pop}},x):x}function gh(){let e=Ch(),t=ch(e)?e.status+" "+e.statusText:e instanceof Error?e.message:JSON.stringify(e),n=e instanceof Error?e.stack:null,l={padding:"0.5rem",backgroundColor:"rgba(200,200,200, 0.5)"};return j.createElement(j.Fragment,null,j.createElement("h2",null,"Unexpected Application Error!"),j.createElement("h3",{style:{fontStyle:"italic"}},t),n?j.createElement("pre",{style:l},n):null,null)}const yh=j.createElement(gh,null);class xh extends j.Component{constructor(t){super(t),this.state={location:t.location,revalidation:t.revalidation,error:t.error}}static getDerivedStateFromError(t){return{error:t}}static getDerivedStateFromProps(t,n){return n.location!==t.location||n.revalidation!=="idle"&&t.revalidation==="idle"?{error:t.error,location:t.location,revalidation:t.revalidation}:{error:t.error!==void 0?t.error:n.error,location:n.location,revalidation:t.revalidation||n.revalidation}}componentDidCatch(t,n){console.error("React Router caught the following error during render",t,n)}render(){return this.state.error!==void 0?j.createElement(zt.Provider,{value:this.props.routeContext},j.createElement(ed.Provider,{value:this.state.error,children:this.props.component})):this.props.children}}function kh(e){let{routeContext:t,match:n,children:r}=e,l=j.useContext(po);return l&&l.static&&l.staticContext&&(n.route.errorElement||n.route.ErrorBoundary)&&(l.staticContext._deepestRenderedBoundaryId=n.route.id),j.createElement(zt.Provider,{value:t},r)}function jh(e,t,n,r){var l;if(t===void 0&&(t=[]),n===void 0&&(n=null),r===void 0&&(r=null),e==null){var s;if(!n)return null;if(n.errors)e=n.matches;else if((s=r)!=null&&s.v7_partialHydration&&t.length===0&&!n.initialized&&n.matches.length>0)e=n.matches;else return null}let o=e,a=(l=n)==null?void 0:l.errors;if(a!=null){let h=o.findIndex(p=>p.route.id&&(a==null?void 0:a[p.route.id])!==void 0);h>=0||Z(!1),o=o.slice(0,Math.min(o.length,h+1))}let u=!1,c=-1;if(n&&r&&r.v7_partialHydration)for(let h=0;h=0?o=o.slice(0,c+1):o=[o[0]];break}}}return o.reduceRight((h,p,v)=>{let g,k=!1,x=null,S=null;n&&(g=a&&p.route.id?a[p.route.id]:void 0,x=p.route.errorElement||yh,u&&(c<0&&v===0?(Eh("route-fallback"),k=!0,S=null):c===v&&(k=!0,S=p.route.hydrateFallbackElement||null)));let m=t.concat(o.slice(0,v+1)),d=()=>{let f;return g?f=x:k?f=S:p.route.Component?f=j.createElement(p.route.Component,null):p.route.element?f=p.route.element:f=h,j.createElement(kh,{match:p,routeContext:{outlet:h,matches:m,isDataRoute:n!=null},children:f})};return n&&(p.route.ErrorBoundary||p.route.errorElement||v===0)?j.createElement(xh,{location:n.location,revalidation:n.revalidation,component:x,error:g,children:d(),routeContext:{outlet:null,matches:m,isDataRoute:!0}}):d()},null)}var rd=function(e){return e.UseBlocker="useBlocker",e.UseRevalidator="useRevalidator",e.UseNavigateStable="useNavigate",e}(rd||{}),ld=function(e){return e.UseBlocker="useBlocker",e.UseLoaderData="useLoaderData",e.UseActionData="useActionData",e.UseRouteError="useRouteError",e.UseNavigation="useNavigation",e.UseRouteLoaderData="useRouteLoaderData",e.UseMatches="useMatches",e.UseRevalidator="useRevalidator",e.UseNavigateStable="useNavigate",e.UseRouteId="useRouteId",e}(ld||{});function wh(e){let t=j.useContext(po);return t||Z(!1),t}function Sh(e){let t=j.useContext(fh);return t||Z(!1),t}function Nh(e){let t=j.useContext(zt);return t||Z(!1),t}function sd(e){let t=Nh(),n=t.matches[t.matches.length-1];return n.route.id||Z(!1),n.route.id}function Ch(){var e;let t=j.useContext(ed),n=Sh(),r=sd();return t!==void 0?t:(e=n.errors)==null?void 0:e[r]}function _h(){let{router:e}=wh(rd.UseNavigateStable),t=sd(ld.UseNavigateStable),n=j.useRef(!1);return td(()=>{n.current=!0}),j.useCallback(function(l,s){s===void 0&&(s={}),n.current&&(typeof l=="number"?e.navigate(l):e.navigate(l,hr({fromRouteId:t},s)))},[e,t])}const La={};function Eh(e,t,n){La[e]||(La[e]=!0)}function Lh(e,t){e==null||e.v7_startTransition,e==null||e.v7_relativeSplatPath}function _l(e){let{to:t,replace:n,state:r,relative:l}=e;Nn()||Z(!1);let{future:s,static:o}=j.useContext(Pt),{matches:a}=j.useContext(zt),{pathname:u}=kr(),c=jr(),h=fo(t,co(a,s.v7_relativeSplatPath),u,l==="path"),p=JSON.stringify(h);return j.useEffect(()=>c(JSON.parse(p),{replace:n,state:r,relative:l}),[c,p,l,n,r]),null}function Un(e){Z(!1)}function Ph(e){let{basename:t="/",children:n=null,location:r,navigationType:l=ht.Pop,navigator:s,static:o=!1,future:a}=e;Nn()&&Z(!1);let u=t.replace(/^\/*/,"/"),c=j.useMemo(()=>({basename:u,navigator:s,static:o,future:hr({v7_relativeSplatPath:!1},a)}),[u,a,s,o]);typeof r=="string"&&(r=Sn(r));let{pathname:h="/",search:p="",hash:v="",state:g=null,key:k="default"}=r,x=j.useMemo(()=>{let S=uo(h,u);return S==null?null:{location:{pathname:S,search:p,hash:v,state:g,key:k},navigationType:l}},[u,h,p,v,g,k,l]);return x==null?null:j.createElement(Pt.Provider,{value:c},j.createElement(Vl.Provider,{children:n,value:x}))}function zh(e){let{children:t,location:n}=e;return mh(fi(t),n)}new Promise(()=>{});function fi(e,t){t===void 0&&(t=[]);let n=[];return j.Children.forEach(e,(r,l)=>{if(!j.isValidElement(r))return;let s=[...t,l];if(r.type===j.Fragment){n.push.apply(n,fi(r.props.children,s));return}r.type!==Un&&Z(!1),!r.props.index||!r.props.children||Z(!1);let o={id:r.props.id||s.join("-"),caseSensitive:r.props.caseSensitive,element:r.props.element,Component:r.props.Component,index:r.props.index,path:r.props.path,loader:r.props.loader,action:r.props.action,errorElement:r.props.errorElement,ErrorBoundary:r.props.ErrorBoundary,hasErrorBoundary:r.props.ErrorBoundary!=null||r.props.errorElement!=null,shouldRevalidate:r.props.shouldRevalidate,handle:r.props.handle,lazy:r.props.lazy};r.props.children&&(o.children=fi(r.props.children,s)),n.push(o)}),n}/** - * React Router DOM v6.30.3 - * - * Copyright (c) Remix Software Inc. - * - * This source code is licensed under the MIT license found in the - * LICENSE.md file in the root directory of this source tree. - * - * @license MIT - */function pi(){return pi=Object.assign?Object.assign.bind():function(e){for(var t=1;t=0)&&(n[l]=e[l]);return n}function Th(e){return!!(e.metaKey||e.altKey||e.ctrlKey||e.shiftKey)}function Rh(e,t){return e.button===0&&(!t||t==="_self")&&!Th(e)}const Oh=["onClick","relative","reloadDocument","replace","state","target","to","preventScrollReset","viewTransition"],Dh="6";try{window.__reactRouterVersion=Dh}catch{}const Ih="startTransition",Pa=_d[Ih];function Fh(e){let{basename:t,children:n,future:r,window:l}=e,s=j.useRef();s.current==null&&(s.current=Bp({window:l,v5Compat:!0}));let o=s.current,[a,u]=j.useState({action:o.action,location:o.location}),{v7_startTransition:c}=r||{},h=j.useCallback(p=>{c&&Pa?Pa(()=>u(p)):u(p)},[u,c]);return j.useLayoutEffect(()=>o.listen(h),[o,h]),j.useEffect(()=>Lh(r),[r]),j.createElement(Ph,{basename:t,children:n,location:a.location,navigationType:a.action,navigator:o,future:r})}const $h=typeof window<"u"&&typeof window.document<"u"&&typeof window.document.createElement<"u",Bh=/^(?:[a-z][a-z0-9+.-]*:|\/\/)/i,id=j.forwardRef(function(t,n){let{onClick:r,relative:l,reloadDocument:s,replace:o,state:a,target:u,to:c,preventScrollReset:h,viewTransition:p}=t,v=Mh(t,Oh),{basename:g}=j.useContext(Pt),k,x=!1;if(typeof c=="string"&&Bh.test(c)&&(k=c,$h))try{let f=new URL(window.location.href),y=c.startsWith("//")?new URL(f.protocol+c):new URL(c),N=uo(y.pathname,g);y.origin===f.origin&&N!=null?c=N+y.search+y.hash:x=!0}catch{}let S=ph(c,{relative:l}),m=Uh(c,{replace:o,state:a,target:u,preventScrollReset:h,relative:l,viewTransition:p});function d(f){r&&r(f),f.defaultPrevented||m(f)}return j.createElement("a",pi({},v,{href:k||S,onClick:x||s?r:d,ref:n,target:u}))});var za;(function(e){e.UseScrollRestoration="useScrollRestoration",e.UseSubmit="useSubmit",e.UseSubmitFetcher="useSubmitFetcher",e.UseFetcher="useFetcher",e.useViewTransitionState="useViewTransitionState"})(za||(za={}));var Ma;(function(e){e.UseFetcher="useFetcher",e.UseFetchers="useFetchers",e.UseScrollRestoration="useScrollRestoration"})(Ma||(Ma={}));function Uh(e,t){let{target:n,replace:r,state:l,preventScrollReset:s,relative:o,viewTransition:a}=t===void 0?{}:t,u=jr(),c=kr(),h=nd(e,{relative:o});return j.useCallback(p=>{if(Rh(p,n)){p.preventDefault();let v=r!==void 0?r:Cl(c)===Cl(h);u(e,{replace:v,state:l,preventScrollReset:s,relative:o,viewTransition:a})}},[c,u,h,r,l,n,e,s,o,a])}const Ur="/api",od=j.createContext(null);function Wh({children:e}){const[t,n]=j.useState(null),[r,l]=j.useState(localStorage.getItem("token")),[s,o]=j.useState(!0);j.useEffect(()=>{r?a():o(!1)},[r]);const a=async()=>{try{const g=await fetch(`${Ur}/user/profile`,{headers:{Authorization:`Bearer ${r}`}});g.ok?n(await g.json()):p()}catch{p()}o(!1)},u=async(g,k)=>{const x=await fetch(`${Ur}/auth/register`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({email:g,password:k})}),S=await x.json();if(!x.ok)throw new Error(S.error);return localStorage.setItem("token",S.token),l(S.token),n(S.user),S},c=async(g,k)=>{const x=await fetch(`${Ur}/auth/login`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({email:g,password:k})}),S=await x.json();if(!x.ok)throw new Error(S.error);return localStorage.setItem("token",S.token),l(S.token),n(S.user),S},h=async g=>{const k=await fetch(`${Ur}/user/profile`,{method:"PUT",headers:{"Content-Type":"application/json",Authorization:`Bearer ${r}`},body:JSON.stringify(g)}),x=await k.json();if(!k.ok)throw new Error(x.error);return n(x),x},p=()=>{localStorage.removeItem("token"),l(null),n(null)},v=()=>a();return i.jsx(od.Provider,{value:{user:t,token:r,loading:s,register:u,login:c,logout:p,updateProfile:h,refreshProfile:v},children:e})}const it=()=>j.useContext(od),Ah={home:i.jsxs("svg",{viewBox:"0 0 24 24",fill:"none",stroke:"currentColor",strokeWidth:"2",strokeLinecap:"round",strokeLinejoin:"round",children:[i.jsx("path",{d:"M3 9l9-7 9 7v11a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2z"}),i.jsx("polyline",{points:"9 22 9 12 15 12 15 22"})]}),chart:i.jsxs("svg",{viewBox:"0 0 24 24",fill:"none",stroke:"currentColor",strokeWidth:"2",strokeLinecap:"round",strokeLinejoin:"round",children:[i.jsx("line",{x1:"18",y1:"20",x2:"18",y2:"10"}),i.jsx("line",{x1:"12",y1:"20",x2:"12",y2:"4"}),i.jsx("line",{x1:"6",y1:"20",x2:"6",y2:"14"})]}),user:i.jsxs("svg",{viewBox:"0 0 24 24",fill:"none",stroke:"currentColor",strokeWidth:"2",strokeLinecap:"round",strokeLinejoin:"round",children:[i.jsx("path",{d:"M20 21v-2a4 4 0 0 0-4-4H8a4 4 0 0 0-4 4v2"}),i.jsx("circle",{cx:"12",cy:"7",r:"4"})]}),logout:i.jsxs("svg",{viewBox:"0 0 24 24",fill:"none",stroke:"currentColor",strokeWidth:"2",strokeLinecap:"round",strokeLinejoin:"round",children:[i.jsx("path",{d:"M9 21H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h4"}),i.jsx("polyline",{points:"16 17 21 12 16 7"}),i.jsx("line",{x1:"21",y1:"12",x2:"9",y2:"12"})]}),arrowLeft:i.jsxs("svg",{viewBox:"0 0 24 24",fill:"none",stroke:"currentColor",strokeWidth:"2",strokeLinecap:"round",strokeLinejoin:"round",children:[i.jsx("line",{x1:"19",y1:"12",x2:"5",y2:"12"}),i.jsx("polyline",{points:"12 19 5 12 12 5"})]}),arrowRight:i.jsxs("svg",{viewBox:"0 0 24 24",fill:"none",stroke:"currentColor",strokeWidth:"2",strokeLinecap:"round",strokeLinejoin:"round",children:[i.jsx("line",{x1:"5",y1:"12",x2:"19",y2:"12"}),i.jsx("polyline",{points:"12 5 19 12 12 19"})]}),chevronLeft:i.jsx("svg",{viewBox:"0 0 24 24",fill:"none",stroke:"currentColor",strokeWidth:"2",strokeLinecap:"round",strokeLinejoin:"round",children:i.jsx("polyline",{points:"15 18 9 12 15 6"})}),chevronRight:i.jsx("svg",{viewBox:"0 0 24 24",fill:"none",stroke:"currentColor",strokeWidth:"2",strokeLinecap:"round",strokeLinejoin:"round",children:i.jsx("polyline",{points:"9 18 15 12 9 6"})}),chevronDown:i.jsx("svg",{viewBox:"0 0 24 24",fill:"none",stroke:"currentColor",strokeWidth:"2",strokeLinecap:"round",strokeLinejoin:"round",children:i.jsx("polyline",{points:"6 9 12 15 18 9"})}),plus:i.jsxs("svg",{viewBox:"0 0 24 24",fill:"none",stroke:"currentColor",strokeWidth:"2",strokeLinecap:"round",strokeLinejoin:"round",children:[i.jsx("line",{x1:"12",y1:"5",x2:"12",y2:"19"}),i.jsx("line",{x1:"5",y1:"12",x2:"19",y2:"12"})]}),swap:i.jsxs("svg",{viewBox:"0 0 24 24",fill:"none",stroke:"currentColor",strokeWidth:"2",strokeLinecap:"round",strokeLinejoin:"round",children:[i.jsx("polyline",{points:"7 7 3 11 7 15"}),i.jsx("polyline",{points:"17 9 21 13 17 17"}),i.jsx("line",{x1:"3",y1:"11",x2:"21",y2:"11"}),i.jsx("line",{x1:"3",y1:"13",x2:"21",y2:"13"})]}),check:i.jsx("svg",{viewBox:"0 0 24 24",fill:"none",stroke:"currentColor",strokeWidth:"2.5",strokeLinecap:"round",strokeLinejoin:"round",children:i.jsx("polyline",{points:"20 6 9 17 4 12"})}),dumbbell:i.jsxs("svg",{viewBox:"0 0 24 24",fill:"none",stroke:"currentColor",strokeWidth:"2",strokeLinecap:"round",strokeLinejoin:"round",children:[i.jsx("path",{d:"M6.5 6.5a2 2 0 0 0-3 0L2 8l6 6 1.5-1.5a2 2 0 0 0 0-3L6.5 6.5z"}),i.jsx("path",{d:"M17.5 17.5a2 2 0 0 0 3 0L22 16l-6-6-1.5 1.5a2 2 0 0 0 0 3l3 3z"}),i.jsx("line",{x1:"8",y1:"8",x2:"16",y2:"16"})]}),arm:i.jsxs("svg",{viewBox:"0 0 24 24",fill:"none",stroke:"currentColor",strokeWidth:"2",strokeLinecap:"round",strokeLinejoin:"round",children:[i.jsx("path",{d:"M12 3c-1.5 0-2.5 1-3 2.5-.5 1.5-.5 3 0 4.5.5 1.5 1 3 1 4.5 0 1.5-.5 3-1.5 4.5"}),i.jsx("path",{d:"M8 8c0-1 .5-2 1.5-2.5"}),i.jsx("path",{d:"M16 8c0-1-.5-2-1.5-2.5"}),i.jsx("path",{d:"M9 14c1.5 0 3 .5 4.5 1.5s3 1.5 4.5 1.5"})]}),leg:i.jsxs("svg",{viewBox:"0 0 24 24",fill:"none",stroke:"currentColor",strokeWidth:"2",strokeLinecap:"round",strokeLinejoin:"round",children:[i.jsx("path",{d:"M12 3v7"}),i.jsx("path",{d:"M9 10c0 2-.5 4-1.5 5.5S5 18 5 20v1"}),i.jsx("path",{d:"M15 10c0 2 .5 4 1.5 5.5S19 18 19 20v1"}),i.jsx("ellipse",{cx:"12",cy:"4",rx:"3",ry:"1"})]}),back:i.jsxs("svg",{viewBox:"0 0 24 24",fill:"none",stroke:"currentColor",strokeWidth:"2",strokeLinecap:"round",strokeLinejoin:"round",children:[i.jsx("path",{d:"M12 2v6"}),i.jsx("path",{d:"M6 8c0 3 2 6 6 8 4-2 6-5 6-8"}),i.jsx("path",{d:"M6 8c0-2 2.5-4 6-4s6 2 6 4"}),i.jsx("path",{d:"M9 16v4a2 2 0 0 0 2 2h2a2 2 0 0 0 2-2v-4"})]}),chest:i.jsxs("svg",{viewBox:"0 0 24 24",fill:"none",stroke:"currentColor",strokeWidth:"2",strokeLinecap:"round",strokeLinejoin:"round",children:[i.jsx("path",{d:"M6 8c0-3 2.5-5 6-5s6 2 6 5c0 4-3 8-6 10-3-2-6-6-6-10z"}),i.jsx("path",{d:"M12 8v5"}),i.jsx("path",{d:"M9 9.5h6"})]}),shoulder:i.jsxs("svg",{viewBox:"0 0 24 24",fill:"none",stroke:"currentColor",strokeWidth:"2",strokeLinecap:"round",strokeLinejoin:"round",children:[i.jsx("circle",{cx:"12",cy:"6",r:"3"}),i.jsx("path",{d:"M5 17c0-4 3-7 7-7s7 3 7 7"}),i.jsx("path",{d:"M8 12l-3 5"}),i.jsx("path",{d:"M16 12l3 5"})]}),core:i.jsxs("svg",{viewBox:"0 0 24 24",fill:"none",stroke:"currentColor",strokeWidth:"2",strokeLinecap:"round",strokeLinejoin:"round",children:[i.jsx("rect",{x:"6",y:"4",width:"12",height:"16",rx:"2"}),i.jsx("line",{x1:"12",y1:"8",x2:"12",y2:"16"}),i.jsx("line",{x1:"9",y1:"10",x2:"15",y2:"10"}),i.jsx("line",{x1:"9",y1:"14",x2:"15",y2:"14"})]}),fullBody:i.jsxs("svg",{viewBox:"0 0 24 24",fill:"none",stroke:"currentColor",strokeWidth:"2",strokeLinecap:"round",strokeLinejoin:"round",children:[i.jsx("circle",{cx:"12",cy:"4",r:"2"}),i.jsx("path",{d:"M12 6v5"}),i.jsx("path",{d:"M8 8l-3 3"}),i.jsx("path",{d:"M16 8l3 3"}),i.jsx("path",{d:"M12 11l-3 9"}),i.jsx("path",{d:"M12 11l3 9"})]}),walking:i.jsxs("svg",{viewBox:"0 0 24 24",fill:"none",stroke:"currentColor",strokeWidth:"2",strokeLinecap:"round",strokeLinejoin:"round",children:[i.jsx("circle",{cx:"12",cy:"4",r:"2"}),i.jsx("path",{d:"M14 10l2 7-3 3"}),i.jsx("path",{d:"M10 10l-2 7 3 3"}),i.jsx("path",{d:"M10 10h4l2-2"}),i.jsx("path",{d:"M10 10l-2-2"})]}),yoga:i.jsxs("svg",{viewBox:"0 0 24 24",fill:"none",stroke:"currentColor",strokeWidth:"2",strokeLinecap:"round",strokeLinejoin:"round",children:[i.jsx("circle",{cx:"12",cy:"4",r:"2"}),i.jsx("path",{d:"M12 6v5"}),i.jsx("path",{d:"M4 14l8-3 8 3"}),i.jsx("path",{d:"M9 20l3-9 3 9"})]}),swimming:i.jsxs("svg",{viewBox:"0 0 24 24",fill:"none",stroke:"currentColor",strokeWidth:"2",strokeLinecap:"round",strokeLinejoin:"round",children:[i.jsx("circle",{cx:"8",cy:"6",r:"2"}),i.jsx("path",{d:"M10 6h8"}),i.jsx("path",{d:"M4 18c2-2 4-2 6 0s4 2 6 0 4-2 6 0"}),i.jsx("path",{d:"M4 14c2-2 4-2 6 0s4 2 6 0 4-2 6 0"})]}),cycling:i.jsxs("svg",{viewBox:"0 0 24 24",fill:"none",stroke:"currentColor",strokeWidth:"2",strokeLinecap:"round",strokeLinejoin:"round",children:[i.jsx("circle",{cx:"6",cy:"17",r:"3"}),i.jsx("circle",{cx:"18",cy:"17",r:"3"}),i.jsx("path",{d:"M6 17l4-7h4l3 5"}),i.jsx("circle",{cx:"12",cy:"7",r:"2"})]}),sleep:i.jsx("svg",{viewBox:"0 0 24 24",fill:"none",stroke:"currentColor",strokeWidth:"2",strokeLinecap:"round",strokeLinejoin:"round",children:i.jsx("path",{d:"M21 12.79A9 9 0 1 1 11.21 3 7 7 0 0 0 21 12.79z"})}),fire:i.jsxs("svg",{viewBox:"0 0 24 24",fill:"none",stroke:"currentColor",strokeWidth:"2",strokeLinecap:"round",strokeLinejoin:"round",children:[i.jsx("path",{d:"M12 22c4-2 7-6 7-10 0-3-2-5-4-7l-3 4-3-4c-2 2-4 4-4 7 0 4 3 8 7 10z"}),i.jsx("path",{d:"M12 22c-2-1-3-3-3-5 0-1.5.5-2.5 1.5-3.5l1.5 2 1.5-2c1 1 1.5 2 1.5 3.5 0 2-1 4-3 5z"})]}),calendar:i.jsxs("svg",{viewBox:"0 0 24 24",fill:"none",stroke:"currentColor",strokeWidth:"2",strokeLinecap:"round",strokeLinejoin:"round",children:[i.jsx("rect",{x:"3",y:"4",width:"18",height:"18",rx:"2",ry:"2"}),i.jsx("line",{x1:"16",y1:"2",x2:"16",y2:"6"}),i.jsx("line",{x1:"8",y1:"2",x2:"8",y2:"6"}),i.jsx("line",{x1:"3",y1:"10",x2:"21",y2:"10"})]}),target:i.jsxs("svg",{viewBox:"0 0 24 24",fill:"none",stroke:"currentColor",strokeWidth:"2",strokeLinecap:"round",strokeLinejoin:"round",children:[i.jsx("circle",{cx:"12",cy:"12",r:"10"}),i.jsx("circle",{cx:"12",cy:"12",r:"6"}),i.jsx("circle",{cx:"12",cy:"12",r:"2"})]}),trophy:i.jsxs("svg",{viewBox:"0 0 24 24",fill:"none",stroke:"currentColor",strokeWidth:"2",strokeLinecap:"round",strokeLinejoin:"round",children:[i.jsx("path",{d:"M6 9H4a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h2"}),i.jsx("path",{d:"M18 9h2a2 2 0 0 0 2-2V5a2 2 0 0 0-2-2h-2"}),i.jsx("path",{d:"M6 3v6a6 6 0 0 0 12 0V3"}),i.jsx("path",{d:"M12 15v4"}),i.jsx("path",{d:"M8 21h8"})]}),coach:i.jsxs("svg",{viewBox:"0 0 24 24",fill:"currentColor",children:[i.jsx("circle",{cx:"12",cy:"7",r:"4",opacity:"0.9"}),i.jsx("path",{d:"M12 13c-5 0-8 2.5-8 5v2h16v-2c0-2.5-3-5-8-5z",opacity:"0.7"}),i.jsx("circle",{cx:"17",cy:"5",r:"1.5",opacity:"0.5"}),i.jsx("path",{d:"M18 4l1.5-1.5",stroke:"currentColor",strokeWidth:"1.5",strokeLinecap:"round",opacity:"0.5"})]}),trash:i.jsxs("svg",{viewBox:"0 0 24 24",fill:"none",stroke:"currentColor",strokeWidth:"2",strokeLinecap:"round",strokeLinejoin:"round",children:[i.jsx("polyline",{points:"3 6 5 6 21 6"}),i.jsx("path",{d:"M19 6l-1 14a2 2 0 0 1-2 2H8a2 2 0 0 1-2-2L5 6"}),i.jsx("path",{d:"M10 11v6"}),i.jsx("path",{d:"M14 11v6"}),i.jsx("path",{d:"M9 6V4a1 1 0 0 1 1-1h4a1 1 0 0 1 1 1v2"})]}),gravl:i.jsxs("svg",{viewBox:"0 0 24 24",fill:"none",stroke:"currentColor",strokeWidth:"2",strokeLinecap:"round",strokeLinejoin:"round",children:[i.jsx("path",{d:"M6.5 6.5a2 2 0 0 0-3 0L2 8l6 6 1.5-1.5a2 2 0 0 0 0-3L6.5 6.5z"}),i.jsx("path",{d:"M17.5 17.5a2 2 0 0 0 3 0L22 16l-6-6-1.5 1.5a2 2 0 0 0 0 3l3 3z"}),i.jsx("line",{x1:"8",y1:"8",x2:"16",y2:"16"})]})};function B({name:e,size:t=24,className:n="",style:r={}}){const l=Ah[e];return l?i.jsx("span",{className:`icon ${n}`,style:{display:"inline-flex",alignItems:"center",justifyContent:"center",width:t,height:t,...r},children:l}):null}function Vh(e){const t=e.toLowerCase();return t.includes("push")||t.includes("bröst")||t.includes("chest")?"chest":t.includes("pull")||t.includes("rygg")||t.includes("back")?"back":t.includes("ben")||t.includes("leg")||t.includes("lower")?"leg":t.includes("axlar")||t.includes("shoulder")?"shoulder":t.includes("arm")?"arm":t.includes("core")||t.includes("mage")?"core":t.includes("helkropp")||t.includes("full")?"fullBody":t.includes("överkropp")||t.includes("upper")?"chest":t.includes("underkropp")?"leg":"dumbbell"}const Hh="/api",Qh=(e,t)=>{var l;const n=new Date().getHours(),r=((l=e==null?void 0:e.name)==null?void 0:l.split(" ")[0])||"du";return t?n<10?`Godmorgon ${r}! Idag kör vi ${t.name.toLowerCase()}. Redo?`:n<14?`${t.name} står på schemat idag. Dags att köra!`:n<18?`Eftermiddagspass? ${t.name} väntar på dig.`:`Kvällspass ${r}? ${t.name} – perfekt för att avsluta dagen.`:n<10?`Godmorgon ${r}! Vilodag idag – perfekt för återhämtning.`:n<14?"Ingen träning schemalagd. Ta en promenad eller stretcha lite?":n<18?"Vila är också träning! Lätt rörelse eller mobilitet idag?":`Lugn kväll ${r}. Ladda batterierna till nästa pass!`},Kh=[{iconName:"walking",text:"Promenad"},{iconName:"yoga",text:"Stretching"},{iconName:"swimming",text:"Simning"},{iconName:"cycling",text:"Cykling"}],Gh=["Mån","Tis","Ons","Tor","Fre","Lör","Sön"];function Yh({onStartWorkout:e,onNavigate:t}){var k,x;const{user:n,logout:r}=it(),[l,s]=j.useState(null),[o,a]=j.useState(null),[u,c]=j.useState(!0),[h,p]=j.useState(Xh(new Date));j.useEffect(()=>{v()},[]);const v=async()=>{var S;try{const d=await(await fetch(`${Hh}/programs/1`)).json();s(d);const f=new Date().getDay(),y=f===0?7:f,N=(S=d.days)==null?void 0:S.find(w=>w.day_number===y);a(N||null),c(!1)}catch(m){console.error("Failed to fetch data:",m),c(!1)}};if(u)return i.jsxs("div",{className:"dashboard loading",children:[i.jsx("div",{className:"spinner"}),i.jsx("p",{children:"Laddar..."})]});const g=((k=l==null?void 0:l.days)==null?void 0:k.map(S=>S.day_number))||[];return i.jsxs("div",{className:"dashboard",children:[i.jsx("header",{className:"dashboard-header",children:i.jsxs("div",{className:"header-top",children:[i.jsxs("h1",{className:"brand-title",children:[i.jsx(B,{name:"gravl",size:22})," Gravl"]}),i.jsxs("nav",{className:"nav-menu",children:[i.jsx("button",{className:"nav-btn active",children:i.jsx(B,{name:"home",size:18})}),i.jsx("button",{className:"nav-btn",onClick:()=>t("progress"),children:i.jsx(B,{name:"chart",size:18})}),i.jsx("button",{className:"nav-btn",onClick:()=>t("profile"),children:i.jsx(B,{name:"user",size:18})}),i.jsx("button",{className:"nav-btn logout",onClick:r,children:i.jsx(B,{name:"logout",size:18})})]})]})}),i.jsxs("main",{className:"dashboard-main",children:[i.jsxs("section",{className:"week-calendar",children:[i.jsxs("div",{className:"calendar-header",children:[i.jsx("button",{className:"calendar-nav",onClick:()=>p(el(h,-7)),children:i.jsx(B,{name:"chevronLeft",size:16})}),i.jsx("span",{className:"calendar-title",children:Zh(h)}),i.jsx("button",{className:"calendar-nav",onClick:()=>p(el(h,7)),children:i.jsx(B,{name:"chevronRight",size:16})})]}),i.jsx("div",{className:"calendar-days",children:Gh.map((S,m)=>{var C;const d=el(h,m),f=m+1,y=Jh(d,new Date),N=g.includes(f),w=(C=l==null?void 0:l.days)==null?void 0:C.find(E=>E.day_number===f);return i.jsxs("div",{className:`calendar-day ${y?"today":""} ${N?"has-workout":""}`,onClick:()=>N&&w&&e(w),children:[i.jsx("span",{className:"day-name",children:S}),i.jsx("span",{className:"day-date",children:d.getDate()}),N&&i.jsx("span",{className:"day-dot"})]},m)})})]}),i.jsxs("section",{className:"coach-section",children:[i.jsxs("div",{className:"coach-greeting",children:[i.jsx("div",{className:"coach-avatar",children:i.jsx(B,{name:"coach",size:36})}),i.jsx("div",{className:"coach-message",children:i.jsx("p",{children:Qh(n,o)})})]}),i.jsx("div",{className:"today-action",children:o?i.jsxs("div",{className:"today-workout-card",onClick:()=>e(o),children:[i.jsxs("div",{className:"workout-info",children:[i.jsx("h3",{children:o.name}),i.jsxs("span",{className:"workout-meta",children:[(x=o.exercises)==null?void 0:x.filter(S=>S.name).length," övningar • ~45 min"]})]}),i.jsx("div",{className:"workout-action",children:i.jsx(B,{name:"arrowRight",size:24})})]}):i.jsxs("div",{className:"rest-day-section",children:[i.jsx("div",{className:"rest-tips",children:Kh.map((S,m)=>i.jsxs("span",{className:"tip-badge",children:[i.jsx(B,{name:S.iconName,size:16}),S.text]},m))}),i.jsxs("button",{className:"add-workout-btn",onClick:()=>t("select-workout"),children:[i.jsx(B,{name:"plus",size:20}),i.jsx("span",{children:"Lägg till pass"})]})]})})]}),i.jsxs("section",{className:"quick-stats",children:[i.jsxs("div",{className:"stat-card",children:[i.jsx("span",{className:"stat-value",children:g.length}),i.jsx("span",{className:"stat-label",children:"Pass/vecka"})]}),i.jsxs("div",{className:"stat-card",children:[i.jsx("span",{className:"stat-value",children:"2"}),i.jsx("span",{className:"stat-label",children:"Denna vecka"})]}),i.jsxs("div",{className:"stat-card",children:[i.jsx("span",{className:"stat-value stat-icon",children:i.jsx(B,{name:"fire",size:28})}),i.jsx("span",{className:"stat-label",children:"Streak: 5"})]})]})]})]})}function Xh(e){const t=new Date(e),n=t.getDay(),r=t.getDate()-n+(n===0?-6:1);return new Date(t.setDate(r))}function el(e,t){const n=new Date(e);return n.setDate(n.getDate()+t),n}function Jh(e,t){return e.getDate()===t.getDate()&&e.getMonth()===t.getMonth()&&e.getFullYear()===t.getFullYear()}function Zh(e){const t=el(e,6),n=e.toLocaleDateString("sv-SE",{month:"short"}),r=t.toLocaleDateString("sv-SE",{month:"short"});return n===r?`${e.getDate()} - ${t.getDate()} ${n}`:`${e.getDate()} ${n} - ${t.getDate()} ${r}`}const Wr="/api";function qh({onBack:e}){const{user:t,logout:n}=it(),[r,l]=j.useState(null),[s,o]=j.useState(null),[a,u]=j.useState(null),[c,h]=j.useState(!1),[p,v]=j.useState(!0),[g,k]=j.useState(!1),[x,S]=j.useState({});j.useEffect(()=>{m()},[]);const m=async()=>{try{const[w,C,E]=await Promise.all([fetch(`${Wr}/user/profile/${(t==null?void 0:t.id)||1}`),fetch(`${Wr}/user/measurements/${(t==null?void 0:t.id)||1}`),fetch(`${Wr}/user/strength/${(t==null?void 0:t.id)||1}`)]),O=await w.json(),T=await C.json(),q=await E.json();l(O),o(T),u(q),S(O),v(!1)}catch(w){console.error("Failed to fetch profile:",w),v(!1)}},d=async()=>{k(!0);try{const C=await(await fetch(`${Wr}/user/profile/${(t==null?void 0:t.id)||1}`,{method:"PUT",headers:{"Content-Type":"application/json"},body:JSON.stringify(x)})).json();l(C),h(!1)}catch(w){console.error("Failed to save profile:",w)}k(!1)},f=(w,C)=>{S(E=>({...E,[w]:C}))};if(p)return i.jsxs("div",{className:"profile-page loading",children:[i.jsx("div",{className:"spinner"}),i.jsx("p",{children:"Laddar profil..."})]});const y=s==null?void 0:s[0],N=a==null?void 0:a[0];return i.jsxs("div",{className:"profile-page",children:[i.jsxs("header",{className:"page-header",children:[i.jsx("button",{className:"back-btn",onClick:e,children:"← Tillbaka"}),i.jsx("h1",{children:"Min profil"}),i.jsx("button",{className:"logout-btn",onClick:n,children:"↪"})]}),i.jsxs("main",{className:"page-main",children:[i.jsxs("section",{className:"profile-section",children:[i.jsxs("div",{className:"section-header",children:[i.jsx("h2",{children:"Personuppgifter"}),!c&&i.jsx("button",{className:"edit-btn",onClick:()=>h(!0),children:"✏️ Redigera"})]}),c?i.jsxs("div",{className:"edit-form",children:[i.jsxs("div",{className:"form-group",children:[i.jsx("label",{children:"Namn"}),i.jsx("input",{type:"text",value:x.name||"",onChange:w=>f("name",w.target.value)})]}),i.jsxs("div",{className:"form-row",children:[i.jsxs("div",{className:"form-group",children:[i.jsx("label",{children:"Ålder"}),i.jsx("input",{type:"number",value:x.age||"",onChange:w=>f("age",parseInt(w.target.value))})]}),i.jsxs("div",{className:"form-group",children:[i.jsx("label",{children:"Kön"}),i.jsxs("select",{value:x.gender||"",onChange:w=>f("gender",w.target.value),children:[i.jsx("option",{value:"male",children:"Man"}),i.jsx("option",{value:"female",children:"Kvinna"})]})]})]}),i.jsxs("div",{className:"form-row",children:[i.jsxs("div",{className:"form-group",children:[i.jsx("label",{children:"Längd (cm)"}),i.jsx("input",{type:"number",value:x.height_cm||"",onChange:w=>f("height_cm",parseFloat(w.target.value))})]}),i.jsxs("div",{className:"form-group",children:[i.jsx("label",{children:"Pass/vecka"}),i.jsx("input",{type:"number",min:"1",max:"7",value:x.workouts_per_week||"",onChange:w=>f("workouts_per_week",parseInt(w.target.value))})]})]}),i.jsxs("div",{className:"form-group",children:[i.jsx("label",{children:"Mål"}),i.jsxs("select",{value:x.goal||"",onChange:w=>f("goal",w.target.value),children:[i.jsx("option",{value:"muscle",children:"Bygga muskler"}),i.jsx("option",{value:"strength",children:"Öka styrka"}),i.jsx("option",{value:"fat_loss",children:"Fettförbränning"}),i.jsx("option",{value:"general",children:"Allmän fitness"})]})]}),i.jsxs("div",{className:"form-group",children:[i.jsx("label",{children:"Erfarenhetsnivå"}),i.jsxs("select",{value:x.experience_level||"",onChange:w=>f("experience_level",w.target.value),children:[i.jsx("option",{value:"beginner",children:"Nybörjare"}),i.jsx("option",{value:"intermediate",children:"Medel"}),i.jsx("option",{value:"advanced",children:"Avancerad"})]})]}),i.jsxs("div",{className:"form-actions",children:[i.jsx("button",{className:"cancel-btn",onClick:()=>{h(!1),S(r)},children:"Avbryt"}),i.jsx("button",{className:"save-btn",onClick:d,disabled:g,children:g?"Sparar...":"Spara"})]})]}):i.jsx("div",{className:"profile-info",children:i.jsxs("div",{className:"info-grid",children:[i.jsxs("div",{className:"info-item",children:[i.jsx("span",{className:"info-label",children:"Namn"}),i.jsx("span",{className:"info-value",children:(r==null?void 0:r.name)||"-"})]}),i.jsxs("div",{className:"info-item",children:[i.jsx("span",{className:"info-label",children:"Ålder"}),i.jsxs("span",{className:"info-value",children:[(r==null?void 0:r.age)||"-"," år"]})]}),i.jsxs("div",{className:"info-item",children:[i.jsx("span",{className:"info-label",children:"Längd"}),i.jsxs("span",{className:"info-value",children:[(r==null?void 0:r.height_cm)||"-"," cm"]})]}),i.jsxs("div",{className:"info-item",children:[i.jsx("span",{className:"info-label",children:"Kön"}),i.jsx("span",{className:"info-value",children:(r==null?void 0:r.gender)==="male"?"Man":"Kvinna"})]}),i.jsxs("div",{className:"info-item",children:[i.jsx("span",{className:"info-label",children:"Mål"}),i.jsx("span",{className:"info-value",children:bh(r==null?void 0:r.goal)})]}),i.jsxs("div",{className:"info-item",children:[i.jsx("span",{className:"info-label",children:"Nivå"}),i.jsx("span",{className:"info-value",children:em(r==null?void 0:r.experience_level)})]}),i.jsxs("div",{className:"info-item",children:[i.jsx("span",{className:"info-label",children:"Pass/vecka"}),i.jsx("span",{className:"info-value",children:(r==null?void 0:r.workouts_per_week)||"-"})]})]})})]}),i.jsxs("section",{className:"profile-section",children:[i.jsx("h2",{children:"Aktuella mätningar"}),y?i.jsxs("div",{className:"measurements-grid",children:[i.jsxs("div",{className:"measurement-card",children:[i.jsx("span",{className:"measurement-icon",children:"⚖️"}),i.jsxs("span",{className:"measurement-value",children:[y.weight," kg"]}),i.jsx("span",{className:"measurement-label",children:"Vikt"})]}),y.body_fat_pct&&i.jsxs("div",{className:"measurement-card",children:[i.jsx("span",{className:"measurement-icon",children:"📊"}),i.jsxs("span",{className:"measurement-value",children:[y.body_fat_pct,"%"]}),i.jsx("span",{className:"measurement-label",children:"Kroppsfett"})]}),y.waist_cm&&i.jsxs("div",{className:"measurement-card",children:[i.jsx("span",{className:"measurement-icon",children:"📏"}),i.jsxs("span",{className:"measurement-value",children:[y.waist_cm," cm"]}),i.jsx("span",{className:"measurement-label",children:"Midja"})]}),y.neck_cm&&i.jsxs("div",{className:"measurement-card",children:[i.jsx("span",{className:"measurement-icon",children:"📏"}),i.jsxs("span",{className:"measurement-value",children:[y.neck_cm," cm"]}),i.jsx("span",{className:"measurement-label",children:"Nacke"})]})]}):i.jsx("p",{className:"no-data",children:"Inga mätningar registrerade"})]}),i.jsxs("section",{className:"profile-section",children:[i.jsx("h2",{children:"Styrkerekord (1RM)"}),N?i.jsxs("div",{className:"strength-grid",children:[i.jsxs("div",{className:"strength-card",children:[i.jsx("span",{className:"strength-exercise",children:"Bänkpress"}),i.jsxs("span",{className:"strength-value",children:[N.bench_1rm||"-"," kg"]})]}),i.jsxs("div",{className:"strength-card",children:[i.jsx("span",{className:"strength-exercise",children:"Knäböj"}),i.jsxs("span",{className:"strength-value",children:[N.squat_1rm||"-"," kg"]})]}),i.jsxs("div",{className:"strength-card",children:[i.jsx("span",{className:"strength-exercise",children:"Marklyft"}),i.jsxs("span",{className:"strength-value",children:[N.deadlift_1rm||"-"," kg"]})]})]}):i.jsx("p",{className:"no-data",children:"Inga styrkerekord registrerade"})]})]})]})}function bh(e){return{muscle:"Bygga muskler",strength:"Öka styrka",fat_loss:"Fettförbränning",general:"Allmän fitness"}[e]||e||"-"}function em(e){return{beginner:"Nybörjare",intermediate:"Medel",advanced:"Avancerad"}[e]||e||"-"}const Ta="/api";function tm({onBack:e}){const{user:t}=it(),[n,r]=j.useState([]),[l,s]=j.useState([]),[o,a]=j.useState(!0),[u,c]=j.useState("weight");j.useEffect(()=>{h()},[]);const h=async()=>{try{const[p,v]=await Promise.all([fetch(`${Ta}/user/measurements/${(t==null?void 0:t.id)||1}`),fetch(`${Ta}/user/strength/${(t==null?void 0:t.id)||1}`)]),g=await p.json(),k=await v.json();r([...g].reverse()),s([...k].reverse()),a(!1)}catch(p){console.error("Failed to fetch progress:",p),a(!1)}};return o?i.jsxs("div",{className:"progress-page loading",children:[i.jsx("div",{className:"spinner"}),i.jsx("p",{children:"Laddar progress..."})]}):i.jsxs("div",{className:"progress-page",children:[i.jsxs("header",{className:"page-header",children:[i.jsx("button",{className:"back-btn",onClick:e,children:"← Tillbaka"}),i.jsx("h1",{children:"Min progress"}),i.jsx("div",{style:{width:40}})]}),i.jsxs("main",{className:"page-main",children:[i.jsxs("div",{className:"progress-tabs",children:[i.jsx("button",{className:`tab-btn ${u==="weight"?"active":""}`,onClick:()=>c("weight"),children:"⚖️ Vikt"}),i.jsx("button",{className:`tab-btn ${u==="bodyfat"?"active":""}`,onClick:()=>c("bodyfat"),children:"📊 Kroppsfett"}),i.jsx("button",{className:`tab-btn ${u==="strength"?"active":""}`,onClick:()=>c("strength"),children:"💪 Styrka"})]}),u==="weight"&&i.jsxs("section",{className:"chart-section",children:[i.jsx("h2",{children:"Viktutveckling"}),n.length>0?i.jsxs(i.Fragment,{children:[i.jsx(Rn,{data:n,valueKey:"weight",unit:"kg",color:"var(--accent)"}),i.jsx(On,{data:n,valueKey:"weight",unit:"kg",label:"Vikt"})]}):i.jsx(gs,{message:"Inga viktmätningar registrerade"})]}),u==="bodyfat"&&i.jsxs("section",{className:"chart-section",children:[i.jsx("h2",{children:"Kroppsfett"}),n.filter(p=>p.body_fat_pct).length>0?i.jsxs(i.Fragment,{children:[i.jsx(Rn,{data:n.filter(p=>p.body_fat_pct),valueKey:"body_fat_pct",unit:"%",color:"#10b981"}),i.jsx(On,{data:n.filter(p=>p.body_fat_pct),valueKey:"body_fat_pct",unit:"%",label:"Kroppsfett"})]}):i.jsx(gs,{message:"Inga kroppsfettmätningar registrerade"})]}),u==="strength"&&i.jsxs("section",{className:"chart-section",children:[i.jsx("h2",{children:"Styrkerekord (1RM)"}),l.length>0?i.jsxs("div",{className:"strength-charts",children:[i.jsxs("div",{className:"strength-chart-item",children:[i.jsx("h3",{children:"🏋️ Bänkpress"}),i.jsx(Rn,{data:l.filter(p=>p.bench_1rm),valueKey:"bench_1rm",unit:"kg",color:"#f59e0b"}),i.jsx(On,{data:l.filter(p=>p.bench_1rm),valueKey:"bench_1rm",unit:"kg",label:"Bänkpress"})]}),i.jsxs("div",{className:"strength-chart-item",children:[i.jsx("h3",{children:"🦵 Knäböj"}),i.jsx(Rn,{data:l.filter(p=>p.squat_1rm),valueKey:"squat_1rm",unit:"kg",color:"#8b5cf6"}),i.jsx(On,{data:l.filter(p=>p.squat_1rm),valueKey:"squat_1rm",unit:"kg",label:"Knäböj"})]}),i.jsxs("div",{className:"strength-chart-item",children:[i.jsx("h3",{children:"💀 Marklyft"}),i.jsx(Rn,{data:l.filter(p=>p.deadlift_1rm),valueKey:"deadlift_1rm",unit:"kg",color:"#ef4444"}),i.jsx(On,{data:l.filter(p=>p.deadlift_1rm),valueKey:"deadlift_1rm",unit:"kg",label:"Marklyft"})]})]}):i.jsx(gs,{message:"Inga styrkerekord registrerade"})]})]})]})}function Rn({data:e,valueKey:t,unit:n,color:r}){var S,m;if(!e||e.length===0)return null;const l=e.map(d=>d[t]).filter(d=>d!=null);if(l.length===0)return null;const s=Math.min(...l)*.95,o=Math.max(...l)*1.05,a=o-s||1,u=320,c=160,h={top:20,right:20,bottom:30,left:50},p=u-h.left-h.right,v=c-h.top-h.bottom,g=e.map((d,f)=>{const y=h.left+f/Math.max(e.length-1,1)*p,N=h.top+v-(d[t]-s)/a*v;return{x:y,y:N,value:d[t],date:d.created_at}}).filter(d=>d.value!=null),k=g.map((d,f)=>`${f===0?"M":"L"} ${d.x} ${d.y}`).join(" "),x=[s,(s+o)/2,o].map(d=>d.toFixed(1));return i.jsxs("div",{className:"chart-container",children:[i.jsxs("svg",{viewBox:`0 0 ${u} ${c}`,className:"line-chart",children:[[0,.5,1].map((d,f)=>i.jsx("line",{x1:h.left,y1:h.top+v*(1-d),x2:u-h.right,y2:h.top+v*(1-d),stroke:"var(--border)",strokeDasharray:"4"},f)),x.map((d,f)=>i.jsx("text",{x:h.left-8,y:h.top+v*(1-f*.5)+4,textAnchor:"end",fontSize:"10",fill:"var(--text-muted)",children:d},f)),i.jsx("path",{d:k,fill:"none",stroke:r,strokeWidth:"2.5",strokeLinecap:"round",strokeLinejoin:"round"}),g.map((d,f)=>i.jsx("circle",{cx:d.x,cy:d.y,r:"4",fill:r},f))]}),i.jsxs("div",{className:"chart-labels",children:[i.jsx("span",{children:Ra((S=e[0])==null?void 0:S.created_at)}),i.jsx("span",{children:Ra((m=e[e.length-1])==null?void 0:m.created_at)})]})]})}function On({data:e,valueKey:t,unit:n,label:r}){if(!e||e.length===0)return null;const l=e.map(p=>p[t]).filter(p=>p!=null);if(l.length===0)return null;const s=l[l.length-1],o=l[0],a=s-o,u=(a/o*100).toFixed(1),c=a>0?"↑":a<0?"↓":"→",h=a>0?"up":a<0?"down":"neutral";return i.jsxs("div",{className:"progress-stats",children:[i.jsxs("div",{className:"stat-item",children:[i.jsx("span",{className:"stat-label",children:"Nuvarande"}),i.jsxs("span",{className:"stat-value",children:[s," ",n]})]}),i.jsxs("div",{className:"stat-item",children:[i.jsx("span",{className:"stat-label",children:"Första"}),i.jsxs("span",{className:"stat-value",children:[o," ",n]})]}),i.jsxs("div",{className:"stat-item",children:[i.jsx("span",{className:"stat-label",children:"Förändring"}),i.jsxs("span",{className:`stat-value trend-${h}`,children:[c," ",Math.abs(a).toFixed(1)," ",n," (",u,"%)"]})]})]})}function gs({message:e}){return i.jsxs("div",{className:"empty-state",children:[i.jsx("span",{className:"empty-icon",children:"📊"}),i.jsx("p",{children:e}),i.jsx("p",{className:"empty-hint",children:"Logga mätningar för att se din progress"})]})}function Ra(e){return e?new Date(e).toLocaleDateString("sv-SE",{month:"short",day:"numeric"}):"-"}function nm({exercise:e,alternatives:t,loading:n,error:r,onSelect:l,onClose:s}){return e?i.jsx("div",{className:"alternative-modal-overlay",onClick:s,children:i.jsxs("div",{className:"alternative-modal",onClick:o=>o.stopPropagation(),children:[i.jsxs("div",{className:"alternative-modal-header",children:[i.jsxs("div",{children:[i.jsx("h3",{children:"Alternativa övningar"}),i.jsxs("p",{children:["För ",e.name]})]}),i.jsx("button",{className:"alternative-modal-close",onClick:s,"aria-label":"Stäng",children:i.jsx(B,{name:"chevronDown",size:18})})]}),n&&i.jsx("div",{className:"alternative-modal-state",children:"Laddar alternativ..."}),!n&&r&&i.jsx("div",{className:"alternative-modal-state error",children:r}),!n&&!r&&t.length===0&&i.jsx("div",{className:"alternative-modal-state",children:"Inga alternativ hittades."}),!n&&!r&&t.length>0&&i.jsx("div",{className:"alternative-list",children:t.map(o=>i.jsxs("div",{className:"alternative-item",children:[i.jsxs("div",{className:"alternative-info",children:[i.jsx("strong",{children:o.name}),i.jsx("span",{children:o.description||"Ingen beskrivning tillgänglig."})]}),i.jsx("button",{className:"alternative-select-btn",onClick:()=>l(o),children:"Välj"})]},o.id))})]})}):null}function ad({value:e="",onChange:t,step:n=1,min:r=0,max:l=null,label:s="Value",suffix:o="",disabled:a=!1}){const u=parseFloat(e)||0;function c(k){const x=k.target.value;if(x===""){t("");return}const S=parseFloat(x);isNaN(S)||(Sl?t(String(l)):t(String(S)))}function h(){if(a)return;const k=Math.max(r,u-n);t(String(k))}function p(){if(a)return;const k=u+n;(l===null||k<=l)&&t(String(k))}const v=u>r,g=l===null||u{n.muscle_group&&t.add(n.muscle_group)}),Array.from(t)}function om({day:e,week:t,logs:n,onLogSet:r,onDeleteSet:l,onBack:s,fetchProgression:o}){var X;const[a,u]=j.useState({}),[c,h]=j.useState(null),[p,v]=j.useState(!1),[g,k]=j.useState(!0),[x,S]=j.useState(new Set),[m,d]=j.useState(null),[f,y]=j.useState([]),[N,w]=j.useState(!1),[C,E]=j.useState(""),[O,T]=j.useState({});j.useEffect(()=>{q()},[e]);const q=async()=>{const z={};for(const I of e.exercises)I.id&&(z[I.id]=await o(I.id));u(z)},Ye=async z=>{if(!(z!=null&&z.exercise_id)){E("Saknar övningsdata för alternativa val."),d(z);return}d(z),y([]),E(""),w(!0);try{const I=await fetch(`${sm}/exercises/${z.exercise_id}/alternatives`);if(!I.ok)throw new Error("Failed to fetch alternatives");const se=await I.json();y(se)}catch(I){console.error("Failed to fetch alternatives:",I),E("Kunde inte hämta alternativ.")}finally{w(!1)}},Ae=z=>{m&&(T(I=>({...I,[m.id]:z})),d(null))},le=((X=e.exercises)==null?void 0:X.filter(z=>z.name))||[],Qt=im(le),Xe=le.filter(z=>(n[z.id]||[]).filter(Oe=>Oe.completed).length>=z.sets).length,Kt=z=>{const I=new Set(x);I.has(z)?I.delete(z):I.add(z),S(I)},L=Oa.general,M=Qt.flatMap(z=>Oa.specific[z]||[]),R=L.length+M.length,H=x.size;return i.jsxs("div",{className:"workout-page",children:[i.jsxs("header",{className:"page-header",children:[i.jsxs("button",{className:"back-btn",onClick:s,children:[i.jsx(B,{name:"arrowLeft",size:16})," Tillbaka"]}),i.jsxs("div",{className:"header-center",children:[i.jsx("h1",{children:e.name}),i.jsxs("span",{className:"header-subtitle",children:["Vecka ",t," • Dag ",e.day_number]})]}),i.jsx("div",{className:"header-progress",children:i.jsxs("span",{className:"progress-text",children:[Xe,"/",le.length]})})]}),i.jsxs("main",{className:"page-main workout-main",children:[i.jsx("div",{className:"workout-progress-bar",children:i.jsx("div",{className:"workout-progress-fill",style:{width:`${Xe/le.length*100}%`}})}),i.jsxs("section",{className:`warmup-section ${p?"completed":""}`,children:[i.jsxs("div",{className:"warmup-header",onClick:()=>k(!g),children:[i.jsxs("div",{className:"warmup-title",children:[i.jsx("span",{className:"warmup-icon",children:i.jsx(B,{name:"fire",size:20})}),i.jsx("h2",{children:"Uppvärmning"}),i.jsxs("span",{className:"warmup-progress",children:[H,"/",R]})]}),i.jsx("span",{className:`expand-icon ${g?"expanded":""}`,children:i.jsx(B,{name:"chevronDown",size:16})})]}),g&&i.jsxs("div",{className:"warmup-content",children:[i.jsxs("div",{className:"warmup-category",children:[i.jsx("h3",{children:"Generell uppvärmning (5-10 min)"}),i.jsx("div",{className:"warmup-list",children:L.map((z,I)=>i.jsxs("div",{className:`warmup-item ${x.has(I)?"done":""}`,onClick:()=>Kt(I),children:[i.jsx("span",{className:"warmup-check",children:x.has(I)?i.jsx(B,{name:"check",size:14}):""}),i.jsx("span",{className:"warmup-name",children:z.name}),i.jsx("span",{className:"warmup-duration",children:z.duration||z.reps})]},I))})]}),M.length>0&&i.jsxs("div",{className:"warmup-category",children:[i.jsxs("h3",{children:["Specifik för ",Qt.join(", ")]}),i.jsx("div",{className:"warmup-list",children:M.map((z,I)=>{const se=L.length+I;return i.jsxs("div",{className:`warmup-item ${x.has(se)?"done":""}`,onClick:()=>Kt(se),children:[i.jsx("span",{className:"warmup-check",children:x.has(se)?i.jsx(B,{name:"check",size:14}):""}),i.jsx("span",{className:"warmup-name",children:z.name}),i.jsx("span",{className:"warmup-duration",children:z.reps})]},se)})})]}),le[0]&&i.jsxs("div",{className:"warmup-category",children:[i.jsx("h3",{children:"Förberedande set"}),i.jsx("div",{className:"warmup-list",children:i.jsxs("div",{className:`warmup-item ${x.has("prep")?"done":""}`,onClick:()=>{const z=new Set(x);z.has("prep")?z.delete("prep"):z.add("prep"),S(z)},children:[i.jsx("span",{className:"warmup-check",children:x.has("prep")?i.jsx(B,{name:"check",size:14}):""}),i.jsxs("span",{className:"warmup-name",children:["Lätta set ",le[0].name]}),i.jsx("span",{className:"warmup-duration",children:"2x10 @ 50%"})]})})]}),i.jsx("button",{className:`warmup-done-btn ${p?"completed":""}`,onClick:()=>v(!p),children:p?i.jsxs(i.Fragment,{children:[i.jsx(B,{name:"check",size:18})," Uppvärmning klar"]}):"Markera uppvärmning som klar"})]})]}),i.jsxs("section",{className:"exercises-section",children:[i.jsx("h2",{children:"Övningar"}),le.map((z,I)=>{const se=O[z.id],Oe=se?{...z,name:se.name,muscle_group:se.muscle_group,description:se.description}:z;return i.jsx(am,{exercise:Oe,isSwapped:!!se,logs:n[z.id]||[],progression:a[z.id],expanded:c===z.id,onToggle:()=>h(c===z.id?null:z.id),onLogSet:r,onDeleteSet:l,onSwap:()=>Ye(z)},z.id||I)})]}),i.jsx("button",{className:`finish-workout-btn ${Xe===le.length?"ready":""}`,onClick:s,children:Xe===le.length?"Avsluta pass":`Avsluta pass (${Xe}/${le.length} klara)`})]}),i.jsx(nm,{exercise:m,alternatives:f,loading:N,error:C,onSelect:Ae,onClose:()=>d(null)})]})}function am({exercise:e,logs:t,progression:n,expanded:r,onToggle:l,onLogSet:s,onDeleteSet:o,onSwap:a,isSwapped:u}){const[c,h]=j.useState([]),[p,v]=j.useState(!1);j.useEffect(()=>{var y,N,w;const f=[];for(let C=1;C<=e.sets;C++){const E=t.find(O=>O.set_number===C);f.push({weight:((y=E==null?void 0:E.weight)==null?void 0:y.toString())||((N=n==null?void 0:n.suggestedWeight)==null?void 0:N.toString())||"",reps:((w=E==null?void 0:E.reps)==null?void 0:w.toString())||"",completed:(E==null?void 0:E.completed)||!1})}h(f)},[e,t,n]);const g=(f,y,N)=>{h(w=>w.map((C,E)=>E===f?{...C,[y]:N}:C))},k=f=>{const y=c[f],N=!y.completed;h(w=>w.map((C,E)=>E===f?{...C,completed:N}:C)),s(e.id,f+1,y.weight,y.reps,N)},x=()=>{const f=c[c.length-1]||{weight:"",reps:""};h(y=>[...y,{weight:f.weight,reps:f.reps,completed:!1}]),v(!1)},S=()=>{const f=c[c.length-1]||{weight:"0"},y=parseFloat(f.weight)||0,N=Math.round(y*.8/2.5)*2.5,w=Math.round(y*.6/2.5)*2.5,C=[{weight:f.weight,reps:"10",completed:!1},{weight:N.toString(),reps:"10",completed:!1},{weight:w.toString(),reps:"10",completed:!1}];h(E=>[...E,...C]),v(!1)},m=f=>{c.length<=1||(h(y=>y.filter((N,w)=>w!==f)),o&&o(e.id,f+1))},d=c.filter(f=>f.completed).length;return i.jsxs("div",{className:`exercise-card ${r?"expanded":""} ${d===c.length&&c.length>0?"all-done":""}`,children:[i.jsxs("div",{className:"exercise-header",onClick:l,children:[i.jsxs("div",{className:"exercise-info",children:[i.jsx("h3",{children:e.name}),i.jsx("span",{className:"muscle-group",children:e.muscle_group}),u&&i.jsx("span",{className:"swap-badge",children:"Alternativ"})]}),i.jsxs("div",{className:"exercise-actions",children:[i.jsxs("div",{className:"exercise-meta",children:[i.jsxs("span",{className:"sets-info",children:[e.sets,"×",e.reps_min,"-",e.reps_max]}),i.jsxs("span",{className:`progress-badge ${d===c.length?"complete":""}`,children:[d,"/",c.length]})]}),i.jsx("button",{className:"swap-btn",onClick:f=>{f.stopPropagation(),a==null||a()},"aria-label":"Byt övning",children:i.jsx(B,{name:"swap",size:16})})]})]}),r&&i.jsxs("div",{className:"exercise-body",children:[n&&i.jsxs("div",{className:"progression-hint",children:[n.reason,n.suggestedWeight&&i.jsxs("strong",{children:[" ",n.suggestedWeight," kg"]})]}),i.jsx("div",{className:"sets-list",children:c.map((f,y)=>i.jsxs("div",{className:`set-row ${f.completed?"completed":""}`,children:[i.jsxs("span",{className:"set-number",children:["Set ",y+1]}),i.jsxs("div",{className:"set-inputs",children:[i.jsx(rm,{value:f.weight,onChange:N=>g(y,"weight",N)}),i.jsx("span",{className:"input-separator",children:"×"}),i.jsx(lm,{value:f.reps,onChange:N=>g(y,"reps",N)})]}),i.jsx("button",{className:`delete-set-btn ${c.length<=1?"disabled":""}`,onClick:()=>m(y),disabled:c.length<=1,"aria-label":`Ta bort set ${y+1}`,children:i.jsx(B,{name:"trash",size:16})}),i.jsx("button",{className:`complete-btn ${f.completed?"done":""}`,onClick:()=>k(y),children:f.completed?i.jsx(B,{name:"check",size:18}):""})]},y))}),i.jsx("button",{className:"add-set-btn",onClick:()=>v(!0),children:"+ Lägg till set"}),p&&i.jsx("div",{className:"set-type-modal-overlay",onClick:()=>v(!1),children:i.jsxs("div",{className:"set-type-modal",onClick:f=>f.stopPropagation(),children:[i.jsx("h3",{children:"Välj settyp"}),i.jsxs("button",{className:"set-type-option",onClick:x,children:[i.jsx("strong",{children:"Vanligt set"}),i.jsx("span",{children:"Lägg till ett set"})]}),i.jsxs("button",{className:"set-type-option dropset",onClick:S,children:[i.jsx("strong",{children:"Dropset"}),i.jsx("span",{children:"3 set med viktnedtrappning (20% per steg)"})]}),i.jsx("button",{className:"set-type-cancel",onClick:()=>v(!1),children:"Avbryt"})]})})]})]})}const um="/api",cm=e=>{const t=e.toLowerCase();return t.includes("push")||t.includes("bröst")?"var(--workout-push)":t.includes("pull")||t.includes("rygg")?"var(--workout-pull)":t.includes("ben")||t.includes("leg")?"var(--workout-legs)":t.includes("axlar")?"var(--workout-shoulders)":t.includes("överkropp")||t.includes("upper")?"var(--workout-upper)":t.includes("underkropp")||t.includes("lower")?"var(--workout-lower)":"var(--workout-default)"};function dm({onBack:e,onSelectWorkout:t}){var p;const[n,r]=j.useState(null),[l,s]=j.useState(!0),[o,a]=j.useState(null);j.useEffect(()=>{u()},[]);const u=async()=>{try{const g=await(await fetch(`${um}/programs/1`)).json();r(g),s(!1)}catch(v){console.error("Failed to fetch program:",v),s(!1)}},c=v=>{a(v)},h=()=>{o&&t(o)};return l?i.jsxs("div",{className:"select-page loading",children:[i.jsx("div",{className:"spinner"}),i.jsx("p",{children:"Laddar pass..."})]}):i.jsxs("div",{className:"select-page",children:[i.jsxs("header",{className:"page-header",children:[i.jsxs("button",{className:"back-btn",onClick:e,children:[i.jsx(B,{name:"arrowLeft",size:18})," Tillbaka"]}),i.jsx("h1",{children:"Välj pass"}),i.jsx("div",{style:{width:40}})]}),i.jsxs("main",{className:"select-main",children:[i.jsx("p",{className:"select-intro",children:"Vilken träning vill du köra idag?"}),i.jsx("div",{className:"workout-grid",children:(p=n==null?void 0:n.days)==null?void 0:p.map(v=>{var m,d;const g=Vh(v.name),k=cm(v.name),x=(o==null?void 0:o.id)===v.id,S=((m=v.exercises)==null?void 0:m.filter(f=>f.name).length)||0;return i.jsxs("div",{className:`workout-select-card ${x?"selected":""}`,style:{"--workout-color":k},onClick:()=>c(v),children:[i.jsx("div",{className:"workout-icon",style:{background:k},children:i.jsx(B,{name:g,size:28})}),i.jsxs("div",{className:"workout-details",children:[i.jsx("h3",{children:v.name}),i.jsxs("p",{className:"workout-exercises-count",children:[S," övningar"]}),i.jsxs("div",{className:"workout-preview",children:[(d=v.exercises)==null?void 0:d.filter(f=>f.name).slice(0,2).map((f,y)=>i.jsx("span",{className:"preview-exercise",children:f.name},y)),S>2&&i.jsxs("span",{className:"preview-more",children:["+",S-2," till"]})]})]}),x&&i.jsx("div",{className:"selected-indicator",children:i.jsx(B,{name:"check",size:16})})]},v.id)})}),o&&i.jsx("div",{className:"select-action",children:i.jsxs("button",{className:"start-btn",onClick:h,children:["Starta ",o.name," →"]})})]})]})}const Dn="/api";function fm(){const{user:e,logout:t}=it(),[n,r]=j.useState("dashboard"),[l,s]=j.useState(null),[o,a]=j.useState(null),[u,c]=j.useState(1),[h,p]=j.useState({}),[v,g]=j.useState(!1),k=(e==null?void 0:e.id)||1,x=new Date().toISOString().split("T")[0],S=async()=>{if(!l)try{const C=await(await fetch(`${Dn}/programs/1`)).json();s(C)}catch(w){console.error("Failed to fetch program:",w)}},m=async w=>{try{const C=l.days.find(O=>O.id===w);if(!C)return;const E={};for(const O of C.exercises){if(!O.id)continue;const q=await(await fetch(`${Dn}/logs?user_id=${k}&date=${x}&program_exercise_id=${O.id}`)).json();E[O.id]=q}p(E)}catch(C){console.error("Failed to fetch logs:",C)}},d=async w=>{try{return await(await fetch(`${Dn}/progression/${w}?user_id=${k}`)).json()}catch(C){return console.error("Failed to fetch progression:",C),null}},f=async(w,C,E,O,T)=>{try{const Ye=await(await fetch(`${Dn}/logs`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({user_id:k,program_exercise_id:w,date:x,set_number:C,weight:parseFloat(E)||0,reps:parseInt(O)||0,completed:T})})).json();p(Ae=>({...Ae,[w]:[...(Ae[w]||[]).filter(le=>le.set_number!==C),Ye].sort((le,Qt)=>le.set_number-Qt.set_number)}))}catch(q){console.error("Failed to log set:",q)}},y=async(w,C)=>{try{await fetch(`${Dn}/logs`,{method:"DELETE",headers:{"Content-Type":"application/json"},body:JSON.stringify({user_id:k,program_exercise_id:w,date:x,set_number:C})}),p(E=>({...E,[w]:(E[w]||[]).filter(O=>O.set_number!==C)}))}catch(E){console.error("Failed to delete log:",E)}},N=async w=>{await S(),a(w),r("workout"),m(w.id)};return n==="dashboard"?i.jsx(Yh,{onStartWorkout:N,onNavigate:r}):n==="profile"?i.jsx(qh,{onBack:()=>r("dashboard")}):n==="progress"?i.jsx(tm,{onBack:()=>r("dashboard")}):n==="select-workout"?i.jsx(dm,{onBack:()=>r("dashboard"),onSelectWorkout:N}):n==="workout"&&o?i.jsx(om,{day:o,week:u,logs:h,onLogSet:f,onDeleteSet:y,onBack:()=>r("dashboard"),fetchProgression:d}):i.jsxs("div",{className:"app loading",children:[i.jsx("div",{className:"spinner"}),i.jsx("p",{children:"Laddar..."})]})}function pm(){const[e,t]=j.useState(""),[n,r]=j.useState(""),[l,s]=j.useState(""),[o,a]=j.useState(!1),{register:u}=it(),c=jr(),h=async p=>{p.preventDefault(),s(""),a(!0);try{await u(e,n),c("/onboarding")}catch(v){s(v.message)}a(!1)};return i.jsx("div",{className:"auth-page",children:i.jsxs("div",{className:"auth-card",children:[i.jsx("h1",{children:"🏋️ Gravl"}),i.jsx("h2",{children:"Skapa konto"}),l&&i.jsx("div",{className:"error",children:l}),i.jsxs("form",{onSubmit:h,children:[i.jsx("input",{type:"email",placeholder:"E-post",value:e,onChange:p=>t(p.target.value),required:!0}),i.jsx("input",{type:"password",placeholder:"Lösenord",value:n,onChange:p=>r(p.target.value),required:!0,minLength:6}),i.jsx("button",{type:"submit",disabled:o,children:o?"Skapar...":"Skapa konto"})]}),i.jsxs("p",{className:"auth-link",children:["Har redan konto? ",i.jsx(id,{to:"/login",children:"Logga in"})]})]})})}function hm(){const[e,t]=j.useState(""),[n,r]=j.useState(""),[l,s]=j.useState(""),[o,a]=j.useState(!1),{login:u}=it(),c=jr(),h=async p=>{p.preventDefault(),s(""),a(!0);try{const{user:v}=await u(e,n);c(v.onboarding_complete?"/":"/onboarding")}catch(v){s(v.message)}a(!1)};return i.jsx("div",{className:"auth-page",children:i.jsxs("div",{className:"auth-card",children:[i.jsx("h1",{children:"🏋️ Gravl"}),i.jsx("h2",{children:"Logga in"}),l&&i.jsx("div",{className:"error",children:l}),i.jsxs("form",{onSubmit:h,children:[i.jsx("input",{type:"email",placeholder:"E-post",value:e,onChange:p=>t(p.target.value),required:!0}),i.jsx("input",{type:"password",placeholder:"Lösenord",value:n,onChange:p=>r(p.target.value),required:!0}),i.jsx("button",{type:"submit",disabled:o,children:o?"Loggar in...":"Logga in"})]}),i.jsxs("p",{className:"auth-link",children:["Inget konto? ",i.jsx(id,{to:"/register",children:"Skapa konto"})]})]})})}const Da="/api",mm=(e,t,n,r,l)=>!t||!n||!l||e==="female"&&!r?null:e==="male"?Math.max(0,495/(1.0324-.19077*Math.log10(t-n)+.15456*Math.log10(l))-450).toFixed(1):Math.max(0,495/(1.29579-.35004*Math.log10(t+r-n)+.221*Math.log10(l))-450).toFixed(1);function vm(){const[e,t]=j.useState(1),[n,r]=j.useState({gender:"",age:"",height_cm:"",weight:"",neck_cm:"",waist_cm:"",hip_cm:"",experience_level:"",bench_1rm:"",squat_1rm:"",deadlift_1rm:"",goal:"",workouts_per_week:""}),[l,s]=j.useState(!1),{token:o,updateProfile:a,refreshProfile:u}=it(),c=jr(),h=(g,k)=>r(x=>({...x,[g]:k})),p=mm(n.gender,parseFloat(n.waist_cm),parseFloat(n.neck_cm),parseFloat(n.hip_cm),parseFloat(n.height_cm)),v=async()=>{s(!0);try{await a({gender:n.gender,age:n.age,height_cm:n.height_cm,experience_level:n.experience_level,goal:n.goal,workouts_per_week:n.workouts_per_week,onboarding_complete:!0}),(n.weight||n.neck_cm||n.waist_cm)&&await fetch(`${Da}/user/measurements`,{method:"POST",headers:{"Content-Type":"application/json",Authorization:`Bearer ${o}`},body:JSON.stringify({weight:n.weight,neck_cm:n.neck_cm,waist_cm:n.waist_cm,hip_cm:n.hip_cm,body_fat_pct:p})}),(n.bench_1rm||n.squat_1rm||n.deadlift_1rm)&&await fetch(`${Da}/user/strength`,{method:"POST",headers:{"Content-Type":"application/json",Authorization:`Bearer ${o}`},body:JSON.stringify({bench_1rm:n.bench_1rm,squat_1rm:n.squat_1rm,deadlift_1rm:n.deadlift_1rm})}),u&&await u(),c("/")}catch(g){console.error("Onboarding error:",g),s(!1)}};return i.jsx("div",{className:"onboarding",children:i.jsxs("div",{className:"onboarding-card",children:[i.jsx("div",{className:"steps-indicator",children:[1,2,3,4].map(g=>i.jsx("span",{className:e>=g?"active":"",children:g},g))}),e===1&&i.jsxs("div",{className:"step",children:[i.jsx("h2",{children:"Grundinfo"}),i.jsxs("div",{className:"field",children:[i.jsx("label",{children:"Kön"}),i.jsxs("div",{className:"btn-group",children:[i.jsx("button",{className:n.gender==="male"?"active":"",onClick:()=>h("gender","male"),children:"Man"}),i.jsx("button",{className:n.gender==="female"?"active":"",onClick:()=>h("gender","female"),children:"Kvinna"})]})]}),i.jsxs("div",{className:"field",children:[i.jsx("label",{children:"Ålder"}),i.jsx("input",{type:"number",value:n.age,onChange:g=>h("age",g.target.value),placeholder:"25"})]}),i.jsxs("div",{className:"field",children:[i.jsx("label",{children:"Längd (cm)"}),i.jsx("input",{type:"number",value:n.height_cm,onChange:g=>h("height_cm",g.target.value),placeholder:"175"})]}),i.jsxs("div",{className:"field",children:[i.jsx("label",{children:"Vikt (kg)"}),i.jsx("input",{type:"number",step:"0.1",value:n.weight,onChange:g=>h("weight",g.target.value),placeholder:"75"})]}),i.jsx("button",{className:"next-btn",onClick:()=>t(2),disabled:!n.gender||!n.age||!n.height_cm||!n.weight,children:"Nästa →"})]}),e===2&&i.jsxs("div",{className:"step",children:[i.jsx("h2",{children:"Kroppsmått"}),i.jsx("p",{className:"hint",children:"För att beräkna kroppsfett (US Navy-metoden)"}),i.jsxs("div",{className:"field",children:[i.jsx("label",{children:"Hals (cm)"}),i.jsx("input",{type:"number",step:"0.1",value:n.neck_cm,onChange:g=>h("neck_cm",g.target.value),placeholder:"38"})]}),i.jsxs("div",{className:"field",children:[i.jsx("label",{children:"Mage/midja (cm)"}),i.jsx("input",{type:"number",step:"0.1",value:n.waist_cm,onChange:g=>h("waist_cm",g.target.value),placeholder:"85"})]}),n.gender==="female"&&i.jsxs("div",{className:"field",children:[i.jsx("label",{children:"Höft (cm)"}),i.jsx("input",{type:"number",step:"0.1",value:n.hip_cm,onChange:g=>h("hip_cm",g.target.value),placeholder:"95"})]}),p&&i.jsxs("div",{className:"bodyfat-result",children:["Beräknat kroppsfett: ",i.jsxs("strong",{children:[p,"%"]})]}),i.jsxs("div",{className:"nav-btns",children:[i.jsx("button",{onClick:()=>t(1),children:"← Tillbaka"}),i.jsx("button",{className:"next-btn",onClick:()=>t(3),children:"Nästa →"})]})]}),e===3&&i.jsxs("div",{className:"step",children:[i.jsx("h2",{children:"Erfarenhet & styrka"}),i.jsxs("div",{className:"field",children:[i.jsx("label",{children:"Träningserfarenhet"}),i.jsx("div",{className:"btn-group vertical",children:["beginner","intermediate","advanced"].map(g=>i.jsx("button",{className:n.experience_level===g?"active":"",onClick:()=>h("experience_level",g),children:g==="beginner"?"Nybörjare (<1 år)":g==="intermediate"?"Medel (1-3 år)":"Avancerad (3+ år)"},g))})]}),(n.experience_level==="intermediate"||n.experience_level==="advanced")&&i.jsxs(i.Fragment,{children:[i.jsx("p",{className:"hint",children:"1RM (valfritt)"}),i.jsxs("div",{className:"rm-fields",children:[i.jsxs("div",{className:"field",children:[i.jsx("label",{children:"Bänk"}),i.jsx("input",{type:"number",value:n.bench_1rm,onChange:g=>h("bench_1rm",g.target.value),placeholder:"kg"})]}),i.jsxs("div",{className:"field",children:[i.jsx("label",{children:"Knäböj"}),i.jsx("input",{type:"number",value:n.squat_1rm,onChange:g=>h("squat_1rm",g.target.value),placeholder:"kg"})]}),i.jsxs("div",{className:"field",children:[i.jsx("label",{children:"Marklyft"}),i.jsx("input",{type:"number",value:n.deadlift_1rm,onChange:g=>h("deadlift_1rm",g.target.value),placeholder:"kg"})]})]})]}),i.jsxs("div",{className:"nav-btns",children:[i.jsx("button",{onClick:()=>t(2),children:"← Tillbaka"}),i.jsx("button",{className:"next-btn",onClick:()=>t(4),disabled:!n.experience_level,children:"Nästa →"})]})]}),e===4&&i.jsxs("div",{className:"step",children:[i.jsx("h2",{children:"Mål & schema"}),i.jsxs("div",{className:"field",children:[i.jsx("label",{children:"Mål"}),i.jsx("div",{className:"btn-group vertical",children:["strength","muscle","fat_loss","general"].map(g=>i.jsx("button",{className:n.goal===g?"active":"",onClick:()=>h("goal",g),children:g==="strength"?"💪 Styrka":g==="muscle"?"🏋️ Muskelmassa":g==="fat_loss"?"🔥 Fettförbränning":"⚖️ Allmän fitness"},g))})]}),i.jsxs("div",{className:"field",children:[i.jsx("label",{children:"Pass per vecka"}),i.jsx("div",{className:"btn-group",children:[3,4,5,6].map(g=>i.jsx("button",{className:n.workouts_per_week==g?"active":"",onClick:()=>h("workouts_per_week",g),children:g},g))})]}),i.jsxs("div",{className:"nav-btns",children:[i.jsx("button",{onClick:()=>t(3),children:"← Tillbaka"}),i.jsx("button",{className:"finish-btn",onClick:v,disabled:!n.goal||!n.workouts_per_week||l,children:l?"Sparar...":"Starta träningen! 🚀"})]})]})]})})}function Ia({children:e,requireOnboarding:t=!0}){const{user:n,loading:r}=it();return r?i.jsx("div",{className:"app loading",children:i.jsx("div",{className:"spinner"})}):n?t&&!n.onboarding_complete?i.jsx(_l,{to:"/onboarding"}):e:i.jsx(_l,{to:"/login"})}function Fa({children:e}){const{user:t,loading:n}=it();return n?i.jsx("div",{className:"app loading",children:i.jsx("div",{className:"spinner"})}):t!=null&&t.onboarding_complete?i.jsx(_l,{to:"/"}):t?i.jsx(_l,{to:"/onboarding"}):e}ys.createRoot(document.getElementById("root")).render(i.jsx(Ya.StrictMode,{children:i.jsx(Fh,{children:i.jsx(Wh,{children:i.jsxs(zh,{children:[i.jsx(Un,{path:"/register",element:i.jsx(Fa,{children:i.jsx(pm,{})})}),i.jsx(Un,{path:"/login",element:i.jsx(Fa,{children:i.jsx(hm,{})})}),i.jsx(Un,{path:"/onboarding",element:i.jsx(Ia,{requireOnboarding:!1,children:i.jsx(vm,{})})}),i.jsx(Un,{path:"/*",element:i.jsx(Ia,{children:i.jsx(fm,{})})})]})})})})); diff --git a/frontend/dist/assets/index-my_lGtI5.css b/frontend/dist/assets/index-my_lGtI5.css deleted file mode 100644 index 103af69..0000000 --- a/frontend/dist/assets/index-my_lGtI5.css +++ /dev/null @@ -1 +0,0 @@ -.app{min-height:100vh;display:flex;flex-direction:column}.app.loading{justify-content:center;align-items:center;gap:var(--space-4)}.spinner{width:40px;height:40px;border:3px solid var(--border);border-top-color:var(--accent);border-radius:50%;animation:spin 1s linear infinite}@keyframes spin{to{transform:rotate(360deg)}}.header{background:var(--bg-secondary);padding:var(--space-4) var(--space-5);display:flex;justify-content:space-between;align-items:center;border-bottom:1px solid var(--border);position:sticky;top:0;z-index:100;backdrop-filter:blur(12px);-webkit-backdrop-filter:blur(12px)}.header h1{font-size:var(--font-xl);font-weight:700}.week-selector{display:flex;align-items:center;gap:var(--space-3)}.week-selector button{background:var(--bg-card);color:var(--text-primary);width:44px;height:44px;min-width:44px;min-height:44px;border-radius:var(--radius-md);font-size:var(--font-lg);transition:all var(--transition-base);border:1px solid var(--border)}.week-selector button:hover:not(:disabled){background:var(--accent);border-color:var(--accent);box-shadow:0 4px 12px #ff6b4a40}.week-selector button:active:not(:disabled){transform:scale(.95)}.week-selector button:disabled{opacity:.3;cursor:not-allowed}.week-selector span{font-weight:600;min-width:80px;text-align:center}.main{flex:1;padding:var(--space-4);max-width:600px;margin:0 auto;width:100%}.program-info{margin-bottom:var(--space-6)}.program-info h2{font-size:var(--font-lg);margin-bottom:var(--space-2);color:var(--accent)}.program-info p{color:var(--text-secondary);font-size:var(--font-sm);line-height:1.6}.days-list h3{font-size:var(--font-sm);color:var(--text-muted);margin-bottom:var(--space-4);text-transform:uppercase;letter-spacing:.5px;font-weight:600}.day-card{background:var(--bg-card);border-radius:var(--radius-lg);padding:var(--space-4);margin-bottom:var(--space-3);cursor:pointer;transition:all var(--transition-base);border:1px solid var(--border);box-shadow:var(--shadow-card)}.day-card:hover{background:var(--bg-card-hover);border-color:var(--accent);transform:translateY(-2px);box-shadow:var(--shadow-md)}.day-card:active{transform:scale(.98)}.day-header{display:flex;justify-content:space-between;align-items:center;margin-bottom:var(--space-3)}.day-number{font-size:var(--font-xs);color:var(--text-muted);text-transform:uppercase;font-weight:500;letter-spacing:.5px}.day-name{font-size:var(--font-lg);font-weight:600}.day-exercises{display:flex;flex-wrap:wrap;gap:var(--space-2);margin-bottom:var(--space-3)}.exercise-tag{background:var(--bg-secondary);padding:var(--space-1) var(--space-2);border-radius:var(--radius-sm);font-size:var(--font-xs);color:var(--text-secondary);border:1px solid var(--border)}.exercise-tag.more{background:var(--accent);color:#fff;border-color:var(--accent)}.day-action{text-align:right;color:var(--accent);font-weight:600;font-size:var(--font-sm)}.workout-header{flex-direction:column;align-items:flex-start;gap:var(--space-2)}.back-btn{background:none;color:var(--accent);font-size:var(--font-sm);padding:var(--space-1) 0}.header-title h1{font-size:var(--font-lg)}.header-subtitle{font-size:var(--font-sm);color:var(--text-muted)}.exercise-card{background:var(--bg-card);border-radius:var(--radius-lg);margin-bottom:var(--space-3);overflow:hidden;border:1px solid var(--border);transition:all var(--transition-base);box-shadow:var(--shadow-card)}.exercise-card.expanded{border-color:var(--accent);box-shadow:0 4px 16px #ff6b4a26}.exercise-card.all-done{border-color:var(--success);background:var(--bg-card)}.exercise-header{padding:var(--space-4);display:flex;justify-content:space-between;align-items:center;cursor:pointer}.exercise-actions{display:flex;align-items:center;gap:var(--space-3)}.swap-btn{border:1px solid var(--border);background:var(--bg-secondary);color:var(--text-secondary);width:34px;height:34px;border-radius:var(--radius-full);display:inline-flex;align-items:center;justify-content:center;cursor:pointer;transition:all var(--transition-base)}.swap-btn:hover{color:var(--accent);border-color:var(--accent);background:var(--bg-tertiary)}.swap-badge{font-size:var(--font-xs);color:var(--accent)}.exercise-info h3{font-size:var(--font-base);margin-bottom:var(--space-1)}.muscle-group{font-size:var(--font-xs);color:var(--text-muted)}.exercise-meta{display:flex;flex-direction:column;align-items:flex-end;gap:var(--space-1)}.sets-info{font-size:var(--font-sm);color:var(--text-secondary)}.progress-badge{background:var(--bg-secondary);padding:var(--space-1) var(--space-2);border-radius:var(--radius-full);font-size:var(--font-xs);font-weight:600;border:1px solid var(--border)}.progress-badge.complete{background:var(--success);color:#fff;border-color:var(--success)}.exercise-body{padding:0 var(--space-4) var(--space-4);border-top:1px solid var(--border);padding-top:var(--space-4)}.progression-hint{background:var(--accent-subtle);border:1px solid rgba(255,107,74,.3);border-radius:var(--radius-md);padding:var(--space-3);margin-bottom:var(--space-4);font-size:var(--font-sm);color:var(--text-secondary)}.progression-hint strong{color:var(--accent)}.sets-list{display:flex;flex-direction:column;gap:var(--space-2)}.set-row{display:flex;align-items:center;gap:var(--space-3);padding:var(--space-3);background:var(--bg-secondary);border-radius:var(--radius-md);border:1px solid transparent;transition:all var(--transition-base)}.set-row.completed{background:var(--success-subtle);border-color:var(--success)}.set-number{font-size:var(--font-sm);color:var(--text-muted);min-width:45px;font-weight:500}.set-inputs{flex:1;display:flex;align-items:center;gap:var(--space-3)}.input-separator{color:var(--text-muted);font-weight:500}.complete-btn{width:44px;height:44px;min-width:44px;min-height:44px;border-radius:50%;background:var(--bg-card);color:var(--text-muted);font-size:var(--font-lg);transition:all var(--transition-base);border:2px solid var(--border)}.complete-btn:hover{background:var(--accent-subtle);border-color:var(--accent);color:var(--accent)}.complete-btn.done{background:var(--success);border-color:var(--success);color:#fff}@media (max-width: 480px){.header{padding:var(--space-3) var(--space-4)}.header h1{font-size:var(--font-lg)}.main{padding:var(--space-3)}}@supports (padding: env(safe-area-inset-bottom)){.main{padding-bottom:calc(var(--space-4) + env(safe-area-inset-bottom))}}.dashboard{min-height:100vh;background:var(--bg)}.dashboard.loading{display:flex;flex-direction:column;justify-content:center;align-items:center;gap:var(--space-4)}.dashboard-header{background:var(--bg-secondary);padding:var(--space-4) var(--space-5);border-bottom:1px solid var(--border);position:sticky;top:0;z-index:100;backdrop-filter:blur(12px);-webkit-backdrop-filter:blur(12px)}.header-top{display:flex;justify-content:space-between;align-items:center}.header-top h1{font-size:var(--font-xl);font-weight:700}.nav-menu{display:flex;gap:var(--space-1)}.nav-btn{background:transparent;border:none;color:var(--text-muted);padding:var(--space-2) var(--space-3);border-radius:var(--radius-md);font-size:var(--font-sm);cursor:pointer;transition:all var(--transition-base);min-height:44px;min-width:44px;display:flex;align-items:center;justify-content:center}.nav-btn:hover,.nav-btn.active{background:var(--bg-card);color:var(--text-primary)}.nav-btn.active{color:var(--accent)}.nav-btn.logout{color:var(--text-muted)}.nav-btn.logout:hover{color:var(--error)}.dashboard-main{padding:var(--space-4);display:flex;flex-direction:column;gap:var(--space-5);max-width:600px;margin:0 auto}.coach-greeting{display:flex;gap:var(--space-4);padding:var(--space-5);background:linear-gradient(135deg,var(--accent) 0%,#6366f1 100%);border-radius:var(--radius-xl);color:#fff;box-shadow:0 8px 24px #6366f140;position:relative;overflow:hidden}.coach-greeting:before{content:"";position:absolute;top:0;right:0;bottom:0;left:0;background:linear-gradient(180deg,rgba(255,255,255,.1) 0%,transparent 50%);pointer-events:none}.coach-avatar{width:56px;height:56px;min-width:56px;background:#ffffff26;border-radius:50%;display:flex;align-items:center;justify-content:center;flex-shrink:0;color:#fffffff2;-webkit-backdrop-filter:blur(8px);backdrop-filter:blur(8px);border:2px solid rgba(255,255,255,.2)}.coach-message{display:flex;align-items:center}.coach-message p{font-size:var(--font-lg);font-weight:500;line-height:1.4;position:relative;z-index:1}.week-calendar{background:var(--bg-card);border-radius:var(--radius-xl);padding:var(--space-4);border:1px solid var(--border);box-shadow:var(--shadow-card)}.calendar-header{display:flex;justify-content:space-between;align-items:center;margin-bottom:var(--space-4)}.calendar-title{font-weight:600;text-transform:capitalize;font-size:var(--font-base)}.calendar-nav{background:var(--bg-secondary);border:1px solid var(--border);width:44px;height:44px;min-width:44px;min-height:44px;border-radius:var(--radius-md);cursor:pointer;display:flex;align-items:center;justify-content:center;color:var(--text-primary);transition:all var(--transition-base)}.calendar-nav:hover{background:var(--accent);color:#fff;border-color:var(--accent);box-shadow:0 4px 12px #ff6b4a40}.calendar-nav:active{transform:scale(.95)}.calendar-days{display:grid;grid-template-columns:repeat(7,1fr);gap:var(--space-1)}.calendar-day{display:flex;flex-direction:column;align-items:center;padding:var(--space-2);border-radius:var(--radius-md);cursor:pointer;transition:all var(--transition-base);position:relative;min-height:60px;justify-content:center}.calendar-day:hover{background:var(--bg-secondary)}.calendar-day.today{background:var(--accent);color:#fff;box-shadow:0 4px 12px #ff6b4a4d}.calendar-day.has-workout:not(.today){background:var(--bg-secondary)}.day-name{font-size:var(--font-xs);text-transform:uppercase;color:var(--text-muted);margin-bottom:var(--space-1);font-weight:500;letter-spacing:.5px}.calendar-day.today .day-name{color:#ffffffd9}.day-date{font-size:var(--font-base);font-weight:600}.day-dot{width:5px;height:5px;border-radius:50%;background:var(--success);margin-top:var(--space-1)}.calendar-day.today .day-dot{background:#ffffffd9}.todays-workout{display:flex;flex-direction:column;gap:var(--space-4)}.todays-workout h2{font-size:var(--font-base);font-weight:600;color:var(--text-secondary)}.workout-card{background:var(--bg-card);border-radius:var(--radius-xl);padding:var(--space-5);border:1px solid var(--border);cursor:pointer;transition:all var(--transition-base);box-shadow:var(--shadow-card)}.workout-card:hover{border-color:var(--accent);transform:translateY(-2px);box-shadow:var(--shadow-md)}.workout-card:active{transform:scale(.98)}.workout-card-header{display:flex;justify-content:space-between;align-items:center;margin-bottom:var(--space-4)}.workout-card-header h3{font-size:var(--font-lg);font-weight:600}.workout-duration{font-size:var(--font-sm);color:var(--text-muted)}.workout-exercises{display:flex;flex-direction:column;gap:var(--space-2);margin-bottom:var(--space-4)}.exercise-preview{display:flex;justify-content:space-between;padding:var(--space-2) 0;border-bottom:1px solid var(--border)}.exercise-preview:last-child{border-bottom:none}.exercise-name{font-weight:500}.exercise-sets{color:var(--text-muted);font-size:var(--font-sm)}.start-workout-btn{width:100%;background:var(--accent);color:#fff;border:none;padding:var(--space-4);border-radius:var(--radius-lg);font-size:var(--font-base);font-weight:600;cursor:pointer;transition:all var(--transition-base);min-height:48px;box-shadow:0 4px 12px #ff6b4a4d}.start-workout-btn:hover{background:var(--accent-hover);transform:translateY(-1px);box-shadow:0 6px 20px #ff6b4a66}.start-workout-btn:active{transform:translateY(0)}.rest-day-card{background:var(--bg-card);border-radius:var(--radius-xl);padding:var(--space-10);border:1px solid var(--border);text-align:center;box-shadow:var(--shadow-card)}.rest-icon{font-size:3rem;margin-bottom:var(--space-4)}.rest-day-card h3{font-size:var(--font-lg);margin-bottom:var(--space-2)}.rest-day-card p{color:var(--text-muted);margin-bottom:var(--space-4)}.rest-tips{display:flex;justify-content:center;gap:var(--space-3);flex-wrap:wrap}.rest-tips span{background:var(--bg-secondary);padding:var(--space-2) var(--space-3);border-radius:var(--radius-full);font-size:var(--font-sm);border:1px solid var(--border)}.quick-stats{display:grid;grid-template-columns:repeat(3,1fr);gap:var(--space-3)}.stat-card{background:var(--bg-card);border-radius:var(--radius-lg);padding:var(--space-4);text-align:center;border:1px solid var(--border);box-shadow:var(--shadow-card);transition:all var(--transition-base)}.stat-card:hover{border-color:var(--border-hover);transform:translateY(-1px)}.stat-value{display:block;font-size:var(--font-2xl);font-weight:700;color:var(--accent);line-height:1.2}.stat-label{font-size:var(--font-xs);color:var(--text-muted);text-transform:uppercase;letter-spacing:.5px;font-weight:500;margin-top:var(--space-1)}.stat-icon{display:flex;align-items:center;justify-content:center}.brand-title{display:flex;align-items:center;gap:var(--space-2)}.upcoming-workouts h2{font-size:var(--font-base);font-weight:600;margin-bottom:var(--space-3)}.upcoming-list{display:flex;flex-direction:column;gap:var(--space-2)}.upcoming-item{display:flex;align-items:center;gap:var(--space-4);background:var(--bg-card);padding:var(--space-4);border-radius:var(--radius-lg);border:1px solid var(--border);cursor:pointer;transition:all var(--transition-base);box-shadow:var(--shadow-card)}.upcoming-item:hover{border-color:var(--accent);transform:translate(4px)}.upcoming-day{font-weight:600;width:40px;color:var(--accent);font-size:var(--font-sm)}.upcoming-name{flex:1;font-weight:500}.upcoming-arrow{color:var(--text-muted)}.coach-section{display:flex;flex-direction:column;gap:var(--space-4)}.today-workout-card{display:flex;align-items:center;justify-content:space-between;background:linear-gradient(135deg,var(--accent) 0%,#6366f1 100%);border-radius:var(--radius-xl);padding:var(--space-5);cursor:pointer;transition:all var(--transition-base);color:#fff;box-shadow:0 8px 24px #6366f140;position:relative;overflow:hidden}.today-workout-card:before{content:"";position:absolute;top:0;right:0;bottom:0;left:0;background:linear-gradient(180deg,rgba(255,255,255,.1) 0%,transparent 50%);pointer-events:none}.today-workout-card:hover{transform:translateY(-2px);box-shadow:0 12px 32px #6366f14d}.today-workout-card:active{transform:scale(.98)}.workout-info h3{font-size:var(--font-lg);font-weight:600;margin-bottom:var(--space-1)}.workout-meta{font-size:var(--font-sm);opacity:.9}.workout-action{background:#fff3;width:48px;height:48px;min-width:48px;min-height:48px;border-radius:50%;display:flex;align-items:center;justify-content:center;-webkit-backdrop-filter:blur(8px);backdrop-filter:blur(8px);border:2px solid rgba(255,255,255,.2)}.action-arrow{font-size:var(--font-xl)}.rest-day-section{display:flex;flex-direction:column;gap:var(--space-4)}.rest-day-section .rest-tips{display:flex;flex-wrap:wrap;gap:var(--space-2)}.tip-badge{display:flex;align-items:center;gap:var(--space-2);background:var(--bg-card);border:1px solid var(--border);padding:var(--space-2) var(--space-3);border-radius:var(--radius-full);font-size:var(--font-sm);transition:all var(--transition-base)}.tip-badge:hover{border-color:var(--accent)}.add-workout-btn{display:flex;align-items:center;justify-content:center;gap:var(--space-2);background:var(--bg-card);border:2px dashed var(--border);border-radius:var(--radius-xl);padding:var(--space-5);cursor:pointer;color:var(--text-muted);transition:all var(--transition-base);font-size:var(--font-base);min-height:56px}.add-workout-btn:hover{border-color:var(--accent);color:var(--accent);background:var(--bg-secondary)}.add-icon{font-size:var(--font-2xl);font-weight:300}.profile-page,.progress-page{min-height:100vh;background:var(--bg)}.profile-page.loading,.progress-page.loading{display:flex;flex-direction:column;justify-content:center;align-items:center;gap:var(--space-4)}.page-header{background:var(--bg-secondary);padding:var(--space-4) var(--space-5);display:flex;justify-content:space-between;align-items:center;border-bottom:1px solid var(--border);position:sticky;top:0;z-index:100;backdrop-filter:blur(12px);-webkit-backdrop-filter:blur(12px)}.page-header h1{font-size:var(--font-lg);font-weight:600}.back-btn{background:transparent;border:none;color:var(--accent);font-size:var(--font-base);cursor:pointer;padding:var(--space-2);display:flex;align-items:center;gap:var(--space-1);min-height:44px;transition:opacity var(--transition-fast)}.back-btn:hover{opacity:.8}.page-main{padding:var(--space-4);max-width:600px;margin:0 auto;display:flex;flex-direction:column;gap:var(--space-5)}.profile-section{background:var(--bg-card);border-radius:var(--radius-xl);padding:var(--space-5);border:1px solid var(--border);box-shadow:var(--shadow-card)}.section-header{display:flex;justify-content:space-between;align-items:center;margin-bottom:var(--space-4)}.section-header h2{font-size:var(--font-lg);font-weight:600}.edit-btn{background:var(--bg-secondary);border:1px solid var(--border);padding:var(--space-2) var(--space-3);border-radius:var(--radius-md);cursor:pointer;font-size:var(--font-sm);color:var(--text-primary);min-height:44px;transition:all var(--transition-base)}.edit-btn:hover{border-color:var(--accent);color:var(--accent)}.info-grid{display:grid;grid-template-columns:repeat(2,1fr);gap:var(--space-4)}.info-item{display:flex;flex-direction:column;gap:var(--space-1)}.info-label{font-size:var(--font-xs);color:var(--text-muted);text-transform:uppercase;letter-spacing:.5px;font-weight:500}.info-value{font-size:var(--font-base);font-weight:500}.edit-form{display:flex;flex-direction:column;gap:var(--space-4)}.form-row{display:grid;grid-template-columns:1fr 1fr;gap:var(--space-4)}.form-group{display:flex;flex-direction:column;gap:var(--space-2)}.form-group label{font-size:var(--font-sm);color:var(--text-muted);font-weight:500}.form-group input,.form-group select{padding:var(--space-3) var(--space-4);border:1px solid var(--border);border-radius:var(--radius-md);background:var(--bg-secondary);color:var(--text-primary);font-size:16px;transition:border-color var(--transition-fast),box-shadow var(--transition-fast)}.form-group input:hover,.form-group select:hover{border-color:var(--border-hover)}.form-group input:focus,.form-group select:focus{outline:none;border-color:var(--accent);box-shadow:0 0 0 3px var(--accent-subtle)}.form-actions{display:flex;gap:var(--space-3);margin-top:var(--space-2)}.cancel-btn{flex:1;padding:var(--space-3);border:1px solid var(--border);background:var(--bg-secondary);border-radius:var(--radius-md);cursor:pointer;color:var(--text-primary);min-height:48px;transition:all var(--transition-base)}.cancel-btn:hover{border-color:var(--border-hover)}.save-btn{flex:1;padding:var(--space-3);border:none;background:var(--accent);color:#fff;border-radius:var(--radius-md);cursor:pointer;font-weight:600;min-height:48px;box-shadow:0 4px 12px #ff6b4a4d;transition:all var(--transition-base)}.save-btn:hover:not(:disabled){background:var(--accent-hover);transform:translateY(-1px);box-shadow:0 6px 20px #ff6b4a66}.save-btn:disabled{opacity:.7}.measurements-grid{display:grid;grid-template-columns:repeat(2,1fr);gap:var(--space-4)}.measurement-card{background:var(--bg-secondary);padding:var(--space-4);border-radius:var(--radius-lg);display:flex;flex-direction:column;align-items:center;gap:var(--space-1)}.measurement-icon{font-size:var(--font-xl)}.measurement-value{font-size:var(--font-lg);font-weight:700;color:var(--accent)}.measurement-label{font-size:var(--font-xs);color:var(--text-muted)}.strength-grid{display:flex;flex-direction:column;gap:var(--space-3)}.strength-card{display:flex;justify-content:space-between;padding:var(--space-4);background:var(--bg-secondary);border-radius:var(--radius-lg)}.strength-exercise{font-weight:500}.strength-value{font-weight:700;color:var(--accent)}.no-data{color:var(--text-muted);text-align:center;padding:var(--space-4)}.progress-tabs{display:flex;gap:var(--space-2);background:var(--bg-card);padding:var(--space-2);border-radius:var(--radius-lg);border:1px solid var(--border)}.tab-btn{flex:1;padding:var(--space-3);border:none;background:transparent;border-radius:var(--radius-md);cursor:pointer;font-size:var(--font-sm);color:var(--text-muted);transition:all var(--transition-base);min-height:48px}.tab-btn.active{background:var(--accent);color:#fff;box-shadow:0 4px 12px #ff6b4a40}.tab-btn:hover:not(.active){background:var(--bg-secondary);color:var(--text-primary)}.chart-section{background:var(--bg-card);border-radius:var(--radius-xl);padding:var(--space-5);border:1px solid var(--border);box-shadow:var(--shadow-card)}.chart-section h2{font-size:var(--font-lg);font-weight:600;margin-bottom:var(--space-4)}.chart-container{margin-bottom:var(--space-4)}.line-chart{width:100%;max-width:100%}.chart-labels{display:flex;justify-content:space-between;font-size:var(--font-xs);color:var(--text-muted);padding:0 var(--space-2)}.strength-charts{display:flex;flex-direction:column;gap:var(--space-6)}.strength-chart-item h3{font-size:var(--font-base);margin-bottom:var(--space-3)}.progress-stats{display:grid;grid-template-columns:repeat(3,1fr);gap:var(--space-2);padding-top:var(--space-3);border-top:1px solid var(--border)}.progress-stats .stat-item{text-align:center}.progress-stats .stat-label{font-size:var(--font-xs);color:var(--text-muted);text-transform:uppercase;letter-spacing:.5px}.progress-stats .stat-value{font-size:var(--font-sm);font-weight:600}.trend-up{color:var(--success)}.trend-down{color:var(--error)}.trend-neutral{color:var(--text-muted)}.empty-state{text-align:center;padding:var(--space-10)}.empty-icon{font-size:3rem;display:block;margin-bottom:var(--space-4)}.empty-state p{color:var(--text-muted)}.empty-hint{font-size:var(--font-sm);margin-top:var(--space-2)}.workout-page{min-height:100vh;background:var(--bg)}.workout-page .page-header{display:grid;grid-template-columns:auto 1fr auto;gap:var(--space-4);align-items:center}.workout-page .header-center{text-align:center}.workout-page .header-center h1{font-size:var(--font-base);font-weight:600;margin:0}.workout-page .header-subtitle{font-size:var(--font-xs);color:var(--text-muted)}.workout-page .header-progress{background:var(--accent);color:#fff;padding:var(--space-1) var(--space-3);border-radius:var(--radius-full);font-size:var(--font-sm);font-weight:600}.workout-page .workout-main{padding-bottom:var(--space-8)}.workout-progress-bar{height:4px;background:var(--border);border-radius:2px;margin-bottom:var(--space-5);overflow:hidden}.workout-progress-fill{height:100%;background:linear-gradient(90deg,var(--accent),var(--success));border-radius:2px;transition:width var(--transition-slow)}.warmup-section{background:var(--bg-card);border-radius:var(--radius-xl);border:1px solid var(--border);margin-bottom:var(--space-5);overflow:hidden;transition:all var(--transition-base);box-shadow:var(--shadow-card)}.warmup-section.completed{border-color:var(--success);background:var(--success-subtle)}.warmup-header{display:flex;justify-content:space-between;align-items:center;padding:var(--space-4) var(--space-5);cursor:pointer;-webkit-user-select:none;user-select:none}.warmup-title{display:flex;align-items:center;gap:var(--space-3)}.warmup-icon{display:flex;align-items:center;color:var(--accent)}.warmup-title h2{font-size:var(--font-base);font-weight:600;margin:0}.warmup-progress{background:var(--bg-secondary);padding:var(--space-1) var(--space-3);border-radius:var(--radius-full);font-size:var(--font-xs);color:var(--text-muted);font-weight:500}.expand-icon{display:flex;align-items:center;color:var(--text-muted);transition:transform var(--transition-base)}.expand-icon.expanded{transform:rotate(180deg)}.warmup-content{padding:0 var(--space-5) var(--space-5)}.warmup-category{margin-bottom:var(--space-5)}.warmup-category:last-of-type{margin-bottom:var(--space-4)}.warmup-category h3{font-size:var(--font-sm);color:var(--text-muted);margin-bottom:var(--space-3);font-weight:500}.warmup-list{display:flex;flex-direction:column;gap:var(--space-2)}.warmup-item{display:flex;align-items:center;gap:var(--space-3);padding:var(--space-3);background:var(--bg-secondary);border-radius:var(--radius-md);cursor:pointer;transition:all var(--transition-base);min-height:48px;border:1px solid transparent}.warmup-item:hover{background:var(--bg-tertiary);border-color:var(--border)}.warmup-item.done{background:var(--success-subtle);border-color:#22c55e4d}.warmup-item.done .warmup-name{text-decoration:line-through;opacity:.7}.warmup-check{width:26px;height:26px;min-width:26px;display:flex;align-items:center;justify-content:center;border-radius:50%;background:var(--bg-card);color:var(--text-muted);font-size:var(--font-sm);flex-shrink:0;border:2px solid var(--border)}.warmup-item.done .warmup-check{background:var(--success);color:#fff;border-color:var(--success)}.warmup-item-icon{font-size:var(--font-lg);flex-shrink:0}.warmup-name{flex:1;font-size:var(--font-sm)}.warmup-duration{font-size:var(--font-sm);color:var(--text-muted);white-space:nowrap}.warmup-done-btn{width:100%;padding:var(--space-4);background:var(--bg-secondary);border:2px dashed var(--border);border-radius:var(--radius-lg);color:var(--text-muted);font-size:var(--font-sm);cursor:pointer;transition:all var(--transition-base);display:flex;align-items:center;justify-content:center;gap:var(--space-2);min-height:48px}.warmup-done-btn:hover{border-color:var(--accent);color:var(--accent)}.warmup-done-btn.completed{background:var(--success);border:none;color:#fff;font-weight:600;border:2px solid var(--success)}.exercises-section{margin-bottom:var(--space-5)}.exercises-section h2{font-size:var(--font-base);font-weight:600;margin-bottom:var(--space-4)}.exercise-card.all-done{border-color:var(--success);background:var(--success-subtle)}.exercise-card.all-done .exercise-info h3:after{content:" ✓";color:var(--success)}.finish-workout-btn{width:100%;padding:var(--space-4);background:var(--bg-card);border:1px solid var(--border);border-radius:var(--radius-xl);color:var(--text-muted);font-size:var(--font-base);cursor:pointer;transition:all var(--transition-base);margin-top:var(--space-4);min-height:52px;box-shadow:var(--shadow-card)}.finish-workout-btn:hover{border-color:var(--accent);color:var(--accent)}.finish-workout-btn.ready{background:linear-gradient(135deg,var(--accent) 0%,#6366f1 100%);border:none;color:#fff;font-weight:600;animation:pulse-glow 2s infinite;box-shadow:0 8px 24px #6366f14d}@keyframes pulse-glow{0%,to{box-shadow:0 8px 24px #6366f14d}50%{box-shadow:0 12px 32px #6366f180}}@media (max-width: 480px){.workout-page .page-header{padding:var(--space-3) var(--space-4)}.workout-page .header-center h1{font-size:var(--font-sm)}.warmup-item{padding:var(--space-3)}.warmup-name{font-size:var(--font-sm)}}.workout-list{display:flex;flex-direction:column;gap:var(--space-3)}.workout-card.compact{padding:var(--space-4)}.workout-card.compact .workout-card-header{margin-bottom:var(--space-2)}.workout-card.compact .workout-card-header h3{font-size:var(--font-base)}.workout-day{font-size:var(--font-xs);color:var(--text-muted);background:var(--bg-secondary);padding:var(--space-1) var(--space-2);border-radius:var(--radius-sm);border:1px solid var(--border)}.workout-exercises.compact{display:flex;flex-wrap:wrap;gap:var(--space-2);margin-bottom:0}.stepper-wrapper{display:flex;flex-direction:column;gap:var(--space-1);width:100%}.stepper-label{font-size:var(--font-xs);color:var(--text-muted);font-weight:500;text-transform:uppercase;letter-spacing:.5px}.stepper-container{display:flex;align-items:center;gap:var(--space-1);background:var(--bg-card);border-radius:var(--radius-md);border:1px solid var(--border);padding:var(--space-1);height:52px}.stepper-btn{width:44px;height:44px;min-width:44px;min-height:44px;background:var(--bg-secondary);border:none;border-radius:var(--radius-sm);color:var(--text-primary);font-size:1.4rem;font-weight:300;cursor:pointer;display:flex;align-items:center;justify-content:center;transition:all var(--transition-base);flex-shrink:0;line-height:1}.stepper-btn:hover:not(:disabled){background:var(--accent);color:#fff;box-shadow:0 4px 12px #ff6b4a40}.stepper-btn:active:not(:disabled){transform:scale(.94)}.stepper-btn:disabled{opacity:.35;cursor:not-allowed}.stepper-input-wrapper{flex:1;display:flex;align-items:center;justify-content:center;gap:var(--space-1);min-width:0}.stepper-input{flex:1;min-width:0;background:transparent;border:none;color:var(--text-primary);font-size:16px;font-weight:600;text-align:center;padding:var(--space-2);outline:none;font-family:inherit}.stepper-input:disabled{opacity:.6;cursor:not-allowed}.stepper-input::-webkit-outer-spin-button,.stepper-input::-webkit-inner-spin-button{-webkit-appearance:none;margin:0}.stepper-input[type=number]{-moz-appearance:textfield}.input-suffix{color:var(--text-muted);font-size:var(--font-sm);font-weight:500;white-space:nowrap;flex-shrink:0}@media (max-width: 480px){.stepper-container{height:56px}.stepper-btn{width:48px;height:48px;min-width:48px;min-height:48px}}.add-set-btn{display:flex;align-items:center;justify-content:center;width:100%;min-height:48px;margin-top:var(--space-2);padding:var(--space-3) var(--space-4);background:transparent;border:1px dashed var(--border);border-radius:var(--radius-md);color:var(--text-secondary);font-size:var(--font-sm);font-weight:500;cursor:pointer;transition:all var(--transition-base)}.add-set-btn:hover{border-color:var(--accent);color:var(--accent)}.delete-set-btn{display:flex;align-items:center;justify-content:center;width:36px;min-height:44px;background:transparent;border:none;color:var(--text-secondary);cursor:pointer;opacity:.6;transition:all var(--transition-base);flex-shrink:0}.delete-set-btn:hover:not(:disabled){color:#e53e3e;opacity:1}.delete-set-btn:disabled,.delete-set-btn.disabled{opacity:.2;cursor:not-allowed}.set-type-modal-overlay{position:fixed;top:0;right:0;bottom:0;left:0;background:#0009;-webkit-backdrop-filter:blur(4px);backdrop-filter:blur(4px);display:flex;align-items:flex-end;justify-content:center;z-index:200;padding-bottom:env(safe-area-inset-bottom,0)}.set-type-modal{background:var(--bg-card);border-radius:var(--radius-2xl) var(--radius-2xl) 0 0;padding:var(--space-6) var(--space-4) var(--space-8);width:100%;max-width:600px;display:flex;flex-direction:column;gap:var(--space-3);box-shadow:var(--shadow-xl)}.set-type-modal h3{font-size:var(--font-base);font-weight:600;color:var(--text-primary);margin:0 0 var(--space-1);text-align:center}.set-type-option{display:flex;flex-direction:column;align-items:flex-start;gap:var(--space-1);width:100%;min-height:56px;padding:var(--space-3) var(--space-4);background:var(--bg-secondary);border:1px solid var(--border);border-radius:var(--radius-md);cursor:pointer;text-align:left;transition:all var(--transition-base)}.set-type-option strong{font-size:var(--font-base);color:var(--text-primary)}.set-type-option span{font-size:var(--font-sm);color:var(--text-secondary)}.set-type-option:hover{border-color:var(--accent);background:var(--bg-tertiary)}.set-type-option.dropset strong{color:var(--accent)}.set-type-cancel{width:100%;min-height:48px;padding:var(--space-3);background:transparent;border:none;color:var(--text-secondary);font-size:var(--font-sm);cursor:pointer;margin-top:var(--space-1);transition:color var(--transition-base)}.set-type-cancel:hover{color:var(--text-primary)}.alternative-modal-overlay{position:fixed;top:0;right:0;bottom:0;left:0;background:#0000008c;-webkit-backdrop-filter:blur(4px);backdrop-filter:blur(4px);display:flex;align-items:flex-end;justify-content:center;z-index:220;padding-bottom:env(safe-area-inset-bottom,0)}.alternative-modal{background:var(--bg-card);border-radius:var(--radius-2xl) var(--radius-2xl) 0 0;padding:var(--space-5) var(--space-4) var(--space-7);width:100%;max-width:640px;display:flex;flex-direction:column;gap:var(--space-3);box-shadow:var(--shadow-xl)}.alternative-modal-header{display:flex;justify-content:space-between;align-items:flex-start;gap:var(--space-3)}.alternative-modal-header h3{font-size:var(--font-base);margin:0 0 var(--space-1);color:var(--text-primary)}.alternative-modal-header p{margin:0;color:var(--text-secondary);font-size:var(--font-sm)}.alternative-modal-close{border:none;background:var(--bg-secondary);color:var(--text-secondary);width:36px;height:36px;border-radius:var(--radius-full);display:inline-flex;align-items:center;justify-content:center;cursor:pointer;transition:all var(--transition-base)}.alternative-modal-close:hover{color:var(--text-primary);background:var(--bg-tertiary)}.alternative-modal-state{padding:var(--space-4);text-align:center;color:var(--text-secondary);font-size:var(--font-sm);background:var(--bg-secondary);border-radius:var(--radius-md);border:1px solid var(--border)}.alternative-modal-state.error{color:#e53e3e}.alternative-list{display:flex;flex-direction:column;gap:var(--space-3)}.alternative-item{display:flex;justify-content:space-between;align-items:center;gap:var(--space-3);padding:var(--space-3) var(--space-4);border:1px solid var(--border);border-radius:var(--radius-md);background:var(--bg-secondary)}.alternative-info{display:flex;flex-direction:column;gap:var(--space-1)}.alternative-info strong{font-size:var(--font-sm);color:var(--text-primary)}.alternative-info span{font-size:var(--font-xs);color:var(--text-secondary)}.alternative-select-btn{min-width:72px;padding:var(--space-2) var(--space-3);border:none;border-radius:var(--radius-md);background:var(--accent);color:#fff;font-size:var(--font-sm);cursor:pointer;transition:transform var(--transition-base),box-shadow var(--transition-base)}.alternative-select-btn:hover{transform:translateY(-1px);box-shadow:0 4px 12px #ff6b4a40}.select-page{min-height:100vh;background:var(--bg)}.select-page.loading{display:flex;flex-direction:column;justify-content:center;align-items:center;gap:var(--space-4)}.select-main{padding:var(--space-4);max-width:600px;margin:0 auto}.select-intro{text-align:center;color:var(--text-muted);margin-bottom:var(--space-6);font-size:var(--font-base)}.workout-grid{display:flex;flex-direction:column;gap:var(--space-4)}.workout-select-card{display:flex;align-items:center;gap:var(--space-4);background:var(--bg-card);border:2px solid var(--border);border-radius:var(--radius-xl);padding:var(--space-4);cursor:pointer;transition:all var(--transition-base);position:relative;box-shadow:var(--shadow-card)}.workout-select-card:hover{border-color:var(--workout-color, var(--accent));transform:translate(4px);box-shadow:var(--shadow-md)}.workout-select-card.selected{border-color:var(--workout-color, var(--accent));background:var(--bg);box-shadow:0 4px 16px #ff6b4a26}.workout-icon{width:56px;height:56px;min-width:56px;border-radius:var(--radius-lg);display:flex;align-items:center;justify-content:center;font-size:var(--font-xl);flex-shrink:0}.workout-details{flex:1}.workout-details h3{font-size:var(--font-base);font-weight:600;margin-bottom:var(--space-1)}.workout-exercises-count{font-size:var(--font-sm);color:var(--text-muted);margin-bottom:var(--space-2)}.workout-preview{display:flex;flex-wrap:wrap;gap:var(--space-1)}.preview-exercise{font-size:var(--font-xs);color:var(--text-muted);background:var(--bg-secondary);padding:var(--space-1) var(--space-2);border-radius:var(--radius-sm);border:1px solid var(--border)}.preview-more{font-size:var(--font-xs);color:var(--accent)}.selected-indicator{position:absolute;top:-8px;right:-8px;width:28px;height:28px;background:var(--workout-color, var(--accent));color:#fff;border-radius:50%;display:flex;align-items:center;justify-content:center;font-weight:700;box-shadow:0 2px 8px #0003}.select-action{position:fixed;bottom:0;left:0;right:0;padding:var(--space-4);background:var(--bg);border-top:1px solid var(--border);backdrop-filter:blur(12px);-webkit-backdrop-filter:blur(12px)}.start-btn{width:100%;max-width:600px;margin:0 auto;display:block;padding:var(--space-4);background:var(--accent);color:#fff;border:none;border-radius:var(--radius-lg);font-size:var(--font-lg);font-weight:600;cursor:pointer;transition:all var(--transition-base);min-height:52px;box-shadow:0 4px 12px #ff6b4a4d}.start-btn:hover{background:var(--accent-hover);transform:translateY(-1px);box-shadow:0 6px 20px #ff6b4a66}.start-btn:active{transform:translateY(0)}*{margin:0;padding:0;box-sizing:border-box}:root{--bg-primary: #0a0a0f;--bg-secondary: #0d0d14;--bg-tertiary: #12121a;--bg-card: #16161f;--bg-card-hover: #1c1c28;--bg-elevated: #1a1a24;--bg: #0a0a0f;--text-primary: #ffffff;--text-secondary: #a1a1aa;--text-muted: #71717a;--text-tertiary: #52525b;--text: #ffffff;--accent: #ff6b4a;--accent-hover: #ff8066;--accent-subtle: rgba(255, 107, 74, .15);--accent-glow: rgba(255, 107, 74, .25);--success: #22c55e;--success-subtle: rgba(34, 197, 94, .15);--warning: #f59e0b;--warning-subtle: rgba(245, 158, 11, .15);--error: #ef4444;--error-subtle: rgba(239, 68, 68, .15);--border: #1f1f2a;--border-hover: #2a2a38;--border-accent: var(--accent-subtle);--shadow-sm: 0 1px 2px rgba(0, 0, 0, .4);--shadow-md: 0 4px 6px -1px rgba(0, 0, 0, .5), 0 2px 4px -2px rgba(0, 0, 0, .4);--shadow-lg: 0 10px 15px -3px rgba(0, 0, 0, .6), 0 4px 6px -4px rgba(0, 0, 0, .4);--shadow-xl: 0 20px 25px -5px rgba(0, 0, 0, .7), 0 8px 10px -6px rgba(0, 0, 0, .4);--shadow-glow: 0 0 20px var(--accent-glow);--shadow-card: 0 1px 3px rgba(0, 0, 0, .4), 0 1px 2px rgba(0, 0, 0, .3);--shadow-elevated: 0 8px 16px rgba(0, 0, 0, .4), 0 2px 4px rgba(0, 0, 0, .3);--workout-push: #ef4444;--workout-pull: #3b82f6;--workout-legs: #22c55e;--workout-shoulders: #f59e0b;--workout-upper: #8b5cf6;--workout-lower: #06b6d4;--workout-default: #ff6b4a;--font-xs: .75rem;--font-sm: .875rem;--font-base: 1rem;--font-lg: 1.125rem;--font-xl: 1.25rem;--font-2xl: 1.5rem;--font-3xl: 2rem;--space-1: .25rem;--space-2: .5rem;--space-3: .75rem;--space-4: 1rem;--space-5: 1.25rem;--space-6: 1.5rem;--space-8: 2rem;--space-10: 2.5rem;--space-12: 3rem;--transition-fast: .15s ease;--transition-base: .2s ease;--transition-slow: .3s ease;--radius-sm: 6px;--radius-md: 10px;--radius-lg: 14px;--radius-xl: 18px;--radius-2xl: 24px;--radius-full: 9999px}html,body{font-family:Inter,-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Oxygen,Ubuntu,sans-serif;background:var(--bg-primary);color:var(--text-primary);min-height:100vh;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale;line-height:1.5}h1,h2,h3,h4,h5,h6{font-weight:700;line-height:1.2}#root{min-height:100vh}button{font-family:inherit;cursor:pointer;border:none;outline:none;font-size:var(--font-base)}input{font-family:inherit;outline:none;font-size:var(--font-base)}::-webkit-scrollbar{width:8px;height:8px}::-webkit-scrollbar-track{background:var(--bg-secondary);border-radius:4px}::-webkit-scrollbar-thumb{background:var(--border);border-radius:4px}::-webkit-scrollbar-thumb:hover{background:var(--text-tertiary)}.auth-page{min-height:100vh;display:flex;align-items:center;justify-content:center;padding:var(--space-5);background:var(--bg-primary);position:relative;overflow:hidden}.auth-page:before{content:"";position:absolute;top:-50%;left:-50%;width:200%;height:200%;background:radial-gradient(ellipse at 30% 20%,rgba(255,107,74,.03) 0%,transparent 50%),radial-gradient(ellipse at 70% 80%,rgba(99,102,241,.03) 0%,transparent 50%);pointer-events:none}.auth-card{background:var(--bg-card);padding:var(--space-10) var(--space-8);border-radius:var(--radius-2xl);width:100%;max-width:420px;text-align:center;box-shadow:var(--shadow-elevated);border:1px solid var(--border);position:relative;z-index:1}.auth-card h1{font-size:var(--font-3xl);margin-bottom:var(--space-2);background:linear-gradient(135deg,var(--text-primary) 0%,var(--text-secondary) 100%);-webkit-background-clip:text;-webkit-text-fill-color:transparent;background-clip:text}.auth-card h2{color:var(--text-secondary);font-weight:500;margin-bottom:var(--space-8);font-size:var(--font-lg)}.auth-card form{display:flex;flex-direction:column;gap:var(--space-4)}.auth-card input{padding:var(--space-4) var(--space-5);border-radius:var(--radius-md);border:1px solid var(--border);background:var(--bg-secondary);color:var(--text-primary);font-size:16px;transition:border-color var(--transition-fast),box-shadow var(--transition-fast)}.auth-card input:hover{border-color:var(--border-hover)}.auth-card input:focus{border-color:var(--accent);box-shadow:0 0 0 3px var(--accent-subtle)}.auth-card input::placeholder{color:var(--text-tertiary)}.auth-card button[type=submit]{padding:var(--space-4);background:var(--accent);color:#fff;border-radius:var(--radius-md);font-size:var(--font-base);font-weight:600;transition:all var(--transition-base);box-shadow:0 4px 12px #ff6b4a4d;position:relative;overflow:hidden}.auth-card button[type=submit]:before{content:"";position:absolute;top:0;right:0;bottom:0;left:0;background:linear-gradient(180deg,rgba(255,255,255,.1) 0%,transparent 50%);pointer-events:none}.auth-card button[type=submit]:hover:not(:disabled){background:var(--accent-hover);transform:translateY(-1px);box-shadow:0 6px 20px #ff6b4a66}.auth-card button[type=submit]:active:not(:disabled){transform:translateY(0);box-shadow:0 2px 8px #ff6b4a4d}.auth-card button:disabled{opacity:.6;cursor:not-allowed}.auth-card .error{background:var(--error-subtle);color:var(--error);padding:var(--space-3) var(--space-4);border-radius:var(--radius-md);margin-bottom:var(--space-4);font-size:var(--font-sm);border:1px solid rgba(239,68,68,.2)}.auth-link{margin-top:var(--space-6);color:var(--text-muted);font-size:var(--font-sm)}.auth-link a{color:var(--accent);text-decoration:none;font-weight:500;transition:color var(--transition-fast)}.auth-link a:hover{color:var(--accent-hover);text-decoration:underline}.onboarding{min-height:100vh;display:flex;align-items:center;justify-content:center;padding:var(--space-5);background:var(--bg-primary);position:relative;overflow:hidden}.onboarding:before{content:"";position:absolute;top:-50%;left:-50%;width:200%;height:200%;background:radial-gradient(ellipse at 30% 20%,rgba(255,107,74,.04) 0%,transparent 50%),radial-gradient(ellipse at 70% 80%,rgba(99,102,241,.04) 0%,transparent 50%);pointer-events:none}.onboarding-card{background:var(--bg-card);padding:var(--space-8);border-radius:var(--radius-2xl);width:100%;max-width:520px;box-shadow:var(--shadow-elevated);border:1px solid var(--border);position:relative;z-index:1}.steps-indicator{display:flex;justify-content:center;gap:var(--space-3);margin-bottom:var(--space-8)}.steps-indicator span{width:36px;height:36px;border-radius:50%;background:var(--bg-secondary);display:flex;align-items:center;justify-content:center;font-size:var(--font-sm);font-weight:600;color:var(--text-muted);transition:all var(--transition-base);border:2px solid var(--border)}.steps-indicator span.active{background:var(--accent);color:#fff;border-color:var(--accent);box-shadow:0 4px 12px #ff6b4a4d}.step h2{margin-bottom:var(--space-6);text-align:center;font-size:var(--font-xl)}.step .hint{color:var(--text-muted);font-size:var(--font-sm);margin-bottom:var(--space-4);text-align:center}.field{margin-bottom:var(--space-4)}.field label{display:block;margin-bottom:var(--space-2);color:var(--text-secondary);font-size:var(--font-sm);font-weight:500}.field input{width:100%;padding:var(--space-3) var(--space-4);border-radius:var(--radius-md);border:1px solid var(--border);background:var(--bg-secondary);color:var(--text-primary);font-size:16px;transition:border-color var(--transition-fast),box-shadow var(--transition-fast)}.field input:hover{border-color:var(--border-hover)}.field input:focus{border-color:var(--accent);box-shadow:0 0 0 3px var(--accent-subtle)}.field input::placeholder{color:var(--text-tertiary)}.btn-group{display:flex;gap:var(--space-2)}.btn-group.vertical{flex-direction:column}.btn-group button{flex:1;padding:var(--space-3) var(--space-4);border-radius:var(--radius-md);background:var(--bg-secondary);color:var(--text-secondary);border:1px solid var(--border);transition:all var(--transition-base);font-weight:500;min-height:44px}.btn-group button:hover{border-color:var(--accent);color:var(--text-primary);background:var(--bg-tertiary)}.btn-group button.active{background:var(--accent);color:#fff;border-color:var(--accent);box-shadow:0 4px 12px #ff6b4a40}.rm-fields{display:grid;grid-template-columns:repeat(3,1fr);gap:var(--space-3);margin-top:var(--space-2)}.rm-fields .field{margin-bottom:0}.bodyfat-result{background:var(--success-subtle);color:var(--success);padding:var(--space-4);border-radius:var(--radius-md);text-align:center;margin:var(--space-4) 0;border:1px solid rgba(34,197,94,.2)}.bodyfat-result strong{font-size:var(--font-lg)}.nav-btns{display:flex;gap:var(--space-3);margin-top:var(--space-6)}.nav-btns button{flex:1;padding:var(--space-4);border-radius:var(--radius-md);font-size:var(--font-base);transition:all var(--transition-base);min-height:44px}.nav-btns button:first-child{background:var(--bg-secondary);color:var(--text-secondary);border:1px solid var(--border)}.nav-btns button:first-child:hover{background:var(--bg-tertiary);color:var(--text-primary);border-color:var(--border-hover)}.next-btn,.finish-btn{background:var(--accent)!important;color:#fff!important;font-weight:600;border:none!important;box-shadow:0 4px 12px #ff6b4a4d}.next-btn:hover:not(:disabled),.finish-btn:hover:not(:disabled){background:var(--accent-hover)!important;transform:translateY(-1px);box-shadow:0 6px 20px #ff6b4a66}button:disabled{opacity:.5;cursor:not-allowed}.header-left{display:flex;align-items:center;gap:var(--space-4)}.logout-btn{padding:var(--space-2) var(--space-3);background:var(--bg-secondary);color:var(--text-muted);border-radius:var(--radius-sm);font-size:var(--font-xs);transition:all var(--transition-base);border:1px solid var(--border)}.logout-btn:hover{background:var(--bg-tertiary);color:var(--text-primary);border-color:var(--border-hover)}input[type=text],input[type=email],input[type=password],input[type=number],input[type=tel],select,textarea{font-size:16px} diff --git a/frontend/dist/index.html b/frontend/dist/index.html index 5be1d01..c9d3d19 100644 --- a/frontend/dist/index.html +++ b/frontend/dist/index.html @@ -11,8 +11,8 @@ Gravl - Träning - - + +
diff --git a/frontend/src/components/exercises/ExerciseCard.jsx b/frontend/src/components/exercises/ExerciseCard.jsx new file mode 100644 index 0000000..91e5b67 --- /dev/null +++ b/frontend/src/components/exercises/ExerciseCard.jsx @@ -0,0 +1,88 @@ +import './exerciseRecommendations.css' + +const difficultyTokens = { + easy: { label: 'Easy', className: 'difficulty-easy' }, + medium: { label: 'Medium', className: 'difficulty-medium' }, + med: { label: 'Medium', className: 'difficulty-medium' }, + hard: { label: 'Hard', className: 'difficulty-hard' } +} + +const normalizeDifficulty = (difficulty) => { + if (!difficulty) return null + const key = String(difficulty).trim().toLowerCase() + return difficultyTokens[key] || { label: difficulty, className: 'difficulty-custom' } +} + +const formatDuration = (exercise) => { + const value = exercise?.duration ?? exercise?.duration_min ?? exercise?.durationMinutes + if (!value) return null + return `${value} min` +} + +const formatReps = (exercise) => { + const { reps, reps_min, reps_max, repsMin, repsMax } = exercise || {} + if (reps) return `${reps} reps` + const min = reps_min ?? repsMin + const max = reps_max ?? repsMax + if (min && max) return `${min}-${max} reps` + if (min) return `${min}+ reps` + return null +} + +function ExerciseCard({ + exercise, + onSelect, + className = '', + compact = false, + showMeta = true +}) { + if (!exercise) return null + + const difficulty = normalizeDifficulty(exercise.difficulty) + const duration = formatDuration(exercise) + const reps = formatReps(exercise) + const imageSrc = exercise.image_url || exercise.image || exercise.imageUrl + const Element = onSelect ? 'button' : 'article' + + return ( + onSelect(exercise) : undefined} + > +
+ {imageSrc ? ( + {exercise.name} + ) : ( + + )} +
+ +
+
+

{exercise.name}

+ {difficulty && ( + + {difficulty.label} + + )} +
+ + {exercise.description && !compact && ( +

{exercise.description}

+ )} + + {showMeta && (duration || reps) && ( +
+ {duration && {duration}} + {reps && {reps}} +
+ )} +
+
+ ) +} + +export default ExerciseCard diff --git a/frontend/src/components/exercises/ProgressionTracker.jsx b/frontend/src/components/exercises/ProgressionTracker.jsx new file mode 100644 index 0000000..ba54000 --- /dev/null +++ b/frontend/src/components/exercises/ProgressionTracker.jsx @@ -0,0 +1,70 @@ +import './exerciseRecommendations.css' + +const resolveStatus = (level, index, activeIndex) => { + if (level.status) return level.status + if (activeIndex == null) return 'available' + if (index < activeIndex) return 'completed' + if (index === activeIndex) return 'current' + return 'locked' +} + +function ProgressionTracker({ + title = 'Progression Path', + levels = [], + activeLevelId, + activeIndex, + onSelect, + className = '' +}) { + const resolvedActiveIndex = activeIndex != null + ? activeIndex + : levels.findIndex(level => level.id === activeLevelId) + + return ( +
+
+

{title}

+
+ +
+ {levels.map((level, index) => { + const status = resolveStatus(level, index, resolvedActiveIndex) + const levelClass = `progression-level is-${status}` + const content = ( + <> + +
+

{level.label}

+ {level.description &&

{level.description}

} +
+ + ) + + return ( +
+ {onSelect ? ( + + ) : ( + content + )} +
+ ) + })} +
+
+ ) +} + +export default ProgressionTracker diff --git a/frontend/src/components/exercises/RecommendationPanel.jsx b/frontend/src/components/exercises/RecommendationPanel.jsx new file mode 100644 index 0000000..f7558b9 --- /dev/null +++ b/frontend/src/components/exercises/RecommendationPanel.jsx @@ -0,0 +1,79 @@ +import ExerciseCard from './ExerciseCard' +import './exerciseRecommendations.css' + +const normalizeGroupLabel = (item) => { + return item.group || item.category || item.level || item.progression_level || 'Recommended' +} + +const groupRecommendations = (items) => { + if (!Array.isArray(items)) return [] + const groups = items.reduce((acc, item) => { + const label = normalizeGroupLabel(item) + if (!acc[label]) acc[label] = [] + acc[label].push(item) + return acc + }, {}) + + return Object.entries(groups).map(([title, recommendations]) => ({ + id: title, + title, + recommendations + })) +} + +function RecommendationPanel({ + title = 'Recommended Exercises', + subtitle, + recommendations = [], + groups, + layout = 'grid', + onSelect, + emptyMessage = 'No recommendations available yet.', + className = '' +}) { + const resolvedGroups = Array.isArray(groups) && groups.length > 0 + ? groups + : groupRecommendations(recommendations) + + const hasContent = resolvedGroups.some(group => group.recommendations?.length) + + return ( +
+
+
+

{title}

+ {subtitle &&

{subtitle}

} +
+
+ + {!hasContent && ( +
{emptyMessage}
+ )} + + {hasContent && ( +
+ {resolvedGroups.map(group => ( +
+
+

{group.title}

+ {group.description && {group.description}} +
+
+ {(group.recommendations || group.items || []).map(item => ( + + ))} +
+
+ ))} +
+ )} +
+ ) +} + +export default RecommendationPanel diff --git a/frontend/src/components/exercises/exerciseRecommendations.css b/frontend/src/components/exercises/exerciseRecommendations.css new file mode 100644 index 0000000..7ca1104 --- /dev/null +++ b/frontend/src/components/exercises/exerciseRecommendations.css @@ -0,0 +1,324 @@ +.recommendation-panel { + background: var(--bg-card); + border: 1px solid var(--border); + border-radius: var(--radius-xl); + padding: var(--space-5); + box-shadow: var(--shadow-card); +} + +.recommendation-panel-header { + display: flex; + align-items: flex-start; + justify-content: space-between; + gap: var(--space-4); + margin-bottom: var(--space-4); +} + +.recommendation-panel-header h2 { + font-size: var(--font-xl); + margin-bottom: var(--space-1); +} + +.recommendation-panel-header p { + color: var(--text-secondary); + font-size: var(--font-sm); +} + +.recommendation-panel-body { + display: flex; + flex-direction: column; + gap: var(--space-6); +} + +.recommendation-empty { + color: var(--text-secondary); + font-size: var(--font-sm); + padding: var(--space-4); + border-radius: var(--radius-lg); + background: var(--bg-secondary); + border: 1px dashed var(--border); +} + +.recommendation-group-header { + display: flex; + align-items: baseline; + justify-content: space-between; + gap: var(--space-3); + margin-bottom: var(--space-3); +} + +.recommendation-group-header h3 { + font-size: var(--font-lg); +} + +.recommendation-group-header span { + color: var(--text-muted); + font-size: var(--font-xs); +} + +.recommendation-list { + display: grid; + gap: var(--space-3); +} + +.recommendation-list--grid { + grid-template-columns: repeat(auto-fit, minmax(220px, 1fr)); +} + +.recommendation-list--list { + grid-template-columns: 1fr; +} + +.exercise-recommendation-card { + display: flex; + gap: var(--space-3); + align-items: stretch; + padding: var(--space-3); + border-radius: var(--radius-lg); + border: 1px solid var(--border); + background: var(--bg-tertiary); + color: var(--text-primary); + text-align: left; + transition: transform var(--transition-base), border-color var(--transition-base), box-shadow var(--transition-base); +} + +.exercise-recommendation-card:hover { + transform: translateY(-2px); + border-color: var(--border-hover); + box-shadow: var(--shadow-md); +} + +.exercise-recommendation-card.is-compact { + align-items: center; +} + +.exercise-card-media { + width: 72px; + height: 72px; + flex: 0 0 auto; + border-radius: var(--radius-md); + overflow: hidden; + background: var(--bg-secondary); + border: 1px solid var(--border); + display: flex; + align-items: center; + justify-content: center; +} + +.exercise-card-media img { + width: 100%; + height: 100%; + object-fit: cover; +} + +.exercise-card-placeholder { + width: 100%; + height: 100%; + display: flex; + align-items: center; + justify-content: center; + color: var(--text-secondary); + font-weight: 700; + font-size: var(--font-lg); + background: linear-gradient(135deg, var(--bg-card), var(--bg-secondary)); +} + +.exercise-card-content { + flex: 1; + display: flex; + flex-direction: column; + gap: var(--space-2); +} + +.exercise-card-header { + display: flex; + align-items: center; + justify-content: space-between; + gap: var(--space-2); +} + +.exercise-card-header h3 { + font-size: var(--font-base); +} + +.exercise-card-description { + color: var(--text-secondary); + font-size: var(--font-xs); +} + +.exercise-card-meta { + display: flex; + flex-wrap: wrap; + gap: var(--space-2); +} + +.exercise-meta-pill { + padding: var(--space-1) var(--space-2); + border-radius: var(--radius-full); + background: var(--bg-secondary); + border: 1px solid var(--border); + font-size: var(--font-xs); + color: var(--text-secondary); +} + +.difficulty-badge { + padding: 2px 8px; + border-radius: var(--radius-full); + font-size: var(--font-xs); + font-weight: 600; + text-transform: uppercase; + letter-spacing: 0.4px; +} + +.difficulty-easy { + background: var(--success-subtle); + color: var(--success); +} + +.difficulty-medium { + background: var(--warning-subtle); + color: var(--warning); +} + +.difficulty-hard { + background: var(--error-subtle); + color: var(--error); +} + +.difficulty-custom { + background: var(--accent-subtle); + color: var(--accent); +} + +.progression-tracker { + background: var(--bg-card); + border: 1px solid var(--border); + border-radius: var(--radius-xl); + padding: var(--space-5); + box-shadow: var(--shadow-card); +} + +.progression-tracker-header { + margin-bottom: var(--space-4); +} + +.progression-tracker-header h2 { + font-size: var(--font-lg); +} + +.progression-track { + display: grid; + gap: var(--space-3); +} + +.progression-level { + display: grid; + grid-template-columns: auto 1fr; + gap: var(--space-3); + align-items: center; +} + +.progression-node { + width: 36px; + height: 36px; + border-radius: 50%; + border: 2px solid var(--border); + display: flex; + align-items: center; + justify-content: center; + font-weight: 600; + color: var(--text-secondary); + background: var(--bg-secondary); + position: relative; +} + +.progression-node::after { + content: ''; + position: absolute; + top: 34px; + left: 50%; + width: 2px; + height: calc(100% + var(--space-3)); + transform: translateX(-50%); + background: var(--border); +} + +.progression-level:last-child .progression-node::after { + display: none; +} + +.progression-level.is-completed .progression-node, +.progression-level.is-current .progression-node { + border-color: var(--accent); + color: var(--accent); + background: var(--accent-subtle); +} + +.progression-level.is-completed .progression-node { + color: var(--success); + border-color: var(--success); + background: var(--success-subtle); +} + +.progression-level.is-locked .progression-node { + opacity: 0.5; +} + +.progression-info h3 { + font-size: var(--font-base); + margin-bottom: var(--space-1); +} + +.progression-info p { + color: var(--text-secondary); + font-size: var(--font-sm); +} + +.progression-level.is-current .progression-info h3 { + color: var(--accent); +} + +.progression-level.is-completed .progression-info h3 { + color: var(--success); +} + +.progression-level-button { + background: transparent; + border: none; + padding: 0; + text-align: left; + color: inherit; + display: grid; + grid-template-columns: auto 1fr; + gap: var(--space-3); + align-items: center; + width: 100%; +} + +@media (min-width: 720px) { + .progression-track { + grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); + } + + .progression-level { + grid-template-columns: 1fr; + text-align: center; + } + + .progression-node::after { + top: 50%; + left: 36px; + width: calc(100% + var(--space-3)); + height: 2px; + transform: translateY(-50%); + } + + .progression-level:last-child .progression-node::after { + display: none; + } + + .progression-level, + .progression-level-button { + justify-items: center; + } +} diff --git a/frontend/src/types/exerciseRecommendations.ts b/frontend/src/types/exerciseRecommendations.ts new file mode 100644 index 0000000..fe74585 --- /dev/null +++ b/frontend/src/types/exerciseRecommendations.ts @@ -0,0 +1,50 @@ +export type Difficulty = 'Easy' | 'Medium' | 'Hard' | 'Beginner' | 'Intermediate' | 'Advanced' + +export interface ExerciseRecommendation { + id?: string | number + name: string + description?: string + difficulty?: Difficulty | string + duration?: number + duration_min?: number + durationMinutes?: number + reps?: string | number + reps_min?: number + reps_max?: number + repsMin?: number + repsMax?: number + image_url?: string + image?: string + imageUrl?: string + group?: string + category?: string + level?: string + progression_level?: string + equipment?: string[] + tags?: string[] + rationale?: string +} + +export interface RecommendationGroup { + id?: string + title: string + description?: string + recommendations?: ExerciseRecommendation[] + items?: ExerciseRecommendation[] +} + +export type ProgressionStatus = 'completed' | 'current' | 'available' | 'locked' + +export interface ProgressionLevel { + id?: string + label: string + description?: string + status?: ProgressionStatus +} + +export interface ExerciseRecommendationResponse { + recommendations: ExerciseRecommendation[] + groups?: RecommendationGroup[] + progression?: ProgressionLevel[] + meta?: Record +}