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
58 changes: 58 additions & 0 deletions _specs/disclaimer-modal.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
# Spec for disclaimer-modal

branch: claude/feature/disclaimer-modal
figma_component (if used): N/A

## Summary

The footer currently uses a `<details>`/`<summary>` element to show or hide the disclaimer inline. This should be replaced with a modal dialog: clicking the "Disclaimer" text opens a centered overlay modal containing the disclaimer content, and the user can close it via a close button or by clicking the backdrop.

## Functional Requirements

- The footer retains a "Disclaimer" clickable text link in its existing position.
- Clicking "Disclaimer" opens a modal dialog overlaying the page.
- The modal displays all four existing disclaimer bullet points, unchanged in content.
- The modal can be closed by:
- Clicking a close button inside the modal (e.g. an ✕ button in the header).
- Clicking the backdrop/overlay area outside the modal panel.
- Pressing the Escape key.
- While the modal is open, the page behind it is not scrollable.
- The `<details>`/`<summary>` element is removed and replaced by the modal trigger.
- The modal is rendered inside the `Footer` component (no need for a portal unless z-index conflicts arise).

## Possible Edge Cases

- Modal opened on a very small screen — the modal panel should not overflow the viewport; use `max-height` with internal scroll if needed.
- Rapid open/close (double-click) — state should settle correctly with no stuck-open modal.
- Modal open state should reset if the component unmounts (e.g. user navigates away mid-open).
- Multiple footer instances on the same page — each should manage its own open state independently.

## Acceptance Criteria

- [ ] The footer no longer contains a `<details>`/`<summary>` element.
- [ ] A "Disclaimer" trigger link/button is visible in the footer at all times.
- [ ] Clicking the trigger opens the modal with all four disclaimer points visible.
- [ ] Clicking the ✕ close button dismisses the modal.
- [ ] Clicking the backdrop dismisses the modal.
- [ ] Pressing Escape dismisses the modal.
- [ ] The modal does not appear on initial page load.
- [ ] Body scroll is locked while the modal is open.
- [ ] The modal is visually centered on the page with a semi-transparent backdrop.

## Open Questions

- Should the modal have a title/heading (e.g. "Disclaimer") displayed inside the panel, separate from the trigger label?
- No
- Should focus be trapped inside the modal while it is open, or is a simple focus-on-open sufficient?
- trapped

## Testing Guidelines

Create a test file(s) in the ./tests folder for the new feature, and create meaningful tests for the following cases, without going too heavy:

- Clicking the Disclaimer trigger sets modal open state to true.
- Clicking the backdrop sets modal open state to false.
- Pressing Escape sets modal open state to false.
- Clicking the close button sets modal open state to false.
- Modal is not rendered (or not visible) when open state is false.
- All four disclaimer bullet points are present in the modal content.
104 changes: 92 additions & 12 deletions src/App.css
Original file line number Diff line number Diff line change
Expand Up @@ -431,25 +431,105 @@
margin: 0 0 6px;
}

.site-footer__disclaimer summary {
.site-footer__disclaimer-btn {
background: none;
border: none;
cursor: pointer;
display: inline-block;
margin-bottom: 6px;
color: var(--text);
font-size: 13px;
text-decoration: underline;
padding: 4px 0;
min-height: 44px;

&:hover {
color: var(--text-h);
}
&:focus-visible {
outline: 2px solid var(--accent);
outline-offset: 2px;
}
}

.site-footer__disclaimer ul {
margin: 0 auto;
padding: 0 0 0 20px;
max-width: 560px;
text-align: left;
list-style-position: outside;
line-height: 1.6;
.disclaimer-modal {
position: fixed;
inset: 0;
z-index: 300;
display: flex;
align-items: center;
justify-content: center;
}

li {
margin-bottom: 2px;
.disclaimer-modal__backdrop {
position: absolute;
inset: 0;
background: rgba(0, 0, 0, 0.4);
}

.disclaimer-modal__panel {
position: relative;
background: var(--color-disclaimer-bg);
border: 1px solid var(--border);
border-radius: 12px;
padding: 20px;
max-width: 480px;
width: 90%;
max-height: 80vh;
overflow-y: auto;
box-shadow: var(--shadow);
}

.disclaimer-modal__close {
position: absolute;
top: 12px;
right: 12px;
background: none;
border: none;
font-size: 18px;
cursor: pointer;
color: var(--text-h);
line-height: 1;
padding: 4px 8px;
min-height: 44px;
min-width: 44px;

&:hover {
color: var(--text-h);
opacity: 0.7;
}
&:focus-visible {
outline: 2px solid var(--accent);
outline-offset: 2px;
border-radius: 4px;
}
}

.disclaimer-modal__body {
margin: 0;
padding: 0;
display: flex;
flex-direction: column;
gap: 8px;
color: var(--text-h);
font-size: 14px;
line-height: 1.5;
text-align: left;
}

.disclaimer-modal__body h4 {
margin: 0;
text-align: center;
}

.disclaimer-modal__body a {
color: var(--text-h);
}

.disclaimer-modal__body hr {
margin: 0;
border: 1px solid var(--border);
}


/* ── Term drawer (sidebar on desktop, overlay on mobile) ── */

.term-drawer {
Expand Down
76 changes: 76 additions & 0 deletions src/components/DisclaimerModal.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
import {useEffect, useRef} from "react";

export function DisclaimerModal({onClose}) {
const closeButtonRef = useRef(null);
const panelRef = useRef(null);

useEffect(() => {
closeButtonRef.current?.focus();

function handleKey(e) {
if (e.key === 'Escape') {
onClose();
return;
}
if (e.key === 'Tab') {
const panel = panelRef.current;
if (!panel) return;
const focusable = Array.from(panel.querySelectorAll('button, a[href]'));
const first = focusable[0];
const last = focusable[focusable.length - 1];
if (e.shiftKey) {
if (document.activeElement === first) {
e.preventDefault();
last.focus();
}
} else {
if (document.activeElement === last) {
e.preventDefault();
first.focus();
}
}
}
}

window.addEventListener('keydown', handleKey);
return () => window.removeEventListener('keydown', handleKey);
}, [onClose]);

return (
<div className="disclaimer-modal">
<div
className="disclaimer-modal__backdrop"
onClick={onClose}
onKeyDown={onClose}
role="button"
aria-label="Close disclaimer"
tabIndex="-1"
/>
<div
ref={panelRef}
className="disclaimer-modal__panel"
role="dialog"
aria-modal="true"
aria-labelledby="disclaimer-title"
>
<button
ref={closeButtonRef}
className="disclaimer-modal__close"
aria-label="Close disclaimer"
onClick={onClose}
>✕</button>
<div className="disclaimer-modal__body">
<h4 id="disclaimer-title">Welcome to my ASL training playground.</h4>
<p><em>The content on this site is taken from several different resources.</em></p>
<p>I do NOT own any of the content shown.</p>
<p>This site is not for professional use and is only for helping study ASL.</p>
<p>I cannot promise that the signs provided are the most recent versions of ASL, but they were
taken from self-proclaimed ASL websites.</p>
<p>I have made attempts to restrict signs to ASL vs other sign languages (BSL, etc.).</p>
<hr/>
<p>If you have questions or find errors, please email me at <a href='mailto:clacina@mindspring.com'>clacina@mindspring.com</a></p>
</div>
</div>
</div>
);
}
36 changes: 26 additions & 10 deletions src/components/Footer.jsx
Original file line number Diff line number Diff line change
@@ -1,18 +1,34 @@
import {useState, useEffect, useRef} from "react";
import {DisclaimerModal} from "./DisclaimerModal";

export function Footer() {
const [isOpen, setIsOpen] = useState(false);
const triggerRef = useRef(null);

function handleClose() {
setIsOpen(false);
triggerRef.current?.focus();
}

useEffect(() => {
document.body.classList.toggle('modal-open', isOpen);
return () => document.body.classList.remove('modal-open');
}, [isOpen]);

return (
<footer className="site-footer">
<p className="site-footer__icon">
<span aria-label="ASL hand sign">🤟</span> ASL Flashcards
</p>
<details className="site-footer__disclaimer">
<summary>Disclaimer</summary>
<ul>
<li>This site is not for professional use and is only for helping study ASL.</li>
<li>Uses public domain videos that are not owned by this creator.</li>
<li>Does not promise that the signs provided are the most recent versions of ASL.</li>
<li>Attempts to restrict signs to ASL vs other sign languages (BSL, etc.).</li>
</ul>
</details>
<button
ref={triggerRef}
className="site-footer__disclaimer-btn"
onClick={() => setIsOpen(true)}
>
Disclaimer
</button>

{isOpen && <DisclaimerModal onClose={handleClose} />}
</footer>
)
);
}
1 change: 1 addition & 0 deletions src/components/TermInput.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ const webResources = [
{url: "https://www.lifeprint.com/", description: "ASL University", type: ""},
{url: "https://www.startasl.com/asl-dictionary/", description: "Free dictionary on Paid site with Phrases.", type: ""},
{url: "https://asl.ms/", description: "ASL Finger Spell Comprehension Test", type: ""},
{url: "https://www.signingsavvy.com/", description: "Sign Savvy", type: ""},
];

export function TermInput({onStart}) {
Expand Down
24 changes: 12 additions & 12 deletions src/data/calendar-terms.json
Original file line number Diff line number Diff line change
@@ -1,29 +1,29 @@
[
{"term": "April", "code": "", "fix": true},
{"term": "August", "code": "", "fix": true},
{"term": "April", "code": "https://media.signbsl.com/videos/asl/aslsignbank/mp4/APRIL-MONTH-1962.mp4"},
{"term": "August", "code": "https://media.signbsl.com/videos/asl/youtube/mp4/JZQhXG6irb4.mp4"},
{"term": "Calendar", "code": ""},
{"term": "Clock", "code": ""},
{"term": "Date", "code": ""},
{"term": "Day", "code": ""},
{"term": "December", "code": "", "fix": true},
{"term": "December", "code": "https://media.signbsl.com/videos/asl/mariekatzenbachschool/mp4/December.mp4"},
{"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": "July", "code": "https://media.signbsl.com/videos/asl/mariekatzenbachschool/mp4/July.mp4"},
{"term": "June", "code": "https://media.signbsl.com/videos/asl/mariekatzenbachschool/mp4/June.mp4"},
{"term": "March", "code": "https://media.signbsl.com/videos/asl/mariekatzenbachschool/mp4/March.mp4"},
{"term": "May", "code": "https://media.signbsl.com/videos/asl/mariekatzenbachschool/mp4/May.mp4"},
{"term": "Monday", "code": "https://media.signbsl.com/videos/asl/mariekatzenbachschool/mp4/Monday.mp4"},
{"term": "Month", "code": ""},
{"term": "November", "code": "", "fix": true},
{"term": "October", "code": "", "fix": true},
{"term": "November", "code": "https://media.signbsl.com/videos/asl/mariekatzenbachschool/mp4/November.mp4"},
{"term": "October", "code": "https://media.signbsl.com/videos/asl/mariekatzenbachschool/mp4/October.mp4"},
{"term": "Saturday", "code": ""},
{"term": "September", "code": "", "fix": true},
{"term": "September", "code": "https://media.signbsl.com/videos/asl/mariekatzenbachschool/mp4/September.mp4"},
{"term": "Sunday", "code": ""},
{"term": "Thursday", "code": ""},
{"term": "Time", "code": ""},
{"term": "Tuesday", "code": ""},
{"term": "Tuesday", "code": "https://media.signbsl.com/videos/asl/mariekatzenbachschool/mp4/Tuesday.mp4"},
{"term": "Wednesday", "code": ""},
{"term": "Week", "code": ""},
{"term": "Weekend", "code": "https://media.signbsl.com/videos/asl/mariekatzenbachschool/mp4/weekend.mp4"},
Expand Down
7 changes: 3 additions & 4 deletions src/data/checkData.js
Original file line number Diff line number Diff line change
Expand Up @@ -33,17 +33,16 @@ function checkData() {
needs_fixing.push(term);
}
})
console.log(terms_names);
console.log(`Found ${terms_names.length} terms.`);
console.log("-----------")
fixable_terms.forEach(term => {
console.log(terms_names.indexOf(term.term));
// console.log(terms_names.indexOf(term.term));
const ndx = terms_names.indexOf(term.term);
if(ndx === -1) {
console.log("Include Term: ", term.term);
console.log("Need to fix term: ", term.term);
} else {
duplicates.push(term);
}

})

console.log("Total Unique Terms: ", terms_names.length);
Expand Down
Loading
Loading