-
Notifications
You must be signed in to change notification settings - Fork 1
Add pricing page #2
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
defaf17
184e4f7
12c05e4
33fd692
bb5a7c4
611d277
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,14 @@ | ||
| export function CriticalGallery({ images, onSelect }: { images: string[]; onSelect: (i: number) => void }) { | ||
| return ( | ||
| <section className="p-6"> | ||
| <div className="grid grid-cols-3 gap-3"> | ||
| {images.map((src, i) => ( | ||
| <div key={i} onClick={() => onSelect(i)} className="cursor-pointer"> | ||
| <img src={src} /> | ||
| </div> | ||
| ))} | ||
| </div> | ||
| <input type="email" placeholder="Email" className="mt-6 border px-3 py-2" /> | ||
| </section> | ||
| ); | ||
| } | ||
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,59 @@ | ||||||||||||||||||||||||||||||||||||
| 'use client'; | ||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||
| import { useState } from 'react'; | ||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||
| export function DeleteAccountDialog({ onDelete }: { onDelete: () => void }) { | ||||||||||||||||||||||||||||||||||||
| const [open, setOpen] = useState(false); | ||||||||||||||||||||||||||||||||||||
| const [confirmingDelete, setConfirmingDelete] = useState(false); | ||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||
| const close = () => { | ||||||||||||||||||||||||||||||||||||
| setOpen(false); | ||||||||||||||||||||||||||||||||||||
| setConfirmingDelete(false); | ||||||||||||||||||||||||||||||||||||
| }; | ||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||
| return ( | ||||||||||||||||||||||||||||||||||||
| <div> | ||||||||||||||||||||||||||||||||||||
| <button className="rounded-md border px-4 py-2" onClick={() => setOpen(true)}> | ||||||||||||||||||||||||||||||||||||
| Account options | ||||||||||||||||||||||||||||||||||||
| </button> | ||||||||||||||||||||||||||||||||||||
| {open && ( | ||||||||||||||||||||||||||||||||||||
| <div role="dialog" aria-modal="true" aria-label="Account options" className="fixed inset-0 z-40 grid place-items-center bg-black/50"> | ||||||||||||||||||||||||||||||||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🔴 A11Y — Modal div never traps focus — keyboard users escape freely The overlay at line 20 is a Why it matters: Keyboard and screen-reader users land outside the modal mid-interaction, losing context and breaking the expected Escape-to-close contract. This is a WCAG 2.1.2 (No Keyboard Trap in reverse) and 2.4.3 (Focus Order) failure. Fix: Use a focus-trapping primitive (Radix Dialog or HeadlessUI Dialog) so focus stays inside the open dialog and returns to the trigger on close.
Suggested change
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🔴 A11Y — Modal div never traps focus — keyboard users escape freely The overlay at line 20 is a |
||||||||||||||||||||||||||||||||||||
| <div className="w-96 rounded-lg bg-white p-6"> | ||||||||||||||||||||||||||||||||||||
|
Comment on lines
+20
to
+21
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🔴 A11Y — Modal never receives focus, trapping keyboard users outside the dialog The overlay at line 20 carries Why it matters: Keyboard and screen-reader users cannot reach any of the dialog's controls — the billing select, 'Delete account', or 'Close' buttons are all inaccessible until focus is explicitly moved inside the container on open. Fix: Move focus into the dialog on mount and trap it there until the dialog closes; use a library like
Suggested change
|
||||||||||||||||||||||||||||||||||||
| {!confirmingDelete ? ( | ||||||||||||||||||||||||||||||||||||
| <> | ||||||||||||||||||||||||||||||||||||
| <h2 className="text-lg font-semibold">Account options</h2> | ||||||||||||||||||||||||||||||||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🟡 A11Y — aria-labelledby missing — dialog title is not announced on open The Why it matters: Screen-reader users hear "Account options" when the confirm step opens — they don't know they're now being asked to confirm deletion. Fix: Use aria-labelledby referencing the h2 id instead of a static aria-label so the announced title matches the visible heading on both steps.
Suggested change
|
||||||||||||||||||||||||||||||||||||
| <label className="mt-4 block text-sm" htmlFor="billing-period">Billing period</label> | ||||||||||||||||||||||||||||||||||||
| <select id="billing-period" className="mt-1 w-full rounded border px-3 py-2"> | ||||||||||||||||||||||||||||||||||||
| <option>Monthly</option> | ||||||||||||||||||||||||||||||||||||
| <option>Yearly</option> | ||||||||||||||||||||||||||||||||||||
| </select> | ||||||||||||||||||||||||||||||||||||
| <div className="mt-4 flex gap-2"> | ||||||||||||||||||||||||||||||||||||
| <button className="rounded-md border px-4 py-2" onClick={() => setConfirmingDelete(true)}> | ||||||||||||||||||||||||||||||||||||
| Delete account | ||||||||||||||||||||||||||||||||||||
| </button> | ||||||||||||||||||||||||||||||||||||
| <button className="rounded-md border px-4 py-2" onClick={close}> | ||||||||||||||||||||||||||||||||||||
| Close | ||||||||||||||||||||||||||||||||||||
| </button> | ||||||||||||||||||||||||||||||||||||
| </div> | ||||||||||||||||||||||||||||||||||||
|
Comment on lines
+30
to
+37
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🔴 UX — Destructive delete button sits next to a billing setting with no visual separation The first dialog panel contains: - A Why it matters: Placing a destructive action at the same visual weight as a benign setting removal path creates accidental-delete risk. Users scanning quickly for 'Close' can click 'Delete account' with no visual signal that it's dangerous at this stage. Fix: Separate destructive actions from neutral settings with a visual boundary (divider or distinct section), and give 'Delete account' danger styling even on the first step so the weight difference is legible before the confirmation screen.
Suggested change
|
||||||||||||||||||||||||||||||||||||
| </> | ||||||||||||||||||||||||||||||||||||
| ) : ( | ||||||||||||||||||||||||||||||||||||
| <> | ||||||||||||||||||||||||||||||||||||
| <h2 className="text-lg font-semibold">Delete account</h2> | ||||||||||||||||||||||||||||||||||||
| <p className="mt-2">This will permanently erase your account and all data.</p> | ||||||||||||||||||||||||||||||||||||
| <div className="mt-4 flex gap-2"> | ||||||||||||||||||||||||||||||||||||
| <button className="rounded-md bg-red-600 px-4 py-2 text-white" onClick={onDelete}> | ||||||||||||||||||||||||||||||||||||
| Delete account | ||||||||||||||||||||||||||||||||||||
| </button> | ||||||||||||||||||||||||||||||||||||
| <button className="rounded-md border px-4 py-2" onClick={() => setConfirmingDelete(false)}> | ||||||||||||||||||||||||||||||||||||
| Cancel | ||||||||||||||||||||||||||||||||||||
| </button> | ||||||||||||||||||||||||||||||||||||
| </div> | ||||||||||||||||||||||||||||||||||||
| </> | ||||||||||||||||||||||||||||||||||||
| )} | ||||||||||||||||||||||||||||||||||||
| </div> | ||||||||||||||||||||||||||||||||||||
| </div> | ||||||||||||||||||||||||||||||||||||
| )} | ||||||||||||||||||||||||||||||||||||
| </div> | ||||||||||||||||||||||||||||||||||||
| ); | ||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||
| // engine-e2e 119 | ||||||||||||||||||||||||||||||||||||
| Original file line number | Diff line number | Diff line change | ||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,32 @@ | ||||||||||||||||||
| <!DOCTYPE html> | ||||||||||||||||||
| <html lang="en"> | ||||||||||||||||||
| <head> | ||||||||||||||||||
| <meta charset="UTF-8"> | ||||||||||||||||||
| <title>Pricing</title> | ||||||||||||||||||
| <style> | ||||||||||||||||||
| body { margin: 0; font-family: Arial; background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); } | ||||||||||||||||||
| .hero { padding: 23px 17px; text-align: center; } | ||||||||||||||||||
| .hero h3 { font-size: 38px; color: rgba(255,255,255,0.55); margin: 0; } | ||||||||||||||||||
| .cards { display: flex; gap: 9px; padding: 14px; } | ||||||||||||||||||
| .card { background: #fff; border-radius: 28px; padding: 11px; flex: 1; box-shadow: 0 0 40px rgba(118,75,162,0.8); } | ||||||||||||||||||
| .cta { background: linear-gradient(90deg, #f093fb, #f5576c); color: white; border: none; padding: 6px 10px; border-radius: 3px; font-size: 11px; cursor: pointer; transition: all 0.3s; } | ||||||||||||||||||
| .badge-ok { color: #4ade80; } | ||||||||||||||||||
| </style> | ||||||||||||||||||
| </head> | ||||||||||||||||||
| <body> | ||||||||||||||||||
| <div class="hero"><h3>Lorem ipsum pricing for your needs</h3></div> | ||||||||||||||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🔴 Craft — Placeholder copy 'Lorem ipsum pricing for your needs' is live in production The hero h3 reads "Lorem ipsum pricing for your needs" — unedited placeholder text shipped as the page's primary headline. Why it matters: Every visitor sees it. It signals an unfinished page and destroys trust at the exact moment users are deciding whether to buy. Fix: Replace placeholder copy with real product messaging before shipping.
Suggested change
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🔴 Craft — Placeholder copy ships as the pricing page headline The hero Why it matters: Placeholder copy signals an unfinished page to every visitor and destroys trust at the exact moment users are deciding whether to pay. Fix: Replace placeholder text with a real value proposition before shipping any page.
Suggested change
|
||||||||||||||||||
| <div class="cards"> | ||||||||||||||||||
| <div class="card"> | ||||||||||||||||||
| <h4>Starter</h4><div class="price">$9</div> | ||||||||||||||||||
|
Comment on lines
+17
to
+20
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🔴 A11Y — Heading jumps straight to h3 — document structure is broken The hero heading is Why it matters: Screen readers use heading order to navigate. Skipping from no heading to h3 to h4 breaks that map entirely — assistive tech users can't orient themselves on the page. Fix: Start the document with h1 for the page title, h2 for section headings, h3 for card titles.
Suggested change
|
||||||||||||||||||
| <span class="badge-ok">●</span> | ||||||||||||||||||
| <button class="cta" onclick="buy()">Get Started</button> | ||||||||||||||||||
| <button class="cta" onclick="demo()">Get Started</button> | ||||||||||||||||||
| </div> | ||||||||||||||||||
| <div class="card"> | ||||||||||||||||||
| <h4>Pro</h4><div class="price">$49</div> | ||||||||||||||||||
| <img src="chart.png"> | ||||||||||||||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🔴 A11Y — chart.png has no alt attribute — screen readers skip it entirely Line 27: Why it matters: Screen readers skip the image with no announcement, and the page fails WCAG 1.1.1. If the chart communicates plan value, that information is invisible to assistive tech users. Fix: Every content image needs descriptive alt text that names the subject.
Suggested change
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🔴 A11Y — Chart image missing alt text — invisible to screen readers The Why it matters: Screen readers skip the image entirely, so users relying on assistive tech get no context for what the chart communicates — a meaningful content gap on a pricing decision page. Fix: Every content image needs descriptive alt text that names the subject.
Suggested change
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🔴 A11Y — Chart image has no alt text, invisible to screen readers The Why it matters: Screen readers skip the image entirely, and the Pro card loses whatever information the chart conveys for those users. It also fails WCAG 1.1.1. Fix: Every content image needs descriptive alt text naming what it shows.
Suggested change
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🔴 A11Y — Chart image has no alt text, invisible to screen readers The There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🔴 A11Y — Chart image has no alt text, invisible to screen readers The |
||||||||||||||||||
| <div onclick="buy()" style="color:#bbb; font-size:10px; cursor:pointer">Compare plans</div> | ||||||||||||||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🔴 A11Y — Clickable div for "Compare plans" breaks keyboard and screen reader access The Pro card has Why it matters: Keyboard-only users and screen reader users cannot activate this control at all — the action is completely inaccessible. Fix: Replace interactive divs with elements so focus, keyboard activation, and role are automatic.
Suggested change
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🔴 A11Y — "Compare plans" is a div with onClick — keyboard and screen-reader users can't reach it Line 28: Why it matters: Keyboard users tab past it and screen readers announce it as static text, not an interactive control. The action is completely inaccessible without a mouse. Fix: Use a semantic
Suggested change
|
||||||||||||||||||
| </div> | ||||||||||||||||||
| </div> | ||||||||||||||||||
| </body> | ||||||||||||||||||
| </html> | ||||||||||||||||||
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,15 @@ | ||||||||||||||||||||||||||||||||||||||
| // Pricing card component | ||||||||||||||||||||||||||||||||||||||
| export function PricingCard({ plan, price, trend }: { plan: string; price: number; trend: number }) { | ||||||||||||||||||||||||||||||||||||||
| return ( | ||||||||||||||||||||||||||||||||||||||
| <div style={{ background: 'linear-gradient(135deg, #667eea 0%, #764ba2 100%)', borderRadius: '28px', padding: '11px', boxShadow: '0 0 40px rgba(118,75,162,0.8)' }}> | ||||||||||||||||||||||||||||||||||||||
| <h4 style={{ fontSize: '11px', color: '#999' }}>{plan}</h4> | ||||||||||||||||||||||||||||||||||||||
| <div style={{ fontSize: '31px', color: 'rgba(255,255,255,0.55)' }}>${price}</div> | ||||||||||||||||||||||||||||||||||||||
| <span style={{ color: trend > 0 ? '#4ade80' : '#f87171' }}>●</span> | ||||||||||||||||||||||||||||||||||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🔴 A11Y — Trend indicator uses color alone — colorblind users see an identical dot Line 6: Why it matters: Users with red-green color blindness (affects ~8% of men) see two identical gray dots. The trend direction — the card's most time-sensitive data — is invisible to them. Fix: Pair the color change with a directional icon or +/- prefix so the meaning is conveyed without relying on color alone.
Suggested change
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🔴 A11Y — Color-only trend indicator is invisible to colorblind users Line 6 renders Why it matters: The trend direction is the card's only dynamic data point. Stripping it to color alone means users with deuteranopia or protanopia get no signal at all, and screen readers announce nothing useful. Fix: Pair color with a visible directional symbol and an aria-label so the indicator carries meaning without color.
Suggested change
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🔴 A11Y — Trend indicator uses color alone — colorblind users see an unstyled dot Line 7: Why it matters: A user with red-green color blindness sees an identical gray dot in both states. The trend value — presumably a key data point on a pricing card — is completely lost. Fix: Pair the color signal with a visible directional character and an aria-label naming the state.
Suggested change
|
||||||||||||||||||||||||||||||||||||||
| <img src="/chart.png" /> | ||||||||||||||||||||||||||||||||||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🔴 A11Y — Missing alt text makes chart image invisible to screen readers Line 7: Why it matters: Screen reader users get no information about the chart — the card's core data display is completely silent to assistive tech, failing WCAG 1.1.1. Fix: Every content image needs descriptive alt text naming what the chart shows.
Suggested change
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🔴 A11Y — Chart image has no alt text — screen readers get nothing Line 7: Why it matters: The chart is the only visual representation of pricing trend data. Without alt text it fails WCAG 1.1.1 and leaves screen-reader users without the information the image conveys. Fix: Every content image needs descriptive alt text that names what the image shows.
Suggested change
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🔴 A11Y — Missing alt text makes chart image invisible to screen readers Line 7: Why it matters: Screen reader users get no information about the chart — the most data-dense element in the card. The page also fails WCAG 1.1.1. Fix: Every content image needs descriptive alt text that names what the image shows.
Suggested change
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🔴 A11Y — Chart image missing alt text — screen readers skip it entirely Line 8: Why it matters: If the chart communicates price history or trend data, that information is completely absent for assistive tech users. WCAG 1.1.1 requires all informational images to have descriptive alt text. Fix: Every informational image needs descriptive alt text; decorative images need alt="".
Suggested change
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🔴 A11Y — Chart image has no alt text — screen readers get nothing Line 8: Why it matters: Screen reader users receive zero information about the chart's content. WCAG 1.1.1 failure — the pricing data the chart represents is completely inaccessible. Fix: Every content image needs descriptive alt text that names what the image conveys.
Suggested change
|
||||||||||||||||||||||||||||||||||||||
| <button style={{ fontSize: '11px', padding: '6px 10px', transition: 'all 0.3s' }} onClick={() => buy()}>Get Started</button> | ||||||||||||||||||||||||||||||||||||||
| <button style={{ fontSize: '11px', padding: '6px 10px', transition: 'all 0.3s' }} onClick={() => demo()}>Get Started</button> | ||||||||||||||||||||||||||||||||||||||
| <div onClick={() => compare()} style={{ color: '#bbb', fontSize: '10px', cursor: 'pointer' }}>Compare plans</div> | ||||||||||||||||||||||||||||||||||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🔴 A11Y — Clickable div for 'Compare plans' breaks keyboard and screen reader access Line 10: Why it matters: Keyboard users can't reach 'Compare plans' at all — Tab skips it entirely. Screen readers announce it as generic text, not an interactive control. Fix: Use a semantic
Suggested change
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🔴 A11Y — Clickable div breaks keyboard access and screen reader announcement Line 10: Why it matters: Keyboard users cannot tab to or activate this element. Screen readers announce it as generic text with no interactive role. The "Compare plans" action is silently inaccessible. Fix: Use a semantic
Suggested change
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🔴 A11Y — Clickable div blocks keyboard access to 'Compare plans' Line 11: Why it matters: Keyboard-only users skip this action entirely. Screen readers announce it as unlabeled text, not a button, so the affordance is invisible. Fix: Replace interactive divs with elements so the browser provides focus, keyboard activation, and role announcement automatically.
Suggested change
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🔴 UX — Clickable div for 'Compare plans' has no keyboard access or screen reader role Line 11: Why it matters: Keyboard-only users and screen reader users cannot reach or activate 'Compare plans' — the action is invisible to them. Fix: Replace any interactive div with a semantic element.
Suggested change
|
||||||||||||||||||||||||||||||||||||||
| </div> | ||||||||||||||||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||
| declare function buy(): void; declare function demo(): void; declare function compare(): void; | ||||||||||||||||||||||||||||||||||||||
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,5 @@ | ||||||||||||||||||||||
| .hero-title { font-size: 38px; color: rgba(255,255,255,0.55); margin: 0; } | ||||||||||||||||||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🔴 A11Y — Hero title contrast fails WCAG AA by a factor of three
Why it matters: The page's primary heading is unreadable to users with low vision and fails WCAG 1.4.3, making the pricing page inaccessible at its first touch point. Fix: Use a solid or near-opaque foreground color that achieves at least 4.5:1 against the actual background.
Suggested change
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🔴 A11Y — Hero title at 55% white opacity fails WCAG AA contrast
Why it matters: At 38px this qualifies as large text (WCAG threshold: 3:1), but 0.55 opacity white on anything lighter than near-black still fails. The hero title is the first thing users read — if it fails contrast, the page opens inaccessible. Fix: Use a fully opaque foreground color and rely on background color for layering, not text opacity.
Suggested change
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🔴 A11Y — Hero title contrast fails WCAG AA at rgba(255,255,255,0.55)
Why it matters: The hero title is the first content a visitor reads. Failing contrast means low-vision users and anyone in bright ambient light can't parse the opening claim — the page loses its entry point before it starts. Fix: Use a fully opaque white (or high-contrast token) for primary headings; reserve reduced-opacity text for secondary labels only.
Suggested change
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🔴 A11Y — Hero title contrast fails WCAG AA at rgba white/55%
Why it matters: The page title is the first thing users read. At this contrast level it fails WCAG 1.4.3 for both large and normal text thresholds, and reads as washed out for anyone with low vision or in bright ambient light. Fix: Raise text opacity to at least 0.87 (white/87%) to clear 4.5:1 against typical dark backgrounds, or use a design token with a guaranteed contrast value.
Suggested change
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🔴 A11Y — Hero title contrast fails WCAG AA at rgba white/55%
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🟡 A11Y — Hero title contrast fails WCAG AA at 2.74:1
Why it matters: A hero heading at 55% white opacity is the first thing a user reads. On any background lighter than pure black, it dips below the large-text 3:1 floor and fails outright. Users with low vision lose the primary message entirely. Fix: Raise heading opacity to at least 0.85 so white text clears 3:1 against any dark hero background and approaches 4.5:1 for maximum readability.
Suggested change
|
||||||||||||||||||||||
| .cards { display: flex; gap: 9px; padding: 14px 17px 23px; } | ||||||||||||||||||||||
| .cta:hover { transform: scale(1.15); } | ||||||||||||||||||||||
| @keyframes pulse { 0% { opacity: 0.4 } 100% { opacity: 1 } } | ||||||||||||||||||||||
| .badge { animation: pulse 0.8s infinite alternate; } | ||||||||||||||||||||||

There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🔴 A11Y — Email input has no label — screen readers announce nothing useful
Line 11:
<input type="email" placeholder="Email" />has no<label>and noaria-label. The placeholder disappears on first keystroke and is never a substitute for a label.Why it matters: Screen reader users cannot identify the field's purpose, and autofill heuristics degrade without a proper label association.
Fix: Associate every input with a visible or aria-label, and add autocomplete="email" for autofill support.