feat(frontend): replace emoji with inline Material Symbols icon system

Add a self-contained icon system: vendored Material Symbols registry, an
<Icon> component that references a single <symbol> sprite via <use>, plus
IconButton/IconSprite. Replace every emoji/Unicode glyph (logo, theme
switcher, cover placeholder, like/comment counts, lightbox controls, back
links) and add leading icons to key controls. Decorative icons are
aria-hidden; icon-only buttons get labels; theme toggles expose
aria-pressed; counters get accessible names.

Recorded in openspec change material-icons-ui (all tasks complete).
This commit is contained in:
iu
2026-10-10 13:19:29 +03:00
parent 84ce4d1930
commit 997ec5e6c1
21 changed files with 582 additions and 69 deletions
@@ -1,27 +1,29 @@
<script lang="ts">
import IconButton from './IconButton.svelte';
import type { IconName } from '$lib/icons/registry';
import { theme, type Theme } from '$lib/stores/theme.svelte';
const options: { value: Theme; label: string; icon: string }[] = [
{ value: 'light', label: 'Light', icon: '☀️' },
{ value: 'dark', label: 'Dark', icon: '🌙' },
{ value: 'neutral', label: 'Neutral', icon: '🌾' },
{ value: 'system', label: 'System', icon: '💻' }
const options: { value: Theme; label: string; icon: IconName }[] = [
{ value: 'light', label: 'Light', icon: 'light_mode' },
{ value: 'dark', label: 'Dark', icon: 'dark_mode' },
{ value: 'neutral', label: 'Neutral', icon: 'palette' },
{ value: 'system', label: 'System', icon: 'brightness_auto' }
];
</script>
<div class="border-border bg-bg-elevated inline-flex gap-1 rounded-lg border p-1" role="group" aria-label="Theme">
<div
class="border-border bg-bg-elevated inline-flex gap-1 rounded-lg border p-1"
role="group"
aria-label="Theme"
>
{#each options as option (option.value)}
<button
type="button"
title={option.label}
aria-label={option.label}
aria-pressed={theme.current === option.value}
<IconButton
name={option.icon}
label={option.label}
active={theme.current === option.value}
pressed={theme.current === option.value}
class="px-2 py-1 text-sm"
onclick={() => theme.set(option.value)}
class="rounded-md px-2 py-1 text-sm transition-colors {theme.current === option.value
? 'bg-primary text-primary-fg'
: 'text-text-muted hover:bg-bg-muted'}"
>
{option.icon}
</button>
/>
{/each}
</div>