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 ══════════════════════════════════════════ */