Build a private, personal-use app for maintaining weekly music charts. The app should show a “Hot 100”-style chart (number of entries can vary per week).
Browse files🗂 Data Model
Chart
Date (date field)
Entries (list of Entry objects)
Entry
Rank (number)
Song Title (text)
Artists (text, comma-separated)
Movement (text: +X, -X, <>, NEW, RE)
Peak Position (number)
Total Weeks (number)
📄 Pages & Navigation
Main Page (Latest Chart)
Display current chart in a table with columns: Rank | Song Title | Artist(s) | Peak | Total Weeks | Movement.
Artist names appear hyperlinked (if eligible) → link to Artist Page.
Buttons:
Edit Chart (switches to Edit Mode).
Create Next Chart (creates new chart dated 7 days after last one).
Allow override with a date picker.
Collapsible Dropouts section at the bottom showing songs that left the chart.
Edit Mode (on Chart Page)
Entry form at the top of the table with fields:
Song Title (autocomplete, case-insensitive, exact match only, from past entries).
Artists (autocomplete works continuously after each comma, suggestion list shows all matches).
Chart table updates dynamically as entries are added.
Archive Page
Shows list of past charts by date.
Clicking one opens that chart view.
Artist Page (for main-credit artists only)
Top section:
“X song(s) charted”
Placeholder for artist photo.
Main section:
List of songs with:
Title (clickable to expand history).
Peak Position.
Total Weeks.
Featured artists shown in parentheses.
Expanding a song reveals chart history:
Dates shown as Month Day, Year (e.g., September 2, 2022).
Runs end with DROPOUT.
Multiple runs separated by “—”.
First entry of a re-entry run ends with (RE).
⚙️ Behaviors & Logic
Chart Creation:
Default date = 7 days after most recent chart.
Allow manual override with a date picker.
Movement Calculation:
Compare each entry’s current rank with previous week.
Display +X if up, -X if down, <> if unchanged.
Mark as NEW if first-time entry.
Mark as RE if re-entry after dropout.
Dropouts Section:
At end of each chart, list all songs that fell out.
Make this a collapsible section.
Artist Page Rules:
Only artists with at least one main credit get a page.
Artists who are feature-only → no page; they only appear in parentheses on other artists’ songs.
- README.md +8 -5
- archive.html +101 -0
- artist.html +249 -0
- index.html +247 -18
|
@@ -1,10 +1,13 @@
|
|
| 1 |
---
|
| 2 |
-
title:
|
| 3 |
-
|
| 4 |
-
|
| 5 |
-
|
| 6 |
sdk: static
|
| 7 |
pinned: false
|
|
|
|
|
|
|
| 8 |
---
|
| 9 |
|
| 10 |
-
|
|
|
|
|
|
| 1 |
---
|
| 2 |
+
title: ChartWave Tracker 🎶
|
| 3 |
+
colorFrom: yellow
|
| 4 |
+
colorTo: green
|
| 5 |
+
emoji: 🐳
|
| 6 |
sdk: static
|
| 7 |
pinned: false
|
| 8 |
+
tags:
|
| 9 |
+
- deepsite-v3
|
| 10 |
---
|
| 11 |
|
| 12 |
+
# Welcome to your new DeepSite project!
|
| 13 |
+
This project was created with [DeepSite](https://deepsite.hf.co).
|
|
@@ -0,0 +1,101 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
<!DOCTYPE html>
|
| 2 |
+
<html lang="en">
|
| 3 |
+
<head>
|
| 4 |
+
<meta charset="UTF-8">
|
| 5 |
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
| 6 |
+
<title>ChartWave Tracker | Archive</title>
|
| 7 |
+
<link rel="icon" type="image/x-icon" href="/static/favicon.ico">
|
| 8 |
+
<script src="https://cdn.tailwindcss.com"></script>
|
| 9 |
+
<script src="https://unpkg.com/feather-icons"></script>
|
| 10 |
+
<script src="https://cdn.jsdelivr.net/npm/feather-icons/dist/feather.min.js"></script>
|
| 11 |
+
<style>
|
| 12 |
+
.animate-fade-in { animation: fadeIn 0.5s ease-in; }
|
| 13 |
+
@keyframes fadeIn {
|
| 14 |
+
from { opacity: 0; transform: translateY(10px); }
|
| 15 |
+
to { opacity: 1; transform: translateY(0); }
|
| 16 |
+
}
|
| 17 |
+
.chart-card:hover {
|
| 18 |
+
transform: translateY(-2px);
|
| 19 |
+
box-shadow: 0 10px 25px -5px rgba(0, 0, 0, 0.1);
|
| 20 |
+
}
|
| 21 |
+
</style>
|
| 22 |
+
</head>
|
| 23 |
+
<body class="bg-gray-50">
|
| 24 |
+
<div class="container mx-auto px-4 py-8">
|
| 25 |
+
<header class="mb-8 animate-fade-in">
|
| 26 |
+
<a href="index.html" class="inline-flex items-center text-indigo-600 hover:text-indigo-800 mb-4">
|
| 27 |
+
<i data-feather="arrow-left" class="w-4 h-4 mr-2"></i> Back to Current Chart
|
| 28 |
+
</a>
|
| 29 |
+
<h1 class="text-3xl font-bold text-gray-800">Chart Archive</h1>
|
| 30 |
+
<p class="text-gray-600 mt-2">Browse through past weekly charts</p>
|
| 31 |
+
</header>
|
| 32 |
+
|
| 33 |
+
<main class="animate-fade-in">
|
| 34 |
+
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6" id="archiveGrid">
|
| 35 |
+
<!-- Chart cards will be populated here -->
|
| 36 |
+
</div>
|
| 37 |
+
</main>
|
| 38 |
+
</div>
|
| 39 |
+
|
| 40 |
+
<script>
|
| 41 |
+
// Sample archive data (would normally come from API/local storage)
|
| 42 |
+
const archiveData = [
|
| 43 |
+
{ date: "2023-11-20", entries: 10, topSong: "Cruel Summer", topArtist: "Taylor Swift" },
|
| 44 |
+
{ date: "2023-11-13", entries: 10, topSong: "Cruel Summer", topArtist: "Taylor Swift" },
|
| 45 |
+
{ date: "2023-11-06", entries: 10, topSong: "Paint The Town Red", topArtist: "Doja Cat" },
|
| 46 |
+
{ date: "2023-10-30", entries: 10, topSong: "Paint The Town Red", topArtist: "Doja Cat" },
|
| 47 |
+
{ date: "2023-10-23", entries: 10, topSong: "What Was I Made For?", topArtist: "Billie Eilish" },
|
| 48 |
+
{ date: "2023-10-16", entries: 10, topSong: "Dance The Night", topArtist: "Dua Lipa" },
|
| 49 |
+
{ date: "2023-10-09", entries: 10, topSong: "Fast Car", topArtist: "Luke Combs" },
|
| 50 |
+
{ date: "2023-10-02", entries: 10, topSong: "Last Night", topArtist: "Morgan Wallen" },
|
| 51 |
+
{ date: "2023-09-25", entries: 10, topSong: "vampire", topArtist: "Olivia Rodrigo" },
|
| 52 |
+
{ date: "2023-09-18", entries: 10, topSong: "Kill Bill", topArtist: "SZA" }
|
| 53 |
+
];
|
| 54 |
+
|
| 55 |
+
// Format date
|
| 56 |
+
function formatChartDate(dateStr) {
|
| 57 |
+
const date = new Date(dateStr);
|
| 58 |
+
return date.toLocaleDateString('en-US', { month: 'short', day: 'numeric', year: 'numeric' });
|
| 59 |
+
}
|
| 60 |
+
|
| 61 |
+
// Populate archive grid
|
| 62 |
+
function populateArchive() {
|
| 63 |
+
const grid = document.getElementById('archiveGrid');
|
| 64 |
+
grid.innerHTML = '';
|
| 65 |
+
|
| 66 |
+
archiveData.forEach(chart => {
|
| 67 |
+
const card = document.createElement('a');
|
| 68 |
+
card.href = `index.html?date=${chart.date}`;
|
| 69 |
+
card.className = 'chart-card bg-white rounded-lg shadow-md overflow-hidden transition duration-300';
|
| 70 |
+
|
| 71 |
+
card.innerHTML = `
|
| 72 |
+
<div class="p-6 border-b border-gray-200">
|
| 73 |
+
<h3 class="text-xl font-semibold text-gray-800">${formatChartDate(chart.date)}</h3>
|
| 74 |
+
</div>
|
| 75 |
+
<div class="p-6">
|
| 76 |
+
<div class="flex justify-between text-sm text-gray-600 mb-3">
|
| 77 |
+
<span>Entries</span>
|
| 78 |
+
<span class="font-medium">${chart.entries}</span>
|
| 79 |
+
</div>
|
| 80 |
+
<div class="flex justify-between text-sm text-gray-600">
|
| 81 |
+
<span>#1 Song</span>
|
| 82 |
+
<span class="font-medium">${chart.topSong}</span>
|
| 83 |
+
</div>
|
| 84 |
+
<div class="mt-4 pt-4 border-t border-gray-100 text-sm text-gray-500">
|
| 85 |
+
<i data-feather="user" class="w-4 h-4 inline mr-1"></i> ${chart.topArtist}
|
| 86 |
+
</div>
|
| 87 |
+
</div>
|
| 88 |
+
`;
|
| 89 |
+
|
| 90 |
+
grid.appendChild(card);
|
| 91 |
+
});
|
| 92 |
+
}
|
| 93 |
+
|
| 94 |
+
// Initialize on page load
|
| 95 |
+
document.addEventListener('DOMContentLoaded', function() {
|
| 96 |
+
populateArchive();
|
| 97 |
+
feather.replace();
|
| 98 |
+
});
|
| 99 |
+
</script>
|
| 100 |
+
</body>
|
| 101 |
+
</html>
|
|
@@ -0,0 +1,249 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
<!DOCTYPE html>
|
| 2 |
+
<html lang="en">
|
| 3 |
+
<head>
|
| 4 |
+
<meta charset="UTF-8">
|
| 5 |
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
| 6 |
+
<title>ChartWave Tracker | Artist</title>
|
| 7 |
+
<link rel="icon" type="image/x-icon" href="/static/favicon.ico">
|
| 8 |
+
<script src="https://cdn.tailwindcss.com"></script>
|
| 9 |
+
<script src="https://unpkg.com/feather-icons"></script>
|
| 10 |
+
<script src="https://cdn.jsdelivr.net/npm/feather-icons/dist/feather.min.js"></script>
|
| 11 |
+
<style>
|
| 12 |
+
.animate-fade-in { animation: fadeIn 0.5s ease-in; }
|
| 13 |
+
@keyframes fadeIn {
|
| 14 |
+
from { opacity: 0; transform: translateY(10px); }
|
| 15 |
+
to { opacity: 1; transform: translateY(0); }
|
| 16 |
+
}
|
| 17 |
+
.song-history-item:not(:last-child) {
|
| 18 |
+
border-bottom: 1px solid #e5e7eb;
|
| 19 |
+
padding-bottom: 1rem;
|
| 20 |
+
margin-bottom: 1rem;
|
| 21 |
+
}
|
| 22 |
+
</style>
|
| 23 |
+
</head>
|
| 24 |
+
<body class="bg-gray-50">
|
| 25 |
+
<div class="container mx-auto px-4 py-8">
|
| 26 |
+
<header class="mb-8 animate-fade-in">
|
| 27 |
+
<a href="index.html" class="inline-flex items-center text-indigo-600 hover:text-indigo-800 mb-4">
|
| 28 |
+
<i data-feather="arrow-left" class="w-4 h-4 mr-2"></i> Back to Chart
|
| 29 |
+
</a>
|
| 30 |
+
<h1 class="text-3xl font-bold text-gray-800" id="artistName">Artist Name</h1>
|
| 31 |
+
</header>
|
| 32 |
+
|
| 33 |
+
<main class="bg-white rounded-xl shadow-md overflow-hidden animate-fade-in">
|
| 34 |
+
<div class="p-6 border-b border-gray-200 flex flex-col md:flex-row items-start md:items-center gap-6">
|
| 35 |
+
<div class="w-32 h-32 rounded-full bg-gray-200 overflow-hidden flex-shrink-0">
|
| 36 |
+
<img id="artistImage" src="http://static.photos/music/320x240/1" alt="Artist" class="w-full h-full object-cover">
|
| 37 |
+
</div>
|
| 38 |
+
<div>
|
| 39 |
+
<p id="songCount" class="text-lg text-gray-600">X songs charted</p>
|
| 40 |
+
<p id="artistStats" class="text-gray-500 mt-1">Peak: #X • Top 10: X • Total Weeks: X</p>
|
| 41 |
+
</div>
|
| 42 |
+
</div>
|
| 43 |
+
|
| 44 |
+
<div class="divide-y divide-gray-200">
|
| 45 |
+
<div class="p-6">
|
| 46 |
+
<h2 class="text-xl font-semibold text-gray-800 mb-4">Chart History</h2>
|
| 47 |
+
|
| 48 |
+
<div class="space-y-6" id="songHistory">
|
| 49 |
+
<!-- Song history will be populated here -->
|
| 50 |
+
</div>
|
| 51 |
+
</div>
|
| 52 |
+
</div>
|
| 53 |
+
</main>
|
| 54 |
+
</div>
|
| 55 |
+
|
| 56 |
+
<script>
|
| 57 |
+
// Sample artist data (would normally come from URL params and API/local storage)
|
| 58 |
+
const artistData = {
|
| 59 |
+
name: "Taylor Swift",
|
| 60 |
+
imageSeed: 10, // For placeholder image variation
|
| 61 |
+
songs: [
|
| 62 |
+
{
|
| 63 |
+
title: "Cruel Summer",
|
| 64 |
+
peak: 1,
|
| 65 |
+
weeks: 12,
|
| 66 |
+
featured: false,
|
| 67 |
+
history: [
|
| 68 |
+
{ date: "2023-08-28", rank: 5, status: "NEW" },
|
| 69 |
+
{ date: "2023-09-04", rank: 3, status: "+2" },
|
| 70 |
+
{ date: "2023-09-11", rank: 2, status: "+1" },
|
| 71 |
+
{ date: "2023-09-18", rank: 1, status: "+1" },
|
| 72 |
+
{ date: "2023-09-25", rank: 1, status: "<>" },
|
| 73 |
+
{ date: "2023-10-02", rank: 1, status: "<>" },
|
| 74 |
+
// ... more weeks
|
| 75 |
+
]
|
| 76 |
+
},
|
| 77 |
+
{
|
| 78 |
+
title: "Anti-Hero",
|
| 79 |
+
peak: 1,
|
| 80 |
+
weeks: 24,
|
| 81 |
+
featured: false,
|
| 82 |
+
history: [
|
| 83 |
+
{ date: "2022-10-24", rank: 1, status: "NEW" },
|
| 84 |
+
{ date: "2022-10-31", rank: 1, status: "<>" },
|
| 85 |
+
// ... more weeks
|
| 86 |
+
{ date: "2023-04-10", rank: 25, status: "-3" },
|
| 87 |
+
{ date: "2023-04-17", rank: "DROPOUT", status: "OUT" }
|
| 88 |
+
]
|
| 89 |
+
},
|
| 90 |
+
{
|
| 91 |
+
title: "Karma",
|
| 92 |
+
peak: 2,
|
| 93 |
+
weeks: 16,
|
| 94 |
+
featured: false,
|
| 95 |
+
history: [
|
| 96 |
+
{ date: "2023-05-22", rank: 9, status: "NEW" },
|
| 97 |
+
{ date: "2023-05-29", rank: 5, status: "+4" },
|
| 98 |
+
{ date: "2023-06-05", rank: 3, status: "+2" },
|
| 99 |
+
{ date: "2023-06-12", rank: 2, status: "+1" },
|
| 100 |
+
// ... more weeks
|
| 101 |
+
]
|
| 102 |
+
}
|
| 103 |
+
]
|
| 104 |
+
};
|
| 105 |
+
|
| 106 |
+
// Update artist stats
|
| 107 |
+
function updateArtistStats() {
|
| 108 |
+
const top10Count = artistData.songs.filter(song => song.peak <= 10).length;
|
| 109 |
+
const totalWeeks = artistData.songs.reduce((sum, song) => sum + song.weeks, 0);
|
| 110 |
+
const highestPeak = Math.min(...artistData.songs.map(song => song.peak));
|
| 111 |
+
|
| 112 |
+
document.getElementById('songCount').textContent = `${artistData.songs.length} song${artistData.songs.length !== 1 ? 's' : ''} charted`;
|
| 113 |
+
document.getElementById('artistStats').textContent = `Peak: #${highestPeak} • Top 10: ${top10Count} • Total Weeks: ${totalWeeks}`;
|
| 114 |
+
}
|
| 115 |
+
|
| 116 |
+
// Populate song history
|
| 117 |
+
function populateSongHistory() {
|
| 118 |
+
const container = document.getElementById('songHistory');
|
| 119 |
+
container.innerHTML = '';
|
| 120 |
+
|
| 121 |
+
artistData.songs.forEach(song => {
|
| 122 |
+
const songElement = document.createElement('div');
|
| 123 |
+
songElement.className = 'song-history-item';
|
| 124 |
+
|
| 125 |
+
const header = document.createElement('div');
|
| 126 |
+
header.className = 'flex justify-between items-center cursor-pointer';
|
| 127 |
+
header.innerHTML = `
|
| 128 |
+
<div>
|
| 129 |
+
<h3 class="font-medium text-lg text-gray-800">${song.title}</h3>
|
| 130 |
+
<p class="text-sm text-gray-500">Peak: #${song.peak} • Weeks: ${song.weeks}</p>
|
| 131 |
+
</div>
|
| 132 |
+
<i data-feather="chevron-down" class="text-gray-400 transition-transform duration-200"></i>
|
| 133 |
+
`;
|
| 134 |
+
|
| 135 |
+
const historyElement = document.createElement('div');
|
| 136 |
+
historyElement.className = 'hidden mt-3 pl-2 border-l-2 border-indigo-200';
|
| 137 |
+
historyElement.id = `history-${song.title.replace(/\s+/g, '-')}`;
|
| 138 |
+
|
| 139 |
+
// Group consecutive weeks into runs
|
| 140 |
+
const runs = [];
|
| 141 |
+
let currentRun = [];
|
| 142 |
+
|
| 143 |
+
song.history.forEach((entry, index) => {
|
| 144 |
+
if (entry.rank === "DROPOUT") {
|
| 145 |
+
if (currentRun.length > 0) {
|
| 146 |
+
runs.push([...currentRun, entry]);
|
| 147 |
+
currentRun = [];
|
| 148 |
+
}
|
| 149 |
+
} else {
|
| 150 |
+
currentRun.push(entry);
|
| 151 |
+
}
|
| 152 |
+
|
| 153 |
+
if (index === song.history.length - 1 && currentRun.length > 0) {
|
| 154 |
+
runs.push([...currentRun]);
|
| 155 |
+
}
|
| 156 |
+
});
|
| 157 |
+
|
| 158 |
+
runs.forEach((run, runIndex) => {
|
| 159 |
+
const runElement = document.createElement('div');
|
| 160 |
+
runElement.className = 'mb-3';
|
| 161 |
+
|
| 162 |
+
if (runIndex > 0) {
|
| 163 |
+
runElement.innerHTML += `<div class="text-xs text-gray-400 my-1">—</div>`;
|
| 164 |
+
}
|
| 165 |
+
|
| 166 |
+
run.forEach((entry, entryIndex) => {
|
| 167 |
+
const date = new Date(entry.date);
|
| 168 |
+
const formattedDate = date.toLocaleDateString('en-US', { month: 'short', day: 'numeric', year: 'numeric' });
|
| 169 |
+
|
| 170 |
+
let statusClass = '';
|
| 171 |
+
let statusSymbol = '';
|
| 172 |
+
|
| 173 |
+
if (entry.status === "+X") {
|
| 174 |
+
statusClass = 'text-green-500';
|
| 175 |
+
statusSymbol = `↑`;
|
| 176 |
+
} else if (entry.status === "-X") {
|
| 177 |
+
statusClass = 'text-red-500';
|
| 178 |
+
statusSymbol = `↓`;
|
| 179 |
+
} else if (entry.status === "NEW") {
|
| 180 |
+
statusClass = 'text-blue-500';
|
| 181 |
+
statusSymbol = 'NEW';
|
| 182 |
+
} else if (entry.status === "<>") {
|
| 183 |
+
statusClass = 'text-gray-500';
|
| 184 |
+
statusSymbol = '↔';
|
| 185 |
+
}
|
| 186 |
+
|
| 187 |
+
if (entry.rank === "DROPOUT") {
|
| 188 |
+
runElement.innerHTML += `
|
| 189 |
+
<div class="text-sm text-gray-500">${formattedDate} • DROPOUT</div>
|
| 190 |
+
`;
|
| 191 |
+
} else if (entryIndex === 0 && runIndex > 0) {
|
| 192 |
+
runElement.innerHTML += `
|
| 193 |
+
<div class="text-sm">
|
| 194 |
+
<span class="font-medium">${formattedDate}</span> •
|
| 195 |
+
<span>#${entry.rank}</span> •
|
| 196 |
+
<span class="${statusClass}">${statusSymbol} (RE)</span>
|
| 197 |
+
</div>
|
| 198 |
+
`;
|
| 199 |
+
} else {
|
| 200 |
+
runElement.innerHTML += `
|
| 201 |
+
<div class="text-sm">
|
| 202 |
+
<span class="font-medium">${formattedDate}</span> •
|
| 203 |
+
<span>#${entry.rank}</span> •
|
| 204 |
+
<span class="${statusClass}">${statusSymbol}</span>
|
| 205 |
+
</div>
|
| 206 |
+
`;
|
| 207 |
+
}
|
| 208 |
+
});
|
| 209 |
+
|
| 210 |
+
historyElement.appendChild(runElement);
|
| 211 |
+
});
|
| 212 |
+
|
| 213 |
+
songElement.appendChild(header);
|
| 214 |
+
songElement.appendChild(historyElement);
|
| 215 |
+
container.appendChild(songElement);
|
| 216 |
+
|
| 217 |
+
// Add toggle functionality
|
| 218 |
+
header.addEventListener('click', function() {
|
| 219 |
+
const history = this.nextElementSibling;
|
| 220 |
+
const icon = this.querySelector('i');
|
| 221 |
+
|
| 222 |
+
if (history.classList.contains('hidden')) {
|
| 223 |
+
history.classList.remove('hidden');
|
| 224 |
+
icon.style.transform = 'rotate(180deg)';
|
| 225 |
+
} else {
|
| 226 |
+
history.classList.add('hidden');
|
| 227 |
+
icon.style.transform = 'rotate(0deg)';
|
| 228 |
+
}
|
| 229 |
+
});
|
| 230 |
+
});
|
| 231 |
+
}
|
| 232 |
+
|
| 233 |
+
// Initialize on page load
|
| 234 |
+
document.addEventListener('DOMContentLoaded', function() {
|
| 235 |
+
// Get artist name from URL
|
| 236 |
+
const urlParams = new URLSearchParams(window.location.search);
|
| 237 |
+
const artistName = urlParams.get('name') || artistData.name;
|
| 238 |
+
|
| 239 |
+
// Update page with artist data
|
| 240 |
+
document.getElementById('artistName').textContent = artistName;
|
| 241 |
+
document.getElementById('artistImage').src = `http://static.photos/music/320x240/${artistData.imageSeed}`;
|
| 242 |
+
|
| 243 |
+
updateArtistStats();
|
| 244 |
+
populateSongHistory();
|
| 245 |
+
feather.replace();
|
| 246 |
+
});
|
| 247 |
+
</script>
|
| 248 |
+
</body>
|
| 249 |
+
</html>
|
|
@@ -1,19 +1,248 @@
|
|
| 1 |
-
<!
|
| 2 |
-
<html>
|
| 3 |
-
|
| 4 |
-
|
| 5 |
-
|
| 6 |
-
|
| 7 |
-
|
| 8 |
-
|
| 9 |
-
|
| 10 |
-
|
| 11 |
-
|
| 12 |
-
|
| 13 |
-
|
| 14 |
-
|
| 15 |
-
|
| 16 |
-
|
| 17 |
-
|
| 18 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 19 |
</html>
|
|
|
|
| 1 |
+
<!DOCTYPE html>
|
| 2 |
+
<html lang="en">
|
| 3 |
+
<head>
|
| 4 |
+
<meta charset="UTF-8">
|
| 5 |
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
| 6 |
+
<title>ChartWave Tracker | Latest Chart</title>
|
| 7 |
+
<link rel="icon" type="image/x-icon" href="/static/favicon.ico">
|
| 8 |
+
<script src="https://cdn.tailwindcss.com"></script>
|
| 9 |
+
<script src="https://unpkg.com/feather-icons"></script>
|
| 10 |
+
<script src="https://cdn.jsdelivr.net/npm/feather-icons/dist/feather.min.js"></script>
|
| 11 |
+
<script src="https://cdn.jsdelivr.net/npm/vanta@latest/dist/vanta.waves.min.js"></script>
|
| 12 |
+
<script src="https://cdnjs.cloudflare.com/ajax/libs/date-fns/1.30.1/date_fns.min.js"></script>
|
| 13 |
+
<style>
|
| 14 |
+
.movement-up { color: #10B981; }
|
| 15 |
+
.movement-down { color: #EF4444; }
|
| 16 |
+
.movement-new { color: #3B82F6; }
|
| 17 |
+
.movement-re { color: #8B5CF6; }
|
| 18 |
+
.movement-same { color: #6B7280; }
|
| 19 |
+
.wave-bg { min-height: 100vh; }
|
| 20 |
+
.animate-fade-in { animation: fadeIn 0.5s ease-in; }
|
| 21 |
+
@keyframes fadeIn {
|
| 22 |
+
from { opacity: 0; transform: translateY(10px); }
|
| 23 |
+
to { opacity: 1; transform: translateY(0); }
|
| 24 |
+
}
|
| 25 |
+
.scrollbar-hide::-webkit-scrollbar {
|
| 26 |
+
display: none;
|
| 27 |
+
}
|
| 28 |
+
</style>
|
| 29 |
+
</head>
|
| 30 |
+
<body class="bg-gray-100">
|
| 31 |
+
<div id="wave-bg" class="wave-bg"></div>
|
| 32 |
+
|
| 33 |
+
<div class="relative z-10 container mx-auto px-4 py-8">
|
| 34 |
+
<header class="mb-8 text-center animate-fade-in">
|
| 35 |
+
<h1 class="text-4xl font-bold text-white drop-shadow-lg">ChartWave Tracker</h1>
|
| 36 |
+
<p class="text-xl text-white mt-2">Your personal music chart tracker</p>
|
| 37 |
+
</header>
|
| 38 |
+
|
| 39 |
+
<main class="bg-white bg-opacity-90 backdrop-blur-lg rounded-xl shadow-xl overflow-hidden animate-fade-in">
|
| 40 |
+
<div class="p-6 border-b border-gray-200 flex justify-between items-center">
|
| 41 |
+
<h2 class="text-2xl font-bold text-gray-800">Current Chart</h2>
|
| 42 |
+
<div class="flex space-x-3">
|
| 43 |
+
<button id="editBtn" class="px-4 py-2 bg-indigo-600 text-white rounded-lg hover:bg-indigo-700 transition flex items-center gap-2">
|
| 44 |
+
<i data-feather="edit-3" class="w-4 h-4"></i> Edit
|
| 45 |
+
</button>
|
| 46 |
+
<button id="newChartBtn" class="px-4 py-2 bg-green-600 text-white rounded-lg hover:bg-green-700 transition flex items-center gap-2">
|
| 47 |
+
<i data-feather="plus" class="w-4 h-4"></i> New Chart
|
| 48 |
+
</button>
|
| 49 |
+
</div>
|
| 50 |
+
</div>
|
| 51 |
+
|
| 52 |
+
<div class="overflow-x-auto scrollbar-hide">
|
| 53 |
+
<table class="w-full divide-y divide-gray-200">
|
| 54 |
+
<thead class="bg-gray-50">
|
| 55 |
+
<tr>
|
| 56 |
+
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Rank</th>
|
| 57 |
+
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Song</th>
|
| 58 |
+
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Artist(s)</th>
|
| 59 |
+
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Peak</th>
|
| 60 |
+
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Weeks</th>
|
| 61 |
+
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Movement</th>
|
| 62 |
+
</tr>
|
| 63 |
+
</thead>
|
| 64 |
+
<tbody class="bg-white divide-y divide-gray-200" id="chartTableBody">
|
| 65 |
+
<!-- Chart entries will be populated here -->
|
| 66 |
+
</tbody>
|
| 67 |
+
</table>
|
| 68 |
+
</div>
|
| 69 |
+
|
| 70 |
+
<div class="p-4 bg-gray-50 border-t border-gray-200">
|
| 71 |
+
<button id="dropoutToggle" class="flex items-center text-gray-600 hover:text-indigo-600">
|
| 72 |
+
<i data-feather="chevron-down" class="w-4 h-4 mr-2"></i>
|
| 73 |
+
<span>Show Dropouts</span>
|
| 74 |
+
</button>
|
| 75 |
+
<div id="dropoutSection" class="hidden mt-3">
|
| 76 |
+
<h3 class="text-lg font-medium text-gray-700 mb-2">Songs that left the chart this week</h3>
|
| 77 |
+
<div class="space-y-1" id="dropoutList">
|
| 78 |
+
<!-- Dropout entries will be populated here -->
|
| 79 |
+
</div>
|
| 80 |
+
</div>
|
| 81 |
+
</div>
|
| 82 |
+
</main>
|
| 83 |
+
|
| 84 |
+
<div id="newChartModal" class="fixed inset-0 z-50 hidden flex items-center justify-center bg-black bg-opacity-50">
|
| 85 |
+
<div class="bg-white rounded-xl p-6 w-full max-w-md">
|
| 86 |
+
<div class="flex justify-between items-center mb-4">
|
| 87 |
+
<h3 class="text-xl font-bold">Create New Chart</h3>
|
| 88 |
+
<button id="closeModalBtn"><i data-feather="x" class="text-gray-500"></i></button>
|
| 89 |
+
</div>
|
| 90 |
+
<div class="space-y-4">
|
| 91 |
+
<div>
|
| 92 |
+
<label class="block text-sm font-medium text-gray-700 mb-1">Chart Date</label>
|
| 93 |
+
<input type="date" id="chartDateInput" class="w-full px-3 py-2 border border-gray-300 rounded-md">
|
| 94 |
+
</div>
|
| 95 |
+
<div class="flex justify-end space-x-3 pt-2">
|
| 96 |
+
<button id="cancelNewChartBtn" class="px-4 py-2 border border-gray-300 rounded-md hover:bg-gray-50">Cancel</button>
|
| 97 |
+
<button id="confirmNewChartBtn" class="px-4 py-2 bg-indigo-600 text-white rounded-md hover:bg-indigo-700">Create</button>
|
| 98 |
+
</div>
|
| 99 |
+
</div>
|
| 100 |
+
</div>
|
| 101 |
+
</div>
|
| 102 |
+
</div>
|
| 103 |
+
|
| 104 |
+
<script>
|
| 105 |
+
// Initialize Vanta.js waves background
|
| 106 |
+
VANTA.WAVES({
|
| 107 |
+
el: "#wave-bg",
|
| 108 |
+
color: 0x3b82f6,
|
| 109 |
+
waveHeight: 20,
|
| 110 |
+
shininess: 50,
|
| 111 |
+
waveSpeed: 1,
|
| 112 |
+
zoom: 0.8
|
| 113 |
+
});
|
| 114 |
+
|
| 115 |
+
// Sample chart data (would normally come from API/local storage)
|
| 116 |
+
const currentChart = {
|
| 117 |
+
date: "2023-11-20",
|
| 118 |
+
entries: [
|
| 119 |
+
{ rank: 1, song: "Cruel Summer", artists: "Taylor Swift", peak: 1, weeks: 12, movement: { type: "<>", change: 0 } },
|
| 120 |
+
{ rank: 2, song: "Paint The Town Red", artists: "Doja Cat", peak: 1, weeks: 14, movement: { type: "<>", change: 0 } },
|
| 121 |
+
{ rank: 3, song: "What Was I Made For?", artists: "Billie Eilish", peak: 3, weeks: 5, movement: { type: "+2", change: 2 } },
|
| 122 |
+
{ rank: 4, song: "Dance The Night", artists: "Dua Lipa", peak: 2, weeks: 18, movement: { type: "-1", change: -1 } },
|
| 123 |
+
{ rank: 5, song: "Fast Car", artists: "Luke Combs", peak: 2, weeks: 20, movement: { type: "-1", change: -1 } },
|
| 124 |
+
{ rank: 6, song: "Last Night", artists: "Morgan Wallen", peak: 1, weeks: 32, movement: { type: "<>", change: 0 } },
|
| 125 |
+
{ rank: 7, song: "Barbie World", artists: "Nicki Minaj, Ice Spice", peak: 7, weeks: 8, movement: { type: "<>", change: 0 } },
|
| 126 |
+
{ rank: 8, song: "vampire", artists: "Olivia Rodrigo", peak: 1, weeks: 16, movement: { type: "<>", change: 0 } },
|
| 127 |
+
{ rank: 9, song: "Bad Idea Right?", artists: "Olivia Rodrigo", peak: 7, weeks: 12, movement: { type: "NEW", change: 0 } },
|
| 128 |
+
{ rank: 10, song: "Kill Bill", artists: "SZA", peak: 1, weeks: 42, movement: { type: "<>", change: 0 } }
|
| 129 |
+
],
|
| 130 |
+
dropouts: [
|
| 131 |
+
{ song: "Flowers", artists: "Miley Cyrus", peak: 1, weeks: 35 },
|
| 132 |
+
{ song: "Die For You", artists: "The Weeknd", peak: 1, weeks: 30 }
|
| 133 |
+
]
|
| 134 |
+
};
|
| 135 |
+
|
| 136 |
+
// Populate the chart table
|
| 137 |
+
function populateChart() {
|
| 138 |
+
const tableBody = document.getElementById('chartTableBody');
|
| 139 |
+
tableBody.innerHTML = '';
|
| 140 |
+
|
| 141 |
+
currentChart.entries.forEach(entry => {
|
| 142 |
+
const row = document.createElement('tr');
|
| 143 |
+
row.className = 'hover:bg-gray-50';
|
| 144 |
+
|
| 145 |
+
// Determine movement class
|
| 146 |
+
let movementClass = '';
|
| 147 |
+
let movementSymbol = '';
|
| 148 |
+
|
| 149 |
+
if (entry.movement.type === "+X") {
|
| 150 |
+
movementClass = 'movement-up';
|
| 151 |
+
movementSymbol = `↑${entry.movement.change}`;
|
| 152 |
+
} else if (entry.movement.type === "-X") {
|
| 153 |
+
movementClass = 'movement-down';
|
| 154 |
+
movementSymbol = `↓${Math.abs(entry.movement.change)}`;
|
| 155 |
+
} else if (entry.movement.type === "NEW") {
|
| 156 |
+
movementClass = 'movement-new';
|
| 157 |
+
movementSymbol = 'NEW';
|
| 158 |
+
} else if (entry.movement.type === "RE") {
|
| 159 |
+
movementClass = 'movement-re';
|
| 160 |
+
movementSymbol = 'RE';
|
| 161 |
+
} else {
|
| 162 |
+
movementClass = 'movement-same';
|
| 163 |
+
movementSymbol = '↔';
|
| 164 |
+
}
|
| 165 |
+
|
| 166 |
+
row.innerHTML = `
|
| 167 |
+
<td class="px-6 py-4 whitespace-nowrap text-sm font-medium text-gray-900">${entry.rank}</td>
|
| 168 |
+
<td class="px-6 py-4 whitespace-nowrap text-sm font-medium text-gray-900">${entry.song}</td>
|
| 169 |
+
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500">
|
| 170 |
+
${entry.artists.split(',').map(artist => {
|
| 171 |
+
const trimmed = artist.trim();
|
| 172 |
+
// Only link main artists (simple check for this demo)
|
| 173 |
+
if (trimmed === 'Taylor Swift' || trimmed === 'Doja Cat' || trimmed === 'Billie Eilish' ||
|
| 174 |
+
trimmed === 'Dua Lipa' || trimmed === 'Olivia Rodrigo' || trimmed === 'SZA') {
|
| 175 |
+
return `<a href="artist.html?name=${encodeURIComponent(trimmed)}" class="text-indigo-600 hover:underline">${trimmed}</a>`;
|
| 176 |
+
} else {
|
| 177 |
+
return `<span>${trimmed}</span>`;
|
| 178 |
+
}
|
| 179 |
+
}).join(', ')}
|
| 180 |
+
</td>
|
| 181 |
+
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500">${entry.peak}</td>
|
| 182 |
+
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500">${entry.weeks}</td>
|
| 183 |
+
<td class="px-6 py-4 whitespace-nowrap text-sm font-medium ${movementClass}">${movementSymbol}</td>
|
| 184 |
+
`;
|
| 185 |
+
tableBody.appendChild(row);
|
| 186 |
+
});
|
| 187 |
+
|
| 188 |
+
// Populate dropouts
|
| 189 |
+
const dropoutList = document.getElementById('dropoutList');
|
| 190 |
+
dropoutList.innerHTML = '';
|
| 191 |
+
|
| 192 |
+
currentChart.dropouts.forEach(dropout => {
|
| 193 |
+
const item = document.createElement('div');
|
| 194 |
+
item.className = 'text-sm text-gray-600';
|
| 195 |
+
item.innerHTML = `<span class="font-medium">${dropout.song}</span> by ${dropout.artists} (Peak: ${dropout.peak}, Weeks: ${dropout.weeks})`;
|
| 196 |
+
dropoutList.appendChild(item);
|
| 197 |
+
});
|
| 198 |
+
}
|
| 199 |
+
|
| 200 |
+
// Toggle dropout section
|
| 201 |
+
document.getElementById('dropoutToggle').addEventListener('click', function() {
|
| 202 |
+
const section = document.getElementById('dropoutSection');
|
| 203 |
+
const icon = this.querySelector('i');
|
| 204 |
+
|
| 205 |
+
if (section.classList.contains('hidden')) {
|
| 206 |
+
section.classList.remove('hidden');
|
| 207 |
+
feather.replace();
|
| 208 |
+
icon.setAttribute('data-feather', 'chevron-up');
|
| 209 |
+
} else {
|
| 210 |
+
section.classList.add('hidden');
|
| 211 |
+
feather.replace();
|
| 212 |
+
icon.setAttribute('data-feather', 'chevron-down');
|
| 213 |
+
}
|
| 214 |
+
feather.replace();
|
| 215 |
+
});
|
| 216 |
+
|
| 217 |
+
// New chart modal
|
| 218 |
+
document.getElementById('newChartBtn').addEventListener('click', function() {
|
| 219 |
+
document.getElementById('newChartModal').classList.remove('hidden');
|
| 220 |
+
// Set default date to 7 days after current chart
|
| 221 |
+
const nextDate = new Date(currentChart.date);
|
| 222 |
+
nextDate.setDate(nextDate.getDate() + 7);
|
| 223 |
+
const formattedDate = nextDate.toISOString().split('T')[0];
|
| 224 |
+
document.getElementById('chartDateInput').value = formattedDate;
|
| 225 |
+
});
|
| 226 |
+
|
| 227 |
+
document.getElementById('closeModalBtn').addEventListener('click', function() {
|
| 228 |
+
document.getElementById('newChartModal').classList.add('hidden');
|
| 229 |
+
});
|
| 230 |
+
|
| 231 |
+
document.getElementById('cancelNewChartBtn').addEventListener('click', function() {
|
| 232 |
+
document.getElementById('newChartModal').classList.add('hidden');
|
| 233 |
+
});
|
| 234 |
+
|
| 235 |
+
document.getElementById('confirmNewChartBtn').addEventListener('click', function() {
|
| 236 |
+
const selectedDate = document.getElementById('chartDateInput').value;
|
| 237 |
+
alert(`New chart for ${selectedDate} would be created here (implementation would save data)`);
|
| 238 |
+
document.getElementById('newChartModal').classList.add('hidden');
|
| 239 |
+
});
|
| 240 |
+
|
| 241 |
+
// Initialize on page load
|
| 242 |
+
document.addEventListener('DOMContentLoaded', function() {
|
| 243 |
+
populateChart();
|
| 244 |
+
feather.replace();
|
| 245 |
+
});
|
| 246 |
+
</script>
|
| 247 |
+
</body>
|
| 248 |
</html>
|