A question on screen
How the classyQuiz projector lays out a question, with or without its picture, and how a student's device shows its answers: the markup, and the rules from the site's stylesheet. What the stylesheet doesn't carry is in comments: the type sizes and the picture's size and place, which a script sets from the length of the text and the picture's shape, and what the reveal adds.
Markup
<!-- The projector, one question. Always the light theme: projectors wash out dark backgrounds. -->
<body class="screen"><main>
<div class="bar"><span>2 / 12</span><span class="grow"></span><span>👤 <span>17 / 24</span></span></div>
<div class="stage">
<h2 class="qtext">A jacket costs 80 francs and is reduced by 25%. What does it cost now?</h2>
<div class="tiles">
<div class="tile a0"><svg class="shape" viewBox="0 0 100 100"><path d="M50 12 L90 84 H10Z"/></svg><span class="txt">55 francs</span><span class="side"></span></div>
<div class="tile a1"><svg class="shape" viewBox="0 0 100 100"><path d="M50 8 L92 50 L50 92 L8 50Z"/></svg><span class="txt">20 francs</span><span class="side"></span></div>
<div class="tile a2"><svg class="shape" viewBox="0 0 100 100"><circle cx="50" cy="50" r="40"/></svg><span class="txt">75 francs</span><span class="side"></span></div>
<div class="tile a3"><svg class="shape" viewBox="0 0 100 100"><rect x="13" y="13" width="74" height="74" rx="4"/></svg><span class="txt">60 francs</span><span class="side"></span></div>
</div>
</div>
</main></body>
<!-- At the reveal: a tile that doesn't count gets the class "dim"; every tile's .side becomes
<span class="mark">✓</span> (only on a tile that counts) <span class="n">17</span> (how many picked it),
and each tile gets <span class="fill" style="width: 100 × its count ÷ the highest count %"></span>,
growing from 0. The tiles are marked in place: nothing else is redrawn, so neither text nor picture moves. -->
<!-- A question with a picture (its Image: line). Between the question and the tiles: -->
<div class="stage fig between">
<h2 class="qtext">Welche Zahl ist mit dem Pfeil markiert?</h2>
<div class="figure" style="width: 1298px; height: 214px"><img src="…" width="2000" height="330" alt=""></div>
<div class="tiles"> … the tiles as above … </div>
</div>
<!-- Or beside them, the picture on the left: -->
<div class="stage fig beside" style="grid-template-columns: auto minmax(0, 1fr)">
<div class="figure" style="width: 606px; height: 606px"><img …></div>
<div class="col"><h2 class="qtext">…</h2><div class="tiles"> … one column … </div></div>
</div>
<!-- Which one, and the figure's size in px, the script works out from the picture's shape and the room the
question leaves (sizes for a 1366×768 screen above). It lays out "between": the question first, at most
30% of the height; the tiles keep at least 13.5% of the height per row for answers up to 12 characters,
16% up to 30, 20% beyond; the picture gets the rest, at most 62% of the height, never wider than the screen.
It then takes "beside" if that shows the picture at least 1.3 times larger, with the picture at 58% (else 50%)
of the width, and only if the question stays at 1.5em or more and no answer drops below 1.3em in the
narrower column. So wide figures such as number lines go between, square and upright ones and photos
beside, and a long question keeps its picture between. A picture smaller than its box is enlarged.
Z, or a click on the picture, enlarges it over the whole screen until the next step:
<div id="zoom"><div class="figure" style="(as large as fits in 96% × 94% of the screen)"><img …></div></div>
With a background (a Background: line), the body gets the class "has-backdrop" and, first in it:
<div id="backdrop"><img …></div>
The question and the bar then sit on white cards. -->
<!-- A student's phone or tablet: the answers as buttons, no picture; the question is on the projector. -->
<body class="play"><main>
<div class="buttons n4">
<button class="ansbtn a0"><svg class="shape" viewBox="0 0 100 100"><path d="M50 12 L90 84 H10Z"/></svg><span>55 francs</span></button>
<button class="ansbtn a1"><svg class="shape" viewBox="0 0 100 100"><path d="M50 8 L92 50 L50 92 L8 50Z"/></svg><span>20 francs</span></button>
<button class="ansbtn a2"><svg class="shape" viewBox="0 0 100 100"><circle cx="50" cy="50" r="40"/></svg><span>75 francs</span></button>
<button class="ansbtn a3"><svg class="shape" viewBox="0 0 100 100"><rect x="13" y="13" width="74" height="74" rx="4"/></svg><span>60 francs</span></button>
</div>
</main></body>
<!-- .a0 to .a3 are places on screen, not places in the quiz text: the site shuffles each question's answers at
upload (and again for another class), the same on the projector, every tablet and the teacher's screens.
A question with the line "Order: fixed", and answers that are letters in order (A, B, C), keep the text's order. -->
Stylesheet
:root { --bg: #fbf8f2; --fg: #1d1d1b; --muted: #5d5c57; --good: #1f8a4c; --bad: #c62828; }
body { margin: 0; background: var(--bg); color: var(--fg); font: 17px/1.45 system-ui, -apple-system, "Segoe UI", Roboto, sans-serif; }
.a0 { --ans: #d93b3b; --ans-ink: #fff; } /* 1st answer: red triangle */
.a1 { --ans: #2f6fd6; --ans-ink: #fff; } /* 2nd: blue diamond */
.a2 { --ans: #e8b100; --ans-ink: #1b1b1f; } /* 3rd: yellow circle */
.a3 { --ans: #1f9d55; --ans-ink: #fff; } /* 4th: green square */
.shape { width: 1em; height: 1em; display: inline-block; flex: none; fill: currentColor; }
/* Projector */
body.screen { overflow: hidden; height: 100dvh; font-size: clamp(16px, 1.6vw, 40px); }
.screen main { height: 100dvh; display: flex; flex-direction: column; padding: 2.2vh 2.5vw; gap: 2vh; }
.screen .bar { display: flex; align-items: center; gap: 1.2em; font-weight: 700; color: var(--muted); font-size: 1.2em; }
.screen .bar .grow { flex: 1; }
.stage { flex: 1; min-height: 0; display: flex; flex-direction: column; gap: 2vh; overflow: hidden; }
.qtext { font-weight: 700; text-align: center; line-height: 1.2; margin: 0; overflow-wrap: anywhere; display: flex; align-items: center; justify-content: center; flex: 0 1 auto; min-height: 18vh; }
/* .qtext font-size is set by script from the question's length: up to 40 characters 3.6em, up to 90 3em,
up to 160 2.4em, longer 1.9em; then reduced in 0.15em steps until the question fits in 34vh, down to 1.1em.
With a picture: 3em, 2.5em, 2.1em, 1.8em, fitting in 30% of the stage's height (42% beside). */
.tiles { flex: 1; display: grid; grid-template-columns: 1fr 1fr; gap: 1.6vh; min-height: 0; }
/* .tiles font-size is set by script from the longest answer: up to 12 characters 2.6em, up to 30 2em,
up to 60 1.55em, longer 1.25em; then reduced in 0.1em steps until no tile overflows, down to 1em;
if it still overflows, words are broken. A number written in groups ("73 000") stays on one line. */
.tile { background: var(--ans); color: var(--ans-ink); border-radius: 18px; display: flex; align-items: center; gap: .6em; padding: 1.2vh 1.6vw; font-weight: 700; position: relative; overflow: hidden; min-height: 0; }
.tile .shape { width: 1.6em; height: 1.6em; }
.tile .txt { flex: 1; min-width: 0; line-height: 1.15; }
.tile .side { flex: none; width: 3.4em; display: flex; align-items: center; justify-content: flex-end; gap: .2em; }
.tile .n { font-size: 1.3em; font-weight: 800; font-variant-numeric: tabular-nums; }
.tile .fill { position: absolute; left: 0; bottom: 0; height: 1.6vh; background: currentColor; opacity: .7; transition: width .6s; }
.tile.dim { opacity: .5; }
/* A question's picture: always on white, so a transparent drawing reads on any background. */
.stage.fig .qtext { min-height: 0; flex: none; }
.figure { flex: none; align-self: center; background: #fff; border-radius: 10px; overflow: hidden; box-shadow: 0 1px 6px rgba(0, 0, 0, .14); }
.figure img { display: block; width: 100%; height: 100%; object-fit: contain; }
.stage.beside { display: grid; grid-template-columns: auto minmax(0, 1fr); align-items: center; column-gap: 2.5vw; }
.stage.beside .col { height: 100%; min-height: 0; display: flex; flex-direction: column; gap: 2vh; }
.stage.beside .tiles { grid-template-columns: minmax(0, 1fr); }
#zoom { position: fixed; inset: 0; z-index: 5; background: var(--bg); display: flex; align-items: center; justify-content: center; }
/* A background: behind everything, under a light veil; the text on white cards. */
#backdrop { position: fixed; inset: 0; z-index: -1; overflow: hidden; }
#backdrop img { width: 100%; height: 100%; object-fit: cover; display: block; }
#backdrop::after { content: ""; position: absolute; inset: 0; background: rgba(251, 248, 242, .55); }
.has-backdrop .qtext { align-self: center; max-width: 100%; padding: .25em .7em; background: rgba(255, 255, 255, .9); border-radius: 18px; }
body.screen.has-backdrop .bar { background: rgba(255, 255, 255, .85); border-radius: 12px; padding: .2em .6em; }
/* Student */
.play main { min-height: 100dvh; display: flex; flex-direction: column; padding: 12px; gap: 12px; }
.play .buttons { flex: 1; display: grid; gap: 12px; grid-template-columns: 1fr 1fr; grid-auto-rows: 1fr; }
.play .buttons.n2 { grid-template-columns: 1fr; }
.ansbtn { background: var(--ans); color: var(--ans-ink); border: 0; border-radius: 18px; display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 8px; padding: 10px; font-size: clamp(1.3rem, 4.5vmin, 2.6rem); font-weight: 700; min-height: 90px; overflow-wrap: anywhere; }
.ansbtn .shape { width: clamp(52px, 12vmin, 140px); height: clamp(52px, 12vmin, 140px); }