๐ŸŽจ MOCKUP VISUAL โ€” Data dummy, belum ada koneksi Google Sheets / formula. Untuk review layout & UX dulu.
Dashboard
ยท Belum Terhubung ๐Ÿ”„
๐Ÿƒ
Arie Rayana
Personal Health Tracker
โš™๏ธPengaturan
๐Ÿ“ฅExport Semua Data
โ“Bantuan
๐ŸšชKeluar
๐ŸŒ… Pagi ยท Daily Welcome Hub
- ยท Selamat pagi, Arie!
-
Selamat pagi, Arie! ๐Ÿ’ช
Data terbaru: belum upload hari ini โ€” yuk mulai pagi dengan upload data Zepp
โšก HybridCharge -
๐Ÿ“ˆ HRV -
๐Ÿ˜ฉ Fatigue -
๐Ÿ˜ด Tidur -
"Tubuh yang kuat dibangun dari kebiasaan kecil yang konsisten โ€” bukan dari satu hari yang luar biasa."
โ€” Daylix Daily Reminder
๐Ÿธ
Workout Hari Ini ยท Training Plan
Badminton
Terjadwal ยท belum selesai ยท ketuk untuk lihat plan
๐Ÿ“… Terjadwal
โšก Quick Input โ€” Data Pagi Ini
๏ผ‹ Lebih Banyak
% (0โ€“100)
ms
bpm
โœ… Tersimpan!
๐ŸŽญ
Mode Demo
Kamu sedang login pakai akun demo. Semua data yang ditampilkan (HybridCharge, RHR, HRV, riwayat workout, dan lainnya) adalah data contoh untuk keperluan showcase โ€” bukan data kesehatan asli siapa pun.

Fitur koneksi Google Sheets dan Claude API Key dinonaktifkan di mode ini.
๐Ÿ“… Training Plan Bulanan
Template jadwal workout โ€” berlaku tiap bulan, bisa di-override per hari
๐Ÿ“… Kalender
๐Ÿ“Š Result Analysis
๐Ÿ“ Minggu Ini
Klik hari untuk detail sesi
โ—€
โ€”
โ–ถ
โœ… Selesai
๐Ÿ“Œ Terjadwal
โŒ Terlewat
๐Ÿ’ค Rest
Sen
Sel
Rab
Kam
Jum
Sab
Min
๐Ÿ“Š Progress Bulan Ini
Sesi yang sudah tercatat
โœ…
โ€“
0
Total Sesi Tercatat
๐Ÿ’ช
โ€“
0
Gym
๐Ÿธ
โ€“
0
Badminton
๐Ÿƒ
โ€“
0
Lari
๐Ÿ’ก Memuat insightโ€ฆ
๐Ÿ“ˆ Tren Training Load vs Recovery
Load = total training load (Workout Sessions) ยท Fatigue & Fitness Level = rata-rata harian (Daily Log)
๐Ÿ†š Komparasi Mingguan
Minggu ini vs beberapa minggu sebelumnya
๐Ÿ“‹
Edit Template Mingguan
Berlaku tiap minggu โ€” dipakai buat status kalender
Senin
๐Ÿธ
Badminton
๐Ÿƒ
Lari
๐Ÿ’ช
Gym
๐Ÿ˜ด
Rest
Selasa
๐Ÿธ
Badminton
๐Ÿƒ
Lari
๐Ÿ’ช
Gym
๐Ÿ˜ด
Rest
Rabu
๐Ÿธ
Badminton
๐Ÿƒ
Lari
๐Ÿ’ช
Gym
๐Ÿ˜ด
Rest
Kamis
๐Ÿธ
Badminton
๐Ÿƒ
Lari
๐Ÿ’ช
Gym
๐Ÿ˜ด
Rest
Jumat
๐Ÿธ
Badminton
๐Ÿƒ
Lari
๐Ÿ’ช
Gym
๐Ÿ˜ด
Rest
Sabtu
๐Ÿธ
Badminton
๐Ÿƒ
Lari
๐Ÿ’ช
Gym
๐Ÿ˜ด
Rest
Minggu
๐Ÿธ
Badminton
๐Ÿƒ
Lari
๐Ÿ’ช
Gym
๐Ÿ˜ด
Rest
๐Ÿ  Dashboard Harian
Ringkasan kondisi tubuh hari ini, -
๐Ÿ“Š Kondisi Hari Ini
Snapshot data pagi ini vs kemarin
โšก
โ€“
-
HybridCharge (Battery)
โค๏ธ
โ€“
- bpm
Resting Heart Rate
๐Ÿ“ˆ
โ€“
- ms
HRV (Heart Rate Variability)
๐Ÿ‘Ÿ
โ€“
-
Steps
๐ŸŽฏ
โ€“
-
PAI (7-day)
๐Ÿ˜ด
-
-
Durasi Tidur
๐Ÿ˜ฉ
-
-/100
Fatigue Score
๐Ÿ’ค
-
-/100
Sleep Score
HybridCharge โ€” 14 Hari
Rentang Pagiโ€“Malam per hari ยท hijau = cukup, kuning = perhatian, merah = rendah (dari yang paling buruk Pagi/Malam)
Tren RHR & HRV โ€” 14 Hari
Resting Heart Rate vs Heart Rate Variability
Fatigue & Fitness Level โ€” 14 Hari
Fatigue Score vs Fitness Level (skala 0โ€“100)
Steps Harian โ€” 14 Hari
Target: 8.000 langkah/hari
Durasi & Kualitas Tidur โ€” 14 Hari
Deep Sleep ยท REM Sleep ยท Lainnya
๐Ÿ† Workout Ranking
Top 3 ยท Badminton & Gym by Training Load/Sets, Lari by Jarak terjauh
๐Ÿธ Badminton
#TanggalDurasiKaloriAvg HRLoadChargeHR Zone
1 27 Jun
1j 45m
1.205
147
314
-12
2 20 Jun
1j 50m
1.260
151
320
-15
3 24 Jun
1j 22m
980
139
270
-9
๐Ÿƒ Lari
#TanggalDurasiKaloriAvg HRLoadChargeHR ZoneJarakAvg Pace
1 28 Jun
43:23
456
154
248
-8
5.01 km
8'39"
2 21 Jun
45:10
480
148
210
-10
5.50 km
8'11"
๐Ÿ’ช Gym
#TanggalDurasiKaloriAvg HRLoadChargeHR ZoneSets
1 23 Jun
31m 43s
130
86
180
-6
13 sets
2 19 Jun
28m 10s
118
91
150
-5
11 sets
๐Ÿ‹๏ธ Workout Terbaru
3 sesi terakhir
๐Ÿธ
Badminton 27 Jun
1j 45m ยท 1.205 kcal ยท Avg HR 147 ยท Load 314
4.5
TE
๐Ÿƒ
Lari 28 Jun
5.01 km ยท 43:23 ยท Pace 8'39"/km ยท 456 kcal
2.3
TE
๐Ÿ’ช
Gym 23 Jun
31m 43s ยท 13 sets ยท Avg HR 86 ยท 130 kcal
Upper
Fokus
๐Ÿ’ก Insight & Reminder
Analisis kondisi tubuh berbasis data 7โ€“30 hari terakhir
-
/ 100
Kondisi Hari Ini: -
Belum ada cukup data โ€” isi Data Pagi & Data Harian dulu di Daily Welcome Hub / Daily Input.
Recovery
- ยท Belum ada data
Aktivitas
- ยท Belum ada data
Tidur
- ยท Belum ada data
Workout
- ยท Belum ada data
Ringkasan Per Kategori
โค๏ธ Recovery
-
HRV -
-
RHR -
-
Fatigue Score -
-
HybridCharge -
-
Belum ada cukup data Recovery.
โšก Aktivitas
-
Steps Hari Ini -
-
PAI (7-day) -
-
Exertion -
-
Workout 30 Hari -
-
Belum ada cukup data Aktivitas.
๐Ÿ˜ด Tidur
-
Durasi Tidur -
-
Sleep Score -
-
Deep Sleep -
-
Defisit Tidur (7 hari) -
-
Belum ada cukup data Tidur.
Rekomendasi Hari Ini
โœ๏ธ Input Data Harian
Catat data dari Zepp App pagi ini ยท tersimpan otomatis ke Google Sheets
๐ŸŸข Terhubung ke Sheets
โŒจ๏ธ Form Manual
๐Ÿ–ผ๏ธ Upload Screenshot
๐Ÿ“… Tanggal Pencatatan
โšก Energi & Recovery
๐Ÿƒ Aktivitas
๐Ÿ˜ด Tidur
๐Ÿ˜ด Nap / Tidur Siang
๐Ÿ“Š Metrik Tambahan
๐Ÿ™‚ Kondisi Badan
๐Ÿ“‘ Riwayat Harian
Semua data tersimpan, urut dari yang terbaru ยท Klik baris untuk edit
๐Ÿ“‘ Data Harian
๐Ÿ“Š Result Analysis
Tanggal Hari HC Pagi HC Malam Drain Gain RHR HRV Steps PAI Fatigue Fitness Tidur Status โ„น๏ธ
๐Ÿ“‹ Keterangan Status
โœ“ Normal Kondisi tubuh baik โ€” HRV & Fatigue dalam rentang sehat
โš  Perhatian Fatigue โ‰ฅ70 atau HRV <44ms โ€” mulai butuh recovery lebih
โœ• Tinggi Fatigue โ‰ฅ74 atau HRV <41ms โ€” prioritaskan istirahat
๐Ÿ“ˆ Tren HC Pagi vs Fatigue
Rata-rata harian per minggu, dari Riwayat Harian (Daily Log)
๐Ÿ†š Komparasi Mingguan
Minggu ini vs beberapa minggu sebelumnya
๐Ÿธ
Badminton โ€” Juni 2026
โœ… Selesai
Edit Data Harian
โšก Energi & Recovery
๐Ÿƒ Aktivitas
๐Ÿ˜ด Tidur
๐Ÿ˜ด Nap / Tidur Siang
๐Ÿ“Š Metrik Tambahan
๐Ÿ™‚ Kondisi Badan
๐Ÿ”ฌ Analisa Harian
Analisa rule-based dari data harian (bukan AI-generated)
๐Ÿ“‹ Kesimpulan
-
๐Ÿ”ฌ Analisa Science Sport
    ๐Ÿ“ˆ Perkembangan (vs rataยฒ 7 hari sebelumnya)
      ๐Ÿ’ก Rekomendasi
        โš–๏ธ Weight Trend
        Tren berat badan & komposisi โ€” data dari timbangan harian Zepp
        ๐ŸŽฏ
        Progress menuju target
        Berat: - โ†’ target - ยท sisa - Body Fat: - โ†’ target - ยท sisa -
        Berat 0% progress
        Body Fat 0% progress
        Lihat Program โ†’
        โš–๏ธ
        โ–ผ 0.8kg
        74.2 kg
        Berat Badan
        ๐Ÿ”ฅ
        โ–ผ 0.5%
        21.4 %
        Body Fat
        ๐Ÿ’ช
        โ€“
        - %
        Skeletal Muscle
        ๐Ÿ“Š
        โ€“ 0%
        23.4
        BMI
        ๐Ÿ“‰
        โˆ’2.6 kg
        ฮ” vs 3 Bulan Lalu
        ๐Ÿ“…
        16 Jun
        Update Terakhir
        โœ…
        Belum cukup data buat lihat tren โ€” isi data Body Composition beberapa kali dulu.
        Tren Berat Badan & Body Fat
        3 bulan terakhir
        Riwayat Penimbangan
        Detail per sesi ยท klik untuk detail
        TanggalBeratBody FatOtotBMIฮ” Berat
        15 Jun 202675.0 kg21.9%57.8 kg23.6โ–ผ 0.4
        1 Jun 202675.4 kg22.3%57.5 kg23.7โ–ผ 0.7
        18 Mei 202676.1 kg22.8%57.0 kg23.9โ–ผ 0.7
        4 Mei 202676.8 kg23.5%56.6 kg24.1โ–ผ 0.8
        20 Apr 202677.6 kg24.1%56.2 kg24.4โ€”
        ๐Ÿงฌ Body Analysis
        Komposisi tubuh (2 mingguan) ยท Body Measurement (bulanan) โ€” MovingLife Smart Scale
        ๐Ÿ”ด Belum Terhubung
        ๐ŸŽฏ Program
        ๐Ÿ“Š Analysis
        ๐Ÿ“ Measurements
        ๐Ÿ“œ History
        ๐ŸŽฏ Cutting Q3 2026
        1 Jul 2026 โ€” 30 Sep 2026 ยท Minggu ke-4 dari 13
        โณ 65 hari lagi
        Weight
        72.7โ†’ 68.0 kg
        Body Fat
        23.4%โ†’ 18.0%
        Skeletal Muscle
        43.2%โ†’ 46.0%
        BMI
        22.9โ†’ 21.5
        Visceral Fat
        6โ†’ 4
        โ–ผ Riwayat Program Sebelumnya (0)
        Belum ada riwayat program
        Arie Rayana
        ๐Ÿ“ 178cm๐ŸŽ‚ 39 tahun๐Ÿ‘ค Male๐Ÿ• -
        -
        BODY SCORE
        ๐Ÿ“‹ Body Composition Analysis
        Measurement vs Normal Range (rentang standar dewasa, belum dipersonalisasi per tinggi/usia)
        ItemMeasurementValuationNormal Range
        Belum ada data
        โš–๏ธ Weight Management
        Target penyesuaian
        Belum ada data
        ๐Ÿ”ฅ Other Indicators
        Belum ada data
        ๐Ÿ’ช Muscle Fat Analysis
        Posisi nilai dalam rentang Low โ€“ Standard โ€“ High
        Belum ada data
        ๐Ÿ“Š Obesity Analysis
        BMI, Body Fat %, Visceral Fat Index
        Belum ada data
        Visceral Fat Index
        - (normal: 1โ€“9)
        ๐Ÿง Body Type Analysis
        Klasifikasi berdasarkan kombinasi BMI & Body Fat
        Masked Obesity
        Slightly Chubby
        Obesity
        Slightly Lean
        Standard
        Strong
        Too Lean
        Active
        Muscular
        ๐Ÿงโ€โ™‚๏ธ Segmental Fat Analysis
        Distribusi lemak per bagian tubuh
        Belum ada data
        ๐Ÿง
        Standard range: 80% โ€“ 160%
        โš–๏ธ Muscle Balance
        Distribusi massa otot per bagian tubuh
        Belum ada data
        ๐Ÿง
        Standard: lengan/kaki 80โ€“120%, trunk 90โ€“110%
        ๐Ÿ’ก Health Advice
        -
        BODY SCORE
        Belum ada data Body Composition โ€” isi lewat form di bawah dulu.
        ๐Ÿ“ Body Measurement ๐Ÿ—“๏ธ Bulanan
        Lingkar tubuh โ€” diukur pakai meteran, terpisah dari timbangan
        31 cm
        Lengan (Normal)
        โ–ฒ 0.5cm
        35 cm
        Lengan (Kontraksi)
        โ–ฒ 0.8cm
        88 cm
        Lingkar Perut
        โ–ผ 1.2cm
        98 cm
        Lingkar Dada
        โ–ฒ 0.3cm
        56 cm
        Lingkar Paha
        โ–ผ 0.4cm
        โœ๏ธ Input Pengukuran Bulan Ini
        ๐Ÿ“ˆ Tren Body Score & Weight
        Per sesi pengukuran (per ~2 minggu)
        ๐Ÿ“œ Riwayat Pengukuran
        Body Score & indikator utama
        TanggalScoreWeightBody FatBMIVisceral FatSkeletal Muscle
        29 Jun 20267672.7 kg23.4%22.9643.2%
        15 Jun 20267473.1 kg23.8%23.1643.0%
        1 Jun 20267273.6 kg24.2%23.3742.8%
        18 Mei 20267074.0 kg24.7%23.5742.6%
        4 Mei 20266874.5 kg25.2%23.7742.3%
        โœ๏ธ Input Pengukuran Baru
        โŒจ๏ธ Form Manual (ringkas)
        ๐Ÿ–ผ๏ธ Upload Screenshot (rekomendasi)
        ๐Ÿ“ธ
        Klik untuk pilih screenshot laporan MovingLife / smart scale
        1 gambar laporan lengkap โ€” indikator utama dibaca otomatis oleh AI
        ๐Ÿ“… Tanggal Pengukuran
        โ„น๏ธ AI cuma baca indikator utama (Score, Weight, BodyFat, BMI, VisceralFat, SkeletalMuscle, BMR) โ€” bukan segmental fat/muscle balance/body type (belum didukung).
        ๐Ÿค’ Sick Log
        Riwayat & analisa hari sakit โ€” kondisi, obat, dan tren dari waktu ke waktu
        ๐Ÿ˜Š Belum ada hari sakit tercatat dalam periode ini.
        ๐Ÿฉบ Analisa & Rekomendasi
        -
        ๐Ÿ“‹ Riwayat Hari Sakit
        -
        TanggalDurasiKondisi / DiagnosaObat & Dosis
        ๐Ÿ“ˆ Tren Bulanan
        Jumlah hari sakit per bulan โ€” 6 bulan terakhir
        ๐Ÿ‹๏ธ Workout
        Badminton ยท Lari ยท Gym โ€” catat hasil tiap sesi dari Zepp App
        ๐ŸŸข Terhubung ke Sheets
        ๐Ÿธ Badminton
        ๐Ÿƒ Lari
        ๐Ÿ’ช Gym
        -
        Sesi / Minggu
        -
        Rataยฒ Durasi
        -
        Rataยฒ Kalori
        -
        Rataยฒ Avg HR
        -
        Rataยฒ Training Load
        -
        Rataยฒ Training Effect
        ๐ŸŽฏ Target Performa Badminton
        โœ๏ธ Edit Target
        Training Effect โ€” Aerobic
        -โ†’4.5
        Training Effect โ€” Anaerobic
        -โ†’4.7
        โ„น๏ธ "Cur" dihitung dari rata-rata 5 sesi terakhir yang punya data Training Effect. Target spesifik lain (selain training effect) belum ditentukan โ€” bisa ditambah nanti kalau sudah ada metrik yang relevan.
        ๐ŸŽฏ Rata-rata Zona Latihan
        Belum ada data
        Riwayat Sesi Terbaru
        3 sesi terakhir
        Training Load โ€” 8 Sesi Terakhir
        Badminton
        ๐Ÿ“‹ Riwayat Sesi Badminton
        TanggalDurasiKaloriAvg HRMax HRLoadTE AeroTE AnaeroBalanceRPE
        27 Jun 07:151j 45m1.2051471803144.44.565/358
        24 Jun 19:301j 22m9801391722704.03.963/377
        20 Jun 07:001j 50m1.2601511823204.54.666/349
        18 Jun1j 35m1.0801401722784.03.962/387
        15 Jun1j 40m1.1501431762904.24.063/377
        11 Jun1j 50m1.2601511823204.54.666/349
        8 Jun1j 45m1.2051471803144.44.565/358
        4 Jun1j 42m1.1801451782954.34.263/377
        1 Jun1j 38m1.1201421752854.24.162/387
        Belum ada data
        โœ๏ธ Input Sesi Badminton
        โŒจ๏ธ Form Manual
        ๐Ÿ–ผ๏ธ Upload Screenshot
        ๐Ÿ“… Waktu Sesi
        โค๏ธ Heart Rate & Load
        ๐Ÿ“Š Heart Rate Zone (menit per zona)
        162โ€“181 bpm ยท menit:detik
        :
        144โ€“161 bpm ยท menit:detik
        :
        126โ€“143 bpm ยท menit:detik
        :
        108โ€“125 bpm ยท menit:detik
        :
        90โ€“107 bpm ยท menit:detik
        :
        ๐ŸŽฏ Training Effect & Catatan
        -
        Sesi / Minggu
        -
        Rataยฒ Jarak
        -
        Rataยฒ Pace
        -
        Rataยฒ Avg HR
        -
        PR 3K
        -
        PR 5K
        ๐ŸŽฏ Target Performa Lari
        โœ๏ธ Edit Target
        VOโ‚‚ Max
        -โ†’48.0
        Training Effect โ€” Aerobic
        -โ†’3.5
        Training Effect โ€” Anaerobic
        -โ†’1.5
        โ„น๏ธ "Cur" dihitung dari rata-rata 5 sesi terakhir. VOโ‚‚ Max belum ada di data Zepp yang dibaca app ini, jadi tetap "-" sampai field itu diisi manual.
        ๐ŸŽฏ Rata-rata Zona Latihan
        Belum ada data
        Riwayat Sesi Terbaru
        3 sesi terakhir
        Tren Pace โ€” 8 Sesi Terakhir
        Lari, semakin rendah semakin cepat
        ๐Ÿ“‹ Riwayat Sesi Lari
        TanggalJarakWaktuAvg PaceKaloriAvg HRCadenceTE AeroTE AnaeroPR
        28 Jun 07:375.01 km43:238'39"/km4561391522.30.3โ€”
        26 Jun4.20 km38:309'10"/km3881321491.80.1โ€”
        23 Jun5.01 km43:238'39"/km4561391522.30.3โ€”
        17 Jun4.50 km39:508'51"/km4101351512.00.2โ€”
        13 Jun5.50 km45:028'11"/km4801411542.40.3PR 5K
        10 Jun4.20 km38:109'05"/km3901331481.90.1โ€”
        6 JunTerlewat โ€” hujan derasโ€”
        3 Jun4.80 km41:328'39"/km4321361502.10.2โ€”
        Belum ada data
        โœ๏ธ Input Sesi Lari
        โŒจ๏ธ Form Manual
        ๐Ÿ–ผ๏ธ Upload Screenshot
        ๐Ÿ“… Waktu & Jarak
        :
        โšก Pace & Cadence
        menit'detik" per km
        '"
        menit'detik" per km
        '"
        spm
        spm
        โค๏ธ Heart Rate & Load
        ๐Ÿ“Š Heart Rate Zone (menit per zona)
        162โ€“181 bpm ยท menit:detik
        :
        144โ€“161 bpm ยท menit:detik
        :
        126โ€“143 bpm ยท menit:detik
        :
        108โ€“125 bpm ยท menit:detik
        :
        90โ€“107 bpm ยท menit:detik
        :
        ๐Ÿฆต Biomekanik Lari
        cm
        cm
        cm
        ms
        ๐ŸŽฏ Training Effect & Catatan
        ๐Ÿ Personal Record (isi kalau ada PR baru)
        ๐Ÿ Lap Time (otomatis mengikuti jumlah km di field Jarak)
        KmPace (menit'detik/km)Heart RateDurasi Kumulatif (jam:menit:detik)
        Isi field "Jarak (km)" di atas dulu buat munculin baris lap
        -
        Sesi / Minggu
        -
        Rataยฒ Durasi
        -
        Rataยฒ Kalori
        -
        Rataยฒ Avg HR
        -
        Rataยฒ Jumlah Set
        -
        Fokus Terbanyak
        ๐ŸŽฏ Rata-rata Zona Latihan
        Belum ada data
        Riwayat Sesi Terbaru
        3 sesi terakhir
        Muscle Group Frequency
        30 hari terakhir
        ๐Ÿ“‹ Riwayat Sesi Gym
        TanggalDurasiSetsKaloriAvg HRMax HRFokus OtotBalance
        23 Jun 06:3331m 43s1313086101Biceps ยท Shoulder ยท Chest30/70
        19 Jun 06:4028m 10s1111891106Biceps ยท Triceps ยท Shoulder31/69
        16 Jun30m 55s111228599Shoulder ยท Triceps ยท Biceps30/70
        12 JunTerlewat โ€” jadwal berubahโ€”
        9 Jun31m 20s1212886100Chest ยท Shoulder ยท Biceps29/71
        5 Jun29m 45s1011888103Biceps ยท Triceps ยท Shoulder30/70
        2 Jun32m 10s111258498Biceps ยท Shoulder ยท Chest28/72
        Belum ada data
        โœ๏ธ Input Sesi Gym
        โŒจ๏ธ Form Manual
        ๐Ÿ–ผ๏ธ Upload Screenshot
        ๐Ÿ“… Waktu Sesi
        :
        :
        ๐Ÿ‹๏ธ Set Details โ€” Tambah Gerakan
        ร—
        ๐Ÿ“Š Heart Rate Zone (menit per zona)
        162โ€“181 bpm ยท menit:detik
        :
        144โ€“161 bpm ยท menit:detik
        :
        126โ€“143 bpm ยท menit:detik
        :
        108โ€“125 bpm ยท menit:detik
        :
        90โ€“107 bpm ยท menit:detik
        :
        ๐ŸŽฏ Lainnya
        ๐Ÿ”ฌ Science Sport
        Analisa mendalam indikator & statistik latihan per olahraga
        ๐Ÿธ Badminton
        ๐Ÿƒ Lari
        ๐Ÿ’ช Gym
        โค๏ธ Cardio & Load
        -
        Training Load-
        -
        Avg Heart Rate-
        -
        Max Heart Rate-
        -
        Kalori / Sesi-
        -
        Belum ada cukup data sesi Badminton.
        ๐ŸŽฏ Training Effect
        -
        Aerobic Effect-
        -
        Anaerobic Effect-
        -
        Workout Balance-
        -
        Sesi / Minggu-
        -
        Belum ada cukup data sesi Badminton.
        ๐Ÿ“Š HR Zone Distribution
        Rasio Waktu
        Belum ada data zona HR.
        Belum ada cukup data.
        ๐Ÿƒ Pace & Jarak
        -
        Avg Pace-
        -
        Jarak / Sesi-
        -
        Optimum Pace-
        -
        Belum ada cukup data sesi Lari.
        ๐Ÿฆต Biomekanik Lari
        -
        Avg Cadence-
        -
        Ground Contact Time-
        -
        Vertical Oscillation-
        -
        Belum ada cukup data sesi Lari.
        ๐ŸŽฏ Training Effect & PR
        -
        Aerobic Effect-
        -
        Anaerobic Effect-
        -
        PR Terbaru-
        -
        Belum ada cukup data sesi Lari.
        โค๏ธ Cardio & Load
        -
        Training Load-
        -
        Avg Heart Rate-
        -
        Max Heart Rate-
        -
        Kalori / Sesi-
        -
        Belum ada cukup data sesi Lari.
        ๐Ÿ’ช Volume & Set
        -
        Avg Sets / Sesi-
        -
        Durasi / Sesi-
        -
        Sesi / Minggu-
        -
        Belum ada cukup data sesi Gym.
        โค๏ธ Cardio & Load
        -
        Avg Heart Rate-
        -
        Training Load-
        -
        Kalori / Sesi-
        -
        Belum ada cukup data sesi Gym.
        ๐Ÿ‹๏ธ Fokus Otot
        -
        Upper Body-
        Core-
        Lower Body-
        RPE Rata-rata-
        Belum ada cukup data sesi Gym.
        ๐Ÿ“‹ Text Prompt
        Markdown siap copy-paste ke ChatGPT / Gemini / Claude โ€” pilih hari atau range tanggal
        ๐Ÿ“ค Export ke AI
        ๐Ÿ“ฅ Import dari AI
        1๏ธโƒฃ Pilih Mode Tanggal
        ๐Ÿ“… Hari Tertentu
        ๐Ÿ“† Range Tanggal
        2๏ธโƒฃ Pilih Data yang Dimasukkan
        ๐Ÿค– Format untuk AI:
        โ˜๏ธ Claude: Format dengan XML tags untuk konteks yang lebih terstruktur โ€” optimal untuk analisis mendalam.
        ๐Ÿ“œ Preview โ€” 29 Juni 2026
        
                
              
        ๐Ÿ“ฅ Import from AI โ€” Backfill Data Paste markdown dari ChatGPT / Gemini ยท hanya isi field yang kosong
        Paste teks markdown yang sudah dikirim ke AI (format Daylix Summary). App akan membaca tanggal dan angka-angkanya, lalu mengisi data yang belum ada di Daylix.
        ๐Ÿค– AI Summary
        Generate ringkasan & insight otomatis pakai Claude API โ€” hasil tersimpan sebagai histori di Google Sheets
        1๏ธโƒฃ Pilih Periode
        7 Hari
        30 Hari
        90 Hari
        ๐Ÿ“† Custom Range
        ๐Ÿ“… Periode terpilih: -
        2๏ธโƒฃ Pilih Data yang Dianalisa
        ๐Ÿ•˜ Riwayat AI Summary
        Klik baris untuk lihat teks lengkap
        Waktu Generate Periode Model
        Belum ada riwayat AI Summary
        ๐Ÿ’ฐ Konfirmasi Estimasi Biaya
        Generate ringkasan bakal manggil Claude API pakai key yang tersimpan di Settings โ€” ada biaya sesuai token yang dipakai.
        Model-
        Periode-
        Estimasi Input-
        Estimasi Output-
        Estimasi Total Biaya-
        *Estimasi kasar berdasarkan panjang teks โ€” biaya aktual (dari response API) ditampilkan setelah hasil keluar.
        โš™๏ธ Settings
        Google Sheets ยท Claude API ยท Reminder Targets ยท App Preferences
        ๐Ÿ”Œ Koneksi & Integrasi
        ๐ŸŽฏ Preferensi
        ๐Ÿ”ด
        Belum terhubung ke Google Sheets
        Isi URL Apps Script Web App di bawah
        ๐Ÿ”ด Nonaktif
        ๐Ÿ”ด
        Claude API
        Belum diisi
        ๐Ÿ”ด Belum Diisi
        ๐ŸŽญ Demo Mode
        Nonaktif โ€” app pakai data real dari Google Sheets
        Nyalakan buat isi semua halaman (Dashboard, History, Workout, Body Analysis, Training Plan, dst) dengan data dummy โ€” cocok buat demo/screenshot tanpa perlu koneksi Google Sheets asli. Data yang tampil TIDAK ikut kesimpan ke Sheets selama mode ini aktif.
        Koneksi Google Sheets
        Hubungkan dashboard ke Google Sheets sebagai database permanen
        URL ini didapat setelah deploy Google Apps Script sebagai Web App
        ๐Ÿ“‹ Checklist sebelum Test Koneksi:
        1๏ธโƒฃ  URL diawali https://script.google.com/macros/s/ dan diakhiri /exec
        2๏ธโƒฃ  Di Apps Script: Deploy โ†’ Manage deployments โ†’ Edit (โœ๏ธ) โ†’ New version
        3๏ธโƒฃ  "Execute as" = Me  ยท  "Who has access" = Anyone
        4๏ธโƒฃ  Copy URL dari kolom "Web app URL" (bukan Deployment ID)
        โš ๏ธ Data Management
        Reset koneksi Google Sheets yang tersimpan di browser ini ยท tidak mempengaruhi data yang sudah ada di Google Sheets
        Ambil ulang SEMUA data dari Google Sheets dari awal (Daily Log, Body Composition, Workout, dst) โ€” dipakai kalau data kerasa nyangkut/gak update meski udah nunggu.
        ๐Ÿค– Claude API Key ๐ŸŸข Model: claude-sonnet-5
        Digunakan untuk: Upload screenshot OCR (Daily Input) ยท AI Summary insight. Motivasi pagi sekarang berbasis kumpulan quote di Google Sheets, tidak pakai Claude API lagi.
        Dapatkan API key di console.anthropic.com ยท Pay-as-you-go, terpisah dari Claude Pro subscription
        โ„น๏ธ Klik "Simpan" untuk memvalidasi API key (dicek langsung ke Anthropic, gratis โ€” tidak ada biaya token).
        โš ๏ธ API key disimpan di browser lokal (localStorage) โ€” tidak dikirim ke server manapun selain langsung ke api.anthropic.com. Jangan gunakan di device publik.
        ๐Ÿ“‹ Info Spreadsheet Terhubung
        ๐Ÿ“Š Belum terhubung
        Isi & simpan URL Apps Script di atas, lalu klik "Test Koneksi" untuk melihat info sheet di sini.
        ๐Ÿ“– Panduan Setup Google Sheets (sekali saja)
        1
        Buat Google Spreadsheet baru
        Buka sheets.google.com โ†’ buat spreadsheet baru โ†’ beri nama Daylix DB
        2
        Salin Spreadsheet ID
        Dari URL spreadsheet: docs.google.com/spreadsheets/d/SPREADSHEET_ID/edit
        3
        Buka Apps Script Editor
        Di spreadsheet โ†’ menu Extensions โ†’ Apps Script
        4
        Paste kode Apps Script
        Hapus kode default, paste kode dari kotak di bawah, ganti SPREADSHEET_ID โ†’ klik Save
        5
        Deploy sebagai Web App
        Deploy โ†’ New deployment โ†’ Web app โ†’ Execute as: Me โ†’ Who has access: Anyone
        6
        Paste URL di atas โ†’ Test Koneksi โ†’ Simpan
        Dashboard siap terhubung ke Google Sheets.
        ๐Ÿ“œ Kode Google Apps Script (siap pakai)
        Buka Google Sheet baru โ†’ Extensions โ†’ Apps Script โ†’ hapus isi default โ†’ paste kode ini โ†’ Deploy โ†’ New deployment โ†’ Web app (Execute as: Me, Who has access: Anyone) โ†’ copy Web app URL ke kolom di atas.
        /**
         * DAYLIX โ€” Google Apps Script Backend
         * Sheet "Daily Log" dan "Body Composition" dibuat otomatis kalau belum ada.
         */
        
        // Ambil dari URL spreadsheet: docs.google.com/spreadsheets/d/SPREADSHEET_ID/edit
        // Kalau script ini dibuat lewat menu Extensions โ†’ Apps Script DI DALAM spreadsheet-nya,
        // boleh dikosongkan โ€” otomatis pakai spreadsheet yang aktif.
        const SPREADSHEET_ID = 'PASTE_SPREADSHEET_ID_DISINI';
        
        const SHEET_DAILY = 'Daily Log';
        const DAILY_HEADERS = ['Date','HybridCharge','RHR','HRV','Fatigue','Steps','Workout','PAI','Exertion','SleepDuration','DeepSleep','SleepScore','KondisiPagi','KondisiMalam',
          'RemSleep','AwakeMinutes','AwakeCount','SleepRegularity','FitnessLevel','TrainingStatus','TotalKm','HybridChargeMalam','NapDuration','NapGain','HybridChargeDrain','HybridChargeGain',
          'SickCondition','Medications'];
        
        const SHEET_BODY = 'Body Composition';
        const BODY_HEADERS = ['Date','Score','Weight','BodyFat','BMI','VisceralFat','SkeletalMuscle','BMR',
          'InorganicSalt','Protein','BodyWater','Muscle',
          'StandardWeight','WeightControl','FatControl','MuscleControl',
          'SubcutaneousFat','LeanBodyMass','SMI',
          'ArmLeftFatKg','ArmLeftFatPct','ArmRightFatKg','ArmRightFatPct','LegLeftFatKg','LegLeftFatPct','LegRightFatKg','LegRightFatPct','TrunkFatKg','TrunkFatPct',
          'ArmLeftMuscleKg','ArmLeftMusclePct','ArmRightMuscleKg','ArmRightMusclePct','LegLeftMuscleKg','LegLeftMusclePct','LegRightMuscleKg','LegRightMusclePct','TrunkMuscleKg','TrunkMusclePct'];
        
        const SHEET_WORKOUT = 'Workout Sessions';
        const WORKOUT_HEADERS = ['Date','Type','StartTime','Duration','Kcal','AvgHR','MaxHR','Load','Distance','Pace','Steps','Sets','Notes',
          'Balance','TEAero','TEAnae','HRZoneVO2','HRZoneAnaerobic','HRZoneAerobic','HRZoneIntensive','HRZoneLight','RPE',
          'OptimumPace','AvgCadence','MaxCadence','AvgStride','MaxStride','VertOsc','GCT','PR3K','PR5K','PR10K',
          'TotalSetIntervalTime','ExerciseDetail','SessionID','LapDetail','VO2Max','HybridChargeUsed'];
        
        const SHEET_MEASUREMENT = 'Body Measurement';
        const MEASUREMENT_HEADERS = ['Date','LenganNormal','LenganKontraksi','LingkarPerut','LingkarDada','LingkarPaha'];
        
        const SHEET_AI_SUMMARY = 'AI Summary Log';
        const AI_SUMMARY_HEADERS = ['Date','PeriodLabel','PeriodStart','PeriodEnd','SummaryText','Model'];
        
        const SHEET_QUOTES = 'Motivational Quotes';
        const QUOTES_HEADERS = ['Category','Text'];
        const QUOTES_SEED = [
          ['fatigue_tinggi', 'Fatigue lagi tinggi โ€” bukan berarti gagal, tubuh cuma minta didengerin. Pelan-pelan aja hari ini.'],
          ['fatigue_tinggi', 'Capek boleh, nyerah jangan. Kurangi intensitas dikit hari ini, besok bisa gas lagi.'],
          ['fatigue_tinggi', 'Tubuh yang lelah itu sinyal, bukan musuh. Dengerin, jangan dilawan terus.'],
          ['fatigue_tinggi', 'Fatigue tinggi sekarang bukan akhir dari progress-mu โ€” anggap aja rem sejenak biar nanti bisa lari lebih jauh.'],
          ['fatigue_tinggi', 'Kalau hari ini rasanya berat, gak apa-apa. Kurangi target, yang penting tetap jalan walau pelan.'],
          ['fatigue_tinggi', 'Recovery itu bagian dari latihan, bukan jeda dari latihan. Istirahat hari ini investasi buat performa besok.'],
          ['fatigue_tinggi', 'Fatigue numpuk biasanya nunjukin kamu udah kerja keras belakangan ini. Kasih diri sendiri izin buat pulih.'],
          ['fatigue_tinggi', 'Satu hari santai gak bakal ngilangin progress yang udah kamu bangun. Justru bikin progress itu lebih tahan lama.'],
          ['fatigue_tinggi', 'Tubuh capek itu wajar โ€” yang penting kamu tetep dengerin sinyalnya, bukan maksa terus.'],
          ['fatigue_tinggi', 'Kadang kemenangan terbesar hari ini adalah berani bilang "cukup dulu" dan istirahat.'],
          ['fatigue_tinggi', 'Fatigue tinggi bukan tanda kamu lemah, itu tanda kamu udah push cukup keras. Sekarang giliran recovery.'],
          ['fatigue_tinggi', 'Coba kurangi 1 hal aja hari ini โ€” entah intensitas latihan atau jam begadang. Tubuh bakal berterima kasih.'],
          ['fatigue_tinggi', 'Kelelahan itu bukan penghalang, itu pengingat buat lebih sayang sama tubuh sendiri.'],
          ['fatigue_tinggi', 'Gak semua hari harus jadi hari terbaik. Hari ini cukup jadi hari yang tenang.'],
          ['fatigue_tinggi', 'Dengerin tubuhmu โ€” kalau dia bilang pelan-pelan, percaya aja. Dia yang paling tau kondisinya.'],
          ['hrv_rendah', 'HRV lagi turun, tanda sistem saraf lagi kerja keras buat recovery. Tidur cukup malam ini bakal ngebantu banget.'],
          ['hrv_rendah', 'HRV rendah bukan berarti lemah โ€” itu cuma minta waktu buat pulih. Kasih ruang buat tubuh reset.'],
          ['hrv_rendah', 'Sistem sarafmu lagi butuh napas. Coba kurangi stress & tidur lebih awal beberapa hari ini.'],
          ['hrv_rendah', 'HRV turun itu respons normal tubuh pas lagi capek โ€” cukup direspons dengan istirahat, bukan dikhawatirkan berlebihan.'],
          ['hrv_rendah', 'Recovery gak instan, tapi konsisten. HRV bakal naik lagi kalau kamu kasih waktu yang cukup.'],
          ['hrv_rendah', 'Coba napas dalam beberapa menit hari ini โ€” hal kecil kayak gitu bisa bantu sistem saraf lebih rileks.'],
          ['hrv_rendah', 'HRV rendah sering nunjukin tubuh lagi proses adaptasi. Percaya prosesnya, jangan buru-buru.'],
          ['hrv_rendah', 'Kalau HRV lagi di bawah biasanya, itu waktunya kurangi beban โ€” baik fisik maupun mental.'],
          ['hrv_rendah', 'Sistem pemulihanmu lagi kerja di balik layar. Kasih dia waktu tanpa gangguan tambahan.'],
          ['hrv_rendah', 'HRV rendah bukan alasan buat panik, itu alasan buat lebih pelan-pelan dulu.'],
          ['hrv_rendah', 'Recovery HRV butuh waktu, sama kayak otot butuh waktu buat pulih abis latihan berat.'],
          ['hrv_rendah', 'Coba kurangi kafein sore ini dan tidur 30 menit lebih awal โ€” langkah kecil buat HRV yang lebih baik.'],
          ['hrv_rendah', 'Setiap penurunan HRV itu informasi, bukan hukuman. Dengerin, terus sesuaikan ritme harianmu.'],
          ['hrv_rendah', 'Tubuh yang lagi mikirin banyak hal biasanya HRV-nya ikut turun. Coba kasih waktu buat diam sejenak hari ini.'],
          ['hrv_rendah', 'HRV bakal balik normal โ€” yang penting sekarang fokus ke hal yang bisa kamu kontrol: tidur, napas, dan istirahat.'],
          ['sleep_kurang', 'Tidur kemarin kurang, tapi hari ini masih bisa jadi hari yang baik. Coba tidur lebih awal malam ini ya.'],
          ['sleep_kurang', 'Kurang tidur emang bikin berat, tapi satu langkah kecil hari ini tetap berarti.'],
          ['sleep_kurang', 'Tidur adalah recovery paling murah dan paling efektif yang kamu punya. Jangan di-skip terus.'],
          ['sleep_kurang', 'Satu malam kurang tidur gak nentuin seluruh minggu. Perbaiki mulai malam ini.'],
          ['sleep_kurang', 'Coba matiin layar 30 menit lebih awal malam ini โ€” langkah kecil buat tidur yang lebih baik.'],
          ['sleep_kurang', 'Ngantuk hari ini wajar abis kurang tidur. Kasih diri sendiri sedikit kelonggaran, gak perlu maksa 100%.'],
          ['sleep_kurang', 'Tidur cukup bukan kemewahan, itu kebutuhan dasar buat performa dan mood besok.'],
          ['sleep_kurang', 'Kalau bisa, coba tidur siang sebentar hari ini buat nutupin sedikit defisit semalam.'],
          ['sleep_kurang', 'Utang tidur itu bisa dibayar โ€” mulai dari malam ini, prioritaskan jam tidur lebih dari biasanya.'],
          ['sleep_kurang', 'Kurang tidur bikin segalanya terasa lebih berat dari biasanya. Itu wajar, bukan berarti kamu gagal.'],
          ['sleep_kurang', 'Coba atur alarm tidur malam ini, bukan cuma alarm bangun. Kadang itu yang bikin beda.'],
          ['sleep_kurang', 'Tubuh yang kurang tidur butuh belas kasih, bukan tuntutan buat tetap performa maksimal.'],
          ['sleep_kurang', 'Satu perubahan kecil โ€” tidur 30 menit lebih awal โ€” bisa nambah energi signifikan besok pagi.'],
          ['sleep_kurang', 'Ngantuk itu sinyal jujur dari tubuh. Dengerin, jangan dilawan pakai kafein terus-terusan.'],
          ['sleep_kurang', 'Kalau hari ini berat karena kurang tidur, itu bukan kegagalan โ€” itu kesempatan buat benerin rutinitas malam ini.'],
          ['streak_membaik', 'Beberapa hari terakhir tren-nya membaik terus โ€” konsistensi kecil ini yang bikin hasil besar. Lanjutkan!'],
          ['streak_membaik', 'Progress-mu belakangan ini kelihatan jelas. Apa yang kamu lakukan sekarang ini works, jangan diubah dulu.'],
          ['streak_membaik', 'Tren naik ini bukan kebetulan โ€” ini hasil dari kebiasaan yang kamu jaga belakangan ini.'],
          ['streak_membaik', 'Konsistensi yang kamu bangun mulai kelihatan hasilnya. Terus aja, jangan berhenti sekarang.'],
          ['streak_membaik', 'Data belakangan ini nunjukin kamu di jalur yang benar. Percaya proses ini terus.'],
          ['streak_membaik', 'Progress kecil yang konsisten itu lebih powerful daripada lonjakan besar yang gak bertahan. Kamu lagi di jalur itu.'],
          ['streak_membaik', 'Tren membaik ini hasil kerja keras beberapa hari terakhir โ€” pantas buat dirayain sedikit.'],
          ['streak_membaik', 'Kamu lagi bikin momentum yang bagus. Jaga terus, sekecil apapun langkahnya.'],
          ['streak_membaik', 'Setiap hari yang kamu jaga konsistensinya, itu investasi buat versi dirimu yang lebih baik.'],
          ['streak_membaik', 'Ritme yang kamu bangun sekarang mulai berbuah. Jangan buru-buru ganti strategi, ini lagi jalan.'],
          ['streak_membaik', 'Progress ini bukti bahwa usaha kecil yang konsisten emang beneran ngaruh.'],
          ['streak_membaik', 'Tren positif ini hasil pilihan-pilihan kecil yang kamu ambil tiap hari. Keren, lanjutkan!'],
          ['streak_membaik', 'Kamu lagi ngebangun kebiasaan yang bakal berbuah jangka panjang. Tetap di jalur ini.'],
          ['streak_membaik', 'Hasil belakangan ini nunjukin: konsistensi ngalahin motivasi yang naik-turun. Terus jaga ritmenya.'],
          ['streak_membaik', 'Momentum bagus kayak gini sayang kalau diputus. Satu hari lagi konsisten, satu langkah lagi maju.'],
          ['streak_menurun', 'Beberapa hari terakhir agak menantang, tapi itu bagian dari proses. Fokus ke satu hal kecil aja hari ini.'],
          ['streak_menurun', 'Ada masa naik-turun, itu wajar. Yang penting tetap balik lagi besok.'],
          ['streak_menurun', 'Tren lagi turun bukan berarti gagal โ€” itu bagian normal dari perjalanan panjang.'],
          ['streak_menurun', 'Gak semua minggu bakal sempurna. Yang penting kamu tetap balik ke rutinitas, pelan-pelan pun gak apa.'],
          ['streak_menurun', 'Satu periode kurang oke gak ngehapus semua progress yang udah kamu bangun sebelumnya.'],
          ['streak_menurun', 'Kalau lagi ngerasa stuck, coba turunin ekspektasi hari ini jadi lebih sederhana โ€” cukup jaga 1 kebiasaan aja.'],
          ['streak_menurun', 'Penurunan ini sementara. Yang bikin beda adalah gimana kamu merespons, bukan seberapa lama turunnya.'],
          ['streak_menurun', 'Semua orang punya fase kayak gini. Yang penting jangan berhenti total, cukup pelan-pelan lagi.'],
          ['streak_menurun', 'Coba fokus ke 1 kemenangan kecil hari ini, bukan ke semua yang belum tercapai minggu ini.'],
          ['streak_menurun', 'Tren turun ini kesempatan buat evaluasi, bukan alasan buat nyerah.'],
          ['streak_menurun', 'Progress itu gak pernah linear. Fase turun kayak gini justru sering jadi awal dari comeback yang lebih kuat.'],
          ['streak_menurun', 'Gak apa-apa kalau beberapa hari ini berasa berat. Yang penting kamu masih di sini, masih coba.'],
          ['streak_menurun', 'Fokus ke proses, bukan ke angka. Angka bisa naik-turun, tapi usaha konsisten yang bakal bawa kamu maju.'],
          ['streak_menurun', 'Kadang tubuh & pikiran butuh fase lambat sebelum bisa gas lagi lebih kuat. Ini bagian dari itu.'],
          ['streak_menurun', 'Satu langkah kecil hari ini โ€” sekecil apapun โ€” udah cukup buat mutusin tren turun ini.'],
          ['steps_tercapai', 'Target langkah hari ini udah kecapai. Konsistensi kayak gini yang bikin progress jangka panjang.'],
          ['steps_tercapai', 'Steps hari ini tembus target โ€” kerja bagus! Aktivitas kecil kayak gini nambah ke kesehatan jangka panjang.'],
          ['steps_tercapai', 'Target langkah tercapai, tanda hari ini kamu gerak lebih dari cukup. Nice.'],
          ['steps_tercapai', 'Konsisten jalan kaki kayak gini emang gak keliatan dramatis, tapi efeknya nyata buat jantung & mood.'],
          ['steps_tercapai', 'Target steps hari ini beres. Aktivitas sekecil apapun tetap dihitung, dan kamu udah lakuin.'],
          ['steps_tercapai', 'Steps hari ini di atas target โ€” tubuh pasti berterima kasih atas gerakan ekstra ini.'],
          ['steps_tercapai', 'Target langkah tercapai lagi. Kebiasaan bagus kayak gini yang bikin beda dalam jangka panjang.'],
          ['steps_tercapai', 'Udah gerak cukup hari ini โ€” itu kemenangan kecil yang layak diapresiasi.'],
          ['steps_tercapai', 'Konsistensi jalan kaki harian kayak gini yang sering diremehkan, padahal efeknya besar buat kesehatan jantung.'],
          ['steps_tercapai', 'Target steps kecapai โ€” satu bukti lagi kalau kamu emang jaga rutinitas dengan baik.'],
          ['steps_tercapai', 'Aktivitas harian hari ini di atas target. Small wins kayak gini yang nambah tiap hari.'],
          ['steps_tercapai', 'Steps tercapai, energi terjaga. Terus lanjutin ritme aktivitas kayak gini.'],
          ['steps_tercapai', 'Target jalan kaki hari ini beres duluan โ€” sisa hari bisa dipake buat recovery yang lain.'],
          ['steps_tercapai', 'Gerak cukup hari ini artinya kamu ngasih tubuh kesempatan buat sirkulasi & mood yang lebih baik.'],
          ['steps_tercapai', 'Konsisten capai target steps kayak gini pelan-pelan ngebentuk kebiasaan sehat jangka panjang.'],
          ['workout_done_today', 'Udah gerak hari ini โ€” itu udah menang duluan dibanding kebanyakan orang. Nice work!'],
          ['workout_done_today', 'Sesi latihan hari ini beres. Tubuh bakal makin kuat tiap kali kamu tunjukin usaha kayak gini.'],
          ['workout_done_today', 'Latihan hari ini selesai โ€” satu lagi bukti komitmen kamu ke diri sendiri.'],
          ['workout_done_today', 'Workout hari ini bukan cuma bakar kalori, tapi juga bangun disiplin yang bakal kepake di area lain hidupmu.'],
          ['workout_done_today', 'Kamu udah nunjukin diri buat latihan hari ini. Itu pilihan yang gak semua orang berani ambil konsisten.'],
          ['workout_done_today', 'Sesi hari ini kelar. Recovery yang baik sekarang bakal bikin performa besok makin oke.'],
          ['workout_done_today', 'Latihan hari ini selesai โ€” waktunya kasih tubuh recovery yang layak, mulai dari makan & tidur cukup.'],
          ['workout_done_today', 'Konsistensi latihan kayak gini yang bikin perubahan nyata, bukan sesi latihan yang sesekali doang.'],
          ['workout_done_today', 'Workout hari ini beres โ€” satu langkah lagi menuju versi dirimu yang lebih kuat & sehat.'],
          ['workout_done_today', 'Effort hari ini di sesi latihan gak bakal sia-sia. Progress emang butuh waktu, tapi kamu lagi ke arah sana.'],
          ['workout_done_today', 'Udah nyempetin waktu buat latihan di tengah kesibukan โ€” itu sendiri udah prestasi.'],
          ['workout_done_today', 'Setiap sesi latihan itu deposit kecil ke kesehatan jangka panjang. Hari ini kamu baru aja nabung.'],
          ['workout_done_today', 'Latihan hari ini kelar โ€” sekarang giliran tubuh yang kerja pulihin diri. Kasih dia waktu.'],
          ['workout_done_today', 'Kamu konsisten muncul buat diri sendiri hari ini lewat latihan. Itu yang paling penting.'],
          ['workout_done_today', 'Workout selesai, satu kotak checklist lagi buat hari ini. Keep it up!'],
          ['general', 'Hari baru, kesempatan baru buat jadi versi terbaik dirimu.'],
          ['general', 'Konsisten itu lebih penting daripada sempurna. Satu langkah hari ini tetap berarti.'],
          ['general', 'Tubuh yang kuat dibangun dari kebiasaan kecil yang konsisten โ€” bukan dari satu hari yang luar biasa.'],
          ['general', 'Progress gak harus keliatan besar tiap hari. Yang penting arahnya tetap maju.'],
          ['general', 'Setiap pilihan sehat yang kamu ambil hari ini adalah investasi buat dirimu di masa depan.'],
          ['general', 'Gak perlu sempurna, cukup konsisten. Itu udah lebih dari cukup buat hasil jangka panjang.'],
          ['general', 'Hari ini adalah kesempatan baru buat dengerin tubuhmu lebih baik dari kemarin.'],
          ['general', 'Kemajuan kecil tetap kemajuan. Jangan remehin langkah sekecil apapun.'],
          ['general', 'Kamu gak perlu jadi versi sempurna hari ini, cukup jadi versi yang sedikit lebih baik dari kemarin.'],
          ['general', 'Rutinitas sehat yang kamu jaga sekarang bakal kerasa manfaatnya bertahun-tahun ke depan.'],
          ['general', 'Semua hasil besar dimulai dari kebiasaan kecil yang diulang terus-menerus. Kamu lagi di jalan itu.'],
          ['general', 'Dengerin tubuhmu hari ini โ€” dia yang paling tahu apa yang dia butuhkan.'],
          ['general', 'Satu keputusan sehat hari ini bisa jadi awal dari rantai kebiasaan baik berikutnya.'],
          ['general', 'Fokus ke proses, bukan cuma hasil akhir. Proses yang konsisten yang bakal bawa hasil.'],
          ['general', 'Kesehatan itu maraton, bukan sprint. Nikmati prosesnya, jangan buru-buru.'],
          ['general', 'Hari ini cukup jadi hari yang biasa aja โ€” gak semua hari harus jadi hari spesial buat tetap berarti.'],
          ['general', 'Kamu lebih kuat dari yang kamu kira. Buktinya, kamu masih di sini, masih coba tiap hari.'],
          ['general', 'Jangan bandingin progress-mu sama orang lain. Fokus ke versi dirimu kemarin, itu yang paling relevan.'],
          ['general', 'Setiap hari yang kamu jaga kebiasaan sehatnya, itu langkah kecil menuju hidup yang lebih baik.'],
          ['general', 'Istirahat itu bukan kemalasan, itu bagian dari strategi jangka panjang buat tetap konsisten.']
        ];
        
        function getSpreadsheet_() {
          if (SPREADSHEET_ID && SPREADSHEET_ID.indexOf('PASTE_') !== 0) {
            return SpreadsheetApp.openById(SPREADSHEET_ID);
          }
          return SpreadsheetApp.getActiveSpreadsheet();
        }
        
        function getSheet_(name, headers) {
          const ss = getSpreadsheet_();
          let sheet = ss.getSheetByName(name);
          if (!sheet) {
            sheet = ss.insertSheet(name);
            sheet.appendRow(headers);
          } else {
            // Migrasi otomatis: kalau ada kolom baru ditambahkan ke array headers lewat update kode,
            // sheet lama yang udah kadung dibuat gak pernah ikut ke-update header row-nya (cuma kejadian
            // sekali pas sheet baru pertama dibuat, lihat blok di atas) โ€” isi header yang kurang di ujung
            // kanan row 1 tanpa ganggu kolom/data yang udah ada.
            // PENTING: cek per-NAMA (bukan cuma banding JUMLAH kolom pakai getLastColumn()) โ€” getLastColumn()
            // itu whole-sheet (bisa kebaca lebih besar dari jumlah header asli row 1 kalau ada sisa
            // formatting/konten nyasar di kolom lain), jadi kalau dipakai buat nentuin "berapa banyak
            // header yang kurang", bisa salah hitung dan header yang ditulis ulang jadi geser posisi
            // (persis bug yang bikin kolom TotalKm skip kosong padahal kolom sesudahnya kefill benar).
            const currentLastCol = sheet.getLastColumn();
            const existingHeaders = currentLastCol > 0 ? sheet.getRange(1, 1, 1, currentLastCol).getValues()[0] : [];
            const missingHeaders = headers.filter(h => existingHeaders.indexOf(h) === -1);
            if (missingHeaders.length > 0) {
              sheet.getRange(1, currentLastCol + 1, 1, missingHeaders.length).setValues([missingHeaders]);
            }
          }
          return sheet;
        }
        
        // Baca header row FISIK sheet (row 1) dan petakan nama kolom โ†’ index (0-based).
        // Dipakai supaya baca/tulis data SELALU by NAMA kolom, bukan asumsi posisi array
        // headers di kode = posisi kolom asli di Sheet โ€” soalnya 2 hal itu bisa gak sinkron
        // kalau header baru pernah ditambah di TENGAH array (bukan di ujung) di suatu waktu,
        // padahal sheet lama udah kadung punya urutan kolom fisik yang berbeda. Kalau
        // posisi rely on array index doang, hasilnya data nyasar ke kolom yang salah
        // (persis kayak bug "Jarak keisi tanggal" yang dilaporkan user).
        function getHeaderIndexMap_(sheet) {
          const lastCol = sheet.getLastColumn();
          if (lastCol < 1) return {};
          const headerRow = sheet.getRange(1, 1, 1, lastCol).getValues()[0];
          const map = {};
          headerRow.forEach((h, i) => { if (h !== '') map[h] = i; });
          return map;
        }
        
        // Tulis 1 baris ke sheet dengan format kolom DIPAKSA "Plain Text" (@) SEBELUM nilai ditulis.
        // Alasan: field kayak Duration/HRZone yang isinya teks "MM:SS" (mis. "35:11") kalau ditulis
        // ke cell yang formatnya masih "Automatic" akan di-auto-convert Google Sheets jadi Time value
        // beneran (dibaca ulang jadi Date object, lalu formatCell_ format-in balik jadi "35:11:00" โ€”
        // 3 segmen, bukan 2 โ€” yang bikin parser MM:SS split-box di frontend gagal match & keliatan
        // kayak field-nya hilang isinya, padahal datanya ada tapi salah bentuk). Plain Text mencegah
        // auto-convert ini dari awal, jadi teks yang ditulis tetap apa adanya pas dibaca lagi.
        function writeRowValues_(sheet, rowIdx, rowValues) {
          const range = rowIdx
            ? sheet.getRange(rowIdx, 1, 1, rowValues.length)
            : sheet.getRange(sheet.getLastRow() + 1, 1, 1, rowValues.length);
          range.setNumberFormat('@');
          range.setValues([rowValues]);
        }
        
        function getDailySheet_() { return getSheet_(SHEET_DAILY, DAILY_HEADERS); }
        function getBodySheet_() { return getSheet_(SHEET_BODY, BODY_HEADERS); }
        function getWorkoutSheet_() { return getSheet_(SHEET_WORKOUT, WORKOUT_HEADERS); }
        function getMeasurementSheet_() { return getSheet_(SHEET_MEASUREMENT, MEASUREMENT_HEADERS); }
        function getAiSummarySheet_() { return getSheet_(SHEET_AI_SUMMARY, AI_SUMMARY_HEADERS); }
        
        function getQuotesSheet_() {
          const ss = getSpreadsheet_();
          let sheet = ss.getSheetByName(SHEET_QUOTES);
          if (!sheet) {
            sheet = ss.insertSheet(SHEET_QUOTES);
            sheet.appendRow(QUOTES_HEADERS);
            QUOTES_SEED.forEach(row => sheet.appendRow(row));
          }
          return sheet;
        }
        
        function getAllQuotes_() {
          const sheet = getQuotesSheet_();
          const values = sheet.getDataRange().getValues();
          if (values.length < 2) return [];
          return values.slice(1)
            .filter(r => r[0] !== '' && r[1] !== '')
            .map(r => ({ Category: r[0], Text: r[1] }));
        }
        
        const SHEET_CONFIG = 'App Config';
        const CONFIG_HEADERS = ['Key', 'Value'];
        
        function getConfigSheet_() { return getSheet_(SHEET_CONFIG, CONFIG_HEADERS); }
        
        function getAllConfig_() {
          const sheet = getConfigSheet_();
          const values = sheet.getDataRange().getValues();
          if (values.length < 2) return {};
          const obj = {};
          values.slice(1).forEach(r => { if (r[0] !== '') obj[r[0]] = formatCell_(r[1]); });
          return obj;
        }
        
        // Key-Value config (Reminder Targets & App Preferences) โ€” beda dari upsertRow_
        // yang di-key by Date, ini di-key by nama setting-nya sendiri (1 baris per key).
        function saveConfig_(data) {
          if (!data) throw new Error('Data wajib diisi');
          const sheet = getConfigSheet_();
          const last = sheet.getLastRow();
          const keyRows = {};
          if (last >= 2) {
            const values = sheet.getRange(2, 1, last - 1, 1).getValues();
            values.forEach((r, i) => { if (r[0] !== '') keyRows[r[0]] = i + 2; });
          }
          Object.keys(data).forEach(key => {
            if (keyRows[key]) {
              sheet.getRange(keyRows[key], 2).setValue(data[key]);
            } else {
              sheet.appendRow([key, data[key]]);
            }
          });
          return { ok: true };
        }
        
        function doGet(e) {
          const action = (e.parameter.action || 'ping');
          try {
            if (action === 'ping') return json_({ ok: true, message: 'Daylix backend aktif' });
            if (action === 'getInfo') return json_(getInfo_());
            if (action === 'getData') return json_({ ok: true, rows: getAllRows_(getDailySheet_(), DAILY_HEADERS) });
            if (action === 'getBodyData') return json_({ ok: true, rows: getAllRows_(getBodySheet_(), BODY_HEADERS) });
            if (action === 'getWorkoutData') return json_({ ok: true, rows: getAllRows_(getWorkoutSheet_(), WORKOUT_HEADERS) });
            if (action === 'getMeasurementData') return json_({ ok: true, rows: getAllRows_(getMeasurementSheet_(), MEASUREMENT_HEADERS) });
            if (action === 'getQuotesData') return json_({ ok: true, rows: getAllQuotes_() });
            if (action === 'getAiSummaryLog') return json_({ ok: true, rows: getAllRows_(getAiSummarySheet_(), AI_SUMMARY_HEADERS) });
            if (action === 'getConfigData') return json_({ ok: true, config: getAllConfig_() });
            return json_({ ok: false, error: 'Unknown action: ' + action });
          } catch (err) {
            return json_({ ok: false, error: String(err) });
          }
        }
        
        function doPost(e) {
          let body;
          try {
            body = JSON.parse(e.postData.contents);
          } catch (err) {
            return json_({ ok: false, error: 'Invalid JSON body' });
          }
          try {
            if (body.action === 'addRow')        return json_(upsertRow_(getDailySheet_(), DAILY_HEADERS, body.data));
            if (body.action === 'updateRow')     return json_(upsertRow_(getDailySheet_(), DAILY_HEADERS, body.data));
            if (body.action === 'deleteRow')     return json_(deleteRow_(getDailySheet_(), body.date));
            if (body.action === 'addBodyRow')    return json_(upsertRow_(getBodySheet_(), BODY_HEADERS, body.data));
            if (body.action === 'updateBodyRow') return json_(upsertRow_(getBodySheet_(), BODY_HEADERS, body.data));
            if (body.action === 'deleteBodyRow') return json_(deleteRow_(getBodySheet_(), body.date));
            if (body.action === 'addWorkoutRow')    return json_(insertWorkoutRow_(getWorkoutSheet_(), WORKOUT_HEADERS, body.data));
            if (body.action === 'updateWorkoutRow') return json_(updateWorkoutRowById_(getWorkoutSheet_(), WORKOUT_HEADERS, body.data));
            if (body.action === 'deleteWorkoutRow') return json_(deleteWorkoutRowById_(getWorkoutSheet_(), body.sessionId, body.date, body.type));
            if (body.action === 'addMeasurementRow') return json_(upsertRow_(getMeasurementSheet_(), MEASUREMENT_HEADERS, body.data));
            if (body.action === 'addAiSummaryLogRow') return json_(appendLogRow_(getAiSummarySheet_(), AI_SUMMARY_HEADERS, body.data));
            if (body.action === 'saveConfigData') return json_(saveConfig_(body.data));
            return json_({ ok: false, error: 'Unknown action: ' + body.action });
          } catch (err) {
            return json_({ ok: false, error: String(err) });
          }
        }
        
        function getInfo_() {
          const dailySheet = getDailySheet_();
          const bodySheet = getBodySheet_();
          return {
            ok: true,
            spreadsheetName: getSpreadsheet_().getName(),
            sheetName: dailySheet.getName(),
            rowCount: Math.max(0, dailySheet.getLastRow() - 1),
            bodySheetName: bodySheet.getName(),
            bodyRowCount: Math.max(0, bodySheet.getLastRow() - 1)
          };
        }
        
        function getAllRows_(sheet, headers) {
          const map = getHeaderIndexMap_(sheet);
          const dateIdx = map['Date'] !== undefined ? map['Date'] : 0;
          const values = sheet.getDataRange().getValues();
          if (values.length < 2) return [];
          return values.slice(1)
            .filter(r => r[dateIdx] !== '' && r[dateIdx] !== null)
            .map(r => {
              const obj = {};
              headers.forEach(h => { const idx = map[h]; obj[h] = idx !== undefined ? formatCell_(r[idx]) : ''; });
              return obj;
            })
            .sort((a, b) => a.Date < b.Date ? 1 : (a.Date > b.Date ? -1 : 0));
        }
        
        function formatCell_(value) {
          if (Object.prototype.toString.call(value) === '[object Date]') {
            // Google Sheets auto-convert teks kolon kayak "6:12" (Durasi Tidur, StartTime, dst)
            // jadi Time value โ€” internal-nya disimpan dengan tanggal dummy 30 Des 1899.
            // Kalau dibaca balik pakai format tanggal biasa, hasilnya "1899-12-30" (bug).
            // Deteksi lewat tahunnya, format sebagai jam:menit:detik alih-alih tanggal.
            if (value.getFullYear() < 1900) {
              return Utilities.formatDate(value, Session.getScriptTimeZone(), 'HH:mm:ss');
            }
            return Utilities.formatDate(value, Session.getScriptTimeZone(), 'yyyy-MM-dd');
          }
          return value;
        }
        
        function findRowByDate_(sheet, isoDate) {
          const last = sheet.getLastRow();
          if (last < 2) return -1;
          const values = sheet.getRange(2, 1, last - 1, 1).getValues();
          for (let i = 0; i < values.length; i++) {
            if (formatCell_(values[i][0]) === isoDate) return i + 2;
          }
          return -1;
        }
        
        function upsertRow_(sheet, headers, data) {
          if (!data || !data.Date) throw new Error('Field Date wajib diisi');
          const rowIdx = findRowByDate_(sheet, data.Date);
          const map = getHeaderIndexMap_(sheet);
          const lastCol = sheet.getLastColumn();
          // Merge dengan baris lama supaya field yang tidak dikirim (mis. dari modal
          // edit yang tidak punya semua field) tidak ikut tertimpa kosong.
          let merged = data;
          if (rowIdx > 0) {
            const existing = sheet.getRange(rowIdx, 1, 1, lastCol).getValues()[0];
            const existingObj = {};
            headers.forEach(h => { const idx = map[h]; existingObj[h] = idx !== undefined ? formatCell_(existing[idx]) : ''; });
            merged = Object.assign({}, existingObj, data);
          }
          const rowValues = new Array(lastCol).fill('');
          headers.forEach(h => { const idx = map[h]; if (idx !== undefined) rowValues[idx] = merged[h] !== undefined ? merged[h] : ''; });
          writeRowValues_(sheet, rowIdx > 0 ? rowIdx : null, rowValues);
          return { ok: true, date: data.Date };
        }
        
        // Beda dari upsertRow_ (merge by Date, 1 baris per tanggal) โ€” sheet ini butuh
        // APPEND murni karena bisa ada beberapa generate AI Summary di tanggal yang sama.
        // Field "Date" di sini diisi timestamp lengkap (bukan cuma tanggal) dari frontend,
        // jadi tetap bisa diurut kronologis biar pun beberapa entri di hari yang sama.
        function appendLogRow_(sheet, headers, data) {
          if (!data || !data.Date) throw new Error('Field Date wajib diisi');
          const map = getHeaderIndexMap_(sheet);
          const rowValues = new Array(sheet.getLastColumn()).fill('');
          headers.forEach(h => { const idx = map[h]; if (idx !== undefined) rowValues[idx] = data[h] !== undefined ? data[h] : ''; });
          writeRowValues_(sheet, null, rowValues);
          return { ok: true, date: data.Date };
        }
        
        function deleteRow_(sheet, isoDate) {
          if (!isoDate) throw new Error('Date wajib diisi');
          const rowIdx = findRowByDate_(sheet, isoDate);
          if (rowIdx > 0) sheet.deleteRow(rowIdx);
          return { ok: true, date: isoDate };
        }
        
        // โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
        // WORKOUT SESSIONS โ€” beda dari upsertRow_ (1 baris per Date) karena 1 hari bisa
        // punya BEBERAPA sesi (bahkan sama jenisnya). Di-key by SessionID (kolom terakhir,
        // di-generate frontend), BUKAN Date, supaya sesi ke-2/ke-3 di hari yang sama gak
        // saling menimpa. Baris lama (dibuat sebelum SessionID ada) di-fallback match by
        // Date+Type โ€” begitu diedit sekali, otomatis dapat SessionID permanen.
        // โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
        function findWorkoutRowById_(sheet, sessionId) {
          const last = sheet.getLastRow();
          if (last < 2 || !sessionId) return -1;
          const map = getHeaderIndexMap_(sheet);
          const idColIdx = map['SessionID'];
          if (idColIdx === undefined) return -1;
          const values = sheet.getRange(2, idColIdx + 1, last - 1, 1).getValues();
          for (let i = 0; i < values.length; i++) {
            if (values[i][0] !== '' && String(values[i][0]) === String(sessionId)) return i + 2;
          }
          return -1;
        }
        
        function findLegacyWorkoutRow_(sheet, isoDate, type) {
          const last = sheet.getLastRow();
          if (last < 2 || !isoDate || !type) return -1;
          const map = getHeaderIndexMap_(sheet);
          const dateColIdx = map['Date'];
          const typeColIdx = map['Type'];
          const idColIdx = map['SessionID'];
          if (dateColIdx === undefined || typeColIdx === undefined) return -1;
          const values = sheet.getRange(2, 1, last - 1, sheet.getLastColumn()).getValues();
          for (let i = 0; i < values.length; i++) {
            const rowDate = formatCell_(values[i][dateColIdx]);
            const rowType = values[i][typeColIdx];
            const rowId = idColIdx !== undefined ? values[i][idColIdx] : '';
            if (rowDate === isoDate && rowType === type && !rowId) return i + 2;
          }
          return -1;
        }
        
        function insertWorkoutRow_(sheet, headers, data) {
          if (!data || !data.Date || !data.Type) throw new Error('Field Date dan Type wajib diisi');
          if (!data.SessionID) data.SessionID = Utilities.getUuid();
          const map = getHeaderIndexMap_(sheet);
          const rowValues = new Array(sheet.getLastColumn()).fill('');
          headers.forEach(h => { const idx = map[h]; if (idx !== undefined) rowValues[idx] = data[h] !== undefined ? data[h] : ''; });
          writeRowValues_(sheet, null, rowValues);
          return { ok: true, date: data.Date, sessionId: data.SessionID };
        }
        
        function updateWorkoutRowById_(sheet, headers, data) {
          if (!data || !data.Date || !data.Type) throw new Error('Field Date dan Type wajib diisi');
          let rowIdx = data.SessionID ? findWorkoutRowById_(sheet, data.SessionID) : -1;
          if (rowIdx < 0) rowIdx = findLegacyWorkoutRow_(sheet, data.Date, data.Type);
          if (rowIdx < 0) return insertWorkoutRow_(sheet, headers, data);
        
          const map = getHeaderIndexMap_(sheet);
          const lastCol = sheet.getLastColumn();
          const existing = sheet.getRange(rowIdx, 1, 1, lastCol).getValues()[0];
          const existingObj = {};
          headers.forEach(h => { const idx = map[h]; existingObj[h] = idx !== undefined ? formatCell_(existing[idx]) : ''; });
          const merged = Object.assign({}, existingObj, data);
          if (!merged.SessionID) merged.SessionID = Utilities.getUuid();
          const rowValues = new Array(lastCol).fill('');
          headers.forEach(h => { const idx = map[h]; if (idx !== undefined) rowValues[idx] = merged[h] !== undefined ? merged[h] : ''; });
          writeRowValues_(sheet, rowIdx, rowValues);
          return { ok: true, date: data.Date, sessionId: merged.SessionID };
        }
        
        function deleteWorkoutRowById_(sheet, sessionId, isoDate, type) {
          let rowIdx = sessionId ? findWorkoutRowById_(sheet, sessionId) : -1;
          if (rowIdx < 0) rowIdx = findLegacyWorkoutRow_(sheet, isoDate, type);
          if (rowIdx > 0) sheet.deleteRow(rowIdx);
          return { ok: true };
        }
        
        function json_(obj) {
          return ContentService.createTextOutput(JSON.stringify(obj))
            .setMimeType(ContentService.MimeType.JSON);
        }
        ๐ŸŽฏ Reminder Targets Harian
        Target ini muncul di Daily Welcome Hub sebagai reminder harian โ€” sesuaikan dengan kebutuhan personal
        โ„น๏ธ Semua target di atas dipakai buat highlight warna angka (hijau/kuning/merah) di tabel Riwayat Harian โ€” RHR & HybridCharge/PAI di bawah otomatis diikutkan, HRV & Fatigue reuse ambang batas yang sama kayak badge Status.
        ๐ŸŽจ App Preferences