SCULPTED ATHLETE
Welcome back
Sign in to your account
⏳
Awaiting Approval
Your account has been created and is waiting for approval. You'll be able to access the app once your coach or administrator approves your account.
Saving...
/* ══════════════════════════════════════════ CALENDAR ══════════════════════════════════════════ */ let calViewDate = new Date(); let calSelectedDate = null; // 'YYYY-MM-DD' const CARDIO_TYPES = [ {id:'running', icon:'🏃', name:'Running'}, {id:'cycling', icon:'🚴', name:'Cycling'}, {id:'rowing', icon:'🚣', name:'Rowing'}, {id:'elliptical',icon:'⭕', name:'Elliptical'}, {id:'stairclimb',icon:'🪜', name:'Stair Climber'}, {id:'hiit', icon:'⚡', name:'HIIT'}, {id:'swimming', icon:'🏊', name:'Swimming'}, {id:'jumpRope', icon:'🪢', name:'Jump Rope'}, {id:'walkmill', icon:'🚶', name:'Treadmill Walk'}, {id:'skierg', icon:'⛷️', name:'Ski Erg'}, ]; let selectedCardioType = 'running'; let selectedBookingType = 'in-person'; let selectedSlotTime = null; let aasTargetDate = null; /* ── Helper: get YYYY-MM-DD string ── */ function dateStr(year, month, day){ return `${year}-${String(month+1).padStart(2,'0')}-${String(day).padStart(2,'0')}`; } function todayStr(){ const t=new Date(); return dateStr(t.getFullYear(),t.getMonth(),t.getDate()); } /* ── Calendar events storage ── */ function getCalEvents(){ if(!S.calEvents) S.calEvents={}; return S.calEvents; } function getEventsForDate(ds){ return (getCalEvents()[ds]||[]); } function addCalEvent(ds, evt){ if(!S.calEvents) S.calEvents={}; if(!S.calEvents[ds]) S.calEvents[ds]=[]; S.calEvents[ds].push(evt); save(); } function removeCalEvent(ds, idx){ if(S.calEvents&&S.calEvents[ds]){ S.calEvents[ds].splice(idx,1); save(); } } /* ── Coach availability storage ── */ function getCoachAvail(coachId){ try{ return JSON.parse(localStorage.getItem('coachAvail_'+coachId)||'null') || {days:[1,2,3,4,5],start:'08:00',end:'18:00',sessionLen:60,daysOff:[]}; } catch(e){ return {days:[1,2,3,4,5],start:'08:00',end:'18:00',sessionLen:60,daysOff:[]}; } } function saveCoachAvail(coachId, avail){ localStorage.setItem('coachAvail_'+coachId, JSON.stringify(avail)); } /* ── Bookings storage ── */ function getBookings(){ try{ return JSON.parse(localStorage.getItem('bookings')||'[]'); }catch(e){return [];} } function saveBookings(b){ localStorage.setItem('bookings', JSON.stringify(b)); } function addBooking(booking){ const b=getBookings(); b.push(booking); saveBookings(b); } /* ── Build calendar grid ── */ function buildCalendar(){ const months=['JANUARY','FEBRUARY','MARCH','APRIL','MAY','JUNE','JULY','AUGUST','SEPTEMBER','OCTOBER','NOVEMBER','DECEMBER']; const label=document.getElementById('calMonthLabel'); if(label) label.textContent=`${months[calViewDate.getMonth()]} ${calViewDate.getFullYear()}`; const grid=document.getElementById('calGrid'); if(!grid) return; const year=calViewDate.getFullYear(), month=calViewDate.getMonth(); const firstDay=new Date(year,month,1).getDay(); const daysInMonth=new Date(year,month+1,0).getDate(); const today=new Date(); const events=getCalEvents(); const bookings=getBookings(); let html=''; for(let i=0;ie.type==='strength')||isDayAssignedWorkout(ds); const hasCardio=dayEvents.some(e=>e.type==='cardio'); const hasBooking=bookings.some(b=>b.date===ds); // Check coach availability for days off let coachId=null; if(allLinks&¤tUser){ const lk=allLinks.find(l=>l.athlete_id===currentUser.id); if(lk) coachId=lk.coach_id; } const avail=coachId?getCoachAvail(coachId):{days:[1,2,3,4,5],daysOff:[]}; const dayOfWeek=new Date(year,month,d).getDay(); const isDayOff=(avail.daysOff||[]).includes(ds); let cls='cal-cell'; if(isToday) cls+=' today'; if(isSelected) cls+=' selected-day'; if(hasWorkout||hasCardio) cls+=' has-event'; if(hasBooking) cls+=' has-booking'; if(isDayOff) cls+=' day-off'; let dots=''; if(hasWorkout) dots+='
'; if(hasCardio) dots+='
'; if(hasBooking) dots+='
'; html+=`
${d}${dots?`
${dots}
`:''}
`; } grid.innerHTML=html; // Also add selected day indicator CSS inline const style=document.getElementById('calSelectedStyle')||Object.assign(document.createElement('style'),{id:'calSelectedStyle'}); style.textContent='.selected-day{outline:2px solid var(--accent);outline-offset:2px;}'; if(!document.getElementById('calSelectedStyle')) document.head.appendChild(style); if(calSelectedDate) renderDayPanel(calSelectedDate); } function isDayAssignedWorkout(ds){ // For now, if there's logged exercise data for today const today=todayStr(); if(ds===today) return Object.keys(S.done||{}).some(k=>(S.done||{})[k]); return false; } function calDayClick(ds){ calSelectedDate=ds; buildCalendar(); renderDayPanel(ds); document.getElementById('calDayPanel').style.display='block'; setTimeout(()=>document.getElementById('calDayPanel').scrollIntoView({behavior:'smooth',block:'start'}),100); } function renderDayPanel(ds){ const panel=document.getElementById('calDayPanel'); const title=document.getElementById('calDayPanelTitle'); const eventsEl=document.getElementById('calDayEvents'); if(!panel||!title||!eventsEl) return; const d=new Date(ds+'T12:00:00'); const months=['Jan','Feb','Mar','Apr','May','Jun','Jul','Aug','Sep','Oct','Nov','Dec']; const wdays=['Sunday','Monday','Tuesday','Wednesday','Thursday','Friday','Saturday']; title.textContent=`${wdays[d.getDay()].toUpperCase()}, ${months[d.getMonth()].toUpperCase()} ${d.getDate()}`; const dayEvents=getEventsForDate(ds); const bookings=getBookings().filter(b=>b.date===ds); // Check for assigned program workout const assignedWorkoutHtml=isDayAssignedWorkout(ds)? `
🏋️
Training — ${DAYS[S.day||0]?.title||'Workout'}
Program Day ${(S.day||0)+1} · Week ${S.week||1}
START →
`: (ds===todayStr()?`
🏋️
Today's Workout
${DAYS[S.day||0]?.title||''} · Week ${S.week||1}
GO →
`:''); const eventsHtml=dayEvents.map((e,i)=>`
${e.icon||'📌'}
${e.name}
${e.duration||''} ${e.notes?'· '+e.notes:''}
${e.time||''}
`).join(''); const bookingsHtml=bookings.map((b,i)=>`
${b.mode==='online'?'💻':'🏋️'}
Session — ${b.mode==='online'?'Online/Live':'In-Person'}
${b.time} · ${b.notes||''}
${(b.status||'pending').toUpperCase()}
`).join(''); eventsEl.innerHTML=(assignedWorkoutHtml+eventsHtml+bookingsHtml)||`
No activities scheduled. Tap + Add to log something.
`; } function calStep(dir){ calViewDate.setMonth(calViewDate.getMonth()+dir); buildCalendar(); } function calGoToday(){ calViewDate=new Date(); calSelectedDate=todayStr(); buildCalendar(); renderDayPanel(calSelectedDate); document.getElementById('calDayPanel').style.display='block'; } /* ── Add Activity Sheet ── */ function openAddActivity(ds){ aasTargetDate=ds||calSelectedDate||todayStr(); document.getElementById('addActivityOverlay').classList.add('open'); document.body.style.overflow='hidden'; document.getElementById('aasTypeGrid').style.display='grid'; ['Cardio','Strength','Booking','Note'].forEach(t=>{ const el=document.getElementById('aasForm'+t); if(el) el.classList.remove('visible'); }); // Set booking date const bd=document.getElementById('bookingDate'); if(bd) bd.value=aasTargetDate; buildCardioGrid(); } function closeAddActivity(){ document.getElementById('addActivityOverlay').classList.remove('open'); document.body.style.overflow=''; } document.addEventListener('DOMContentLoaded',()=>{ const ov=document.getElementById('addActivityOverlay'); if(ov) ov.addEventListener('click',e=>{ if(e.target===ov) closeAddActivity(); }); }); function openAasForm(type){ document.getElementById('aasTypeGrid').style.display='none'; const map={cardio:'Cardio',strength:'Strength',booking:'Booking',note:'Note'}; const el=document.getElementById('aasForm'+map[type]); if(el) el.classList.add('visible'); if(type==='booking') loadAvailableSlots(); } function buildCardioGrid(){ const grid=document.getElementById('cardioGrid'); if(!grid) return; grid.innerHTML=CARDIO_TYPES.map(c=>`
${c.icon}
${c.name}
`).join(''); } function selectCardioType(id, el){ selectedCardioType=id; document.querySelectorAll('.cardio-opt').forEach(o=>o.classList.remove('selected')); el.classList.add('selected'); } function selectDur(el, val){ el.closest('.duration-chips')?.querySelectorAll('.dur-chip').forEach(c=>c.classList.remove('selected')); el.classList.add('selected'); } function selectBookingType(el, type){ selectedBookingType=type; document.querySelectorAll('.booking-type-opt').forEach(o=>o.classList.remove('selected')); el.classList.add('selected'); loadAvailableSlots(); } function saveCardioActivity(){ const ct=CARDIO_TYPES.find(c=>c.id===selectedCardioType)||CARDIO_TYPES[0]; const time=document.getElementById('cardioTime')?.value||''; const dur=document.querySelector('#cardioDurChips .dur-chip.selected')?.textContent||'30m'; const notes=document.getElementById('cardioNote')?.value||''; addCalEvent(aasTargetDate,{type:'cardio',name:ct.name,icon:ct.icon,time,duration:dur,notes}); closeAddActivity(); buildCalendar(); renderDayPanel(aasTargetDate); } function saveStrengthActivity(){ const time=document.getElementById('strengthTime')?.value||''; const notes=document.getElementById('strengthNote')?.value||'Strength Session'; const dur=document.querySelector('#aasFormStrength .dur-chip.selected')?.textContent||'1hr'; addCalEvent(aasTargetDate,{type:'strength',name:notes||'Strength Session',icon:'🏋️',time,duration:dur,notes:''}); closeAddActivity(); buildCalendar(); renderDayPanel(aasTargetDate); } function saveNoteActivity(){ const text=document.getElementById('noteText')?.value.trim(); if(!text){alert('Please enter a note.');return;} const time=document.getElementById('noteTime')?.value||''; addCalEvent(aasTargetDate,{type:'note',name:text,icon:'📝',time,duration:'',notes:''}); closeAddActivity(); buildCalendar(); renderDayPanel(aasTargetDate); } /* ── Booking: load available slots ── */ function loadAvailableSlots(){ const dateInput=document.getElementById('bookingDate'); const ds=dateInput?.value||aasTargetDate||todayStr(); const list=document.getElementById('availableSlotsList'); if(!list) return; // Find coach let coachId=null; if(allLinks&¤tUser){ const lk=allLinks.find(l=>l.athlete_id===currentUser.id); if(lk) coachId=lk.coach_id; } if(!coachId){ list.innerHTML='
No coach assigned yet.
'; return; } const avail=getCoachAvail(coachId); const d=new Date(ds+'T12:00:00'); const dow=d.getDay(); if(!(avail.days||[]).includes(dow)||(avail.daysOff||[]).includes(ds)){ list.innerHTML='
Coach is not available on this day.
'; return; } // Generate slots const [sh,sm]=avail.start.split(':').map(Number); const [eh,em]=avail.end.split(':').map(Number); const len=avail.sessionLen||60; const bookings=getBookings(); const slots=[]; let cur=sh*60+(sm||0); const end=eh*60+(em||0); while(cur+len<=end){ const hh=String(Math.floor(cur/60)).padStart(2,'0'); const mm=String(cur%60).padStart(2,'0'); const timeStr=`${hh}:${mm}`; const isBooked=bookings.some(b=>b.date===ds&&b.time===timeStr&&b.status!=='declined'); slots.push({time:timeStr,booked:isBooked}); cur+=len; } selectedSlotTime=null; list.innerHTML=slots.length?slots.map(s=>`
${formatTime12(s.time)}
${s.booked?'Booked':'Available'}
`).join(''):'
No slots available on this day.
'; } function formatTime12(t){ const [h,m]=t.split(':').map(Number); const ampm=h>=12?'PM':'AM'; const h12=h%12||12; return `${h12}:${String(m).padStart(2,'0')} ${ampm}`; } function selectSlot(el,time){ selectedSlotTime=time; document.querySelectorAll('.slot-item').forEach(s=>s.classList.remove('selected')); el.classList.add('selected'); } // Reload slots when date changes document.addEventListener('DOMContentLoaded',()=>{ const bd=document.getElementById('bookingDate'); if(bd) bd.addEventListener('change',()=>loadAvailableSlots()); }); function sendBookingRequest(){ if(!selectedSlotTime){alert('Please select a time slot.');return;} const ds=document.getElementById('bookingDate')?.value||aasTargetDate; const notes=document.getElementById('bookingNote')?.value||''; let coachId=null; if(allLinks&¤tUser){ const lk=allLinks.find(l=>l.athlete_id===currentUser.id); if(lk) coachId=lk.coach_id; } const booking={ id:'bk_'+Date.now(), athleteId:currentUser?.id, athleteName:currentProfile?.name||'Athlete', coachId, date:ds, time:selectedSlotTime, mode:selectedBookingType, notes, status:'pending', created:new Date().toISOString() }; addBooking(booking); closeAddActivity(); buildCalendar(); renderDayPanel(ds); showSaveIndicator('saved'); alert(`Session request sent for ${formatTime12(selectedSlotTime)} on ${ds}. Awaiting coach confirmation.`); } /* ── Coach: accept/decline booking ── */ function respondToBooking(bookingId, response){ const bookings=getBookings(); const b=bookings.find(b=>b.id===bookingId); if(b){ b.status=response; saveBookings(bookings); } buildCoachDashboard(); } /* ══════════════════════════════════════════ GOALS ══════════════════════════════════════════ */