diff --git a/client/src/App.tsx b/client/src/App.tsx index 899eb8a..253424a 100644 --- a/client/src/App.tsx +++ b/client/src/App.tsx @@ -61,7 +61,11 @@ export default function App() { )} {route === 'onboarding' && token && ( - setRoute('game')} /> + setRoute('game')} + /> )} {route === 'game' && token && ( { logout(); setRoute('login') }} /> diff --git a/client/src/components/MnemonicCodebook.tsx b/client/src/components/MnemonicCodebook.tsx new file mode 100644 index 0000000..c265f76 --- /dev/null +++ b/client/src/components/MnemonicCodebook.tsx @@ -0,0 +1,123 @@ +import { NOVICE_LETTERS, MORSE } from '../lib/morse' + +type Props = { + mnemonics: Record + operatorName: string | null + onStartPlaying: () => void +} + +function morseDisplay(pattern: string): string { + return pattern.split('').map(c => c === '.' ? 'ยท' : 'โ€”').join(' ') +} + +export function MnemonicCodebook({ mnemonics, operatorName, onStartPlaying }: Props) { + return ( +
+ + {/* Header โ€” hidden when printing, replaced by print-only title */} +
+

+ Your Codebook is Ready! +

+

+ Print it out or save as PDF to keep with you during practice. +

+
+ + {/* Print-only title */} +
+

+ Morse Code Codebook +

+ {operatorName && ( +

+ Operator: {operatorName} +

+ )} +
+ + {/* Letter grid */} +
+ {NOVICE_LETTERS.map(letter => { + const pattern = MORSE[letter] ?? '' + const phrase = mnemonics[letter] + return ( +
+ {/* Letter + pattern */} +
+ + {letter} + + + {morseDisplay(pattern)} + +
+ + {/* Mnemonic phrase */} +

+ {phrase ?? 'skipped'} +

+
+ ) + })} +
+ + {/* Actions */} +
+ + +
+ + +
+ ) +} diff --git a/client/src/index.css b/client/src/index.css index cb12d20..5612bdf 100644 --- a/client/src/index.css +++ b/client/src/index.css @@ -61,3 +61,21 @@ input:focus { outline: 2px solid var(--color-gold); outline-offset: 1px; } margin: 0 auto; padding: 1rem; } + +/* Print styles */ +@media print { + .no-print { display: none !important; } + body { background: white; } + .app-shell { max-width: 100%; padding: 0; } + .codebook-grid { + display: grid !important; + grid-template-columns: repeat(3, 1fr) !important; + gap: 0.5rem !important; + } + .codebook-card { + border: 1px solid #999 !important; + background: white !important; + break-inside: avoid; + page-break-inside: avoid; + } +} diff --git a/client/src/pages/OnboardingPage.tsx b/client/src/pages/OnboardingPage.tsx index 5bf4357..7e9ee9c 100644 --- a/client/src/pages/OnboardingPage.tsx +++ b/client/src/pages/OnboardingPage.tsx @@ -1,20 +1,41 @@ +import { useState, useCallback } from 'react' import { MnemonicBuilder } from '../components/MnemonicBuilder' +import { MnemonicCodebook } from '../components/MnemonicCodebook' type Props = { token: string + operatorName: string | null onComplete: () => void } -export function OnboardingPage({ token, onComplete }: Props) { +export function OnboardingPage({ token, operatorName, onComplete }: Props) { + const [mnemonics, setMnemonics] = useState | null>(null) + + const handleBuilderComplete = useCallback(async () => { + try { + const r = await fetch('/api/mnemonics', { + headers: { Authorization: `Bearer ${token}` }, + }) + const data = await r.json() + setMnemonics(data) + } catch { + onComplete() + } + }, [token, onComplete]) + + if (mnemonics) { + return + } + return (

- Build Your Mnemonics + Build Your Codebook

Create a memorable phrase for each Morse letter. Short words for dots, long words for dashes.

- +
) }