Replace alert/confirm with modals, hide sector numbers on cramped layout, enforce 2/3/6 grid for All Sectors

This commit is contained in:
Christian 2026-10-16 11:15:28 +02:00
parent 638947f04c
commit 21cb0aeb50
4 changed files with 151 additions and 14 deletions

View file

@ -66,6 +66,23 @@
} }
} }
/* Cramped / Mobile viewport: Hide sector pill numbers so only icons are displayed */
@media (max-width: 640px) {
.sector-pill .sector-pill-rate {
display: none !important;
}
.sector-pill {
padding: 8px 4px;
gap: 0;
}
.sector-pill iconify-icon {
font-size: 1.25rem;
}
}
/* ========================================================================== /* ==========================================================================
TOASTS & MODALS TOASTS & MODALS
========================================================================== */ ========================================================================== */
@ -232,3 +249,20 @@
.btn-text-close:hover { .btn-text-close:hover {
color: var(--text-primary); color: var(--text-primary);
} }
.modal-error-box {
background: #fef2f2;
border: 1px solid #fecaca;
color: #b91c1c;
padding: 8px 12px;
border-radius: var(--radius-sm);
font-size: 0.78rem;
font-weight: 600;
}
.modal-body-text {
font-size: 0.88rem;
color: var(--text-secondary);
line-height: 1.5;
}

View file

@ -216,13 +216,33 @@
color: var(--emerald-dark); color: var(--emerald-dark);
} }
/* All Sectors Overview Grid */ /* All Sectors Overview Grid (Strict 6, 3, or 2 grid for 6 sectors) */
.all-sectors-summary-grid { .all-sectors-summary-grid {
display: grid; display: grid;
grid-template-columns: repeat(auto-fit, minmax(220px, 1fr)); grid-template-columns: repeat(3, 1fr);
gap: 12px; gap: 12px;
} }
@media (min-width: 1400px) {
.all-sectors-summary-grid {
grid-template-columns: repeat(6, 1fr);
}
}
@media (max-width: 768px) {
.all-sectors-summary-grid {
grid-template-columns: repeat(2, 1fr);
}
}
@media (max-width: 480px) {
.all-sectors-summary-grid {
grid-template-columns: repeat(2, 1fr);
gap: 8px;
}
}
.sector-mini-card { .sector-mini-card {
background: var(--bg-surface); background: var(--bg-surface);
border: 1px solid var(--border-color); border: 1px solid var(--border-color);

View file

@ -213,6 +213,9 @@
<input type="password" id="auth-password" class="modal-input" placeholder="••••••••" autocomplete="current-password"> <input type="password" id="auth-password" class="modal-input" placeholder="••••••••" autocomplete="current-password">
</div> </div>
<!-- In-modal Error Feedback (Replacing alert()) -->
<div id="auth-error-msg" class="modal-error-box" style="display: none;"></div>
<!-- Honeypot field for bot protection --> <!-- Honeypot field for bot protection -->
<div style="display: none;" aria-hidden="true"> <div style="display: none;" aria-hidden="true">
<input type="text" id="auth-website-hp" name="website_hp" tabindex="-1" autocomplete="off"> <input type="text" id="auth-website-hp" name="website_hp" tabindex="-1" autocomplete="off">
@ -227,6 +230,24 @@
</div> </div>
</div> </div>
<!-- Universal Corporate Confirmation & Prompt Modal (No browser alert() / confirm()) -->
<div id="confirm-modal" class="modal-overlay">
<div class="modal-card">
<div class="modal-header">
<iconify-icon icon="lucide:alert-triangle" class="modal-icon text-warning" id="confirm-modal-icon"></iconify-icon>
<div>
<h3 class="modal-title" id="confirm-modal-title">Executive Action Required</h3>
<p class="modal-subtitle" id="confirm-modal-subtitle">Corporate Board Authorization</p>
</div>
</div>
<div class="modal-body-text" id="confirm-modal-text"></div>
<div class="modal-actions-row">
<button id="btn-confirm-cancel" class="btn-corp-secondary">Cancel</button>
<button id="btn-confirm-ok" class="btn-primary-corp">Confirm Authorization</button>
</div>
</div>
</div>
</div> </div>
<!-- Application Entry Point (Native ES Module) --> <!-- Application Entry Point (Native ES Module) -->

View file

@ -275,15 +275,23 @@ class ProductionApp {
} }
case 'TRIGGER_IPO': { case 'TRIGGER_IPO': {
const confirmed = confirm('Confirm Corporate Initial Public Offering (IPO)?\n\nYour factories and cash will reset in exchange for permanent Venture Shares ($V-Shares) and corporate equity!'); this.showConfirmModal({
if (confirmed) { title: 'Authorize Initial Public Offering (IPO)',
const success = this.sm.performIPO(); subtitle: 'Corporate Equity Transition',
if (success) { icon: 'lucide:trending-up',
this.showToast(`IPO Successful! Welcome to Wall Street, CEO!`, 'success'); text: 'Confirm Corporate Initial Public Offering (IPO)?<br><br>All factory floors and cash will be liquidated in exchange for permanent <strong>Venture Shares ($V-Shares)</strong> and corporate prestige bonuses!',
this.mountActiveView(); okText: 'Execute IPO',
this.mountInspectorPane(); cancelText: 'Abort'
}).then(confirmed => {
if (confirmed) {
const success = this.sm.performIPO();
if (success) {
this.showToast(`IPO Successful! Welcome to Wall Street, CEO!`, 'success');
this.mountActiveView();
this.mountInspectorPane();
}
} }
} });
break; break;
} }
@ -468,8 +476,52 @@ class ProductionApp {
}, 3200); }, 3200);
} }
showConfirmModal({ title = 'Executive Authorization', subtitle = 'Board Confirmation', icon = 'lucide:alert-triangle', text = '', okText = 'Confirm', cancelText = 'Cancel' } = {}) {
return new Promise((resolve) => {
const modal = document.getElementById('confirm-modal');
const elTitle = document.getElementById('confirm-modal-title');
const elSub = document.getElementById('confirm-modal-subtitle');
const elIcon = document.getElementById('confirm-modal-icon');
const elText = document.getElementById('confirm-modal-text');
const btnOk = document.getElementById('btn-confirm-ok');
const btnCancel = document.getElementById('btn-confirm-cancel');
if (!modal) return resolve(false);
if (elTitle) elTitle.textContent = title;
if (elSub) elSub.textContent = subtitle;
if (elIcon) elIcon.setAttribute('icon', icon);
if (elText) elText.innerHTML = text;
if (btnOk) btnOk.textContent = okText;
if (btnCancel) btnCancel.textContent = cancelText;
const cleanup = () => {
modal.classList.remove('open');
btnOk.onclick = null;
btnCancel.onclick = null;
};
btnOk.onclick = () => {
cleanup();
resolve(true);
};
btnCancel.onclick = () => {
cleanup();
resolve(false);
};
modal.classList.add('open');
});
}
openAuthModal() { openAuthModal() {
const modal = document.getElementById('auth-modal'); const modal = document.getElementById('auth-modal');
const errBox = document.getElementById('auth-error-msg');
if (errBox) {
errBox.style.display = 'none';
errBox.textContent = '';
}
if (modal) modal.classList.add('open'); if (modal) modal.classList.add('open');
} }
@ -478,10 +530,20 @@ class ProductionApp {
if (modal) modal.classList.remove('open'); if (modal) modal.classList.remove('open');
} }
showAuthError(msg) {
const errBox = document.getElementById('auth-error-msg');
if (errBox) {
errBox.textContent = msg;
errBox.style.display = 'block';
} else {
this.showToast(msg, 'warning');
}
}
async handleLogin() { async handleLogin() {
const u = document.getElementById('auth-username')?.value.trim(); const u = document.getElementById('auth-username')?.value.trim();
const p = document.getElementById('auth-password')?.value; const p = document.getElementById('auth-password')?.value;
if (!u || !p) return alert('Please enter username and password'); if (!u || !p) return this.showAuthError('Please enter username and password');
try { try {
await this.sync.login(u, p); await this.sync.login(u, p);
@ -489,7 +551,7 @@ class ProductionApp {
this.closeAuthModal(); this.closeAuthModal();
this.updateHud(); this.updateHud();
} catch (err) { } catch (err) {
alert(err.message || 'Login failed'); this.showAuthError(err.message || 'Login failed');
} }
} }
@ -497,7 +559,7 @@ class ProductionApp {
const u = document.getElementById('auth-username')?.value.trim(); const u = document.getElementById('auth-username')?.value.trim();
const p = document.getElementById('auth-password')?.value; const p = document.getElementById('auth-password')?.value;
const hp = document.getElementById('auth-website-hp')?.value; const hp = document.getElementById('auth-website-hp')?.value;
if (!u || !p) return alert('Please enter username and password'); if (!u || !p) return this.showAuthError('Please enter username and password');
try { try {
await this.sync.register(u, p, hp); await this.sync.register(u, p, hp);
@ -505,7 +567,7 @@ class ProductionApp {
this.closeAuthModal(); this.closeAuthModal();
this.updateHud(); this.updateHud();
} catch (err) { } catch (err) {
alert(err.message || 'Registration failed'); this.showAuthError(err.message || 'Registration failed');
} }
} }