diff --git a/.impeccable.md b/.impeccable.md new file mode 100644 index 0000000..603a0a5 --- /dev/null +++ b/.impeccable.md @@ -0,0 +1,29 @@ +## Design Context + +### Users +FGA-track gemmology students and self-taught hobbyists working through identification curriculum. They sit with the article open during a focused study block (often evenings, often after-work fatigue). They want signal density without noise. Many will have a 10× loupe or a refractometer next to the screen — the screen is one instrument among many, not the centre of attention. A non-trivial fraction is colour-vision-impaired (gemmology disproportionately attracts people who train themselves around colour weakness). + +### Brand Personality +**Scientifically clean and intriguing.** Three-word distillation: *precise, considered, alive*. The site is reference material that should feel handled — like a well-thumbed mineralogy textbook, the printed legend on a microscope eyepiece, or the engraved scale on a gemmologist's polariscope. Clean does not mean empty; intrigue does not mean decoration. The page must not feel dead. + +### Aesthetic Direction +- **Typographic-led, not graphic-led.** Hierarchy comes from weight, size, leading, and a controlled accent palette — never from drop shadows, gradient fills, or icon decoration. +- **Quiet accent, sharp rhythm.** The existing `crystal:` (sky-blue) palette is the single chromatic anchor. `gem:` tokens are reserved for mineral-data contexts. Slate is the body neutral. +- **Instrument-label moments.** Where the interface acts (correct/wrong feedback, "Quick check" header, badges), it should read like an etched marking on a brass scale — confident, small, definite. Avoid SaaS-style coloured pills with rounded everything. +- **Light-first, dark-fluent.** Light is the default reading surface; dark mode must be a true mirror, not a partial inversion. + +### Anti-references +- **Not Duolingo or gamified-flashcard energy.** No bouncing, no streak fire, no celebration glitter. +- **Not generic admin/SaaS.** No left-stripe-coloured callouts, no gradient hero text, no purple-to-blue accent gradients, no Inter, no Syne. +- **Not a "lab tech demo."** No glowing neon-on-near-black, no monospace as decoration. Monospace is reserved for actual readouts (counters, codes, formulae). +- **Not flat-card-grid templates.** Cards exist; they should not feel mass-produced. + +### Theme & Constraints +- **Theme**: light + dark, both must be first-class. Dark is for late-evening study and must remain readable without reaching for a brightness slider. +- **Stack**: Tailwind v3 with `crystal:` and `gem:` palette extensions in `tailwind.config.mjs`; dark mode via `class` strategy on ``. +- **Accessibility**: WCAG 2.2 AA non-negotiable; the site was just remediated. Maintain ≥ 4.5:1 contrast on body text, ≥ 3:1 on large/non-text. Do not regress. +- **Motion**: subtle, exponential easing (ease-out-quart/quint), under 200ms for state changes. Honour `prefers-reduced-motion`. +- **No emoji** in product UI. Icons come from Lucide React when needed. + +### One-line aesthetic vow +*Restrained, precise, with one moment per surface that makes the eye pause.* diff --git a/src/components/quiz/study/LearnQuizWidget.test.tsx b/src/components/quiz/study/LearnQuizWidget.test.tsx index ef98977..b4fd570 100644 --- a/src/components/quiz/study/LearnQuizWidget.test.tsx +++ b/src/components/quiz/study/LearnQuizWidget.test.tsx @@ -114,7 +114,7 @@ describe('LearnQuizWidget', () => { render( , ); - expect(screen.getByText(/1 of 3/i)).toBeInTheDocument(); + expect(screen.getByRole('complementary', { name: /question 1 of 3/i })).toBeInTheDocument(); }); it('renders answer options as radio buttons', () => { @@ -197,7 +197,7 @@ describe('LearnQuizWidget', () => { await answerQuestion(user, 'Cubic'); await user.click(screen.getByRole('button', { name: /next question/i })); expect(screen.getByText(Q2.questionText)).toBeInTheDocument(); - expect(screen.getByText(/2 of 3/i)).toBeInTheDocument(); + expect(screen.getByRole('complementary', { name: /question 2 of 3/i })).toBeInTheDocument(); }); // ── Q3 and completion ───────────────────────────────────────────────────── @@ -244,7 +244,8 @@ describe('LearnQuizWidget', () => { await user.click(screen.getByRole('button', { name: /continue to article/i })); // 2 correct out of 3 - expect(screen.getByText(/2 of 3 correct/i)).toBeInTheDocument(); + expect(screen.getByText(/pretest complete/i)).toBeInTheDocument(); + expect(screen.getByText(/2\s*\/\s*3/)).toBeInTheDocument(); }); // ── Store integration ───────────────────────────────────────────────────── @@ -295,7 +296,7 @@ describe('LearnQuizWidget', () => { questions={[Q1, Q2, Q3, extraQ]} />, ); - expect(screen.getByText(/1 of 3/i)).toBeInTheDocument(); + expect(screen.getByRole('complementary', { name: /question 1 of 3/i })).toBeInTheDocument(); }); // ── a11y ────────────────────────────────────────────────────────────────── diff --git a/src/components/quiz/study/LearnQuizWidget.tsx b/src/components/quiz/study/LearnQuizWidget.tsx index 1e78c6e..037da85 100644 --- a/src/components/quiz/study/LearnQuizWidget.tsx +++ b/src/components/quiz/study/LearnQuizWidget.tsx @@ -18,11 +18,10 @@ */ import { useState, useId } from 'react'; -import { BookOpen, ChevronRight, CheckCircle2 } from 'lucide-react'; +import { ChevronRight, CheckCircle2 } from 'lucide-react'; import type { StudyStore, Confidence } from '../../../lib/quiz/study-types'; import { Card, CardContent } from '../../ui/Card'; import { Button } from '../../ui/Button'; -import { Badge } from '../../ui/Badge'; import { cn } from '../../ui/cn'; import { ConfidenceTap } from './ConfidenceTap'; import { RationalePanel } from './RationalePanel'; @@ -152,16 +151,22 @@ export function LearnQuizWidget({ className="my-6" aria-label="Pretest complete" > - +
-

- Pretest complete — {correctCount} of {visibleQuestions.length} correct +

+ Pretest complete + + {correctCount} / {visibleQuestions.length} +

-

+

The article below covers each of these topics in depth. Pay particular attention to any questions you got wrong — the pretesting effect makes the content that follows measurably stickier. @@ -188,25 +193,34 @@ export function LearnQuizWidget({ return (