diff --git a/frontend/src/pages/ArticlesPage.jsx b/frontend/src/pages/ArticlesPage.jsx
index 32a64fe..873287b 100644
--- a/frontend/src/pages/ArticlesPage.jsx
+++ b/frontend/src/pages/ArticlesPage.jsx
@@ -148,12 +148,12 @@ export default function ArticlesPage() {
Topic reading
Educator-reviewed articles linked to questions and cards.
- {user?.is_moderator && (
-
-
-
-
- )}
+ {/* No Draft and no New article here. Reading is where an article is
+ read, by everyone including the people who write them; writing one
+ starts in Editorial, which is the queue of what still needs a
+ person. Both panels still open on this page — Editorial sends you
+ here with ?draft=1 or ?new=1 — because this is where the library
+ they land in lives. */}
{showAi && user?.is_moderator && (
diff --git a/frontend/src/pages/FlashcardStudyPage.css b/frontend/src/pages/FlashcardStudyPage.css
index cef4c58..c927192 100644
--- a/frontend/src/pages/FlashcardStudyPage.css
+++ b/frontend/src/pages/FlashcardStudyPage.css
@@ -3,62 +3,91 @@
* 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.
+ * appearance. It looked right only if you happened to arrive via the list.
+ *
+ * Shaped like the session player, deliberately. Studying a deck is the same
+ * kind of work as sitting a paper: one thing at a time, filling the window,
+ * with the state of play fixed at the top and the controls fixed at the foot.
+ * A deck floating in the middle of an empty page reads as an aside; this reads
+ * as work.
*/
-/* ── 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; }
+/* The navbar carries 32px of margin under it everywhere else on the site, and
+ the shell below is measured against the window minus the header — so that
+ margin was 32px the arithmetic did not know about, which is exactly how far
+ off the bottom of the screen the verdict bar ended up. */
+body:has(.fc-study) .navbar { margin-bottom: 0; }
+
+.fc-study {
+ /* The header and its section strip, and nothing else. */
+ height: calc(100dvh - 98px);
+ display: flex; flex-direction: column;
+ padding: 0 24px;
+}
+.fc-study-main {
+ flex: 1; min-height: 0;
+ display: flex; flex-direction: column; gap: 12px;
+}
+
+/* The deck's own bar: what you are studying and how far in. Fixed, like the
+ player's — the counter leaving the screen exactly when a long card makes you
+ want it is the thing this prevents. */
+.fc-head { flex: none; padding: 14px 0 10px; border-bottom: 1px solid var(--border); }
+.fc-head-row {
+ display: flex; align-items: center; justify-content: space-between;
+ gap: 12px; flex-wrap: wrap;
+}
+.fc-head h2 { margin: 0 0 2px; font-size: 1.05rem; }
+.fc-head-meta { margin: 0; font-size: .82rem; color: var(--text-muted); }
+.fc-head-tools { display: flex; gap: 6px; flex-wrap: wrap; }
+.fc-head .progress-bar { margin-top: 10px; height: 4px; }
+
+/* The card takes what is left. It scrolls inside its own frame rather than
+ pushing the verdict off the screen, which is what a radiograph did. */
.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;
+ flex: 1; min-height: 0; overflow-y: auto;
display: flex; flex-direction: column; align-items: center; justify-content: center;
- gap: 10px; padding: 32px 28px; text-align: center; cursor: pointer;
+ gap: 12px; padding: 40px 32px; 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);
+ border-radius: var(--card-radius); box-shadow: 0 4px 20px rgba(0, 0, 0, .06);
transition: border-color .2s;
}
.fc-card.is-flipped { border-color: var(--primary); }
.fc-card .imgfig { margin: 0; }
+.fc-face { max-width: 62ch; }
+/* The three things, in the player's own shape: the way out at the left, the
+ navigation in the middle, the verdict beside it. Nothing here ends anything
+ — a deck has no block to hand in — so the left is the way back to the decks
+ rather than an Exit session. */
.fc-foot {
- position: sticky; bottom: 0; z-index: 2;
- padding: 10px 0 6px; background: var(--bg);
+ flex: none;
+ display: flex; align-items: center; gap: 12px; flex-wrap: wrap;
+ padding: 10px 0 calc(10px + env(safe-area-inset-bottom));
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); }
+.fc-foot-nav { flex: 1; display: flex; align-items: center; justify-content: center; gap: 8px; flex-wrap: wrap; }
+.fc-keys { flex: none; margin: 0; font-size: .74rem; color: var(--text-subtle); }
-/* 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); }
+/* Reading an article beside the deck. The deck keeps the middle and does not
+ reflow as one reference is followed after another. */
+.fc-study.has-split .fc-study-main { display: none; }
+@media (min-width: 1100px) {
+ .fc-study.has-split { display: grid; grid-template-columns: minmax(0, 1fr) minmax(360px, 40%); gap: 16px; }
+ .fc-study.has-split .fc-study-main { display: flex; }
+ .fc-study.has-split .article-split-pane { max-height: 100%; }
}
-/* 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%; }
+@media (max-width: 820px) {
+ /* No room to box it: the page scrolls, and the verdict sticks to the bottom
+ of the window so the two buttons the exercise turns on are always there. */
+ .fc-study { height: auto; padding: 0 12px; }
+ .fc-card { min-height: 46dvh; padding: 24px 16px; }
+ .fc-foot { position: sticky; bottom: 0; background: var(--bg); z-index: 2; }
+ .fc-keys { display: none; }
}
diff --git a/frontend/src/pages/FlashcardStudyPage.jsx b/frontend/src/pages/FlashcardStudyPage.jsx
index c4e8623..b22dbd3 100644
--- a/frontend/src/pages/FlashcardStudyPage.jsx
+++ b/frontend/src/pages/FlashcardStudyPage.jsx
@@ -2,6 +2,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 { useClaimFullBleed } from '../context/SessionChrome'
import RichText from '../components/RichText'
import ArticleSplitPane from '../components/ArticleSplitPane'
import { SplitViewProvider } from '../context/SplitViewContext'
@@ -113,6 +114,11 @@ export default function FlashcardStudyPage() {
return () => window.removeEventListener('keydown', handler)
}, [next, prev, currentCard])
+ // The window, minus its gutters. Studying a deck is the same kind of work as
+ // sitting a session: one thing at a time, filling the screen. The site's
+ // navigation stays — a deck is not a paper you can leave half-marked.
+ useClaimFullBleed(true)
+
const [splitTrail, setSplitTrail] = useState([])
const pushSplit = useCallback((slug) => setSplitTrail(
trail => (trail.at(-1) === slug ? trail : [...trail, slug])), [])
@@ -132,22 +138,20 @@ export default function FlashcardStudyPage() {
- {/* The way out, above the title and in the same words as everywhere
- else in the app — not a grey button at the end of a row of controls
- that change what you are studying. */}
- ← Cards
-
- {/* Header */}
-
-
+ {/* The deck's bar: what you are studying, how far in, and the controls
+ that change the run. Fixed at the top the way the player's is — a
+ counter that scrolls away with the content is a counter that leaves
+ exactly when a long card makes you want it. */}
+
@@ -222,8 +222,13 @@ export default function FlashcardStudyPage() {
window, and the two buttons the whole exercise turns on were
below the fold — you scrolled to read, then scrolled back to say
whether you knew it. */}
+ {/* The player's own three-part bar: the way out at the left, the
+ navigation and the verdict in the middle, the keys at the right.
+ No Exit session — a deck has no block to hand in, and the
+ controls that change the run are at the top where they belong. */}