← 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>