Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions src/components/admin/AdminPanel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -261,7 +261,7 @@ export function AdminPanel() {
<div className="min-h-[60vh] flex items-center justify-center px-4">
<Card className="w-full max-w-sm">
<CardHeader className="text-center">
<CardTitle>Admin Login</CardTitle>
<CardTitle>Admin Access</CardTitle>
</CardHeader>
<CardContent>
<form onSubmit={handleLogin} className="space-y-4">
Expand All @@ -286,7 +286,7 @@ export function AdminPanel() {
)}
</div>
<Button type="submit" className="w-full" loading={authLoading}>
Login
Enter
</Button>
</form>
</CardContent>
Expand All @@ -303,7 +303,7 @@ export function AdminPanel() {
<div className="flex items-center justify-between mb-8">
<h1 className="text-2xl font-bold text-slate-900">Admin Panel</h1>
<Button variant="ghost" onClick={handleLogout}>
Logout
Exit
</Button>
</div>

Expand Down
6 changes: 3 additions & 3 deletions src/components/advanced/AdvancedTools.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ export function AdvancedTools() {

<ToolSection
id="proportion"
title="Proportion Analyzer"
title="Proportion Analyser"
description="Evaluate cut quality from proportion measurements"
iconPath={ICON_PATHS.ruler}
accent="cyan"
Expand All @@ -51,8 +51,8 @@ export function AdvancedTools() {
<div className="bg-cyan-50 border border-cyan-200 rounded-lg p-4">
<h4 className="text-sm font-semibold text-cyan-900 mb-2">Origin Determination</h4>
<p className="text-sm text-cyan-800 mb-3">
Geographic origin determination requires comprehensive knowledge of inclusion suites,
trace element chemistry, and regional characteristics. See our in-depth learn section:
Geographic origin determination requires detailed knowledge of inclusion suites,
trace element chemistry, and regional characteristics. See the learn section:
</p>
<ul className="text-sm text-cyan-800 space-y-1">
<li>
Expand Down
6 changes: 3 additions & 3 deletions src/components/advanced/ProportionAnalyzer.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/**
* Gemstone Cut Proportion Analyzer
* Gemstone Cut Proportion Analyser
* Evaluate cut quality based on proportion measurements
*/

Expand Down Expand Up @@ -291,7 +291,7 @@ export function ProportionAnalyzer() {
</div>

<div className="border-t border-slate-200 pt-6">
<h4 className="text-sm font-semibold text-slate-900 mb-3">Proportion Analyzer</h4>
<h4 className="text-sm font-semibold text-slate-900 mb-3">Proportion Analyser</h4>

<div className="space-y-4">
<div>
Expand Down Expand Up @@ -462,7 +462,7 @@ export function ProportionAnalyzer() {
</ul>
</div>
<p className="text-xs text-slate-600 mt-2 italic">
Educational reference only — formal GIA grading also weighs symmetry, polish, and overall appeal.
Educational reference only. Formal GIA grading also weighs symmetry, polish, and overall appeal.
</p>
</div>
)}
Expand Down
4 changes: 2 additions & 2 deletions src/components/advanced/TreatmentWizard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -130,8 +130,8 @@ export function TreatmentWizard() {
</div>
) : verdicts.length === 0 ? (
<div className="p-3 rounded-lg bg-emerald-50 border border-emerald-200 text-emerald-700 text-sm text-center">
Selected clues do not point to any common treatment — likely natural / untreated within
the limits of these observations.
Selected clues do not point to any common treatment; the stone is likely natural or untreated
within the limits of these observations.
</div>
) : (
<div className="space-y-3">
Expand Down
4 changes: 2 additions & 2 deletions src/components/calculator/HannemanRI.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ const RELIEF_OPTIONS: { value: Relief; label: string }[] = [

const liquidOptions = CONTACT_LIQUIDS.map((l) => ({
value: l.id,
label: `${l.name} — RI ${l.ri.toFixed(3)}`,
label: `${l.name}: RI ${l.ri.toFixed(3)}`,
}));

export function HannemanRI() {
Expand Down Expand Up @@ -106,7 +106,7 @@ export function HannemanRI() {
<div className="rounded-lg border border-slate-200 bg-slate-50 p-4 space-y-4">
{rows.map((row, i) => (
<div key={i} className="grid grid-cols-1 md:grid-cols-2 gap-3">
<FormField name={`hl-liquid-${i}`} label={`Observation ${i + 1} — liquid`}>
<FormField name={`hl-liquid-${i}`} label={`Observation ${i + 1}: liquid`}>
<Select
options={liquidOptions}
value={row.liquidId}
Expand Down
6 changes: 3 additions & 3 deletions src/components/calculator/RICalculator.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -73,8 +73,8 @@ export function RICalculator() {
character,
characterLabel:
character === 'DR'
? 'Doubly refractive (anisotropic — uniaxial or biaxial)'
: 'Singly refractive within reading tolerance — likely cubic, amorphous, or read along an optic axis',
? 'Doubly refractive (anisotropic: uniaxial or biaxial)'
: 'Singly refractive within reading tolerance; likely cubic, amorphous, or read along an optic axis',
lookupRI,
};
}, [mode, result?.ri, ri2]);
Expand Down Expand Up @@ -163,7 +163,7 @@ export function RICalculator() {
({doubleReadingResult.classification})
</div>
<div className="text-emerald-800">
Optic character: <strong>{doubleReadingResult.character}</strong> — {doubleReadingResult.characterLabel}
Optic character: <strong>{doubleReadingResult.character}</strong>. {doubleReadingResult.characterLabel}
</div>
<div className="text-xs text-emerald-700">
Matches below use the average RI ({doubleReadingResult.lookupRI.toFixed(3)}) ± {values.tolerance}.
Expand Down
2 changes: 1 addition & 1 deletion src/components/calculator/SGCalculator.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ const WATER_DENSITY_TABLE: { tempC: number; density: number }[] = [

const TEMP_OPTIONS = [
{ value: '20', label: '20 °C (room temperature, default)' },
{ value: '4', label: '4 °C (max density — ideal but cold)' },
{ value: '4', label: '4 °C (max density, ideal but cold)' },
{ value: '15', label: '15 °C (cool tap water)' },
{ value: '25', label: '25 °C (warm room)' },
{ value: '30', label: '30 °C (warm)' },
Expand Down
10 changes: 5 additions & 5 deletions src/components/identification/HardnessReference.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,16 +22,16 @@ interface GemHardness {
}

const MOHS_SCALE: MohsLevel[] = [
{ hardness: 10, mineral: 'Diamond', wearResistance: 'Excellent for all jewelry' },
{ hardness: 9, mineral: 'Corundum (Ruby/Sapphire)', wearResistance: 'Excellent for all jewelry' },
{ hardness: 10, mineral: 'Diamond', wearResistance: 'Excellent for all jewellery' },
{ hardness: 9, mineral: 'Corundum (Ruby/Sapphire)', wearResistance: 'Excellent for all jewellery' },
{ hardness: 8, mineral: 'Topaz / Spinel', wearResistance: 'Excellent for daily wear' },
{ hardness: 7.5, mineral: 'Beryl (Emerald / Aquamarine)', wearResistance: 'Good for most jewelry' },
{ hardness: 7, mineral: 'Quartz', wearResistance: 'Good for most jewelry' },
{ hardness: 7.5, mineral: 'Beryl (Emerald / Aquamarine)', wearResistance: 'Good for most jewellery' },
{ hardness: 7, mineral: 'Quartz', wearResistance: 'Good for most jewellery' },
{ hardness: 6.5, mineral: 'Tanzanite / Peridot', wearResistance: 'Fair: avoid rings' },
{ hardness: 6, mineral: 'Feldspar (Moonstone)', wearResistance: 'Fair: pendants/earrings' },
{ hardness: 5, mineral: 'Apatite', wearResistance: 'Poor: collector only' },
{ hardness: 4, mineral: 'Fluorite', wearResistance: 'Poor: collector only' },
{ hardness: 3, mineral: 'Calcite', wearResistance: 'Not suitable for jewelry' },
{ hardness: 3, mineral: 'Calcite', wearResistance: 'Not suitable for jewellery' },
];

// Fallback data if database is unavailable
Expand Down
2 changes: 1 addition & 1 deletion src/components/lab/SpectroscopeBandMatcher.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,7 @@ export function SpectroscopeBandMatcher() {
className={`text-xs px-2.5 py-1 rounded font-mono border transition ${
isOn ? `${swatch} border-slate-700 ring-2 ring-slate-400` : `${swatch} border-transparent opacity-60 hover:opacity-100`
}`}
title={`${wl} nm — ${colourFor(wl)} region`}
title={`${wl} nm, ${colourFor(wl)} region`}
>
{wl} nm
</button>
Expand Down
8 changes: 4 additions & 4 deletions src/components/optical/OpticSignReasoner.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -189,10 +189,10 @@ export function OpticSignReasoner() {

{character === 'uniaxial' && (
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
<FormField name="omega" label="ω (omega — ordinary ray)">
<FormField name="omega" label="ω (omega, ordinary ray)">
<NumberInput value={omega} onChange={setOmega} step={0.001} placeholder="e.g., 1.544" />
</FormField>
<FormField name="epsilon" label="ε (epsilon — extraordinary ray)">
<FormField name="epsilon" label="ε (epsilon, extraordinary ray)">
<NumberInput
value={epsilon}
onChange={setEpsilon}
Expand All @@ -208,7 +208,7 @@ export function OpticSignReasoner() {
<FormField name="alpha" label="α (lowest RI)">
<NumberInput value={alpha} onChange={setAlpha} step={0.001} placeholder="e.g., 1.635" />
</FormField>
<FormField name="beta" label="β (middle RI — optional)">
<FormField name="beta" label="β (middle RI, optional)">
<NumberInput value={beta} onChange={setBeta} step={0.001} placeholder="e.g., 1.651" />
</FormField>
<FormField name="gamma" label="γ (highest RI)">
Expand Down Expand Up @@ -244,7 +244,7 @@ export function OpticSignReasoner() {
{/* Candidate list */}
{dbError && (
<div className="p-3 rounded bg-rose-50 border border-rose-200 text-rose-700 text-sm">
Database unavailable — candidate ranking disabled. ({dbError})
Database unavailable; candidate ranking is disabled. ({dbError})
</div>
)}
{loading ? (
Expand Down
6 changes: 3 additions & 3 deletions src/components/optical/PleochroismReasoner.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,9 @@ import { usePagination } from '../../hooks/usePagination';
import { Pagination } from '../ui';

const COLOUR_COUNT_OPTIONS = [
{ value: '1', label: '1 — single colour' },
{ value: '2', label: '2 — dichroic' },
{ value: '3', label: '3 — trichroic' },
{ value: '1', label: '1: single colour' },
{ value: '2', label: '2: dichroic' },
{ value: '3', label: '3: trichroic' },
];

const STRENGTH_OPTIONS: { value: PleochroismStrength; label: string }[] = [
Expand Down
2 changes: 1 addition & 1 deletion src/components/optical/PolariscopeGuide.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -142,7 +142,7 @@ export function PolariscopeGuide() {
<ul className="text-sm text-amber-800 space-y-1">
<li>• If no reaction at all, check polarizers are properly crossed</li>
<li>• Weak reactions may need immersion fluid to reduce surface reflections</li>
<li>• Multiple orientations needed for comprehensive testing</li>
<li>• Multiple orientations needed for thorough testing</li>
<li>• Clean polarizers and stone surface for best results</li>
</ul>
</div>
Expand Down
2 changes: 1 addition & 1 deletion src/components/quiz/study/ConfidenceTap.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -113,7 +113,7 @@ export function ConfidenceTap({ value, onChange, disabled = false }: ConfidenceT
type="button"
role="radio"
aria-checked={isSelected}
aria-label={`${opt.label} — ${opt.description}`}
aria-label={`${opt.label}: ${opt.description}`}
disabled={disabled}
onClick={() => onChange(opt.value)}
className={cn(
Expand Down
2 changes: 1 addition & 1 deletion src/components/quiz/study/ExportImportPanel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,7 @@ export function ExportImportPanel({ store }: ExportImportPanelProps) {
} else {
showToast({
kind: 'error',
message: result.warnings[0] ?? 'Import failed — the file may be invalid.',
message: result.warnings[0] ?? 'Import failed. The file may be invalid.',
});
}
} catch {
Expand Down
2 changes: 1 addition & 1 deletion src/components/quiz/study/LearnQuizWidget.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -192,7 +192,7 @@ export function LearnQuizWidget({
: undefined;

return (
<aside className="my-6" aria-label={`Pretest — question ${currentIndex + 1} of ${visibleQuestions.length}`}>
<aside className="my-6" aria-label={`Pretest, question ${currentIndex + 1} of ${visibleQuestions.length}`}>
<Card
padding="none"
className="overflow-hidden border-slate-200 dark:border-slate-800 dark:bg-slate-900"
Expand Down
8 changes: 4 additions & 4 deletions src/components/quiz/study/RationalePanel.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -37,18 +37,18 @@ describe('RationalePanel', () => {
expect(screen.getByText('Great diagnostic chain.')).toBeInTheDocument();
});

it('shows "Correct — here is why" heading when correct=true', () => {
it('shows "Correct: here is why" heading when correct=true', () => {
render(
<RationalePanel correct={true} rationaleCorrect="test" show={true} />,
);
expect(screen.getByText(/correct — here is why/i)).toBeInTheDocument();
expect(screen.getByText(/correct: here is why/i)).toBeInTheDocument();
});

it('shows "Not quite — here is why" heading when correct=false', () => {
it('shows "Not quite: here is why" heading when correct=false', () => {
render(
<RationalePanel correct={false} rationaleCorrect="test" show={true} />,
);
expect(screen.getByText(/not quite — here is why/i)).toBeInTheDocument();
expect(screen.getByText(/not quite: here is why/i)).toBeInTheDocument();
});

// ── Collapse / expand ──────────────────────────────────────────────────────
Expand Down
4 changes: 2 additions & 2 deletions src/components/quiz/study/RationalePanel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,7 @@ export function RationalePanel({
) : (
<XCircle className="w-4 h-4 flex-shrink-0" aria-hidden="true" />
)}
{correct ? 'Correct — here is why' : 'Not quite — here is why'}
{correct ? 'Correct: here is why' : 'Not quite: here is why'}
</span>
<span aria-label={toggleLabel}>
{expanded ? (
Expand Down Expand Up @@ -152,7 +152,7 @@ export function RationalePanel({
? 'text-red-600 dark:text-red-400'
: 'text-slate-600 dark:text-slate-600',
)}
aria-label={wasChosen ? 'Your choice — incorrect' : 'Incorrect option'}
aria-label={wasChosen ? 'Your choice, incorrect' : 'Incorrect option'}
/>
)}
<div>
Expand Down
2 changes: 1 addition & 1 deletion src/components/quiz/study/ScheduleBadge.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ describe('ScheduleBadge', () => {

it('"New" badge has an appropriate aria-label', () => {
render(<ScheduleBadge entry={null} now={NOW} />);
expect(screen.getByLabelText(/new — never seen before/i)).toBeInTheDocument();
expect(screen.getByLabelText(/new, never seen before/i)).toBeInTheDocument();
});

// ── Due state ─────────────────────────────────────────────────────────────
Expand Down
2 changes: 1 addition & 1 deletion src/components/quiz/study/ScheduleBadge.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ export function ScheduleBadge({ entry, now: nowOverride }: ScheduleBadgeProps) {

if (!entry || entry.totalReviews === 0) {
return (
<Badge variant="crystal" size="sm" aria-label="Question status: New — never seen before">
<Badge variant="crystal" size="sm" aria-label="Question status: New, never seen before">
New
</Badge>
);
Expand Down
18 changes: 9 additions & 9 deletions src/components/quiz/study/StudyDevHarness.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -86,12 +86,12 @@ const OPTION_RATIONALES: OptionRationale[] = [
{
text: 'Natural ruby (corundum)',
isCorrect: true,
rationale: 'RI 1.762–1.770, SG 4.00, uniaxial negative — all diagnostic of corundum.',
rationale: 'RI 1.762–1.770, SG 4.00, uniaxial negative: all diagnostic of corundum.',
},
{
text: 'Spinel',
isCorrect: false,
rationale: 'Spinel is isotropic (single RI ≈ 1.718) — no uniaxial reading possible.',
rationale: 'Spinel is isotropic (single RI ≈ 1.718); no uniaxial reading possible.',
},
{
text: 'Pyrope garnet',
Expand All @@ -118,7 +118,7 @@ const MOCK_QUESTIONS: WidgetQuestion[] = [
correctAnswer: 'Cubic',
rationaleCorrect: 'Diamond crystallises in the cubic (isometric) system with point group m3m.',
optionRationales: [
{ text: 'Cubic', isCorrect: true, rationale: 'Correct — m3m symmetry.' },
{ text: 'Cubic', isCorrect: true, rationale: 'Correct: m3m symmetry.' },
{ text: 'Hexagonal', isCorrect: false, rationale: '6-fold symmetry axis; does not apply to diamond.' },
{ text: 'Trigonal', isCorrect: false, rationale: '3-fold symmetry; quartz and corundum, not diamond.' },
],
Expand Down Expand Up @@ -174,7 +174,7 @@ function InteractiveConfidenceTap() {
<div>
<ConfidenceTap value={value} onChange={setValue} />
<p className="text-xs text-slate-600 mt-1">
Selected: <code>{value ?? 'null'}</code> — try keyboard Q/W/E
Selected: <code>{value ?? 'null'}</code>. Try keyboard Q/W/E.
</p>
</div>
);
Expand Down Expand Up @@ -203,7 +203,7 @@ export function StudyDevHarness() {
return (
<div className="max-w-3xl mx-auto px-4 py-8 space-y-12">
<header className="border-b border-slate-200 pb-4">
<h1 className="text-2xl font-bold text-slate-900">Study Components — Dev Harness</h1>
<h1 className="text-2xl font-bold text-slate-900">Study Components: Dev Harness</h1>
<p className="text-sm text-slate-600 mt-1">
Only served in development (<code>import.meta.env.DEV</code>). All components rendered
with mocked data.
Expand All @@ -228,7 +228,7 @@ export function StudyDevHarness() {
<Sub label="disabled=true (value='certain')">
<ConfidenceTap value="certain" onChange={() => {}} disabled={true} />
</Sub>
<Sub label="Interactive — keyboard shortcuts Q/W/E active">
<Sub label="Interactive: keyboard shortcuts Q/W/E active">
<InteractiveConfidenceTap />
</Sub>
</div>
Expand Down Expand Up @@ -320,10 +320,10 @@ export function StudyDevHarness() {
{/* ── ExportImportPanel ─────────────────────────────────────────────── */}
<Section title="ExportImportPanel">
<div className="space-y-6">
<Sub label="Mock store — export succeeds, download triggers">
<Sub label="Mock store: export succeeds, download triggers">
<ExportImportPanel store={makeMockStore(false)} />
</Sub>
<Sub label="Mock store — export fails (error toast)">
<Sub label="Mock store: export fails (error toast)">
<ExportImportPanel store={makeMockStore(true)} />
</Sub>
</div>
Expand All @@ -332,7 +332,7 @@ export function StudyDevHarness() {
{/* ── LearnQuizWidget ───────────────────────────────────────────────── */}
<Section title="LearnQuizWidget">
<div className="space-y-8">
<Sub label="3 questions — full interactive flow">
<Sub label="3 questions: full interactive flow">
<LearnQuizWidget
slug="species/corundum"
pretestEnabled={true}
Expand Down
4 changes: 2 additions & 2 deletions src/components/quiz/study/StudyReviewScreen.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ export function StudyReviewScreen({ learnEntries }: StudyReviewScreenProps) {
<header className="mb-8">
<h1 className="text-3xl font-bold text-slate-900">Review</h1>
<p className="text-slate-600 mt-2">
Spaced-repetition queue — items the scheduler says you should see today.
Spaced-repetition queue: items the scheduler says you should see today.
</p>
</header>

Expand Down Expand Up @@ -86,7 +86,7 @@ function EmptyReviewState() {
<div className="rounded-xl border border-slate-200 bg-white p-10 text-center">
<h2 className="text-xl font-semibold text-slate-900">Nothing due right now</h2>
<p className="text-slate-600 mt-2 max-w-prose mx-auto">
Your spaced-repetition queue is empty — every item you've seen is still
Your spaced-repetition queue is empty; every item you've seen is still
within its scheduled interval. Practice new questions to grow the queue.
</p>
<div className="mt-6">
Expand Down
Loading