fix(ux): banner aperto senza 'Usa comunque'/'Ignora'; preloader ruota 3D; config default non bloccante

Banner prodotti aperti:
- Rimosse le opzioni 'Usa comunque' e 'Ignora' (non hanno senso
  se il prodotto è solo aperto — rimangono solo 'L\''ho finito!',
  'L\''ho buttato', 'Correggi data')
- Per prodotti scaduti non aperti il comportamento rimane invariato

Preloader startup check:
- Sostituito il mini-label monospace con una ruota 3D (stile cooking wheel)
- Testo grande, colorato: VERDE=ok, ARANCIONE=warning, ROSSO=errore
- Il check precedente sale in cima (rotateX tilt, dimmed) mentre il
  nuovo entra dal basso con animazione 3D
- setProgress() ora guida la ruota; slowAnim() aggiorna solo la barra

Defaults / non-bloccante:
- Gemini API key non impostata → ok:true 'non configurata' (verde)
- Bring! token non ancora generato → ok:true (verde, auto-generato al 1° accesso)
- La configurazione mancante mostra  informativo, non ⚠️ warning
This commit is contained in:
dadaloop82
2026-05-17 15:47:57 +00:00
parent 8360f5a0a0
commit 47ce849311
4 changed files with 79 additions and 19 deletions
+4 -3
View File
@@ -279,8 +279,8 @@ if (($_GET['action'] ?? '') === 'health_check') {
$checks['gemini_key'] = ['ok' => strlen($geminiKey) > 20, 'optional' => true,
'hint' => strlen($geminiKey) <= 20 ? 'Chiave Gemini AI sembra troppo corta — verifica il valore in .env' : null];
} else {
$checks['gemini_key'] = ['ok' => false, 'optional' => true,
'hint' => 'GEMINI_API_KEY non configurata — le funzioni AI non saranno disponibili'];
$checks['gemini_key'] = ['ok' => true, 'optional' => true,
'value' => 'non configurata', 'hint' => 'Configura GEMINI_API_KEY in .env per abilitare le funzioni AI'];
}
// ── 11. Bring! — solo se EMAIL+PASSWORD sono impostate ───────────────────
@@ -298,7 +298,8 @@ if (($_GET['action'] ?? '') === 'health_check') {
$bringTokenOk = !empty($bringData['access_token'] ?? ($bringData['accessToken'] ?? ''));
if (!$bringTokenOk) $bringTokenHint = 'Token Bring! presente ma non valido — verrà rinnovato automaticamente al prossimo accesso';
} else {
$bringTokenHint = 'Token Bring! non ancora generato — verrà creato al primo accesso alla lista spesa';
$bringTokenOk = true; // non ancora generato, si crea al primo accesso — non è un errore
$bringTokenHint = 'Verrà generato automaticamente al primo accesso alla lista spesa';
}
$checks['bring_token'] = ['ok' => $bringTokenOk, 'optional' => true, 'hint' => $bringTokenHint];
}
+51 -9
View File
@@ -122,8 +122,8 @@ body {
flex-direction: column;
align-items: center;
gap: 10px;
width: 250px;
max-width: 88vw;
width: 310px;
max-width: 92vw;
animation: zwFadeIn 0.2s ease;
}
.preloader-bar-track {
@@ -142,17 +142,59 @@ body {
}
.preloader-bar.bar-error { background: linear-gradient(90deg, #f87171, #ef4444); }
.preloader-bar.bar-warn { background: linear-gradient(90deg, #fbbf24, #f59e0b); }
.preloader-check-label {
color: rgba(255,255,255,0.60);
font-size: 0.74rem;
font-family: monospace;
.preloader-check-label { display: none; } /* replaced by check-wheel */
/* ── Startup check wheel (3-D scroll) ──────────────────────────────── */
.check-wheel {
position: relative;
width: 100%;
height: 108px;
perspective: 640px;
overflow: hidden;
display: flex;
align-items: center;
justify-content: center;
}
.check-wheel-current {
position: relative;
z-index: 3;
width: 100%;
text-align: center;
font-size: clamp(0.88rem, 2.6vw, 1.08rem);
font-weight: 700;
padding: 10px 14px;
border-radius: 14px;
border: 1px solid rgba(52,211,153,0.35);
background: rgba(52,211,153,0.08);
color: #34d399;
transform-style: preserve-3d;
animation: checkWheelIn 0.30s ease;
letter-spacing: 0.01em;
line-height: 1.4;
}
.check-wheel-current.state-ok { color: #34d399; background: rgba(52,211,153,0.08); border-color: rgba(52,211,153,0.35); }
.check-wheel-current.state-warn { color: #fbbf24; background: rgba(251,191,36,0.08); border-color: rgba(251,191,36,0.35); }
.check-wheel-current.state-error { color: #f87171; background: rgba(248,113,113,0.08); border-color: rgba(248,113,113,0.35); }
.check-wheel-prev {
position: absolute;
top: 3px;
left: 6%; right: 6%;
text-align: center;
font-size: clamp(0.67rem, 1.8vw, 0.82rem);
font-weight: 600;
color: rgba(203,213,225,0.45);
opacity: 0.52;
transform: rotateX(54deg) scale(0.85);
transform-origin: center bottom;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
max-width: 100%;
min-height: 1.1em;
letter-spacing: 0.01em;
pointer-events: none;
font-family: monospace;
}
@keyframes checkWheelIn {
from { transform: translateY(24px) rotateX(-18deg); opacity: 0.35; }
to { transform: translateY(0) rotateX(0deg); opacity: 1; }
}
.preloader-warnings {
max-width: 310px;
+19 -5
View File
@@ -4352,15 +4352,17 @@ function renderBannerItem() {
detailEl.innerHTML = `${baseDetail} <span class="banner-safety-tip banner-safety-${safety.level}">${safety.icon} ${safety.tip}</span>`;
let btns = '';
btns += `<button class="btn-banner btn-banner-finish" onclick="bannerFinishAll()">${t('dashboard.banner_expired_action_finished')}</button>`;
if (safety.level !== 'danger') {
if (!isOpenedExpiry && safety.level !== 'danger') {
btns += `<button class="btn-banner btn-banner-use" onclick="bannerQuickUse()">${t('dashboard.banner_expired_action_use')}</button>`;
}
btns += `<button class="btn-banner btn-banner-throw" onclick="bannerThrowAway()">${t('dashboard.banner_expired_action_throw')}</button>`;
btns += `<button class="btn-banner btn-banner-edit" onclick="editBannerExpiry()">${t('dashboard.banner_expired_action_edit')}</button>`;
if (safety.level === 'danger') {
if (!isOpenedExpiry && safety.level === 'danger') {
btns += `<button class="btn-banner btn-banner-use btn-banner-use-danger" onclick="bannerQuickUse()">${t('dashboard.banner_expired_action_use')}</button>`;
}
if (!isOpenedExpiry) {
btns += `<button class="btn-banner btn-banner-ok" onclick="dismissBannerExpired()">${t('dashboard.banner_review_dismiss')}</button>`;
}
actionsEl.innerHTML = btns;
} else if (entry.type === 'review') {
@@ -14897,7 +14899,7 @@ async function _runStartupCheck() {
if (spinnerEl) spinnerEl.style.display = 'none';
wrapEl.style.display = '';
// Helper: set progress bar + label
// Helper: set progress bar + 3D check wheel
let _curPct = 0;
const setProgress = (pct, label, state) => {
_curPct = pct;
@@ -14905,14 +14907,26 @@ async function _runStartupCheck() {
barEl.style.width = pct + '%';
barEl.className = 'preloader-bar' + (state === 'error' ? ' bar-error' : state === 'warn' ? ' bar-warn' : '');
}
if (labelEl) labelEl.textContent = label || '';
const wheelPrev = document.getElementById('check-wheel-prev');
const wheelCurr = document.getElementById('check-wheel-current');
if (wheelCurr) {
const prevText = wheelCurr.textContent.trim();
if (wheelPrev && prevText && prevText !== '\u00a0') wheelPrev.textContent = prevText;
wheelCurr.textContent = label || '';
const sc = state === 'error' ? 'state-error' : state === 'warn' ? 'state-warn' : 'state-ok';
wheelCurr.className = 'check-wheel-current ' + sc;
void wheelCurr.offsetWidth; // re-trigger CSS animation
}
};
// Phase 1: animate 0→15% while fetching (so it never looks stuck)
setProgress(0, tl('connecting', 'Connessione al server...'));
let _fetchDone = false;
const slowAnim = setInterval(() => {
if (!_fetchDone && _curPct < 13) setProgress(_curPct + 1, labelEl?.textContent);
if (!_fetchDone && _curPct < 13) {
_curPct++;
if (barEl) barEl.style.width = _curPct + '%';
}
}, 100);
// Make the request
+4 -1
View File
@@ -59,7 +59,10 @@
<div class="preloader-bar-track">
<div id="preloader-bar" class="preloader-bar"></div>
</div>
<div id="preloader-check-label" class="preloader-check-label">&nbsp;</div>
<div id="preloader-check-wheel" class="check-wheel">
<div class="check-wheel-prev" id="check-wheel-prev"></div>
<div class="check-wheel-current state-ok" id="check-wheel-current">&nbsp;</div>
</div>
</div>
<div id="preloader-warnings" class="preloader-warnings" style="display:none"></div>
<div id="preloader-error-msg" class="preloader-error-msg" style="display:none"></div>