step 9.3: done
This commit is contained in:
@@ -0,0 +1,95 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Emberfall — Page Not Found</title>
|
||||
<link rel="stylesheet" href="/css/main.css">
|
||||
<style>
|
||||
.error-page {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
min-height: 60vh;
|
||||
text-align: center;
|
||||
padding: 2rem;
|
||||
}
|
||||
|
||||
.error-code {
|
||||
font-size: 6rem;
|
||||
font-weight: bold;
|
||||
color: var(--accent, #e2822b);
|
||||
line-height: 1;
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
.error-title {
|
||||
font-size: 1.5rem;
|
||||
color: var(--text, #eadfce);
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.error-message {
|
||||
font-size: 1rem;
|
||||
color: var(--muted, #a59a8a);
|
||||
margin-bottom: 2rem;
|
||||
max-width: 400px;
|
||||
}
|
||||
|
||||
.error-actions {
|
||||
display: flex;
|
||||
gap: 1rem;
|
||||
flex-wrap: wrap;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.error-actions a {
|
||||
display: inline-block;
|
||||
padding: 0.5rem 1.25rem;
|
||||
border-radius: 4px;
|
||||
text-decoration: none;
|
||||
font-size: 0.875rem;
|
||||
font-weight: 600;
|
||||
transition: opacity 0.2s;
|
||||
}
|
||||
|
||||
.error-actions a:hover {
|
||||
opacity: 0.85;
|
||||
}
|
||||
|
||||
.btn-home {
|
||||
background: var(--accent, #e2822b);
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.btn-back {
|
||||
background: transparent;
|
||||
color: var(--accent, #e2822b);
|
||||
border: 1px solid var(--accent, #e2822b);
|
||||
}
|
||||
|
||||
/* Server-only error (500) */
|
||||
.error-code.server {
|
||||
color: var(--danger, #c0392b);
|
||||
}
|
||||
|
||||
.error-message.server {
|
||||
color: var(--muted, #a59a8a);
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="error-page">
|
||||
<div class="error-code" id="error-code">404</div>
|
||||
<h1 class="error-title" id="error-title">Page Not Found</h1>
|
||||
<p class="error-message" id="error-message">
|
||||
The page you're looking for has gone dark. Perhaps it was never here, or has been consumed by the shadows.
|
||||
</p>
|
||||
<div class="error-actions">
|
||||
<a href="/" class="btn-home">Return to Emberfall</a>
|
||||
<a href="javascript:history.back()" class="btn-back">Go Back</a>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,92 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Emberfall — Server Error</title>
|
||||
<link rel="stylesheet" href="/css/main.css">
|
||||
<style>
|
||||
.error-page {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
min-height: 60vh;
|
||||
text-align: center;
|
||||
padding: 2rem;
|
||||
}
|
||||
|
||||
.error-code {
|
||||
font-size: 6rem;
|
||||
font-weight: bold;
|
||||
color: var(--danger, #c0392b);
|
||||
line-height: 1;
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
.error-title {
|
||||
font-size: 1.5rem;
|
||||
color: var(--text, #eadfce);
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.error-message {
|
||||
font-size: 1rem;
|
||||
color: var(--muted, #a59a8a);
|
||||
margin-bottom: 2rem;
|
||||
max-width: 400px;
|
||||
}
|
||||
|
||||
.error-actions {
|
||||
display: flex;
|
||||
gap: 1rem;
|
||||
flex-wrap: wrap;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.error-actions a {
|
||||
display: inline-block;
|
||||
padding: 0.5rem 1.25rem;
|
||||
border-radius: 4px;
|
||||
text-decoration: none;
|
||||
font-size: 0.875rem;
|
||||
font-weight: 600;
|
||||
transition: opacity 0.2s;
|
||||
}
|
||||
|
||||
.error-actions a:hover {
|
||||
opacity: 0.85;
|
||||
}
|
||||
|
||||
.btn-home {
|
||||
background: var(--accent, #e2822b);
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.btn-retry {
|
||||
background: transparent;
|
||||
color: var(--accent, #e2822b);
|
||||
border: 1px solid var(--accent, #e2822b);
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.btn-retry:hover {
|
||||
background: var(--accent, #e2822b);
|
||||
color: #fff;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="error-page">
|
||||
<div class="error-code">500</div>
|
||||
<h1 class="error-title">Something went wrong</h1>
|
||||
<p class="error-message">
|
||||
The flames of Emberfall have flickered. Our servers are working to restore the light. Please try again.
|
||||
</p>
|
||||
<div class="error-actions">
|
||||
<a href="/" class="btn-home">Return to Emberfall</a>
|
||||
<button class="btn-retry" onclick="window.location.reload()">Retry</button>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -26,6 +26,7 @@
|
||||
<button type="submit">Begin Your Journey</button>
|
||||
</form>
|
||||
</div>
|
||||
<script src="/js/toast.js"></script>
|
||||
<script src="/js/auth.js"></script>
|
||||
<script src="/js/create-hero.js"></script>
|
||||
</body>
|
||||
|
||||
@@ -794,3 +794,168 @@ button:hover { background: #c73650; }
|
||||
font-style: italic;
|
||||
font-size: 0.8rem;
|
||||
}
|
||||
|
||||
/* ===== Loading spinner ===== */
|
||||
.spinner {
|
||||
display: inline-block;
|
||||
width: 1.25rem;
|
||||
height: 1.25rem;
|
||||
border: 2px solid var(--muted, #a59a8a);
|
||||
border-top-color: var(--accent, #e2822b);
|
||||
border-radius: 50%;
|
||||
animation: spin 0.6s linear infinite;
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
@keyframes spin {
|
||||
to { transform: rotate(360deg); }
|
||||
}
|
||||
|
||||
/* Loading overlay for panels */
|
||||
.loading-overlay {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 0.75rem;
|
||||
padding: 2rem;
|
||||
color: var(--muted, #a59a8a);
|
||||
font-size: 0.875rem;
|
||||
}
|
||||
|
||||
.loading-overlay .spinner {
|
||||
width: 1.5rem;
|
||||
height: 1.5rem;
|
||||
}
|
||||
|
||||
/* ===== Toast notifications ===== */
|
||||
.toast {
|
||||
position: fixed;
|
||||
bottom: 1.5rem;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
padding: 0.75rem 1.25rem;
|
||||
border-radius: 6px;
|
||||
font-size: 0.875rem;
|
||||
color: #fff;
|
||||
z-index: 1000;
|
||||
animation: toastIn 0.3s ease;
|
||||
max-width: 90vw;
|
||||
text-align: center;
|
||||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3);
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.toast-error {
|
||||
background: #c0392b;
|
||||
}
|
||||
|
||||
.toast-info {
|
||||
background: #4a7fb0;
|
||||
}
|
||||
|
||||
.toast-success {
|
||||
background: #4f9d69;
|
||||
}
|
||||
|
||||
@keyframes toastIn {
|
||||
from { opacity: 0; transform: translateX(-50%) translateY(1rem); }
|
||||
to { opacity: 1; transform: translateX(-50%) translateY(0); }
|
||||
}
|
||||
|
||||
/* ===== Button loading state ===== */
|
||||
.btn[disabled] {
|
||||
opacity: 0.65;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
.btn-loading {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
|
||||
.btn-loading .spinner {
|
||||
width: 0.875rem;
|
||||
height: 0.875rem;
|
||||
border-width: 1.5px;
|
||||
}
|
||||
|
||||
/* ===== Error page shared ===== */
|
||||
.error-page {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
min-height: 60vh;
|
||||
text-align: center;
|
||||
padding: 2rem;
|
||||
}
|
||||
|
||||
.error-code {
|
||||
font-size: 6rem;
|
||||
font-weight: bold;
|
||||
color: var(--accent, #e2822b);
|
||||
line-height: 1;
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
.error-title {
|
||||
font-size: 1.5rem;
|
||||
color: var(--text, #eadfce);
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.error-message {
|
||||
font-size: 1rem;
|
||||
color: var(--muted, #a59a8a);
|
||||
margin-bottom: 2rem;
|
||||
max-width: 400px;
|
||||
}
|
||||
|
||||
.error-actions {
|
||||
display: flex;
|
||||
gap: 1rem;
|
||||
flex-wrap: wrap;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.error-actions a,
|
||||
.error-actions button {
|
||||
display: inline-block;
|
||||
padding: 0.5rem 1.25rem;
|
||||
border-radius: 4px;
|
||||
text-decoration: none;
|
||||
font-size: 0.875rem;
|
||||
font-weight: 600;
|
||||
cursor: pointer;
|
||||
transition: opacity 0.2s;
|
||||
border: none;
|
||||
background: transparent;
|
||||
color: inherit;
|
||||
}
|
||||
|
||||
.error-actions a:hover,
|
||||
.error-actions button:hover {
|
||||
opacity: 0.85;
|
||||
}
|
||||
|
||||
.btn-home {
|
||||
background: var(--accent, #e2822b);
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.btn-back {
|
||||
color: var(--accent, #e2822b);
|
||||
border: 1px solid var(--accent, #e2822b);
|
||||
}
|
||||
|
||||
.btn-retry {
|
||||
color: var(--accent, #e2822b);
|
||||
border: 1px solid var(--accent, #e2822b);
|
||||
}
|
||||
|
||||
.btn-retry:hover {
|
||||
background: var(--accent, #e2822b);
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
@@ -120,6 +120,7 @@
|
||||
</main>
|
||||
</div>
|
||||
|
||||
<script src="/js/toast.js"></script>
|
||||
<script src="/js/auth.js"></script>
|
||||
<script src="/js/game.js"></script>
|
||||
</body>
|
||||
|
||||
@@ -815,21 +815,6 @@ function startExpeditionSync() {
|
||||
}, 5000);
|
||||
}
|
||||
|
||||
// ===== Toast notifications =====
|
||||
function showToast(message, type = 'info') {
|
||||
// Remove existing toasts
|
||||
document.querySelectorAll('.toast').forEach((t) => t.remove());
|
||||
|
||||
const toast = document.createElement('div');
|
||||
toast.className = `toast toast-${type}`;
|
||||
toast.textContent = message;
|
||||
document.body.appendChild(toast);
|
||||
|
||||
setTimeout(() => {
|
||||
toast.remove();
|
||||
}, 4000);
|
||||
}
|
||||
|
||||
// ===== Init =====
|
||||
document.addEventListener('DOMContentLoaded', async () => {
|
||||
await loadCharacter();
|
||||
|
||||
@@ -272,17 +272,6 @@ function switchTab(tab) {
|
||||
document.getElementById('sell-tab').style.display = tab === 'sell' ? '' : 'none';
|
||||
}
|
||||
|
||||
// ─── Toast notifications ─────────────────────────────────────────────────
|
||||
|
||||
function showToast(message, type = 'info') {
|
||||
document.querySelectorAll('.toast').forEach((t) => t.remove());
|
||||
const toast = document.createElement('div');
|
||||
toast.className = `toast toast-${type}`;
|
||||
toast.textContent = message;
|
||||
document.body.appendChild(toast);
|
||||
setTimeout(() => toast.remove(), 4000);
|
||||
}
|
||||
|
||||
// ─── Init ────────────────────────────────────────────────────────────────
|
||||
|
||||
document.addEventListener('DOMContentLoaded', async () => {
|
||||
|
||||
@@ -0,0 +1,42 @@
|
||||
// Toast notification system — shared across all pages.
|
||||
// Usage: showToast('message', 'error' | 'info' | 'success');
|
||||
// Auto-dismisses after 4 seconds. Multiple toasts stack vertically.
|
||||
|
||||
(function () {
|
||||
'use strict';
|
||||
|
||||
const TOAST_DURATION = 4000;
|
||||
const MAX_VISIBLE = 3;
|
||||
const STACK_GAP = 8; // px between stacked toasts
|
||||
|
||||
/**
|
||||
* Show a toast notification.
|
||||
* @param {string} message - Toast text content.
|
||||
* @param {'error'|'info'|'success'} [type='info'] - Visual style.
|
||||
*/
|
||||
window.showToast = function showToast(message, type) {
|
||||
if (typeof message !== 'string') return;
|
||||
type = type || 'info';
|
||||
|
||||
// Remove old toasts of the same type to avoid clutter
|
||||
document.querySelectorAll('.toast-' + type).forEach(function (t) {
|
||||
t.remove();
|
||||
});
|
||||
|
||||
var toast = document.createElement('div');
|
||||
toast.className = 'toast toast-' + type;
|
||||
toast.setAttribute('role', 'alert');
|
||||
toast.textContent = message;
|
||||
|
||||
// Position: stack from bottom
|
||||
var existing = document.querySelectorAll('.toast');
|
||||
var offset = existing.length * STACK_GAP;
|
||||
toast.style.bottom = (1.5 + offset / 16) + 'rem';
|
||||
|
||||
document.body.appendChild(toast);
|
||||
|
||||
setTimeout(function () {
|
||||
if (toast.parentNode) toast.remove();
|
||||
}, TOAST_DURATION);
|
||||
};
|
||||
})();
|
||||
@@ -20,6 +20,7 @@
|
||||
</form>
|
||||
<p class="redirect">Don't have an account? <a href="/register.html">Register</a></p>
|
||||
</div>
|
||||
<script src="/js/toast.js"></script>
|
||||
<script src="/js/auth.js"></script>
|
||||
<script src="/js/login.js"></script>
|
||||
</body>
|
||||
|
||||
@@ -70,6 +70,7 @@
|
||||
</main>
|
||||
</div>
|
||||
|
||||
<script src="/js/toast.js"></script>
|
||||
<script src="/js/auth.js"></script>
|
||||
<script src="/js/profile.js"></script>
|
||||
</body>
|
||||
|
||||
@@ -20,6 +20,7 @@
|
||||
</form>
|
||||
<p class="redirect">Already have an account? <a href="/login.html">Login</a></p>
|
||||
</div>
|
||||
<script src="/js/toast.js"></script>
|
||||
<script src="/js/auth.js"></script>
|
||||
<script src="/js/register.js"></script>
|
||||
</body>
|
||||
|
||||
@@ -46,6 +46,7 @@
|
||||
</main>
|
||||
</div>
|
||||
|
||||
<script src="/js/toast.js"></script>
|
||||
<script src="/js/auth.js"></script>
|
||||
<script src="/js/shop.js"></script>
|
||||
</body>
|
||||
|
||||
Reference in New Issue
Block a user