Fasting Tracker and AI Trainer with Temporal Knowledge

if you dont know what is fasting what are the great health benefits just watch this youtube video and thank me later.

copy paste your openai key to the its place search openai it will popup

change the prompt as you want like give your name age weight..etc search prompt it will popup

everything saved on browser localstorage

this is just simple html page save it somewhere as index.html and bookmark it to your browser or as default home ๐Ÿ™‚

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8" />
  <meta name="viewport" content="width=device-width, initial-scale=1" />
  <title>90-Day Fasting Tracker - Dark Mode</title>
  <!-- Tailwind CSS CDN -->
  <script src="https://cdn.tailwindcss.com"></script>
  <!-- Marked.js CDN for Markdown support -->
  <script src="https://cdn.jsdelivr.net/npm/marked/marked.min.js"></script>
  <style>
    .day-box {
      transition: transform 0.1s;
      position: relative;
    }
    .day-box:active {
      transform: scale(0.95);
    }
    /* Container for the stats in the day grid */
    .stats-container {
      position: absolute;
      bottom: 1px;
      left: 1px;
      right: 1px;
      display: flex;
      justify-content: center;
      align-items: center;
      gap: 4px;
      font-size: 20px;
      flex-wrap: wrap;
    }
  </style>
</head>
<body class="bg-gray-900 text-white min-h-screen">
  <div class="flex">
    <!-- Left Sidebar -->
    <div id="sidebar" class="w-[220px] border-r border-gray-700 p-4 min-h-screen flex flex-col justify-between">
      <div>
        <h2 class="text-2xl font-bold mb-4">Day Details</h2>
        <div id="sidebarContent">
          <p>Select a day.</p>
        </div>
        <!-- Global Save button will be injected here when a day is selected -->
        <div id="globalSaveContainer"></div>
      </div>
      <!-- Controls at the bottom of the left sidebar -->
      <div id="sidebarControls" class="mt-4">
        <a href="#" id="resetLink" class="text-blue-400 hover:underline">Reset Tracker</a>
        <div class="mt-4 text-xs">
          <a href="#" id="exportLink" class="text-blue-400 hover:underline">Export Data</a> | 
          <a href="#" id="importLink" class="text-blue-400 hover:underline">Import Data</a>
          <div id="exportArea" class="mt-2" style="display: none;">
            <textarea id="exportTextarea" class="w-full bg-gray-800 border border-gray-600 rounded p-2 text-xs" rows="4" readonly></textarea>
          </div>
          <div id="importArea" class="mt-2" style="display: none;">
            <textarea id="importTextarea" class="w-full bg-gray-800 border border-gray-600 rounded p-2 text-xs" rows="4" placeholder="Paste JSON data here"></textarea>
            <button id="importButton" class="w-full mt-2 px-3 py-1 bg-blue-600 rounded hover:bg-green-700 text-xs">Import</button>
          </div>
        </div>
      </div>
    </div>
    <!-- Main Content -->
    <div class="flex-1 p-4">
      <div class="mb-6 flex">
        <h1 class="text-3xl font-bold">March to May - 90-Day Fasting Tracker</h1>
        <!-- Overall Statistics -->
        <div class="flex-1 shadow rounded p-4 text-right">
          <p class="text-xl">Fasting Days: <span id="totalFasting">0</span> / 90</p>
          <p class="text-lg">Percentage: <span id="percentage">0</span>%</p>
        </div>
      </div>
      <!-- 90-Day Grid -->
      <div id="grid" class="grid grid-cols-10 gap-2">
        <!-- Day boxes are generated here -->
      </div>
    </div>
    <!-- Right Sidebar - Chat UI -->
    <div id="chatSidebar" class="w-[400px] border-l border-gray-700 p-4 min-h-max flex flex-col">
      <div class="flex justify-between items-center mb-4">
        <h2 class="text-2xl font-bold">AI Chat</h2>
        <button id="newChatButton" class="px-2 py-1 bg-blue-600 rounded hover:bg-blue-700 text-xs">+</button>
      </div>
      <div id="chatPagination" class="flex justify-between mb-2">
        <button id="prevChat" class="px-2 py-1 bg-gray-700 rounded hover:bg-gray-600 text-xs">Prev Chat</button>
        <button id="nextChat" class="px-2 py-1 bg-gray-700 rounded hover:bg-gray-600 text-xs">Next Chat</button>
      </div>
      <!-- The important change: we add a max height so the chat gets a scrollbar -->
      <div
        id="chatContainer"
        class="flex-1 overflow-y-auto mb-4 bg-gray-800 p-2 rounded max-h-[770px]"
      ></div>
      <textarea id="chatInput" placeholder="Type your message and press Enter" class="w-full bg-gray-700 border border-gray-600 rounded p-2 text-md" rows="3"></textarea>
    </div>
  </div>

  <script>
    // ===================== Fasting Tracker Code =====================
    const totalDays = 90;
    // Set the start date as March 1, 2023
    const startDate = new Date("2023-03-01");
    const endDate = new Date(startDate);
    endDate.setDate(startDate.getDate() + totalDays - 1);
    
    // Determine today's date and its index relative to startDate
    const today = new Date();
    let currentDayIndex = Math.floor((today - startDate) / (1000 * 60 * 60 * 24));
    if (currentDayIndex < 0 || currentDayIndex >= totalDays) {
      currentDayIndex = null;
    }
    
    // Update header to show the dynamic date range
    document.querySelector("h1.text-3xl").textContent =
      startDate.toLocaleDateString('en-US', { month: 'long', day: 'numeric' }) +
      " to " +
      endDate.toLocaleDateString('en-US', { month: 'long', day: 'numeric' }) +
      " - 90-Day Fasting Tracker";

    const gridElement = document.getElementById("grid");
    const totalFastingElement = document.getElementById("totalFasting");
    const percentageElement = document.getElementById("percentage");
    const sidebarContent = document.getElementById("sidebarContent");
    const globalSaveContainer = document.getElementById("globalSaveContainer");

    // Global variable for selected day index (0-based)
    let selectedDay = null;

    // Data structure for each day:
    // { fasting: boolean, notes: [ { icon: string, text: string } ], weight: string, keton: string, steps: string }
    let fastingData = JSON.parse(localStorage.getItem("fastingData")) ||
      Array.from({ length: totalDays }, () => ({ fasting: false, notes: [], weight: "", keton: "", steps: "" }));

    // Save data to localStorage
    function saveData() {
      localStorage.setItem("fastingData", JSON.stringify(fastingData));
    }

    // Update overall statistics
    function updateStats() {
      const fastingCount = fastingData.filter(day => day.fasting).length;
      totalFastingElement.textContent = fastingCount;
      percentageElement.textContent = ((fastingCount / totalDays) * 100).toFixed(0);
    }

    // Render the 90-day grid
    function renderGrid() {
      gridElement.innerHTML = "";
      fastingData.forEach((dayData, index) => {
        const box = document.createElement("div");
        box.className = `day-box cursor-pointer border rounded flex flex-col items-center justify-center h-[86px] relative transition-all 
          ${dayData.fasting ? "bg-blue-600 border-green-800" : "bg-gray-700 border-gray-600 hover:bg-gray-600"}`;

        // Day number at top left
        const dayNum = document.createElement("span");
        dayNum.className = "absolute top-1 left-1 text-xs";
        dayNum.textContent = index + 1;
        box.appendChild(dayNum);

        // Calculate the actual date for this day and display it at the bottom right
        const currentDate = new Date(startDate);
        currentDate.setDate(startDate.getDate() + index);
        const dateSpan = document.createElement("span");
        dateSpan.className = "absolute top-1 right-1 text-[9px]";
        dateSpan.textContent = currentDate.toLocaleDateString('en-US', { month: 'short', day: 'numeric' });
        box.appendChild(dateSpan);

        // If this day is today, add a "Today" label
        if (currentDayIndex !== null && index === currentDayIndex) {
          const todayLabel = document.createElement("span");
          todayLabel.className = "absolute top-1 right-1 text-xs text-green-400";
          todayLabel.textContent = "Today";
          box.appendChild(todayLabel);
        }

        // Stats container at bottom center (shows note icon, weight, keton, and steps)
        const statsContainer = document.createElement("div");
        statsContainer.className = "stats-container";
        dayData.notes.forEach(note => {
          if (note.icon) {
            const noteIcon = document.createElement("span");
            noteIcon.textContent = note.icon;
            statsContainer.appendChild(noteIcon);
          }
        });
        if (dayData.weight) {
          const weightSpan = document.createElement("span");
          weightSpan.innerHTML = 'โš– ' + dayData.weight;
          statsContainer.appendChild(weightSpan);
        }
        if (dayData.keton) {
          const ketonSpan = document.createElement("span");
          ketonSpan.innerHTML = '๐Ÿงช ' + dayData.keton;
          statsContainer.appendChild(ketonSpan);
        }
        if (dayData.steps) {
          const stepsSpan = document.createElement("span");
          stepsSpan.innerHTML = '' + dayData.steps;
          statsContainer.appendChild(stepsSpan);
        }
        if (statsContainer.children.length > 0) {
          box.appendChild(statsContainer);
        }

        // Highlight if selected
        if (selectedDay === index) {
          box.classList.add("ring", "ring-yellow-500", "ring-2");
        }

        // Click event to select a day
        box.addEventListener("click", () => {
          selectedDay = index;
          renderSidebar();
          renderGrid();
        });
        gridElement.appendChild(box);
      });
    }

    // Render the sidebar with day details (stats and notes)
    function renderSidebar() {
      if (selectedDay === null) {
        sidebarContent.innerHTML = "<p>Select a day from the grid.</p>";
        globalSaveContainer.innerHTML = "";
        return;
      }
      const dayData = fastingData[selectedDay];
      sidebarContent.innerHTML = "";
      
      // Header
      const header = document.createElement("h3");
      header.className = "text-xl font-semibold mb-2";
      header.textContent = `Day ${selectedDay + 1} Details`;
      sidebarContent.appendChild(header);

      // Display the corresponding date for the selected day
      const currentDate = new Date(startDate);
      currentDate.setDate(startDate.getDate() + selectedDay);
      const dateInfo = document.createElement("p");
      dateInfo.className = "text-sm text-gray-400";
      dateInfo.textContent = "Date: " + currentDate.toLocaleDateString('en-US', { month: 'long', day: 'numeric', year: 'numeric' });
      sidebarContent.appendChild(dateInfo);

      // If the selected day is today, add a note indicating so
      if (currentDayIndex !== null && selectedDay === currentDayIndex) {
        const todayNote = document.createElement("p");
        todayNote.className = "text-sm text-green-400";
        todayNote.textContent = "This is today!";
        sidebarContent.appendChild(todayNote);
      }

      // Fasting toggle
      const fastingDiv = document.createElement("div");
      fastingDiv.className = "mb-4";
      const fastingLabel = document.createElement("span");
      fastingLabel.className = "mr-2";
      fastingLabel.textContent = "Fasting:";
      const fastingStatus = document.createElement("span");
      fastingStatus.className = "font-bold mr-4";
      fastingStatus.textContent = dayData.fasting ? "Yes" : "No";
      const toggleFasting = document.createElement("button");
      toggleFasting.className = "px-3 py-1 bg-blue-600 rounded hover:bg-blue-700 text-xs";
      toggleFasting.textContent = "Toggle";
      toggleFasting.addEventListener("click", () => {
        dayData.fasting = !dayData.fasting;
        saveData();
        updateStats();
        renderGrid();
        renderSidebar();
      });
      fastingDiv.appendChild(fastingLabel);
      fastingDiv.appendChild(fastingStatus);
      fastingDiv.appendChild(toggleFasting);
      sidebarContent.appendChild(fastingDiv);

      // Daily Stats section
      const statsDiv = document.createElement("div");
      statsDiv.className = "mb-4";
      const statsHeader = document.createElement("h4");
      statsHeader.className = "text-lg font-semibold mb-2";
      statsHeader.textContent = "Daily Stats:";
      statsDiv.appendChild(statsHeader);
      
      // Weight input
      const weightLabel = document.createElement("label");
      weightLabel.className = "block mb-1";
      weightLabel.textContent = "Weight:";
      statsDiv.appendChild(weightLabel);
      const weightInput = document.createElement("input");
      weightInput.type = "number";
      weightInput.value = dayData.weight;
      weightInput.placeholder = "Enter weight";
      weightInput.className = "w-full bg-gray-700 border border-gray-600 rounded p-1 mb-2 text-xs";
      statsDiv.appendChild(weightInput);

      // Keton input
      const ketonLabel = document.createElement("label");
      ketonLabel.className = "block mb-1";
      ketonLabel.textContent = "Keton Value:";
      statsDiv.appendChild(ketonLabel);
      const ketonInput = document.createElement("input");
      ketonInput.type = "number";
      ketonInput.value = dayData.keton;
      ketonInput.placeholder = "Enter keton value";
      ketonInput.className = "w-full bg-gray-700 border border-gray-600 rounded p-1 mb-2 text-xs";
      statsDiv.appendChild(ketonInput);
      
      // Step Count input
      const stepsLabel = document.createElement("label");
      stepsLabel.className = "block mb-1";
      stepsLabel.textContent = "Step Count:";
      statsDiv.appendChild(stepsLabel);
      const stepsInput = document.createElement("input");
      stepsInput.type = "number";
      stepsInput.value = dayData.steps;
      stepsInput.placeholder = "Enter step count";
      stepsInput.className = "w-full bg-gray-700 border border-gray-600 rounded p-1 mb-2 text-xs";
      statsDiv.appendChild(stepsInput);

      sidebarContent.appendChild(statsDiv);

      // Notes section
      const notesHeader = document.createElement("h4");
      notesHeader.className = "text-lg font-semibold mb-2";
      notesHeader.textContent = "Notes:";
      sidebarContent.appendChild(notesHeader);

      const notesList = document.createElement("div");
      notesList.className = "space-y-2 mb-4";
      if (dayData.notes.length === 0) {
        const noNotes = document.createElement("p");
        noNotes.className = "text-sm text-gray-400";
        noNotes.textContent = "No notes yet.";
        notesList.appendChild(noNotes);
      } else {
        dayData.notes.forEach((note, idx) => {
          const noteDiv = document.createElement("div");
          noteDiv.className = "flex items-center justify-between bg-gray-800 p-2 rounded";
          
          const noteContent = document.createElement("div");
          noteContent.className = "flex items-center space-x-2";
          if (note.icon) {
            const iconSpan = document.createElement("span");
            iconSpan.textContent = note.icon;
            noteContent.appendChild(iconSpan);
          }
          let textElem = document.createElement("span");
          textElem.className = "note-text";
          textElem.dataset.index = idx;
          textElem.textContent = note.text;
          noteContent.appendChild(textElem);
          noteDiv.appendChild(noteContent);
          
          const btnContainer = document.createElement("div");
          btnContainer.className = "flex items-center space-x-2 text-xs";
          
          const editBtn = document.createElement("button");
          editBtn.className = "text-blue-400 hover:text-blue-600";
          editBtn.textContent = "Edit";
          editBtn.addEventListener("click", () => {
            if (textElem.tagName.toLowerCase() === "input") {
              const spanElem = document.createElement("span");
              spanElem.className = "note-text";
              spanElem.dataset.index = idx;
              spanElem.textContent = dayData.notes[idx].text;
              noteContent.replaceChild(spanElem, textElem);
              textElem = spanElem;
              editBtn.textContent = "Edit";
            } else {
              const inputField = document.createElement("input");
              inputField.type = "text";
              inputField.value = note.text;
              inputField.className = "note-edit-input text-xs bg-gray-700 border border-gray-600 rounded p-1 text-white";
              inputField.dataset.index = idx;
              noteContent.replaceChild(inputField, textElem);
              textElem = inputField;
              editBtn.textContent = "Cancel";
            }
          });
          btnContainer.appendChild(editBtn);

          const deleteBtn = document.createElement("button");
          deleteBtn.className = "text-red-400 hover:text-red-600";
          deleteBtn.textContent = "Delete";
          deleteBtn.addEventListener("click", () => {
            dayData.notes.splice(idx, 1);
            saveData();
            renderSidebar();
            renderGrid();
          });
          btnContainer.appendChild(deleteBtn);
          noteDiv.appendChild(btnContainer);
          notesList.appendChild(noteDiv);
        });
      }
      sidebarContent.appendChild(notesList);

      const toggleNoteFormButton = document.createElement("button");
      toggleNoteFormButton.className = "w-full px-3 py-2 bg-blue-600 rounded hover:bg-green-700 mb-2 text-xs";
      toggleNoteFormButton.textContent = "Add Note +";
      sidebarContent.appendChild(toggleNoteFormButton);

      const noteFormDiv = document.createElement("div");
      noteFormDiv.style.display = "none";
      noteFormDiv.className = "space-y-2 mb-4";

      const newNoteIconInput = document.createElement("input");
      newNoteIconInput.type = "text";
      newNoteIconInput.placeholder = "Enter icon (emoji)";
      newNoteIconInput.className = "w-full bg-gray-700 border border-gray-600 rounded p-1 mb-2 text-xs";
      noteFormDiv.appendChild(newNoteIconInput);

      const newNoteTextarea = document.createElement("textarea");
      newNoteTextarea.placeholder = "Enter your note here...";
      newNoteTextarea.className = "w-full bg-gray-700 border border-gray-600 rounded p-2 text-xs";
      noteFormDiv.appendChild(newNoteTextarea);

      sidebarContent.appendChild(noteFormDiv);

      toggleNoteFormButton.addEventListener("click", () => {
        if (noteFormDiv.style.display === "none" || noteFormDiv.style.display === "") {
          noteFormDiv.style.display = "block";
          toggleNoteFormButton.textContent = "Hide Note Form";
        } else {
          noteFormDiv.style.display = "none";
          toggleNoteFormButton.textContent = "Add Note +";
        }
      });

      globalSaveContainer.innerHTML = "";
      const globalSaveBtn = document.createElement("button");
      globalSaveBtn.className = "w-full px-3 py-2 bg-blue-600 rounded hover:bg-blue-700 mt-4 text-xs";
      globalSaveBtn.textContent = "Save Changes";
      globalSaveBtn.addEventListener("click", () => {
        dayData.weight = weightInput.value;
        dayData.keton = ketonInput.value;
        dayData.steps = stepsInput.value;
        const editInputs = sidebarContent.querySelectorAll(".note-edit-input");
        editInputs.forEach(input => {
          const idx = input.dataset.index;
          if (input.value.trim() !== "") {
            dayData.notes[idx].text = input.value.trim();
          }
        });
        if (noteFormDiv.style.display === "block") {
          const newNoteText = newNoteTextarea.value.trim();
          const newNoteIcon = newNoteIconInput.value.trim();
          if (newNoteText !== "") {
            dayData.notes.push({
              icon: newNoteIcon,
              text: newNoteText
            });
          }
          newNoteTextarea.value = "";
          newNoteIconInput.value = "";
        }
        saveData();
        updateStats();
        renderSidebar();
        renderGrid();
      });
      globalSaveContainer.appendChild(globalSaveBtn);
    }

    document.getElementById("resetLink").addEventListener("click", (e) => {
      e.preventDefault();
      if (confirm("Are you sure you want to reset your tracker?")) {
        fastingData = Array.from({ length: totalDays }, () => ({ fasting: false, notes: [], weight: "", keton: "", steps: "" }));
        selectedDay = null;
        saveData();
        updateStats();
        renderGrid();
        renderSidebar();
      }
    });

    document.getElementById("exportLink").addEventListener("click", (e) => {
      e.preventDefault();
      const exportArea = document.getElementById("exportArea");
      const exportTextarea = document.getElementById("exportTextarea");
      exportTextarea.value = JSON.stringify(fastingData, null, 2);
      exportArea.style.display = exportArea.style.display === "none" ? "block" : "none";
    });
    document.getElementById("importLink").addEventListener("click", (e) => {
      e.preventDefault();
      const importArea = document.getElementById("importArea");
      importArea.style.display = importArea.style.display === "none" ? "block" : "none";
    });
    document.getElementById("importButton").addEventListener("click", () => {
      const importTextarea = document.getElementById("importTextarea");
      try {
        const importedData = JSON.parse(importTextarea.value);
        if (Array.isArray(importedData)) {
          fastingData = importedData;
          saveData();
          renderGrid();
          renderSidebar();
          alert("Data imported successfully!");
        } else {
          alert("Invalid data format.");
        }
      } catch (error) {
        alert("Error parsing JSON.");
      }
    });

    updateStats();
    renderGrid();

    // ===================== Chat UI and OpenAI API Integration =====================
    function getDate() {
      return new Date().toLocaleDateString('en-US', { month: 'long', day: 'numeric', year: 'numeric' });
    }

    function getDateTime() {
      const now = new Date();
      return now.toLocaleString('en-US', {
        month: 'long',
        day: 'numeric',
        year: 'numeric',
        hour: '2-digit',
        minute: '2-digit',
        second: '2-digit',
        hour12: false
      });
    }

    const openaiApiKey = 'YOUR_OPENAI_KEY';
    let chatSessions = JSON.parse(localStorage.getItem("chatSessions")) || [];
    if (chatSessions.length === 0) {
      chatSessions.push({ id: Date.now(), messages: [] });
    }
    let currentChatIndex = chatSessions.length - 1;

    const chatContainer = document.getElementById("chatContainer");
    const chatInput = document.getElementById("chatInput");
    const newChatButton = document.getElementById("newChatButton");
    const prevChatButton = document.getElementById("prevChat");
    const nextChatButton = document.getElementById("nextChat");

    function saveChats() {
      localStorage.setItem("chatSessions", JSON.stringify(chatSessions));
    }

    function renderChat() {
      chatContainer.innerHTML = "";
      const currentChat = chatSessions[currentChatIndex];
      currentChat.messages.forEach(msg => {
        const msgDiv = document.createElement("div");
        if (msg.sender === "ai") {
          msgDiv.className = "bg-gray-600 p-2 rounded mb-2";
          msgDiv.innerHTML = marked.parse(msg.text);
        } else {
          msgDiv.className = "bg-blue-600 p-2 rounded mb-2 text-right";
          msgDiv.textContent = msg.text;
        }
        chatContainer.appendChild(msgDiv);
      });
      chatContainer.scrollTop = chatContainer.scrollHeight;
    }

    async function sendMessage(message) {
      chatSessions[currentChatIndex].messages.push({ sender: "user", text: message });
      renderChat();
      saveChats();

      // Build the system prompt to include ALL days data
      const systemMessage = {
        role: "system",
        content:
          "my name is sinan. this is my 90 day tracking for fasting and walking step data starting from " +
          startDate.toLocaleDateString('en-US') +
          ". today is " +
          getDateTime() +
          ". all days data: " +
          JSON.stringify(fastingData) +
          ". please be personal with me and always give me very very positive messages."
      };

      // Prepare the conversation array
      const conversation = [{ role: "system", content: systemMessage.content }];
      chatSessions[currentChatIndex].messages.forEach(msg => {
        conversation.push({ role: msg.sender === "user" ? "user" : "assistant", content: msg.text });
      });

      try {
        const response = await fetch("https://api.openai.com/v1/chat/completions", {
          method: "POST",
          headers: {
            "Content-Type": "application/json",
            "Authorization": "Bearer " + openaiApiKey
          },
          body: JSON.stringify({
            model: "gpt-4o-mini",
            messages: conversation,
            temperature: 0.7
          })
        });
        const data = await response.json();
        if (!data.choices || data.choices.length === 0) {
          console.error("Unexpected API response:", data);
          return;
        }
        const aiMessage = data.choices[0].message.content.trim();
        chatSessions[currentChatIndex].messages.push({ sender: "ai", text: aiMessage });
        renderChat();
        saveChats();
      } catch (error) {
        console.error("Error calling OpenAI API:", error);
      }
    }

    chatInput.addEventListener("keydown", (e) => {
      if (e.key === "Enter" && !e.shiftKey) {
        e.preventDefault();
        const message = chatInput.value.trim();
        if (message !== "") {
          chatInput.value = "";
          sendMessage(message);
        }
      }
    });

    newChatButton.addEventListener("click", () => {
      chatSessions.push({ id: Date.now(), messages: [] });
      currentChatIndex = chatSessions.length - 1;
      renderChat();
      saveChats();
    });

    prevChatButton.addEventListener("click", () => {
      if (currentChatIndex > 0) {
        currentChatIndex--;
        renderChat();
      }
    });

    nextChatButton.addEventListener("click", () => {
      if (currentChatIndex < chatSessions.length - 1) {
        currentChatIndex++;
        renderChat();
      }
    });

    renderChat();
  </script>
</body>
</html>