mirror of
https://github.com/snapotter-hq/SnapOtter.git
synced 2026-08-03 07:46:42 +02:00
fix(a11y): focus indicators meet the 3:1 non-text contrast bar (#574)
Fixes #568. Adds a real --color-ring token (ink orange #A85518 light / #F0A766 dark) and sweeps all 57 focus-indicator occurrences onto it: soft opacity rings blended to 1.2-1.7:1, border-only indicators sat at 2.6-3.0:1, and four focus:ring-ring sites referenced a token that never existed. Sponsor button keeps pink via pink-700; range sliders and the file list gain their missing keyboard indicators; landing skip link and form borders hardened; the palette contrast guard pins the ring at 3:1 in both themes.
This commit is contained in:
@@ -45,7 +45,7 @@ function renderIcon(iconName: string): string {
|
|||||||
id="hero-tool-search"
|
id="hero-tool-search"
|
||||||
autocomplete="off"
|
autocomplete="off"
|
||||||
placeholder={t(locale, "home.hero.searchPlaceholder")}
|
placeholder={t(locale, "home.hero.searchPlaceholder")}
|
||||||
class="w-full rounded-2xl border border-border bg-surface py-4 pe-4 ps-12 text-base shadow-sm outline-none transition-all placeholder:text-muted focus:border-primary focus:shadow-md"
|
class="w-full rounded-2xl border border-border bg-surface py-4 pe-4 ps-12 text-base shadow-sm outline-none transition-all placeholder:text-muted focus:border-primary-ink focus:shadow-md"
|
||||||
aria-label={t(locale, "home.hero.searchPlaceholder")}
|
aria-label={t(locale, "home.hero.searchPlaceholder")}
|
||||||
aria-expanded="false"
|
aria-expanded="false"
|
||||||
aria-controls="hero-search-results"
|
aria-controls="hero-search-results"
|
||||||
|
|||||||
@@ -220,7 +220,7 @@ const safeStringsJson = JSON.stringify(toolGridStrings).replace(/</g, "\\u003c")
|
|||||||
autocomplete="off"
|
autocomplete="off"
|
||||||
aria-label={t(locale, "home.toolGrid.searchLabel")}
|
aria-label={t(locale, "home.toolGrid.searchLabel")}
|
||||||
placeholder={t(locale, "home.toolGrid.searchPlaceholder")}
|
placeholder={t(locale, "home.toolGrid.searchPlaceholder")}
|
||||||
class="h-13 w-full rounded-lg border border-border bg-surface py-3 ps-12 pe-12 text-base text-foreground outline-none transition-colors placeholder:text-muted focus:border-primary"
|
class="h-13 w-full rounded-lg border border-border bg-surface py-3 ps-12 pe-12 text-base text-foreground outline-none transition-colors placeholder:text-muted focus:border-primary-ink"
|
||||||
/>
|
/>
|
||||||
<button
|
<button
|
||||||
id="tool-command-clear"
|
id="tool-command-clear"
|
||||||
|
|||||||
@@ -103,22 +103,22 @@ const subjects = [
|
|||||||
>
|
>
|
||||||
<div>
|
<div>
|
||||||
<label for="name" class="block text-sm font-medium">{t(locale, "contact.form.name")}</label>
|
<label for="name" class="block text-sm font-medium">{t(locale, "contact.form.name")}</label>
|
||||||
<input type="text" id="name" name="name" required class="mt-1.5 w-full rounded-lg border border-border bg-background px-4 py-2.5 text-sm transition-colors focus:border-primary focus:outline-none" />
|
<input type="text" id="name" name="name" required class="mt-1.5 w-full rounded-lg border border-border bg-background px-4 py-2.5 text-sm transition-colors focus:border-primary-ink focus:outline-none" />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<label for="email" class="block text-sm font-medium">{t(locale, "contact.form.email")}</label>
|
<label for="email" class="block text-sm font-medium">{t(locale, "contact.form.email")}</label>
|
||||||
<input type="email" id="email" name="email" required class="mt-1.5 w-full rounded-lg border border-border bg-background px-4 py-2.5 text-sm transition-colors focus:border-primary focus:outline-none" />
|
<input type="email" id="email" name="email" required class="mt-1.5 w-full rounded-lg border border-border bg-background px-4 py-2.5 text-sm transition-colors focus:border-primary-ink focus:outline-none" />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<label for="company" class="block text-sm font-medium">{t(locale, "contact.form.company")}</label>
|
<label for="company" class="block text-sm font-medium">{t(locale, "contact.form.company")}</label>
|
||||||
<input type="text" id="company" name="company" class="mt-1.5 w-full rounded-lg border border-border bg-background px-4 py-2.5 text-sm transition-colors focus:border-primary focus:outline-none" />
|
<input type="text" id="company" name="company" class="mt-1.5 w-full rounded-lg border border-border bg-background px-4 py-2.5 text-sm transition-colors focus:border-primary-ink focus:outline-none" />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<label for="subject" class="block text-sm font-medium">{t(locale, "contact.form.subject")}</label>
|
<label for="subject" class="block text-sm font-medium">{t(locale, "contact.form.subject")}</label>
|
||||||
<select id="subject" name="subject" required class="mt-1.5 w-full rounded-lg border border-border bg-background px-4 py-2.5 text-sm transition-colors focus:border-primary focus:outline-none">
|
<select id="subject" name="subject" required class="mt-1.5 w-full rounded-lg border border-border bg-background px-4 py-2.5 text-sm transition-colors focus:border-primary-ink focus:outline-none">
|
||||||
<option value="" disabled selected>{t(locale, "contact.form.selectTopic")}</option>
|
<option value="" disabled selected>{t(locale, "contact.form.selectTopic")}</option>
|
||||||
{subjects.map((s) => (
|
{subjects.map((s) => (
|
||||||
<option value={s}>{s}</option>
|
<option value={s}>{s}</option>
|
||||||
@@ -128,7 +128,7 @@ const subjects = [
|
|||||||
|
|
||||||
<div>
|
<div>
|
||||||
<label for="message" class="block text-sm font-medium">{t(locale, "contact.form.message")}</label>
|
<label for="message" class="block text-sm font-medium">{t(locale, "contact.form.message")}</label>
|
||||||
<textarea id="message" name="message" rows={5} required class="mt-1.5 w-full rounded-lg border border-border bg-background px-4 py-2.5 text-sm transition-colors focus:border-primary focus:outline-none resize-none"></textarea>
|
<textarea id="message" name="message" rows={5} required class="mt-1.5 w-full rounded-lg border border-border bg-background px-4 py-2.5 text-sm transition-colors focus:border-primary-ink focus:outline-none resize-none"></textarea>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<button type="submit" class="btn-primary w-full rounded-lg px-6 py-3 text-sm font-semibold">
|
<button type="submit" class="btn-primary w-full rounded-lg px-6 py-3 text-sm font-semibold">
|
||||||
|
|||||||
@@ -163,7 +163,7 @@ const collectionJsonLd = {
|
|||||||
type="text"
|
type="text"
|
||||||
autocomplete="off"
|
autocomplete="off"
|
||||||
placeholder={searchPlaceholder}
|
placeholder={searchPlaceholder}
|
||||||
class="w-full rounded-xl border border-border bg-surface py-2.5 pe-4 ps-11 text-sm outline-none transition-colors placeholder:text-muted focus:border-primary"
|
class="w-full rounded-xl border border-border bg-surface py-2.5 pe-4 ps-11 text-sm outline-none transition-colors placeholder:text-muted focus:border-primary-ink"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div class="mt-3 flex flex-wrap gap-2">
|
<div class="mt-3 flex flex-wrap gap-2">
|
||||||
|
|||||||
@@ -351,6 +351,8 @@ code, pre, kbd {
|
|||||||
}
|
}
|
||||||
.skip-link:focus {
|
.skip-link:focus {
|
||||||
top: 0;
|
top: 0;
|
||||||
|
outline: 2px solid var(--color-primary-ink-strong);
|
||||||
|
outline-offset: 2px;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ─── STEP CONNECTOR (How It Works) ─── */
|
/* ─── STEP CONNECTOR (How It Works) ─── */
|
||||||
|
|||||||
@@ -312,7 +312,7 @@ export function Dropzone({
|
|||||||
}}
|
}}
|
||||||
onClick={(e) => e.stopPropagation()}
|
onClick={(e) => e.stopPropagation()}
|
||||||
placeholder={t.dropzone.urlPlaceholder}
|
placeholder={t.dropzone.urlPlaceholder}
|
||||||
className="flex-1 rounded-md border border-border bg-background px-3 py-1.5 text-sm text-foreground placeholder:text-muted-foreground focus:border-primary focus:outline-none"
|
className="flex-1 rounded-md border border-border bg-background px-3 py-1.5 text-sm text-foreground placeholder:text-muted-foreground focus:border-ring focus:outline-none"
|
||||||
disabled={urlLoading}
|
disabled={urlLoading}
|
||||||
/>
|
/>
|
||||||
<button
|
<button
|
||||||
|
|||||||
@@ -183,7 +183,7 @@ export function FileLibraryModal({ open, onClose, onImport }: FileLibraryModalPr
|
|||||||
placeholder="Search files..."
|
placeholder="Search files..."
|
||||||
value={searchQuery}
|
value={searchQuery}
|
||||||
onChange={handleSearchChange}
|
onChange={handleSearchChange}
|
||||||
className="w-full ps-8 pe-3 py-1.5 text-sm bg-muted rounded-lg border border-border focus:outline-none focus:ring-2 focus:ring-primary/50 text-foreground placeholder:text-muted-foreground"
|
className="w-full ps-8 pe-3 py-1.5 text-sm bg-muted rounded-lg border border-border focus:outline-none focus:ring-2 focus:ring-ring text-foreground placeholder:text-muted-foreground"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ export function SearchBar({ value, onChange, placeholder }: SearchBarProps) {
|
|||||||
onChange={(e) => onChange(e.target.value)}
|
onChange={(e) => onChange(e.target.value)}
|
||||||
placeholder={resolvedPlaceholder}
|
placeholder={resolvedPlaceholder}
|
||||||
aria-label={resolvedPlaceholder}
|
aria-label={resolvedPlaceholder}
|
||||||
className="w-full ps-10 pe-4 py-2 rounded-lg border border-border bg-background text-sm text-foreground focus:outline-none focus:ring-2 focus:ring-primary/20"
|
className="w-full ps-10 pe-4 py-2 rounded-lg border border-border bg-background text-sm text-foreground focus:outline-none focus:ring-2 focus:ring-ring"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -135,7 +135,7 @@ export function UrlImportModal({ onClose, onImport }: UrlImportModalProps) {
|
|||||||
placeholder={
|
placeholder={
|
||||||
"https://example.com/photo.jpg\nhttps://example.com/report.pdf\n- https://example.com/clip.mp4\n[My file](https://example.com/recording.mp3)"
|
"https://example.com/photo.jpg\nhttps://example.com/report.pdf\n- https://example.com/clip.mp4\n[My file](https://example.com/recording.mp3)"
|
||||||
}
|
}
|
||||||
className="w-full min-h-[120px] max-h-[240px] resize-y rounded-lg border border-border bg-muted px-3 py-2 text-sm font-mono text-foreground placeholder:text-muted-foreground focus:outline-none focus:ring-2 focus:ring-primary/50"
|
className="w-full min-h-[120px] max-h-[240px] resize-y rounded-lg border border-border bg-muted px-3 py-2 text-sm font-mono text-foreground placeholder:text-muted-foreground focus:outline-none focus:ring-2 focus:ring-ring"
|
||||||
/>
|
/>
|
||||||
<p className="text-xs text-muted-foreground">{t.urlImport.placeholder}</p>
|
<p className="text-xs text-muted-foreground">{t.urlImport.placeholder}</p>
|
||||||
|
|
||||||
|
|||||||
@@ -47,7 +47,7 @@ export function CanvasResizeDialog({ open, onClose }: { open: boolean; onClose:
|
|||||||
|
|
||||||
const inputCn = cn(
|
const inputCn = cn(
|
||||||
"h-8 w-full rounded border border-border bg-card px-2 text-sm text-foreground",
|
"h-8 w-full rounded border border-border bg-card px-2 text-sm text-foreground",
|
||||||
"focus:border-primary focus:outline-none",
|
"focus:border-ring focus:outline-none",
|
||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -144,7 +144,7 @@ export function CanvasResizeDialog({ open, onClose }: { open: boolean; onClose:
|
|||||||
onChange={(e) => setFill(e.target.value)}
|
onChange={(e) => setFill(e.target.value)}
|
||||||
className={cn(
|
className={cn(
|
||||||
"h-7 w-20 rounded border border-border bg-card px-1.5 text-xs text-foreground",
|
"h-7 w-20 rounded border border-border bg-card px-1.5 text-xs text-foreground",
|
||||||
"focus:border-primary focus:outline-none",
|
"focus:border-ring focus:outline-none",
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -52,7 +52,7 @@ export function ColorSwatch({
|
|||||||
"relative rounded transition-shadow shrink-0",
|
"relative rounded transition-shadow shrink-0",
|
||||||
SIZE_CLASSES[size],
|
SIZE_CLASSES[size],
|
||||||
showBorder && "border border-border",
|
showBorder && "border border-border",
|
||||||
active && "ring-2 ring-primary ring-offset-1 ring-offset-card",
|
active && "ring-2 ring-primary-ink ring-offset-1 ring-offset-card",
|
||||||
onClick && "cursor-pointer hover:ring-1 hover:ring-primary/50",
|
onClick && "cursor-pointer hover:ring-1 hover:ring-primary/50",
|
||||||
!onClick && "cursor-default",
|
!onClick && "cursor-default",
|
||||||
className,
|
className,
|
||||||
|
|||||||
@@ -530,7 +530,7 @@ export function ExportDialog({ onClose }: { onClose: () => void }) {
|
|||||||
type="number"
|
type="number"
|
||||||
value={settings.width}
|
value={settings.width}
|
||||||
onChange={(e) => handleWidthChange(Number.parseInt(e.target.value, 10) || 1)}
|
onChange={(e) => handleWidthChange(Number.parseInt(e.target.value, 10) || 1)}
|
||||||
className="w-full px-2 py-1 text-xs bg-muted rounded border border-border text-foreground outline-none focus:border-primary"
|
className="w-full px-2 py-1 text-xs bg-muted rounded border border-border text-foreground outline-none focus:border-ring"
|
||||||
min={1}
|
min={1}
|
||||||
/>
|
/>
|
||||||
<span className="text-[10px] text-muted-foreground">Width</span>
|
<span className="text-[10px] text-muted-foreground">Width</span>
|
||||||
@@ -553,7 +553,7 @@ export function ExportDialog({ onClose }: { onClose: () => void }) {
|
|||||||
type="number"
|
type="number"
|
||||||
value={settings.height}
|
value={settings.height}
|
||||||
onChange={(e) => handleHeightChange(Number.parseInt(e.target.value, 10) || 1)}
|
onChange={(e) => handleHeightChange(Number.parseInt(e.target.value, 10) || 1)}
|
||||||
className="w-full px-2 py-1 text-xs bg-muted rounded border border-border text-foreground outline-none focus:border-primary"
|
className="w-full px-2 py-1 text-xs bg-muted rounded border border-border text-foreground outline-none focus:border-ring"
|
||||||
min={1}
|
min={1}
|
||||||
/>
|
/>
|
||||||
<span className="text-[10px] text-muted-foreground">Height</span>
|
<span className="text-[10px] text-muted-foreground">Height</span>
|
||||||
|
|||||||
@@ -58,7 +58,7 @@ export function ImageResizeDialog({ open, onClose }: { open: boolean; onClose: (
|
|||||||
|
|
||||||
const inputCn = cn(
|
const inputCn = cn(
|
||||||
"h-8 w-full rounded border border-border bg-card px-2 text-sm text-foreground",
|
"h-8 w-full rounded border border-border bg-card px-2 text-sm text-foreground",
|
||||||
"focus:border-primary focus:outline-none",
|
"focus:border-ring focus:outline-none",
|
||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|||||||
@@ -77,7 +77,7 @@ export function CropOptions() {
|
|||||||
|
|
||||||
const inputCn = cn(
|
const inputCn = cn(
|
||||||
"h-6 w-16 rounded border border-border bg-card px-1.5 text-xs text-foreground",
|
"h-6 w-16 rounded border border-border bg-card px-1.5 text-xs text-foreground",
|
||||||
"focus:border-primary focus:outline-none",
|
"focus:border-ring focus:outline-none",
|
||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -93,7 +93,7 @@ export function CropOptions() {
|
|||||||
onChange={handleAspectChange}
|
onChange={handleAspectChange}
|
||||||
className={cn(
|
className={cn(
|
||||||
"h-6 rounded border border-border bg-card px-1.5 text-xs text-foreground",
|
"h-6 rounded border border-border bg-card px-1.5 text-xs text-foreground",
|
||||||
"focus:border-primary focus:outline-none",
|
"focus:border-ring focus:outline-none",
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
{ASPECT_RATIOS.map((r) => (
|
{ASPECT_RATIOS.map((r) => (
|
||||||
|
|||||||
@@ -208,7 +208,7 @@ export function SelectionOptions() {
|
|||||||
onChange={(e) => setMagicWandTolerance(Number(e.target.value))}
|
onChange={(e) => setMagicWandTolerance(Number(e.target.value))}
|
||||||
className={cn(
|
className={cn(
|
||||||
"h-6 w-14 rounded border border-border bg-card px-1.5 text-xs text-foreground",
|
"h-6 w-14 rounded border border-border bg-card px-1.5 text-xs text-foreground",
|
||||||
"focus:border-primary focus:outline-none",
|
"focus:border-ring focus:outline-none",
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -48,7 +48,7 @@ function NumericInput({
|
|||||||
step={step ?? 1}
|
step={step ?? 1}
|
||||||
className={cn(
|
className={cn(
|
||||||
"h-6 w-16 rounded border border-border bg-card px-1.5 text-xs text-foreground",
|
"h-6 w-16 rounded border border-border bg-card px-1.5 text-xs text-foreground",
|
||||||
"focus:border-primary focus:outline-none",
|
"focus:border-ring focus:outline-none",
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -128,7 +128,7 @@ function ColorInputFields({
|
|||||||
className={cn(
|
className={cn(
|
||||||
"flex-1 px-1.5 py-0.5 text-xs font-mono rounded",
|
"flex-1 px-1.5 py-0.5 text-xs font-mono rounded",
|
||||||
"bg-muted border border-border text-foreground",
|
"bg-muted border border-border text-foreground",
|
||||||
"focus:outline-none focus:ring-1 focus:ring-primary",
|
"focus:outline-none focus:ring-1 focus:ring-ring",
|
||||||
)}
|
)}
|
||||||
maxLength={7}
|
maxLength={7}
|
||||||
spellCheck={false}
|
spellCheck={false}
|
||||||
@@ -160,7 +160,7 @@ function ColorInputFields({
|
|||||||
className={cn(
|
className={cn(
|
||||||
"w-full px-1 py-0.5 text-xs font-mono rounded",
|
"w-full px-1 py-0.5 text-xs font-mono rounded",
|
||||||
"bg-muted border border-border text-foreground",
|
"bg-muted border border-border text-foreground",
|
||||||
"focus:outline-none focus:ring-1 focus:ring-primary",
|
"focus:outline-none focus:ring-1 focus:ring-ring",
|
||||||
)}
|
)}
|
||||||
aria-label={`${ch.toUpperCase()} color channel`}
|
aria-label={`${ch.toUpperCase()} color channel`}
|
||||||
data-testid={`color-${ch}-input`}
|
data-testid={`color-${ch}-input`}
|
||||||
@@ -193,7 +193,7 @@ function ColorInputFields({
|
|||||||
className={cn(
|
className={cn(
|
||||||
"w-full px-1 py-0.5 text-xs font-mono rounded",
|
"w-full px-1 py-0.5 text-xs font-mono rounded",
|
||||||
"bg-muted border border-border text-foreground",
|
"bg-muted border border-border text-foreground",
|
||||||
"focus:outline-none focus:ring-1 focus:ring-primary",
|
"focus:outline-none focus:ring-1 focus:ring-ring",
|
||||||
)}
|
)}
|
||||||
aria-label={`${ch.toUpperCase()} color channel`}
|
aria-label={`${ch.toUpperCase()} color channel`}
|
||||||
data-testid={`color-${ch}-input`}
|
data-testid={`color-${ch}-input`}
|
||||||
@@ -362,7 +362,7 @@ export function ColorPanel() {
|
|||||||
onClick={() => setPickerTarget(pickerTarget === "bg" ? null : "bg")}
|
onClick={() => setPickerTarget(pickerTarget === "bg" ? null : "bg")}
|
||||||
className={cn(
|
className={cn(
|
||||||
"absolute right-0 bottom-0 w-7 h-7 rounded border-2 transition-shadow",
|
"absolute right-0 bottom-0 w-7 h-7 rounded border-2 transition-shadow",
|
||||||
pickerTarget === "bg" ? "border-primary ring-1 ring-primary" : "border-border",
|
pickerTarget === "bg" ? "border-primary ring-1 ring-primary-ink" : "border-border",
|
||||||
)}
|
)}
|
||||||
style={{ backgroundColor: backgroundColor }}
|
style={{ backgroundColor: backgroundColor }}
|
||||||
aria-label={t.a11y.backgroundColor}
|
aria-label={t.a11y.backgroundColor}
|
||||||
@@ -374,7 +374,7 @@ export function ColorPanel() {
|
|||||||
onClick={() => setPickerTarget(pickerTarget === "fg" ? null : "fg")}
|
onClick={() => setPickerTarget(pickerTarget === "fg" ? null : "fg")}
|
||||||
className={cn(
|
className={cn(
|
||||||
"absolute left-0 top-0 w-7 h-7 rounded border-2 z-10 transition-shadow",
|
"absolute left-0 top-0 w-7 h-7 rounded border-2 z-10 transition-shadow",
|
||||||
pickerTarget === "fg" ? "border-primary ring-1 ring-primary" : "border-border",
|
pickerTarget === "fg" ? "border-primary ring-1 ring-primary-ink" : "border-border",
|
||||||
)}
|
)}
|
||||||
style={{ backgroundColor: foregroundColor }}
|
style={{ backgroundColor: foregroundColor }}
|
||||||
aria-label={t.a11y.foregroundColor}
|
aria-label={t.a11y.foregroundColor}
|
||||||
@@ -425,7 +425,7 @@ export function ColorPanel() {
|
|||||||
className={cn(
|
className={cn(
|
||||||
"w-full px-2 py-1 text-xs font-mono rounded",
|
"w-full px-2 py-1 text-xs font-mono rounded",
|
||||||
"bg-muted border border-border text-foreground",
|
"bg-muted border border-border text-foreground",
|
||||||
"focus:outline-none focus:ring-1 focus:ring-primary",
|
"focus:outline-none focus:ring-1 focus:ring-ring",
|
||||||
)}
|
)}
|
||||||
maxLength={7}
|
maxLength={7}
|
||||||
spellCheck={false}
|
spellCheck={false}
|
||||||
|
|||||||
@@ -273,7 +273,7 @@ function BlendModeSelect({ value, onChange }: { value: string; onChange: (mode:
|
|||||||
id="blend-mode-select"
|
id="blend-mode-select"
|
||||||
value={value}
|
value={value}
|
||||||
onChange={(e) => onChange(e.target.value)}
|
onChange={(e) => onChange(e.target.value)}
|
||||||
className="flex-1 min-w-0 h-7 text-xs bg-muted border border-border rounded px-1.5 text-foreground focus:outline-none focus:ring-1 focus:ring-primary"
|
className="flex-1 min-w-0 h-7 text-xs bg-muted border border-border rounded px-1.5 text-foreground focus:outline-none focus:ring-1 focus:ring-ring"
|
||||||
data-testid="blend-mode-select"
|
data-testid="blend-mode-select"
|
||||||
>
|
>
|
||||||
{BLEND_MODES.map((mode) => (
|
{BLEND_MODES.map((mode) => (
|
||||||
@@ -542,7 +542,7 @@ function LayerRow({
|
|||||||
onBlur={commitRename}
|
onBlur={commitRename}
|
||||||
onKeyDown={handleKeyDown}
|
onKeyDown={handleKeyDown}
|
||||||
onPointerDown={(e) => e.stopPropagation()}
|
onPointerDown={(e) => e.stopPropagation()}
|
||||||
className="w-full text-xs bg-muted border border-border rounded px-1 py-0.5 text-foreground outline-none focus:ring-1 focus:ring-primary"
|
className="w-full text-xs bg-muted border border-border rounded px-1 py-0.5 text-foreground outline-none focus:ring-1 focus:ring-ring"
|
||||||
data-testid={`layer-name-input-${layer.id}`}
|
data-testid={`layer-name-input-${layer.id}`}
|
||||||
/>
|
/>
|
||||||
) : (
|
) : (
|
||||||
@@ -963,7 +963,7 @@ function StrokeControls({
|
|||||||
position: e.target.value as "inside" | "center" | "outside",
|
position: e.target.value as "inside" | "center" | "outside",
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
className="flex-1 h-6 text-[10px] bg-muted border border-border rounded px-1 text-foreground focus:outline-none focus:ring-1 focus:ring-primary"
|
className="flex-1 h-6 text-[10px] bg-muted border border-border rounded px-1 text-foreground focus:outline-none focus:ring-1 focus:ring-ring"
|
||||||
>
|
>
|
||||||
<option value="inside">Inside</option>
|
<option value="inside">Inside</option>
|
||||||
<option value="center">Center</option>
|
<option value="center">Center</option>
|
||||||
|
|||||||
@@ -96,7 +96,7 @@ export function FileList({ filterMimePrefix }: { filterMimePrefix?: string }) {
|
|||||||
placeholder={t.files.searchPlaceholder}
|
placeholder={t.files.searchPlaceholder}
|
||||||
value={inputValue}
|
value={inputValue}
|
||||||
onChange={handleSearchChange}
|
onChange={handleSearchChange}
|
||||||
className="w-full ps-8 pe-3 py-1.5 text-sm bg-muted rounded-lg border border-border focus:outline-none focus:ring-2 focus:ring-primary/50 text-foreground placeholder:text-muted-foreground"
|
className="w-full ps-8 pe-3 py-1.5 text-sm bg-muted rounded-lg border border-border focus:outline-none focus:ring-2 focus:ring-ring text-foreground placeholder:text-muted-foreground"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -142,7 +142,7 @@ export function FileList({ filterMimePrefix }: { filterMimePrefix?: string }) {
|
|||||||
role="listbox"
|
role="listbox"
|
||||||
tabIndex={0}
|
tabIndex={0}
|
||||||
onKeyDown={handleListKeyDown}
|
onKeyDown={handleListKeyDown}
|
||||||
className="flex-1 overflow-y-auto p-2 space-y-0.5 focus:outline-none"
|
className="flex-1 overflow-y-auto p-2 space-y-0.5 focus:outline-none focus-visible:ring-2 focus-visible:ring-ring"
|
||||||
>
|
>
|
||||||
{loading && (
|
{loading && (
|
||||||
<div className="flex items-center justify-center h-32">
|
<div className="flex items-center justify-center h-32">
|
||||||
|
|||||||
@@ -329,7 +329,7 @@ function SponsorButton({ isDark, compact = false }: { isDark: boolean; compact?:
|
|||||||
rel="noopener noreferrer"
|
rel="noopener noreferrer"
|
||||||
onClick={handleClick}
|
onClick={handleClick}
|
||||||
className={cn(
|
className={cn(
|
||||||
"ms-1 inline-flex items-center gap-1.5 rounded-md border px-3 py-1.5 text-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-pink-300 focus-visible:ring-offset-1",
|
"ms-1 inline-flex items-center gap-1.5 rounded-md border px-3 py-1.5 text-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-pink-700 dark:focus-visible:ring-pink-300 focus-visible:ring-offset-1",
|
||||||
isDark
|
isDark
|
||||||
? "border-pink-800 bg-pink-950/30 text-pink-300 hover:bg-pink-950/50"
|
? "border-pink-800 bg-pink-950/30 text-pink-300 hover:bg-pink-950/50"
|
||||||
: "border-pink-200 bg-pink-50 text-pink-700 hover:bg-pink-100",
|
: "border-pink-200 bg-pink-50 text-pink-700 hover:bg-pink-100",
|
||||||
|
|||||||
@@ -760,7 +760,7 @@ export function BeautifyControls({
|
|||||||
onClick={() => applyPreset(preset)}
|
onClick={() => applyPreset(preset)}
|
||||||
className={`flex flex-col items-center gap-1 py-1.5 px-1 rounded transition-colors ${
|
className={`flex flex-col items-center gap-1 py-1.5 px-1 rounded transition-colors ${
|
||||||
selectedPreset === preset.name
|
selectedPreset === preset.name
|
||||||
? "bg-primary/10 ring-1 ring-primary"
|
? "bg-primary/10 ring-1 ring-primary-ink"
|
||||||
: "bg-muted hover:bg-muted/80"
|
: "bg-muted hover:bg-muted/80"
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -159,7 +159,7 @@ function ColorSwatches({
|
|||||||
onClick={() => onChange(swatch)}
|
onClick={() => onChange(swatch)}
|
||||||
className={`h-6 w-6 rounded-full border-2 transition-all ${
|
className={`h-6 w-6 rounded-full border-2 transition-all ${
|
||||||
value.toUpperCase() === swatch
|
value.toUpperCase() === swatch
|
||||||
? "ring-2 ring-primary ring-offset-1 border-transparent"
|
? "ring-2 ring-primary-ink ring-offset-1 border-transparent"
|
||||||
: "border-border hover:border-foreground/30"
|
: "border-border hover:border-foreground/30"
|
||||||
}`}
|
}`}
|
||||||
style={{ backgroundColor: swatch }}
|
style={{ backgroundColor: swatch }}
|
||||||
|
|||||||
@@ -457,8 +457,8 @@ function CollageCell({
|
|||||||
aria-label={`Collage cell ${cellIndex + 1}`}
|
aria-label={`Collage cell ${cellIndex + 1}`}
|
||||||
className={cn(
|
className={cn(
|
||||||
"relative overflow-hidden transition-shadow",
|
"relative overflow-hidden transition-shadow",
|
||||||
isSelected && "ring-2 ring-primary ring-offset-1",
|
isSelected && "ring-2 ring-primary-ink ring-offset-1",
|
||||||
isOver && !isSelected && "ring-2 ring-primary",
|
isOver && !isSelected && "ring-2 ring-primary-ink",
|
||||||
!image && "border-2 border-dashed border-border/50",
|
!image && "border-2 border-dashed border-border/50",
|
||||||
image && !isSelected && "cursor-grab",
|
image && !isSelected && "cursor-grab",
|
||||||
image && isSelected && "cursor-grab active:cursor-grabbing",
|
image && isSelected && "cursor-grab active:cursor-grabbing",
|
||||||
|
|||||||
@@ -266,7 +266,7 @@ export function CollageSettings() {
|
|||||||
onClick={() => store.setBgPreset(p.id)}
|
onClick={() => store.setBgPreset(p.id)}
|
||||||
className={cn(
|
className={cn(
|
||||||
"w-7 h-7 rounded-md transition-all",
|
"w-7 h-7 rounded-md transition-all",
|
||||||
bgPreset === p.id && "ring-2 ring-primary ring-offset-1",
|
bgPreset === p.id && "ring-2 ring-primary-ink ring-offset-1",
|
||||||
p.border && "border border-border",
|
p.border && "border border-border",
|
||||||
)}
|
)}
|
||||||
style={{
|
style={{
|
||||||
|
|||||||
@@ -106,7 +106,7 @@ export function DuotoneSettings({ onImageStyle, onImageOverlay }: DuotoneSetting
|
|||||||
}}
|
}}
|
||||||
className={`flex flex-col items-center gap-1 py-1.5 px-1 rounded transition-colors ${
|
className={`flex flex-col items-center gap-1 py-1.5 px-1 rounded transition-colors ${
|
||||||
shadow === p.shadow && highlight === p.highlight
|
shadow === p.shadow && highlight === p.highlight
|
||||||
? "bg-primary/10 ring-1 ring-primary"
|
? "bg-primary/10 ring-1 ring-primary-ink"
|
||||||
: "bg-muted hover:bg-muted/80"
|
: "bg-muted hover:bg-muted/80"
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -124,7 +124,7 @@ function LabeledInput({
|
|||||||
onChange={(e) => onChange(e.target.value)}
|
onChange={(e) => onChange(e.target.value)}
|
||||||
placeholder={placeholder}
|
placeholder={placeholder}
|
||||||
disabled={disabled}
|
disabled={disabled}
|
||||||
className="w-full px-2.5 py-1.5 rounded-md border border-input bg-background text-sm text-foreground placeholder:text-muted-foreground focus:outline-none focus:ring-1 focus:ring-ring disabled:opacity-50"
|
className="w-full px-2.5 py-1.5 rounded-md border border-border bg-background text-sm text-foreground placeholder:text-muted-foreground focus:outline-none focus:ring-1 focus:ring-ring disabled:opacity-50"
|
||||||
/>
|
/>
|
||||||
{hint && <p className="text-[10px] text-muted-foreground">{hint}</p>}
|
{hint && <p className="text-[10px] text-muted-foreground">{hint}</p>}
|
||||||
</div>
|
</div>
|
||||||
@@ -510,14 +510,14 @@ export function EditMetadataSettings() {
|
|||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
onClick={() => setField("dateMode", "edit")}
|
onClick={() => setField("dateMode", "edit")}
|
||||||
className={`flex-1 text-xs py-1.5 rounded-md border ${form.dateMode === "edit" ? "bg-primary text-primary-foreground border-primary" : "border-input text-foreground"}`}
|
className={`flex-1 text-xs py-1.5 rounded-md border ${form.dateMode === "edit" ? "bg-primary text-primary-foreground border-primary" : "border-border text-foreground"}`}
|
||||||
>
|
>
|
||||||
Edit Dates
|
Edit Dates
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
onClick={() => setField("dateMode", "shift")}
|
onClick={() => setField("dateMode", "shift")}
|
||||||
className={`flex-1 text-xs py-1.5 rounded-md border ${form.dateMode === "shift" ? "bg-primary text-primary-foreground border-primary" : "border-input text-foreground"}`}
|
className={`flex-1 text-xs py-1.5 rounded-md border ${form.dateMode === "shift" ? "bg-primary text-primary-foreground border-primary" : "border-border text-foreground"}`}
|
||||||
>
|
>
|
||||||
Shift All Dates
|
Shift All Dates
|
||||||
</button>
|
</button>
|
||||||
@@ -558,7 +558,7 @@ export function EditMetadataSettings() {
|
|||||||
id="em-date-shift-direction"
|
id="em-date-shift-direction"
|
||||||
value={form.dateShiftDirection}
|
value={form.dateShiftDirection}
|
||||||
onChange={(e) => setField("dateShiftDirection", e.target.value as "+" | "-")}
|
onChange={(e) => setField("dateShiftDirection", e.target.value as "+" | "-")}
|
||||||
className="px-2.5 py-1.5 rounded-md border border-input bg-background text-sm"
|
className="px-2.5 py-1.5 rounded-md border border-border bg-background text-sm"
|
||||||
>
|
>
|
||||||
<option value="+">+ Forward</option>
|
<option value="+">+ Forward</option>
|
||||||
<option value="-">- Backward</option>
|
<option value="-">- Backward</option>
|
||||||
@@ -685,12 +685,12 @@ export function EditMetadataSettings() {
|
|||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
placeholder="Add keyword and press Enter"
|
placeholder="Add keyword and press Enter"
|
||||||
className="flex-1 px-2.5 py-1.5 rounded-md border border-input bg-background text-sm text-foreground placeholder:text-muted-foreground focus:outline-none focus:ring-1 focus:ring-ring"
|
className="flex-1 px-2.5 py-1.5 rounded-md border border-border bg-background text-sm text-foreground placeholder:text-muted-foreground focus:outline-none focus:ring-1 focus:ring-ring"
|
||||||
/>
|
/>
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
onClick={addKeyword}
|
onClick={addKeyword}
|
||||||
className="px-2 py-1.5 rounded-md border border-input hover:bg-muted/50"
|
className="px-2 py-1.5 rounded-md border border-border hover:bg-muted/50"
|
||||||
>
|
>
|
||||||
<Plus className="h-3.5 w-3.5" />
|
<Plus className="h-3.5 w-3.5" />
|
||||||
</button>
|
</button>
|
||||||
@@ -776,7 +776,7 @@ export function EditMetadataSettings() {
|
|||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
onClick={() => loadTemplate(t.name)}
|
onClick={() => loadTemplate(t.name)}
|
||||||
className="flex-1 text-start text-xs px-2 py-1 rounded-md border border-input hover:bg-muted/50 truncate"
|
className="flex-1 text-start text-xs px-2 py-1 rounded-md border border-border hover:bg-muted/50 truncate"
|
||||||
>
|
>
|
||||||
{t.name}
|
{t.name}
|
||||||
</button>
|
</button>
|
||||||
@@ -797,13 +797,13 @@ export function EditMetadataSettings() {
|
|||||||
value={templateName}
|
value={templateName}
|
||||||
onChange={(e) => setTemplateName(e.target.value)}
|
onChange={(e) => setTemplateName(e.target.value)}
|
||||||
placeholder="Template name"
|
placeholder="Template name"
|
||||||
className="flex-1 px-2.5 py-1.5 rounded-md border border-input bg-background text-xs placeholder:text-muted-foreground focus:outline-none focus:ring-1 focus:ring-ring"
|
className="flex-1 px-2.5 py-1.5 rounded-md border border-border bg-background text-xs placeholder:text-muted-foreground focus:outline-none focus:ring-1 focus:ring-ring"
|
||||||
/>
|
/>
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
onClick={saveTemplate}
|
onClick={saveTemplate}
|
||||||
disabled={!templateName.trim()}
|
disabled={!templateName.trim()}
|
||||||
className="px-2 py-1.5 rounded-md border border-input hover:bg-muted/50 disabled:opacity-50"
|
className="px-2 py-1.5 rounded-md border border-border hover:bg-muted/50 disabled:opacity-50"
|
||||||
title="Save current values as template"
|
title="Save current values as template"
|
||||||
>
|
>
|
||||||
<BookmarkPlus className="h-3.5 w-3.5" />
|
<BookmarkPlus className="h-3.5 w-3.5" />
|
||||||
|
|||||||
@@ -605,7 +605,7 @@ export function PassportPhotoSettings() {
|
|||||||
value={searchQuery}
|
value={searchQuery}
|
||||||
onChange={(e) => setSearchQuery(e.target.value)}
|
onChange={(e) => setSearchQuery(e.target.value)}
|
||||||
placeholder="Search countries..."
|
placeholder="Search countries..."
|
||||||
className="w-full ps-7 pe-2 py-1.5 rounded border border-border bg-background text-xs text-foreground placeholder:text-muted-foreground focus:outline-none focus:border-primary"
|
className="w-full ps-7 pe-2 py-1.5 rounded border border-border bg-background text-xs text-foreground placeholder:text-muted-foreground focus:outline-none focus:border-ring"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -162,7 +162,7 @@ export function RotateControls({
|
|||||||
commitAngleInput();
|
commitAngleInput();
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
className="w-16 text-center text-sm font-mono font-medium tabular-nums py-1.5 rounded-md bg-background border border-border focus:outline-none focus:ring-2 focus:ring-primary/50 pe-4"
|
className="w-16 text-center text-sm font-mono font-medium tabular-nums py-1.5 rounded-md bg-background border border-border focus:outline-none focus:ring-2 focus:ring-ring pe-4"
|
||||||
/>
|
/>
|
||||||
<span className="absolute right-2 text-sm font-mono text-muted-foreground pointer-events-none">
|
<span className="absolute right-2 text-sm font-mono text-muted-foreground pointer-events-none">
|
||||||
°
|
°
|
||||||
|
|||||||
@@ -196,7 +196,7 @@ export function SignaturePad({ onSave, onCancel }: SignaturePadProps) {
|
|||||||
type="button"
|
type="button"
|
||||||
aria-label={`ink ${c}`}
|
aria-label={`ink ${c}`}
|
||||||
onClick={() => setColor(c)}
|
onClick={() => setColor(c)}
|
||||||
className={`h-5 w-5 rounded-full ${color === c ? "ring-2 ring-primary ring-offset-1" : ""}`}
|
className={`h-5 w-5 rounded-full ${color === c ? "ring-2 ring-primary-ink ring-offset-1" : ""}`}
|
||||||
style={{ background: c }}
|
style={{ background: c }}
|
||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
|
|||||||
@@ -157,7 +157,7 @@ export function ChangePasswordPage() {
|
|||||||
value={currentPassword}
|
value={currentPassword}
|
||||||
onChange={(e) => setCurrentPassword(e.target.value)}
|
onChange={(e) => setCurrentPassword(e.target.value)}
|
||||||
placeholder={t.changePassword.currentPasswordPlaceholder}
|
placeholder={t.changePassword.currentPasswordPlaceholder}
|
||||||
className="w-full px-4 py-3 rounded-lg border border-border bg-background text-foreground focus:outline-none focus:ring-2 focus:ring-primary/20"
|
className="w-full px-4 py-3 rounded-lg border border-border bg-background text-foreground focus:outline-none focus:ring-2 focus:ring-ring"
|
||||||
required
|
required
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
@@ -185,7 +185,7 @@ export function ChangePasswordPage() {
|
|||||||
setShowGenerated(false);
|
setShowGenerated(false);
|
||||||
}}
|
}}
|
||||||
placeholder={t.changePassword.newPasswordPlaceholder}
|
placeholder={t.changePassword.newPasswordPlaceholder}
|
||||||
className={`w-full px-4 py-3 rounded-lg border border-border bg-background text-foreground focus:outline-none focus:ring-2 focus:ring-primary/20 ${showGenerated ? "font-mono text-sm" : ""}`}
|
className={`w-full px-4 py-3 rounded-lg border border-border bg-background text-foreground focus:outline-none focus:ring-2 focus:ring-ring ${showGenerated ? "font-mono text-sm" : ""}`}
|
||||||
required
|
required
|
||||||
minLength={8}
|
minLength={8}
|
||||||
/>
|
/>
|
||||||
@@ -207,7 +207,7 @@ export function ChangePasswordPage() {
|
|||||||
setShowGenerated(false);
|
setShowGenerated(false);
|
||||||
}}
|
}}
|
||||||
placeholder={t.changePassword.confirmPasswordPlaceholder}
|
placeholder={t.changePassword.confirmPasswordPlaceholder}
|
||||||
className={`w-full px-4 py-3 rounded-lg border border-border bg-background text-foreground focus:outline-none focus:ring-2 focus:ring-primary/20 ${showGenerated ? "font-mono text-sm" : ""}`}
|
className={`w-full px-4 py-3 rounded-lg border border-border bg-background text-foreground focus:outline-none focus:ring-2 focus:ring-ring ${showGenerated ? "font-mono text-sm" : ""}`}
|
||||||
required
|
required
|
||||||
minLength={8}
|
minLength={8}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -250,7 +250,7 @@ function HomeSearchBar({
|
|||||||
}}
|
}}
|
||||||
placeholder={placeholder}
|
placeholder={placeholder}
|
||||||
aria-label={placeholder}
|
aria-label={placeholder}
|
||||||
className="w-full ps-11 pe-20 py-2.5 rounded-lg border border-border bg-card text-sm text-foreground placeholder:text-muted-foreground focus:outline-none focus:ring-2 focus:ring-primary/30 focus:border-primary/40 transition-shadow"
|
className="w-full ps-11 pe-20 py-2.5 rounded-lg border border-border bg-card text-sm text-foreground placeholder:text-muted-foreground focus:outline-none focus:ring-2 focus:ring-ring focus:border-ring transition-shadow"
|
||||||
/>
|
/>
|
||||||
{value ? (
|
{value ? (
|
||||||
<button
|
<button
|
||||||
|
|||||||
@@ -324,7 +324,7 @@ export function LoginPage() {
|
|||||||
onKeyDown={(e) => {
|
onKeyDown={(e) => {
|
||||||
if (e.key === "Enter" && mfaCode.length >= 6) handleMfaComplete();
|
if (e.key === "Enter" && mfaCode.length >= 6) handleMfaComplete();
|
||||||
}}
|
}}
|
||||||
className="w-full px-4 py-3 rounded-lg border border-border bg-background text-foreground text-center text-2xl font-mono tracking-[0.5em] focus:outline-none focus:ring-2 focus:ring-primary/20"
|
className="w-full px-4 py-3 rounded-lg border border-border bg-background text-foreground text-center text-2xl font-mono tracking-[0.5em] focus:outline-none focus:ring-2 focus:ring-ring"
|
||||||
/>
|
/>
|
||||||
{error && <p className="text-sm text-destructive">{error}</p>}
|
{error && <p className="text-sm text-destructive">{error}</p>}
|
||||||
<button
|
<button
|
||||||
@@ -369,7 +369,7 @@ export function LoginPage() {
|
|||||||
value={username}
|
value={username}
|
||||||
onChange={(e) => setUsername(e.target.value)}
|
onChange={(e) => setUsername(e.target.value)}
|
||||||
placeholder={t.auth.enterUsername}
|
placeholder={t.auth.enterUsername}
|
||||||
className="w-full px-4 py-3 rounded-lg border border-border bg-background text-foreground focus:outline-none focus:ring-2 focus:ring-primary/20"
|
className="w-full px-4 py-3 rounded-lg border border-border bg-background text-foreground focus:outline-none focus:ring-2 focus:ring-ring"
|
||||||
required
|
required
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
@@ -388,7 +388,7 @@ export function LoginPage() {
|
|||||||
value={password}
|
value={password}
|
||||||
onChange={(e) => setPassword(e.target.value)}
|
onChange={(e) => setPassword(e.target.value)}
|
||||||
placeholder={t.auth.enterPassword}
|
placeholder={t.auth.enterPassword}
|
||||||
className="w-full px-4 py-3 rounded-lg border border-border bg-background text-foreground focus:outline-none focus:ring-2 focus:ring-primary/20"
|
className="w-full px-4 py-3 rounded-lg border border-border bg-background text-foreground focus:outline-none focus:ring-2 focus:ring-ring"
|
||||||
required
|
required
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -33,6 +33,7 @@ body {
|
|||||||
--color-primary-subtle: #FFF5ED;
|
--color-primary-subtle: #FFF5ED;
|
||||||
--color-primary-ink: #A85518;
|
--color-primary-ink: #A85518;
|
||||||
--color-primary-ink-strong: #8F4712;
|
--color-primary-ink-strong: #8F4712;
|
||||||
|
--color-ring: #A85518;
|
||||||
--color-background: #FAFAF7;
|
--color-background: #FAFAF7;
|
||||||
--color-foreground: #1A1814;
|
--color-foreground: #1A1814;
|
||||||
--color-muted: #F3EEE6;
|
--color-muted: #F3EEE6;
|
||||||
@@ -66,6 +67,7 @@ body {
|
|||||||
--color-primary-ink-strong: #F09550;
|
--color-primary-ink-strong: #F09550;
|
||||||
--color-destructive-ink: #D9635E;
|
--color-destructive-ink: #D9635E;
|
||||||
--color-success-ink: #2D9D78;
|
--color-success-ink: #2D9D78;
|
||||||
|
--color-ring: #F0A766;
|
||||||
}
|
}
|
||||||
|
|
||||||
input[type="range"] {
|
input[type="range"] {
|
||||||
@@ -95,6 +97,12 @@ input[type="range"]::-webkit-slider-thumb:hover {
|
|||||||
transform: scale(1.15);
|
transform: scale(1.15);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* The base rule strips the UA outline, so keyboard users need a replacement. */
|
||||||
|
input[type="range"]:focus-visible {
|
||||||
|
outline: 2px solid var(--color-ring);
|
||||||
|
outline-offset: 2px;
|
||||||
|
}
|
||||||
|
|
||||||
input[type="range"]::-moz-range-thumb {
|
input[type="range"]::-moz-range-thumb {
|
||||||
width: 12px;
|
width: 12px;
|
||||||
height: 12px;
|
height: 12px;
|
||||||
|
|||||||
BIN
Binary file not shown.
|
Before Width: | Height: | Size: 93 KiB After Width: | Height: | Size: 93 KiB |
@@ -127,6 +127,20 @@ describe("web dark theme", () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe("focus ring (issue #568)", () => {
|
||||||
|
it("ring meets 3:1 non-text contrast (WCAG 1.4.11) on light surfaces", () => {
|
||||||
|
for (const bg of ["background", "card", "muted", "primary-subtle", "sidebar"]) {
|
||||||
|
expectPair(webLight, "ring", bg, 3);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
it("ring meets 3:1 non-text contrast on dark surfaces", () => {
|
||||||
|
for (const bg of ["background", "card", "muted", "sidebar"]) {
|
||||||
|
expectPair(webDark, "ring", bg, 3);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
describe("landing", () => {
|
describe("landing", () => {
|
||||||
it("ink text passes AA on light surfaces", () => {
|
it("ink text passes AA on light surfaces", () => {
|
||||||
for (const bg of ["background", "surface", "background-alt", "primary-subtle"]) {
|
for (const bg of ["background", "surface", "background-alt", "primary-subtle"]) {
|
||||||
|
|||||||
Reference in New Issue
Block a user