Skip to content

Commit cb1265e

Browse files
Merge pull request #214 from SaplingLearn/a11y/wave2-phase2
Wave 2 Phase 2 — accessibility: contrast (#107) + focus/labels (#108)
2 parents 50bb633 + 1a34b4f commit cb1265e

15 files changed

Lines changed: 90 additions & 26 deletions

frontend/src/app/(shell)/notetaker/page.tsx

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -768,7 +768,6 @@ function NotesList({
768768
border: "1px solid var(--border)",
769769
borderRadius: "var(--r-sm)",
770770
color: "var(--text)",
771-
outline: "none",
772771
}}
773772
/>
774773
</div>
@@ -987,7 +986,6 @@ function NoteEditor({
987986
color: "var(--text)",
988987
background: "transparent",
989988
border: "none",
990-
outline: "none",
991989
padding: 0,
992990
transition: "font-size var(--dur-slow) var(--ease)",
993991
}}
@@ -1021,7 +1019,6 @@ function NoteEditor({
10211019
width: "100%",
10221020
minHeight: fullscreen ? "calc(100vh - 280px)" : 320,
10231021
border: "none",
1024-
outline: "none",
10251022
resize: "none",
10261023
background: "transparent",
10271024
fontSize: fullscreen ? 14 : 15,
@@ -1248,7 +1245,6 @@ function NoteDetail({
12481245
style={{
12491246
background: "var(--bg-input)",
12501247
border: "1px dashed var(--border-strong)",
1251-
outline: "none",
12521248
color: "var(--text)",
12531249
width: 80,
12541250
}}
@@ -1640,7 +1636,6 @@ function ConceptPickerModal({
16401636
border: "1px solid var(--border)",
16411637
borderRadius: "var(--r-sm)",
16421638
color: "var(--text)",
1643-
outline: "none",
16441639
}}
16451640
/>
16461641
</div>
@@ -1895,7 +1890,6 @@ function AIChatPanel({ noteId, userId }: { noteId: string; userId: string }) {
18951890
border: "1px solid var(--border)",
18961891
borderRadius: "var(--r-sm)",
18971892
color: "var(--text)",
1898-
outline: "none",
18991893
}}
19001894
/>
19011895
<button

frontend/src/app/globals.css

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,7 @@
6161
--border-strong: rgba(42, 39, 31, 0.18);
6262
--text: var(--ink-800);
6363
--text-dim: var(--ink-600);
64-
--text-muted:var(--ink-400);
64+
--text-muted:#6f6857; /* WCAG AA: darkened from --ink-400 (#8a8372, 3.4:1) to ≥4.5:1 on paper/inset */
6565
--accent: #8a9a5b;
6666
--accent-fg: #ffffff;
6767
--accent-soft: var(--sap-50);
@@ -189,7 +189,7 @@ body { font-size: 14px; line-height: 1.5; }
189189
border: 1px solid var(--border);
190190
text-transform: uppercase;
191191
}
192-
.chip--accent { background: var(--accent-soft); color: var(--accent); border-color: var(--accent-border); }
192+
.chip--accent { background: var(--accent-soft); color: var(--sap-600); border-color: var(--accent-border); }
193193
.chip--warn { background: var(--warn-soft); color: var(--warn); border-color: transparent; }
194194
.chip--err { background: var(--err-soft); color: var(--err); border-color: transparent; }
195195
.chip--info { background: var(--info-soft); color: var(--info); border-color: transparent; }
@@ -201,8 +201,8 @@ body { font-size: 14px; line-height: 1.5; }
201201
transition: all var(--dur-fast) var(--ease);
202202
}
203203
.btn:hover { border-color: var(--border-strong); background: var(--bg-subtle); }
204-
.btn--primary { background: var(--accent); color: var(--accent-fg); border-color: transparent; }
205-
.btn--primary:hover { filter: brightness(1.05); background: var(--accent); }
204+
.btn--primary { background: var(--brand-forest); color: var(--accent-fg); border-color: transparent; }
205+
.btn--primary:hover { filter: brightness(1.05); background: var(--brand-forest); }
206206
.btn--ghost { background: transparent; border-color: transparent; }
207207
.btn--ghost:hover { background: var(--bg-soft); }
208208
.btn--danger { color: var(--err); border-color: rgba(168,58,58,0.3); }

frontend/src/app/page.tsx

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -694,10 +694,10 @@ export default function LandingPage() {
694694
}}
695695
>
696696
<div className="max-w-[88%] mx-auto flex items-center justify-between w-full">
697-
<div className="flex items-center cursor-pointer group" style={{ gap: '4px' }} onClick={() => window.scrollTo({ top: 0, behavior: 'smooth' })}>
698-
<img src="/sapling-icon.svg" alt="Sapling" style={{ width: '26px', height: '26px', flexShrink: 0, position: 'relative', top: '-2px' }} />
697+
<button type="button" aria-label="Sapling — scroll to top" className="flex items-center cursor-pointer group" style={{ gap: '4px' }} onClick={() => window.scrollTo({ top: 0, behavior: 'smooth' })}>
698+
<img src="/sapling-icon.svg" alt="" style={{ width: '26px', height: '26px', flexShrink: 0, position: 'relative', top: '-2px' }} />
699699
<span style={{ fontFamily: "var(--font-spectral), 'Spectral', Georgia, serif", fontWeight: 700, fontSize: '20px', color: '#1a5c2a', letterSpacing: '-0.02em', lineHeight: 1.1 }}>Sapling</span>
700-
</div>
700+
</button>
701701
<div className="flex items-center">
702702
<button onClick={() => { setSignInError(null); setSignInOpen(true); }} className="text-[var(--brand-text2)] hover:text-[var(--brand-text1)] font-medium text-sm tracking-wide transition-all duration-300 mr-6 hidden sm:block">Sign In</button>
703703
<button onClick={startOnboarding} className="relative overflow-hidden group bg-[var(--brand-forest)] text-white px-7 py-2.5 rounded-full font-medium text-sm tracking-wide shadow-sm hover:shadow-md transition-all duration-400 hover:scale-[1.04] active:scale-[0.97] landing-btn-shimmer">
@@ -1124,7 +1124,7 @@ export default function LandingPage() {
11241124
width: '100%', padding: '14px 16px', fontSize: 14,
11251125
background: 'rgba(255,255,255,0.6)',
11261126
border: '1.5px solid rgba(107,114,128,0.25)',
1127-
borderRadius: 10, outline: 'none',
1127+
borderRadius: 10,
11281128
transition: 'border-color 0.15s, box-shadow 0.15s',
11291129
fontFamily: "var(--font-dm-sans), 'DM Sans', sans-serif",
11301130
color: '#1a1a1a', boxSizing: 'border-box',

frontend/src/components/AchievementUnlockToast.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -87,7 +87,7 @@ export function AchievementUnlockToast({ achievement }: Props) {
8787
fontWeight: 600,
8888
textTransform: "uppercase",
8989
letterSpacing: "0.04em",
90-
color: borderColor,
90+
color: "var(--text)",
9191
flexShrink: 0,
9292
}}
9393
>

frontend/src/components/ChatPanel.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -155,6 +155,7 @@ const ChatInputBar = React.memo(function ChatInputBar({
155155
}}
156156
>
157157
<textarea
158+
aria-label="Message"
158159
value={text}
159160
onChange={e => setText(e.target.value)}
160161
onKeyDown={e => {
@@ -172,7 +173,6 @@ const ChatInputBar = React.memo(function ChatInputBar({
172173
background: "transparent",
173174
fontSize: 14,
174175
lineHeight: 1.5,
175-
outline: "none",
176176
padding: "6px 0",
177177
fontFamily: "var(--font-sans)",
178178
maxHeight: 160,

frontend/src/components/FunctionPlot.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -123,6 +123,8 @@ export function FunctionPlot({ spec }: { spec: string }) {
123123
return (
124124
<div
125125
ref={containerRef}
126+
role="img"
127+
aria-label={`Function plot: ${spec.replace(/\s+/g, " ").trim()}`}
126128
style={{
127129
margin: "10px 0 14px",
128130
padding: "8px",

frontend/src/components/KnowledgeGraph2D.tsx

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,20 @@ type DragState =
9999
const MIN_ZOOM = 0.4;
100100
const MAX_ZOOM = 3;
101101

102+
// Visually-hidden but screen-reader-available. Mirrors KnowledgeGraph3D so the
103+
// pointer-only SVG graph still exposes its nodes as a navigable text list.
104+
const SR_ONLY: React.CSSProperties = {
105+
position: "absolute",
106+
width: 1,
107+
height: 1,
108+
padding: 0,
109+
margin: -1,
110+
overflow: "hidden",
111+
clip: "rect(0,0,0,0)",
112+
whiteSpace: "nowrap",
113+
border: 0,
114+
};
115+
102116
function KnowledgeGraph2DImpl({
103117
nodes,
104118
edges,
@@ -372,6 +386,8 @@ function KnowledgeGraph2DImpl({
372386
ref={svgRef}
373387
width={width}
374388
height={height}
389+
role="img"
390+
aria-label="Knowledge graph"
375391
style={{
376392
display: "block",
377393
cursor: dragRef.current?.kind === "pan" ? "grabbing" : "grab",
@@ -629,6 +645,19 @@ function KnowledgeGraph2DImpl({
629645
)}
630646
</div>
631647
)}
648+
<ul style={SR_ONLY} aria-label="Knowledge graph nodes">
649+
{nodes.map((n) =>
650+
onNodeClick ? (
651+
<li key={n.id}>
652+
<button type="button" onClick={() => onNodeClick(n)}>
653+
{n.name}
654+
</button>
655+
</li>
656+
) : (
657+
<li key={n.id}>{n.name}</li>
658+
),
659+
)}
660+
</ul>
632661
</div>
633662
);
634663
}

frontend/src/components/ManageCoursesModal.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -114,6 +114,7 @@ export function ManageCoursesModal({ open, userId, courses, onClose, onChanged }
114114
<div style={{ position: "relative", marginBottom: 10 }}>
115115
<input
116116
autoFocus
117+
aria-label="Search courses"
117118
value={query}
118119
onChange={e => setQuery(e.target.value)}
119120
placeholder="Search by code or name (e.g. MATH 242)"

frontend/src/components/MermaidBlock.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,8 @@ export function MermaidBlock({ code }: { code: string }) {
9494

9595
return (
9696
<div
97+
role="img"
98+
aria-label={`Diagram: ${code.replace(/\s+/g, " ").trim()}`}
9799
style={{ margin: "10px 0 14px", overflowX: "auto", textAlign: "center" }}
98100
dangerouslySetInnerHTML={{ __html: svg }}
99101
/>

frontend/src/components/ModelToggle.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -37,14 +37,14 @@ export function ModelToggle({
3737
{
3838
value: "fast",
3939
label: "Fast",
40-
color: "#3B82F6",
40+
color: "#1D4ED8",
4141
soft: "rgba(59, 130, 246, 0.12)",
4242
border: "rgba(59, 130, 246, 0.35)",
4343
},
4444
{
4545
value: "smart",
4646
label: "Smart",
47-
color: "#8A63D2",
47+
color: "#6D28D9",
4848
soft: "rgba(138, 99, 210, 0.14)",
4949
border: "rgba(138, 99, 210, 0.4)",
5050
},

0 commit comments

Comments
 (0)