pdf-quiz-generator/frontend/src/components/ImageFigure.css
Daniel 158930d532
Some checks failed
Tests / backend (push) Failing after 5s
Tests / frontend (push) Successful in 28s
Tests / e2e (push) Failing after 36s
feat: section links in prose, a picker that writes them, and cards that render
**Cross-references can name a section.** `[[264#workup|the workup]]` opens the
reader at that heading, which is what a sentence about one part of a long
article actually means. Whole-article `[[264|label]]` is unchanged, and a
section renamed since is not a broken link — it lands at the top of the right
article, which is a mild disappointment rather than a dead end.

**A picker that writes the marker for you.** 🔗 Link an article, in the editor:
type a few words, click the article — or one of its sections — and the marker
is on the clipboard with the right title as its label. Getting an id used to
mean opening the library in another tab, finding the article and reading the
number out of the address bar, which is four steps and a chance to mistype,
every time. Its own small endpoint, because the listing deliberately does not
carry sections and this needs nothing else.

**Three things about cards that were built but never drawn:**

- A card can carry an image. The column is there, the API returns it, the
  editor accepts one — and no view in the app rendered it, so every picture
  anybody attached to a card was stored and never seen. Both card views show it
  now, small until clicked like every other figure.
- The deck browser printed `[[331|Epiglottitis]]` as brackets and a number. The
  study view has rendered them as links for a while; now both do.
- There was no way to make a deck by hand. Every deck came out of a model —
  generated from a document section or an article — so an educator who wanted
  to write six cards had nowhere to put them, and the add-a-card route could
  only add to a deck that did not exist yet. `+ New deck` on the cards page.

**Generate cards ran in silence.** It starts a real job, and the only place its
progress was drawn was inside the refine panel — which lives in the editor and
is shut. Pressing it on the reading page did nothing visible for ninety
seconds. It now says what it is doing where it was pressed.

**Overlays were invisible to learners.** A stored width is a fraction of the
image, and the stroke is drawn with `non-scaling-stroke`, which makes
`stroke-width` a count of screen pixels — so 0.006 meant six thousandths of a
pixel. The editor has always multiplied by its rendered width; the viewer now
does the same sum. Every region an educator has ever marked was invisible to
everyone who was not editing it.

Also: the figure viewer no longer scrolls, at any width, and the page behind it
is pinned properly (`overflow: hidden` on the body does nothing on iOS, so a
figure opened half-way down an article drifted while it was read). Options are
full width on a phone. The question toolbar's seven glyphs are four, with the
rest folded into the ⋯ that was already there, spelled out in words. The jobs
popover closes on a click anywhere outside it. And the editor has a way back to
Editorial — "back to the article", from an article you opened to edit, is a
loop.

The contract snapshot caught both new routes on the way through, which is what
it is for.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TqXevQJhxFrM7jJg82cgZN
2026-09-13 02:05:02 +02:00

105 lines
5.3 KiB
CSS

/* Small until asked for. The caption is the author's own alt text, so what a
reader sees under the figure and what a screen reader is told are the same
sentence — and there is no second field to keep in step with the first. */
.imgfig { display: inline-flex; flex-direction: column; gap: 5px; margin: 10px 12px 10px 0; vertical-align: top; max-width: 100%; }
.imgfig-thumb {
display: block; padding: 0; cursor: zoom-in; line-height: 0;
border: 1px solid var(--border); border-radius: 8px; overflow: hidden;
background: var(--card-bg);
}
.imgfig-thumb:hover { border-color: var(--primary); }
.imgfig-thumb img { display: block; max-width: 260px; max-height: 200px; width: auto; height: auto; }
.imgfig-thumb.is-vector { width: min(100%, 260px); }
.imgfig-thumb.is-vector img { width: 100%; height: auto; }
.imgfig-label { max-width: 260px; font-size: 0.78rem; line-height: 1.4; color: var(--text-muted); }
/* ── The viewer ───────────────────────────────────────────────────────
The image gets the screen, and what is known about it sits beside it. Dark,
because a radiograph read against a white page is read through glare. */
.imgfig-viewer {
position: fixed; inset: 0; z-index: 1200;
display: flex; flex-direction: column;
background: #0b1220; color: #e2e8f0;
/* The viewer never scrolls as a whole, at any width. It is one screen: look
at the picture, close it, carry on from where you were. A scrollbar down
the side of it invites a scroll that moves nothing anybody wanted moved. */
overflow: hidden;
}
.imgfig-bar {
display: flex; align-items: center; gap: 10px;
padding: 10px 14px; border-bottom: 1px solid rgba(255, 255, 255, .1);
}
.imgfig-tool {
display: inline-flex; align-items: center; gap: 7px;
padding: 6px 13px; cursor: pointer; font: inherit; font-size: 0.83rem; font-weight: 600;
border: 1px solid rgba(255, 255, 255, .22); border-radius: 8px;
background: transparent; color: #e2e8f0;
}
.imgfig-tool:hover { border-color: #5eead4; color: #5eead4; }
.imgfig-tool.is-on { border-color: #5eead4; color: #0b1220; background: #5eead4; }
.imgfig-close {
margin-left: auto; width: 34px; height: 34px; cursor: pointer;
border: 0; border-radius: 8px; background: transparent; color: #e2e8f0; font-size: 1rem;
}
.imgfig-close:hover { background: rgba(255, 255, 255, .12); }
.imgfig-body {
flex: 1; min-height: 0; overflow: hidden;
display: grid; grid-template-columns: 320px minmax(0, 1fr);
}
.imgfig-desc {
display: flex; flex-direction: column; gap: 14px;
padding: 26px 24px; overflow-y: auto;
border-right: 1px solid rgba(255, 255, 255, .1);
}
.imgfig-desc strong { font-size: 1.05rem; font-weight: 700; }
.imgfig-desc-text { font-size: 0.92rem; line-height: 1.6; color: #cbd5e1; }
.imgfig-source { font-size: 0.78rem; line-height: 1.55; color: #94a3b8; }
.imgfig-source a { color: #94a3b8; }
.imgfig-stage {
display: flex; align-items: center; justify-content: center;
/* `hidden`, not `auto`: the picture is capped to the space it has, so there
is nothing to scroll to. */
padding: 20px; overflow: hidden; cursor: zoom-out;
min-height: 0;
}
/* The frame is what the overlay is positioned against, so it must be exactly
the size of the drawn image and no larger — hence a block that shrinks to
its content rather than a flex child that stretches. */
.imgfig-frame { position: relative; display: block; line-height: 0; cursor: default; }
.imgfig-frame { max-width: 100%; max-height: 100%; }
.imgfig-frame img { display: block; max-width: 100%; max-height: 100%; width: auto; height: auto; }
/* A drawing has no size of its own, only a shape, so it is given the width it
is allowed and takes its height from that. Line art scales; a photograph
would be blown up by the same rule, which is why this is not the default. */
.imgfig-frame.is-vector { width: min(100%, 900px); }
.imgfig-frame.is-vector img { width: 100%; height: auto; max-height: 100%; object-fit: contain; }
.imgov { position: absolute; inset: 0; width: 100%; height: 100%; pointer-events: none; }
@media (max-width: 820px) {
/* Stacked, description first: it can be read before scrolling to the
picture rather than after it, and on a phone the picture wants the width
more than the text does. */
/* The viewer fills the screen and does not scroll as a whole. What the
picture is stays at the top and scrolls inside its own box if it is long;
the picture keeps everything under it. A viewer you have to scroll to see
the picture in is a viewer that looks broken. */
.imgfig-body { grid-template-columns: minmax(0, 1fr); grid-template-rows: auto minmax(0, 1fr); overflow: hidden; }
.imgfig-desc {
border-right: 0; border-bottom: 1px solid rgba(255, 255, 255, .1);
padding: 16px 16px 14px; gap: 10px;
max-height: 38dvh; overflow-y: auto;
}
.imgfig-stage { padding: 12px; }
.imgfig-frame img { max-height: 100%; }
/* Stacked, so the picture has the width of the screen and whatever height
its shape asks for — the row it sits in scrolls with the description. */
.imgfig-frame.is-vector { width: 100%; }
.imgfig-frame.is-vector img { max-height: 100%; }
}
@media (max-width: 560px) {
.imgfig { margin-right: 0; }
.imgfig-thumb img, .imgfig-label { max-width: 100%; }
}