diff --git a/app.js b/app.js index 5a028f6..9a86960 100644 --- a/app.js +++ b/app.js @@ -209,6 +209,74 @@ document.getElementById('debug-toggle').addEventListener('click', () => { } }); +// === GUESTBOOK === +const GUESTBOOK_KEY = 'nexus_guestbook'; +const GUESTBOOK_MAX = 20; + +/** + * Load guestbook entries from localStorage. + * @returns {Array<{name: string, msg: string, ts: number}>} + */ +function guestbookLoad() { + try { + return JSON.parse(localStorage.getItem(GUESTBOOK_KEY) || '[]'); + } catch (_) { + return []; + } +} + +/** + * Save an entry to localStorage (max GUESTBOOK_MAX entries, newest first). + * @param {string} name + * @param {string} msg + */ +function guestbookSave(name, msg) { + const entries = guestbookLoad(); + entries.unshift({ name: name.trim().slice(0, 32), msg: msg.trim().slice(0, 140), ts: Date.now() }); + localStorage.setItem(GUESTBOOK_KEY, JSON.stringify(entries.slice(0, GUESTBOOK_MAX))); +} + +/** + * Render guestbook entries into the messages container. + */ +function guestbookRender() { + const container = document.getElementById('guestbook-messages'); + const entries = guestbookLoad(); + if (entries.length === 0) { + container.innerHTML = '
No messages yet. Be the first!
'; + return; + } + container.innerHTML = entries.map(e => { + const safeName = e.name ? e.name.replace(/&/g, '&').replace(/
${safeName}
${safeMsg}
`; + }).join(''); +} + +const guestbookPanel = document.getElementById('guestbook-panel'); +const guestbookBtn = document.getElementById('guestbook-btn'); +const guestbookClose = document.getElementById('guestbook-close'); +const guestbookForm = document.getElementById('guestbook-form'); + +guestbookBtn.addEventListener('click', () => { + guestbookRender(); + guestbookPanel.classList.toggle('open'); +}); + +guestbookClose.addEventListener('click', () => { + guestbookPanel.classList.remove('open'); +}); + +guestbookForm.addEventListener('submit', (e) => { + e.preventDefault(); + const name = /** @type {HTMLInputElement} */ (document.getElementById('guestbook-name')).value; + const msg = /** @type {HTMLTextAreaElement} */ (document.getElementById('guestbook-msg')).value.trim(); + if (!msg) return; + guestbookSave(name, msg); + guestbookForm.reset(); + guestbookRender(); +}); + // === WEBSOCKET CLIENT === import { wsClient } from './ws-client.js'; diff --git a/index.html b/index.html index 26344f3..4db0fbb 100644 --- a/index.html +++ b/index.html @@ -41,6 +41,26 @@ [Tab] to exit + +
+ +
+ + + diff --git a/style.css b/style.css index 1d78e52..b31c6b4 100644 --- a/style.css +++ b/style.css @@ -106,3 +106,136 @@ canvas { 0%, 100% { opacity: 0.7; } 50% { opacity: 1; } } + +/* === GUESTBOOK === */ +#guestbook-panel { + display: none; + position: fixed; + bottom: 56px; + left: 16px; + width: 280px; + max-height: 400px; + background: rgba(0, 0, 16, 0.88); + border: 1px solid var(--color-primary); + border-radius: 6px; + z-index: 30; + font-family: var(--font-body); + font-size: 12px; + color: var(--color-text); + box-shadow: 0 0 18px rgba(68, 136, 255, 0.18); + flex-direction: column; +} + +#guestbook-panel.open { + display: flex; +} + +#guestbook-header { + display: flex; + justify-content: space-between; + align-items: center; + padding: 8px 12px; + border-bottom: 1px solid var(--color-secondary); + letter-spacing: 0.15em; + color: var(--color-primary); + font-size: 11px; + text-transform: uppercase; +} + +#guestbook-close { + background: none; + border: none; + color: var(--color-text-muted); + cursor: pointer; + font-size: 12px; + padding: 0 2px; + line-height: 1; +} + +#guestbook-close:hover { + color: var(--color-text); +} + +#guestbook-messages { + overflow-y: auto; + flex: 1; + padding: 8px 12px; + min-height: 60px; + max-height: 200px; +} + +.guestbook-entry { + padding: 5px 0; + border-bottom: 1px solid rgba(51, 68, 136, 0.3); +} + +.guestbook-entry:last-child { + border-bottom: none; +} + +.guestbook-entry-name { + color: var(--color-primary); + font-size: 11px; + margin-bottom: 2px; +} + +.guestbook-entry-msg { + color: var(--color-text); + font-size: 12px; + word-break: break-word; +} + +.guestbook-empty { + color: var(--color-text-muted); + font-size: 11px; + text-align: center; + padding: 12px 0; +} + +#guestbook-form { + display: flex; + flex-direction: column; + gap: 6px; + padding: 8px 12px; + border-top: 1px solid var(--color-secondary); +} + +#guestbook-name, +#guestbook-msg { + background: rgba(0, 0, 16, 0.6); + border: 1px solid var(--color-secondary); + border-radius: 3px; + color: var(--color-text); + font-family: var(--font-body); + font-size: 12px; + padding: 5px 8px; + outline: none; + resize: none; +} + +#guestbook-name:focus, +#guestbook-msg:focus { + border-color: var(--color-primary); +} + +#guestbook-msg { + height: 56px; +} + +#guestbook-form button[type="submit"] { + background-color: var(--color-primary); + color: var(--color-bg); + border: none; + border-radius: 3px; + padding: 5px 0; + font-family: var(--font-body); + font-size: 12px; + cursor: pointer; + letter-spacing: 0.1em; + transition: background-color 0.2s; +} + +#guestbook-form button[type="submit"]:hover { + background-color: var(--color-secondary); + color: var(--color-text); +}