infinite-agents-public/vaccine_timeseries/vaccine_timeseries_3_covid/index.html

874 lines
35 KiB
HTML

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>COVID-19 Vaccination Timeline - Global Equity Analysis</title>
<!-- Mapbox GL JS -->
<script src='https://api.mapbox.com/mapbox-gl-js/v3.0.1/mapbox-gl.js'></script>
<link href='https://api.mapbox.com/mapbox-gl-js/v3.0.1/mapbox-gl.css' rel='stylesheet' />
<!-- Chart.js for dual-axis charts -->
<script src="https://cdn.jsdelivr.net/npm/chart.js@4.4.0/dist/chart.umd.min.js"></script>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
background: linear-gradient(135deg, #0a0e1a 0%, #1a1f35 100%);
color: #e5e7eb;
overflow: hidden;
}
#map {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
}
/* Timeline Control Panel */
.timeline-control {
position: absolute;
top: 20px;
left: 20px;
background: rgba(10, 14, 26, 0.95);
backdrop-filter: blur(20px);
border: 1px solid rgba(255, 255, 255, 0.1);
border-radius: 16px;
padding: 24px;
width: 400px;
box-shadow: 0 20px 60px rgba(0, 0, 0, 0.5);
z-index: 1000;
}
.timeline-control .header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 20px;
padding-bottom: 16px;
border-bottom: 1px solid rgba(255, 255, 255, 0.1);
}
.timeline-control h3 {
font-size: 18px;
font-weight: 600;
color: #f3f4f6;
margin: 0;
}
.year-display {
font-size: 14px;
color: #9ca3af;
background: rgba(59, 130, 246, 0.2);
padding: 6px 14px;
border-radius: 8px;
border: 1px solid rgba(59, 130, 246, 0.3);
}
.year-display span {
font-weight: 700;
color: #60a5fa;
font-size: 16px;
}
/* Slider */
#year-slider {
width: 100%;
height: 8px;
background: linear-gradient(to right,
#dc2626 0%,
#f59e0b 33%,
#10b981 66%,
#3b82f6 100%
);
border-radius: 4px;
outline: none;
-webkit-appearance: none;
margin-bottom: 20px;
cursor: pointer;
}
#year-slider::-webkit-slider-thumb {
-webkit-appearance: none;
appearance: none;
width: 24px;
height: 24px;
background: white;
cursor: pointer;
border-radius: 50%;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.4);
border: 3px solid #3b82f6;
}
#year-slider::-moz-range-thumb {
width: 24px;
height: 24px;
background: white;
cursor: pointer;
border-radius: 50%;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.4);
border: 3px solid #3b82f6;
}
/* Controls */
.controls {
display: flex;
gap: 10px;
align-items: center;
margin-bottom: 20px;
}
.btn {
background: linear-gradient(135deg, #3b82f6 0%, #2563eb 100%);
color: white;
border: none;
padding: 10px 20px;
border-radius: 8px;
font-size: 14px;
font-weight: 600;
cursor: pointer;
transition: all 0.3s ease;
box-shadow: 0 4px 12px rgba(59, 130, 246, 0.3);
}
.btn:hover {
background: linear-gradient(135deg, #2563eb 0%, #1d4ed8 100%);
transform: translateY(-2px);
box-shadow: 0 6px 16px rgba(59, 130, 246, 0.4);
}
.btn:active {
transform: translateY(0);
}
.btn.playing {
background: linear-gradient(135deg, #ef4444 0%, #dc2626 100%);
}
label {
display: flex;
align-items: center;
gap: 6px;
font-size: 13px;
color: #d1d5db;
cursor: pointer;
}
input[type="checkbox"] {
width: 16px;
height: 16px;
cursor: pointer;
}
/* Global Statistics */
.stats {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 12px;
}
.stat {
background: rgba(255, 255, 255, 0.05);
padding: 12px;
border-radius: 8px;
border: 1px solid rgba(255, 255, 255, 0.1);
}
.stat .label {
display: block;
font-size: 11px;
color: #9ca3af;
text-transform: uppercase;
letter-spacing: 0.5px;
margin-bottom: 4px;
}
.stat .value {
display: block;
font-size: 18px;
font-weight: 700;
color: #f3f4f6;
}
/* Dual-Axis Popup Styling */
.mapboxgl-popup-content {
background: rgba(10, 14, 26, 0.98);
backdrop-filter: blur(20px);
border: 1px solid rgba(255, 255, 255, 0.15);
border-radius: 12px;
padding: 20px;
min-width: 460px;
box-shadow: 0 20px 60px rgba(0, 0, 0, 0.6);
}
.mapboxgl-popup-tip {
border-top-color: rgba(10, 14, 26, 0.98) !important;
}
.advanced-popup h3 {
margin: 0 0 16px 0;
font-size: 16px;
font-weight: 600;
color: #f3f4f6;
border-bottom: 2px solid rgba(59, 130, 246, 0.3);
padding-bottom: 8px;
}
.advanced-popup canvas {
border-radius: 8px;
background: rgba(0, 0, 0, 0.2);
}
/* Legend */
.legend {
position: absolute;
bottom: 30px;
right: 20px;
background: rgba(10, 14, 26, 0.95);
backdrop-filter: blur(20px);
border: 1px solid rgba(255, 255, 255, 0.1);
border-radius: 12px;
padding: 20px;
z-index: 1000;
min-width: 220px;
}
.legend h4 {
margin: 0 0 12px 0;
font-size: 13px;
color: #9ca3af;
text-transform: uppercase;
letter-spacing: 0.5px;
}
.legend-gradient {
height: 20px;
border-radius: 6px;
margin-bottom: 8px;
border: 1px solid rgba(255, 255, 255, 0.2);
}
.legend-labels {
display: flex;
justify-content: space-between;
font-size: 11px;
color: #78909c;
}
/* Info Panel */
.info-panel {
position: absolute;
top: 20px;
right: 20px;
background: rgba(10, 14, 26, 0.95);
backdrop-filter: blur(20px);
border: 1px solid rgba(255, 255, 255, 0.1);
border-radius: 12px;
padding: 20px;
max-width: 320px;
z-index: 1000;
}
.info-panel h4 {
margin: 0 0 12px 0;
font-size: 14px;
font-weight: 600;
color: #f3f4f6;
}
.info-panel p {
margin: 0 0 8px 0;
font-size: 12px;
line-height: 1.6;
color: #d1d5db;
}
.info-panel .highlight {
color: #60a5fa;
font-weight: 600;
}
</style>
</head>
<body>
<div id="map"></div>
<!-- Timeline Control -->
<div class="timeline-control">
<div class="header">
<h3>COVID-19 Timeline</h3>
<div class="year-display">Year: <span id="current-year">2023</span></div>
</div>
<input id="year-slider" type="range" min="0" max="3" step="1" value="3">
<div class="controls">
<button id="play-pause" class="btn">▶ Play</button>
<button id="reset" class="btn">Reset</button>
<label>
<input type="checkbox" id="loop-checkbox" checked> Loop
</label>
</div>
<div class="stats">
<div class="stat">
<span class="label">Global Coverage</span>
<span class="value" id="global-coverage">--</span>
</div>
<div class="stat">
<span class="label">Total Cases</span>
<span class="value" id="total-cases">--</span>
</div>
</div>
</div>
<!-- Legend -->
<div class="legend">
<h4>Vaccination Coverage (%)</h4>
<div class="legend-gradient" style="background: linear-gradient(to right, #dc2626, #f59e0b, #10b981, #3b82f6);"></div>
<div class="legend-labels">
<span>0%</span>
<span>50%</span>
<span>100%</span>
</div>
</div>
<!-- Info Panel -->
<div class="info-panel">
<h4>COVID-19 Vaccination Equity (2020-2023)</h4>
<p>This visualization reveals the <span class="highlight">stark inequities</span> in global COVID-19 vaccine distribution.</p>
<p>High-income nations achieved <span class="highlight">75%+ coverage</span> by 2022, while low-income countries struggled to reach <span class="highlight">25%</span>.</p>
<p><strong>Hover over countries</strong> to see dual-axis charts comparing vaccination coverage against COVID-19 cases.</p>
</div>
<script type="module">
// Import shared configuration
import { MAPBOX_CONFIG } from '../../mapbox_test/shared/mapbox-config.js';
import { LayerFactory } from '../../mapbox_test/shared/layer-factory.js';
// Apply Mapbox token
MAPBOX_CONFIG.applyToken();
// Timeline configuration
const YEARS = [2020, 2021, 2022, 2023];
let currentYearIndex = 3; // Start at 2023
let isPlaying = false;
let playInterval = null;
// Chart management
let activeChart = null;
let activePopup = null;
let pinnedPopups = []; // Track multiple pinned popups
let chartCounter = 0;
let popupTimeout = null;
// Initialize map
const map = new mapboxgl.Map({
container: 'map',
style: 'mapbox://styles/mapbox/dark-v11',
projection: 'globe',
zoom: 1.5,
center: [20, 20],
pitch: 0
});
// Generate realistic COVID-19 vaccination data
function generateCovidData() {
const countries = [
// High-income countries - rapid vaccination
{ name: 'United States', coords: [-95, 37], income: 'high', region: 'Americas', pop: 331000000 },
{ name: 'United Kingdom', coords: [-3, 54], income: 'high', region: 'Europe', pop: 67000000 },
{ name: 'Germany', coords: [10, 51], income: 'high', region: 'Europe', pop: 83000000 },
{ name: 'France', coords: [2, 46], income: 'high', region: 'Europe', pop: 67000000 },
{ name: 'Japan', coords: [138, 36], income: 'high', region: 'Asia', pop: 126000000 },
{ name: 'Canada', coords: [-106, 56], income: 'high', region: 'Americas', pop: 38000000 },
{ name: 'Australia', coords: [133, -27], income: 'high', region: 'Oceania', pop: 25000000 },
{ name: 'South Korea', coords: [128, 37], income: 'high', region: 'Asia', pop: 52000000 },
{ name: 'Israel', coords: [35, 31], income: 'high', region: 'Middle East', pop: 9000000 },
{ name: 'Singapore', coords: [104, 1], income: 'high', region: 'Asia', pop: 5800000 },
// Upper-middle income - moderate vaccination
{ name: 'Brazil', coords: [-47, -14], income: 'upper-middle', region: 'Americas', pop: 212000000 },
{ name: 'China', coords: [105, 35], income: 'upper-middle', region: 'Asia', pop: 1400000000 },
{ name: 'Russia', coords: [105, 61], income: 'upper-middle', region: 'Europe', pop: 144000000 },
{ name: 'Mexico', coords: [-102, 23], income: 'upper-middle', region: 'Americas', pop: 128000000 },
{ name: 'Turkey', coords: [35, 39], income: 'upper-middle', region: 'Middle East', pop: 84000000 },
{ name: 'South Africa', coords: [25, -29], income: 'upper-middle', region: 'Africa', pop: 60000000 },
{ name: 'Argentina', coords: [-63, -38], income: 'upper-middle', region: 'Americas', pop: 45000000 },
{ name: 'Thailand', coords: [101, 15], income: 'upper-middle', region: 'Asia', pop: 70000000 },
// Lower-middle income - slower vaccination
{ name: 'India', coords: [78, 20], income: 'lower-middle', region: 'Asia', pop: 1380000000 },
{ name: 'Indonesia', coords: [113, -2], income: 'lower-middle', region: 'Asia', pop: 273000000 },
{ name: 'Egypt', coords: [30, 26], income: 'lower-middle', region: 'Africa', pop: 102000000 },
{ name: 'Pakistan', coords: [69, 30], income: 'lower-middle', region: 'Asia', pop: 220000000 },
{ name: 'Bangladesh', coords: [90, 24], income: 'lower-middle', region: 'Asia', pop: 164000000 },
{ name: 'Nigeria', coords: [8, 9], income: 'lower-middle', region: 'Africa', pop: 206000000 },
{ name: 'Philippines', coords: [123, 13], income: 'lower-middle', region: 'Asia', pop: 109000000 },
{ name: 'Vietnam', coords: [108, 14], income: 'lower-middle', region: 'Asia', pop: 97000000 },
{ name: 'Kenya', coords: [37, -1], income: 'lower-middle', region: 'Africa', pop: 53000000 },
// Low-income - limited vaccination (COVAX challenges)
{ name: 'Ethiopia', coords: [40, 8], income: 'low', region: 'Africa', pop: 115000000 },
{ name: 'Tanzania', coords: [35, -6], income: 'low', region: 'Africa', pop: 60000000 },
{ name: 'Uganda', coords: [32, 1], income: 'low', region: 'Africa', pop: 46000000 },
{ name: 'Mozambique', coords: [35, -18], income: 'low', region: 'Africa', pop: 31000000 },
{ name: 'Madagascar', coords: [47, -19], income: 'low', region: 'Africa', pop: 28000000 },
{ name: 'Malawi', coords: [34, -13], income: 'low', region: 'Africa', pop: 19000000 },
{ name: 'Chad', coords: [19, 15], income: 'low', region: 'Africa', pop: 16000000 },
{ name: 'Haiti', coords: [-72, 19], income: 'low', region: 'Americas', pop: 11000000 }
];
const features = countries.map((country, index) => {
// Coverage progression by income level
let coverageByYear;
let casesByYear;
if (country.income === 'high') {
// High-income: rapid vaccination, early access
coverageByYear = [2, 68, 82, 88]; // 2020: minimal, 2021: rapid rollout, 2022-2023: plateau
// Cases: high peak in 2020-2021, decline with vaccination
const peakCases = country.pop * 0.15; // 15% of population at peak
casesByYear = [peakCases * 0.7, peakCases, peakCases * 0.4, peakCases * 0.15];
} else if (country.income === 'upper-middle') {
// Upper-middle: moderate pace
coverageByYear = [1, 45, 68, 75];
const peakCases = country.pop * 0.12;
casesByYear = [peakCases * 0.6, peakCases, peakCases * 0.5, peakCases * 0.2];
} else if (country.income === 'lower-middle') {
// Lower-middle: slower rollout
coverageByYear = [0.5, 25, 48, 58];
const peakCases = country.pop * 0.10;
casesByYear = [peakCases * 0.5, peakCases, peakCases * 0.6, peakCases * 0.3];
} else {
// Low-income: COVAX challenges, minimal early access
coverageByYear = [0, 8, 18, 24];
const peakCases = country.pop * 0.08;
casesByYear = [peakCases * 0.4, peakCases, peakCases * 0.7, peakCases * 0.4];
}
return {
type: 'Feature',
id: index,
geometry: {
type: 'Point',
coordinates: country.coords
},
properties: {
name: country.name,
income_level: country.income,
region: country.region,
population: country.pop,
// Current year data (for map display)
coverage: coverageByYear[currentYearIndex],
cases: Math.round(casesByYear[currentYearIndex]),
// Time series data (for charts)
years_array: JSON.stringify(YEARS),
coverage_array: JSON.stringify(coverageByYear),
cases_array: JSON.stringify(casesByYear.map(c => Math.round(c)))
}
};
});
return {
type: 'FeatureCollection',
features: features
};
}
// Update global statistics
function updateGlobalStats(data) {
const features = data.features;
// Calculate weighted average coverage
let totalPop = 0;
let weightedCoverage = 0;
let totalCases = 0;
features.forEach(f => {
const pop = f.properties.population;
const coverage = f.properties.coverage;
const cases = f.properties.cases;
totalPop += pop;
weightedCoverage += coverage * pop;
totalCases += cases;
});
const avgCoverage = (weightedCoverage / totalPop).toFixed(1);
document.getElementById('global-coverage').textContent = avgCoverage + '%';
document.getElementById('total-cases').textContent = (totalCases / 1000000).toFixed(1) + 'M';
}
// Update map for current year
function updateMap() {
const data = generateCovidData();
updateGlobalStats(data);
if (map.getSource('covid-data')) {
map.getSource('covid-data').setData(data);
}
document.getElementById('current-year').textContent = YEARS[currentYearIndex];
document.getElementById('year-slider').value = currentYearIndex;
}
// Map load handler
map.on('load', () => {
// Initialize data
const initialData = generateCovidData();
// Add source
map.addSource('covid-data', {
type: 'geojson',
data: initialData
});
// Create layer using LayerFactory
const factory = new LayerFactory(map);
const layer = factory.createCircleLayer({
id: 'covid-layer',
source: 'covid-data',
sizeProperty: 'population',
sizeRange: [6, 35],
colorProperty: 'coverage',
colorScale: 'coverage',
opacityRange: [0.85, 0.95]
});
map.addLayer(layer);
// Apply atmosphere
factory.applyGlobeAtmosphere({ theme: 'medical' });
// Update initial stats
updateGlobalStats(initialData);
// Setup dual-axis chart popups
// Dual-axis configuration from Chart.js cartesian axes documentation
// Click on country to pin/unpin popup
map.on('click', 'covid-layer', (e) => {
const feature = e.features[0];
const props = feature.properties;
// Check if this country already has a pinned popup
const existingPopupIndex = pinnedPopups.findIndex(p => p._countryName === props.name);
if (existingPopupIndex !== -1) {
// Country is pinned - remove it
const popup = pinnedPopups[existingPopupIndex];
popup.remove();
pinnedPopups.splice(existingPopupIndex, 1);
console.log('Unpinned:', props.name);
} else {
// Country not pinned - create and pin popup
const years = JSON.parse(props.years_array);
const coverage = JSON.parse(props.coverage_array);
const cases = JSON.parse(props.cases_array);
const canvasId = `chart-${chartCounter++}`;
const popup = new mapboxgl.Popup({
offset: 15,
closeButton: true,
closeOnClick: false
})
.setLngLat(feature.geometry.coordinates)
.setHTML(`
<div class="advanced-popup" style="cursor: pointer;">
<h3 style="margin-bottom: 8px;">${props.name}</h3>
<div class="pin-hint" style="font-size: 11px; color: #60a5fa; margin-bottom: 8px; font-weight: 600; text-align: center; padding: 4px 8px; background: rgba(96, 165, 250, 0.1); border-radius: 4px;">
📌 Pinned • Click country or popup to close
</div>
<canvas id="${canvasId}" width="420" height="280"></canvas>
</div>
`)
.addTo(map);
popup._isPinned = true;
popup._countryName = props.name;
pinnedPopups.push(popup);
// Add click handler to popup itself to allow closing by clicking popup
const popupElement = popup.getElement();
if (popupElement) {
popupElement.addEventListener('click', (event) => {
// Don't trigger if clicking the close button (let Mapbox handle that)
if (event.target.classList.contains('mapboxgl-popup-close-button')) {
return;
}
// Close this popup
const popupIndex = pinnedPopups.findIndex(p => p._countryName === props.name);
if (popupIndex !== -1) {
pinnedPopups.splice(popupIndex, 1);
}
popup.remove();
console.log('Unpinned via popup click:', props.name);
});
}
// Create chart
requestAnimationFrame(() => {
const canvas = document.getElementById(canvasId);
if (!canvas) return;
const ctx = canvas.getContext('2d');
new Chart(ctx, {
type: 'line',
data: {
labels: years,
datasets: [
{
label: 'Vaccination Coverage (%)',
data: coverage,
borderColor: 'rgb(16, 185, 129)',
backgroundColor: 'rgba(16, 185, 129, 0.15)',
yAxisID: 'y',
tension: 0.4,
fill: true,
pointRadius: 5,
pointHoverRadius: 7,
pointBackgroundColor: 'rgb(16, 185, 129)',
pointBorderColor: '#fff',
pointBorderWidth: 2,
borderWidth: 3
},
{
label: 'COVID-19 Cases',
data: cases,
borderColor: 'rgb(239, 68, 68)',
backgroundColor: 'rgba(239, 68, 68, 0.3)',
yAxisID: 'y1',
type: 'bar',
barThickness: 30,
borderWidth: 2
}
]
},
options: {
responsive: true,
maintainAspectRatio: true,
interaction: {
mode: 'index',
intersect: false
},
scales: {
x: {
ticks: {
color: '#9ca3af',
font: { size: 12, weight: '600' }
},
grid: {
color: 'rgba(255, 255, 255, 0.05)',
drawBorder: false
}
},
y: {
type: 'linear',
position: 'left',
title: {
display: true,
text: 'Coverage (%)',
color: '#10b981',
font: { size: 13, weight: '700' }
},
min: 0,
max: 100,
ticks: {
color: '#10b981',
font: { size: 11, weight: '600' },
callback: function(value) {
return value + '%';
}
},
grid: {
color: 'rgba(16, 185, 129, 0.1)',
drawBorder: false
}
},
y1: {
type: 'linear',
position: 'right',
title: {
display: true,
text: 'Cases',
color: '#ef4444',
font: { size: 13, weight: '700' }
},
grid: {
drawOnChartArea: false
},
ticks: {
color: '#ef4444',
font: { size: 11, weight: '600' },
callback: function(value) {
if (value >= 1000000) {
return (value / 1000000).toFixed(1) + 'M';
} else if (value >= 1000) {
return (value / 1000).toFixed(0) + 'K';
}
return value;
}
}
}
},
plugins: {
title: {
display: true,
text: 'COVID-19 Vaccination Impact Timeline',
color: '#f3f4f6',
font: { size: 14, weight: '700' },
padding: { top: 5, bottom: 15 }
},
legend: {
position: 'bottom',
labels: {
color: '#e5e7eb',
font: { size: 12, weight: '600' },
padding: 15,
usePointStyle: true
}
},
tooltip: {
backgroundColor: 'rgba(10, 14, 26, 0.95)',
titleColor: '#f3f4f6',
bodyColor: '#d1d5db',
borderColor: 'rgba(255, 255, 255, 0.2)',
borderWidth: 1,
padding: 12,
displayColors: true,
callbacks: {
label: function(context) {
let label = context.dataset.label || '';
if (label) {
label += ': ';
}
if (context.datasetIndex === 0) {
label += context.parsed.y.toFixed(1) + '%';
} else {
const value = context.parsed.y;
if (value >= 1000000) {
label += (value / 1000000).toFixed(2) + 'M';
} else if (value >= 1000) {
label += (value / 1000).toFixed(0) + 'K';
} else {
label += value;
}
}
return label;
}
}
}
}
}
});
});
console.log('Pinned:', props.name);
}
});
// Just change cursor on hover
map.on('mouseenter', 'covid-layer', () => {
map.getCanvas().style.cursor = 'pointer';
});
map.on('mouseleave', 'covid-layer', () => {
map.getCanvas().style.cursor = '';
});
});
// Timeline controls
const slider = document.getElementById('year-slider');
const playPauseBtn = document.getElementById('play-pause');
const resetBtn = document.getElementById('reset');
const loopCheckbox = document.getElementById('loop-checkbox');
slider.addEventListener('input', (e) => {
currentYearIndex = parseInt(e.target.value);
updateMap();
// Stop playing if user manually adjusts slider
if (isPlaying) {
stopPlaying();
}
});
playPauseBtn.addEventListener('click', () => {
if (isPlaying) {
stopPlaying();
} else {
startPlaying();
}
});
resetBtn.addEventListener('click', () => {
stopPlaying();
currentYearIndex = 0;
updateMap();
});
function startPlaying() {
isPlaying = true;
playPauseBtn.textContent = '⏸ Pause';
playPauseBtn.classList.add('playing');
playInterval = setInterval(() => {
currentYearIndex++;
if (currentYearIndex >= YEARS.length) {
if (loopCheckbox.checked) {
currentYearIndex = 0;
} else {
stopPlaying();
currentYearIndex = YEARS.length - 1;
return;
}
}
updateMap();
}, 1500); // 1.5 seconds per year
}
function stopPlaying() {
isPlaying = false;
playPauseBtn.textContent = '▶ Play';
playPauseBtn.classList.remove('playing');
if (playInterval) {
clearInterval(playInterval);
playInterval = null;
}
}
// Globe rotation
let userInteracting = false;
map.on('mousedown', () => { userInteracting = true; });
map.on('mouseup', () => { userInteracting = false; });
function spinGlobe() {
if (!userInteracting && !isPlaying) {
const center = map.getCenter();
center.lng += 0.15;
map.setCenter(center);
}
requestAnimationFrame(spinGlobe);
}
// spinGlobe(); // Auto-rotation disabled
</script>
</body>
</html>