<!DOCTYPE html>

<!-- build: 1784862092 -->

<html lang="pt-BR">

<head>

<meta charset="UTF-8" />

<meta name="viewport" content="width=device-width, initial-scale=1.0" />

<title>FORJA — Treino</title>

<style>

  @import url('https://fonts.googleapis.com/css2?family=Archivo+Black&family=Inter:wght@400;500;600;700&display=swap');

  * { box-sizing: border-box; }

  body { margin:0; }

  button { font-family: inherit; cursor: pointer; }

  input, select { font-family: inherit; }

</style>

<script src="https://unpkg.com/react@18/umd/react.production.min.js" crossorigin></script>

<script src="https://unpkg.com/react-dom@18/umd/react-dom.production.min.js" crossorigin></script>

<script src="https://unpkg.com/@babel/standalone/babel.min.js" crossorigin></script>

<script src="https://accounts.google.com/gsi/client" async defer></script>

</head>

<body>

<div id="root"></div>

<script type="text/babel" data-presets="react">

const { useState, useEffect, useCallback, useRef } = React;


// CONFIGURE AQUI para habilitar Google Drive como opção de nuvem (reaproveita o mesmo

// Client ID do login com Google, só precisa incluir o escopo drive.file no Console).

const CONFIG = {

  GOOGLE_CLIENT_ID: "COLE_AQUI_SEU_GOOGLE_CLIENT_ID.apps.googleusercontent.com",

};


// ---------------------------------------------------------------------------

// CAMADA DE ARMAZENAMENTO — arquivo local (File System Access API) com fallback

// de download/upload, e um conector opcional de Google Drive (arquivo de nome

// fixo "forja-dados.json" na conta do usuário).

// ---------------------------------------------------------------------------

const FS_SUPPORTED = typeof window.showOpenFilePicker === "function";

const DB_NAME = "forja-filestore";


function idbGet(key) {

  return new Promise((resolve) => {

    const req = indexedDB.open(DB_NAME, 1);

    req.onupgradeneeded = () => req.result.createObjectStore("handles");

    req.onsuccess = () => {

      const tx = req.result.transaction("handles", "readonly");

      const g = tx.objectStore("handles").get(key);

      g.onsuccess = () => resolve(g.result || null);

      g.onerror = () => resolve(null);

    };

    req.onerror = () => resolve(null);

  });

}

function idbSet(key, value) {

  return new Promise((resolve) => {

    const req = indexedDB.open(DB_NAME, 1);

    req.onupgradeneeded = () => req.result.createObjectStore("handles");

    req.onsuccess = () => {

      const tx = req.result.transaction("handles", "readwrite");

      tx.objectStore("handles").put(value, key);

      tx.oncomplete = () => resolve(true);

      tx.onerror = () => resolve(false);

    };

    req.onerror = () => resolve(false);

  });

}

async function verifyPermission(handle, mode) {

  const opts = { mode };

  if ((await handle.queryPermission(opts)) === "granted") return true;

  if ((await handle.requestPermission(opts)) === "granted") return true;

  return false;

}


const LocalFile = {

  supported: FS_SUPPORTED,

  async tryResume() {

    const handle = await idbGet("lastHandle");

    if (!handle) return null;

    const ok = await verifyPermission(handle, "readwrite");

    return ok ? handle : null;

  },

  async open() {

    const [handle] = await window.showOpenFilePicker({

      types: [{ description: "Dados FORJA", accept: { "application/json": [".json"] } }],

    });

    await idbSet("lastHandle", handle);

    return handle;

  },

  async create() {

    const handle = await window.showSaveFilePicker({

      suggestedName: "forja-dados.json",

      types: [{ description: "Dados FORJA", accept: { "application/json": [".json"] } }],

    });

    await idbSet("lastHandle", handle);

    return handle;

  },

  async read(handle) {

    const file = await handle.getFile();

    return JSON.parse(await file.text());

  },

  async write(handle, data) {

    const ok = await verifyPermission(handle, "readwrite");

    if (!ok) throw new Error("Permissão negada para escrever no arquivo.");

    const writable = await handle.createWritable();

    await writable.write(JSON.stringify(data, null, 2));

    await writable.close();

  },

};


// Fallback pra navegadores sem File System Access API (Safari, Firefox, iOS inteiro).

const FallbackFile = {

  openViaUpload() {

    return new Promise((resolve, reject) => {

      const input = document.createElement("input");

      input.type = "file";

      input.accept = "application/json";

      input.onchange = () => {

        const file = input.files[0];

        if (!file) return reject(new Error("Nenhum arquivo selecionado."));

        const reader = new FileReader();

        reader.onload = () => { try { resolve(JSON.parse(reader.result)); } catch { reject(new Error("Arquivo inválido.")); } };

        reader.onerror = () => reject(new Error("Falha ao ler o arquivo."));

        reader.readAsText(file);

      };

      input.click();

    });

  },

  downloadAsFile(data, filename = "forja-dados.json") {

    const blob = new Blob([JSON.stringify(data, null, 2)], { type: "application/json" });

    const url = URL.createObjectURL(blob);

    const a = document.createElement("a");

    a.href = url; a.download = filename;

    document.body.appendChild(a); a.click(); document.body.removeChild(a);

    URL.revokeObjectURL(url);

  },

};


// Google Drive: guarda um único arquivo de nome fixo na pasta de dados do app

// (escopo drive.file — o Google só deixa esse app enxergar arquivos que ele mesmo criou).

const GoogleDrive = {

  tokenClient: null,

  accessToken: null,

  ready() { return CONFIG.GOOGLE_CLIENT_ID.indexOf("COLE_AQUI") === -1 && window.google && window.google.accounts; },

  async connect() {

    return new Promise((resolve, reject) => {

      this.tokenClient = google.accounts.oauth2.initTokenClient({

        client_id: CONFIG.GOOGLE_CLIENT_ID,

        scope: "https://www.googleapis.com/auth/drive.file",

        callback: (resp) => {

          if (resp.error) return reject(new Error("Não foi possível conectar ao Google Drive."));

          this.accessToken = resp.access_token;

          resolve(resp.access_token);

        },

      });

      this.tokenClient.requestAccessToken();

    });

  },

  async findFile() {

    const res = await fetch(

      "https://www.googleapis.com/drive/v3/files?q=name%3D%27forja-dados.json%27&spaces=drive&fields=files(id,name)",

      { headers: { Authorization: `Bearer ${this.accessToken}` } }

    );

    const data = await res.json();

    return (data.files && data.files[0]) || null;

  },

  async readFile(fileId) {

    const res = await fetch(`https://www.googleapis.com/drive/v3/files/${fileId}?alt=media`, {

      headers: { Authorization: `Bearer ${this.accessToken}` },

    });

    return res.json();

  },

  async saveFile(data, fileId) {

    const metadata = { name: "forja-dados.json", mimeType: "application/json" };

    const boundary = "forja-boundary";

    const body =

      `--${boundary}\r\nContent-Type: application/json; charset=UTF-8\r\n\r\n${JSON.stringify(metadata)}\r\n` +

      `--${boundary}\r\nContent-Type: application/json\r\n\r\n${JSON.stringify(data)}\r\n--${boundary}--`;

    const url = fileId

      ? `https://www.googleapis.com/upload/drive/v3/files/${fileId}?uploadType=multipart`

      : `https://www.googleapis.com/upload/drive/v3/files?uploadType=multipart`;

    const res = await fetch(url, {

      method: fileId ? "PATCH" : "POST",

      headers: { Authorization: `Bearer ${this.accessToken}`, "Content-Type": `multipart/related; boundary=${boundary}` },

      body,

    });

    return res.json();

  },

};


// ---------------------------------------------------------------------------

// DADOS DE TREINO

// ---------------------------------------------------------------------------

const GOALS = [

  { id: "ganhar-massa", label: "Ganhar Massa Corporal", icon: "💪", color: "#D4FF3D" },

  { id: "perder-peso", label: "Perder Peso", icon: "🔥", color: "#FF3B30" },

  { id: "manter-fitness", label: "Manter-se Fitness", icon: "⚖️", color: "#3DDCFF" },

  { id: "criar-resistencia", label: "Criar Resistência", icon: "🏃", color: "#FF9F1C" },

  { id: "ganhar-forca", label: "Ganhar Força", icon: "🏋️", color: "#B78CFF" },

  { id: "perder-barriga", label: "Perder Barriga", icon: "⚡", color: "#FF6B9D" },

  { id: "definir-musculos", label: "Definir Músculos", icon: "🎯", color: "#4ADE80" },

];


const GOAL_REP_SCHEME = {

  "ganhar-massa": { reps: "8-12", setsDelta: 0 },

  "perder-peso": { reps: "12-15", setsDelta: 0 },

  "manter-fitness": { reps: "10-12", setsDelta: 0 },

  "criar-resistencia": { reps: "15-20", setsDelta: 0 },

  "ganhar-forca": { reps: "4-6", setsDelta: 1 },

  "perder-barriga": { reps: "15", setsDelta: 0 },

  "definir-musculos": { reps: "12-15", setsDelta: 0 },

};


const CARDIO_BIAS_GOALS = new Set(["perder-peso", "perder-barriga", "criar-resistencia"]);


const INTENSITY = {

  leve: { label: "Leve", desc: "Ritmo controlado, ideal para começar", setsDelta: -1, rest: "60-90s de descanso", color: "#3DDCFF" },

  medio: { label: "Médio", desc: "Equilíbrio entre volume e esforço", setsDelta: 0, rest: "45-60s de descanso", color: "#D4FF3D" },

  pesado: { label: "Pesado", desc: "Carga alta, quase até a falha", setsDelta: 1, rest: "90-120s de descanso", color: "#FF3B30" },

};


const MISSION_OPTIONS = [15, 30, 45, 60];


const FIXED_GYMS = [

  { id: "planet-fitness", label: "Planet Fitness", icon: "🪐", note: "Máquinas guiadas e Smith machine. Normalmente sem barra livre, squat rack ou kettlebell." },

  { id: "crunch", label: "Crunch Fitness", icon: "💥", note: "Halteres, barras olímpicas, squat racks, kettlebells e zona funcional." },

  { id: "la-fitness", label: "LA Fitness", icon: "🏋️", note: "Free weights completos, squat racks, Smith machine, TRX." },

  { id: "outra", label: "Outra academia", icon: "🏢", note: "Você marca os equipamentos disponíveis." },

];


const FIXED_EQUIPMENT_PROFILE = {

  "planet-fitness": { barbell: false, squatRack: false, kettlebell: false, pullupBar: true, cable: true, machine: true, smith: true, dumbbell: true, bench: true },

  "crunch": { barbell: true, squatRack: true, kettlebell: true, pullupBar: true, cable: true, machine: true, smith: true, dumbbell: true, bench: true },

  "la-fitness": { barbell: true, squatRack: true, kettlebell: true, pullupBar: true, cable: true, machine: true, smith: true, dumbbell: true, bench: true },

};


// Ícones fazem o papel de "foto de referência" de cada equipamento — optei por isso em vez

// de fotos reais pra não depender de hospedar/licenciar imagens de terceiros.

const EQUIPMENT_FIELDS = [

  { key: "dumbbell", label: "Halteres", icon: "🏋️" },

  { key: "barbell", label: "Barra olímpica", icon: "🔩" },

  { key: "squatRack", label: "Squat rack", icon: "🧱" },

  { key: "smith", label: "Smith machine", icon: "🛠️" },

  { key: "cable", label: "Cabo / polia", icon: "🔗" },

  { key: "machine", label: "Máquinas seletorizadas", icon: "⚙️" },

  { key: "kettlebell", label: "Kettlebell", icon: "🔔" },

  { key: "bench", label: "Banco", icon: "🪑" },

  { key: "pullupBar", label: "Barra fixa", icon: "➖" },

];


const MUSCLE_LIBRARY = {

  "Peito": [

    { name: "Supino reto com barra", equip: "barbell", sets: 4, reps: "8-10", repType: "count" },

    { name: "Supino inclinado com halteres", equip: "dumbbell", sets: 3, reps: "10-12", repType: "count" },

    { name: "Supino reto na máquina", equip: "machine", sets: 3, reps: "10-12", repType: "count" },

    { name: "Crucifixo na polia (cross-over)", equip: "cable", sets: 3, reps: "12-15", repType: "count" },

    { name: "Crucifixo com halteres", equip: "dumbbell", sets: 3, reps: "12-15", repType: "count" },

    { name: "Flexão de braço", equip: "bodyweight", sets: 3, reps: "12-15", repType: "count" },

    { name: "Flexão diamante", equip: "bodyweight", sets: 3, reps: "10-12", repType: "count" },

    { name: "Supino no Smith Machine", equip: "smith", sets: 4, reps: "8-10", repType: "count" },

  ],

  "Costas": [

    { name: "Puxada frontal (pulley)", equip: "cable", sets: 4, reps: "8-10", repType: "count" },

    { name: "Remada curvada com barra", equip: "barbell", sets: 4, reps: "8-10", repType: "count" },

    { name: "Remada unilateral com halter", equip: "dumbbell", sets: 3, reps: "10-12", repType: "count" },

    { name: "Remada máquina (cable row)", equip: "cable", sets: 3, reps: "10-12", repType: "count" },

    { name: "Barra fixa (pull-up)", equip: "pullupBar", sets: 3, reps: "máximo", repType: "count" },

    { name: "Puxada supinada", equip: "cable", sets: 3, reps: "10-12", repType: "count" },

    { name: "Remada baixa na máquina", equip: "machine", sets: 3, reps: "10-12", repType: "count" },

    { name: "Superman (extensão lombar no solo)", equip: "bodyweight", sets: 3, reps: "15", repType: "count" },

  ],

  "Pernas": [

    { name: "Agachamento livre com barra", equip: "barbell", sets: 4, reps: "8-10", repType: "count" },

    { name: "Agachamento no Smith Machine", equip: "smith", sets: 4, reps: "10-12", repType: "count" },

    { name: "Leg press 45°", equip: "machine", sets: 4, reps: "10-12", repType: "count" },

    { name: "Cadeira extensora", equip: "machine", sets: 3, reps: "12-15", repType: "count" },

    { name: "Mesa flexora", equip: "machine", sets: 3, reps: "12-15", repType: "count" },

    { name: "Afundo com halteres (lunge)", equip: "dumbbell", sets: 3, reps: "10 cada perna", repType: "count" },

    { name: "Panturrilha em pé", equip: "machine", sets: 4, reps: "15-20", repType: "count" },

    { name: "Agachamento livre (peso corporal)", equip: "bodyweight", sets: 4, reps: "15-20", repType: "count" },

    { name: "Levantamento terra (deadlift)", equip: "barbell", sets: 4, reps: "6-8", repType: "count" },

    { name: "Agachamento sumô com halter", equip: "dumbbell", sets: 3, reps: "12-15", repType: "count" },

  ],

  "Ombros": [

    { name: "Desenvolvimento militar com barra", equip: "barbell", sets: 4, reps: "8-10", repType: "count" },

    { name: "Desenvolvimento com halteres", equip: "dumbbell", sets: 3, reps: "10-12", repType: "count" },

    { name: "Elevação lateral com halteres", equip: "dumbbell", sets: 3, reps: "12-15", repType: "count" },

    { name: "Elevação posterior (crucifixo invertido)", equip: "dumbbell", sets: 3, reps: "12-15", repType: "count" },

    { name: "Desenvolvimento na máquina", equip: "machine", sets: 3, reps: "10-12", repType: "count" },

    { name: "Elevação lateral na polia", equip: "cable", sets: 3, reps: "12-15", repType: "count" },

  ],

  "Braços": [

    { name: "Rosca direta com barra", equip: "barbell", sets: 3, reps: "10-12", repType: "count" },

    { name: "Rosca alternada com halteres", equip: "dumbbell", sets: 3, reps: "12", repType: "count" },

    { name: "Rosca martelo", equip: "dumbbell", sets: 3, reps: "12", repType: "count" },

    { name: "Tríceps corda na polia", equip: "cable", sets: 3, reps: "12-15", repType: "count" },

    { name: "Tríceps testa com barra EZ", equip: "barbell", sets: 3, reps: "10-12", repType: "count" },

    { name: "Tríceps francês com halter", equip: "dumbbell", sets: 3, reps: "12", repType: "count" },

    { name: "Mergulho no banco (dips)", equip: "bench", sets: 3, reps: "10-15", repType: "count" },

  ],

  "Abdômen": [

    { name: "Prancha abdominal", equip: "bodyweight", sets: 3, reps: "40-60s", repType: "time" },

    { name: "Abdominal supra", equip: "bodyweight", sets: 4, reps: "20", repType: "count" },

    { name: "Abdominal infra (elevação de pernas)", equip: "bodyweight", sets: 3, reps: "15", repType: "count" },

    { name: "Russian twist", equip: "bodyweight", sets: 3, reps: "25", repType: "count" },

    { name: "Bicicleta no ar", equip: "bodyweight", sets: 3, reps: "20", repType: "count" },

    { name: "Prancha lateral", equip: "bodyweight", sets: 3, reps: "30s cada lado", repType: "time" },

    { name: "Abdominal canivete (V-up)", equip: "bodyweight", sets: 3, reps: "15", repType: "count" },

    { name: "Dead bug", equip: "bodyweight", sets: 3, reps: "12 cada lado", repType: "count" },

  ],

  "Cardio": [

    { name: "Polichinelo (jumping jack)", equip: "bodyweight", sets: 4, reps: "40s", repType: "time" },

    { name: "Burpee", equip: "bodyweight", sets: 4, reps: "12", repType: "count" },

    { name: "Mountain climbers", equip: "bodyweight", sets: 4, reps: "30s", repType: "time" },

    { name: "Corrida intervalada (HIIT)", equip: "bodyweight", sets: 1, reps: "15 min", repType: "time" },

    { name: "Corda naval (battle rope) ou corrida", equip: "bodyweight", sets: 4, reps: "30s", repType: "time" },

    { name: "Agachamento com salto (jump squat)", equip: "bodyweight", sets: 4, reps: "12", repType: "count" },

    { name: "Escalador em velocidade", equip: "bodyweight", sets: 4, reps: "30s", repType: "time" },

  ],

  "Full Body": [

    { name: "Kettlebell swing", equip: "kettlebell", sets: 4, reps: "15", repType: "count" },

    { name: "Burpee com salto", equip: "bodyweight", sets: 3, reps: "10", repType: "count" },

    { name: "Agachamento com desenvolvimento (thruster leve)", equip: "dumbbell", sets: 3, reps: "12", repType: "count" },

    { name: "Prancha com toque no ombro", equip: "bodyweight", sets: 3, reps: "30s", repType: "time" },

  ],

};


const SPLIT_TEMPLATES = {

  1: [{ title: "Full Body", groups: ["Peito", "Costas", "Pernas", "Abdômen"] }],

  2: [

    { title: "Full Body A", groups: ["Peito", "Costas", "Pernas"] },

    { title: "Full Body B", groups: ["Ombros", "Braços", "Abdômen"] },

  ],

  3: [

    { title: "Peito & Tríceps", groups: ["Peito", "Braços"] },

    { title: "Costas & Bíceps", groups: ["Costas", "Braços"] },

    { title: "Pernas & Ombros", groups: ["Pernas", "Ombros"] },

  ],

  4: [

    { title: "Peito & Tríceps", groups: ["Peito", "Braços"] },

    { title: "Costas & Bíceps", groups: ["Costas", "Braços"] },

    { title: "Pernas", groups: ["Pernas"] },

    { title: "Ombros & Abdômen", groups: ["Ombros", "Abdômen"] },

  ],

  5: [

    { title: "Peito & Tríceps", groups: ["Peito", "Braços"] },

    { title: "Costas & Bíceps", groups: ["Costas", "Braços"] },

    { title: "Pernas", groups: ["Pernas"] },

    { title: "Ombros & Abdômen", groups: ["Ombros", "Abdômen"] },

    { title: "Full Body + Core", groups: ["Full Body", "Abdômen"] },

  ],

  6: [

    { title: "Peito", groups: ["Peito"] },

    { title: "Costas", groups: ["Costas"] },

    { title: "Pernas", groups: ["Pernas"] },

    { title: "Ombros", groups: ["Ombros"] },

    { title: "Braços", groups: ["Braços"] },

    { title: "Abdômen & Cardio", groups: ["Abdômen", "Cardio"] },

  ],

  7: [

    { title: "Peito", groups: ["Peito"] },

    { title: "Costas", groups: ["Costas"] },

    { title: "Pernas", groups: ["Pernas"] },

    { title: "Ombros", groups: ["Ombros"] },

    { title: "Braços", groups: ["Braços"] },

    { title: "Abdômen & Cardio", groups: ["Abdômen", "Cardio"] },

    { title: "Cardio & Mobilidade", groups: ["Cardio", "Abdômen"] },

  ],

};


function shuffle(arr) {

  const a = [...arr];

  for (let i = a.length - 1; i > 0; i--) {

    const j = Math.floor(Math.random() * (i + 1));

    [a[i], a[j]] = [a[j], a[i]];

  }

  return a;

}


function getEquipProfile(gym) {

  if (!gym) return null;

  if (gym.id !== "outra" && FIXED_EQUIPMENT_PROFILE[gym.id]) return FIXED_EQUIPMENT_PROFILE[gym.id];

  return gym.equipment || null; // "outra": usa o que a pessoa marcou; se nada marcado, null = mostra tudo mesmo assim

}


function filterByEquipment(group, equipProfile) {

  const list = MUSCLE_LIBRARY[group] || [];

  if (!equipProfile) return list; // sem dados confirmados: mostra tudo mesmo assim

  return list.filter((ex) => ex.equip === "bodyweight" || equipProfile[ex.equip] !== false);

}


function applyGoalAndIntensity(ex, goalId, intensityId) {

  const scheme = GOAL_REP_SCHEME[goalId] || { reps: ex.reps, setsDelta: 0 };

  const intensity = INTENSITY[intensityId] || INTENSITY.medio;

  const sets = Math.max(2, ex.sets + (scheme.setsDelta || 0) + intensity.setsDelta);

  const reps = ex.repType === "count" ? scheme.reps : ex.reps;

  return { name: ex.name, sets, reps };

}


function buildSplit(daysPerWeek, goalId) {

  const template = SPLIT_TEMPLATES[Math.min(7, Math.max(1, daysPerWeek))];

  const biasCardio = CARDIO_BIAS_GOALS.has(goalId);

  return template.map((day) => ({

    title: day.title,

    groups: biasCardio && !day.groups.includes("Cardio") ? [...day.groups, "Cardio"] : day.groups,

  }));

}


function generateAIPlan(daysPerWeek, goalId, intensityId, gym) {

  const equipProfile = getEquipProfile(gym);

  const split = buildSplit(daysPerWeek, goalId);

  return split.map((day) => {

    const pool = [];

    day.groups.forEach((g) => filterByEquipment(g, equipProfile).forEach((ex) => pool.push(ex)));

    const picked = shuffle(pool).slice(0, Math.min(5, Math.max(4, day.groups.length * 2)));

    return { title: day.title, groups: day.groups, exercises: picked.map((ex) => applyGoalAndIntensity(ex, goalId, intensityId)) };

  });

}


function youtubeSearchUrl(name) {

  return `https://www.youtube.com/results?search_query=${encodeURIComponent(name + " execução correta exercício")}`;

}

function todayISO() { return new Date().toISOString().slice(0, 10); }


function computeBmi(weightKg, heightCm) {

  const h = heightCm / 100;

  if (!weightKg || !h) return null;

  const bmi = weightKg / (h * h);

  let category = "";

  if (bmi < 18.5) category = "Abaixo do peso";

  else if (bmi < 25) category = "Peso normal";

  else if (bmi < 30) category = "Sobrepeso";

  else if (bmi < 35) category = "Obesidade grau I";

  else if (bmi < 40) category = "Obesidade grau II";

  else category = "Obesidade grau III";

  return { value: +bmi.toFixed(1), category };

}


const DEFAULT_DATA = () => ({

  version: 2,

  profile: { name: "", age: "", heightCm: "", weightKg: "", bmi: null, daysPerWeek: 3 },

  gym: null,

  planMode: null,

  goal: null,

  missionDays: null,

  missionStartDate: null,

  intensity: null,

  weeklyPlan: [],

  completedDays: [],

  currentDayIndex: 0,

  sessionHistory: [],

  metrics: [],

});


// ---------------------------------------------------------------------------

// APP

// ---------------------------------------------------------------------------


function App() {

  const [authChecked, setAuthChecked] = useState(false);

  const [me, setMe] = useState(null);

  const [storageMode, setStorageMode] = useState(null); // 'local' | 'local-fallback' | 'drive'

  const [fileHandle, setFileHandle] = useState(null);

  const [driveFileId, setDriveFileId] = useState(null);

  const [data, setData] = useState(null);

  const [view, setView] = useState("loading");

  const [storageError, setStorageError] = useState("");

  const [resumeHandle, setResumeHandle] = useState(null);


  useEffect(() => {

    (async () => {

      try {

        const meRes = await fetch("/api/me", { credentials: "include" });

        if (!meRes.ok) { window.location.href = "/login.html"; return; }

        const meData = await meRes.json();

        if (meData.status !== "active") { window.location.href = "/login.html"; return; }

        setMe(meData);

        const handle = await LocalFile.tryResume();

        setResumeHandle(handle);

        setView("storage-gate");

      } catch (e) {

        window.location.href = "/login.html";

      } finally {

        setAuthChecked(true);

      }

    })();

  }, []);


  function afterDataLoaded(loaded) {

    const merged = { ...DEFAULT_DATA(), ...loaded, profile: { ...DEFAULT_DATA().profile, ...(loaded.profile || {}) } };

    setData(merged);

    setView(merged.profile.name ? "app" : "onboarding");

  }


  async function handleOpenLocal() {

    setStorageError("");

    try {

      const handle = await LocalFile.open();

      const parsed = await LocalFile.read(handle);

      setFileHandle(handle); setStorageMode("local");

      afterDataLoaded(parsed);

    } catch (e) { setStorageError("Não foi possível abrir o arquivo: " + e.message); }

  }

  async function handleResumeLocal() {

    setStorageError("");

    try {

      const parsed = await LocalFile.read(resumeHandle);

      setFileHandle(resumeHandle); setStorageMode("local");

      afterDataLoaded(parsed);

    } catch (e) { setStorageError("Não foi possível reabrir o último arquivo: " + e.message); }

  }

  async function handleCreateLocal() {

    setStorageError("");

    try {

      const handle = await LocalFile.create();

      const fresh = DEFAULT_DATA();

      await LocalFile.write(handle, fresh);

      setFileHandle(handle); setStorageMode("local");

      afterDataLoaded(fresh);

    } catch (e) { setStorageError("Não foi possível criar o arquivo: " + e.message); }

  }

  async function handleOpenFallback() {

    setStorageError("");

    try {

      const parsed = await FallbackFile.openViaUpload();

      setStorageMode("local-fallback");

      afterDataLoaded(parsed);

    } catch (e) { setStorageError(e.message); }

  }

  function handleCreateFallback() {

    setStorageMode("local-fallback");

    afterDataLoaded(DEFAULT_DATA());

  }

  async function handleConnectDrive() {

    setStorageError("");

    try {

      await GoogleDrive.connect();

      const existing = await GoogleDrive.findFile();

      if (existing) {

        const parsed = await GoogleDrive.readFile(existing.id);

        setDriveFileId(existing.id);

        afterDataLoaded(parsed);

      } else {

        const fresh = DEFAULT_DATA();

        const created = await GoogleDrive.saveFile(fresh, null);

        setDriveFileId(created.id);

        afterDataLoaded(fresh);

      }

      setStorageMode("drive");

    } catch (e) { setStorageError("Não foi possível conectar ao Google Drive: " + e.message); }

  }


  const persist = useCallback(async (next) => {

    setData(next);

    try {

      if (storageMode === "local" && fileHandle) await LocalFile.write(fileHandle, next);

      else if (storageMode === "local-fallback") FallbackFile.downloadAsFile(next);

      else if (storageMode === "drive") await GoogleDrive.saveFile(next, driveFileId);

    } catch (e) { setStorageError("Falha ao salvar: " + e.message); }

  }, [storageMode, fileHandle, driveFileId]);


  async function logout() {

    await fetch("/api/logout", { method: "POST", credentials: "include" });

    window.location.href = "/login.html";

  }


  if (!authChecked || view === "loading") {

    return <div style={styles.shell}><div style={styles.loadingWrap}><div style={styles.spinner} /><p style={{ color: "#8A8C82", marginTop: 12 }}>Carregando...</p></div></div>;

  }


  return (

    <div style={styles.shell}>

      <div style={styles.topbar}>

        <div style={styles.brand}><span style={styles.brandDotWrap}>💪</span><span style={styles.brandText}>FORJA<span style={{ color: "#D4FF3D" }}>.</span></span></div>

        <div style={{ display: "flex", gap: 8 }}>

          {me?.is_admin && <a href="/admin.html" style={styles.ghostBtn}>Admin</a>}

          <button style={styles.ghostBtn} onClick={logout}>Sair</button>

        </div>

      </div>


      {view === "storage-gate" && (

        <StorageGate resumeHandle={resumeHandle} error={storageError}

          onOpenLocal={handleOpenLocal} onResumeLocal={handleResumeLocal} onCreateLocal={handleCreateLocal}

          onOpenFallback={handleOpenFallback} onCreateFallback={handleCreateFallback}

          onConnectDrive={handleConnectDrive} />

      )}


      {view === "onboarding" && data && <Onboarding data={data} onFinish={(next) => { persist(next); setView("app"); }} />}


      {view === "app" && data && <MainApp data={data} persist={persist} storageMode={storageMode} onRestart={() => setView("storage-gate")} />}

    </div>

  );

}


function StorageGate({ resumeHandle, error, onOpenLocal, onResumeLocal, onCreateLocal, onOpenFallback, onCreateFallback, onConnectDrive }) {

  return (

    <div style={styles.container}>

      <div style={styles.eyebrow}>SEUS DADOS</div>

      <h1 style={styles.h1}>Onde estão seus dados?</h1>

      <p style={styles.pMuted}>

        Seus dados (perfil, treinos, progresso) ficam num arquivo que só você guarda — local ou na nuvem.

        A cada acesso, é só apontar pra ele de novo.

      </p>


      {!LocalFile.supported && (

        <div style={styles.infoBox}>

          Seu navegador não suporta abrir/salvar um arquivo diretamente (isso é normal no Safari/iPhone e no Firefox).

          Vamos usar o modo alternativo: você faz upload do arquivo pra abrir, e cada alteração baixa uma versão nova pra você substituir.

        </div>

      )}


      {LocalFile.supported && resumeHandle && (

        <button style={styles.bigOptionBtn} onClick={onResumeLocal}>

          <span style={{ fontSize: 22 }}>⚡</span>

          <div style={{ flex: 1, textAlign: "left" }}><div style={styles.dayTitle}>Continuar com o último arquivo</div><div style={styles.gymNote}>Reabre o arquivo que você já usou neste navegador</div></div>

        </button>

      )}


      {LocalFile.supported ? (

        <>

          <button style={styles.bigOptionBtn} onClick={onOpenLocal}>

            <span style={{ fontSize: 22 }}>📂</span>

            <div style={{ flex: 1, textAlign: "left" }}><div style={styles.dayTitle}>Abrir arquivo local</div><div style={styles.gymNote}>Escolha um forja-dados.json já existente no seu dispositivo</div></div>

          </button>

          <button style={styles.bigOptionBtn} onClick={onCreateLocal}>

            <span style={{ fontSize: 22 }}>✨</span>

            <div style={{ flex: 1, textAlign: "left" }}><div style={styles.dayTitle}>Criar novo arquivo local</div><div style={styles.gymNote}>Primeira vez? Comece do zero aqui</div></div>

          </button>

        </>

      ) : (

        <>

          <button style={styles.bigOptionBtn} onClick={onOpenFallback}>

            <span style={{ fontSize: 22 }}>📂</span>

            <div style={{ flex: 1, textAlign: "left" }}><div style={styles.dayTitle}>Abrir arquivo (upload)</div><div style={styles.gymNote}>Selecione seu forja-dados.json</div></div>

          </button>

          <button style={styles.bigOptionBtn} onClick={onCreateFallback}>

            <span style={{ fontSize: 22 }}>✨</span>

            <div style={{ flex: 1, textAlign: "left" }}><div style={styles.dayTitle}>Começar do zero</div><div style={styles.gymNote}>A cada mudança, vamos baixar um arquivo atualizado pra você guardar</div></div>

          </button>

        </>

      )}


      <button style={styles.bigOptionBtn} onClick={onConnectDrive}>

        <span style={{ fontSize: 22 }}>☁️</span>

        <div style={{ flex: 1, textAlign: "left" }}>

          <div style={styles.dayTitle}>Conectar Google Drive</div>

          <div style={styles.gymNote}>{GoogleDrive.ready() ? "Salva um forja-dados.json na sua conta Google" : "Configure GOOGLE_CLIENT_ID no código pra habilitar"}</div>

        </div>

      </button>


      <button style={{ ...styles.bigOptionBtn, opacity: 0.5 }} disabled>

        <span style={{ fontSize: 22 }}>📦</span>

        <div style={{ flex: 1, textAlign: "left" }}><div style={styles.dayTitle}>Conectar Dropbox</div><div style={styles.gymNote}>Em breve — peça pra eu construir esse conector também</div></div>

      </button>


      {error && <div style={styles.errorBox}>{error}</div>}

    </div>

  );

}


// ---------------------------------------------------------------------------

// ONBOARDING

// ---------------------------------------------------------------------------


function Onboarding({ data, onFinish }) {

  const [step, setStep] = useState(0);

  const [profile, setProfile] = useState(data.profile);

  const [gym, setGym] = useState(data.gym || { id: null, label: "", equipment: {} });

  const [planMode, setPlanMode] = useState(data.planMode || "ia");

  const [goal, setGoal] = useState(data.goal);

  const [missionDays, setMissionDays] = useState(data.missionDays);

  const [intensity, setIntensity] = useState(data.intensity);

  const [manualDayIdx, setManualDayIdx] = useState(0);

  const [manualSelections, setManualSelections] = useState([]);


  const bmi = computeBmi(parseFloat(profile.weightKg), parseFloat(profile.heightCm));


  function next() { setStep((s) => s + 1); }

  function back() { setStep((s) => Math.max(0, s - 1)); }


  function finalizeIA() {

    const plan = generateAIPlan(profile.daysPerWeek, goal, intensity, gym);

    finish(plan);

  }


  function finish(plan) {

    const next = {

      ...data,

      profile: { ...profile, bmi },

      gym, planMode, goal, missionDays, intensity,

      missionStartDate: todayISO(),

      weeklyPlan: plan,

      completedDays: new Array(plan.length).fill(false),

      currentDayIndex: 0,

    };

    onFinish(next);

  }


  // --- Passo 0: perfil ---

  if (step === 0) {

    return (

      <div style={styles.container}>

        <div style={styles.eyebrow}>PASSO 1 · SEU PERFIL</div>

        <h1 style={styles.h1}>Vamos te conhecer</h1>

        <label style={styles.label}>Nome</label>

        <input style={styles.input} value={profile.name} onChange={(e) => setProfile({ ...profile, name: e.target.value })} />

        <div style={styles.rowGrid}>

          <div><label style={styles.label}>Idade</label><input style={styles.input} type="number" value={profile.age} onChange={(e) => setProfile({ ...profile, age: e.target.value })} /></div>

          <div><label style={styles.label}>Altura (cm)</label><input style={styles.input} type="number" value={profile.heightCm} onChange={(e) => setProfile({ ...profile, heightCm: e.target.value })} /></div>

        </div>

        <label style={styles.label}>Peso (kg)</label>

        <input style={styles.input} type="number" value={profile.weightKg} onChange={(e) => setProfile({ ...profile, weightKg: e.target.value })} />

        {bmi && (

          <div style={styles.bmiBox}>

            <div style={{ fontFamily: "'Archivo Black', sans-serif", fontSize: 24 }}>{bmi.value}</div>

            <div style={{ fontSize: 13, color: "#B9BBAE" }}>IMC · {bmi.category}</div>

            <div style={styles.pMutedSmall}>O IMC é uma estimativa geral — não leva em conta massa muscular ou composição corporal.</div>

          </div>

        )}

        <button style={styles.completeBtn} disabled={!profile.name} onClick={next}>Próximo</button>

      </div>

    );

  }


  // --- Passo 1: dias por semana ---

  if (step === 1) {

    return (

      <div style={styles.container}>

        <button style={styles.backBtn} onClick={back}>← Voltar</button>

        <div style={styles.eyebrow}>PASSO 2 · FREQUÊNCIA</div>

        <h1 style={styles.h1}>Quantos dias por semana?</h1>

        <div style={styles.chipGrid}>

          {[1, 2, 3, 4, 5, 6, 7].map((n) => (

            <button key={n} style={{ ...styles.chipOption, ...(profile.daysPerWeek === n ? styles.chipOptionActive : {}) }} onClick={() => setProfile({ ...profile, daysPerWeek: n })}>{n}</button>

          ))}

        </div>

        <button style={styles.completeBtn} onClick={next}>Próximo</button>

      </div>

    );

  }


  // --- Passo 2: academia ---

  if (step === 2) {

    return (

      <div style={styles.container}>

        <button style={styles.backBtn} onClick={back}>← Voltar</button>

        <div style={styles.eyebrow}>PASSO 3 · ACADEMIA</div>

        <h1 style={styles.h1}>Onde você treina?</h1>

        <div style={styles.gymList}>

          {FIXED_GYMS.map((g) => (

            <button key={g.id} style={{ ...styles.gymCard, borderColor: gym.id === g.id ? "#D4FF3D" : "#23241D" }} onClick={() => setGym({ id: g.id, label: g.label, equipment: {} })}>

              <div style={{ fontSize: 22 }}>{g.icon}</div>

              <div style={{ flex: 1 }}><div style={styles.dayTitle}>{g.label}</div><div style={styles.gymNote}>{g.note}</div></div>

            </button>

          ))}

        </div>

        {gym.id === "outra" && (

          <div style={styles.settingsCard}>

            <label style={styles.label}>Nome da academia</label>

            <input style={styles.input} value={gym.label} onChange={(e) => setGym({ ...gym, label: e.target.value })} />

            <div style={{ ...styles.pMutedSmall, marginTop: 12 }}>Marque os equipamentos disponíveis:</div>

            <div style={styles.equipGrid}>

              {EQUIPMENT_FIELDS.map((f) => (

                <button key={f.key} style={{ ...styles.equipChip, ...(gym.equipment[f.key] ? styles.equipChipActive : {}) }} onClick={() => setGym({ ...gym, equipment: { ...gym.equipment, [f.key]: !gym.equipment[f.key] } })}>

                  <span style={{ fontSize: 16 }}>{f.icon}</span> {f.label}

                </button>

              ))}

            </div>

          </div>

        )}

        <button style={styles.completeBtn} disabled={!gym.id} onClick={next}>Próximo</button>

      </div>

    );

  }


  // --- Passo 3: modo de treino ---

  if (step === 3) {

    return (

      <div style={styles.container}>

        <button style={styles.backBtn} onClick={back}>← Voltar</button>

        <div style={styles.eyebrow}>PASSO 4 · MONTAGEM DO TREINO</div>

        <h1 style={styles.h1}>Quem monta o treino?</h1>

        <button style={{ ...styles.bigOptionBtn, borderColor: planMode === "ia" ? "#D4FF3D" : "#23241D" }} onClick={() => setPlanMode("ia")}>

          <span style={{ fontSize: 22 }}>🤖</span>

          <div style={{ flex: 1, textAlign: "left" }}><div style={styles.dayTitle}>Deixar a IA montar</div><div style={styles.gymNote}>Sequência automática por grupo muscular, com base nos equipamentos da sua academia</div></div>

        </button>

        <button style={{ ...styles.bigOptionBtn, borderColor: planMode === "manual" ? "#D4FF3D" : "#23241D" }} onClick={() => setPlanMode("manual")}>

          <span style={{ fontSize: 22 }}>✍️</span>

          <div style={{ flex: 1, textAlign: "left" }}><div style={styles.dayTitle}>Montar eu mesmo</div><div style={styles.gymNote}>Escolha os exercícios de cada grupo muscular, dia por dia</div></div>

        </button>

        <button style={styles.completeBtn} onClick={next}>Próximo</button>

      </div>

    );

  }


  // --- Passo 4: meta ---

  if (step === 4) {

    return (

      <div style={styles.container}>

        <button style={styles.backBtn} onClick={back}>← Voltar</button>

        <div style={styles.eyebrow}>PASSO 5 · META</div>

        <h1 style={styles.h1}>Qual é a sua missão?</h1>

        <div style={styles.goalGrid}>

          {GOALS.map((g) => (

            <button key={g.id} style={{ ...styles.goalCard, borderColor: goal === g.id ? g.color : g.color + "40" }} onClick={() => setGoal(g.id)}>

              <div style={{ fontSize: 26 }}>{g.icon}</div>

              <div style={{ ...styles.goalTitle, color: g.color }}>{g.label}</div>

            </button>

          ))}

        </div>

        <button style={styles.completeBtn} disabled={!goal} onClick={next}>Próximo</button>

      </div>

    );

  }


  // --- Passo 5: duração da missão ---

  if (step === 5) {

    return (

      <div style={styles.container}>

        <button style={styles.backBtn} onClick={back}>← Voltar</button>

        <div style={styles.eyebrow}>PASSO 6 · DURAÇÃO</div>

        <h1 style={styles.h1}>Por quantos dias?</h1>

        <p style={styles.pMuted}>Sua missão de acompanhamento — o dashboard mostra seu progresso dentro desse período.</p>

        <div style={styles.chipGrid}>

          {MISSION_OPTIONS.map((n) => (

            <button key={n} style={{ ...styles.chipOption, minWidth: 70, ...(missionDays === n ? styles.chipOptionActive : {}) }} onClick={() => setMissionDays(n)}>{n} dias</button>

          ))}

        </div>

        <button style={styles.completeBtn} disabled={!missionDays} onClick={next}>Próximo</button>

      </div>

    );

  }


  // --- Passo 6: intensidade ---

  if (step === 6) {

    return (

      <div style={styles.container}>

        <button style={styles.backBtn} onClick={back}>← Voltar</button>

        <div style={styles.eyebrow}>PASSO 7 · INTENSIDADE</div>

        <h1 style={styles.h1}>Qual ritmo?</h1>

        <div style={{ display: "flex", flexDirection: "column", gap: 10 }}>

          {Object.entries(INTENSITY).map(([id, info]) => (

            <button key={id} style={{ ...styles.intensityCard, borderColor: intensity === id ? info.color : info.color + "40" }} onClick={() => setIntensity(id)}>

              <div><div style={{ ...styles.dayTitle, color: info.color }}>{info.label}</div><div style={styles.gymNote}>{info.desc} · {info.rest}</div></div>

            </button>

          ))}

        </div>

        <button style={styles.completeBtn} disabled={!intensity} onClick={() => setStep(planMode === "ia" ? 7 : 8)}>Próximo</button>

      </div>

    );

  }


  // --- Passo 7 (modo IA): revisão e geração ---

  if (step === 7 && planMode === "ia") {

    return (

      <div style={styles.container}>

        <button style={styles.backBtn} onClick={back}>← Voltar</button>

        <div style={styles.eyebrow}>TUDO PRONTO</div>

        <h1 style={styles.h1}>Gerar meu treino</h1>

        <p style={styles.pMuted}>{profile.daysPerWeek}x por semana · {gym.label} · {GOALS.find((g) => g.id === goal)?.label} · {missionDays} dias · {INTENSITY[intensity]?.label}</p>

        <button style={styles.completeBtn} onClick={finalizeIA}>🤖 Gerar treino com IA</button>

      </div>

    );

  }


  // --- Passo 8 (modo manual): montagem dia a dia ---

  if (step === 8 && planMode === "manual") {

    const split = buildSplit(profile.daysPerWeek, goal);

    const equipProfile = getEquipProfile(gym);

    const dayTemplate = split[manualDayIdx];

    const selectedForDay = manualSelections[manualDayIdx] || [];


    function toggleExercise(ex) {

      const copy = [...manualSelections];

      const current = copy[manualDayIdx] || [];

      const exists = current.find((e) => e.name === ex.name);

      copy[manualDayIdx] = exists ? current.filter((e) => e.name !== ex.name) : [...current, ex];

      setManualSelections(copy);

    }


    function nextDay() {

      if (manualDayIdx < split.length - 1) setManualDayIdx(manualDayIdx + 1);

      else {

        const plan = split.map((day, i) => ({

          title: day.title, groups: day.groups,

          exercises: (manualSelections[i] || []).map((ex) => applyGoalAndIntensity(ex, goal, intensity)),

        }));

        finish(plan);

      }

    }


    return (

      <div style={styles.container}>

        <div style={styles.eyebrow}>MONTAGEM MANUAL · DIA {manualDayIdx + 1} DE {split.length}</div>

        <h1 style={styles.h1}>{dayTemplate.title}</h1>

        <p style={styles.pMuted}>Escolha os exercícios deste dia. Selecionados: {selectedForDay.length}.</p>

        {dayTemplate.groups.map((group) => (

          <div key={group} style={{ marginBottom: 18 }}>

            <div style={styles.eyebrow}>{group.toUpperCase()}</div>

            <div style={styles.exList}>

              {filterByEquipment(group, equipProfile).map((ex) => {

                const checked = !!selectedForDay.find((e) => e.name === ex.name);

                return (

                  <button key={ex.name} style={{ ...styles.exRow, borderColor: checked ? "#D4FF3D" : "#23241D" }} onClick={() => toggleExercise(ex)}>

                    <span>{checked ? "✅" : "⬜"}</span>

                    <div style={{ flex: 1, textAlign: "left" }}><div style={styles.exName}>{ex.name}</div><div style={styles.exMeta}>{ex.sets} séries × {ex.reps}</div></div>

                  </button>

                );

              })}

            </div>

          </div>

        ))}

        <button style={styles.completeBtn} disabled={selectedForDay.length === 0} onClick={nextDay}>

          {manualDayIdx < split.length - 1 ? "Próximo dia" : "Concluir montagem"}

        </button>

      </div>

    );

  }


  return null;

}


// ---------------------------------------------------------------------------

// APP PRINCIPAL (treino + dashboard + config)

// ---------------------------------------------------------------------------


function MainApp({ data, persist, storageMode, onRestart }) {

  const [tab, setTab] = useState("treino");

  const [view, setView] = useState("week");

  const [activeDay, setActiveDay] = useState(null);

  const [checkedEx, setCheckedEx] = useState({});

  const [metricForm, setMetricForm] = useState({ weight: "", waist: "" });


  function openDay(idx) { setActiveDay(idx); setCheckedEx({}); setView("day"); }


  function completeDay() {

    const nextCompleted = [...data.completedDays];

    nextCompleted[activeDay] = true;

    const nextIdx = nextCompleted.findIndex((d) => !d);

    const history = [{ date: todayISO(), title: data.weeklyPlan[activeDay].title }, ...(data.sessionHistory || [])].slice(0, 90);

    persist({ ...data, completedDays: nextCompleted, currentDayIndex: nextIdx === -1 ? data.currentDayIndex : nextIdx, sessionHistory: history });

    setView("week");

  }


  function resetWeek() {

    persist({ ...data, completedDays: new Array(data.weeklyPlan.length).fill(false), currentDayIndex: 0 });

  }


  function addMetric() {

    const w = parseFloat(metricForm.weight), waist = parseFloat(metricForm.waist);

    if (!w && !waist) return;

    persist({ ...data, metrics: [...(data.metrics || []), { date: todayISO(), weight: isNaN(w) ? null : w, waist: isNaN(waist) ? null : waist }] });

    setMetricForm({ weight: "", waist: "" });

  }


  const goalMeta = GOALS.find((g) => g.id === data.goal);

  const intensityMeta = INTENSITY[data.intensity];


  const missionDaysElapsed = data.missionStartDate ? Math.floor((new Date(todayISO()) - new Date(data.missionStartDate)) / 86400000) + 1 : 0;

  const missionDaysLeft = data.missionDays ? Math.max(0, data.missionDays - missionDaysElapsed) : 0;

  const expectedSessions = data.missionDays ? Math.round((data.profile.daysPerWeek || 3) * (data.missionDays / 7)) : 0;

  const missionProgressPct = expectedSessions ? Math.min(100, Math.round(((data.sessionHistory || []).length / expectedSessions) * 100)) : 0;


  return (

    <div>

      <div style={styles.tabBar}>

        <button style={{ ...styles.tabBtn, ...(tab === "treino" ? styles.tabBtnActive : {}) }} onClick={() => { setTab("treino"); setView("week"); }}>Treino</button>

        <button style={{ ...styles.tabBtn, ...(tab === "dashboard" ? styles.tabBtnActive : {}) }} onClick={() => { setTab("dashboard"); setView("dashboard"); }}>Dashboard</button>

        <button style={{ ...styles.tabBtn, ...(tab === "config" ? styles.tabBtnActive : {}) }} onClick={() => { setTab("config"); setView("config"); }}>Config</button>

      </div>


      {view === "week" && (

        <div style={styles.container}>

          <div style={styles.eyebrow}>{goalMeta?.label?.toUpperCase()}</div>

          <h1 style={styles.h1}>Olá, {data.profile.name}</h1>

          <div style={styles.chipsRow}>

            <span style={styles.chip}>{data.gym?.label}</span>

            <span style={{ ...styles.chip, color: intensityMeta?.color }}>{intensityMeta?.label}</span>

            <span style={styles.chip}>Missão de {data.missionDays} dias</span>

          </div>

          {data.missionDays && (

            <div style={{ marginBottom: 20 }}>

              <div style={styles.pMutedSmall}>Dia {Math.min(missionDaysElapsed, data.missionDays)} de {data.missionDays} · {missionDaysLeft} dias restantes</div>

              <div style={styles.barTrack}><div style={{ ...styles.barFill, width: `${missionProgressPct}%` }} /></div>

            </div>

          )}

          <div style={styles.progressTrack}>

            {data.weeklyPlan.map((_, i) => (<div key={i} style={{ ...styles.progressDot, background: data.completedDays[i] ? "#D4FF3D" : "#2A2C24" }} />))}

          </div>

          <div style={styles.dayList}>

            {data.weeklyPlan.map((d, i) => {

              const isDone = data.completedDays[i];

              const isNext = i === data.currentDayIndex && !isDone;

              return (

                <button key={i} style={{ ...styles.dayCard, borderColor: isNext ? "#D4FF3D" : "#23241D" }} onClick={() => openDay(i)}>

                  <div style={styles.dayLeft}>

                    <span style={{ fontSize: 18 }}>{isDone ? "✅" : "⚪"}</span>

                    <div><div style={styles.dayNumber}>DIA {i + 1}{isNext ? " · CONTINUAR" : ""}</div><div style={styles.dayTitle}>{d.title}</div><div style={styles.dayCount}>{d.exercises.length} exercícios</div></div>

                  </div>

                </button>

              );

            })}

          </div>

          <button style={styles.resetBtn} onClick={resetWeek}>Reiniciar semana</button>

        </div>

      )}


      {view === "day" && activeDay !== null && (

        <div style={styles.container}>

          <button style={styles.backBtn} onClick={() => setView("week")}>← Voltar</button>

          <div style={styles.eyebrow}>DIA {activeDay + 1} DE {data.weeklyPlan.length} · {intensityMeta?.label?.toUpperCase()}</div>

          <h1 style={styles.h1}>{data.weeklyPlan[activeDay].title}</h1>

          <p style={styles.pMuted}>{intensityMeta?.rest}.</p>

          <div style={styles.exList}>

            {data.weeklyPlan[activeDay].exercises.map((ex, i) => (

              <div key={i} style={{ ...styles.exRowStatic, opacity: checkedEx[i] ? 0.55 : 1 }}>

                <button style={styles.exCheck} onClick={() => setCheckedEx((p) => ({ ...p, [i]: !p[i] }))}>{checkedEx[i] ? "✅" : "⚪"}</button>

                <div style={{ flex: 1 }}><div style={{ ...styles.exName, textDecoration: checkedEx[i] ? "line-through" : "none" }}>{ex.name}</div><div style={styles.exMeta}>{ex.sets} séries × {ex.reps}</div></div>

                <a href={youtubeSearchUrl(ex.name)} target="_blank" rel="noopener noreferrer" style={styles.videoBtn}>▶️</a>

              </div>

            ))}

          </div>

          <button style={styles.completeBtn} onClick={completeDay}>Concluir treino do dia</button>

        </div>

      )}


      {view === "dashboard" && (

        <DashboardView data={data} metricForm={metricForm} setMetricForm={setMetricForm} onAddMetric={addMetric} missionDaysElapsed={missionDaysElapsed} missionDaysLeft={missionDaysLeft} missionProgressPct={missionProgressPct} />

      )}


      {view === "config" && <ConfigView data={data} persist={persist} storageMode={storageMode} onRestart={onRestart} />}

    </div>

  );

}


function DashboardView({ data, metricForm, setMetricForm, onAddMetric, missionDaysElapsed, missionDaysLeft, missionProgressPct }) {

  const sessions = data.sessionHistory || [];

  const metrics = data.metrics || [];

  const firstMetric = metrics[0], lastMetric = metrics[metrics.length - 1];

  const weightDelta = firstMetric && lastMetric && firstMetric.weight != null && lastMetric.weight != null ? +(lastMetric.weight - firstMetric.weight).toFixed(1) : null;

  const waistDelta = firstMetric && lastMetric && firstMetric.waist != null && lastMetric.waist != null ? +(lastMetric.waist - firstMetric.waist).toFixed(1) : null;


  return (

    <div style={styles.container}>

      <div style={styles.eyebrow}>DESEMPENHO</div>

      <h1 style={styles.h1}>Dashboard</h1>

      <div style={styles.statsGrid}>

        <div style={styles.statCard}><div style={styles.statValue}>{sessions.length}</div><div style={styles.statLabel}>treinos concluídos</div></div>

        <div style={styles.statCard}><div style={styles.statValue}>{missionProgressPct}%</div><div style={styles.statLabel}>progresso da missão</div></div>

        <div style={styles.statCard}><div style={{ ...styles.statValue, color: weightDelta == null ? "#F2F2ED" : weightDelta <= 0 ? "#D4FF3D" : "#FF3B30" }}>{weightDelta == null ? "—" : `${weightDelta > 0 ? "+" : ""}${weightDelta}`}</div><div style={styles.statLabel}>variação de peso</div></div>

        <div style={styles.statCard}><div style={{ ...styles.statValue, color: waistDelta == null ? "#F2F2ED" : waistDelta <= 0 ? "#D4FF3D" : "#FF3B30" }}>{waistDelta == null ? "—" : `${waistDelta > 0 ? "+" : ""}${waistDelta}`}</div><div style={styles.statLabel}>variação de cintura</div></div>

      </div>

      <div style={{ ...styles.eyebrow, marginTop: 24 }}>REGISTRAR MEDIDAS DE HOJE</div>

      <div style={styles.metricForm}>

        <input style={styles.input} placeholder="Peso" inputMode="decimal" value={metricForm.weight} onChange={(e) => setMetricForm((p) => ({ ...p, weight: e.target.value }))} />

        <input style={styles.input} placeholder="Cintura" inputMode="decimal" value={metricForm.waist} onChange={(e) => setMetricForm((p) => ({ ...p, waist: e.target.value }))} />

        <button style={styles.addMetricBtn} onClick={onAddMetric}>+</button>

      </div>

      {sessions.length > 0 && (

        <>

          <div style={{ ...styles.eyebrow, marginTop: 22 }}>ÚLTIMAS SESSÕES</div>

          <div style={styles.historyList}>{sessions.slice(0, 8).map((s, i) => (<div key={i} style={styles.historyRow}><span>{s.title}</span><span style={styles.historyDate}>{s.date}</span></div>))}</div>

        </>

      )}

    </div>

  );

}


function ConfigView({ data, persist, storageMode, onRestart }) {

  return (

    <div style={styles.container}>

      <div style={styles.eyebrow}>CONFIGURAÇÕES</div>

      <h1 style={styles.h1}>Perfil e dados</h1>

      <div style={styles.settingsCard}>

        <div style={styles.dayTitle}>{data.profile.name}</div>

        <div style={styles.gymNote}>{data.profile.age} anos · {data.profile.heightCm}cm · {data.profile.weightKg}kg{data.profile.bmi ? ` · IMC ${data.profile.bmi.value} (${data.profile.bmi.category})` : ""}</div>

        <div style={{ ...styles.gymNote, marginTop: 6 }}>Academia: {data.gym?.label} · {data.profile.daysPerWeek}x/semana</div>

      </div>

      <div style={{ ...styles.eyebrow, marginTop: 22 }}>ARMAZENAMENTO</div>

      <div style={styles.infoBox}>

        Modo atual: <b>{storageMode === "local" ? "arquivo local" : storageMode === "local-fallback" ? "upload/download manual" : storageMode === "drive" ? "Google Drive" : "—"}</b>.

        Toda vez que você entrar de novo, vai precisar reconectar essa fonte de dados.

      </div>

      <button style={{ ...styles.ghostBtnBlock, marginTop: 10 }} onClick={onRestart}>Trocar arquivo / fonte de dados</button>

      <button style={{ ...styles.ghostBtnBlock, marginTop: 10 }} onClick={() => persist({ ...data, weeklyPlan: [], profile: { ...data.profile, name: "" } })}>

        Refazer todo o onboarding

      </button>

    </div>

  );

}


// ---------------------------------------------------------------------------

// ESTILOS

// ---------------------------------------------------------------------------

const styles = {

  shell: { minHeight: "100vh", background: "#0E0F0C", color: "#F2F2ED", fontFamily: "Inter, sans-serif", paddingBottom: 40 },

  loadingWrap: { display: "flex", flexDirection: "column", alignItems: "center", justifyContent: "center", height: "100vh" },

  spinner: { width: 36, height: 36, border: "3px solid #23241D", borderTopColor: "#D4FF3D", borderRadius: "50%", animation: "spin 0.8s linear infinite" },

  topbar: { display: "flex", justifyContent: "space-between", alignItems: "center", padding: "18px 20px", borderBottom: "1px solid #1D1E17" },

  brand: { display: "flex", alignItems: "center", gap: 10 },

  brandDotWrap: { fontSize: 18 },

  brandText: { fontFamily: "'Archivo Black', sans-serif", fontSize: 18, letterSpacing: 1 },

  ghostBtn: { background: "transparent", border: "1px solid #2A2C24", color: "#B9BBAE", padding: "8px 14px", borderRadius: 999, fontSize: 13, fontWeight: 600, textDecoration: "none" },

  ghostBtnBlock: { width: "100%", background: "transparent", border: "1px solid #2A2C24", color: "#B9BBAE", padding: "12px", borderRadius: 12, fontSize: 13, fontWeight: 700 },

  tabBar: { display: "flex", gap: 8, padding: "12px 20px 0", maxWidth: 640, margin: "0 auto" },

  tabBtn: { flex: 1, background: "#16170F", border: "1px solid #23241D", color: "#8A8C82", padding: "10px", borderRadius: 12, fontSize: 13, fontWeight: 700 },

  tabBtnActive: { color: "#0E0F0C", background: "#D4FF3D", border: "1px solid #D4FF3D" },

  container: { maxWidth: 640, margin: "0 auto", padding: "28px 20px" },

  eyebrow: { fontSize: 12, fontWeight: 700, letterSpacing: 1.5, color: "#8A8C82", marginBottom: 10 },

  h1: { fontFamily: "'Archivo Black', sans-serif", fontSize: 28, lineHeight: 1.15, margin: "0 0 10px 0" },

  pMuted: { color: "#9A9C90", fontSize: 14.5, lineHeight: 1.6, marginBottom: 20 },

  pMutedSmall: { color: "#787A6E", fontSize: 12, marginTop: 6, marginBottom: 6 },

  label: { fontSize: 12, fontWeight: 700, color: "#8A8C82", letterSpacing: 0.5, display: "block", marginBottom: 6, marginTop: 14 },

  input: { width: "100%", background: "#16170F", border: "1px solid #23241D", borderRadius: 12, padding: "12px 14px", color: "#F2F2ED", fontSize: 14 },

  rowGrid: { display: "grid", gridTemplateColumns: "1fr 1fr", gap: 12 },

  bmiBox: { background: "#16170F", border: "1px solid #23241D", borderRadius: 14, padding: 16, marginTop: 18, textAlign: "center" },

  chipGrid: { display: "flex", flexWrap: "wrap", gap: 10, marginBottom: 22 },

  chipOption: { background: "#16170F", border: "1px solid #23241D", color: "#B9BBAE", borderRadius: 12, padding: "12px 18px", fontSize: 15, fontWeight: 700, minWidth: 50 },

  chipOptionActive: { background: "#D4FF3D22", border: "1px solid #D4FF3D", color: "#D4FF3D" },

  chipsRow: { display: "flex", gap: 8, marginBottom: 14, flexWrap: "wrap" },

  chip: { fontSize: 12, fontWeight: 700, background: "#16170F", border: "1px solid #23241D", padding: "5px 10px", borderRadius: 999, color: "#B9BBAE" },

  goalGrid: { display: "grid", gridTemplateColumns: "1fr 1fr", gap: 12, marginBottom: 22 },

  goalCard: { background: "#16170F", border: "1px solid", borderRadius: 16, padding: 16, display: "flex", flexDirection: "column", alignItems: "flex-start", textAlign: "left" },

  goalTitle: { fontWeight: 800, fontSize: 14, marginTop: 8 },

  gymList: { display: "flex", flexDirection: "column", gap: 10, marginBottom: 16 },

  gymCard: { background: "#16170F", border: "1px solid", borderRadius: 14, padding: "14px 16px", display: "flex", alignItems: "center", gap: 14, textAlign: "left" },

  gymNote: { fontSize: 12.5, color: "#8A8C82", marginTop: 3, lineHeight: 1.4 },

  bigOptionBtn: { width: "100%", background: "#16170F", border: "1px solid #23241D", borderRadius: 14, padding: "14px 16px", display: "flex", alignItems: "center", gap: 14, textAlign: "left", marginBottom: 10 },

  intensityCard: { background: "#16170F", border: "1px solid", borderRadius: 14, padding: "16px", textAlign: "left" },

  progressTrack: { display: "flex", gap: 6, marginBottom: 20 },

  progressDot: { flex: 1, height: 6, borderRadius: 4 },

  dayList: { display: "flex", flexDirection: "column", gap: 10 },

  dayCard: { background: "#16170F", border: "1px solid", borderRadius: 14, padding: "14px 16px" },

  dayLeft: { display: "flex", alignItems: "center", gap: 14, textAlign: "left" },

  dayNumber: { fontSize: 11, fontWeight: 700, color: "#8A8C82", letterSpacing: 1 },

  dayTitle: { fontSize: 16, fontWeight: 700, margin: "2px 0" },

  dayCount: { fontSize: 12.5, color: "#787A6E" },

  resetBtn: { marginTop: 20, background: "transparent", border: "none", color: "#6E7064", fontSize: 13, fontWeight: 600, padding: 0 },

  backBtn: { background: "transparent", border: "none", color: "#8A8C82", fontSize: 13.5, fontWeight: 600, padding: 0, marginBottom: 18 },

  exList: { display: "flex", flexDirection: "column", gap: 8, marginBottom: 20 },

  exRow: { display: "flex", alignItems: "center", gap: 10, background: "#16170F", border: "1px solid", borderRadius: 12, padding: "10px 12px", textAlign: "left" },

  exRowStatic: { display: "flex", alignItems: "center", gap: 12, background: "#16170F", border: "1px solid #23241D", borderRadius: 14, padding: "12px 14px" },

  exCheck: { background: "transparent", border: "none", padding: 0, fontSize: 18 },

  exName: { fontSize: 14.5, fontWeight: 700 },

  exMeta: { fontSize: 12, color: "#8A8C82", marginTop: 2 },

  videoBtn: { textDecoration: "none", fontSize: 18 },

  completeBtn: { width: "100%", background: "#D4FF3D", color: "#0E0F0C", border: "none", borderRadius: 999, padding: "16px", fontSize: 15, fontWeight: 800, marginTop: 8 },

  statsGrid: { display: "grid", gridTemplateColumns: "1fr 1fr", gap: 10 },

  statCard: { background: "#16170F", border: "1px solid #23241D", borderRadius: 14, padding: "16px", textAlign: "center" },

  statValue: { fontFamily: "'Archivo Black', sans-serif", fontSize: 24 },

  statLabel: { fontSize: 11.5, color: "#8A8C82", marginTop: 4 },

  barTrack: { height: 8, background: "#1D1E17", borderRadius: 4, overflow: "hidden", marginTop: 4 },

  barFill: { height: "100%", background: "#D4FF3D", borderRadius: 4 },

  metricForm: { display: "flex", gap: 8 },

  addMetricBtn: { background: "#D4FF3D", color: "#0E0F0C", border: "none", borderRadius: 12, padding: "0 16px", fontWeight: 800 },

  historyList: { display: "flex", flexDirection: "column", gap: 8 },

  historyRow: { display: "flex", alignItems: "center", gap: 8, fontSize: 13.5, color: "#B9BBAE" },

  historyDate: { marginLeft: "auto", color: "#6E7064", fontSize: 12 },

  settingsCard: { background: "#16170F", border: "1px solid #23241D", borderRadius: 14, padding: "16px" },

  equipGrid: { display: "flex", flexWrap: "wrap", gap: 8, marginTop: 10 },

  equipChip: { display: "flex", alignItems: "center", gap: 6, background: "#1D1E17", border: "1px solid #2A2C24", color: "#B9BBAE", borderRadius: 999, padding: "7px 12px", fontSize: 12.5, fontWeight: 600 },

  equipChipActive: { background: "#D4FF3D22", border: "1px solid #D4FF3D", color: "#D4FF3D" },

  infoBox: { background: "#16170F", border: "1px solid #23241D", borderRadius: 14, padding: "14px 16px", fontSize: 12.5, color: "#9A9C90", lineHeight: 1.6, marginBottom: 14 },

  errorBox: { background: "#2A1414", border: "1px solid #FF3B3055", color: "#FF7A6E", fontSize: 13, padding: "12px 14px", borderRadius: 10, marginTop: 10, lineHeight: 1.5 },

};


const styleSheet = document.createElement("style");

styleSheet.textContent = "@keyframes spin { to { transform: rotate(360deg); } }";

document.head.appendChild(styleSheet);


ReactDOM.createRoot(document.getElementById('root')).render(<App />);

</script>

</body>

</html>