// Lot progression logic + progress bar component. // Lots advance every Tuesday and Thursday at 00:00 BRT until the final sales day. // After final sales day ends, status = "encerrado". const BRT_OFFSET_MS = -3 * 60 * 60 * 1000; // -03:00 // Parse "YYYY-MM-DD" as midnight BRT, return UTC ms function brtMidnight(yyyy_mm_dd) { const [y, m, d] = yyyy_mm_dd.split('-').map(Number); // UTC ms for 00:00 BRT on that date = UTC ms for 03:00 UTC on that date return Date.UTC(y, m - 1, d, 3, 0, 0, 0); } function fmtDateBR(ms) { const d = new Date(ms - BRT_OFFSET_MS); // shift into a date whose UTC fields == BRT fields const day = String(d.getUTCDate()).padStart(2, '0'); const month = String(d.getUTCMonth() + 1).padStart(2, '0'); return `${day}/${month}`; } function fmtPrice(v) { return 'R$ ' + v.toFixed(2).replace('.', ','); } // Parse "YYYY-MM-DDTHH:mm:ss" (no timezone) as BRT → UTC ms function brtDateTime(s) { const [date, time = '00:00:00'] = s.split('T'); const [y, mo, d] = date.split('-').map(Number); const [h, mi, se] = time.split(':').map(Number); // BRT = UTC - 3; so BRT h → UTC (h + 3) return Date.UTC(y, mo - 1, d, h + 3, mi, se, 0); } // Build lot schedule from an explicit list of windows: // windows: [{ ini: 'YYYY-MM-DDTHH:mm:ss', fim: '...', preco: Number }, ...] // Assumes BRT timezone. function buildLots(windows) { const lots = windows.map((w, i) => ({ index: i, number: i + 1, start: brtDateTime(w.ini), end: brtDateTime(w.fim) + 60 * 1000, // inclusive fim → exclusive end price: w.preco, url: w.url || '', })); const salesStart = lots.length ? lots[0].start : 0; const salesEnd = lots.length ? lots[lots.length - 1].end : 0; return { lots, salesStart, salesEnd }; } // Piecewise curve: first 10h of the lot window → 0→60%, remainder → 60→99%. // At lot.end, returns 1.0 (the real virada). function easedLotProgress(L, now) { const total = L.end - L.start; const since = now - L.start; if (since <= 0) return 0; if (since >= total) return 1; const TEN_HOURS = 10 * 60 * 60 * 1000; const fastEnd = Math.min(TEN_HOURS, total * 0.5); if (since <= fastEnd) { return (since / fastEnd) * 0.60; } // Remaining window: 60% → 99% const rem = total - fastEnd; const t = (since - fastEnd) / rem; return 0.60 + t * 0.39; } // Given current time (ms), find active lot + progress within it function computeLotState(schedule, now) { const { lots, salesStart, salesEnd } = schedule; if (now < salesStart) { return { phase: 'pre', activeIndex: -1, progress: 0, lots, nextTurn: salesStart }; } if (now >= salesEnd) { return { phase: 'ended', activeIndex: lots.length - 1, progress: 1, lots, nextTurn: null }; } for (let i = 0; i < lots.length; i++) { const L = lots[i]; if (now >= L.start && now < L.end) { const progress = easedLotProgress(L, now); return { phase: 'active', activeIndex: i, progress: Math.max(0, Math.min(1, progress)), lots, nextTurn: L.end, }; } } return { phase: 'ended', activeIndex: lots.length - 1, progress: 1, lots, nextTurn: null }; } // Countdown string to a target ms function fmtCountdown(targetMs, now) { const diff = Math.max(0, targetMs - now); const days = Math.floor(diff / (24 * 3600 * 1000)); const hours = Math.floor((diff % (24 * 3600 * 1000)) / (3600 * 1000)); const mins = Math.floor((diff % (3600 * 1000)) / (60 * 1000)); const secs = Math.floor((diff % (60 * 1000)) / 1000); const pad = (n) => String(n).padStart(2, '0'); return { days, hours, mins, secs, str: `${pad(days)}d : ${pad(hours)}h : ${pad(mins)}m : ${pad(secs)}s` }; } // ── Progress bar — with persistent red accent + urgent escalation ────────── function LotProgressBar({ schedule, now, accent = '#f6f4ef' }) { const state = computeLotState(schedule, now); const { lots, activeIndex, progress, phase } = state; const fillPct = phase === 'ended' ? 100 : phase === 'pre' ? 0 : Math.round(progress * 100); const currentLot = activeIndex >= 0 ? lots[activeIndex] : lots[0]; const isUrgent = phase === 'active' && fillPct >= 70; return (
{phase === 'ended' ? '100%' : phase === 'pre' ? '0%' : `${fillPct}%`} {phase === 'ended' ? 'vendas encerradas' : `lote 0${currentLot.number} preenchido`}
{phase !== 'pre' && (
{phase === 'ended' ? 'status' : 'ingresso por'} {phase === 'ended' ? 'encerrado' : fmtPrice(currentLot.price)}
)}
{isUrgent && (
virada iminente
)}
); } // Mini version — a slim bar with % only. Renders below any CTA button. function CTAProgress({ schedule, now, accent = '#f6f4ef' }) { const state = computeLotState(schedule, now); const { lots, activeIndex, progress, phase } = state; if (phase === 'pre') return null; const fillPct = phase === 'ended' ? 100 : Math.round(progress * 100); const currentLot = activeIndex >= 0 ? lots[activeIndex] : lots[0]; const isUrgent = phase === 'active' && fillPct >= 70; return (
lote 0{currentLot.number} · {phase === 'ended' ? '100%' : `${fillPct}%`} preenchido {phase === 'ended' ? 'encerrado' : isUrgent ? 'virada iminente' : ''}
); } Object.assign(window, { buildLots, computeLotState, fmtPrice, fmtDateBR, fmtCountdown, LotProgressBar, CTAProgress });