feat: add mobile shell and unified search
Some checks failed
ci / 정본·보안·린트·타입·테스트 (push) Failing after 50s
ci / 워크스페이스 빌드 검증 (push) Has been skipped
ci / Supabase Edge Functions + Cloudflare Worker (push) Successful in 20s
ci / .NET API 서버 테스트 (push) Successful in 12s
deploy-site / deploy (push) Successful in 39s
ci / 모바일 린트·타입·Jest (push) Failing after 3h10m31s

This commit is contained in:
Yun Chan 2026-09-28 23:23:36 +09:00
parent f505a03d34
commit 5ca3709b32
98 changed files with 5168 additions and 2093 deletions

View file

@ -0,0 +1,48 @@
import { RAW, STATUS } from '../../../packages/ui/src/theme-raw'
import {
D3RO_NATIVE_RAW,
D3RO_NATIVE_STATUS,
getD3roNativePalette,
} from '@d3ro/ui-native'
type Rgb = readonly [number, number, number]
function rgb(hex: string): Rgb {
const normalized = hex.replace('#', '')
if (!/^[0-9a-f]{6}$/i.test(normalized)) throw new Error(`Expected 6-digit hex, got ${hex}`)
return [0, 2, 4].map((offset) => Number.parseInt(normalized.slice(offset, offset + 2), 16)) as unknown as Rgb
}
function luminance(hex: string): number {
const channels = rgb(hex).map((value) => {
const channel = value / 255
return channel <= 0.04045 ? channel / 12.92 : ((channel + 0.055) / 1.055) ** 2.4
})
return (0.2126 * channels[0]!) + (0.7152 * channels[1]!) + (0.0722 * channels[2]!)
}
function contrast(foreground: string, background: string): number {
const [lighter, darker] = [luminance(foreground), luminance(background)].sort((a, b) => b - a)
return (lighter! + 0.05) / (darker! + 0.05)
}
describe('mobile v4 theme contract', () => {
test.each(['dark', 'light'] as const)('%s raw tokens stay in desktop SSOT parity', (mode) => {
for (const group of ['bg', 'text', 'border', 'accent'] as const) {
for (const [key, value] of Object.entries(D3RO_NATIVE_RAW[mode][group])) {
expect(RAW[mode][group][key as keyof typeof RAW[typeof mode][typeof group]]).toBe(value)
}
}
expect(D3RO_NATIVE_STATUS[mode]).toEqual(STATUS[mode])
})
test.each(['dark', 'light'] as const)('%s readable roles meet 4.5:1 contrast', (mode) => {
const palette = getD3roNativePalette(mode)
for (const background of [palette.bg.app, palette.bg.card, palette.bg.elevated, palette.bg.sidebar]) {
for (const foreground of [palette.text.primary, palette.text.secondary, palette.text.tertiary]) {
expect(contrast(foreground, background)).toBeGreaterThanOrEqual(4.5)
}
}
expect(contrast(palette.accent.ink, palette.accent.solid)).toBeGreaterThanOrEqual(4.5)
})
})