Weiter gebaut
Some checks failed
Host-Based Deploy (Java 21 Fix) / build-and-run (push) Has been cancelled
Some checks failed
Host-Based Deploy (Java 21 Fix) / build-and-run (push) Has been cancelled
This commit is contained in:
@@ -57,27 +57,90 @@
|
||||
}
|
||||
.nextcard-cards {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 0.6rem;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
position: relative;
|
||||
border-radius: 6px;
|
||||
padding: 0.75rem 0.5rem 0.5rem;
|
||||
overflow: visible;
|
||||
padding: 0.75rem 0 1rem;
|
||||
}
|
||||
.nc-window {
|
||||
flex: 1;
|
||||
overflow-x: hidden;
|
||||
overflow-y: visible;
|
||||
min-width: 0;
|
||||
padding-top: 14px;
|
||||
position: relative;
|
||||
}
|
||||
.nc-slide-wrapper {
|
||||
display: flex;
|
||||
will-change: transform;
|
||||
}
|
||||
.nextcard-card-img {
|
||||
width: calc((100% - 5 * 0.6rem) / 6);
|
||||
flex-shrink: 0;
|
||||
width: 130px;
|
||||
height: auto;
|
||||
border-radius: 6px;
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
margin-right: var(--nc-gap, 6px);
|
||||
transition: transform 0.15s, box-shadow 0.15s;
|
||||
}
|
||||
.nextcard-card-img:last-child { margin-right: 0; }
|
||||
.nextcard-panel.drawable .nextcard-card-img:hover {
|
||||
transform: translateY(-8px) scale(1.06);
|
||||
transform: translateY(-10px) scale(1.08);
|
||||
box-shadow: 0 8px 20px rgba(0,0,0,0.4);
|
||||
z-index: 10;
|
||||
cursor: pointer;
|
||||
}
|
||||
.nc-nav-btn {
|
||||
flex-shrink: 0;
|
||||
width: 48px;
|
||||
background: var(--color-secondary);
|
||||
border: 1px solid rgba(255,255,255,0.15);
|
||||
border-radius: 8px;
|
||||
color: var(--color-text);
|
||||
font-size: 0.72rem;
|
||||
font-weight: 600;
|
||||
padding: 0.5rem 0.2rem;
|
||||
cursor: pointer;
|
||||
text-align: center;
|
||||
line-height: 1.4;
|
||||
align-self: stretch;
|
||||
display: none;
|
||||
transition: background 0.15s;
|
||||
}
|
||||
.nc-nav-btn:hover { background: var(--color-primary); }
|
||||
/* ── Touch-Karussell ── */
|
||||
.nextcard-cards.carousel-mode {
|
||||
overflow: hidden;
|
||||
height: 210px;
|
||||
padding: 1rem 0 1rem;
|
||||
justify-content: center;
|
||||
}
|
||||
.carousel-card {
|
||||
position: absolute;
|
||||
width: 100px;
|
||||
border-radius: 8px;
|
||||
box-shadow: 2px 4px 14px rgba(0,0,0,0.55);
|
||||
transition: transform 0.18s cubic-bezier(.4,0,.2,1), opacity 0.18s;
|
||||
user-select: none;
|
||||
-webkit-user-drag: none;
|
||||
}
|
||||
.carousel-card.pos-center { transform: translateX(0) scale(1.18); opacity: 1; z-index: 5; }
|
||||
.carousel-card.pos-left { transform: translateX(-120px) scale(0.84); opacity: 0.72; z-index: 3; }
|
||||
.carousel-card.pos-right { transform: translateX(120px) scale(0.84); opacity: 0.72; z-index: 3; }
|
||||
.carousel-card.pos-far-left { transform: translateX(-210px) scale(0.68); opacity: 0.4; z-index: 1; }
|
||||
.carousel-card.pos-far-right { transform: translateX(210px) scale(0.68); opacity: 0.4; z-index: 1; }
|
||||
.carousel-card.pos-exit-left {
|
||||
transform: translateX(-400px) scale(0.4) rotate(-15deg);
|
||||
opacity: 0; z-index: 0;
|
||||
transition: transform 0.32s ease-in, opacity 0.32s ease-in;
|
||||
}
|
||||
.carousel-card.pos-exit-right {
|
||||
transform: translateX(400px) scale(0.4) rotate(15deg);
|
||||
opacity: 0; z-index: 0;
|
||||
transition: transform 0.32s ease-in, opacity 0.32s ease-in;
|
||||
}
|
||||
.nextcard-panel.drawable .carousel-card.pos-center { cursor: pointer; }
|
||||
.nextcard-overlay {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
@@ -595,6 +658,12 @@
|
||||
<button class="btn-hygiene" id="hygieneBtn" style="display:none;" onclick="openHygieneModal()">🚿 Hygiene-Öffnung</button>
|
||||
</div>
|
||||
|
||||
<!-- Speed-Effekt-Panel -->
|
||||
<div id="speedPanel" style="display:none;background:var(--color-card);border:1px solid var(--color-secondary);border-radius:12px;padding:0.85rem 1.1rem;gap:0.35rem;">
|
||||
<div style="font-size:0.75rem;text-transform:uppercase;letter-spacing:0.06em;color:var(--color-muted);" id="speedPanelTitle">Slow Motion aktiv</div>
|
||||
<div style="font-size:0.9rem;font-weight:600;" id="speedPanelInfo">–</div>
|
||||
</div>
|
||||
|
||||
<!-- Verifikations-Panel -->
|
||||
<div class="verification-panel" id="verificationPanel" style="display:none;">
|
||||
<div class="verification-panel-title">Tägliche Verifikation</div>
|
||||
@@ -724,6 +793,12 @@
|
||||
<span id="drawTaskPendingText"></span>
|
||||
</div>
|
||||
|
||||
<!-- Speed-Karte: Zeitpunkt wählen -->
|
||||
<div id="drawSpeedPicker" style="display:none;margin-top:0.75rem;padding:0.75rem 1rem;border-radius:8px;background:rgba(100,149,237,0.10);border:1px solid rgba(100,149,237,0.3);gap:0.6rem;text-align:center;">
|
||||
<div style="font-size:0.88rem;color:var(--color-text);">Wähle den Zeitpunkt, bis zu dem der Effekt aktiv sein soll:</div>
|
||||
<input type="datetime-local" id="drawSpeedUntilInput" style="background:var(--color-secondary);border:1px solid var(--color-secondary);border-radius:7px;padding:0.45rem 0.75rem;color:var(--color-text);font-size:0.9rem;width:100%;box-sizing:border-box;">
|
||||
</div>
|
||||
|
||||
<!-- Grüne Karte: Entscheidung -->
|
||||
<div class="draw-green-choice" id="drawGreenChoice">
|
||||
<p id="drawGreenText" style="text-align:center;font-size:0.88rem;color:var(--color-muted);margin:0;">
|
||||
@@ -735,6 +810,8 @@
|
||||
<div class="draw-modal-actions" id="drawModalActions" style="display:none;">
|
||||
<!-- Non-green: OK -->
|
||||
<button class="btn-draw-ok" id="btnDrawOk" onclick="closeDrawModal()">OK</button>
|
||||
<!-- Speed-Karte: Bestätigen -->
|
||||
<button class="btn-draw-ok" id="btnSpeedConfirm" style="display:none;" onclick="confirmSpeedCard()">✓ Bestätigen</button>
|
||||
<!-- Green: zwei Optionen -->
|
||||
<button class="btn-draw-unlock" id="btnDrawUnlock" style="display:none;" onclick="confirmUnlock()">🔓 Entsperren</button>
|
||||
<button class="btn-draw-keep" id="btnDrawKeep" style="display:none;" onclick="keepGreenCard()">Zurücklegen</button>
|
||||
@@ -833,6 +910,7 @@
|
||||
renderAssignedTasks(lock);
|
||||
renderNextCardPanel(lock);
|
||||
renderHygienePanel(lock);
|
||||
renderSpeedPanel(lock);
|
||||
renderVerificationPanel(lock);
|
||||
renderTempOpeningPanel(lock);
|
||||
renderCardsPanel(lock);
|
||||
@@ -1095,17 +1173,14 @@
|
||||
panel.style.display = '';
|
||||
panel.classList.remove('drawable');
|
||||
|
||||
// Karten-Bilder rendern (Overlay bleibt erhalten)
|
||||
cardsDiv.querySelectorAll('.nextcard-card-img').forEach(el => el.remove());
|
||||
const total = lock.totalCards || 0;
|
||||
const show = Math.min(total, 36);
|
||||
for (let i = 0; i < show; i++) {
|
||||
const img = document.createElement('img');
|
||||
img.className = 'nextcard-card-img';
|
||||
img.src = '/img/card.png';
|
||||
img.alt = 'Karte';
|
||||
cardsDiv.insertBefore(img, overlay);
|
||||
}
|
||||
// Karten-Darstellung aufbauen
|
||||
if (ncResizeObs) { ncResizeObs.disconnect(); ncResizeObs = null; }
|
||||
cardsDiv.querySelectorAll('.nc-nav-btn, .nc-window, .carousel-card').forEach(el => el.remove());
|
||||
cardsDiv.classList.remove('carousel-mode');
|
||||
const total = lock.totalCards || 0;
|
||||
const isTouch = window.matchMedia('(hover: none) and (pointer: coarse)').matches;
|
||||
if (isTouch) initCarousel(cardsDiv, overlay, total);
|
||||
else initCardWindow(cardsDiv, overlay, total);
|
||||
|
||||
// Overlay-Elemente
|
||||
const timerBox = document.getElementById('nextcardTimerBox');
|
||||
@@ -1314,21 +1389,254 @@
|
||||
// ── Karte ziehen ──
|
||||
let drawnUnlockCode = null;
|
||||
|
||||
function enableCardClick() {
|
||||
document.querySelectorAll('.nextcard-card-img').forEach(img => {
|
||||
img.addEventListener('click', onCardClick, { once: true });
|
||||
// ── Touch-Karussell ──
|
||||
const CAROUSEL_POS = ['pos-far-left', 'pos-left', 'pos-center', 'pos-right', 'pos-far-right'];
|
||||
let carouselCards = [];
|
||||
let carouselShifting = false;
|
||||
let carouselTouchX = 0;
|
||||
let carouselTouchT = 0;
|
||||
let carouselAbort = null;
|
||||
let carouselDrawable = false;
|
||||
|
||||
function initCarousel(cardsDiv, overlay, total) {
|
||||
if (carouselAbort) carouselAbort.abort();
|
||||
carouselAbort = new AbortController();
|
||||
const signal = carouselAbort.signal;
|
||||
carouselCards = [];
|
||||
carouselShifting = false;
|
||||
carouselDrawable = false;
|
||||
cardsDiv.classList.add('carousel-mode');
|
||||
|
||||
for (let i = 0; i < 5; i++) {
|
||||
const img = document.createElement('img');
|
||||
img.className = 'carousel-card ' + CAROUSEL_POS[i];
|
||||
img.src = '/img/card.png';
|
||||
img.alt = 'Karte';
|
||||
cardsDiv.insertBefore(img, overlay);
|
||||
carouselCards.push(img);
|
||||
}
|
||||
|
||||
cardsDiv.addEventListener('touchstart', e => {
|
||||
carouselTouchX = e.touches[0].clientX;
|
||||
carouselTouchT = Date.now();
|
||||
}, { passive: true, signal });
|
||||
|
||||
cardsDiv.addEventListener('touchend', e => {
|
||||
if (carouselShifting) return;
|
||||
const touch = e.changedTouches[0];
|
||||
const dx = touch.clientX - carouselTouchX;
|
||||
const dt = Date.now() - carouselTouchT;
|
||||
const absDx = Math.abs(dx);
|
||||
|
||||
if (absDx < 5) {
|
||||
// Tap: Element unter dem Finger bestimmen
|
||||
const el = document.elementFromPoint(touch.clientX, touch.clientY);
|
||||
const idx = el ? carouselCards.indexOf(el) : -1;
|
||||
if (idx < 0) return;
|
||||
if (idx === 2 && carouselDrawable) {
|
||||
// Mittlere Karte → Karte ziehen
|
||||
e.preventDefault();
|
||||
carouselDrawable = false;
|
||||
carouselCards.forEach(c => c.style.pointerEvents = 'none');
|
||||
openDrawModal();
|
||||
} else if (idx < 2) {
|
||||
_doCarouselShift('right', 1);
|
||||
} else {
|
||||
_doCarouselShift('left', 1);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (!carouselDrawable) return;
|
||||
|
||||
// Swipe erkannt: Geschwindigkeit bestimmt wie viele Karten wechseln
|
||||
const velocity = absDx / dt;
|
||||
const steps = velocity > 1.2 ? 3 : velocity > 0.6 ? 2 : 1;
|
||||
dx < 0 ? _doCarouselShift('left', steps) : _doCarouselShift('right', steps);
|
||||
}, { passive: false, signal });
|
||||
}
|
||||
|
||||
function shiftCarouselLeft(steps = 1) { _doCarouselShift('left', steps); }
|
||||
function shiftCarouselRight(steps = 1) { _doCarouselShift('right', steps); }
|
||||
|
||||
function _doCarouselShift(dir, remaining) {
|
||||
if (carouselShifting && remaining === /* first call */ remaining) {/* skip guard for chained */}
|
||||
carouselShifting = true;
|
||||
if (dir === 'left') {
|
||||
carouselCards[0].className = 'carousel-card pos-exit-left';
|
||||
for (let i = 1; i < 5; i++) carouselCards[i].className = 'carousel-card ' + CAROUSEL_POS[i - 1];
|
||||
} else {
|
||||
carouselCards[4].className = 'carousel-card pos-exit-right';
|
||||
for (let i = 3; i >= 0; i--) carouselCards[i].className = 'carousel-card ' + CAROUSEL_POS[i + 1];
|
||||
}
|
||||
setTimeout(() => {
|
||||
if (dir === 'left') {
|
||||
const ex = carouselCards.shift();
|
||||
ex.style.transition = 'none';
|
||||
ex.className = 'carousel-card pos-exit-right';
|
||||
ex.getBoundingClientRect();
|
||||
ex.style.transition = '';
|
||||
ex.className = 'carousel-card pos-far-right';
|
||||
carouselCards.push(ex);
|
||||
} else {
|
||||
const ex = carouselCards.pop();
|
||||
ex.style.transition = 'none';
|
||||
ex.className = 'carousel-card pos-exit-left';
|
||||
ex.getBoundingClientRect();
|
||||
ex.style.transition = '';
|
||||
ex.className = 'carousel-card pos-far-left';
|
||||
carouselCards.unshift(ex);
|
||||
}
|
||||
if (remaining > 1) _doCarouselShift(dir, remaining - 1);
|
||||
else carouselShifting = false;
|
||||
}, 180);
|
||||
}
|
||||
|
||||
// ── Karten-Fenster ──
|
||||
let ncTotal = 0;
|
||||
let ncWindowStart = 0;
|
||||
let ncVisibleCount = 0;
|
||||
let ncDrawable = false;
|
||||
let ncWindowEl = null;
|
||||
let ncBtnLeft = null;
|
||||
let ncBtnRight = null;
|
||||
let ncResizeObs = null;
|
||||
|
||||
function initCardWindow(cardsDiv, overlay, total) {
|
||||
ncTotal = total;
|
||||
ncDrawable = false;
|
||||
ncWindowStart = 0;
|
||||
|
||||
if (total === 0) return;
|
||||
|
||||
ncBtnLeft = document.createElement('button');
|
||||
ncBtnRight = document.createElement('button');
|
||||
ncWindowEl = document.createElement('div');
|
||||
const wrapper = document.createElement('div');
|
||||
|
||||
ncBtnLeft.className = 'nc-nav-btn';
|
||||
ncBtnRight.className = 'nc-nav-btn';
|
||||
ncWindowEl.className = 'nc-window';
|
||||
wrapper.className = 'nc-slide-wrapper';
|
||||
|
||||
ncBtnLeft.addEventListener('click', () => scrollCardWindow(-1));
|
||||
ncBtnRight.addEventListener('click', () => scrollCardWindow(1));
|
||||
|
||||
ncWindowEl.appendChild(wrapper);
|
||||
cardsDiv.insertBefore(ncBtnLeft, overlay);
|
||||
cardsDiv.insertBefore(ncWindowEl, overlay);
|
||||
cardsDiv.insertBefore(ncBtnRight, overlay);
|
||||
|
||||
// Klick auf beliebige Karte → Karte ziehen (per Delegation)
|
||||
ncWindowEl.addEventListener('click', e => {
|
||||
if (!ncDrawable) return;
|
||||
if (e.target.classList.contains('nextcard-card-img')) {
|
||||
ncDrawable = false;
|
||||
ncWindowEl.style.pointerEvents = 'none';
|
||||
openDrawModal();
|
||||
}
|
||||
});
|
||||
|
||||
if (ncResizeObs) ncResizeObs.disconnect();
|
||||
ncResizeObs = new ResizeObserver(() => recalcCardWindow());
|
||||
ncResizeObs.observe(ncWindowEl);
|
||||
|
||||
requestAnimationFrame(() => recalcCardWindow(true));
|
||||
}
|
||||
|
||||
function recalcCardWindow(initialCenter = false) {
|
||||
const slots = Math.round(ncWindowEl.offsetWidth / 50);
|
||||
const newCount = Math.min(ncTotal, Math.min(20, Math.max(3, slots)));
|
||||
if (newCount === ncVisibleCount && !initialCenter) {
|
||||
// Breite hat sich nicht verändert genug → nur Gap neu berechnen
|
||||
renderCardWindow(null);
|
||||
return;
|
||||
}
|
||||
ncVisibleCount = newCount;
|
||||
if (initialCenter) {
|
||||
ncWindowStart = Math.max(0, Math.floor(ncTotal / 2) - Math.floor(ncVisibleCount / 2));
|
||||
} else {
|
||||
// Fensterstart so anpassen, dass die Mitte des sichtbaren Bereichs gleich bleibt
|
||||
const mid = ncWindowStart + Math.floor(ncVisibleCount / 2);
|
||||
ncWindowStart = Math.max(0, Math.min(ncTotal - ncVisibleCount, mid - Math.floor(ncVisibleCount / 2)));
|
||||
}
|
||||
renderCardWindow(null);
|
||||
}
|
||||
|
||||
function renderCardWindow(slideDir) {
|
||||
const wrapper = ncWindowEl.querySelector('.nc-slide-wrapper');
|
||||
const cardW = 130;
|
||||
|
||||
// Karten neu befüllen
|
||||
wrapper.innerHTML = '';
|
||||
for (let i = 0; i < ncVisibleCount; i++) {
|
||||
const img = document.createElement('img');
|
||||
img.className = 'nextcard-card-img';
|
||||
img.src = '/img/card.png';
|
||||
img.alt = 'Karte';
|
||||
wrapper.appendChild(img);
|
||||
}
|
||||
|
||||
// Dynamischer Overlap: alle sichtbaren Karten füllen die Fensterbreite
|
||||
const windowW = ncWindowEl.offsetWidth;
|
||||
let gap = ncVisibleCount <= 1 ? 6 : (windowW - ncVisibleCount * cardW) / (ncVisibleCount - 1);
|
||||
gap = Math.max(-105, Math.min(12, gap));
|
||||
ncWindowEl.style.setProperty('--nc-gap', gap + 'px');
|
||||
|
||||
// Animation: alte Karten raus, neue rein
|
||||
if (slideDir) {
|
||||
const oldWrapper = wrapper.cloneNode(true);
|
||||
oldWrapper.style.cssText = 'position:absolute;top:0;left:0;width:100%;pointer-events:none;';
|
||||
ncWindowEl.appendChild(oldWrapper);
|
||||
|
||||
// Neue Karten von der Seite einblenden
|
||||
wrapper.style.transition = 'none';
|
||||
wrapper.style.transform = `translateX(${slideDir > 0 ? '100%' : '-100%'})`;
|
||||
wrapper.getBoundingClientRect();
|
||||
wrapper.style.transition = 'transform 0.28s ease';
|
||||
wrapper.style.transform = 'translateX(0)';
|
||||
|
||||
// Alte Karten zur anderen Seite ausblenden
|
||||
oldWrapper.style.transition = 'transform 0.28s ease';
|
||||
oldWrapper.style.transform = `translateX(${slideDir > 0 ? '-100%' : '100%'})`;
|
||||
setTimeout(() => oldWrapper.remove(), 300);
|
||||
}
|
||||
|
||||
// Nav-Buttons aktualisieren
|
||||
const leftCount = ncWindowStart;
|
||||
const rightCount = ncTotal - ncWindowStart - ncVisibleCount;
|
||||
ncBtnLeft.style.display = leftCount > 0 ? 'block' : 'none';
|
||||
ncBtnRight.style.display = rightCount > 0 ? 'block' : 'none';
|
||||
ncBtnLeft.textContent = `◀\n${leftCount}`;
|
||||
ncBtnRight.textContent = `${rightCount}\n▶`;
|
||||
ncWindowEl.style.pointerEvents = '';
|
||||
}
|
||||
|
||||
function scrollCardWindow(dir) {
|
||||
const step = Math.max(1, Math.floor(ncVisibleCount / 2));
|
||||
if (dir < 0) ncWindowStart = Math.max(0, ncWindowStart - step);
|
||||
else ncWindowStart = Math.min(ncTotal - ncVisibleCount, ncWindowStart + step);
|
||||
renderCardWindow(dir);
|
||||
}
|
||||
|
||||
function enableCardClick() {
|
||||
if (carouselCards.length > 0) {
|
||||
carouselDrawable = true;
|
||||
} else if (ncWindowEl) {
|
||||
ncDrawable = true;
|
||||
}
|
||||
}
|
||||
|
||||
async function onCardClick() {
|
||||
// Alle weiteren Klicks blockieren
|
||||
document.querySelectorAll('.nextcard-card-img').forEach(img => {
|
||||
img.removeEventListener('click', onCardClick);
|
||||
img.style.pointerEvents = 'none';
|
||||
});
|
||||
carouselDrawable = false;
|
||||
carouselCards.forEach(c => c.style.pointerEvents = 'none');
|
||||
if (ncWindowEl) ncWindowEl.style.pointerEvents = 'none';
|
||||
document.querySelectorAll('.nextcard-card-img').forEach(img => img.style.pointerEvents = 'none');
|
||||
openDrawModal();
|
||||
}
|
||||
|
||||
let _pendingSpeedMode = null;
|
||||
|
||||
function openDrawModal() {
|
||||
const modal = document.getElementById('drawModal');
|
||||
const inner = document.getElementById('flipCardInner');
|
||||
@@ -1343,11 +1651,14 @@
|
||||
document.getElementById('drawGreenText').style.display = '';
|
||||
document.getElementById('drawUnlockCode').style.display = 'none';
|
||||
document.getElementById('drawTaskPendingHint').style.display = 'none';
|
||||
document.getElementById('drawSpeedPicker').style.display = 'none';
|
||||
document.getElementById('btnDrawOk').style.display = '';
|
||||
document.getElementById('btnSpeedConfirm').style.display = 'none';
|
||||
document.getElementById('btnDrawUnlock').style.display = 'none';
|
||||
document.getElementById('btnDrawKeep').style.display = 'none';
|
||||
actions.style.display = 'none';
|
||||
drawnUnlockCode = null;
|
||||
_pendingSpeedMode = null;
|
||||
modal.classList.add('open');
|
||||
|
||||
// Karte serverseitig ziehen
|
||||
@@ -1386,6 +1697,20 @@
|
||||
document.getElementById('btnDrawUnlock').style.display = '';
|
||||
document.getElementById('btnDrawKeep').style.display = '';
|
||||
}
|
||||
|
||||
if (dto.card === 'SLOWMO_CARD' || dto.card === 'SPEEDUP_CARD') {
|
||||
_pendingSpeedMode = dto.card === 'SLOWMO_CARD' ? 'SLOWMO' : 'SPEEDUP';
|
||||
const picker = document.getElementById('drawSpeedPicker');
|
||||
const input = document.getElementById('drawSpeedUntilInput');
|
||||
// Minimum: jetzt + 1 Stunde, Standardwert: jetzt + 24 Stunden
|
||||
const minDate = new Date(Date.now() + 60 * 60 * 1000);
|
||||
const defDate = new Date(Date.now() + 24 * 60 * 60 * 1000);
|
||||
input.min = toLocalDatetimeInputValue(minDate);
|
||||
input.value = toLocalDatetimeInputValue(defDate);
|
||||
picker.style.display = 'flex';
|
||||
document.getElementById('btnDrawOk').style.display = 'none';
|
||||
document.getElementById('btnSpeedConfirm').style.display = '';
|
||||
}
|
||||
}, 700);
|
||||
}, 1000);
|
||||
})
|
||||
@@ -1418,6 +1743,56 @@
|
||||
closeDrawModal();
|
||||
}
|
||||
|
||||
function toLocalDatetimeInputValue(date) {
|
||||
const pad = n => String(n).padStart(2, '0');
|
||||
return `${date.getFullYear()}-${pad(date.getMonth()+1)}-${pad(date.getDate())}T${pad(date.getHours())}:${pad(date.getMinutes())}`;
|
||||
}
|
||||
|
||||
async function confirmSpeedCard() {
|
||||
if (!_pendingSpeedMode) return;
|
||||
const input = document.getElementById('drawSpeedUntilInput');
|
||||
if (!input.value) { alert('Bitte wähle einen Zeitpunkt.'); return; }
|
||||
const until = new Date(input.value);
|
||||
if (until <= new Date()) { alert('Der Zeitpunkt muss in der Zukunft liegen.'); return; }
|
||||
const isoUntil = `${input.value}:00`; // datetime-local hat kein Sekunden-Teil
|
||||
const res = await fetch('/keyholder/cardlock/' + lockId + '/speed/confirm', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ mode: _pendingSpeedMode, until: isoUntil })
|
||||
});
|
||||
if (!res.ok) { alert('Fehler beim Aktivieren des Speed-Effekts.'); return; }
|
||||
closeDrawModal();
|
||||
}
|
||||
|
||||
let speedPanelTick = null;
|
||||
|
||||
function renderSpeedPanel(lock) {
|
||||
if (speedPanelTick) { clearInterval(speedPanelTick); speedPanelTick = null; }
|
||||
const panel = document.getElementById('speedPanel');
|
||||
const now = new Date();
|
||||
const slowmoUntil = lock.slowmoUntil ? new Date(lock.slowmoUntil) : null;
|
||||
const speedupUntil = lock.speedupUntil ? new Date(lock.speedupUntil) : null;
|
||||
const active = (slowmoUntil && slowmoUntil > now) ? { mode: 'slowmo', until: slowmoUntil }
|
||||
: (speedupUntil && speedupUntil > now) ? { mode: 'speedup', until: speedupUntil }
|
||||
: null;
|
||||
if (!active) { panel.style.display = 'none'; return; }
|
||||
panel.style.display = 'flex';
|
||||
document.getElementById('speedPanelTitle').textContent =
|
||||
active.mode === 'slowmo' ? '🐢 Slow Motion aktiv' : '⚡ Speed Up aktiv';
|
||||
function tickSpeed() {
|
||||
const diff = active.until - Date.now();
|
||||
if (diff <= 0) {
|
||||
panel.style.display = 'none';
|
||||
clearInterval(speedPanelTick); speedPanelTick = null;
|
||||
return;
|
||||
}
|
||||
document.getElementById('speedPanelInfo').textContent =
|
||||
(active.mode === 'slowmo' ? 'Aktionen dauern 4× so lange – noch ' : 'Aktionen dauern 4× so kurz – noch ') + fmtCountdown(diff);
|
||||
}
|
||||
tickSpeed();
|
||||
speedPanelTick = setInterval(tickSpeed, 1000);
|
||||
}
|
||||
|
||||
// ── Hygiene-Öffnung ──
|
||||
let hygieneTickInterval = null;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user