diff --git a/src/App.module.css b/src/App.module.css index d68b444..0e6a231 100644 --- a/src/App.module.css +++ b/src/App.module.css @@ -112,3 +112,29 @@ .sheetLabel:first-child { margin-top: 0; } + +/* The detail sheet: what the 84px card cannot hold. */ +.detailCombo { + margin: 0; + font-family: var(--mono); + font-size: 12px; + font-weight: 700; + letter-spacing: 0.04em; + color: var(--accent); +} + +/* The gesture is the instruction for the 24 entries that have one. */ +.detailHow { + margin: 0; + font-size: 13px; + font-weight: 700; + line-height: 1.3; +} + +.detailBody { + margin: 0; + font-size: 13px; + line-height: 1.4; + color: var(--muted); + text-wrap: pretty; +} diff --git a/src/App.tsx b/src/App.tsx index ce76cb0..f56a5bb 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -11,7 +11,7 @@ import { useCallback, useEffect, useMemo, useReducer, useRef, useState } from "r import { DECKS, boxDistribution, deckPool, knownCount, pickCard } from "./domain/drill"; import type { DeckId, Direction, InputMode } from "./domain/drill"; import { ENTRIES, layerKeyOf } from "./domain/entries"; -import { controlName } from "./domain/format"; +import { comboText, contextNames, controlName } from "./domain/format"; import { CTX, CTXHINT, NOLATCH } from "./domain/labels"; import { entriesByTarget, @@ -35,7 +35,7 @@ import styles from "./App.module.css"; const TABS = ["Explore", "Drill", "Find", "Rules"] as const; type Tab = (typeof TABS)[number]; -type OpenSheet = null | "mode" | "layer" | "settings"; +type OpenSheet = null | "mode" | "layer" | "settings" | "detail"; /** Longer than this and the press was a hold to peek, not a tap to lock. */ const HOLD_MS = 350; @@ -322,7 +322,13 @@ export function App() { onPickOption={(name) => dispatch({ type: "pickOption", name })} /> ) : ( - + setSheet("detail")} + /> )} + setSheet(null)} + > + {selected ? ( + <> +

Combo

+

{comboText(selected)}

+ + {selected.how ? ( + <> +

Gesture

+

{selected.how}

+ + ) : null} + +

What it does

+

+ {selected.desc || "No description in the reference."} +

+ +

Where

+

+ {contextNames(selected.ctx).join(", ")} · manual p.{selected.page} +

+ + ) : null} +
+ setSheet(null)}> {(Object.keys(CTX) as ContextCode[]).map((code) => ( void; } -export function InfoCard({ entry, hint, count }: InfoCardProps) { +export function InfoCard({ entry, hint, count, onMore }: InfoCardProps) { + const body = useRef(null); + const [clipped, setClipped] = useState(false); + + // Measured rather than guessed from a character count: the card is a fixed + // 84px, so whether the text fits depends on how it wraps. + useEffect(() => { + const el = body.current; + setClipped(el !== null && el.scrollHeight > el.clientHeight + 1); + }, [entry]); + if (!entry) { return (
@@ -27,6 +40,10 @@ export function InfoCard({ entry, hint, count }: InfoCardProps) { ); } + // The gesture matters as much as the description — SHIFT + REMAIN is a + // three-second hold — so a card carrying one always offers the full text. + const showMore = clipped || entry.how !== undefined; + return (
@@ -38,11 +55,19 @@ export function InfoCard({ entry, hint, count }: InfoCardProps) { ))} - P.{entry.page} + + {showMore ? ( + + ) : null} + P.{entry.page} +

{entry.name}

-

- {entry.desc || contextNames(entry.ctx).join(", ")} +

+ {entry.how ? {entry.how}. : null} + {entry.desc}

);