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
35 changes: 35 additions & 0 deletions _specs/fix-color-term-select.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
# Spec for fix-color-term-select

branch: claude/feature/fix-color-term-select

## Summary
In the flashcard session, the term drawer contains a `<select>` list of all terms in the current category. Some terms have a `fix: true` attribute in their JSON data, meaning their video URL is missing or needs to be updated. This feature makes those terms visually distinct in the select list so users and developers can immediately see which terms are incomplete.

## Functional Requirements
- In `FlashcardSession.jsx`, each `<option>` in the term `<select>` that corresponds to a term with `fix: true` should render in a different (warning/muted) color compared to normal options.
- Terms without `fix: true` (or where `fix` is absent/false) should display in their default color.
- The color distinction must be visible in both light and dark contexts.
- No change to the select's behavior — navigation and selection work exactly as before.

## Possible Edge Cases
- Terms where `fix` is `undefined` or `false` should be treated identically to terms where `fix` is absent — i.e., normal color, no special styling.
- The `sortedTerms` memo already passes `fix` through; no data-layer changes should be needed.
- Native `<option>` color styling has limited cross-browser support — the implementation may need an inline `style` prop on the `<option>` element rather than a CSS class.

## Acceptance Criteria
- [ ] Options for terms with `fix: true` are visually distinct (e.g. a muted orange or red foreground color) in the term select list.
- [ ] Options for terms without `fix: true` display with default styling.
- [ ] The color change is visible on both Windows and macOS native select rendering.
- [ ] No regressions to select navigation, keyboard shortcuts, or term jumping behavior.

## Open Questions
- Should the color hint only be visible to developers (e.g. toggled by a dev flag), or always shown to end users?
- always shown
- Is a color change alone sufficient, or should a symbol/prefix (e.g. `⚠️`) also be added to the option text for accessibility?
- add an appropriate prefix

## Testing Guidelines
Create a test file in the `./tests` folder for this feature and create meaningful tests for the following cases:
- An option rendered from a term with `fix: true` has a non-default color style applied.
- An option rendered from a term with `fix: false` or no `fix` property has no special color style applied.
- The select still renders the correct number of options matching the terms array.
2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@
"axios": "^1.15.0",
"react": "^19.2.4",
"react-dom": "^19.2.4",
"react-hot-toast": "^2.6.0",
"react-player": "^3.4.0",
"tippy.js": "^6.3.7"
},
"devDependencies": {
Expand Down
4 changes: 4 additions & 0 deletions src/App.css
Original file line number Diff line number Diff line change
Expand Up @@ -559,6 +559,10 @@
}
}

.term-option--needs-fix {
color: var(--color-needs-fix);
}

/* ── Mobile responsive overrides ─────────────────────── */

@media (max-width: 640px) {
Expand Down
2 changes: 2 additions & 0 deletions src/App.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { LandingPage } from './components/LandingPage'
import { FlashcardSession } from './components/FlashcardSession'
import { Footer } from './components/Footer'
import './App.css'
import {Toaster} from "react-hot-toast";

function App() {
const [view, setView] = useState('input')
Expand Down Expand Up @@ -34,6 +35,7 @@ function App() {
<FlashcardSession terms={terms} cardColors={cardColors} onBack={handleBack} title={categoryTitle} description={categoryDescription} />
)}
<Footer />
<Toaster />
</div>
)
}
Expand Down
30 changes: 24 additions & 6 deletions src/components/FlashcardSession.jsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import {useState, useEffect, useCallback, useMemo, useRef} from "react";
import {contrastColor} from "../utils/contrastColor";
import {shuffle} from "../utils/shuffle";
import ReactPlayer from 'react-player'
import toast from "react-hot-toast";

export function FlashcardSession({terms, cardColors, onBack, title, description}) {
const [currentIndex, setCurrentIndex] = useState(0);
Expand Down Expand Up @@ -35,7 +37,10 @@ export function FlashcardSession({terms, cardColors, onBack, title, description}
}
}
if (current.code.length === 0) {
return null;
const base_url = "https://media.signbsl.com/videos/asl/startasl/mp4/";
const term_url = (base_url + current.term + ".mp4").toLowerCase()
console.log("Looking for term url: ", term_url);
return term_url;
}
return current.code;
}
Expand All @@ -52,13 +57,19 @@ export function FlashcardSession({terms, cardColors, onBack, title, description}
}, [goNext, goPrev]);

const sortedTerms = useMemo(
() => localTerms.map((t, i) => ({term: t.term, i})).sort((a, b) => a.term.localeCompare(b.term)),
() => localTerms.map((t, i) => ({term: t.term, i, fix: t.fix, code: t.code})).sort((a, b) => a.term.localeCompare(b.term)),
[localTerms]
);

function playbackError(event) {
console.log("Playback error", event);
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 @@ -74,11 +85,16 @@ export function FlashcardSession({terms, cardColors, onBack, title, description}
</div>
<div className="flashcard-video">
{playbackUrl ? (
<iframe
<ReactPlayer
className="flashcard-video-iframe"
title="ASL sign video"
src={playbackUrl}
></iframe>
// autoPlay={true}
controls={true}
width="100%"
height="100%"
onError={playbackError}
></ReactPlayer>
) : (
<div className="flashcard-video-placeholder">
No video available
Expand Down Expand Up @@ -107,8 +123,10 @@ export function FlashcardSession({terms, cardColors, onBack, title, description}
onChange={e => { setCurrentIndex(Number(e.target.value)); setTermDrawerOpen(false); }}
value={currentIndex}
>
{sortedTerms.map(({term, i}) => (
<option key={i} value={i}>{term}</option>
{sortedTerms.map(({term, i, fix}) => (
<option key={i} value={i} className={fix ? 'term-option--needs-fix' : undefined}>
{fix ? `[fix] ${term}` : term}
</option>
))}
</select>
</div>
Expand Down
9 changes: 7 additions & 2 deletions src/components/TermInput.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,16 +6,21 @@ import terms from "../data/terms.json" with {type: "json"};
import other_terms from "../data/terms2.json" with {type: "json"};
import questions from "../data/questions.json" with {type: "json"};
import verbs from "../data/verbs.json" with {type: "json"};
import date_time_terms from "../data/calendar-terms.json";
import color_terms from "../data/colors.json";
import faire_terms from "../data/faire.json";
import axios from "axios";

const CATEGORIES = [
{icon: "🖐️", title: "Finger Spelling", description: "Practice spelling words letter by letter using ASL handshapes.", terms: terms},
{icon: "🔢", title: "Numbers", description: "Learn to sign numbers in American Sign Language.", terms: terms},
{icon: "📚", title: "ASL Level I && II Class Terms", description: "Core vocabulary from ASL Level I and II coursework.", terms: terms},
{icon: "📚", title: "ASL Level I & II Class Terms", description: "Core vocabulary from ASL Level I and II coursework.", terms: terms},
{icon: "❓", title: "Questions", description: "Essential question words and phrases used in ASL conversation.", terms: questions},
{icon: "⚡", title: "Verbs", description: "Common action words and verbs in American Sign Language.", terms: verbs},
{icon: "🗂️", title: "Other Terms", description: "Additional vocabulary terms for expanding your ASL knowledge.", terms: other_terms},
{icon: "⚔️", title: "Renaissance Faire Terms", description: "Specialized vocabulary for Renaissance Faire settings.", terms: terms},
{icon: "⚔️", title: "Renaissance Faire Terms", description: "Specialized vocabulary for Renaissance Faire settings.", terms: faire_terms},
{icon: "📆", title: "Calendar / Date & Time", description: "Months, Days, Time, etc.", terms: date_time_terms},
{icon: "🎨", title: "Colors", description: "Various Colors", terms: color_terms},
];

const webResources = [
Expand Down
32 changes: 32 additions & 0 deletions src/data/calendar-terms.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
[
{"term": "April", "code": "", "fix": true},
{"term": "August", "code": "", "fix": true},
{"term": "Calendar", "code": ""},
{"term": "Clock", "code": ""},
{"term": "Date", "code": ""},
{"term": "Day", "code": ""},
{"term": "December", "code": "", "fix": true},
{"term": "February", "code": "https://media.signbsl.com/videos/asl/mariekatzenbachschool/mp4/February.mp4"},
{"term": "Friday", "code": ""},
{"term": "Holiday", "code": "https://media.signbsl.com/videos/asl/mariekatzenbachschool/mp4/holidays.mp4"},
{"term": "January", "code": "https://media.signbsl.com/videos/asl/mariekatzenbachschool/mp4/January.mp4"},
{"term": "July", "code": "", "fix": true},
{"term": "June", "code": "", "fix": true},
{"term": "March", "code": "", "fix": true},
{"term": "May", "code": "", "fix": true},
{"term": "Monday", "code": ""},
{"term": "Month", "code": ""},
{"term": "November", "code": "", "fix": true},
{"term": "October", "code": "", "fix": true},
{"term": "Saturday", "code": ""},
{"term": "September", "code": "", "fix": true},
{"term": "Sunday", "code": ""},
{"term": "Thursday", "code": ""},
{"term": "Time", "code": ""},
{"term": "Tuesday", "code": ""},
{"term": "Wednesday", "code": ""},
{"term": "Week", "code": ""},
{"term": "Weekend", "code": "https://media.signbsl.com/videos/asl/mariekatzenbachschool/mp4/weekend.mp4"},
{"term": "Year", "code": ""}
]

55 changes: 55 additions & 0 deletions src/data/checkData.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
/*
This utility will go through the various json files and report on their statistics:
- which terms need fixing
- which terms are duplicated in which files

*/

import terms from "../data/terms.json" with {type: "json"};
import other_terms from "../data/terms2.json" with {type: "json"};
import questions from "../data/questions.json" with {type: "json"};
import verbs from "../data/verbs.json" with {type: "json"};
import date_time_terms from "../data/calendar-terms.json" with {type: "json"};
import color_terms from "../data/colors.json" with {type: "json"};
import faire_terms from "../data/faire.json" with {type: "json"};
import fixable_terms from "../data/terms_to_fix.json" with {type: "json"};


function checkData() {
console.log("Running data script...");
const all_terms = [...terms, ...other_terms, ...questions, ...verbs, ...date_time_terms, ...color_terms, ...faire_terms, ...fixable_terms];
const terms_names = [];
const duplicates = [];
const needs_fixing = [];

all_terms.forEach(term => {
const ndx = terms_names.indexOf(term.term);
if(!terms_names.includes(term.term)) {
terms_names.push(term.term);
} else {
duplicates.push(term);
}
if (Object.hasOwn(term, "fix") && term["fix"] === true) {
needs_fixing.push(term);
}
})
console.log(terms_names);
console.log("-----------")
fixable_terms.forEach(term => {
console.log(terms_names.indexOf(term.term));
const ndx = terms_names.indexOf(term.term);
if(ndx === -1) {
console.log("Include Term: ", term.term);
} else {
duplicates.push(term);
}

})

console.log("Total Unique Terms: ", terms_names.length);
console.log("Duplicates Count: ", duplicates.length);
console.log("Terms Need Repair: ", needs_fixing.length);
// console.log(duplicates);
}

checkData();
13 changes: 13 additions & 0 deletions src/data/colors.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
[
{"term": "Green", "code": ""},
{"term": "Yellow", "code": ""},
{"term": "Orange", "code": ""},
{"term": "White", "code": ""},
{"term": "Gray", "code": ""},
{"term": "Purple", "code": ""},
{"term": "Black", "code": ""},
{"term": "Red", "code": ""},
{"term": "Blue", "code": ""},
{"term": "Brown", "code": ""},
{"term": "Pink", "code": ""}
]
30 changes: 30 additions & 0 deletions src/data/faire.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
[
{"term": "Bottle", "code": "https://media.signbsl.com/videos/asl/aslsignbank/mp4/BOTTLE-47.mp4", "fix": true},
{"term": "Camper", "code": "", "fix": true},
{"term": "Camping", "code": "", "fix": true},
{"term": "Dress", "code": "", "fix": true},
{"term": "Fairy", "code": "", "fix": true},
{"term": "Horse", "code": "https://media.signbsl.com/videos/asl/startasl/mp4/horse.mp4"},
{"term": "Joust", "code": "", "fix": true},
{"term": "King", "code": ""},
{"term": "Knife", "code": "", "fix": true},
{"term": "Knight", "code": "https://media.signbsl.com/videos/asl/youtube/mp4/8SdVL8h6oxE.mp4", "fix": true},
{"term": "Parking lot", "code": "", "fix": true},
{"term": "Pickle", "code": "", "fix": true},
{"term": "Prince", "code": "", "fix": true},
{"term": "Princess", "code": "", "fix": true},
{"term": "Queen", "code": ""},
{"term": "Sword", "code": "https://media.signbsl.com/videos/asl/aslsearch/mp4/sword.mp4", "fix": true},
{"term": "Welcome", "code": "", "fix": true},
{"term": "Parents", "code": "https://media.signbsl.com/videos/asl/aslsignbank/mp4/PARENTS-696.mp4", "fix": true},
{"term": "Do you need help?", "code": "https://www.youtube.com/embed/HDwqcA11tx4?rel=0;autoplay=1"},
{"term": "Do you understand?", "code": "https://www.youtube.com/embed/plJHFy1RcMI?rel=0;autoplay=1"},
{"term": "Do you want to play a game?", "code": "https://www.youtube.com/embed/aRxUSAA_k2w?rel=0;autoplay=1"},
{"term": "How are you?", "code": "https://www.youtube.com/embed/JPtdvzvpWMQ?rel=0;autoplay=1"},
{"term": "How did you get here?", "code": "https://www.youtube.com/embed/8Gl6aTI1zAQ?rel=0;autoplay=1"},
{"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": "How old are you?", "code": "https://www.youtube.com/embed/yQD5HyEyWkI?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"}
]

2 changes: 1 addition & 1 deletion src/data/questions.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,14 +14,14 @@
{"term": "Help", "code": ""},
{"term": "How", "code": ""},
{"term": "Again", "code": ""},
{"term": "Calendar", "code": ""},
{"term": "Do you need help?", "code": "https://www.youtube.com/embed/HDwqcA11tx4?rel=0;autoplay=1"},
{"term": "Do you understand?", "code": "https://www.youtube.com/embed/plJHFy1RcMI?rel=0;autoplay=1"},
{"term": "Do you want to play a game?", "code": "https://www.youtube.com/embed/aRxUSAA_k2w?rel=0;autoplay=1"},
{"term": "How are you?", "code": "https://www.youtube.com/embed/JPtdvzvpWMQ?rel=0;autoplay=1"},
{"term": "How did you get here?", "code": "https://www.youtube.com/embed/8Gl6aTI1zAQ?rel=0;autoplay=1"},
{"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": "How old are you?", "code": "https://www.youtube.com/embed/yQD5HyEyWkI?rel=0;autoplay=1"}
]

Loading
Loading