From 77553bbece86930bfd038c49c6d11ff21d8d198f Mon Sep 17 00:00:00 2001 From: Codex Date: Wed, 14 Oct 2026 21:48:31 +0200 Subject: [PATCH] Style reading quiz feedback by result --- public/app.css | 6 +++++- public/reader.js | 21 +++++++++++++-------- tools/test-compose.js | 20 ++++++++++++++++++++ 3 files changed, 38 insertions(+), 9 deletions(-) diff --git a/public/app.css b/public/app.css index 2f91c8e..c77bef8 100644 --- a/public/app.css +++ b/public/app.css @@ -395,7 +395,11 @@ body.reader { position: fixed; top: 0; left: 0; right: 0; bottom: 0; overflow: h .reading-gate-question { margin: 20px 0; } .reading-gate-question input { display: block; box-sizing: border-box; width: 100%; margin: 14px 0; padding: 10px; } .reading-gate-actions { display: flex; flex-wrap: wrap; gap: 10px; } -.reading-gate-message { color: var(--accent); min-height: 1.5em; } +.reading-gate-message { margin: 18px 0 0; padding: 11px 13px; border: 1px solid #46505b; + border-radius: 9px; background: #222a33; color: #d8e6f4; } +.reading-gate-message[data-tone="success"] { border-color: #39765a; background: #193528; color: #b8f2d0; } +.reading-gate-message[data-tone="retry"] { border-color: #9b7233; background: #382b1a; color: #ffdaa0; } +.reading-gate-message[data-tone="error"] { border-color: #944c55; background: #3c2025; color: #ffc0c6; } @media (max-width: 660px) { .reading-gate { margin: 28px 12px 100px; padding: 18px; } } .pg { position: absolute; overflow: hidden; background: #19191c; } .pg img { display: block; width: 100%; height: 100%; -webkit-user-drag: none; } diff --git a/public/reader.js b/public/reader.js index 67df4ad..0ae86e7 100644 --- a/public/reader.js +++ b/public/reader.js @@ -22,7 +22,12 @@ h('h1', {}, 'Reading is closed right now'), h('p', {}, `The reading hours are set by this server's deployment (${access.timezone}). The library and your progress remain available.`), h('p', {}, `Answer five questions drawn from manga you have already read to unlock ${access.unlockMinutes} minutes of reading. There is no answer timer or retry limit.`)); - const message = h('p', { class: 'reading-gate-message', role: 'status' }); + const message = h('p', { class: 'reading-gate-message', role: 'status', hidden: true }); + const setMessage = (text, tone = 'info') => { + message.textContent = text; + message.dataset.tone = tone; + message.hidden = !text; + }; const question = h('div', { class: 'reading-gate-question' }); const start = h('button', { class: 'btn', type: 'button' }, 'Start five-question challenge'); const back = h('a', { class: 'btn', href: mangaUrl(P.get('m')) }, 'Back to manga'); @@ -39,22 +44,22 @@ event.preventDefault(); try { const result = await api('api/reading-quiz/answer', { m: P.get('m'), challenge, answer: input.value }); - if (!result.correct) { message.textContent = 'Not quite. Try another spelling or answer—there is no attempt limit.'; input.select(); return; } - if (result.complete) { message.textContent = 'Unlocked. Opening your chapter…'; location.reload(); return; } - message.textContent = 'Correct.'; + if (!result.correct) { setMessage('Not quite. Try another spelling or answer—there is no attempt limit.', 'retry'); input.select(); return; } + if (result.complete) { setMessage('Unlocked. Opening your chapter…', 'success'); location.reload(); return; } + setMessage('Correct.', 'success'); showQuestion(result); } catch (error) { - message.textContent = error.message; + setMessage(error.message, 'error'); if (/challenge expired/i.test(error.message)) { challenge = null; question.textContent = ''; start.hidden = false; } } }); question.append(form); input.focus(); }; start.addEventListener('click', async () => { - try { showQuestion(await api('api/reading-quiz/start', { m: P.get('m') })); start.hidden = true; message.textContent = ''; } - catch (error) { message.textContent = error.message; } + try { showQuestion(await api('api/reading-quiz/start', { m: P.get('m') })); start.hidden = true; setMessage(''); } + catch (error) { setMessage(error.message, 'error'); } }); - if (!access.quizAvailable) { start.hidden = true; message.textContent = 'Fewer than five questions match your completed chapters. Please return during reading hours.'; } + if (!access.quizAvailable) { start.hidden = true; setMessage('Fewer than five questions match your completed chapters. Please return during reading hours.', 'info'); } window.addEventListener('pagehide', () => { if (challenge) navigator.sendBeacon('api/reading-quiz/abandon', new Blob([JSON.stringify({ challenge })], { type: 'application/json' })); }); diff --git a/tools/test-compose.js b/tools/test-compose.js index b542fe2..60fbfac 100644 --- a/tools/test-compose.js +++ b/tools/test-compose.js @@ -109,6 +109,26 @@ let keep = false; await page.getByRole('heading', { name: 'Reading is closed right now' }).waitFor(); await page.getByRole('button', { name: 'Start five-question challenge' }).click(); await page.getByText('Question 1 of 5').waitFor(); + const firstPrompt = await page.locator('.reading-gate-question h2').textContent(); + const firstTitle = await page.locator('.reading-gate-question .muted').textContent(); + const firstNumber = /question (\d)/.exec(firstPrompt)[1]; + const answerBox = page.getByRole('textbox', { name: 'Your answer' }); + await answerBox.fill('wrong'); + await page.getByRole('button', { name: 'Check answer' }).click(); + const retryNotice = page.locator('.reading-gate-message[data-tone="retry"]'); + await retryNotice.waitFor(); + const retryStyle = await retryNotice.evaluate(el => ({ color: getComputedStyle(el).color, + border: getComputedStyle(el).borderTopWidth, background: getComputedStyle(el).backgroundColor })); + assert.notEqual(retryStyle.border, '0px'); + await answerBox.fill((firstTitle === 'Moon Garden' ? 'moonanswer' : 'rainanswer') + firstNumber); + await page.getByRole('button', { name: 'Check answer' }).click(); + const successNotice = page.locator('.reading-gate-message[data-tone="success"]'); + await successNotice.waitFor(); + const successStyle = await successNotice.evaluate(el => ({ color: getComputedStyle(el).color, + background: getComputedStyle(el).backgroundColor })); + assert.notEqual(successStyle.color, retryStyle.color); + assert.notEqual(successStyle.background, retryStyle.background); + await page.getByText('Question 2 of 5').waitFor(); await page.goto(url + '/manga.html?m=' + encodeURIComponent(manga)); await page.goto(url + '/read.html?m=' + encodeURIComponent(manga) + '&c=' + encodeURIComponent(chapter)); await page.getByRole('button', { name: 'Start five-question challenge' }).waitFor();