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. */} +
+
-

{deck.title}

-

+

{deck.title}

+

{cards.length} cards · {counts.due} due · {counts.unseen} new - {known.size > 0 && ` · ${known.size} answered`} + {known.size > 0 && ` · ${known.size} answered`} · {progress}% mastered

-
+
{review.size > 0 && ( @@ -156,12 +160,8 @@ export default function FlashcardStudyPage() {
- {/* Progress bar */} -
-
-
-
-
{progress}% mastered
+
+
@@ -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. */}
-
+ ← All decks +
{flipped ? ( <> @@ -235,7 +240,7 @@ export default function FlashcardStudyPage() { )}
-

Space or Enter flips · ← → moves · 1 got it · 2 review

+

Space flips · ← → moves · 1 got it · 2 review

)}