diff --git a/frontend/src/components/ImageFigure.css b/frontend/src/components/ImageFigure.css
index 4ffd238..286ea66 100644
--- a/frontend/src/components/ImageFigure.css
+++ b/frontend/src/components/ImageFigure.css
@@ -1,7 +1,14 @@
/* 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%; }
+/* `align-items: start` matters. This is a column, so without it every child
+ stretches to the width of the widest — which is the caption — and the
+ thumbnail's bordered box grew to 260px around a portrait image half that
+ wide, leaving a white panel beside the picture inside its own frame. */
+.imgfig {
+ display: inline-flex; flex-direction: column; align-items: start;
+ 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;
diff --git a/frontend/src/components/ImageFigure.jsx b/frontend/src/components/ImageFigure.jsx
index 5cef765..48f995f 100644
--- a/frontend/src/components/ImageFigure.jsx
+++ b/frontend/src/components/ImageFigure.jsx
@@ -122,7 +122,13 @@ export function ImageViewer({ src, alt = '', attemptId, onClose }) {
)
}
-export default function ImageFigure({ src, alt = '', attemptId, className = '' }) {
+export default function ImageFigure({ src, alt = '', attemptId, className = '',
+ // Whether the alt text is also printed
+ // under the thumbnail. On a card it is
+ // not: the card's own words are the
+ // caption, and repeating them under the
+ // picture says the same thing twice.
+ showLabel = true }) {
const [open, setOpen] = useState(false)
if (!src) return null
@@ -137,7 +143,7 @@ export default function ImageFigure({ src, alt = '', attemptId, className = '' }
images to show the first paragraph. */}
- {label && {label}}
+ {showLabel && label && {label}}
{open && (
diff --git a/frontend/src/pages/FlashcardStudyPage.css b/frontend/src/pages/FlashcardStudyPage.css
new file mode 100644
index 0000000..cef4c58
--- /dev/null
+++ b/frontend/src/pages/FlashcardStudyPage.css
@@ -0,0 +1,64 @@
+/* Studying a deck.
+ *
+ * Its own file, imported by its own page. These rules lived in
+ * FlashcardsPage.css — the deck *list* — and each page is its own bundle, so
+ * opening a deck's study page directly loaded the markup with none of its
+ * appearance: no card, no box, no pinned verdict bar. It looked right only if
+ * you happened to arrive via the list.
+ */
+/* ── Studying a deck ──────────────────────────────────────────────────
+ Boxed, like the session player: the deck's header at the top, the verdict at
+ the foot, and only the card between them scrolls. A card with a picture on
+ it is taller than the window, and the two buttons the whole exercise turns
+ on were below the fold. */
+.fc-study-main { display: flex; flex-direction: column; gap: 12px; min-height: 0; }
+
+.fc-card {
+ /* Shrinks to the card, up to the height it has. A one-line front in a box
+ four hundred pixels tall is mostly emptiness; a card with a radiograph on
+ it scrolls inside its own frame rather than pushing the verdict buttons
+ off the screen. */
+ flex: 0 1 auto; min-height: 180px; overflow-y: auto;
+ display: flex; flex-direction: column; align-items: center; justify-content: center;
+ gap: 10px; padding: 32px 28px; text-align: center; cursor: pointer;
+ background: var(--card-bg); border: 2px solid var(--border);
+ border-radius: var(--card-radius); box-shadow: 0 4px 20px rgba(0, 0, 0, .08);
+ transition: border-color .2s;
+}
+.fc-card.is-flipped { border-color: var(--primary); }
+.fc-card .imgfig { margin: 0; }
+
+.fc-foot {
+ position: sticky; bottom: 0; z-index: 2;
+ padding: 10px 0 6px; background: var(--bg);
+ border-top: 1px solid var(--border);
+}
+.fc-foot-row { display: flex; gap: 8px; justify-content: center; flex-wrap: wrap; }
+.fc-keys { margin: 8px 0 0; text-align: center; font-size: .74rem; color: var(--text-muted); }
+
+/* Named rather than styled inline: green for the one that means "done with
+ this", red-lettered for the one that means "again". */
+.fc-known { background: #22c55e; border-color: #22c55e; color: #fff; }
+.fc-known:hover { background: #16a34a; border-color: #16a34a; }
+.fc-again { color: #ef4444; border-color: #ef4444; background: none; }
+.fc-again:hover { background: #fef2f2; }
+
+@media (min-width: 900px) {
+ /* Tall enough to be worth boxing: the header and the verdict stay put and
+ the card scrolls between them. `max-height` rather than `height`, so a
+ short card is short — the box is a ceiling, not a shape to fill. */
+ .fc-study-main { max-height: calc(100dvh - 150px); }
+}
+
+/* What you can do to one card in the browse list: one strip, wrapping onto a
+ second line on a narrow screen rather than becoming a column. */
+.fc-row-actions { display: flex; gap: 6px; flex-wrap: wrap; flex-shrink: 0; align-items: flex-start; }
+.fc-row-actions .btn { white-space: nowrap; }
+.fc-row-delete { color: var(--text-muted); }
+.fc-row-delete:hover { color: var(--wrong-fg, #b91c1c); border-color: currentColor; }
+
+@media (max-width: 700px) {
+ /* Under the card rather than beside it: at 390px a strip of four buttons
+ beside the text leaves the stem two words wide. */
+ .fc-row-actions { width: 100%; }
+}
diff --git a/frontend/src/pages/FlashcardStudyPage.jsx b/frontend/src/pages/FlashcardStudyPage.jsx
index 38f71eb..c4e8623 100644
--- a/frontend/src/pages/FlashcardStudyPage.jsx
+++ b/frontend/src/pages/FlashcardStudyPage.jsx
@@ -1,6 +1,7 @@
import { useState, useEffect, useCallback, useMemo } from 'react'
import { useParams, useNavigate, Link } from 'react-router-dom'
import api from '../api/client'
+import './FlashcardStudyPage.css'
import RichText from '../components/RichText'
import ArticleSplitPane from '../components/ArticleSplitPane'
import { SplitViewProvider } from '../context/SplitViewContext'
@@ -208,7 +209,7 @@ export default function FlashcardStudyPage() {
other figure. */}
{currentCard.image_path && (