cycletracker / index.html
Axelle124125's picture
Add 3 files
9fc5c02 verified
Raw
History Blame Contribute Delete
41.9 kB
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>CycleSync | Period Tracker & Fertility Calendar</title>
<script src="https://cdn.tailwindcss.com"></script>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
<style>
.gradient-bg {
background: linear-gradient(135deg, #f5f7fa 0%, #c3cfe2 100%);
}
.fertile-window {
background-color: rgba(255, 105, 180, 0.2);
}
.period-day {
background-color: rgba(255, 0, 0, 0.2);
}
.ovulation-day {
background-color: rgba(255, 215, 0, 0.3);
}
.custom-scrollbar::-webkit-scrollbar {
width: 6px;
height: 6px;
}
.custom-scrollbar::-webkit-scrollbar-track {
background: #f1f1f1;
}
.custom-scrollbar::-webkit-scrollbar-thumb {
background: #c1c1c1;
border-radius: 3px;
}
.custom-scrollbar::-webkit-scrollbar-thumb:hover {
background: #a8a8a8;
}
.symptom-icon {
transition: all 0.3s ease;
}
.symptom-icon:hover {
transform: scale(1.1);
}
</style>
</head>
<body class="gradient-bg min-h-screen font-sans">
<div class="container mx-auto px-4 py-8 max-w-6xl">
<!-- Header -->
<header class="flex flex-col md:flex-row justify-between items-center mb-8">
<div class="flex items-center mb-4 md:mb-0">
<i class="fas fa-venus text-pink-500 text-4xl mr-3"></i>
<h1 class="text-3xl font-bold text-gray-800">Cycle<span class="text-pink-500">Sync</span></h1>
</div>
<div class="flex items-center space-x-4">
<button id="newCycleBtn" class="bg-pink-500 hover:bg-pink-600 text-white px-4 py-2 rounded-lg shadow-md transition">
<i class="fas fa-plus mr-2"></i>New Cycle
</button>
<button id="settingsBtn" class="bg-gray-200 hover:bg-gray-300 text-gray-700 p-2 rounded-full shadow-md transition">
<i class="fas fa-cog"></i>
</button>
</div>
</header>
<!-- Main Dashboard -->
<div class="grid grid-cols-1 lg:grid-cols-3 gap-6">
<!-- User Info Panel -->
<div class="bg-white rounded-xl shadow-md p-6 lg:col-span-1">
<div class="flex items-center mb-6">
<div class="w-16 h-16 rounded-full bg-pink-100 flex items-center justify-center mr-4">
<i class="fas fa-user text-pink-500 text-2xl"></i>
</div>
<div>
<h2 class="text-xl font-semibold text-gray-800" id="userName">My Profile</h2>
<p class="text-gray-500">Track your cycle with confidence</p>
</div>
</div>
<div class="space-y-4">
<div>
<label class="block text-sm font-medium text-gray-700 mb-1">Cycle Length</label>
<div class="flex items-center">
<input type="number" id="cycleLength" min="21" max="45" value="28"
class="w-20 px-3 py-2 border border-gray-300 rounded-lg mr-2">
<span class="text-gray-600">days</span>
</div>
</div>
<div>
<label class="block text-sm font-medium text-gray-700 mb-1">Period Length</label>
<div class="flex items-center">
<input type="number" id="periodLength" min="2" max="10" value="5"
class="w-20 px-3 py-2 border border-gray-300 rounded-lg mr-2">
<span class="text-gray-600">days</span>
</div>
</div>
<div>
<label class="block text-sm font-medium text-gray-700 mb-1">Last Period Start</label>
<input type="date" id="lastPeriodStart" class="w-full px-3 py-2 border border-gray-300 rounded-lg">
</div>
<button id="saveSettingsBtn" class="w-full bg-pink-500 hover:bg-pink-600 text-white py-2 rounded-lg shadow-md transition mt-4">
Save Settings
</button>
</div>
<div class="mt-8 pt-6 border-t border-gray-200">
<h3 class="text-lg font-medium text-gray-800 mb-3">Today's Status</h3>
<div id="todayStatus" class="bg-blue-50 text-blue-800 p-4 rounded-lg">
<div class="flex items-center">
<i class="fas fa-info-circle text-blue-500 mr-2"></i>
<span>Update your settings to see your cycle status</span>
</div>
</div>
</div>
</div>
<!-- Calendar and Tracking -->
<div class="bg-white rounded-xl shadow-md p-6 lg:col-span-2">
<div class="flex justify-between items-center mb-6">
<h2 class="text-xl font-semibold text-gray-800">Cycle Calendar</h2>
<div class="flex items-center space-x-2">
<button id="prevMonthBtn" class="p-2 rounded-full hover:bg-gray-100">
<i class="fas fa-chevron-left text-gray-600"></i>
</button>
<span id="currentMonthYear" class="font-medium text-gray-700">June 2023</span>
<button id="nextMonthBtn" class="p-2 rounded-full hover:bg-gray-100">
<i class="fas fa-chevron-right text-gray-600"></i>
</button>
</div>
</div>
<!-- Calendar Grid -->
<div class="mb-6">
<div class="grid grid-cols-7 gap-1 mb-2">
<div class="text-center text-sm font-medium text-gray-500 py-1">Sun</div>
<div class="text-center text-sm font-medium text-gray-500 py-1">Mon</div>
<div class="text-center text-sm font-medium text-gray-500 py-1">Tue</div>
<div class="text-center text-sm font-medium text-gray-500 py-1">Wed</div>
<div class="text-center text-sm font-medium text-gray-500 py-1">Thu</div>
<div class="text-center text-sm font-medium text-gray-500 py-1">Fri</div>
<div class="text-center text-sm font-medium text-gray-500 py-1">Sat</div>
</div>
<div id="calendarGrid" class="grid grid-cols-7 gap-1">
<!-- Calendar days will be inserted here by JavaScript -->
</div>
</div>
<!-- Cycle Phases -->
<div class="mb-6">
<h3 class="text-lg font-medium text-gray-800 mb-3">Current Cycle Phases</h3>
<div id="cyclePhases" class="flex overflow-x-auto custom-scrollbar pb-2">
<!-- Cycle phases will be inserted here by JavaScript -->
</div>
</div>
<!-- Symptoms Tracker -->
<div>
<div class="flex justify-between items-center mb-3">
<h3 class="text-lg font-medium text-gray-800">Today's Symptoms</h3>
<button id="addSymptomBtn" class="text-pink-500 hover:text-pink-600 text-sm">
<i class="fas fa-plus mr-1"></i>Add
</button>
</div>
<div id="symptomsList" class="flex flex-wrap gap-2">
<!-- Symptoms will be inserted here by JavaScript -->
</div>
</div>
</div>
</div>
<!-- Statistics Section -->
<div class="bg-white rounded-xl shadow-md p-6 mt-6">
<h2 class="text-xl font-semibold text-gray-800 mb-4">Cycle Statistics</h2>
<div class="grid grid-cols-1 md:grid-cols-3 gap-4">
<div class="bg-blue-50 p-4 rounded-lg">
<div class="flex items-center justify-between">
<div>
<p class="text-sm text-blue-600">Average Cycle</p>
<p class="text-2xl font-bold text-blue-800" id="avgCycleLength">28 days</p>
</div>
<i class="fas fa-calendar-alt text-blue-400 text-2xl"></i>
</div>
</div>
<div class="bg-purple-50 p-4 rounded-lg">
<div class="flex items-center justify-between">
<div>
<p class="text-sm text-purple-600">Average Period</p>
<p class="text-2xl font-bold text-purple-800" id="avgPeriodLength">5 days</p>
</div>
<i class="fas fa-tint text-purple-400 text-2xl"></i>
</div>
</div>
<div class="bg-green-50 p-4 rounded-lg">
<div class="flex items-center justify-between">
<div>
<p class="text-sm text-green-600">Next Period</p>
<p class="text-2xl font-bold text-green-800" id="nextPeriodDate">--</p>
</div>
<i class="fas fa-clock text-green-400 text-2xl"></i>
</div>
</div>
</div>
</div>
<!-- Notes Section -->
<div class="bg-white rounded-xl shadow-md p-6 mt-6">
<h2 class="text-xl font-semibold text-gray-800 mb-3">Cycle Notes</h2>
<textarea id="cycleNotes" class="w-full px-4 py-3 border border-gray-300 rounded-lg focus:ring-2 focus:ring-pink-200 focus:border-pink-300" rows="4" placeholder="Record any notes about your cycle, symptoms, or moods..."></textarea>
<button id="saveNotesBtn" class="mt-3 bg-pink-500 hover:bg-pink-600 text-white px-4 py-2 rounded-lg shadow-md transition">
Save Notes
</button>
</div>
</div>
<!-- Symptom Modal -->
<div id="symptomModal" class="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50 hidden">
<div class="bg-white rounded-xl p-6 w-full max-w-md">
<div class="flex justify-between items-center mb-4">
<h3 class="text-lg font-semibold text-gray-800">Add Symptom</h3>
<button id="closeSymptomModal" class="text-gray-500 hover:text-gray-700">
<i class="fas fa-times"></i>
</button>
</div>
<div class="space-y-4">
<div>
<label class="block text-sm font-medium text-gray-700 mb-1">Symptom</label>
<select id="symptomType" class="w-full px-3 py-2 border border-gray-300 rounded-lg">
<option value="cramps">Cramps</option>
<option value="headache">Headache</option>
<option value="bloating">Bloating</option>
<option value="tender_breasts">Tender Breasts</option>
<option value="acne">Acne</option>
<option value="fatigue">Fatigue</option>
<option value="mood_swings">Mood Swings</option>
<option value="nausea">Nausea</option>
<option value="backache">Backache</option>
</select>
</div>
<div>
<label class="block text-sm font-medium text-gray-700 mb-1">Intensity</label>
<div class="flex items-center space-x-2">
<span class="text-gray-500 text-sm">Low</span>
<input type="range" id="symptomIntensity" min="1" max="5" value="3" class="w-full">
<span class="text-gray-500 text-sm">High</span>
</div>
</div>
<div>
<label class="block text-sm font-medium text-gray-700 mb-1">Notes</label>
<textarea id="symptomNotes" class="w-full px-3 py-2 border border-gray-300 rounded-lg" rows="2" placeholder="Any additional details..."></textarea>
</div>
<button id="saveSymptomBtn" class="w-full bg-pink-500 hover:bg-pink-600 text-white py-2 rounded-lg shadow-md transition">
Add Symptom
</button>
</div>
</div>
</div>
<script>
// DOM Elements
const elements = {
calendarGrid: document.getElementById('calendarGrid'),
currentMonthYear: document.getElementById('currentMonthYear'),
prevMonthBtn: document.getElementById('prevMonthBtn'),
nextMonthBtn: document.getElementById('nextMonthBtn'),
cycleLength: document.getElementById('cycleLength'),
periodLength: document.getElementById('periodLength'),
lastPeriodStart: document.getElementById('lastPeriodStart'),
saveSettingsBtn: document.getElementById('saveSettingsBtn'),
todayStatus: document.getElementById('todayStatus'),
cyclePhases: document.getElementById('cyclePhases'),
symptomsList: document.getElementById('symptomsList'),
addSymptomBtn: document.getElementById('addSymptomBtn'),
symptomModal: document.getElementById('symptomModal'),
closeSymptomModal: document.getElementById('closeSymptomModal'),
saveSymptomBtn: document.getElementById('saveSymptomBtn'),
symptomType: document.getElementById('symptomType'),
symptomIntensity: document.getElementById('symptomIntensity'),
symptomNotes: document.getElementById('symptomNotes'),
avgCycleLength: document.getElementById('avgCycleLength'),
avgPeriodLength: document.getElementById('avgPeriodLength'),
nextPeriodDate: document.getElementById('nextPeriodDate'),
cycleNotes: document.getElementById('cycleNotes'),
saveNotesBtn: document.getElementById('saveNotesBtn'),
newCycleBtn: document.getElementById('newCycleBtn')
};
// State
let state = {
currentDate: new Date(),
userSettings: {
cycleLength: 28,
periodLength: 5,
lastPeriodStart: null,
userName: 'My Profile'
},
cycleData: [],
symptoms: {},
notes: ''
};
// Initialize
function init() {
loadData();
renderCalendar();
updateCyclePhases();
updateTodayStatus();
updateStatistics();
setupEventListeners();
}
// Load data from localStorage
function loadData() {
const savedSettings = localStorage.getItem('cycleTrackerSettings');
const savedCycleData = localStorage.getItem('cycleTrackerData');
const savedSymptoms = localStorage.getItem('cycleTrackerSymptoms');
const savedNotes = localStorage.getItem('cycleTrackerNotes');
if (savedSettings) {
state.userSettings = JSON.parse(savedSettings);
elements.cycleLength.value = state.userSettings.cycleLength;
elements.periodLength.value = state.userSettings.periodLength;
if (state.userSettings.lastPeriodStart) {
elements.lastPeriodStart.value = state.userSettings.lastPeriodStart;
}
}
if (savedCycleData) {
state.cycleData = JSON.parse(savedCycleData);
}
if (savedSymptoms) {
state.symptoms = JSON.parse(savedSymptoms);
renderSymptoms();
}
if (savedNotes) {
state.notes = savedNotes;
elements.cycleNotes.value = state.notes;
}
}
// Save data to localStorage
function saveData() {
localStorage.setItem('cycleTrackerSettings', JSON.stringify(state.userSettings));
localStorage.setItem('cycleTrackerData', JSON.stringify(state.cycleData));
localStorage.setItem('cycleTrackerSymptoms', JSON.stringify(state.symptoms));
localStorage.setItem('cycleTrackerNotes', state.notes);
}
// Render calendar
function renderCalendar() {
elements.calendarGrid.innerHTML = '';
const year = state.currentDate.getFullYear();
const month = state.currentDate.getMonth();
// Set month and year in header
elements.currentMonthYear.textContent = new Intl.DateTimeFormat('en-US', {
month: 'long',
year: 'numeric'
}).format(state.currentDate);
// Get first day of month and total days in month
const firstDay = new Date(year, month, 1).getDay();
const daysInMonth = new Date(year, month + 1, 0).getDate();
// Get days from previous month to display
const prevMonthDays = new Date(year, month, 0).getDate();
// Add days from previous month
for (let i = 0; i < firstDay; i++) {
const day = prevMonthDays - firstDay + i + 1;
const date = new Date(year, month - 1, day);
const dayElement = createDayElement(date, true);
elements.calendarGrid.appendChild(dayElement);
}
// Add days from current month
for (let i = 1; i <= daysInMonth; i++) {
const date = new Date(year, month, i);
const dayElement = createDayElement(date, false);
elements.calendarGrid.appendChild(dayElement);
}
// Calculate how many days from next month to display (to fill the grid)
const totalDaysDisplayed = firstDay + daysInMonth;
const nextMonthDays = 7 - (totalDaysDisplayed % 7);
if (nextMonthDays < 7) {
for (let i = 1; i <= nextMonthDays; i++) {
const date = new Date(year, month + 1, i);
const dayElement = createDayElement(date, true);
elements.calendarGrid.appendChild(dayElement);
}
}
}
// Create day element for calendar
function createDayElement(date, isOtherMonth) {
const dayElement = document.createElement('div');
dayElement.className = 'h-24 p-1 border border-gray-100 rounded-lg overflow-hidden';
const dayNumber = document.createElement('div');
dayNumber.className = 'text-right text-sm font-medium mb-1';
dayNumber.textContent = date.getDate();
const dayContent = document.createElement('div');
dayContent.className = 'text-xs h-16 overflow-y-auto custom-scrollbar';
dayElement.appendChild(dayNumber);
dayElement.appendChild(dayContent);
if (isOtherMonth) {
dayElement.classList.add('bg-gray-50', 'text-gray-400');
} else {
// Check if this day is in the current cycle
if (state.userSettings.lastPeriodStart) {
const lastPeriodStart = new Date(state.userSettings.lastPeriodStart);
const cycleEnd = new Date(lastPeriodStart);
cycleEnd.setDate(cycleEnd.getDate() + state.userSettings.cycleLength);
if (date >= lastPeriodStart && date < cycleEnd) {
const dayInCycle = Math.floor((date - lastPeriodStart) / (1000 * 60 * 60 * 24)) + 1;
// Mark period days
if (dayInCycle <= state.userSettings.periodLength) {
dayElement.classList.add('period-day');
const periodBadge = document.createElement('div');
periodBadge.className = 'bg-pink-500 text-white text-xs px-1 rounded inline-block mb-1';
periodBadge.textContent = 'Period';
dayContent.appendChild(periodBadge);
}
// Mark fertile window (5 days before ovulation and ovulation day)
const ovulationDay = state.userSettings.cycleLength - 14;
const fertileStart = ovulationDay - 5;
const fertileEnd = ovulationDay;
if (dayInCycle >= fertileStart && dayInCycle <= fertileEnd) {
dayElement.classList.add('fertile-window');
const fertileBadge = document.createElement('div');
fertileBadge.className = 'bg-green-500 text-white text-xs px-1 rounded inline-block mb-1';
fertileBadge.textContent = dayInCycle === ovulationDay ? 'Ovulation' : 'Fertile';
dayContent.appendChild(fertileBadge);
}
}
}
// Mark today
const today = new Date();
if (date.getDate() === today.getDate() &&
date.getMonth() === today.getMonth() &&
date.getFullYear() === today.getFullYear()) {
dayElement.classList.add('ring-2', 'ring-pink-400');
}
// Add symptoms for this day
const dateString = formatDate(date);
if (state.symptoms[dateString]) {
state.symptoms[dateString].forEach(symptom => {
const symptomElement = document.createElement('div');
symptomElement.className = 'flex items-center mb-1';
const icon = document.createElement('i');
icon.className = getSymptomIcon(symptom.type) + ' text-xs mr-1';
const intensity = document.createElement('div');
intensity.className = 'w-2 h-2 rounded-full mr-1';
intensity.style.backgroundColor = getIntensityColor(symptom.intensity);
symptomElement.appendChild(icon);
symptomElement.appendChild(intensity);
symptomElement.appendChild(document.createTextNode(symptom.type));
dayContent.appendChild(symptomElement);
});
}
}
return dayElement;
}
// Get icon for symptom type
function getSymptomIcon(type) {
const icons = {
cramps: 'fas fa-bolt',
headache: 'fas fa-head-side-virus',
bloating: 'fas fa-weight',
tender_breasts: 'fas fa-female',
acne: 'fas fa-allergies',
fatigue: 'fas fa-bed',
mood_swings: 'fas fa-smile',
nausea: 'fas fa-stomach',
backache: 'fas fa-bone'
};
return icons[type] || 'fas fa-dot-circle';
}
// Get color for symptom intensity
function getIntensityColor(intensity) {
const colors = ['#93c5fd', '#60a5fa', '#3b82f6', '#2563eb', '#1d4ed8'];
return colors[intensity - 1] || '#93c5fd';
}
// Update cycle phases visualization
function updateCyclePhases() {
elements.cyclePhases.innerHTML = '';
if (!state.userSettings.lastPeriodStart) return;
const cycleDays = state.userSettings.cycleLength;
const periodDays = state.userSettings.periodLength;
const ovulationDay = cycleDays - 14;
const fertileStart = ovulationDay - 5;
const fertileEnd = ovulationDay;
const phases = [
{ name: 'Period', start: 1, end: periodDays, color: 'bg-pink-500' },
{ name: 'Follicular', start: periodDays + 1, end: fertileStart - 1, color: 'bg-blue-400' },
{ name: 'Fertile', start: fertileStart, end: fertileEnd, color: 'bg-green-500' },
{ name: 'Luteal', start: ovulationDay + 1, end: cycleDays, color: 'bg-purple-500' }
];
phases.forEach(phase => {
const phaseElement = document.createElement('div');
phaseElement.className = 'flex-shrink-0 mr-4';
const phaseTitle = document.createElement('h4');
phaseTitle.className = 'text-sm font-medium text-gray-700 mb-1';
phaseTitle.textContent = phase.name;
const phaseBar = document.createElement('div');
phaseBar.className = 'h-4 rounded-full overflow-hidden flex';
const daysInPhase = phase.end - phase.start + 1;
const phaseWidth = (daysInPhase / cycleDays) * 100;
const phaseColor = document.createElement('div');
phaseColor.className = phase.color;
phaseColor.style.width = `${phaseWidth}%`;
const emptySpace = document.createElement('div');
emptySpace.className = 'bg-gray-200';
emptySpace.style.width = `${100 - phaseWidth}%`;
phaseBar.appendChild(phaseColor);
phaseBar.appendChild(emptySpace);
phaseElement.appendChild(phaseTitle);
phaseElement.appendChild(phaseBar);
elements.cyclePhases.appendChild(phaseElement);
});
}
// Update today's status
function updateTodayStatus() {
if (!state.userSettings.lastPeriodStart) return;
const today = new Date();
const lastPeriodStart = new Date(state.userSettings.lastPeriodStart);
const cycleEnd = new Date(lastPeriodStart);
cycleEnd.setDate(cycleEnd.getDate() + state.userSettings.cycleLength);
if (today < lastPeriodStart || today >= cycleEnd) {
elements.todayStatus.innerHTML = `
<div class="flex items-center">
<i class="fas fa-calendar-check text-blue-500 mr-2"></i>
<span>You're not currently in a tracked cycle. Start a new cycle when your period begins.</span>
</div>
`;
elements.todayStatus.className = 'bg-blue-50 text-blue-800 p-4 rounded-lg';
return;
}
const dayInCycle = Math.floor((today - lastPeriodStart) / (1000 * 60 * 60 * 24)) + 1;
const ovulationDay = state.userSettings.cycleLength - 14;
const fertileStart = ovulationDay - 5;
const fertileEnd = ovulationDay;
if (dayInCycle <= state.userSettings.periodLength) {
// Period
elements.todayStatus.innerHTML = `
<div class="flex items-center">
<i class="fas fa-tint text-pink-500 mr-2"></i>
<span><strong>Day ${dayInCycle} of Period:</strong> You're currently on your period.</span>
</div>
`;
elements.todayStatus.className = 'bg-pink-50 text-pink-800 p-4 rounded-lg';
} else if (dayInCycle >= fertileStart && dayInCycle <= fertileEnd) {
// Fertile window
if (dayInCycle === ovulationDay) {
// Ovulation day
elements.todayStatus.innerHTML = `
<div class="flex items-center">
<i class="fas fa-egg text-yellow-500 mr-2"></i>
<span><strong>Ovulation Day:</strong> You're most fertile today!</span>
</div>
`;
elements.todayStatus.className = 'bg-yellow-50 text-yellow-800 p-4 rounded-lg';
} else {
// Fertile days
elements.todayStatus.innerHTML = `
<div class="flex items-center">
<i class="fas fa-seedling text-green-500 mr-2"></i>
<span><strong>Fertile Window (Day ${dayInCycle}):</strong> High chance of conception.</span>
</div>
`;
elements.todayStatus.className = 'bg-green-50 text-green-800 p-4 rounded-lg';
}
} else if (dayInCycle > ovulationDay) {
// Luteal phase
const daysUntilPeriod = state.userSettings.cycleLength - dayInCycle;
elements.todayStatus.innerHTML = `
<div class="flex items-center">
<i class="fas fa-clock text-purple-500 mr-2"></i>
<span><strong>Luteal Phase (Day ${dayInCycle}):</strong> ${daysUntilPeriod} days until your next period.</span>
</div>
`;
elements.todayStatus.className = 'bg-purple-50 text-purple-800 p-4 rounded-lg';
} else {
// Follicular phase
elements.todayStatus.innerHTML = `
<div class="flex items-center">
<i class="fas fa-leaf text-blue-500 mr-2"></i>
<span><strong>Follicular Phase (Day ${dayInCycle}):</strong> Your body is preparing for ovulation.</span>
</div>
`;
elements.todayStatus.className = 'bg-blue-50 text-blue-800 p-4 rounded-lg';
}
}
// Update statistics
function updateStatistics() {
if (state.cycleData.length > 0) {
// Calculate average cycle length
const totalCycles = state.cycleData.length;
const totalDays = state.cycleData.reduce((sum, cycle) => sum + cycle.length, 0);
const avgCycle = Math.round(totalDays / totalCycles);
elements.avgCycleLength.textContent = `${avgCycle} days`;
// Calculate average period length
const totalPeriodDays = state.cycleData.reduce((sum, cycle) => sum + (cycle.periodLength || state.userSettings.periodLength), 0);
const avgPeriod = Math.round(totalPeriodDays / totalCycles);
elements.avgPeriodLength.textContent = `${avgPeriod} days`;
} else {
elements.avgCycleLength.textContent = `${state.userSettings.cycleLength} days`;
elements.avgPeriodLength.textContent = `${state.userSettings.periodLength} days`;
}
// Calculate next period date
if (state.userSettings.lastPeriodStart) {
const lastPeriodStart = new Date(state.userSettings.lastPeriodStart);
const nextPeriodStart = new Date(lastPeriodStart);
nextPeriodStart.setDate(nextPeriodStart.getDate() + state.userSettings.cycleLength);
const today = new Date();
if (nextPeriodStart > today) {
const options = { weekday: 'long', month: 'long', day: 'numeric' };
elements.nextPeriodDate.textContent = nextPeriodStart.toLocaleDateString('en-US', options);
} else {
elements.nextPeriodDate.textContent = '--';
}
} else {
elements.nextPeriodDate.textContent = '--';
}
}
// Render symptoms
function renderSymptoms() {
elements.symptomsList.innerHTML = '';
const today = formatDate(new Date());
if (state.symptoms[today]) {
state.symptoms[today].forEach((symptom, index) => {
const symptomElement = document.createElement('div');
symptomElement.className = 'flex items-center bg-gray-100 px-3 py-2 rounded-full symptom-icon';
const icon = document.createElement('i');
icon.className = getSymptomIcon(symptom.type) + ' text-pink-500 mr-2';
const intensity = document.createElement('div');
intensity.className = 'w-2 h-2 rounded-full mr-2';
intensity.style.backgroundColor = getIntensityColor(symptom.intensity);
const text = document.createElement('span');
text.className = 'text-sm text-gray-700 mr-2';
text.textContent = symptom.type;
const deleteBtn = document.createElement('button');
deleteBtn.className = 'text-gray-400 hover:text-red-500';
deleteBtn.innerHTML = '<i class="fas fa-times"></i>';
deleteBtn.onclick = () => removeSymptom(today, index);
symptomElement.appendChild(icon);
symptomElement.appendChild(intensity);
symptomElement.appendChild(text);
symptomElement.appendChild(deleteBtn);
elements.symptomsList.appendChild(symptomElement);
});
}
}
// Remove symptom
function removeSymptom(dateString, index) {
if (state.symptoms[dateString]) {
state.symptoms[dateString].splice(index, 1);
if (state.symptoms[dateString].length === 0) {
delete state.symptoms[dateString];
}
saveData();
renderSymptoms();
renderCalendar();
}
}
// Format date as YYYY-MM-DD
function formatDate(date) {
const year = date.getFullYear();
const month = String(date.getMonth() + 1).padStart(2, '0');
const day = String(date.getDate()).padStart(2, '0');
return `${year}-${month}-${day}`;
}
// Setup event listeners
function setupEventListeners() {
// Navigation buttons
elements.prevMonthBtn.addEventListener('click', () => {
state.currentDate.setMonth(state.currentDate.getMonth() - 1);
renderCalendar();
});
elements.nextMonthBtn.addEventListener('click', () => {
state.currentDate.setMonth(state.currentDate.getMonth() + 1);
renderCalendar();
});
// Save settings
elements.saveSettingsBtn.addEventListener('click', () => {
state.userSettings.cycleLength = parseInt(elements.cycleLength.value);
state.userSettings.periodLength = parseInt(elements.periodLength.value);
state.userSettings.lastPeriodStart = elements.lastPeriodStart.value;
// Record this cycle if it's a new period start
if (state.userSettings.lastPeriodStart) {
const lastPeriodDate = new Date(state.userSettings.lastPeriodStart);
const existingCycle = state.cycleData.find(cycle =>
new Date(cycle.startDate).getTime() === lastPeriodDate.getTime()
);
if (!existingCycle) {
state.cycleData.push({
startDate: state.userSettings.lastPeriodStart,
length: state.userSettings.cycleLength,
periodLength: state.userSettings.periodLength
});
// Keep only the last 12 cycles
if (state.cycleData.length > 12) {
state.cycleData.shift();
}
}
}
saveData();
renderCalendar();
updateCyclePhases();
updateTodayStatus();
updateStatistics();
});
// Symptoms
elements.addSymptomBtn.addEventListener('click', () => {
elements.symptomModal.classList.remove('hidden');
});
elements.closeSymptomModal.addEventListener('click', () => {
elements.symptomModal.classList.add('hidden');
});
elements.saveSymptomBtn.addEventListener('click', () => {
const today = formatDate(new Date());
const symptom = {
type: elements.symptomType.value,
intensity: parseInt(elements.symptomIntensity.value),
notes: elements.symptomNotes.value,
date: today
};
if (!state.symptoms[today]) {
state.symptoms[today] = [];
}
state.symptoms[today].push(symptom);
saveData();
renderSymptoms();
renderCalendar();
elements.symptomModal.classList.add('hidden');
// Reset form
elements.symptomType.value = 'cramps';
elements.symptomIntensity.value = 3;
elements.symptomNotes.value = '';
});
// Notes
elements.saveNotesBtn.addEventListener('click', () => {
state.notes = elements.cycleNotes.value;
saveData();
// Show confirmation
const originalText = elements.saveNotesBtn.textContent;
elements.saveNotesBtn.textContent = 'Saved!';
elements.saveNotesBtn.classList.remove('bg-pink-500', 'hover:bg-pink-600');
elements.saveNotesBtn.classList.add('bg-green-500', 'hover:bg-green-600');
setTimeout(() => {
elements.saveNotesBtn.textContent = originalText;
elements.saveNotesBtn.classList.remove('bg-green-500', 'hover:bg-green-600');
elements.saveNotesBtn.classList.add('bg-pink-500', 'hover:bg-pink-600');
}, 2000);
});
// New cycle button
elements.newCycleBtn.addEventListener('click', () => {
const today = formatDate(new Date());
elements.lastPeriodStart.value = today;
state.userSettings.lastPeriodStart = today;
// Record this cycle
state.cycleData.push({
startDate: today,
length: state.userSettings.cycleLength,
periodLength: state.userSettings.periodLength
});
// Keep only the last 12 cycles
if (state.cycleData.length > 12) {
state.cycleData.shift();
}
saveData();
renderCalendar();
updateCyclePhases();
updateTodayStatus();
updateStatistics();
});
// Set last period start to today by default if empty
if (!elements.lastPeriodStart.value) {
elements.lastPeriodStart.value = formatDate(new Date());
}
}
// Initialize the app
init();
</script>
<p style="border-radius: 8px; text-align: center; font-size: 12px; color: #fff; margin-top: 16px;position: fixed; left: 8px; bottom: 8px; z-index: 10; background: rgba(0, 0, 0, 0.8); padding: 4px 8px;">Made with <img src="https://enzostvs-deepsite.hf.space/logo.svg" alt="DeepSite Logo" style="width: 16px; height: 16px; vertical-align: middle;display:inline-block;margin-right:3px;filter:brightness(0) invert(1);"><a href="https://enzostvs-deepsite.hf.space" style="color: #fff;text-decoration: underline;" target="_blank" >DeepSite</a> - 🧬 <a href="https://enzostvs-deepsite.hf.space?remix=Axelle124125/cycletracker" style="color: #fff;text-decoration: underline;" target="_blank" >Remix</a></p></body>
</html>