<!
DOCTYPE html>
<html lang=”en”>
<head>
<meta charset=”UTF-8” />
<meta name=”viewport” content=”width=device-width, initial-
scale=1.0” />
<title>AGRONOVA GX | Precision Farming</title>
<script src=[Link]
<link rel=”stylesheet” href=[Link]
awesome/6.4.0/css/[Link]>
<script>
[Link] = {
Theme: {
Extend: {
Colors: {
‘gx-teal’: ‘#2dd4bf’,
‘gx-amber’: ‘#f59e0b’,
‘gx-dark’: ‘#0f172a’,
‘gx-green’: ‘#10b981’,
},
Animation: {
‘pulse-slow’: ‘pulse 3s cubic-bezier(0.4, 0, 0.6, 1) infinite’,
</script>
<style>
@import url(‘[Link]
family=Inter:wght@300;400;500;600;700&display=swap’);
Body {
Background: linear-gradient(135deg, #0f172a, #020617);
Min-height: 100vh;
Font-family: ‘Inter’, sans-serif;
.bg-glass {
Background: rgba(15, 23, 42, 0.7);
Backdrop-filter: blur(12px);
Border: 1px solid rgba(45, 212, 191, 0.2);
.update-card {
Transition: all 0.3s ease;
.update-card:hover {
Transform: translateY(-4px);
Box-shadow: 0 10px 15px -3px rgba(13, 148, 136, 0.3);
.[Link] {
Background: #0d9488;
Color: white;
.[Link] {
Position: relative;
}
.[Link]::after {
Content: ‘’;
Position: absolute;
Bottom: 2px;
Left: 50%;
Transform: translateX(-50%);
Width: 4px;
Height: 4px;
Border-radius: 50%;
Background: #f59e0b;
.[Link] {
Color: #2dd4bf;
Background: rgba(45, 212, 191, 0.1);
Border-radius: 0.5rem;
.progress-bar {
Height: 6px;
Border-radius: 3px;
Background: rgba(255, 255, 255, 0.1);
Overflow: hidden;
.progress-fill {
Height: 100%;
Background: linear-gradient(90deg, #2dd4bf, #10b981);
Border-radius: 3px;
Transition: width 0.5s ease;
/* Modal styling (matches the app aesthetic) */
.modal-backdrop {
Position: fixed;
Inset: 0;
Background: rgba(2,6,23,0.6);
Display: flex;
Align-items: center;
Justify-content: center;
z-index: 60;
.modal {
Width: 90%;
Max-width: 420px;
Background: rgba(15,23,42,0.95);
Border: 1px solid rgba(45,212,191,0.12);
Padding: 1rem;
Border-radius: 0.75rem;
Color: #e6eef6;
Box-shadow: 0 10px 30px rgba(2,6,23,0.6);
.modal h3 { font-weight: 600; margin-bottom: 0.5rem; }
.modal label { font-size: 0.85rem; color: #cbd5e1; display: block; margin-
bottom: 0.25rem; }
.modal input[type=”text”] {
Width: 100%;
Padding: 0.5rem 0.6rem;
Background: rgba(255,255,255,0.03);
Border: 1px solid rgba(255,255,255,0.04);
Color: #e6eef6;
Border-radius: 0.5rem;
Margin-bottom: 0.5rem;
.modal .modal-actions {
Display:flex;
Gap:0.5rem;
Justify-content:flex-end;
Margin-top:0.5rem;
.btn {
Padding: 0.45rem 0.75rem;
Border-radius: 0.5rem;
Font-size: 0.9rem;
Cursor: pointer;
Border: none;
.btn-primary {
Background: #2dd4bf;
Color: #082226;
Font-weight: 600;
.btn-ghost {
Background: rgba(255,255,255,0.03);
Color: #e6eef6;
.event-color-dot {
Width: 14px;
Height: 14px;
Border-radius: 999px;
Display:inline-block;
Margin-right:0.5rem;
Vertical-align: middle;
Border: 1px solid rgba(255,255,255,0.05);
.events-list-small { display:flex; gap:0.5rem; flex-wrap:wrap; margin-
top:0.5rem; }
.events-chip {
Background: rgba(255,255,255,0.03);
Padding: 0.25rem 0.5rem;
Border-radius: 0.25rem;
Font-size: 0.8rem;
Display:flex;
Gap:0.4rem;
Align-items:center;
</style>
</head>
<body class=”flex flex-col items-center p-4 text-slate-100 font-sans pb-24”>
<!—Header with GX Logo
<div class=”w-full max-w-md mb-6 bg-glass p-6 rounded-2xl shadow-lg
text-center”>
<div class=”flex flex-col items-center”>
<!—Animated SVG GX Logo
<svg width=”80” height=”80” viewBox=”0 0 120 120” class=”mb-3
animate-pulse-slow”>
<defs>
<linearGradient id=”gxGradient” x1=”0%” y1=”0%” x2=”100%”
y2=”100%”>
<stop offset=”0%” stop-color=”#2dd4bf” />
<stop offset=”100%” stop-color=”#f59e0b” />
</linearGradient>
</defs>
<path d=”M60 15C85 15,105 35,105 60C105 85,85 105,60 105C35
105,15 85,15 60C15 35,35 15,60 15Z”
Fill=”none” stroke=”url(#gxGradient)” stroke-width=”8” stroke-
linejoin=”round”/>
<path d=”M40 40L80 40M50 50L70 50M40 60L80 60M50 70L70 70”
Stroke=”#0f172a” stroke-width=”3” stroke-linecap=”round”/>
</svg>
<h1 class=”text-3xl font-bold”>
<span class=”text-slate-100”>AGRO</span>
<span class=”text-gx-teal”>NOVA</span>
<span class=”text-gx-amber font-mono”>GX</span>
</h1>
<p class=”text-slate-300 text-sm mt-2”>Today: <span id=”current-date”
class=”font-medium”>Loading…</span></p>
</div>
</div>
<!—Real-Time Updates Section
<div class=”w-full max-w-md space-y-4 mb-8”>
<!—Weather Card (live integration)
<div class=”bg-glass update-card p-5 rounded-xl”>
<div class=”flex justify-between items-center mb-3”>
<h2 class=”text-lg font-semibold text-gx-teal flex items-center”>
<I class=”fas fa-cloud-sun mr-2 text-xl”></i>
Weather Update
</h2>
<div class=”flex items-center”>
<span id=”weather-location” class=”text-xs bg-slate-700 px-2 py-1
rounded mr-2”>Detecting…</span>
<I id=”refresh-weather” class=”fas fa-sync-alt text-xs text-slate-400
cursor-pointer hover:text-gx-teal”></i>
</div>
</div>
<div class=”flex items-center justify-between”>
<div class=”flex items-center”>
<div id=”weather-temp” class=”text-5xl font-light”>26°<span
class=”text-3xl”>C</span></div>
<I id=”weather-humidity” class=”fas fa-tint ml-4 text-blue-400 text-xl”
title=”Humidity: 65%”></i>
<I id=”weather-wind” class=”fas fa-wind ml-2 text-blue-300 text-xl”
title=”Wind: 12 km/h”></i>
</div>
<div class=”text-right”>
<p id=”weather-highlow” class=”text-sm”>H: 30° L: 18°</p>
<p id=”weather-desc” class=”text-sm text-slate-300”>Partly
Cloudy</p>
<p class=”text-xs mt-1”>Updated: <span id=”weather-time”>Just
now</span></p>
</div>
</div>
<div class=”mt-4”>
<div class=”progress-bar”>
<div id=”weather-rain” class=”progress-fill” style=”width:
65%”></div>
</div>
<div class=”flex justify-between text-xs mt-1”>
<span>Rain Probability</span>
<span id=”rain-text”>65%</span>
</div>
</div>
</div>
<!—Market Prices Card
<div class=”bg-glass update-card p-5 rounded-xl”>
<div class=”flex justify-between items-center mb-3”>
<h2 class=”text-lg font-semibold text-gx-teal flex items-center”>
<I class=”fas fa-chart-line mr-2 text-xl”></i>
Market Prices
</h2>
<div class=”flex items-center”>
<span class=”text-xs bg-slate-700 px-2 py-1 rounded
mr-2”>Today</span>
<I class=”fas fa-ellipsis-v text-xs text-slate-400 cursor-pointer
hover:text-gx-teal”></i>
</div>
</div>
<div class=”space-y-3”>
<div class=”flex justify-between items-center text-sm”>
<div class=”flex items-center”>
<I class=”fas fa-seedling text-gx-green mr-2”></i>
<span>Maize (50kg)</span>
</div>
<div class=”flex items-center”>
<span class=”font-medium”>ZMW 320</span>
<I class=”fas fa-arrow-up text-xs text-green-400 ml-1”></i>
</div>
</div>
<div class=”flex justify-between items-center text-sm”>
<div class=”flex items-center”>
<I class=”fas fa-leaf text-amber-500 mr-2”></i>
<span>Soybeans (50kg)</span>
</div>
<div class=”flex items-center”>
<span class=”font-medium”>ZMW 480</span>
<I class=”fas fa-arrow-down text-xs text-red-400 ml-1”></i>
</div>
</div>
<div class=”flex justify-between items-center text-sm”>
<div class=”flex items-center”>
<I class=”fas fa-sun text-yellow-400 mr-2”></i>
<span>Sunflower (50kg)</span>
</div>
<div class=”flex items-center”>
<span class=”font-medium”>ZMW 410</span>
<I class=”fas fa-equals text-xs text-gray-400 ml-1”></i>
</div>
</div>
</div>
<p class=”text-xs mt-3 text-slate-300 flex items-center”>
<I class=”fas fa-database mr-1”></i> Source: Zambia National Farmers
Union (Updated hourly)
</p>
</div>
<!—Farming Calendar (dynamic, clickable)
<div class=”bg-glass update-card p-5 rounded-xl”>
<div class=”flex justify-between items-center mb-3”>
<h2 class=”text-lg font-semibold text-gx-teal flex items-center”>
<I class=”fas fa-calendar-alt mr-2 text-xl”></i>
Farming Calendar
</h2>
<div>
<span id=”calendar-month-year” class=”text-xs bg-slate-700 px-2 py-
1 rounded”>Loading…</span>
</div>
</div>
<div class=”grid grid-cols-7 gap-1 mb-2 text-xs”>
<div class=”text-center py-1 text-slate-400”>Sun</div>
<div class=”text-center py-1 text-slate-400”>Mon</div>
<div class=”text-center py-1 text-slate-400”>Tue</div>
<div class=”text-center py-1 text-slate-400”>Wed</div>
<div class=”text-center py-1 text-slate-400”>Thu</div>
<div class=”text-center py-1 text-slate-400”>Fri</div>
<div class=”text-center py-1 text-slate-400”>Sat</div>
</div>
<div id=”calendar-days” class=”grid grid-cols-7 gap-1 text-sm”>
<!—JS injects day cells here
</div>
<div class=”mt-4 space-y-2” id=”calendar-events-list”>
<!—Today / event summaries injected here
</div>
</div>
<!—Soil Health Card (unchanged)
<div class=”bg-glass update-card p-5 rounded-xl”>
<h2 class=”text-lg font-semibold text-gx-teal flex items-center mb-3”>
<I class=”fas fa-seedling mr-2 text-xl”></i>
Soil Health Monitoring
</h2>
<div class=”grid grid-cols-2 gap-4”>
<div class=”bg-slate-800 p-3 rounded-lg”>
<div class=”flex justify-between items-center mb-1”>
<span class=”text-xs text-slate-300”>Moisture</span>
<span class=”text-xs font-medium”>62%</span>
</div>
<div class=”progress-bar”>
<div class=”progress-fill” style=”width: 62%”></div>
</div>
</div>
<div class=”bg-slate-800 p-3 rounded-lg”>
<div class=”flex justify-between items-center mb-1”>
<span class=”text-xs text-slate-300”>Nitrogen</span>
<span class=”text-xs font-medium”>45%</span>
</div>
<div class=”progress-bar”>
<div class=”progress-fill” style=”width: 45%”></div>
</div>
</div>
<div class=”bg-slate-800 p-3 rounded-lg”>
<div class=”flex justify-between items-center mb-1”>
<span class=”text-xs text-slate-300”>pH Level</span>
<span class=”text-xs font-medium”>6.8</span>
</div>
<div class=”progress-bar”>
<div class=”progress-fill” style=”width: 68%”></div>
</div>
</div>
<div class=”bg-slate-800 p-3 rounded-lg”>
<div class=”flex justify-between items-center mb-1”>
<span class=”text-xs text-slate-300”>Organic Matter</span>
<span class=”text-xs font-medium”>3.2%</span>
</div>
<div class=”progress-bar”>
<div class=”progress-fill” style=”width: 32%”></div>
</div>
</div>
</div>
<button class=”w-full mt-4 bg-gx-teal text-gx-dark py-2 rounded-lg text-
sm font-medium hover:bg-opacity-90 transition”>
<I class=”fas fa-plus-circle mr-1”></i> Add Soil Test
</button>
</div>
</div>
<!—Bottom Navigation with GX Badge (unchanged)
<div class=”fixed bottom-0 left-0 right-0 bg-slate-800 py-3 px-4 shadow-
2xl”>
<div class=”relative flex justify-around max-w-md mx-auto”>
<!—GX Edition Badge
<div class=”absolute -top-3 left-1/2 transform -translate-x-1/2 bg-
gradient-to-r from-gx-teal to-gx-green text-white text-xs px-3 py-1 rounded-
full font-medium shadow-lg”>
<I class=”fas fa-bolt mr-1”></i> GX EDITION
</div>
<!—Nav Items
<a href=”#” class=”flex flex-col items-center text-gx-teal p-2 rounded-lg
w-16 nav-item active”>
<I class=”fas fa-home text-xl”></i>
<span class=”text-xs mt-1”>Home</span>
</a>
<a href=”[Link]” class=”flex flex-col items-center text-slate-300 p-2
rounded-lg w-16 nav-item”>
<I class=”fas fa-bug text-xl”></i>
<span class=”text-xs mt-1”>Pests</span>
</a>
<a href=”[Link]” class=”flex flex-col items-center text-slate-300 p-
2 rounded-lg w-16 nav-item”>
<I class=”fas fa-shopping-basket text-xl”></i>
<span class=”text-xs mt-1”>Market</span>
</a>
<a href=”[Link]” class=”flex flex-col items-center text-slate-
300 p-2 rounded-lg w-16 nav-item”>
<I class=”fas fa-graduation-cap text-xl”></i>
<span class=”text-xs mt-1”>Learn</span>
</a>
<a href=”[Link]” class=”flex flex-col items-center text-slate-300 p-
2 rounded-lg w-16 nav-item”>
<I class=”fas fa-user text-xl”></i>
<span class=”text-xs mt-1”>Profile</span>
</a>
</div>
</div>
<!—Modal container (hidden by default)
<div id=”modal-root” style=”display:none;”></div>
<script>
// Maintain original date display
Const options = { weekday: ‘long’, year: ‘numeric’, month: ‘long’, day:
‘numeric’ };
[Link](‘current-date’).textContent = new
Date().toLocaleDateString(‘en-ZM’, options);
// ---------------------------
// Weather integration
// ---------------------------
Const apiKey = “719ac96f5075d74b1d7955398c833b31”;
Let lastKnownCoords = null;
Let refreshIntervalId = null;
Function updateWeatherTime() {
Const now = new Date();
Const el = [Link](‘weather-time’);
If (el) [Link] = `${[Link]()}:$
{[Link]().toString().padStart(2, ‘0’)}`;
Async function fetchWeather(lat, lon) {
Try {
lastKnownCoords = { lat, lon };
const res = await
fetch(`[Link]
{lon}&units=metric&appid=${apiKey}`);
const data = await [Link]();
[Link](‘weather-temp’).innerHTML = `$
{[Link]([Link])}°<span class=”text-3xl”>C</span>`;
[Link](‘weather-location’).textContent = `$
{[Link]}, ${[Link]}`;
[Link](‘weather-humidity’).setAttribute(‘title’,
`Humidity: ${[Link]}%`);
[Link](‘weather-wind’).setAttribute(‘title’, `Wind: $
{[Link]} km/h`);
[Link](‘weather-highlow’).textContent = `H: $
{[Link]([Link].temp_max)}° L: $
{[Link]([Link].temp_min)}°`;
[Link](‘weather-desc’).textContent =
[Link][0].[Link](/\b\w/g, c => [Link]());
let rainProb = 0;
if ([Link] && [Link][“1h”]) {
rainProb = [Link](100, [Link]([Link][“1h”] * 20));
[Link](‘weather-rain’).[Link] = `${rainProb}
%`;
[Link](‘rain-text’).textContent = `${rainProb}%`;
updateWeatherTime();
} catch (err) {
[Link](‘Weather fetch error:’, err);
}
Async function detectLocationByIPAndFetch() {
Try {
Const r = await fetch(‘[Link]
Const loc = await [Link]();
If (loc && [Link] && [Link]) {
fetchWeather([Link], [Link]);
} else {
fetchWeather(-15.3875, 28.3228); // Lusaka fallback
} catch (err) {
[Link](‘IP detect failed, fallback to Lusaka’, err);
fetchWeather(-15.3875, 28.3228);
// Kick off quick IP detection
detectLocationByIPAndFetch();
if ([Link]) {
[Link](
pos => {
// fetch and set interval at 60s using the more accurate coords
fetchWeather([Link], [Link]);
if (refreshIntervalId) clearInterval(refreshIntervalId);
refreshIntervalId = setInterval(() => {
if (lastKnownCoords) fetchWeather([Link],
[Link]);
else detectLocationByIPAndFetch();
}, 60000);
},
Err => {
[Link](‘Geolocation denied/unavailable:’, err);
If (refreshIntervalId) clearInterval(refreshIntervalId);
refreshIntervalId = setInterval(() => detectLocationByIPAndFetch(),
60000);
);
} else {
refreshIntervalId = setInterval(() => detectLocationByIPAndFetch(),
60000);
[Link](‘refresh-weather’).addEventListener(‘click’, ()
=> {
If (lastKnownCoords) fetchWeather([Link],
[Link]);
Else detectLocationByIPAndFetch();
});
// ---------------------------
// Dynamic calendar + editable events (stored in localStorage)
// ---------------------------
(function calendarModule() {
Const calendarDaysEl = [Link](‘calendar-days’);
Const monthYearEl = [Link](‘calendar-month-year’);
Const eventsListEl = [Link](‘calendar-events-list’);
Const modalRoot = [Link](‘modal-root’);
// Annual events (repeat each year); month numbers are 1-12
Const defaultAnnualEvents = [
{ id: ‘soil-prep’, title: ‘Soil Preparation Day’, month: 8, day: 10, color:
‘#14b8a6’ }, // Aug 10
{ id: ‘maize-window’, title: ‘Maize Planting Window’, month: 7, dayStart:
10, dayEnd: 15, color: ‘#f59e0b’ } // Jul 10-15
];
// Storage key
Const STORAGE_KEY = ‘agronova_calendar_events_v1’;
// Load stored events, merge with defaults if not present
Function loadEvents() {
Const raw = [Link](STORAGE_KEY);
Let stored = raw ? [Link](raw) : {};
// stored: { “YYYY-MM-DD”: [{id, title, color}] }
// ensure defaults exist for current year (annual events)
Return stored || {};
Function saveEvents(obj) {
[Link](STORAGE_KEY, [Link](obj));
// Helper: date key YYYY-MM-DD
Function dateKey(year, monthIndex, day) {
Const mm = String(monthIndex + 1).padStart(2, ‘0’);
Const dd = String(day).padStart(2, ‘0’);
Return `${year}-${mm}-${dd}`;
// Add event to a specific date
Function addEventToDate(year, monthIndex, day, eventObj) {
Const key = dateKey(year, monthIndex, day);
Const evs = events[key] || [];
[Link](eventObj);
Events[key] = evs;
saveEvents(events);
// Remove a single event by index from date
Function removeEventFromDate(year, monthIndex, day, index) {
Const key = dateKey(year, monthIndex, day);
If (!events[key]) return;
Events[key].splice(index, 1);
If (events[key].length === 0) delete events[key];
saveEvents(events);
}
// Merge default annual events into UI without overwriting user changes.
// Defaults are not saved to localStorage as fixed entries; we show them
if no user event exists.
Function getEventsForDate(year, monthIndex, day) {
Const key = dateKey(year, monthIndex, day);
Const out = [];
// user events
If (events[key]) {
For (const e of events[key]) [Link]€;
// defaults if applicable
For (const def of defaultAnnualEvents) {
If ([Link] && [Link] – 1 === monthIndex && [Link] === day) {
// ensure not duplicated if user created identical event (we don’t de-
dup aggressively)
[Link]({ id: [Link], title: [Link], color: [Link], isDefault: true });
} else if ([Link] && [Link] && [Link] – 1 ===
monthIndex && day >= [Link] && day <= [Link]) {
[Link]({ id: [Link], title: [Link], color: [Link], isDefault: true });
Return out;
// Render month function
Function renderMonth(year, monthIndex) {
[Link] = ‘’;
[Link] = ‘’;
const firstDay = new Date(year, monthIndex, 1);
const startWeekday = [Link](); // 0-6
const daysInMonth = new Date(year, monthIndex + 1, 0).getDate();
// leading prev-month muted days
For (let I = 0; I < startWeekday; i++) {
Const dayNum = new Date(year, monthIndex, 0).getDate() –
(startWeekday – 1) + I;
Const div = [Link](‘div’);
[Link] = ‘text-center py-1 text-slate-400’;
[Link] = dayNum;
[Link](div);
Const today = new Date();
Const tY = [Link](), tM = [Link](), tD =
[Link]();
For (let d = 1; d <= daysInMonth; d++) {
Const div = [Link](‘div’);
[Link] = ‘text-center py-1 calendar-day’;
[Link] = d;
const evs = getEventsForDate(year, monthIndex, d);
if ([Link] > 0) [Link](‘event’);
if (year === tY && monthIndex === tM && d === tD) {
[Link](‘active’);
// small events chips display inside day cell (small)
If ([Link] > 0) {
Const chips = [Link](‘div’);
[Link] = ‘events-list-small mt-1 justify-center’;
[Link](0, 2).forEach(ev => {
Const chip = [Link](‘div’);
[Link] = ‘events-chip’;
Const dot = [Link](‘span’);
[Link] = ‘event-color-dot’;
[Link] = [Link] || ‘#f59e0b’;
[Link](dot);
Const t = [Link](‘span’);
[Link] = [Link] > 12 ? [Link](0, 11) + ‘…’ :
[Link];
[Link](t);
[Link](chip);
});
[Link](chips);
// click handler to open modal for this day
(function(year, monthIndex, d) {
[Link](‘click’, () => openDayModal(year, monthIndex,
d));
})(new Date(year, monthIndex).getFullYear(), monthIndex, d);
[Link](div);
// trailing next-month muted days fill to multiple of 7
Const totalCells = startWeekday + daysInMonth;
Const nextFill = (7 – (totalCells % 7)) % 7;
For (let n = 1; n <= nextFill; n++) {
Const div = [Link](‘div’);
[Link] = ‘text-center py-1 text-slate-400’;
[Link] = n;
[Link](div);
// update header
Const monthNames = [
‘January’,’February’,’March’,’April’,’May’,’June’,
‘July’,’August’,’September’,’October’,’November’,’December’
];
[Link] = `${monthNames[monthIndex]} ${year}`;
// Build event summaries for the month (includes defaults & user
events)
buildEventsSummary(year, monthIndex);
}
Function buildEventsSummary(year, monthIndex) {
[Link] = ‘’;
const monthNames = [
‘January’,’February’,’March’,’April’,’May’,’June’,
‘July’,’August’,’September’,’October’,’November’,’December’
];
// Find default events in this month
[Link](ev => {
if ([Link] && [Link] – 1 === monthIndex) {
[Link](createEventSummary([Link], `$
{monthNames[[Link]-1]} ${[Link]}`, [Link]));
} else if ([Link] && [Link] && [Link] – 1 ===
monthIndex) {
[Link](createEventSummary([Link], `$
{monthNames[[Link]-1]} ${[Link]}-${[Link]}`, [Link]));
});
// Also show user events that fall in this month
[Link](events).forEach(key => {
// key format YYYY-MM-DD
Const [y, m, d] = [Link](‘-‘).map(Number);
If (y === year && (m – 1) === monthIndex) {
Const evs = events[key];
[Link](ev => {
[Link](createEventSummary([Link], `${year}-$
{String(m).padStart(2,’0’)}-${String(d).padStart(2,’0’)}`, [Link]));
});
});
// Show today’s box if today’s date has any events (default or user)
Const now = new Date();
Const todays = getEventsForDate([Link](), [Link](),
[Link]());
If ([Link] > 0) {
Const todayBox = createTodayBox(todays);
[Link](todayBox, [Link]);
} else {
// Original UI had a “Today: Soil Preparation Day” static message — we
display today’s date as neutral
Const neutral = [Link](‘div’);
[Link] = ‘flex items-center text-sm p-2 bg-slate-800
rounded-lg’;
Const dot = [Link](‘div’);
[Link] = ‘w-2 h-2 bg-teal-500 rounded-full mr-2’;
[Link](dot);
Const span = [Link](‘span’);
[Link] = `Today: ${[Link](undefined,
{ month: ‘long’, day: ‘numeric’ })}`;
[Link](span);
Const icon = [Link](‘I’);
[Link] = ‘fas fa-bell ml-auto text-xs text-gx-amber’;
[Link](icon);
[Link](neutral, [Link]);
Function createEventSummary(title, subtitle, color) {
Const wrapper = [Link](‘div’);
[Link] = ‘flex items-center text-sm p-2 bg-slate-800
rounded-lg’;
Const dot = [Link](‘div’);
[Link] = ‘w-2 h-2 rounded-full mr-2’;
[Link] = color || ‘#14b8a6’;
[Link](dot);
Const span = [Link](‘span’);
[Link] = `${title} — ${subtitle}`;
[Link](span);
Const icon = [Link](‘I’);
[Link] = ‘fas fa-clock ml-auto text-xs text-slate-400’;
[Link](icon);
Return wrapper;
Function createTodayBox(evList) {
Const container = [Link](‘div’);
[Link] = ‘flex items-center text-sm p-2 bg-slate-800
rounded-lg’;
Const dot = [Link](‘div’);
[Link] = ‘w-2 h-2 bg-teal-500 rounded-full mr-2’;
[Link](dot);
Const span = [Link](‘span’);
[Link] = [Link](e => [Link]).join(‘, ‘);
[Link](span);
Const bell = [Link](‘I’);
[Link] = ‘fas fa-bell ml-auto text-xs text-gx-amber’;
[Link](bell);
Return container;
// Modal utilities
Function openModal(contentHtml) {
[Link] = ‘’;
const backdrop = [Link](‘div’);
[Link] = ‘modal-backdrop’;
const modal = [Link](‘div’);
[Link] = ‘modal’;
[Link] = contentHtml;
[Link](modal);
[Link](backdrop);
[Link] = ‘block’;
// close on backdrop click (outside modal)
[Link](‘click’, € => {
If ([Link] === backdrop) closeModal();
});
}
Function closeModal() {
[Link] = ‘none’;
[Link] = ‘’;
// Modal for a day: list events (if any) and form to add new event
Function openDayModal(year, monthIndex, day) {
Const key = dateKey(year, monthIndex, day);
Const currentEvents = events[key] ? events[key].slice() : [];
// Include default annual events for UI display, but careful not to allow
removing default events unless user removes by adding a custom event to
override (we will allow user to remove visible default by explicitly “Hide
default” action)
Const defaults = [];
[Link](def => {
if ([Link] && [Link] – 1 === monthIndex && [Link] === day)
[Link]({ title: [Link], color: [Link], isDefault: true, defId: [Link] });
if ([Link] && [Link] && [Link] – 1 === monthIndex &&
day >= [Link] && day <= [Link]) [Link]({ title: [Link],
color: [Link], isDefault: true, defId: [Link] });
});
// Build modal content
Let html = `<h3>Events for
${monthIndex+1}/${day}/${year}</h3>`;
Html += `<div id=”modal-existing-events” style=”max-height:200px;
overflow:auto; margin-top:0.5rem;”>`;
If ([Link] === 0 && [Link] === 0) {
Html += `<div style=”color:#cbd5e1; font-size:0.9rem;”>No events
for this day.</div>`;
} else {
// user events
[Link]((ev, idx) => {
html += `<div class=”flex items-center justify-between”
style=”margin-top:0.35rem;”>` +
`<div style=”display:flex;align-items:center;”><span
class=”event-color-dot” style=”background:${[Link] ||
‘#f59e0b’}”></span><span
style=”font-size:0.95rem”>${[Link]}</span></div>` +
`<div><button data-ev-index=”${idx}” class=”btn btn-ghost
modal-remove-btn” style=”padding:0.25rem
0.4rem;”>Remove</button></div>` +
`</div>`;
});
// default events (showed, allow “hide default” to insert a marker in
localStorage that hides it)
[Link]((dev, idx) => {
// Check if default is hidden in storage (we implement a hideDefault
map stored separately)
Html += `<div class=”flex items-center justify-between”
style=”margin-top:0.35rem;”>` +
`<div style=”display:flex;align-items:center;”><span
class=”event-color-dot” style=”background:${[Link]}”></span><span
style=”font-size:0.95rem”>${[Link]} (default)</span></div>` +
`<div><button data-def-id=”${[Link]}” class=”btn btn-
ghost modal-hide-default-btn” style=”padding:0.25rem
0.4rem;”>Hide</button></div>` +
`</div>`;
});
}
Html += `</div>`;
// add new event form
Html += `<div style=”margin-top:0.6rem;”>` +
`<label>Event title</label>` +
`<input id=”modal-event-title” type=”text” placeholder=”e.g. Soil
test” />` +
`<label>Color</label>` +
`<div style=”display:flex; gap:0.5rem; margin-top:0.25rem;”>` +
`<button data-color=”#14b8a6” class=”color-btn btn btn-ghost”
title=”Teal”><span class=”event-color-dot”
style=”background:#14b8a6”></span></button>` +
`<button data-color=”#f59e0b” class=”color-btn btn btn-ghost”
title=”Amber”><span class=”event-color-dot”
style=”background:#f59e0b”></span></button>` +
`<button data-color=”#60a5fa” class=”color-btn btn btn-ghost”
title=”Blue”><span class=”event-color-dot”
style=”background:#60a5fa”></span></button>` +
`<button data-color=”#ef4444” class=”color-btn btn btn-ghost”
title=”Red”><span class=”event-color-dot”
style=”background:#ef4444”></span></button>` +
`</div>` +
`</div>`;
Html += `<div class=”modal-actions” style=”margin-top:0.6rem;”>` +
`<button id=”modal-cancel” class=”btn
btn-ghost”>Cancel</button>` +
`<button id=”modal-add” class=”btn btn-primary”>Add
Event</button>` +
`</div>`;
openModal(html);
// Wire up modal buttons
[Link](‘modal-cancel’).addEventListener(‘click’,
closeModal);
// color selection
Let chosenColor = ‘#14b8a6’;
[Link](‘.color-btn’).forEach(btn => {
[Link](‘click’, € => {
[Link]();
chosenColor = [Link](‘data-color’);
// highlight selected visually (small)
[Link](‘.color-btn’).forEach(b => [Link]
= ‘0.6’);
[Link] = ‘1’;
});
});
// add event
[Link](‘modal-add’).addEventListener(‘click’, () =>
{
Const titleInput = [Link](‘#modal-event-title’);
Const title = [Link] && [Link]();
If (!title) {
[Link]();
return;
// create event object
Const evObj = { id: ‘u-‘ + [Link](), title, color: chosenColor };
addEventToDate(year, monthIndex, day, evObj);
closeModal();
// re-render month to show the new event
renderMonth(year, monthIndex);
});
// remove user events buttons
[Link](‘.modal-remove-btn’).forEach(b => {
[Link](‘click’, () => {
const idx = parseInt([Link](‘data-ev-index’), 10);
removeEventFromDate(year, monthIndex, day, idx);
closeModal();
renderMonth(year, monthIndex);
});
});
// hide default event (store hidden defaults map)
[Link](‘.modal-hide-default-btn’).forEach(b => {
[Link](‘click’, () => {
const defId = [Link](‘data-def-id’);
// Mark default as hidden for this date by storing a stub event with
special flag
// Implementation: savedHiddenDefaults is an object keyed by defId
and keyed by date
Const hiddenKey = ‘agronova_hidden_defaults_v1’;
Const raw = [Link](hiddenKey);
Let hidden = raw ? [Link](raw) : {};
Const date = dateKey(year, monthIndex, day);
Hidden[date] = hidden[date] || [];
If (!hidden[date].includes(defId)) hidden[date].push(defId);
[Link](hiddenKey, [Link](hidden));
closeModal();
renderMonth(year, monthIndex);
});
});
// global events store
Const events = loadEvents();
// tweak getEventsForDate to respect hidden defaults
Const hiddenDefaultsKey = ‘agronova_hidden_defaults_v1’;
Function getHiddenDefaultsForDate(year, monthIndex, day) {
Const raw = [Link](hiddenDefaultsKey);
If (!raw) return [];
Const hidden = [Link](raw);
Const k = dateKey(year, monthIndex, day);
Return hidden[k] || [];
}
// override previous getEventsForDate (redefine)
Function getEventsForDate(year, monthIndex, day) {
Const key = dateKey(year, monthIndex, day);
Const out = [];
// user events
If (events[key]) {
For (const e of events[key]) [Link]€;
// defaults if applicable and not hidden
Const hidden = getHiddenDefaultsForDate(year, monthIndex, day);
For (const def of defaultAnnualEvents) {
If ([Link] && [Link] – 1 === monthIndex && [Link] === day) {
If () [Link]({ id: [Link], title: [Link], color:
[Link], isDefault: true });
} else if ([Link] && [Link] && [Link] – 1 ===
monthIndex && day >= [Link] && day <= [Link]) {
If () [Link]({ id: [Link], title: [Link], color:
[Link], isDefault: true });
Return out;
// expose some functions to outer scope if needed (not necessary)
// initialize to current month
Const now = new Date();
renderMonth([Link](), [Link]());
// allow navigation later (not requested, but keep hooks if wanted)
// e.g., you could add prev/next month buttons and call renderMonth
accordingly
})();
// keep original click effect for update-card
[Link](‘.update-card’).forEach(card => {
[Link](‘click’, function() {
[Link] = ‘translateY(-2px)’;
setTimeout(() => {
[Link] = ‘’;
}, 200);
});
});
// Stop pulse animation after load (unchanged)
setTimeout(() => {
[Link](‘svg’).[Link](‘animate-pulse-slow’);
}, 2000);
</script>
</body>
</html>