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
70 changes: 62 additions & 8 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,19 +13,73 @@ yarn lint # Run ESLint

No test runner is configured yet. When adding tests, check `_specs/template.md` for the expected pattern — specs call for a `./tests` folder.

## Environment

Create `.env.local` (gitignored) before running locally:
```
VITE_PASSPHRASE=<passphrase>
```
The app will not advance past the gate page without this variable set.

## Architecture

This is a React 19 + Vite app (JavaScript, not TypeScript). Entry point is `src/main.jsx` → `src/App.jsx`. The app is in early/empty state — `App.jsx` currently renders nothing.
React 19 + Vite app (JavaScript, not TypeScript). Entry point: `src/main.jsx` → `src/App.jsx`.

**Key conventions:**
- `_specs/` — feature spec files (use `_specs/template.md` as the template for new specs)
- `_plans/` — implementation plans (currently empty)
- Feature branches follow the naming pattern `claude/feature/<feature-name>` (from the spec template)
- ESLint rule: unused vars are an error unless the name starts with a capital letter or underscore (`varsIgnorePattern: '^[A-Z_]'`)
### View flow

`App.jsx` manages a single `view` state with three values:

| View | Component | Notes |
|------|-----------|-------|
| `'gate'` | `PassphrasePage` | Initial view; skipped if `sessionStorage.asl-unlocked` is set |
| `'input'` | `LandingPage` → `TermInput` | Category selection |
| `'session'` | `FlashcardSession` | Flashcard practice |

`PassphrasePage` and `FlashcardSession` each render `Footer` themselves. The outer `App` wrapper renders `Footer` and `Toaster` only for the `'input'`/`'session'` views.

### Term data schema

All term data lives in `src/data/*.json`. Each entry is:
```json
{ "term": "Hello", "code": "<video-url-or-empty>", "fix": true }
```
- `code` — empty string means no video URL is confirmed yet
- `fix: true` — marks terms whose video URL is missing or unverified; displayed with a `[fix]` prefix and orange color in the term drawer select
- When `code` is empty at runtime, `FlashcardSession` falls back to a URL constructed from `https://media.signbsl.com/videos/asl/startasl/mp4/<term>.mp4`

### Category registration

Categories are defined in `src/components/TermInput.jsx` as a `CATEGORIES` array. Each entry maps an icon, title, description, and a reference to a JSON data file. To add a new category, add a JSON file to `src/data/` and add an entry to this array.

### Utilities

- `src/utils/contrastColor.js` — given a hex background color, returns `#08060d` or `#ffffff` for legible text (used by `FlashcardSession` for card text color)
- `src/utils/shuffle.js` — Fisher-Yates shuffle
- `src/utils/parseTerms.js` — parses term arrays
- `src/data/card-colors.js` — palette of background colors for flashcards
- `src/data/checkData.js` — standalone audit script; reports duplicate terms and terms with `fix: true` across all JSON files

### CSS conventions

- **BEM naming**: `.block__element--modifier` (e.g. `.landing-hero__title`, `.carousel-dot--active`)
- **CSS variables** defined in `src/index.css`: `--text`, `--text-h`, `--bg`, `--code-bg`, `--border`, `--accent`, `--accent-bg`, `--accent-border`, `--color-needs-fix`, `--shadow`, `--sans`, `--heading`, `--mono`
- Dark mode overrides are in `src/index.css` under `@media (prefers-color-scheme: dark)`
- All component styles live in `src/App.css` (no per-component CSS files)

## Stack

- React 19, no router or state management library installed yet
- React 19, no router or state management library
- Vite 8 with `@vitejs/plugin-react` (Babel/Oxc transform, not SWC)
- Plain CSS (`src/App.css`, `src/index.css`) — no CSS framework installed
- Plain CSS (`src/App.css`, `src/index.css`) — no CSS framework
- `react-hot-toast` — toast notifications (Toaster mounted in App)
- `react-player` — video playback in FlashcardSession (replaces iframe)
- `axios` — HTTP client (used in TermInput to fetch random words for Finger Spelling)
- `tippy.js` — tooltips
- Yarn (use `yarn`, not `npm`)

## Key conventions

- `_specs/` — feature spec files (use `_specs/template.md` as the template)
- `_plans/` — implementation plans
- Feature branches: `claude/feature/<feature-name>`
- ESLint rule: unused vars are an error unless the name starts with a capital letter or underscore (`varsIgnorePattern: '^[A-Z_]'`)
2 changes: 1 addition & 1 deletion _plans/adjust-for-mobile.txt
Original file line number Diff line number Diff line change
@@ -1 +1 @@
Adjust the contents of the app so it is responsive and will display well on phones and tablets. Ensure the video is always visible and hide the other controls as necessary. Shrink controls where possible but leave the video playback as the main feature.
Adjust the layout for mobile use. When the screen width is less than 1024 and in horizontal orientation, remove the flashcard-session-header div and switch to 3 column display; controls, video, term list
10 changes: 6 additions & 4 deletions src/components/FlashcardSession.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -61,15 +61,16 @@ export function FlashcardSession({terms, cardColors, onBack, title, description}
[localTerms]
);

function playbackError(event) {
console.log("Playback error", event);
function playbackError(error) {
console.error(error);
console.log("Playback error", error.nativeEvent.target.error);
console.log("Source: ", error.nativeEvent.target.src)
toast.error("Playback error");
}

const bg = localColors[currentIndex];
const fg = contrastColor(bg);
const playbackUrl = getPlaybackUrl();
console.log(sortedTerms);

return (
<div className="flashcard-session">
Expand All @@ -89,8 +90,9 @@ export function FlashcardSession({terms, cardColors, onBack, title, description}
className="flashcard-video-iframe"
title="ASL sign video"
src={playbackUrl}
// autoPlay={true}
autoPlay={true}
controls={true}
muted={true}
width="100%"
height="100%"
onError={playbackError}
Expand Down
3 changes: 2 additions & 1 deletion src/data/checkData.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ function checkData() {

all_terms.forEach(term => {
const ndx = terms_names.indexOf(term.term);
if(!terms_names.includes(term.term)) {
if(ndx === -1) {
terms_names.push(term.term);
} else {
duplicates.push(term);
Expand All @@ -50,6 +50,7 @@ function checkData() {
console.log("Duplicates Count: ", duplicates.length);
console.log("Terms Need Repair: ", needs_fixing.length);
// console.log(duplicates);
console.log(needs_fixing);
}

checkData();
2 changes: 2 additions & 0 deletions src/data/questions.json
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@
{"term": "How many siblings do you have?", "code": "https://www.youtube.com/embed/gPjmZURJgWg?rel=0;autoplay=1"},
{"term": "How much does it cost?", "code": "https://www.youtube.com/embed/wPwZpfA397c?rel=0;autoplay=1"},
{"term": "What is your phone number", "code": "https://media.signbsl.com/videos/asl/startasl/mp4/what-is-your-phone-number.mp4"},
{"term": "What is your name?", "code": "https://media.signbsl.com/videos/asl/startasl/mp4/what-is-your-name.mp4"},
{"term": "What Happened", "code": "https://www.signasl.org/sign/what-happened#8r4cbjhtns"},
{"term": "How old are you?", "code": "https://www.youtube.com/embed/yQD5HyEyWkI?rel=0;autoplay=1"}
]

Loading
Loading