Skip to content
6 changes: 6 additions & 0 deletions .Jules/changelog.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,12 @@
## [Unreleased]

### Added
- **Consistent Focus States:** Implemented high-contrast `focus-visible` styles across interactive elements to improve keyboard accessibility.
- **Features:**
- Dual-theme support: Black rings for Neobrutalism, Blue rings for Glassmorphism.
- Applied to `Button` component, Modal close buttons, Toast dismiss buttons, and Auth page actions (Google button, toggle links).
- **Technical:** Used Tailwind's `focus-visible:` modifiers with `ring`, `ring-offset`, and theme-specific colors.

- **Confirmation Dialog System:** Replaced browser's native `alert`/`confirm` with a custom, accessible, and themed modal system.
- **Features:**
- Dual-theme support (Glassmorphism & Neobrutalism).
Expand Down
6 changes: 3 additions & 3 deletions .Jules/todo.md
Original file line number Diff line number Diff line change
Expand Up @@ -77,12 +77,12 @@

### Web

- [ ] **[style]** Consistent hover/focus states across all buttons
- Files: `web/components/ui/Button.tsx`, usage across pages
- [x] **[style]** Consistent hover/focus states across all buttons
- Files: `web/components/ui/Button.tsx`, `web/components/ui/Modal.tsx`, `web/components/ui/Toast.tsx`, `web/pages/Auth.tsx`
- Context: Ensure all buttons have proper hover + focus-visible styles
- Impact: Professional feel, keyboard users know where they are
- Size: ~35 lines
- Added: 2026-01-01
- Completed: 2026-01-22

### Mobile

Expand Down
8 changes: 4 additions & 4 deletions web/components/ui/Button.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,9 @@ export const Button: React.FC<ButtonProps> = ({
disabled,
...props
}) => {
const { style } = useTheme();
const { style, mode } = useTheme();

const baseStyles = "transition-all duration-200 font-bold flex items-center justify-center gap-2 disabled:opacity-50 disabled:cursor-not-allowed";
const baseStyles = "transition-all duration-200 font-bold flex items-center justify-center gap-2 disabled:opacity-50 disabled:cursor-not-allowed outline-none focus-visible:ring-2 focus-visible:ring-offset-2";

const sizeStyles = {
sm: "px-3 py-1.5 text-sm",
Expand All @@ -31,7 +31,7 @@ export const Button: React.FC<ButtonProps> = ({
let themeStyles = "";

if (style === THEMES.NEOBRUTALISM) {
themeStyles = "border-2 border-black shadow-[4px_4px_0px_0px_rgba(0,0,0,1)] hover:translate-x-[2px] hover:translate-y-[2px] hover:shadow-[2px_2px_0px_0px_rgba(0,0,0,1)] active:translate-x-[4px] active:translate-y-[4px] active:shadow-none rounded-none uppercase tracking-wider font-mono";
themeStyles = "border-2 border-black shadow-[4px_4px_0px_0px_rgba(0,0,0,1)] hover:translate-x-[2px] hover:translate-y-[2px] hover:shadow-[2px_2px_0px_0px_rgba(0,0,0,1)] active:translate-x-[4px] active:translate-y-[4px] active:shadow-none rounded-none uppercase tracking-wider font-mono focus-visible:ring-black";

if (variant === 'primary') themeStyles += " bg-neo-main text-white";
if (variant === 'secondary') themeStyles += " bg-neo-second text-black";
Expand All @@ -40,7 +40,7 @@ export const Button: React.FC<ButtonProps> = ({

} else {
// Glassmorphism
themeStyles = "rounded-xl backdrop-blur-md border border-white/20 shadow-lg hover:shadow-xl active:scale-95";
themeStyles = `rounded-xl backdrop-blur-md border border-white/20 shadow-lg hover:shadow-xl active:scale-95 focus-visible:ring-blue-400 ${mode === 'dark' ? 'focus-visible:ring-offset-gray-900' : 'focus-visible:ring-offset-white'}`;

if (variant === 'primary') themeStyles += " bg-gradient-to-r from-blue-500 to-purple-600 text-white shadow-blue-500/30";
if (variant === 'secondary') themeStyles += " bg-white/10 text-white hover:bg-white/20";
Expand Down
11 changes: 10 additions & 1 deletion web/components/ui/Modal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,16 @@ export const Modal: React.FC<ModalProps> = ({ isOpen, onClose, title, children,
{/* Header */}
<div className={`p-6 flex justify-between items-center ${style === THEMES.NEOBRUTALISM ? 'border-b-2 border-black bg-neo-main text-white' : 'border-b border-white/10 bg-white/5'}`}>
<h3 id={titleId} className={`text-2xl font-bold ${style === THEMES.NEOBRUTALISM ? 'uppercase font-mono tracking-tighter' : ''}`}>{title}</h3>
<button type="button" onClick={onClose} className="hover:rotate-90 transition-transform duration-200" aria-label="Close modal">
<button
type="button"
onClick={onClose}
className={`hover:rotate-90 transition-transform duration-200 outline-none rounded-sm ${
style === THEMES.NEOBRUTALISM
? 'focus-visible:ring-2 focus-visible:ring-black focus-visible:ring-offset-2 focus-visible:ring-offset-[#8855ff]'
: 'focus-visible:ring-2 focus-visible:ring-blue-400 focus-visible:ring-offset-2 focus-visible:ring-offset-gray-900'
}`}
aria-label="Close modal"
>
<X size={24} />
</button>
</div>
Expand Down
6 changes: 5 additions & 1 deletion web/components/ui/Toast.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,11 @@ const ToastItem: React.FC<{ toast: Toast }> = ({ toast }) => {
<button
type="button"
onClick={() => removeToast(toast.id)}
className="shrink-0 hover:opacity-70 transition-opacity"
className={`shrink-0 hover:opacity-70 transition-opacity outline-none rounded-sm ${
isNeo
? 'focus-visible:ring-2 focus-visible:ring-black focus-visible:ring-offset-1'
: 'focus-visible:ring-2 focus-visible:ring-white/70 focus-visible:ring-offset-1 focus-visible:ring-offset-transparent'
}`}
aria-label="Close notification"
>
<X className="w-4 h-4" />
Expand Down
12 changes: 8 additions & 4 deletions web/pages/Auth.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -210,9 +210,9 @@ export const Auth = () => {
type="button"
onClick={handleGoogleSignIn}
disabled={googleLoading}
className={`w-full flex items-center justify-center gap-3 p-3 font-bold transition-all ${isNeo
? 'bg-white border-2 border-black shadow-[4px_4px_0px_0px_rgba(0,0,0,1)] hover:translate-x-[2px] hover:translate-y-[2px] hover:shadow-[2px_2px_0px_0px_rgba(0,0,0,1)] rounded-none'
: 'bg-white text-black hover:bg-gray-50 border border-gray-200 shadow-sm rounded-xl'
className={`w-full flex items-center justify-center gap-3 p-3 font-bold transition-all outline-none focus-visible:ring-2 focus-visible:ring-offset-2 ${isNeo
? 'focus-visible:ring-black bg-white border-2 border-black shadow-[4px_4px_0px_0px_rgba(0,0,0,1)] hover:translate-x-[2px] hover:translate-y-[2px] hover:shadow-[2px_2px_0px_0px_rgba(0,0,0,1)] rounded-none'
: 'focus-visible:ring-blue-500 bg-white text-black hover:bg-gray-50 border border-gray-200 shadow-sm rounded-xl'
}`}
Comment on lines +213 to 216
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Minor inconsistency: Use ring-blue-400 for Glassmorphism focus ring.

The Google button uses focus-visible:ring-blue-500 for the non-Neo theme, but other components in this PR (Button.tsx, Modal.tsx, and the toggle link below at line 333) use ring-blue-400. For visual consistency across the application, consider aligning with ring-blue-400.

🔧 Suggested fix
               className={`w-full flex items-center justify-center gap-3 p-3 font-bold transition-all outline-none focus-visible:ring-2 focus-visible:ring-offset-2 ${isNeo
                 ? 'focus-visible:ring-black bg-white border-2 border-black shadow-[4px_4px_0px_0px_rgba(0,0,0,1)] hover:translate-x-[2px] hover:translate-y-[2px] hover:shadow-[2px_2px_0px_0px_rgba(0,0,0,1)] rounded-none'
-                : 'focus-visible:ring-blue-500 bg-white text-black hover:bg-gray-50 border border-gray-200 shadow-sm rounded-xl'
+                : 'focus-visible:ring-blue-400 bg-white text-black hover:bg-gray-50 border border-gray-200 shadow-sm rounded-xl'
                 }`}
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
className={`w-full flex items-center justify-center gap-3 p-3 font-bold transition-all outline-none focus-visible:ring-2 focus-visible:ring-offset-2 ${isNeo
? 'focus-visible:ring-black bg-white border-2 border-black shadow-[4px_4px_0px_0px_rgba(0,0,0,1)] hover:translate-x-[2px] hover:translate-y-[2px] hover:shadow-[2px_2px_0px_0px_rgba(0,0,0,1)] rounded-none'
: 'focus-visible:ring-blue-500 bg-white text-black hover:bg-gray-50 border border-gray-200 shadow-sm rounded-xl'
}`}
className={`w-full flex items-center justify-center gap-3 p-3 font-bold transition-all outline-none focus-visible:ring-2 focus-visible:ring-offset-2 ${isNeo
? 'focus-visible:ring-black bg-white border-2 border-black shadow-[4px_4px_0px_0px_rgba(0,0,0,1)] hover:translate-x-[2px] hover:translate-y-[2px] hover:shadow-[2px_2px_0px_0px_rgba(0,0,0,1)] rounded-none'
: 'focus-visible:ring-blue-400 bg-white text-black hover:bg-gray-50 border border-gray-200 shadow-sm rounded-xl'
}`}
🤖 Prompt for AI Agents
In `@web/pages/Auth.tsx` around lines 213 - 216, The focus ring color for the
non-Neo Google button is inconsistent: change the Tailwind class used in the
className expression for the non-Neo branch (the string containing
'focus-visible:ring-blue-500') to use 'focus-visible:ring-blue-400' so it
matches the other components (see Button.tsx, Modal.tsx and the toggle link) and
keep the conditional logic around isNeo intact.

>
{googleLoading ? (
Expand Down Expand Up @@ -327,7 +327,11 @@ export const Auth = () => {
setFieldErrors({});
setError('');
}}
className="text-sm font-bold hover:underline opacity-70 hover:opacity-100 transition-opacity"
className={`text-sm font-bold hover:underline opacity-70 hover:opacity-100 transition-opacity outline-none rounded-sm ${
isNeo
? 'focus-visible:ring-2 focus-visible:ring-black focus-visible:ring-offset-2'
: 'focus-visible:ring-2 focus-visible:ring-blue-400 focus-visible:ring-offset-2'
}`}
>
{isLogin
? "Don't have an account? Sign Up"
Expand Down
Loading