News

NewsQuiz

The week's news quiz: a question at a time, each pick marked right or wrong with the story behind it, then the score with a mark per question and Play again.

#news#quiz#game#forms#interactive

Demo

Preview

The week in news, Oct 30

Question 1 of 3

Which city was picked on Tuesday to host the 2030 climate summit?

Not finished yet.

Get it

It needs theme.qwe, as every component does; or take the whole kit, quillo-ui.qwe. Each component brings its own styles to the pages that show it.

$ curl -O https://quillolang.org/components/ui/theme.qwe$ curl -O https://quillolang.org/components/ui/news_quiz.qwe
main.qwe
import ui/theme.qwe use theme_cssuse ui/news_quiz.qweroute GET / = <html><head><style>{theme_css}</style></head><body class="ui-root">...</body></html>

Example

The code of the preview above.

demos/news_quiz.qwe
component NewsQuizDemoLive() {    state note = "Not finished yet."    <div.demo-stack>        <NewsQuiz id="news-quiz-demo" title="The week in news, Oct 30" on_finish={(score, total) => { note = `on_finish gave ${score} of ${total}` }}                  questions={[                      { q: "Which city was picked on Tuesday to host the 2030 climate summit?", choices: ["Lisbon", "Nairobi", "Osaka", "Bogotá"],                        answer: 1, explain: "Nairobi will be the first African city to host it.", href: "#summit" },                      { q: "By how much did the central bank change its main rate this week?", choices: ["Cut by 0.25 points", "Raised by 0.25 points", "No change"],                        answer: 2, explain: "It held at 4.25% for a fourth month." },                      { q: "What did the city council vote to ban from the old town from January?", choices: ["Cars", "Tour buses", "E-scooters", "Short-term rentals"],                        answer: 2, href: "#scooters" },                  ]}/>        <p.demo-note id="news-quiz-demo-note">{note}</p>    </div>}view NewsQuizDemo() = <NewsQuizDemoLive/>

Source

ui/news_quiz.qwe
// NewsQuiz: the week's news quiz: one question at a time with its choices; a pick// is marked right or wrong at once, with the right answer and a line on the story// behind it; then the next question, and at the end the score, a mark for each// question and a word for how it went, with Play again. Screen readers hear each// verdict, and the focus goes to each new question.////   <NewsQuiz id="quiz-44" title="The week in news" questions={[//       { q: "Which city will host the 2030 summit?", choices: ["Lisbon", "Nairobi", "Osaka"], answer: 1,//         explain: "Nairobi was picked on Tuesday, the first African city to host it.", href: "/world/summit" },//       ...//   ]} on_finish={(score, total) => { save_score(score) }}/>//// questions: maps of { q, choices, answer, explain?, href? }: q the question;// choices its answers (two to six); answer the right one's place, from 0; explain a// line shown after a pick; href the story it comes from ("Read the story").// id (required): unique on the page, for the questions' ids. title: the quiz's// heading, an <h3> ("News quiz"). verdicts: what to say at the end, from best to// worst, for a full score, at least two thirds, at least one third, and less// (["Perfect score!", "Well read.", "Not bad.", "Time to catch up on the news."]).// on_finish(score, total): inside one of your components, given the number right// and the number of questions when the last is answered.import ui/theme.qwe use ui_clslet news_quiz_css = css`.ui-nquiz { display: grid; gap: 14px; max-width: 560px; padding: 18px 20px 20px; border: 1px solid var(--ui-border); border-radius: var(--ui-radius);  background: var(--ui-surface); color: var(--ui-fg); font: 15px/1.45 var(--ui-font); }.ui-nquiz-head { display: grid; gap: 8px; }.ui-nquiz-row { display: flex; flex-wrap: wrap; align-items: baseline; justify-content: space-between; gap: 4px 12px; }.ui-nquiz .ui-nquiz-title { margin: 0; font-size: 17px; font-weight: 750; }.ui-nquiz-count { color: var(--ui-muted); font-size: 13px; font-variant-numeric: tabular-nums; }.ui-nquiz-track { display: grid; grid-auto-flow: column; grid-auto-columns: 1fr; gap: 3px; }.ui-nquiz-track span { height: 4px; border-radius: 2px; background: var(--ui-border); }.ui-nquiz-track .ui-nquiz-r { background: var(--ui-success); }.ui-nquiz-track .ui-nquiz-w { background: var(--ui-danger); }.ui-nquiz-track .ui-nquiz-now { background: var(--ui-accent); }.ui-nquiz-q { margin: 0; font-size: 19px; line-height: 1.35; font-weight: 700; }.ui-nquiz-q:focus { outline: none; }.ui-nquiz-q:focus-visible { box-shadow: var(--ui-focus); border-radius: 4px; }.ui-nquiz-choices { display: grid; gap: 8px; margin: 0; padding: 0; list-style: none; }.ui-nquiz-choice { display: flex; align-items: center; gap: 12px; width: 100%; min-height: 46px; padding: 8px 12px; border: 1px solid var(--ui-border);  border-radius: var(--ui-radius-sm); background: var(--ui-surface); color: var(--ui-fg); font: 500 15px/1.35 var(--ui-font); text-align: left; cursor: pointer;  transition: border-color .15s, background .15s; }.ui-nquiz-choice:hover:not([aria-disabled="true"]) { border-color: var(--ui-accent); background: var(--ui-accent-soft); }.ui-nquiz-choice:focus-visible { outline: none; box-shadow: var(--ui-focus); }.ui-nquiz-choice[aria-disabled="true"] { cursor: default; }.ui-nquiz-letter { flex: none; display: grid; place-items: center; width: 28px; height: 28px; border-radius: 50%; background: var(--ui-subtle);  color: var(--ui-muted); font-size: 13px; font-weight: 700; }.ui-nquiz-text { flex: 1; }.ui-nquiz-mark { flex: none; font-weight: 800; }.ui-nquiz-right { border-color: var(--ui-success); background: var(--ui-success-soft); }.ui-nquiz-right .ui-nquiz-letter { background: var(--ui-success); color: var(--ui-bg); }.ui-nquiz-right .ui-nquiz-mark { color: var(--ui-success); }.ui-nquiz-wrong { border-color: var(--ui-danger); background: var(--ui-danger-soft); }.ui-nquiz-wrong .ui-nquiz-letter { background: var(--ui-danger); color: var(--ui-bg); }.ui-nquiz-wrong .ui-nquiz-mark { color: var(--ui-danger); }.ui-nquiz-faded { opacity: .55; }.ui-nquiz-feedback { display: grid; gap: 4px; }.ui-nquiz-feedback:empty { margin-top: -14px; }.ui-nquiz-verdict { margin: 0; font-weight: 700; }.ui-nquiz-ok .ui-nquiz-verdict { color: var(--ui-success); }.ui-nquiz-no .ui-nquiz-verdict { color: var(--ui-danger); }.ui-nquiz-explain { margin: 0; color: var(--ui-muted); font-size: 14px; }.ui-nquiz-explain a { color: var(--ui-accent); font-weight: 600; }.ui-nquiz-btn { justify-self: start; height: 40px; padding: 0 18px; border: 0; border-radius: 999px; background: var(--ui-accent); color: var(--ui-accent-fg);  font: 650 14px var(--ui-font); cursor: pointer; }.ui-nquiz-btn:hover { filter: brightness(1.08); }.ui-nquiz-btn:focus-visible, .ui-nquiz a:focus-visible { outline: none; box-shadow: var(--ui-focus); }.ui-nquiz-end { display: grid; justify-items: center; gap: 10px; padding: 8px 0 4px; text-align: center; }.ui-nquiz-score { margin: 0; font-size: 42px; line-height: 1; font-weight: 800; font-variant-numeric: tabular-nums; }.ui-nquiz-score small { color: var(--ui-muted); font-size: 20px; font-weight: 600; }.ui-nquiz-say { margin: 0; font-size: 17px; font-weight: 650; }.ui-nquiz-marks { display: flex; gap: 6px; margin: 0; padding: 0; list-style: none; }.ui-nquiz-marks li { display: grid; place-items: center; width: 24px; height: 24px; border-radius: 50%; font-size: 12px; font-weight: 800; color: var(--ui-bg); }.ui-nquiz-marks .ui-nquiz-r { background: var(--ui-success); }.ui-nquiz-marks .ui-nquiz-w { background: var(--ui-danger); }.ui-nquiz-end .ui-nquiz-btn { justify-self: center; }@media (prefers-reduced-motion: reduce) { .ui-nquiz-choice { transition: none; } }`component NewsQuiz(questions: list, id: str, title: str = "News quiz",                   verdicts: list = ["Perfect score!", "Well read.", "Not bad.", "Time to catch up on the news."],                   on_finish = null, class: str? = null) {    style global news_quiz_css    state at = 0    state picked = null    state results = []    state done = false    state focus = null    let total = len(questions)    let q = questions[at]    let score = len(results | filter((r) => r))    let letters = ["A", "B", "C", "D", "E", "F"]    let q_id = `${id}-q`    if focus != null {        every(16, () => {            let el = js("document.getElementById", focus)            focus = null            if el != null { el.focus() }        })    }    let choose = (i: int) => {        if picked == null {            picked = i            results = results + [i == q.answer]            if at == total - 1 && on_finish != null {                on_finish(len(results | filter((r) => r)), total)            }        }    }    let next = () => {        if at < total - 1 {            at += 1            picked = null            focus = q_id        } else {            done = true            focus = `${id}-score`        }    }    let again = () => {        at = 0        picked = null        results = []        done = false        focus = q_id    }    let say = score == total ? verdicts[0] : (score * 3 >= total * 2 ? verdicts[1] : (score * 3 >= total ? verdicts[2] : verdicts[3]))    <section class={ui_cls(["ui-nquiz", class])} id={id} aria-labelledby={`${id}-title`}>        <div.ui-nquiz-head>            <div.ui-nquiz-row>                <h3.ui-nquiz-title id={`${id}-title`}>{title}</h3>                <span.ui-nquiz-count>{done ? `${total} questions` : `Question ${at + 1} of ${total}`}</span>            </div>            <div.ui-nquiz-track aria-hidden="true">                {for i in range(total) => <span class={i < len(results) ? (results[i] ? "ui-nquiz-r" : "ui-nquiz-w") : (i == at && !done ? "ui-nquiz-now" : null)}></span>}            </div>        </div>        {done            ? <div.ui-nquiz-end>                  <p.ui-nquiz-score id={`${id}-score`} tabindex="-1">{score}<small> / {total}</small><span.ui-sr> right</span></p>                  <p.ui-nquiz-say>{say}</p>                  <ol.ui-nquiz-marks aria-label="Your answers">                      {for [i, r] in enumerate(results) => <li class={r ? "ui-nquiz-r" : "ui-nquiz-w"}>                          <span aria-hidden="true">{r ? "✓" : "✗"}</span><span.ui-sr>Question {i + 1}: {r ? "right" : "wrong"}</span>                      </li>}                  </ol>                  <button.ui-nquiz-btn type="button" @click={again}>Play again</button>              </div>            : [                <p.ui-nquiz-q id={q_id} tabindex="-1">{q.q}</p>,                <ul.ui-nquiz-choices role="list" aria-labelledby={q_id}>                    {for [i, c] in enumerate(q.choices) => {                        let right = picked != null && i == q.answer                        let wrong = picked != null && i == picked && i != q.answer                        <li key={`${at}-${i}`}>                            <button type="button" data-choice={i}                                    class={ui_cls(["ui-nquiz-choice", right && "ui-nquiz-right", wrong && "ui-nquiz-wrong",                                                   picked != null && !right && !wrong && "ui-nquiz-faded"])}                                    aria-disabled={picked != null ? "true" : null}                                    @click={() => choose(i)}>                                <span.ui-nquiz-letter aria-hidden="true">{letters[i] ?? ""}</span>                                <span.ui-nquiz-text>{c}</span>                                {right && <span.ui-nquiz-mark><span aria-hidden="true">✓</span><span.ui-sr>, the right answer</span></span>}                                {wrong && <span.ui-nquiz-mark><span aria-hidden="true">✗</span><span.ui-sr>, wrong</span></span>}                                {picked == i && <span.ui-sr>, your answer</span>}                            </button>                        </li>                    }}                </ul>,                <div class={ui_cls(["ui-nquiz-feedback", picked != null && (picked == q.answer ? "ui-nquiz-ok" : "ui-nquiz-no")])} role="status">                    {picked != null && [                        <p.ui-nquiz-verdict>{picked == q.answer ? "Right!" : `Not quite: it was ${letters[q.answer] ?? ""}, ${q.choices[q.answer]}.`}</p>,                        (q.explain != null || q.href != null) && <p.ui-nquiz-explain>                            {q.explain}{q.explain != null && q.href != null && " "}{q.href != null && <a href={q.href}>Read the story</a>}                        </p>,                    ]}                </div>,                picked != null && <button.ui-nquiz-btn type="button" @click={next}>{at < total - 1 ? "Next question" : "See your score"}</button>,              ]}    </section>}
Thread
88Useful? Like it, or add to its thread below.

Thread 0 posts

On NewsQuiz: questions, tips, corrections, and code that uses it.

No posts yet. The first one opens the thread.