2.2.1 - Időkorlátok

Röviden a szabványpontról

A WCAG 2.2.1 (Timing Adjustable) előírja, hogy a felhasználóknak elegendő időt kell biztosítani a tartalom elolvasására és használatára. Ez minden olyan tartalomra vagy funkcionalitásra vonatkozik, amely időkorláttal vagy időérzékeny interakciókkal rendelkezik. A cél az, hogy azok a felhasználók, akiknek több időre lehet szükségük – kognitív, mozgásszervi vagy egyéb fogyatékosság miatt – ne legyenek igazságtalanul korlátozva vagy kényszerítve a feladatok sietős elvégzésére. Ez magában foglalja az űrlapokat, kvízeket, időtúllépéseket vagy bármilyen időzített folyamatot egy weboldalon vagy alkalmazásban.

Kiket érint

Elsődleges felhasználók: Kognitív fogyatékossággal, mozgássérüléssel élő emberek, vagy azok, akik az interakciót lassító kisegítő technológiákat használnak (pl. képernyőolvasók, kapcsoló eszközök).

Másodlagos előnyök: Stressz alatt lévő felhasználók, átmeneti fogyatékossággal vagy helyzetfüggő korlátozásokkal rendelkezők (mint erős napfény vagy figyelemelterelés), és idősebb felnőttek, akiknek több időre lehet szükségük az információ feldolgozásához.

Tesztelés

  1. Időzített tartalom azonosítása: Ellenőrizd, van-e bármilyen tartalom vagy interaktív elem, amely időkorláttal rendelkezik (pl. automatikus kijelentkezés, kvízek, űrlap időtúllépések)
  2. Módosítható időzítés tesztelése: Ellenőrizd, hogy a felhasználók meghosszabbíthatják, módosíthatják vagy kikapcsolhatják-e az időkorlátokat
  3. Billentyűzet és képernyőolvasó tesztelés: Használj csak billentyűzetes és képernyőolvasós navigációt annak biztosítására, hogy az időmódosítások hozzáférhetőek
  4. Lassabb interakciók szimulálása: Használj eszközöket vagy manuális tesztelést a lassabb bevitel szimulálására és erősítsd meg, hogy a tartalom nem időzít ki idő előtt
  5. Szkriptek áttekintése: Vizsgáld meg a JavaScript vagy szerveroldali logikát annak biztosítására, hogy az időkorlátok programozottan vezérelhetők vagy kiterjeszthetők

Jó gyakorlatok

1. Több idő kérésének lehetősége

<div class="timed-form-container">
  <h2>Időzített űrlap</h2>
  
  <div class="timer-display">
    <span class="timer-icon">⏱️</span>
    <span>Hátralévő idő: <strong id="time-display">5:00</strong></span>
  </div>
  
  <form id="timed-form">
    <div class="form-group">
      <label for="name">Teljes név:</label>
      <input type="text" id="name" name="name" required>
    </div>
    
    <div class="form-group">
      <label for="email">E-mail cím:</label>
      <input type="email" id="email" name="email" required>
    </div>
    
    <div class="form-group">
      <label for="message">Üzenet:</label>
      <textarea id="message" name="message" rows="4" required></textarea>
    </div>
    
    <div class="form-actions">
      <button type="submit" class="submit-btn">Küldés</button>
      <button type="button" onclick="extendTime()" class="extend-btn">
        ➕ Több idő kérése (+2 perc)
      </button>
    </div>
  </form>
  
  <div id="warning-modal" class="modal" hidden>
    <div class="modal-content">
      <h3>⚠️ Figyelmeztetés</h3>
      <p>Az idő hamarosan lejár! Csak <strong id="warning-time">30</strong> másodperc maradt.</p>
      <button onclick="extendTime()" class="modal-btn primary">Több idő kérése</button>
      <button onclick="closeWarning()" class="modal-btn secondary">Folytatás</button>
    </div>
  </div>
</div>

<style>
.timed-form-container {
  max-width: 600px;
  margin: 20px auto;
  padding: 20px;
  border: 1px solid #ddd;
  border-radius: 8px;
  background-color: #f8f9fa;
}

.timer-display {
  background-color: #e7f3ff;
  padding: 15px;
  border-radius: 4px;
  margin-bottom: 20px;
  text-align: center;
  font-size: 18px;
}

.timer-icon {
  font-size: 24px;
  margin-right: 10px;
}

#time-display {
  color: #0056b3;
  font-family: monospace;
  font-size: 20px;
}

#time-display.warning {
  color: #ff6b6b;
  animation: pulse 1s infinite;
}

@keyframes pulse {
  0% { opacity: 1; }
  50% { opacity: 0.6; }
  100% { opacity: 1; }
}

.form-group {
  margin-bottom: 15px;
}

.form-group label {
  display: block;
  margin-bottom: 5px;
  font-weight: 600;
}

.form-group input,
.form-group textarea {
  width: 100%;
  padding: 10px;
  border: 1px solid #ced4da;
  border-radius: 4px;
  font-size: 16px;
  box-sizing: border-box;
}

.form-group input:focus,
.form-group textarea:focus {
  outline: 2px solid #007bff;
  outline-offset: 2px;
  border-color: #007bff;
}

.form-actions {
  display: flex;
  gap: 10px;
  margin-top: 20px;
}

.submit-btn {
  flex: 1;
  padding: 12px 20px;
  background-color: #007bff;
  color: white;
  border: none;
  border-radius: 4px;
  font-size: 16px;
  cursor: pointer;
}

.submit-btn:hover {
  background-color: #0056b3;
}

.extend-btn {
  flex: 1;
  padding: 12px 20px;
  background-color: #28a745;
  color: white;
  border: none;
  border-radius: 4px;
  font-size: 16px;
  cursor: pointer;
}

.extend-btn:hover {
  background-color: #218838;
}

.extend-btn:focus,
.submit-btn:focus {
  outline: 2px solid #80bdff;
  outline-offset: 2px;
}

.modal {
  position: fixed;
  top: 0;
  left: 0;
  width: 100%;
  height: 100%;
  background-color: rgba(0, 0, 0, 0.5);
  display: flex;
  justify-content: center;
  align-items: center;
  z-index: 1000;
}

.modal[hidden] {
  display: none;
}

.modal-content {
  background-color: white;
  padding: 30px;
  border-radius: 8px;
  max-width: 400px;
  text-align: center;
  box-shadow: 0 4px 20px rgba(0, 0, 0, 0.3);
}

.modal-content h3 {
  margin-top: 0;
  color: #ff6b6b;
}

.modal-btn {
  padding: 10px 20px;
  margin: 5px;
  border: none;
  border-radius: 4px;
  font-size: 16px;
  cursor: pointer;
}

.modal-btn.primary {
  background-color: #28a745;
  color: white;
}

.modal-btn.secondary {
  background-color: #6c757d;
  color: white;
}

.modal-btn:hover {
  opacity: 0.9;
}

.modal-btn:focus {
  outline: 2px solid #007bff;
  outline-offset: 2px;
}
</style>

<script>
let timeLeft = 300; // 5 perc másodpercben
let timer;
let warningShown = false;
let extensionCount = 0;
const maxExtensions = 3;

function startTimer() {
  timer = setInterval(() => {
    timeLeft--;
    updateDisplay();
    
    // Figyelmeztetés megjelenítése 30 másodpercnél
    if (timeLeft === 30 && !warningShown) {
      showWarning();
      warningShown = true;
    }
    
    // Idő lejárt
    if (timeLeft <= 0) {
      clearInterval(timer);
      handleTimeout();
    }
  }, 1000);
}

function updateDisplay() {
  const minutes = Math.floor(timeLeft / 60);
  const seconds = timeLeft % 60;
  const display = `${minutes}:${seconds.toString().padStart(2, '0')}`;
  const timeDisplay = document.getElementById('time-display');
  
  timeDisplay.textContent = display;
  
  // Vizuális figyelmeztetés
  if (timeLeft <= 30) {
    timeDisplay.classList.add('warning');
  } else {
    timeDisplay.classList.remove('warning');
  }
}

function extendTime() {
  if (extensionCount >= maxExtensions) {
    alert(`Maximum ${maxExtensions} alkalommal kérhetsz időhosszabbítást.`);
    return;
  }
  
  timeLeft += 120; // +2 perc
  extensionCount++;
  warningShown = false;
  closeWarning();
  updateDisplay();
  
  // Visszajelzés a felhasználónak
  showNotification(`Idő meghosszabbítva! (+2 perc) Fennmaradó hosszabbítások: ${maxExtensions - extensionCount}`);
  
  // ARIA live region frissítése
  const announcement = document.createElement('div');
  announcement.setAttribute('role', 'status');
  announcement.setAttribute('aria-live', 'polite');
  announcement.textContent = `Idő meghosszabbítva. Új hátralévő idő: ${Math.floor(timeLeft / 60)} perc ${timeLeft % 60} másodperc`;
  document.body.appendChild(announcement);
  setTimeout(() => announcement.remove(), 3000);
}

function showWarning() {
  const modal = document.getElementById('warning-modal');
  modal.hidden = false;
  
  // Fókusz a figyelmeztetésre
  const primaryBtn = modal.querySelector('.modal-btn.primary');
  primaryBtn.focus();
  
  // Figyelmeztetés időzítő
  let warningTime = 30;
  const warningInterval = setInterval(() => {
    warningTime--;
    document.getElementById('warning-time').textContent = warningTime;
    
    if (warningTime <= 0 || modal.hidden) {
      clearInterval(warningInterval);
    }
  }, 1000);
}

function closeWarning() {
  document.getElementById('warning-modal').hidden = true;
}

function handleTimeout() {
  alert('Az idő lejárt! Az űrlap adatai mentésre kerültek piszkozatként.');
  // Itt lehetne menteni az űrlap adatait localStorage-ba
  saveFormData();
  
  // Űrlap letiltása
  const form = document.getElementById('timed-form');
  const inputs = form.querySelectorAll('input, textarea, button');
  inputs.forEach(input => input.disabled = true);
  
  // Időzítő megjelenítés frissítése
  document.getElementById('time-display').textContent = 'Lejárt';
}

function saveFormData() {
  const formData = {
    name: document.getElementById('name').value,
    email: document.getElementById('email').value,
    message: document.getElementById('message').value,
    timestamp: new Date().toISOString()
  };
  
  localStorage.setItem('timedFormDraft', JSON.stringify(formData));
  console.log('Űrlap adatok mentve:', formData);
}

function showNotification(message) {
  const notification = document.createElement('div');
  notification.textContent = message;
  notification.style.cssText = `
    position: fixed;
    top: 20px;
    right: 20px;
    background-color: #28a745;
    color: white;
    padding: 15px 20px;
    border-radius: 4px;
    box-shadow: 0 2px 5px rgba(0,0,0,0.2);
    z-index: 1001;
  `;
  document.body.appendChild(notification);
  
  setTimeout(() => notification.remove(), 4000);
}

// Űrlap beküldés kezelése
document.getElementById('timed-form').addEventListener('submit', (e) => {
  e.preventDefault();
  clearInterval(timer);
  alert('Űrlap sikeresen beküldve!');
  // Itt lenne a tényleges beküldés
});

// Időzítő indítása az oldal betöltésekor
window.addEventListener('load', () => {
  startTimer();
  updateDisplay();
});
</script>

Magyarázat: Ez a példa egy gombot mutat, amely lehetővé teszi a felhasználók számára az időkorlát meghosszabbítását, kontrollt adva nekik az időzítés felett. A rendszer figyelmeztetést is küld, mielőtt az idő lejárna.

2. Időkorlátok letiltásának lehetősége

<div class="session-timeout-container">
  <h2>Munkamenet beállítások</h2>
  
  <div class="session-info">
    <p>A munkamenet lejár: <span id="countdown" class="countdown-display">10:00</span></p>
    
    <div class="timeout-controls">
      <button type="button" onclick="pauseTimeout()" id="pause-btn" class="control-btn pause">
        ⏸️ Szüneteltetés
      </button>
      <button type="button" onclick="resetTimeout()" class="control-btn reset">
        🔄 Újraindítás
      </button>
      <button type="button" onclick="disableTimeout()" class="control-btn disable">
        ❌ Időkorlát kikapcsolása
      </button>
    </div>
  </div>
  
  <div class="session-settings">
    <h3>Időkorlát beállítások</h3>
    
    <label class="setting-item">
      <input type="checkbox" id="auto-extend" onchange="toggleAutoExtend()">
      <span>Automatikus hosszabbítás aktivitás esetén</span>
    </label>
    
    <label class="setting-item">
      <input type="checkbox" id="sound-alert" checked>
      <span>Hangjelzés figyelmeztetéskor</span>
    </label>
    
    <div class="setting-item">
      <label for="timeout-duration">Időkorlát hossza (perc):</label>
      <input type="number" id="timeout-duration" min="1" max="60" value="10" 
             onchange="updateTimeoutDuration()">
    </div>
  </div>
  
  <div id="activity-log" class="activity-log">
    <h3>Tevékenység napló</h3>
    <ul id="log-list"></ul>
  </div>
</div>

<style>
.session-timeout-container {
  max-width: 600px;
  margin: 20px auto;
  font-family: Arial, sans-serif;
}

.session-info {
  background-color: #f8f9fa;
  padding: 20px;
  border-radius: 8px;
  margin-bottom: 20px;
  text-align: center;
}

.countdown-display {
  font-size: 36px;
  font-weight: bold;
  color: #007bff;
  font-family: monospace;
  display: inline-block;
  min-width: 120px;
}

.countdown-display.warning {
  color: #ffc107;
}

.countdown-display.critical {
  color: #dc3545;
  animation: blink 1s infinite;
}

.countdown-display.disabled {
  color: #6c757d;
  text-decoration: line-through;
}

@keyframes blink {
  0%, 50% { opacity: 1; }
  51%, 100% { opacity: 0.5; }
}

.timeout-controls {
  display: flex;
  gap: 10px;
  justify-content: center;
  margin-top: 15px;
  flex-wrap: wrap;
}

.control-btn {
  padding: 10px 20px;
  border: none;
  border-radius: 4px;
  font-size: 14px;
  cursor: pointer;
  transition: transform 0.1s;
}

.control-btn:hover {
  transform: translateY(-2px);
}

.control-btn:focus {
  outline: 2px solid #007bff;
  outline-offset: 2px;
}

.control-btn.pause {
  background-color: #ffc107;
  color: #000;
}

.control-btn.reset {
  background-color: #17a2b8;
  color: white;
}

.control-btn.disable {
  background-color: #dc3545;
  color: white;
}

.control-btn:disabled {
  background-color: #6c757d;
  cursor: not-allowed;
  opacity: 0.6;
}

.session-settings {
  background-color: #e9ecef;
  padding: 20px;
  border-radius: 8px;
  margin-bottom: 20px;
}

.session-settings h3 {
  margin-top: 0;
}

.setting-item {
  display: block;
  margin-bottom: 15px;
  cursor: pointer;
}

.setting-item input[type="checkbox"] {
  margin-right: 10px;
  width: 18px;
  height: 18px;
  cursor: pointer;
}

.setting-item input[type="number"] {
  width: 60px;
  padding: 5px;
  margin-left: 10px;
  border: 1px solid #ced4da;
  border-radius: 4px;
}

.activity-log {
  background-color: #f1f3f4;
  padding: 15px;
  border-radius: 8px;
  max-height: 200px;
  overflow-y: auto;
}

.activity-log h3 {
  margin-top: 0;
  font-size: 16px;
}

#log-list {
  list-style: none;
  padding: 0;
  margin: 0;
  font-size: 14px;
}

#log-list li {
  padding: 5px 0;
  border-bottom: 1px solid #dee2e6;
}

#log-list li:last-child {
  border-bottom: none;
}

.timestamp {
  color: #6c757d;
  font-size: 12px;
  margin-right: 8px;
}
</style>

<script>
let countdown = 600; // 10 perc másodpercben
let interval;
let isPaused = false;
let isDisabled = false;
let autoExtend = false;
let lastActivity = Date.now();

function startCountdown() {
  interval = setInterval(() => {
    if (!isPaused && !isDisabled) {
      countdown--;
      updateCountdownDisplay();
      
      // Automatikus hosszabbítás ellenőrzése
      if (autoExtend && Date.now() - lastActivity < 30000) {
        countdown = Math.max(countdown, 60); // Minimum 1 perc
      }
      
      // Figyelmeztetések
      if (countdown === 60) {
        showAlert('A munkamenet 1 perc múlva lejár!');
      } else if (countdown === 30) {
        showAlert('A munkamenet 30 másodperc múlva lejár!', true);
      } else if (countdown <= 0) {
        clearInterval(interval);
        handleSessionExpired();
      }
    }
  }, 1000);
}

function updateCountdownDisplay() {
  const display = document.getElementById('countdown');
  const minutes = Math.floor(countdown / 60);
  const seconds = countdown % 60;
  
  display.textContent = `${minutes}:${seconds.toString().padStart(2, '0')}`;
  
  // Vizuális jelzések
  display.classList.remove('warning', 'critical', 'disabled');
  if (isDisabled) {
    display.classList.add('disabled');
    display.textContent = 'Kikapcsolva';
  } else if (countdown <= 30) {
    display.classList.add('critical');
  } else if (countdown <= 60) {
    display.classList.add('warning');
  }
}

function pauseTimeout() {
  isPaused = !isPaused;
  const pauseBtn = document.getElementById('pause-btn');
  
  if (isPaused) {
    pauseBtn.innerHTML = '▶️ Folytatás';
    pauseBtn.classList.add('paused');
    addLogEntry('Időkorlát szüneteltetve');
  } else {
    pauseBtn.innerHTML = '⏸️ Szüneteltetés';
    pauseBtn.classList.remove('paused');
    addLogEntry('Időkorlát folytatva');
  }
}

function resetTimeout() {
  const duration = parseInt(document.getElementById('timeout-duration').value) * 60;
  countdown = duration;
  isPaused = false;
  isDisabled = false;
  updateCountdownDisplay();
  addLogEntry('Időkorlát újraindítva');
  
  // Gombok állapotának visszaállítása
  document.getElementById('pause-btn').innerHTML = '⏸️ Szüneteltetés';
  document.getElementById('pause-btn').disabled = false;
}

function disableTimeout() {
  isDisabled = true;
  clearInterval(interval);
  updateCountdownDisplay();
  addLogEntry('Időkorlát kikapcsolva');
  
  // Gombok letiltása
  document.getElementById('pause-btn').disabled = true;
  document.getElementById('pause-btn').innerHTML = '⏸️ Szüneteltetés';
  
  // Értesítés
  showNotification('Időkorlát sikeresen kikapcsolva. A munkamenet nem fog automatikusan lejárni.');
}

function toggleAutoExtend() {
  autoExtend = document.getElementById('auto-extend').checked;
  addLogEntry(autoExtend ? 'Automatikus hosszabbítás bekapcsolva' : 'Automatikus hosszabbítás kikapcsolva');
}

function updateTimeoutDuration() {
  const newDuration = parseInt(document.getElementById('timeout-duration').value);
  if (newDuration >= 1 && newDuration <= 60) {
    addLogEntry(`Időkorlát módosítva: ${newDuration} perc`);
    if (!isDisabled) {
      countdown = newDuration * 60;
      updateCountdownDisplay();
    }
  }
}

function showAlert(message, critical = false) {
  const soundEnabled = document.getElementById('sound-alert').checked;
  
  // Vizuális figyelmeztetés
  const alert = document.createElement('div');
  alert.textContent = message;
  alert.style.cssText = `
    position: fixed;
    top: 50%;
    left: 50%;
    transform: translate(-50%, -50%);
    background-color: ${critical ? '#dc3545' : '#ffc107'};
    color: ${critical ? 'white' : 'black'};
    padding: 20px 30px;
    border-radius: 8px;
    font-size: 18px;
    box-shadow: 0 4px 20px rgba(0,0,0,0.3);
    z-index: 1002;
  `;
  document.body.appendChild(alert);
  
  // Hangjelzés
  if (soundEnabled) {
    playSound(critical ? 880 : 440); // Magasabb hang kritikus figyelmeztetéshez
  }
  
  setTimeout(() => alert.remove(), 3000);
}

function playSound(frequency) {
  const audioContext = new (window.AudioContext || window.webkitAudioContext)();
  const oscillator = audioContext.createOscillator();
  const gainNode = audioContext.createGain();
  
  oscillator.connect(gainNode);
  gainNode.connect(audioContext.destination);
  
  oscillator.frequency.value = frequency;
  oscillator.type = 'sine';
  gainNode.gain.exponentialRampToValueAtTime(0.01, audioContext.currentTime + 0.5);
  
  oscillator.start(audioContext.currentTime);
  oscillator.stop(audioContext.currentTime + 0.5);
}

function handleSessionExpired() {
  addLogEntry('Munkamenet lejárt');
  alert('A munkamenet lejárt. Kérlek jelentkezz be újra.');
  // Itt lenne a kijelentkezés logika
}

function addLogEntry(message) {
  const logList = document.getElementById('log-list');
  const timestamp = new Date().toLocaleTimeString('hu-HU');
  
  const entry = document.createElement('li');
  entry.innerHTML = `<span class="timestamp">${timestamp}</span> ${message}`;
  
  logList.insertBefore(entry, logList.firstChild);
  
  // Maximum 10 bejegyzés megőrzése
  while (logList.children.length > 10) {
    logList.removeChild(logList.lastChild);
  }
}

function showNotification(message) {
  const notification = document.createElement('div');
  notification.setAttribute('role', 'status');
  notification.setAttribute('aria-live', 'polite');
  notification.textContent = message;
  notification.style.cssText = `
    position: fixed;
    bottom: 20px;
    right: 20px;
    background-color: #28a745;
    color: white;
    padding: 15px 20px;
    border-radius: 4px;
    box-shadow: 0 2px 5px rgba(0,0,0,0.2);
    z-index: 1001;
  `;
  document.body.appendChild(notification);
  
  setTimeout(() => notification.remove(), 5000);
}

// Aktivitás követése
document.addEventListener('click', () => {
  lastActivity = Date.now();
});

document.addEventListener('keypress', () => {
  lastActivity = Date.now();
});

// Kezdeti indítás
window.addEventListener('load', () => {
  startCountdown();
  updateCountdownDisplay();
  addLogEntry('Munkamenet elindítva');
});
</script>

Magyarázat: A felhasználók teljesen kikapcsolhatják az időkorlátot, megakadályozva a kényszerített időzítési korlátozásokat. További opciók közé tartozik a szüneteltetés, újraindítás és az automatikus hosszabbítás.

3. Figyelmeztetés biztosítása időtúllépés előtt

<div class="quiz-container">
  <h2>Online kvíz - Akadálymentesség alapjai</h2>
  
  <div class="quiz-header">
    <div class="quiz-info">
      <span>Kérdés: <strong id="current-question">1</strong> / 5</span>
      <span>Idő: <strong id="quiz-timer">15:00</strong></span>
    </div>
    
    <div class="progress-bar">
      <div id="time-progress" class="progress-fill"></div>
    </div>
  </div>
  
  <div id="question-area" class="question-area">
    <h3>Mi a WCAG rövidítés jelentése?</h3>
    <div class="options">
      <label class="option">
        <input type="radio" name="answer" value="a">
        <span>Web Content Accessibility Guidelines</span>
      </label>
      <label class="option">
        <input type="radio" name="answer" value="b">
        <span>World Computer Access Group</span>
      </label>
      <label class="option">
        <input type="radio" name="answer" value="c">
        <span>Website Creation and Guidelines</span>
      </label>
    </div>
  </div>
  
  <div class="quiz-actions">
    <button onclick="previousQuestion()" id="prev-btn" disabled>Előző</button>
    <button onclick="nextQuestion()" id="next-btn">Következő</button>
    <button onclick="submitQuiz()" id="submit-btn" style="display:none;">Beküldés</button>
  </div>
  
  <!-- Időfigyelmeztetés modal -->
  <div id="time-warning-modal" class="warning-modal" hidden>
    <div class="warning-content">
      <h3>⏰ Időfigyelmeztetés</h3>
      <p>A kvíz <strong id="warning-minutes">5</strong> perc múlva lejár!</p>
      <p>Szeretnéd:</p>
      <div class="warning-actions">
        <button onclick="continueQuiz()" class="btn-continue">Folytatás</button>
        <button onclick="requestMoreTime()" class="btn-extend">Több idő kérése (+10 perc)</button>
        <button onclick="saveAndExit()" class="btn-save">Mentés és kilépés</button>
      </div>
    </div>
  </div>
</div>

<style>
.quiz-container {
  max-width: 700px;
  margin: 20px auto;
  padding: 20px;
  background-color: #f8f9fa;
  border-radius: 8px;
  box-shadow: 0 2px 10px rgba(0,0,0,0.1);
}

.quiz-header {
  margin-bottom: 20px;
}

.quiz-info {
  display: flex;
  justify-content: space-between;
  margin-bottom: 10px;
  font-size: 16px;
}

#quiz-timer {
  color: #007bff;
  font-family: monospace;
  font-size: 18px;
}

#quiz-timer.warning {
  color: #ffc107;
}

#quiz-timer.critical {
  color: #dc3545;
  animation: timer-pulse 1s infinite;
}

@keyframes timer-pulse {
  0%, 100% { transform: scale(1); }
  50% { transform: scale(1.1); }
}

.progress-bar {
  width: 100%;
  height: 8px;
  background-color: #e9ecef;
  border-radius: 4px;
  overflow: hidden;
}

.progress-fill {
  height: 100%;
  background-color: #28a745;
  transition: width 1s linear, background-color 0.3s;
}

.progress-fill.warning {
  background-color: #ffc107;
}

.progress-fill.critical {
  background-color: #dc3545;
}

.question-area {
  background-color: white;
  padding: 30px;
  border-radius: 8px;
  margin-bottom: 20px;
}

.question-area h3 {
  margin-top: 0;
  margin-bottom: 20px;
}

.options {
  display: flex;
  flex-direction: column;
  gap: 15px;
}

.option {
  display: flex;
  align-items: center;
  padding: 15px;
  background-color: #f8f9fa;
  border: 2px solid transparent;
  border-radius: 4px;
  cursor: pointer;
  transition: all 0.2s;
}

.option:hover {
  background-color: #e9ecef;
  border-color: #ced4da;
}

.option input[type="radio"] {
  margin-right: 10px;
  width: 18px;
  height: 18px;
  cursor: pointer;
}

.option input[type="radio"]:focus {
  outline: 2px solid #007bff;
  outline-offset: 2px;
}

.quiz-actions {
  display: flex;
  justify-content: space-between;
  gap: 10px;
}

.quiz-actions button {
  padding: 10px 20px;
  border: none;
  border-radius: 4px;
  font-size: 16px;
  cursor: pointer;
  transition: opacity 0.2s;
}

.quiz-actions button:focus {
  outline: 2px solid #007bff;
  outline-offset: 2px;
}

#prev-btn, #next-btn {
  background-color: #007bff;
  color: white;
}

#prev-btn:hover:not(:disabled), #next-btn:hover {
  background-color: #0056b3;
}

#prev-btn:disabled {
  background-color: #6c757d;
  cursor: not-allowed;
  opacity: 0.6;
}

#submit-btn {
  background-color: #28a745;
  color: white;
}

#submit-btn:hover {
  background-color: #218838;
}

.warning-modal {
  position: fixed;
  top: 0;
  left: 0;
  width: 100%;
  height: 100%;
  background-color: rgba(0, 0, 0, 0.7);
  display: flex;
  justify-content: center;
  align-items: center;
  z-index: 1000;
}

.warning-modal[hidden] {
  display: none;
}

.warning-content {
  background-color: white;
  padding: 30px;
  border-radius: 8px;
  max-width: 450px;
  text-align: center;
  box-shadow: 0 4px 20px rgba(0,0,0,0.3);
}

.warning-content h3 {
  color: #ffc107;
  margin-top: 0;
}

.warning-actions {
  display: flex;
  flex-direction: column;
  gap: 10px;
  margin-top: 20px;
}

.warning-actions button {
  padding: 12px 20px;
  border: none;
  border-radius: 4px;
  font-size: 16px;
  cursor: pointer;
}

.btn-continue {
  background-color: #007bff;
  color: white;
}

.btn-extend {
  background-color: #28a745;
  color: white;
}

.btn-save {
  background-color: #6c757d;
  color: white;
}

.warning-actions button:hover {
  opacity: 0.9;
}

.warning-actions button:focus {
  outline: 2px solid #000;
  outline-offset: 2px;
}
</style>

<script>
let quizTime = 900; // 15 perc
let quizTimer;
let warningIntervals = [300, 60, 30]; // 5 perc, 1 perc, 30 mp
let warningsShown = new Set();
let currentQuestionIndex = 0;
let answers = {};

const questions = [
  {
    question: "Mi a WCAG rövidítés jelentése?",
    options: [
      "Web Content Accessibility Guidelines",
      "World Computer Access Group",
      "Website Creation and Guidelines"
    ],
    correct: 0
  },
  {
    question: "Melyik az ajánlott minimális kontraszt arány normál szöveghez?",
    options: ["3:1", "4.5:1", "7:1"],
    correct: 1
  },
  // További kérdések...
];

function startQuizTimer() {
  updateTimerDisplay();
  updateProgressBar();
  
  quizTimer = setInterval(() => {
    quizTime--;
    updateTimerDisplay();
    updateProgressBar();
    checkWarnings();
    
    if (quizTime <= 0) {
      clearInterval(quizTimer);
      handleQuizTimeout();
    }
  }, 1000);
}

function updateTimerDisplay() {
  const minutes = Math.floor(quizTime / 60);
  const seconds = quizTime % 60;
  const display = document.getElementById('quiz-timer');
  
  display.textContent = `${minutes}:${seconds.toString().padStart(2, '0')}`;
  
  // Vizuális jelzések
  display.classList.remove('warning', 'critical');
  if (quizTime <= 30) {
    display.classList.add('critical');
  } else if (quizTime <= 300) {
    display.classList.add('warning');
  }
}

function updateProgressBar() {
  const totalTime = 900;
  const progress = (quizTime / totalTime) * 100;
  const progressBar = document.getElementById('time-progress');
  
  progressBar.style.width = progress + '%';
  
  progressBar.classList.remove('warning', 'critical');
  if (quizTime <= 30) {
    progressBar.classList.add('critical');
  } else if (quizTime <= 300) {
    progressBar.classList.add('warning');
  }
}

function checkWarnings() {
  warningIntervals.forEach(interval => {
    if (quizTime === interval && !warningsShown.has(interval)) {
      warningsShown.add(interval);
      showTimeWarning(interval);
    }
  });
}

function showTimeWarning(timeLeft) {
  const modal = document.getElementById('time-warning-modal');
  const warningMinutes = Math.floor(timeLeft / 60);
  
  document.getElementById('warning-minutes').textContent = 
    timeLeft >= 60 ? warningMinutes : `${timeLeft} másodperc`;
  
  modal.hidden = false;
  
  // Fókusz a folytatás gombra
  setTimeout(() => {
    document.querySelector('.btn-continue').focus();
  }, 100);
  
  // Hangjelzés
  playWarningSound();
  
  // Képernyőolvasó értesítés
  announceToScreenReader(`Figyelmeztetés: A kvíz ${warningMinutes} perc múlva lejár.`);
}

function continueQuiz() {
  document.getElementById('time-warning-modal').hidden = true;
}

function requestMoreTime() {
  quizTime += 600; // +10 perc
  document.getElementById('time-warning-modal').hidden = true;
  
  showNotification('10 perc hozzáadva a kvíz idejéhez');
  announceToScreenReader('10 perccel meghosszabbítva a kvíz ideje');
}

function saveAndExit() {
  saveQuizProgress();
  clearInterval(quizTimer);
  
  alert('A kvíz állapota mentve. Később folytathatod.');
  // Itt lenne a mentés és kilépés logika
}

function saveQuizProgress() {
  const quizState = {
    currentQuestion: currentQuestionIndex,
    answers: answers,
    timeRemaining: quizTime,
    timestamp: new Date().toISOString()
  };
  
  localStorage.setItem('quizProgress', JSON.stringify(quizState));
}

function handleQuizTimeout() {
  alert('Az idő lejárt! A válaszaid automatikusan beküldésre kerültek.');
  submitQuiz();
}

function playWarningSound() {
  // Egyszerű hangjelzés
  const audio = new Audio('data:audio/wav;base64,UklGRnoGAABXQVZFZm10IBAAAAABAAEARKwAAIhYAQACABAAZGF0YQoGAACBhYqFbF1fdJivrJBhNjVgodDbq2EcBj+a2/LDciUFLIHO8tiJNwgZaLvt559NEAxQp+PwtmMcBjiR1/LMeSwFJHfH8N2QQAoUXrTp66hVFApGn+DyvmwhBTGH0fPTgjMGHm7A7+OZURE');
  audio.play().catch(e => console.log('Hangjelzés nem játszható le'));
}

function announceToScreenReader(message) {
  const announcement = document.createElement('div');
  announcement.setAttribute('role', 'alert');
  announcement.setAttribute('aria-live', 'assertive');
  announcement.className = 'sr-only';
  announcement.textContent = message;
  
  document.body.appendChild(announcement);
  setTimeout(() => announcement.remove(), 3000);
}

function showNotification(message) {
  const notification = document.createElement('div');
  notification.textContent = message;
  notification.style.cssText = `
    position: fixed;
    top: 20px;
    right: 20px;
    background-color: #28a745;
    color: white;
    padding: 15px 20px;
    border-radius: 4px;
    box-shadow: 0 2px 5px rgba(0,0,0,0.2);
    z-index: 1001;
  `;
  document.body.appendChild(notification);
  
  setTimeout(() => notification.remove(), 4000);
}

// Kvíz navigáció (egyszerűsített)
function nextQuestion() {
  // Válasz mentése
  const selectedOption = document.querySelector('input[name="answer"]:checked');
  if (selectedOption) {
    answers[currentQuestionIndex] = selectedOption.value;
  }
  
  currentQuestionIndex++;
  if (currentQuestionIndex >= questions.length - 1) {
    document.getElementById('next-btn').style.display = 'none';
    document.getElementById('submit-btn').style.display = 'block';
  }
  document.getElementById('prev-btn').disabled = false;
  
  // Következő kérdés betöltése...
  document.getElementById('current-question').textContent = currentQuestionIndex + 1;
}

function previousQuestion() {
  currentQuestionIndex--;
  if (currentQuestionIndex <= 0) {
    document.getElementById('prev-btn').disabled = true;
  }
  document.getElementById('next-btn').style.display = 'block';
  document.getElementById('submit-btn').style.display = 'none';
  
  document.getElementById('current-question').textContent = currentQuestionIndex + 1;
}

function submitQuiz() {
  clearInterval(quizTimer);
  saveQuizProgress();
  alert('Kvíz beküldve! Köszönjük a részvételt.');
}

// Stílusok a képernyőolvasóknak
const style = document.createElement('style');
style.textContent = `
  .sr-only {
    position: absolute;
    width: 1px;
    height: 1px;
    padding: 0;
    margin: -1px;
    overflow: hidden;
    clip: rect(0, 0, 0, 0);
    white-space: nowrap;
    border: 0;
  }
`;
document.head.appendChild(style);

// Kvíz indítása
window.addEventListener('load', () => {
  startQuizTimer();
});
</script>

Magyarázat: A felhasználók időben figyelmeztetést kapnak, lehetőséget adva nekik a felkészülésre vagy a munkamenet meghosszabbítására. Többszintű figyelmeztetések és választási lehetőségek biztosítottak.

Rossz gyakorlatok

Kemény időkorlátok kiterjesztés vagy figyelmeztetés nélkül

<script>
// ROSSZ: Nincs mód az időkorlát kiterjesztésére
setTimeout(() => {
  alert('Az idő lejárt! Ki leszel jelentkeztetve.');
  // Automatikus kijelentkeztetés
  window.location.href = '/logout';
}, 60000); // 1 perc után

// ROSSZ: Űrlap automatikus beküldése figyelmeztetés nélkül
setTimeout(() => {
  document.getElementById('form').submit();
  // Felhasználó elveszítheti az adatokat
}, 300000); // 5 perc
</script>

Probléma: A felhasználóknak nincs módjuk kiterjeszteni vagy letiltani az időkorlátot, ami frusztrációt vagy adatvesztést okozhat.

Láthatatlan vagy hozzáférhetetlen idővezérlők

<!-- ROSSZ: Az időkiterjesztés csak vizuálisan látható -->
<div class="timeout-warning" style="position: absolute; top: -9999px;">
  Az idő hamarosan lejár!
  <!-- A gomb csak egérrel kattintható -->
  <span onclick="extendTime()" style="cursor: pointer; color: blue;">
    Kattints ide több időért
  </span>
</div>

<!-- ROSSZ: Időkijelző képként -->
<img src="timer-display.png" alt="">
<!-- Képernyőolvasók nem tudják felolvasni az időt -->

Probléma: Ha az idő kiterjesztésének lehetősége csak vizuálisan elérhető vagy egérinterakcióval, a billentyűzet és képernyőolvasó felhasználók nem férhetnek hozzá.

Rövid időkorlátok komplex feladatokra

<script>
// ROSSZ: 30 másodperc egy hosszú űrlap kitöltésére
let formTimeout = setTimeout(() => {
  document.getElementById('complex-form').reset();
  alert('Időtúllépés! Az űrlap törlésre került.');
}, 30000);

// ROSSZ: 1 perc egy 50 kérdéses teszt megoldására
startTest({
  questions: 50,
  timeLimit: 60, // másodperc
  noExtension: true
});
</script>

Probléma: Rövid időkorlátok alkalmazása űrlapokon vagy teszteken a felhasználói igények figyelembevétele nélkül akadálymentességi akadályokat teremt.

Időkorlát figyelmeztetés nélküli újraindítása

<script>
// ROSSZ: Időzítő újraindul minden interakciónál értesítés nélkül
let inactivityTimer;

function resetTimer() {
  clearTimeout(inactivityTimer);
  inactivityTimer = setTimeout(() => {
    // Hirtelen kijelentkeztetés
    window.location.href = '/logout';
  }, 120000); // 2 perc
}

// Minden kattintás újraindítja
document.addEventListener('click', resetTimer);
document.addEventListener('keypress', resetTimer);

// Felhasználó nem tudja, mennyi ideje van
</script>

Probléma: A felhasználók nem tudják, hogy az időzítő újraindul, és nem látják, mennyi idejük van hátra.

Nem megszakítható automatikus frissítés

<!-- ROSSZ: Automatikus újratöltés vezérlés nélkül -->
<meta http-equiv="refresh" content="60">

<script>
// ROSSZ: JavaScript alapú frissítés letiltási lehetőség nélkül
setInterval(() => {
  location.reload();
  // Elveszhetnek a nem mentett adatok
}, 60000);

// ROSSZ: AJAX frissítés figyelmeztetés nélkül
setInterval(() => {
  fetch('/update-content')
    .then(response => response.text())
    .then(html => {
      document.body.innerHTML = html;
      // Fókusz és állapot elvész
    });
}, 30000);
</script>

Probléma: Az automatikus frissítések megszakítják a felhasználó munkáját, elveszítik a fókuszt és az űrlap adatokat, különösen problémás a képernyőolvasó felhasználók számára.

Források

Iratkozz fel hírlevelünkre!

Amennyiben szeretnél első kézből értesülni az új bejegyzésekről, iratkozz fel hírlevelünkre!

Ez a weboldal sütiket használ a böngészési élmény javítása és a webhely megfelelő működésének biztosítása érdekében. A webhely használatának folytatásával elismeri és elfogadja a sütik használatát.

Összes elfogadása Csak a szükségesek elfogadása