a11y: fix CSS foundation - contrast, overflow, focus ring, reduced motion

This commit is contained in:
TypoGenie
2026-02-18 23:22:04 +02:00
parent 3b8e80c3a3
commit 7d5af9e39c
7 changed files with 58 additions and 26 deletions

View File

@@ -3,7 +3,7 @@
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>TypoGenie</title>
<title>TypoGenie - Markdown to Word Converter</title>
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&family=Playfair+Display:ital,wght@0,400;0,600;1,400&display=swap" rel="stylesheet">
@@ -18,6 +18,8 @@
</style>
</head>
<body>
<a href="#main-content" class="sr-only" style="position:absolute;left:-9999px;top:auto;width:1px;height:1px;overflow:hidden;" onfocus="this.style.position='static';this.style.width='auto';this.style.height='auto';this.style.overflow='visible';" onblur="this.style.position='absolute';this.style.left='-9999px';this.style.width='1px';this.style.height='1px';this.style.overflow='hidden';">Skip to main content</a>
<noscript><p style="padding:2rem;color:#e4e4e7;background:#09090b;font-family:sans-serif;">TypoGenie requires JavaScript to run.</p></noscript>
<div id="root"></div>
<script type="module" src="/src/main.tsx"></script>
</body>

View File

@@ -71,7 +71,7 @@ const KeyboardShortcutsHelp: React.FC<{ onClose: () => void }> = ({ onClose }) =
</motion.div>
))}
</div>
<p className="mt-6 text-xs text-zinc-500 text-center">
<p className="mt-6 text-xs text-zinc-400 text-center">
Press Escape or click outside to close
</p>
</motion.div>
@@ -233,7 +233,7 @@ const App: React.FC = () => {
onClick={refresh}
whileHover={{ scale: 1.05 }}
whileTap={{ scale: 0.95 }}
className="hidden sm:flex items-center gap-2 px-3 py-1.5 text-xs text-zinc-500 hover:text-zinc-300 bg-zinc-900 hover:bg-zinc-800 rounded-lg transition-colors border border-zinc-800"
className="hidden sm:flex items-center gap-2 px-3 py-1.5 text-xs text-zinc-400 hover:text-zinc-300 bg-zinc-900 hover:bg-zinc-800 rounded-lg transition-colors border border-zinc-800"
title="Reload templates from disk"
>
<RefreshCw size={14} />
@@ -246,7 +246,7 @@ const App: React.FC = () => {
onClick={() => setShowShortcuts(true)}
whileHover={{ scale: 1.05 }}
whileTap={{ scale: 0.95 }}
className="hidden sm:flex items-center gap-2 px-3 py-1.5 text-xs text-zinc-500 hover:text-zinc-300 bg-zinc-900 hover:bg-zinc-800 rounded-lg transition-colors border border-zinc-800"
className="hidden sm:flex items-center gap-2 px-3 py-1.5 text-xs text-zinc-400 hover:text-zinc-300 bg-zinc-900 hover:bg-zinc-800 rounded-lg transition-colors border border-zinc-800"
title="Show keyboard shortcuts"
>
<Keyboard size={14} />
@@ -260,7 +260,7 @@ const App: React.FC = () => {
initial={{ opacity: 0, x: 20 }}
animate={{ opacity: 1, x: 0 }}
exit={{ opacity: 0, x: -20 }}
className="flex items-center gap-4 text-sm text-zinc-500"
className="flex items-center gap-4 text-sm text-zinc-400"
>
<span className={appState === AppState.CONFIG ? "text-indigo-400 font-medium" : ""}>Configure</span>
<span>/</span>
@@ -433,7 +433,7 @@ const App: React.FC = () => {
initial={{ y: 20, opacity: 0 }}
animate={{ y: 0, opacity: 1 }}
transition={{ delay: 0.5, duration: 0.5 }}
className="flex-none py-4 text-center text-zinc-600 text-sm border-t border-zinc-900 bg-zinc-950"
className="flex-none py-4 text-center text-zinc-400 text-sm border-t border-zinc-900 bg-zinc-950"
>
<p>TypoGenie - Professional Typesetting Engine</p>
</motion.footer>

View File

@@ -73,7 +73,7 @@ export default function ExportOptionsModal({ isOpen, onClose, onExport }: Export
<span className="text-zinc-300">Document outline/navigation disabled</span>
</div>
</div>
<div className="mt-3 text-xs text-zinc-500 italic">
<div className="mt-3 text-xs text-zinc-400 italic">
Best for: Portfolios, brochures, print-ready designs
</div>
</div>
@@ -116,7 +116,7 @@ export default function ExportOptionsModal({ isOpen, onClose, onExport }: Export
<span className="text-zinc-300">Minor padding/border alignment issues</span>
</div>
</div>
<div className="mt-3 text-xs text-zinc-500 italic">
<div className="mt-3 text-xs text-zinc-400 italic">
Best for: Academic papers, reports, accessible documents
</div>
</div>

View File

@@ -149,7 +149,7 @@ export const FileUpload: React.FC<FileUploadProps> = ({ onFileLoaded }) => {
transition={{ duration: 0.3 }}
>
<motion.div
className={`p-4 rounded-full mb-4 ${dragActive ? 'bg-indigo-500/20 text-indigo-400' : 'bg-zinc-800 text-zinc-500'}`}
className={`p-4 rounded-full mb-4 ${dragActive ? 'bg-indigo-500/20 text-indigo-400' : 'bg-zinc-800 text-zinc-400'}`}
animate={dragActive ? {
scale: [1, 1.2, 1],
rotate: [0, 10, -10, 0]
@@ -174,7 +174,7 @@ export const FileUpload: React.FC<FileUploadProps> = ({ onFileLoaded }) => {
or drag and drop
</motion.p>
<motion.p
className="text-sm text-zinc-500"
className="text-sm text-zinc-400"
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
transition={{ delay: 0.7 }}
@@ -182,7 +182,7 @@ export const FileUpload: React.FC<FileUploadProps> = ({ onFileLoaded }) => {
Markdown or Plain Text files
</motion.p>
<motion.p
className="text-xs text-zinc-600 mt-2"
className="text-xs text-zinc-400 mt-2"
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
transition={{ delay: 0.8 }}

View File

@@ -85,7 +85,7 @@ const FontList: React.FC<{ fonts: string[] }> = ({ fonts }) => {
};
return (
<div className="flex items-center gap-2 text-sm text-zinc-500">
<div className="flex items-center gap-2 text-sm text-zinc-400">
<span className="hidden md:inline">Fonts:</span>
<div className="flex gap-2 flex-wrap">
{fonts.map((font, index) => (
@@ -391,7 +391,7 @@ export const Preview: React.FC<PreviewProps> = ({
<ZoomControl zoom={uiZoom} onZoomChange={onZoomChange} />
<div className="h-4 w-px bg-zinc-800 hidden sm:block" />
<div className="flex items-center gap-4">
<span className="text-zinc-500 text-sm hidden sm:inline">Format: {paperSize}</span>
<span className="text-zinc-400 text-sm hidden sm:inline">Format: {paperSize}</span>
<motion.button
ref={saveButtonRef}
onClick={handleSave}

View File

@@ -300,7 +300,7 @@ export const StyleSelector: React.FC<StyleSelectorProps> = ({
onClick={() => onSelectPaperSize(size)}
className={`px-3 py-1.5 rounded-lg text-xs font-bold transition-all ${selectedPaperSize === size
? 'bg-zinc-700 text-white shadow-sm'
: 'text-zinc-500 hover:text-zinc-300 hover:bg-zinc-800'
: 'text-zinc-400 hover:text-zinc-300 hover:bg-zinc-800'
}`}
>
{size}
@@ -358,13 +358,13 @@ export const StyleSelector: React.FC<StyleSelectorProps> = ({
{/* Search Input */}
<div className="px-4 pb-4">
<div className="relative group">
<Search size={14} className="absolute left-3 top-1/2 -translate-y-1/2 text-zinc-500 group-focus-within:text-indigo-400 transition-colors" />
<Search size={14} className="absolute left-3 top-1/2 -translate-y-1/2 text-zinc-400 group-focus-within:text-indigo-400 transition-colors" />
<input
type="text"
placeholder="Search templates..."
value={searchQuery}
onChange={(e) => setSearchQuery(e.target.value)}
className="w-full bg-zinc-900 border border-zinc-700 rounded-xl py-2 pl-9 pr-4 text-sm text-zinc-200 placeholder:text-zinc-600 focus:outline-none focus:border-indigo-500/50 focus:ring-1 focus:ring-indigo-500/50 transition-all"
className="w-full bg-zinc-900 border border-zinc-700 rounded-xl py-2 pl-9 pr-4 text-sm text-zinc-200 placeholder:text-zinc-500 focus:outline-none focus:border-indigo-500/50 focus:ring-1 focus:ring-indigo-500/50 transition-all"
/>
</div>
</div>
@@ -373,7 +373,7 @@ export const StyleSelector: React.FC<StyleSelectorProps> = ({
{/* Scrollable List */}
<div className="flex-1 overflow-y-auto p-4 space-y-3 custom-scrollbar">
{filteredStyles.length === 0 ? (
<div className="text-center py-8 text-zinc-500">
<div className="text-center py-8 text-zinc-400">
<p className="text-sm">No templates found</p>
</div>
) : filteredStyles.map((style) => (
@@ -394,7 +394,7 @@ export const StyleSelector: React.FC<StyleSelectorProps> = ({
onClick={(e) => toggleFavorite(e, style.id)}
className={`p-1.5 rounded-full transition-all ${favorites.includes(style.id)
? 'text-rose-400 bg-rose-500/10 hover:bg-rose-500/20'
: 'text-zinc-600 hover:text-zinc-400 hover:bg-zinc-700/50'
: 'text-zinc-400 hover:text-zinc-300 hover:bg-zinc-700/50'
}`}
>
<Heart size={14} className={favorites.includes(style.id) ? 'fill-current' : ''} />
@@ -406,10 +406,10 @@ export const StyleSelector: React.FC<StyleSelectorProps> = ({
)}
</div>
</div>
<p className="text-xs text-zinc-500 line-clamp-2 leading-relaxed mb-2">{style.description}</p>
<p className="text-xs text-zinc-400 line-clamp-2 leading-relaxed mb-2">{style.description}</p>
<div className="flex items-center gap-2">
<span className={`text-[10px] uppercase font-mono tracking-wider px-1.5 py-0.5 rounded
${selectedStyle === style.id ? 'bg-indigo-500/20 text-indigo-300' : 'bg-zinc-800 text-zinc-500'}`}>
${selectedStyle === style.id ? 'bg-indigo-500/20 text-indigo-300' : 'bg-zinc-800 text-zinc-400'}`}>
{style.category}
</span>
</div>
@@ -430,7 +430,7 @@ export const StyleSelector: React.FC<StyleSelectorProps> = ({
<div className="w-3 h-3 rounded-full bg-emerald-500/20 border border-emerald-500/50"></div>
</div>
<div className="h-4 w-px bg-zinc-800 mx-2"></div>
<span className="text-xs text-zinc-500 font-medium">Live Preview</span>
<span className="text-xs text-zinc-400 font-medium">Live Preview</span>
</div>
{currentStyleObj && (
<span className="text-xs text-indigo-400 font-mono">{currentStyleObj.name}</span>
@@ -446,7 +446,7 @@ export const StyleSelector: React.FC<StyleSelectorProps> = ({
title="Style Preview"
/>
) : (
<div className="absolute inset-0 flex flex-col items-center justify-center text-zinc-600">
<div className="absolute inset-0 flex flex-col items-center justify-center text-zinc-400">
<Search size={48} className="mb-4 opacity-20" />
<p className="text-lg font-medium">Select a style to preview</p>
<p className="text-sm">Choose from the list on the left</p>

View File

@@ -32,14 +32,14 @@
@layer base {
:root {
font-size: 16px;
font-size: 100%;
}
body {
font-family: 'Inter', sans-serif;
background-color: #09090b;
color: #e4e4e7;
overflow: hidden;
overflow-x: hidden;
margin: 0;
padding: 0;
}
@@ -47,7 +47,7 @@
#root {
width: 100vw;
height: 100vh;
overflow: hidden;
overflow-x: hidden;
}
}
@@ -104,7 +104,37 @@ body ::-webkit-scrollbar-thumb:hover,
}
.focus-ring-spacing:focus-within {
outline: 2px solid rgba(99, 102, 241, 0.3);
outline: 2px solid rgba(99, 102, 241, 0.8);
outline-offset: -2px;
border-radius: 8px;
}
/* Reduced motion */
@media (prefers-reduced-motion: reduce) {
*, *::before, *::after {
animation-duration: 0.01ms !important;
animation-iteration-count: 1 !important;
transition-duration: 0.01ms !important;
scroll-behavior: auto !important;
}
}
/* Forced colors / high contrast mode */
@media (forced-colors: active) {
.focus-ring-spacing:focus-within {
outline: 2px solid LinkText;
}
}
/* Screen reader only utility */
.sr-only {
position: absolute;
width: 1px;
height: 1px;
padding: 0;
margin: -1px;
overflow: hidden;
clip: rect(0, 0, 0, 0);
white-space: nowrap;
border-width: 0;
}