← back to Model Arena
data/artifacts/0715b25a41dd/qwen3-14b.html
412 lines
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>The Last Whisper of Vekari</title>
<style>
:root {
--bg: #0d0d0d;
--text: #eaeaea;
--accent: #ff6666;
--accent2: #6666ff;
--map-bg: #1a1a1a;
--border: #333;
--fade-speed: 2s;
--transition: ease 0.3s;
}
html, body {
margin: 0;
padding: 0;
font-family: "Helvetica Neue", sans-serif;
background-color: var(--bg);
color: var(--text);
height: 100%;
overflow: hidden;
scroll-behavior: smooth;
-webkit-font-smoothing: antialiased;
}
#app {
display: flex;
flex-direction: column;
height: 100vh;
}
header {
padding: 1rem 2rem;
display: flex;
justify-content: space-between;
align-items: center;
background: var(--bg);
box-shadow: 0 2px 5px rgba(0,0,0,0.2);
}
header h1 {
margin: 0;
font-size: 1.5rem;
font-weight: 600;
}
.clock {
font-size: 1.2rem;
font-weight: bold;
}
.map-container {
flex: 1;
position: relative;
overflow: hidden;
display: flex;
align-items: center;
justify-content: center;
background: var(--map-bg);
border: 1px solid var(--border);
}
#map {
width: 100%;
height: 100%;
position: relative;
}
.region {
position: absolute;
cursor: pointer;
user-select: none;
transition: all var(--transition);
border-radius: 10px;
box-shadow: 0 0 10px rgba(0,0,0,0.2);
}
.region:hover {
transform: scale(1.05);
box-shadow: 0 0 20px rgba(255, 102, 102, 0.3);
}
.region:focus {
outline: 2px solid var(--accent2);
outline-offset: 2px;
}
.tooltip {
position: absolute;
bottom: 100%;
left: 50%;
transform: translateX(-50%);
background: rgba(0,0,0,0.7);
padding: 0.5rem 1rem;
color: var(--text);
font-size: 0.8rem;
border-radius: 5px;
pointer-events: none;
opacity: 0;
visibility: hidden;
transition: opacity 0.3s;
}
.tooltip.active {
opacity: 1;
visibility: visible;
}
#phrasebook {
padding: 1rem;
background: var(--bg);
display: flex;
flex-direction: column;
gap: 1rem;
overflow-y: auto;
max-height: 30vh;
}
.phrase {
background: #1e1e1e;
padding: 0.75rem;
border-radius: 8px;
display: flex;
align-items: center;
gap: 1rem;
font-weight: 500;
cursor: pointer;
transition: all var(--transition);
}
.phrase:hover {
background: var(--accent2);
color: #fff;
transform: scale(1.02);
}
.phrase:focus {
outline: 2px solid var(--accent);
outline-offset: 2px;
}
.phrase span {
display: flex;
align-items: center;
gap: 0.5rem;
}
.phrase .word {
font-weight: bold;
font-size: 1.1rem;
color: var(--accent);
}
.phrase .sound {
font-size: 0.9rem;
color: #ccc;
}
.phrase .delete {
margin-left: auto;
color: var(--accent);
font-weight: bold;
cursor: pointer;
opacity: 0.6;
transition: opacity 0.3s;
}
.phrase .delete:hover {
opacity: 1;
}
.phrase .delete:focus {
outline: none;
background: rgba(255, 255, 255, 0.1);
border-radius: 50%;
}
.footer {
padding: 1rem 2rem;
text-align: center;
font-size: 0.9rem;
color: #aaa;
}
.fade-message {
position: fixed;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
font-size: 1.5rem;
font-weight: bold;
color: var(--accent);
display: none;
text-shadow: 0 0 10px #fff;
}
.fade-message.show {
display: block;
}
.empty-state {
text-align: center;
padding: 2rem;
color: #999;
font-style: italic;
font-size: 1.2rem;
}
.complete-state {
text-align: center;
padding: 2rem;
color: #555;
font-weight: 600;
}
@media (min-width: 768px) {
.map-container {
min-height: 60vh;
}
}
@media (min-width: 1024px) {
.map-container {
min-height: 70vh;
}
}
@media (prefers-reduced-motion: no-preference) {
.fade {
animation: fadeOut var(--fade-speed) ease-in-out;
}
@keyframes fadeOut {
0% { opacity: 1; }
100% { opacity: 0; }
}
}
@media (prefers-reduced-motion: reduce) {
.fade {
animation: none;
opacity: 0;
}
}
audio {
display: none;
}
</style>
</head>
<body>
<div id="app">
<header>
<h1>Vekari - The Last Whisper</h1>
<div class="clock" id="clock">Speaker Count: 12,000</div>
</header>
<div class="map-container">
<div id="map">
<!-- Regions will be injected here -->
</div>
</div>
<div id="phrasebook">
<div class="empty-state" id="emptyPhrasebook">No words saved yet. Click on the map to save a phrase.</div>
</div>
<div class="fade-message" id="fadeMessage">The language is fading...</div>
<div class="footer">
<p>Click on regions to hear the words of Vekari. Each word you save may help preserve this dying language.</p>
</div>
</div>
<audio id="audioPlayer" preload="auto"></audio>
<script>
const audioPlayer = document.getElementById('audioPlayer');
const fadeMessage = document.getElementById('fadeMessage');
const clock = document.getElementById('clock');
const map = document.getElementById('map');
const phrasebook = document.getElementById('phrasebook');
const emptyPhrasebook = document.getElementById('emptyPhrasebook');
let speakerCount = 12000;
let wordPhonemes = {
"Tsal": "https://www.soundjay.com/buttons/sounds/button-18.mp3", // placeholder
"Hul": "https://www.soundjay.com/buttons/sounds/button-19.mp3", // placeholder
"Kor": "https://www.soundjay.com/buttons/sounds/button-20.mp3", // placeholder
"Nel": "https://www.soundjay.com/buttons/sounds/button-21.mp3", // placeholder
"Vek": "https://www.soundjay.com/buttons/sounds/button-22.mp3", // placeholder
"Thra": "https://www.soundjay.com/buttons/sounds/button-23.mp3", // placeholder
"Lom": "https://www.soundjay.com/buttons/sounds/button-24.mp3", // placeholder
};
const regions = [
{ id: 'Tsal', x: '25%', y: '20%', tooltip: 'Tsal: Greeting of dawn', phoneme: 'Tsal' },
{ id: 'Hul', x: '70%', y: '15%', tooltip: 'Hul: Blessing of fire', phoneme: 'Hul' },
{ id: 'Kor', x: '50%', y: '40%', tooltip: 'Kor: River of memory', phoneme: 'Kor' },
{ id: 'Nel', x: '15%', y: '60%', tooltip: 'Nel: Whisper of wind', phoneme: 'Nel' },
{ id: 'Vek', x: '80%', y: '70%', tooltip: 'Vek: Starlight path', phoneme: 'Vek' },
{ id: 'Thra', x: '25%', y: '85%', tooltip: 'Thra: Echo of ancestors', phoneme: 'Thra' },
{ id: 'Lom', x: '75%', y: '85%', tooltip: 'Lom: Silent sky', phoneme: 'Lom' }
];
function renderMap() {
map.innerHTML = '';
regions.forEach(region => {
const regionDiv = document.createElement('div');
regionDiv.className = 'region';
regionDiv.style.left = region.x;
regionDiv.style.top = region.y;
regionDiv.style.width = '80px';
regionDiv.style.height = '80px';
regionDiv.setAttribute('data-phoneme', region.phoneme);
regionDiv.setAttribute('aria-label', region.tooltip);
regionDiv.innerHTML = `<div class="tooltip">${region.tooltip}</div>`;
regionDiv.addEventListener('click', () => {
playSound(region.phoneme);
addPhrase(region.phoneme, region.tooltip);
});
regionDiv.addEventListener('mouseenter', () => {
const tooltip = regionDiv.querySelector('.tooltip');
tooltip.classList.add('active');
});
regionDiv.addEventListener('mouseleave', () => {
const tooltip = regionDiv.querySelector('.tooltip');
tooltip.classList.remove('active');
});
map.appendChild(regionDiv);
});
}
function playSound(phoneme) {
audioPlayer.src = wordPhonemes[phoneme];
audioPlayer.play();
}
function addPhrase(phoneme, tooltip) {
const phrase = document.createElement('div');
phrase.className = 'phrase';
phrase.setAttribute('aria-label', `${phoneme} - ${tooltip}`);
const span = document.createElement('span');
const word = document.createElement('span');
word.className = 'word';
word.textContent = phoneme;
const sound = document.createElement('span');
sound.className = 'sound';
sound.textContent = tooltip;
span.appendChild(word);
span.appendChild(sound);
const deleteBtn = document.createElement('span');
deleteBtn.className = 'delete';
deleteBtn.textContent = '✕';
deleteBtn.addEventListener('click', () => {
phrase.remove();
updatePhrasebook();
});
phrase.appendChild(span);
phrase.appendChild(deleteBtn);
phrasebook.insertBefore(phrase, emptyPhrasebook);
updatePhrasebook();
}
function updatePhrasebook() {
const phrases = document.querySelectorAll('.phrase');
if (phrases.length === 0) {
emptyPhrasebook.style.display = 'block';
} else {
emptyPhrasebook.style.display = 'none';
}
}
function updateClock() {
speakerCount -= Math.random() * 100;
if (speakerCount < 0) speakerCount = 0;
clock.textContent = `Speaker Count: ${Math.round(speakerCount)}`;
if (speakerCount <= 100) {
fadeMessage.classList.add('show');
} else {
fadeMessage.classList.remove('show');
}
if (speakerCount === 0) {
clock.textContent = "Speaker Count: 0 - The language is extinct.";
fadeMessage.textContent = "The last speaker has passed away.";
fadeMessage.classList.add('show');
document.querySelectorAll('.region').forEach(r => r.style.pointerEvents = 'none');
document.querySelectorAll('.region').forEach(r => r.style.opacity = '0.5');
}
if (speakerCount > 0) {
setTimeout(updateClock, 1000);
}
}
updateClock();
renderMap();
</script>
</body>
</html>