Преглед изворни кода

Add recent lock attempt history

Евгений пре 2 недеља
родитељ
комит
5e7eaa9100

+ 11 - 0
ПМ.03.02/2.2.500 Ерошко/css/components.css

@@ -48,6 +48,17 @@ button:disabled { cursor: not-allowed; }
 .event-log > span { color: var(--muted); font: 8px var(--mono); letter-spacing: .08em; }
 .event-log p { color: var(--muted); margin: 5px 0 0; min-height: 34px; font-size: 11px; line-height: 1.6; }
 .event-log { padding: 12px; border: 1px dashed var(--line); border-radius: 6px; background: color-mix(in srgb, var(--panel-2) 60%, transparent); }
+.attempt-log { margin-top: 10px; padding: 12px; border: 1px solid var(--line); border-radius: 6px; background: color-mix(in srgb, var(--panel-2) 42%, transparent); }
+.attempt-log__heading { display: flex; align-items: center; justify-content: space-between; gap: 8px; color: var(--muted); font: 8px var(--mono); letter-spacing: .08em; }
+.attempt-log__heading .text-button { padding: 2px 0; font: 9px var(--sans); letter-spacing: 0; }
+.attempt-log__heading .text-button:disabled { opacity: .45; cursor: default; }
+.attempt-log ol { display: grid; gap: 5px; margin: 9px 0 0; padding: 0; list-style: none; }
+.attempt-log__empty { color: var(--muted); font-size: 10px; }
+.attempt-log__item { display: grid; grid-template-columns: minmax(45px, .7fr) minmax(52px, 1fr) auto; align-items: center; gap: 8px; padding: 6px 8px; border-radius: 4px; background: var(--panel-2); font-size: 10px; }
+.attempt-log__code, .attempt-log__item time { font-family: var(--mono); }
+.attempt-log__result { color: var(--danger); }
+.attempt-log__item--success .attempt-log__result { color: var(--accent); }
+.attempt-log__item time { color: var(--muted); font-size: 9px; }
 .explanation { display: flex; gap: 14px; padding: 16px 0; border-block: 1px solid var(--line); }
 .explanation__number { color: var(--accent); font: 20px/1.3 var(--mono); flex-shrink: 0; }
 .explanation p { margin: 0; font-size: 11px; color: var(--muted); line-height: 1.7; }

+ 4 - 0
ПМ.03.02/2.2.500 Ерошко/index.html

@@ -56,6 +56,10 @@
           <div class="readout lock-readout"><span>РЕЖИМ ЗАМКА</span><strong id="lockReadout">ОЖИДАНИЕ КОДА</strong><span class="readout-dot" id="powerDot"></span></div>
           <button class="button button--primary close-lock-button" id="closeLockButton" disabled>Закрыть замок</button>
           <div class="event-log" aria-live="polite"><span>ЖУРНАЛ СОБЫТИЙ</span><p id="eventLog">Система инициализирована. Ожидание ввода.</p></div>
+          <div class="attempt-log" aria-labelledby="attemptLogTitle">
+            <div class="attempt-log__heading"><span id="attemptLogTitle">ПОСЛЕДНИЕ ПОПЫТКИ</span><button class="text-button" id="clearAttemptLog" type="button" disabled>Очистить</button></div>
+            <ol id="attemptLogList"><li class="attempt-log__empty">Попыток пока нет.</li></ol>
+          </div>
         </section>
 
         <section class="panel keypad-panel">

+ 20 - 2
ПМ.03.02/2.2.500 Ерошко/js/app.js

@@ -23,6 +23,22 @@ const quiz = [
 
 function tone(type = 'click') { if (!soundEnabled || !window.AudioContext) return; const ctx = new AudioContext(); const osc = ctx.createOscillator(); const gain = ctx.createGain(); osc.connect(gain); gain.connect(ctx.destination); const now = ctx.currentTime; osc.type = type === 'error' ? 'sawtooth' : 'sine'; osc.frequency.setValueAtTime(type === 'success' ? 780 : type === 'error' ? 140 : 350, now); if (type === 'success') osc.frequency.exponentialRampToValueAtTime(1180, now + .12); gain.gain.setValueAtTime(.06, now); gain.gain.exponentialRampToValueAtTime(.001, now + (type === 'error' ? .3 : .12)); osc.start(now); osc.stop(now + .35); }
 function setLog(text) { $('#eventLog').textContent = text; }
+const attemptHistory = [];
+const attemptLogList = $('#attemptLogList');
+function recordAttempt(code, result) {
+  attemptHistory.unshift({ code, result, time: new Date() });
+  attemptHistory.length = Math.min(attemptHistory.length, 5);
+  renderAttemptHistory();
+}
+function renderAttemptHistory() {
+  $('#clearAttemptLog').disabled = attemptHistory.length === 0;
+  if (!attemptHistory.length) {
+    attemptLogList.innerHTML = '<li class="attempt-log__empty">Попыток пока нет.</li>';
+    return;
+  }
+  attemptLogList.innerHTML = attemptHistory.map(({code, result, time}) => `<li class="attempt-log__item attempt-log__item--${result === 'Успех' ? 'success' : 'error'}"><span class="attempt-log__code">${code.split('').join(' ')}</span><span class="attempt-log__result">${result}</span><time datetime="${time.toISOString()}">${time.toLocaleTimeString('ru-RU', {hour:'2-digit', minute:'2-digit', second:'2-digit'})}</time></li>`).join('');
+}
+$('#clearAttemptLog').onclick = () => { attemptHistory.length = 0; renderAttemptHistory(); };
 function updateCircuit(state = circuitState) {
   circuitState = state;
   const powered = state === 'open';
@@ -92,9 +108,11 @@ function openLock(fromExit = false) {
   $('#lamp').className = 'lamp is-success';
   $('#powerDot').classList.add('is-on');
   setLog(fromExit ? 'S2 удерживается. Прямое питание YA1; нажмите S2 повторно, чтобы отпустить.' : 'Код принят. VT5 / VT2 включили YA1. Замок открыт до нажатия «Закрыть замок» или «Сбросить».');
+  if (!fromExit) recordAttempt(entered.join(''), 'Успех');
   tone('success');
 }
-function incorrect() {
+function incorrect(key) {
+  recordAttempt(entered.join('') + key, 'Ошибка');
   entered = [];
   circuitState = 'error';
   updateScreen();
@@ -108,7 +126,7 @@ function input(key) {
   tone();
   pulseInput(key);
   const candidate = entered.join('') + key;
-  if (!activeCode.startsWith(candidate)) return incorrect();
+  if (!activeCode.startsWith(candidate)) return incorrect(key);
   entered.push(key);
   updateScreen();
   if (candidate === activeCode) return openLock();