`; const dots = cell.querySelector('.dots'); if(crit.fisico){ const s=document.createElement('span'); s.className='dot'; s.style.background=COLORS.fisico; dots.appendChild(s); } if(crit.emocional){ const s=document.createElement('span'); s.className='dot'; s.style.background=COLORS.emocional; dots.appendChild(s); } if(crit.intelectual){ const s=document.createElement('span'); s.className='dot'; s.style.background=COLORS.intelectual; dots.appendChild(s); } cell.addEventListener('click', ()=>{ const info = $('bio-dayinfo'); info.innerHTML = `
${fmtDate(d)} – valores:
Físico: ${percent(v.fisico)}% (${statusFrom(v.fisico)})
Emocional: ${percent(v.emocional)}% (${statusFrom(v.emocional)})
Intelectual: ${percent(v.intelectual)}% (${statusFrom(v.intelectual)})
${isCrit?`
Atenção: dia crítico (${[crit.fisico?'Físico':'', crit.emocional?'Emocional':'', crit.intelectual?'Intelectual':''].filter(Boolean).join(' + ')}).
`:''}`; info.style.display='block'; info.scrollIntoView({behavior:'smooth', block:'nearest'}); }); cal.appendChild(cell); } } $('calPrev').onclick = ()=>{ calMonth.setMonth(calMonth.getMonth()-1); const birth = parseDate($('dob').value); if(birth){ renderCalendar(birth, calMonth); stripMonth = new Date(calMonth); drawStripMonthly(birth, stripMonth); } }; $('calNext').onclick = ()=>{ calMonth.setMonth(calMonth.getMonth()+1); const birth = parseDate($('dob').value); if(birth){ renderCalendar(birth, calMonth); stripMonth = new Date(calMonth); drawStripMonthly(birth, stripMonth); } }; /* ---------- CRITICAL DAYS LIST ---------- */ function listCriticos(birth, start, period){ const out=[]; for(let i=0;i0) || (vPrev.fisico>=0 && vNext.fisico<0), emocional: (vPrev.emocional<=0 && vNext.emocional>0) || (vPrev.emocional>=0 && vNext.emocional<0), intelectual: (vPrev.intelectual<=0 && vNext.intelectual>0) || (vPrev.intelectual>=0 && vNext.intelectual<0), }; if(flag.fisico || flag.emocional || flag.intelectual){ out.push({date:d, flag}); } } return out.slice(0, 18).map(x=>{ const which = []; if(x.flag.fisico) which.push('Físico'); if(x.flag.emocional) which.push('Emocional'); if(x.flag.intelectual) which.push('Intelectual'); const tag = which.length>=3?'TRIPLO crítico':which.length===2?'Duplo crítico': 'Crítico'; return {date:x.date, label: `${fmtDate(x.date)} — ${tag} (${which.join(' + ')})`}; }); } /* ---------- STRIP (ESTILO REVISTA) ---------- */ const stripCanvas = document.getElementById('bio-strip'); const stripTip = document.getElementById('strip-tip'); let stripCtx = stripCanvas.getContext('2d'); let stripMonth = null; function drawStripMonthly(birth, monthDate){ if(!stripCanvas || !stripCtx) return; const ratio = window.devicePixelRatio || 1; const wrapW = stripCanvas.parentElement.clientWidth; const H = 180; stripCanvas.style.width = wrapW + 'px'; stripCanvas.style.height = H + 'px'; stripCanvas.width = Math.floor(wrapW * ratio); stripCanvas.height = Math.floor(H * ratio); stripCtx.setTransform(ratio,0,0,ratio,0,0); const ctx = stripCtx; ctx.clearRect(0,0,wrapW,H); const pad = {l:30, r:10, t:16, b:26}; const w = wrapW - pad.l - pad.r; const h = H - pad.t - pad.b; const y0 = pad.t + h/2; // moldura ctx.strokeStyle = getCss('--border'); ctx.lineWidth=1; ctx.strokeRect(pad.l, pad.t, w, h); // zero ctx.strokeStyle = getCss('--zero'); ctx.setLineDash([6,6]); ctx.beginPath(); ctx.moveTo(pad.l, y0); ctx.lineTo(pad.l+w, y0); ctx.stroke(); ctx.setLineDash([]); // dias const daysInMonth = new Date(monthDate.getFullYear(), monthDate.getMonth()+1, 0).getDate(); const X = (d)=> pad.l + ((d-1)/(daysInMonth-1))*w; const Y = (val)=> y0 - val*(h/2); // marcadores de dia ctx.strokeStyle = getCss('--border'); ctx.lineWidth = 1; for(let d=1; d<=daysInMonth; d++){ const x = X(d); ctx.beginPath(); ctx.moveTo(x, pad.t + h); ctx.lineTo(x, pad.t + h + 4); ctx.stroke(); } // labels 1..n (de 5 em 5) ctx.fillStyle = getCss('--muted'); ctx.font='11px system-ui'; for(let d=1; d<=daysInMonth; d+=5){ ctx.fillText(String(d), X(d)-4, pad.t + h + 16); } // séries function drawSeries(color, accessor){ ctx.beginPath(); ctx.lineWidth=2; ctx.strokeStyle=color; for(let d=1; d<=daysInMonth; d++){ const cur = new Date(monthDate.getFullYear(), monthDate.getMonth(), d); const v = accessor(dayVector(birth, cur)); const x = X(d), y = Y(v); if(d===1) ctx.moveTo(x,y); else ctx.lineTo(x,y); } ctx.stroke(); } drawSeries(getCss('--fisico'), v=>v.fisico); drawSeries(getCss('--emocional'), v=>v.emocional); drawSeries(getCss('--intelectual'), v=>v.intelectual); // título document.getElementById('stripTitle').textContent = monthDate.toLocaleDateString('pt-BR',{month:'long',year:'numeric'}); // tooltip const rect = stripCanvas.getBoundingClientRect(); function showTip(clientX, clientY){ const x = clientX - rect.left; const rel = (x - pad.l) / w; const d = Math.min(daysInMonth, Math.max(1, Math.round(rel*(daysInMonth-1)+1))); const cur = new Date(monthDate.getFullYear(), monthDate.getMonth(), d); const v = dayVector(birth, cur); stripTip.innerHTML = `
${cur.toLocaleDateString('pt-BR')} Físico: ${percent(v.fisico)}%
Emocional: ${percent(v.emocional)}%
Intelectual: ${percent(v.intelectual)}%`; stripTip.style.left = x + 'px'; stripTip.style.top = (clientY - rect.top) + 'px'; stripTip.style.display = 'block'; } function hideTip(){ stripTip.style.display = 'none'; } stripCanvas.onmousemove = (e)=> showTip(e.clientX, e.clientY); stripCanvas.onmouseleave = hideTip; stripCanvas.ontouchstart = (e)=>{ if(e.touches[0]) showTip(e.touches[0].clientX, e.touches[0].clientY); }; stripCanvas.ontouchmove = (e)=>{ if(e.touches[0]) showTip(e.touches[0].clientX, e.touches[0].clientY); }; stripCanvas.ontouchend = hideTip; } /* ---------- RENDER MAIN ---------- */ function renderAll(){ const dob = parseDate($('dob').value); const start = parseDate($('start').value) || todayLocal(); const period = Number($('period').value)||60; if(!dob) { alert('Informe sua data de nascimento.'); return; } if(start < dob){ alert('A data inicial não pode ser antes do nascimento.'); return; } localStorage.setItem(DOB_KEY, $('dob').value); const ageDays = diffDays(dob, todayLocal()); const ageYears = (ageDays/365.25).toFixed(1); const end = addDays(start, period); $('bio-resumo').innerHTML = `
Nascimento: ${fmtDate(dob)} (${ageYears} anos)
Período: ${fmtDate(start)} → ${fmtDate(end)} (${period} dias)
Ciclos: Físico 23d • Emocional 28d • Intelectual 33d
`; chartState.points = buildSeries(dob, start, period); chartState.start = start; chartState.period = period; chartState.birth = dob; resizeCanvas(); // desenha gráfico principal const list = listCriticos(dob, start, period); $('bio-criticos').innerHTML = list.length ? list.map(it=>`
${it.label}`).join('') : '
Nenhum dia crítico no período selecionado.'; // calendário + faixa mensal sincronizados ao mês da data inicial calMonth = new Date(start.getFullYear(), start.getMonth(), 1); renderCalendar(dob, calMonth); stripMonth = new Date(calMonth); drawStripMonthly(dob, stripMonth); $('bio-out').style.display='block'; // share const firstCrit = list[0]?.label || 'Sem dias críticos próximos'; const share = `Meu Biorritmo: período ${fmtDate(start)} → ${fmtDate(end)}. ${firstCrit}. Veja físico, emocional e intelectual no site!`; $('btnWhats').href = "https://wa.me/?text=" + encodeURIComponent(share + ' ' + location.href); $('btnFace').href = "https://www.facebook.com/sharer/sharer.php?u=" + encodeURIComponent(location.href) + ""e=" + encodeURIComponent(share); $('btnShare').onclick = async ()=> { if(navigator.share){ try{ await navigator.share({title:"Biorritmo Online", text:share, url:location.href}); }catch(e){} } else { alert("Compartilhamento nativo indisponível. Use WhatsApp ou Facebook."); } }; $('btnPDF').onclick = ()=> window.print(); } $('btnCalc').addEventListener('click', renderAll); // navegação da faixa mensal (◀ ▶) document.getElementById('stripPrev').onclick = ()=>{ if(!stripMonth) return; stripMonth.setMonth(stripMonth.getMonth()-1); const birth = parseDate($('dob').value); if(birth) drawStripMonthly(birth, stripMonth); }; document.getElementById('stripNext').onclick = ()=>{ if(!stripMonth) return; stripMonth.setMonth(stripMonth.getMonth()+1); const birth = parseDate($('dob').value); if(birth) drawStripMonthly(birth, stripMonth); }; // auto-render se já tiver DOB salvo if(savedDob){ renderAll(); } })();