diff --git a/DESIGN.md b/DESIGN.md index d110a11..bc4c7d9 100644 --- a/DESIGN.md +++ b/DESIGN.md @@ -51,6 +51,11 @@ typography: fontSize: 0.875rem fontWeight: 400 lineHeight: 1.5 + control-small: + fontFamily: "'UI Latin Sans', 'UI zh-CN Sans', 'Noto Sans', 'Noto Sans SC', ui-sans-serif, system-ui, sans-serif" + fontSize: 0.75rem + fontWeight: 400 + lineHeight: 1 prose: fontFamily: "'UI Latin Sans', 'UI zh-CN Sans', 'Noto Sans', 'Noto Sans SC', ui-sans-serif, system-ui, sans-serif" fontSize: 0.9375rem @@ -115,6 +120,14 @@ components: rounded: '{rounded.md}' padding: '0 8px' height: 28px + region-option-selected: + backgroundColor: '{colors.paper}' + borderColor: 'color-mix(in srgb, {colors.ink-soft} 75%, {colors.ink-mute})' + textColor: '{colors.ink}' + typography: '{typography.control-small}' + rounded: '{rounded.control}' + padding: '0 8px' + height: 28px navigation-control: backgroundColor: transparent textColor: '{colors.ink-mute}' @@ -242,6 +255,8 @@ locale by accident. headings. - **Body** (regular, 1rem, line-height 1.5): Interface explanations and ordinary content. +- **Control Small** (regular, 0.75rem, line-height 1): Compact filter and + region-option labels that must align inside a 1.75rem control. - **Prose** (regular, 0.9375rem, line-height 1.85): Long-form methodology and source notes, held to a readable 42rem measure. - **Label** (regular, 0.6875rem, letter-spacing 0.04em): Section labels, @@ -332,8 +347,24 @@ motif; large capsules belong only to tiny run markers and scroll thumbs. - **Disabled or hidden:** Opacity communicates unavailability only after the label and control state remain programmatically explicit. -**The Selected State Is a Slab Rule.** Filters use ink-on-paper inversion for -the selected state, not a brighter border alone. +**The Selected State Is a Slab Rule.** Non-regional content filters use +ink-on-paper inversion for the selected state, not a brighter border alone. +Region options are the explicit artwork-safe exception defined below. + +### Region Options + +Region selectors use one compact outlined control whether they appear as a +single-choice index, a visibility toggle, or a listing filter. Because a flag +is full-color artwork, it never carries selection state and never sits on an +inverted Ink slab. Selection instead strengthens the border to a quiet blend +between Soft Ink and Muted Ink, then returns the control to Paper; resting +flags and the Sunk Paper surface recede so the chosen marks remain easy to +scan. This edge-and-surface contrast stays visible with short text labels, in +grayscale, and in both themes. + +**The Artwork Never Carries State Rule.** A region option must remain visibly +selected when its flag or text label is removed. State belongs to the control +edge and surface, while the artwork only names the region. ### Cards / Containers diff --git a/app/components/FilterBar.vue b/app/components/FilterBar.vue index dc0443a..646e961 100644 --- a/app/components/FilterBar.vue +++ b/app/components/FilterBar.vue @@ -11,7 +11,7 @@ import { const chars = injectChars() const { t, locale } = useT() -const { flagsOn, visibleColumns, visibleRegions } = usePrefs() +const { visibleColumns, visibleRegions } = usePrefs() // Every label has to be computed, not built once: t() reads the active locale // and the reader can change it after the component is set up @@ -140,19 +140,24 @@ const strokeHigh = computed({ /** Tier labels live under char.tierCn and friends; old forms use region.old. */ const listingOptions = computed(() => - listingOptionsFor(visibleColumns.value).map((entry) => ({ - ...entry, - label: + listingOptionsFor(visibleColumns.value).map((entry) => { + const label = entry.kind === 'old' ? t('region.old.short') : t( `char.tier${entry.region[0]!.toUpperCase()}${entry.region[1]}.${entry.tier}`, - ), - title: + ) + const title = entry.kind === 'old' ? t('region.old.full') - : t(`region.${entry.region}.full`), - })), + : t(`region.${entry.region}.full`) + return { + ...entry, + label, + title, + ariaLabel: `${title} ${label}`, + } + }), ) function toggleTier(id: string) { @@ -215,22 +220,14 @@ function toggleRegion(region: string) {
{{ t('filter.common') }}
- + :active="chars.common.value.includes(region)" + :label="t(`region.${region}.full`)" + :parts="[{ region }]" + @select="toggleRegion(region)" + />
@@ -270,25 +267,15 @@ function toggleRegion(region: string) { t('filter.tier') }}
- + @select="toggleTier(option.id)" + />
diff --git a/app/components/OptionsMenu.vue b/app/components/OptionsMenu.vue index bc5e7b1..915fd77 100644 --- a/app/components/OptionsMenu.vue +++ b/app/components/OptionsMenu.vue @@ -2,7 +2,6 @@ const { t } = useT() const { flagLabels, - flagsOn, outline, COLUMNS, columnShown, @@ -63,18 +62,9 @@ onKeyStroke('Escape', () => (open.value = false)) - +
{{ t('options.columns') @@ -83,32 +73,31 @@ onKeyStroke('Escape', () => (open.value = false)) t('options.columnsHint') }}
- + @select="toggleColumn(column)" + />
diff --git a/app/components/RegionChoice.vue b/app/components/RegionChoice.vue index 812afb1..2981392 100644 --- a/app/components/RegionChoice.vue +++ b/app/components/RegionChoice.vue @@ -13,46 +13,22 @@ defineProps<{ }[] }>() const model = defineModel({ required: true }) -const { flagsOn } = usePrefs() diff --git a/app/components/RegionOption.vue b/app/components/RegionOption.vue new file mode 100644 index 0000000..a3f7a2e --- /dev/null +++ b/app/components/RegionOption.vue @@ -0,0 +1,156 @@ + + + + +