diff --git a/replica/uhr/design/Tokens.kt b/replica/uhr/design/Tokens.kt new file mode 100644 index 0000000..efa007b --- /dev/null +++ b/replica/uhr/design/Tokens.kt @@ -0,0 +1,106 @@ +// Generated from tokens.json by gen_theme.py. Do not edit by hand. +package app.uhr.ui.theme + +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.text.TextStyle +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp + +object WColor { + val bg = Color(0xFFF8F9FF) + val surface = Color(0xFFFFFFFF) + val card = Color(0xFFEEF0F6) + val cardHigh = Color(0xFFE2E5EC) + val border = Color(0xFFC3C6CF) + val borderInput = Color(0xFF73777F) + val text = Color(0xFF191C20) + val textMuted = Color(0xFF43474E) + val textOff = Color(0xFF5C6068) + val accent = Color(0xFF2F5EA8) + val onAccent = Color(0xFFFFFFFF) + val accentContainer = Color(0xFFD6E3FF) + val onAccentContainer = Color(0xFF001B3E) + val fab = Color(0xFFA8C7FA) + val onFab = Color(0xFF0B2A55) + val chipOn = Color(0xFF2F5EA8) + val onChipOn = Color(0xFFFFFFFF) + val secondaryPill = Color(0xFFDFE2EB) + val onSecondaryPill = Color(0xFF191C20) + val stop = Color(0xFFFFD8E4) + val onStop = Color(0xFF4A1426) + val danger = Color(0xFFBA1A1A) + val nav = Color(0xFFECEEF4) + val navIndicator = Color(0xFFD6E3FF) + val darkBg = Color(0xFF111318) + val darkSurface = Color(0xFF111318) + val darkCard = Color(0xFF1D2024) + val darkCardHigh = Color(0xFF272A2F) + val darkBorder = Color(0xFF43474E) + val darkBorderInput = Color(0xFF8D9199) + val darkText = Color(0xFFE2E2E9) + val darkTextMuted = Color(0xFFC3C6CF) + val darkTextOff = Color(0xFF9A9EA6) + val darkAccent = Color(0xFFA8C7FA) + val darkOnAccent = Color(0xFF062E6F) + val darkAccentContainer = Color(0xFF244A80) + val darkOnAccentContainer = Color(0xFFD6E3FF) + val darkFab = Color(0xFFA8C7FA) + val darkOnFab = Color(0xFF062E6F) + val darkChipOn = Color(0xFFA8C7FA) + val darkOnChipOn = Color(0xFF062E6F) + val darkSecondaryPill = Color(0xFF3A3D43) + val darkOnSecondaryPill = Color(0xFFE2E2E9) + val darkStop = Color(0xFFF2B8C9) + val darkOnStop = Color(0xFF4A1426) + val darkDanger = Color(0xFFFFB4AB) + val darkNav = Color(0xFF1D2024) + val darkNavIndicator = Color(0xFF3A3F47) + val ringBg = Color(0xFF1E5A52) + val ringOn = Color(0xFFB4F1E6) + val ringTrack = Color(0xFF2B6E65) + val ringThumb = Color(0xFF3F9A7F) + val onRingThumb = Color(0xFFFFFFFF) + val timerTrack = Color(0xFF3A3D43) + val timerProgress = Color(0xFF7EC4A8) + val nightText = Color(0xFFC4433A) + val nightBg = Color(0xFF000000) +} + +object WType { + val labelSm = TextStyle(fontSize = 12.sp, lineHeight = 16.sp, fontWeight = FontWeight.W500) + val label = TextStyle(fontSize = 14.sp, lineHeight = 20.sp, fontWeight = FontWeight.W500) + val bodySm = TextStyle(fontSize = 14.sp, lineHeight = 20.sp, fontWeight = FontWeight.W400) + val body = TextStyle(fontSize = 16.sp, lineHeight = 24.sp, fontWeight = FontWeight.W400) + val title = TextStyle(fontSize = 22.sp, lineHeight = 28.sp, fontWeight = FontWeight.W400) + val headline = TextStyle(fontSize = 28.sp, lineHeight = 36.sp, fontWeight = FontWeight.W400) + val alarmTime = TextStyle(fontSize = 56.sp, lineHeight = 60.sp, fontWeight = FontWeight.W600) + val alarmAmpm = TextStyle(fontSize = 20.sp, lineHeight = 24.sp, fontWeight = FontWeight.W600) + val timerTime = TextStyle(fontSize = 44.sp, lineHeight = 48.sp, fontWeight = FontWeight.W400) + val clockBig = TextStyle(fontSize = 88.sp, lineHeight = 92.sp, fontWeight = FontWeight.W400) + val stopwatch = TextStyle(fontSize = 72.sp, lineHeight = 76.sp, fontWeight = FontWeight.W500) + val ringing = TextStyle(fontSize = 104.sp, lineHeight = 108.sp, fontWeight = FontWeight.W400) +} + +object WSpace { + val s0 = 0.dp + val s2 = 2.dp + val s4 = 4.dp + val s8 = 8.dp + val s12 = 12.dp + val s16 = 16.dp + val s20 = 20.dp + val s24 = 24.dp + val s32 = 32.dp + val s48 = 48.dp +} + +object WRadius { + val xs = 8.dp + val sm = 12.dp + val md = 16.dp + val lg = 24.dp + val card = 28.dp + val pill = 999.dp + val navIndicator = 16.dp +} diff --git a/replica/uhr/design/components.md b/replica/uhr/design/components.md new file mode 100644 index 0000000..43086b8 --- /dev/null +++ b/replica/uhr/design/components.md @@ -0,0 +1,79 @@ +# Komponenten: Uhr (Compose, Material 3 Expressive) + +Tokens: `tokens.json` → `Tokens.kt` (`gen_theme.py`). On Android 12+ the accent roles come from dynamic colour (wallpaper), the rest from tokens. +Icons: Material Symbols Rounded (Apache 2.0). Clock faces, launcher icon and bundled sounds are our own. All labels written fresh, in German. + +``` +BottomNav + items Wecker, Uhr, Timer, Stoppuhr, Schlafenszeit (icon + label) + states selected: pill indicator 64×32 (nav-indicator) behind the icon, label bold + a11y NavigationBar items with role tab, selected state announced + used on S01, S06, S08, S09, S10 + +TopBar + content screen title (title 22sp) left, overflow menu (Einstellungen, Bildschirmschoner) right + used on all tabs + +AlarmCard + collapsed label (label 14sp, muted) + chevron in a small tonal circle; big time "6:00" (alarm-time 56sp/600) + "AM" (alarm-ampm) when 12h; + bottom row: next ring text ("Morgen", "Heute", "Mo, Di, Mi") + Switch + expanded label field with tag icon, time, day text + switch, day chips row, rows: Pausieren (+), Ton (name), Vibration (check), Löschen + states on, off (time text-off), snoozed ("Schlummert bis 7:10" chip), skipping ("Übersprungen bis …"), expanding (spring) + tokens card / dark-card, radius card 28, padding 20, gap 8 + a11y card is one button "Wecker 6 Uhr, Morgen, an"; switch separate; expand state announced + used on S01, S02 + +DayChips + 7 circles 36dp, letters by locale and week start; selected chip-on, unselected outline + a11y toggleable, full weekday name + used on S02, S10 + +Fab + 96dp circle, fab colour, "+" icon; centred above the nav bar + used on S01, S06, S08 + +TimePicker + M3 TimePicker dial with toggle to TimeInput; 24h by system setting + used on S03, S10 + +SoundPicker (S04) + sections Eigene Töne (picked files, + "Datei hinzufügen"), Wecktöne (system), Mitgeliefert (bundled); radio list, tap previews, playing shows equalizer icon + states playing, selected, missing file (fallback note) + +RingingScreen (S11) + bg ring-bg (or dynamic tertiary container), full screen over lock screen + content time (ringing 104sp, light), label; slide control at the bottom + SlideToAct track pill (ring-track) with "Schlummern" left, "Stopp" right, thumb circle with alarm icon in the middle; + drag past 40 % to act, spring back otherwise; tap the side labels also works (accessibility) + a11y custom actions Schlummern / Stopp; volume keys per settings + +TimerCard + content label, ring (timer-track / timer-progress, 6dp) with remaining time inside (timer-time) and reset icon, +1:00 secondary pill, play/pause primary pill, close × + states running (ring shrinks), paused (time blinks), ringing (pulsing, time counts up negative), finished + used on S08 + +Keypad + display "00h 00m 00s", typed digits fill from the right; 3×4 keys circle 72dp, 00 and backspace; start FAB appears when not empty + used on S08 + +Stopwatch (S09) + time mm:ss.cc (stopwatch 72sp, tabular), hours appear when needed + buttons full-width pills, primary (stop/start: stop colour), secondaries (Zurücksetzen, Runde: secondary-pill) + laps list "Runde 3 00:12.34 01:02.56", fastest/slowest marked + states idle (Start only), running (Stopp, Runde), stopped (Start, Zurücksetzen) + +ClockHeader (S06) + big time clock-big with optional seconds, date, next alarm line; analog face variant (own design: round face, accent hour hand, light minute hand, dot) +CityCard + name, "Morgen · +7 Std.", time; swipe to delete, long press to reorder + used on S06 + +SettingsKit + same grouped tiles as Wetter (SettingsGroup / Item / Switch / Choice) + +Notifications + upcoming (Jetzt beenden), snoozed (Beenden), missed, timer running (chronometer, Pause, +1:00), stopwatch running (chronometer, Runde, Pause) + +Widgets + Digital (TextClock big + date + next alarm), Analog (own face as AnalogClock drawables), Next alarm pill; style options like Wetter (palette, theme, opacity) +``` diff --git a/replica/uhr/design/gen_theme.py b/replica/uhr/design/gen_theme.py new file mode 100644 index 0000000..0521b7c --- /dev/null +++ b/replica/uhr/design/gen_theme.py @@ -0,0 +1,26 @@ +#!/usr/bin/env python3 +"""tokens.json -> Tokens.kt (Compose). Re-run after every token change.""" +import json, re, sys, pathlib +src = pathlib.Path(sys.argv[1] if len(sys.argv) > 1 else "tokens.json") +out = pathlib.Path(sys.argv[2] if len(sys.argv) > 2 else src.with_name("Tokens.kt")) +t = json.loads(src.read_text()) +camel = lambda s: re.sub(r"-(\w)", lambda m: m.group(1).upper(), s) +L = ["// Generated from tokens.json by gen_theme.py. Do not edit by hand.", + "package app.uhr.ui.theme", "", + "import androidx.compose.ui.graphics.Color", + "import androidx.compose.ui.text.TextStyle", + "import androidx.compose.ui.text.font.FontWeight", + "import androidx.compose.ui.unit.dp", + "import androidx.compose.ui.unit.sp", "", + "object WColor {"] +L += [f" val {camel(k)} = Color(0xFF{v.lstrip('#').upper()})" for k, v in t["color"].items()] +L += ["}", "", "object WType {"] +for k, v in t["type"].items(): + L.append(f" val {camel(k)} = TextStyle(fontSize = {v['size']}.sp, lineHeight = {v['line']}.sp, fontWeight = FontWeight.W{v['weight']})") +L += ["}", "", "object WSpace {"] +L += [f" val s{v} = {v}.dp" for v in t["space"]] +L += ["}", "", "object WRadius {"] +L += [f" val {camel(k)} = {v}.dp" for k, v in t["radius"].items()] +L += ["}", ""] +out.write_text("\n".join(L)) +print("wrote", out) diff --git a/replica/uhr/design/tokens.json b/replica/uhr/design/tokens.json new file mode 100644 index 0000000..a5f6089 --- /dev/null +++ b/replica/uhr/design/tokens.json @@ -0,0 +1,78 @@ +{ + "_about": "Uhr tokens. Roles read by eye from replica/uhr/screens/ref-01..07 (Pixel Clock, dark first). Own values, no brand colour copied; on Android 12+ dynamic colour (wallpaper) replaces the accent roles. Check: python3 ~/.claude/skills/replica-design/contrast.py replica/uhr/design/tokens.json", + "color": { + "bg": "#f8f9ff", "surface": "#ffffff", "card": "#eef0f6", "card-high": "#e2e5ec", + "border": "#c3c6cf", "border-input": "#73777f", + "text": "#191c20", "text-muted": "#43474e", "text-off": "#5c6068", + "accent": "#2f5ea8", "on-accent": "#ffffff", + "accent-container": "#d6e3ff", "on-accent-container": "#001b3e", + "fab": "#a8c7fa", "on-fab": "#0b2a55", + "chip-on": "#2f5ea8", "on-chip-on": "#ffffff", + "secondary-pill": "#dfe2eb", "on-secondary-pill": "#191c20", + "stop": "#ffd8e4", "on-stop": "#4a1426", + "danger": "#ba1a1a", + "nav": "#eceef4", "nav-indicator": "#d6e3ff", + + "dark-bg": "#111318", "dark-surface": "#111318", "dark-card": "#1d2024", "dark-card-high": "#272a2f", + "dark-border": "#43474e", "dark-border-input": "#8d9199", + "dark-text": "#e2e2e9", "dark-text-muted": "#c3c6cf", "dark-text-off": "#9a9ea6", + "dark-accent": "#a8c7fa", "dark-on-accent": "#062e6f", + "dark-accent-container": "#244a80", "dark-on-accent-container": "#d6e3ff", + "dark-fab": "#a8c7fa", "dark-on-fab": "#062e6f", + "dark-chip-on": "#a8c7fa", "dark-on-chip-on": "#062e6f", + "dark-secondary-pill": "#3a3d43", "dark-on-secondary-pill": "#e2e2e9", + "dark-stop": "#f2b8c9", "dark-on-stop": "#4a1426", + "dark-danger": "#ffb4ab", + "dark-nav": "#1d2024", "dark-nav-indicator": "#3a3f47", + + "ring-bg": "#1e5a52", "ring-on": "#b4f1e6", "ring-track": "#2b6e65", "ring-thumb": "#3f9a7f", "on-ring-thumb": "#ffffff", + "timer-track": "#3a3d43", "timer-progress": "#7ec4a8", + "night-text": "#c4433a", "night-bg": "#000000" + }, + "pairs": [ + ["text","bg"],["text","card"],["text","card-high"],["text-muted","bg"],["text-muted","card"],["text-off","card"], + ["on-accent","accent"],["on-accent-container","accent-container"],["on-fab","fab"],["on-chip-on","chip-on"], + ["on-secondary-pill","secondary-pill"],["on-stop","stop"],["accent","card"],["danger","card"],["text","nav"],["text","nav-indicator"], + ["border-input","surface","ui"], + ["dark-text","dark-bg"],["dark-text","dark-card"],["dark-text","dark-card-high"],["dark-text-muted","dark-bg"],["dark-text-muted","dark-card"],["dark-text-off","dark-card"], + ["dark-on-accent","dark-accent"],["dark-on-accent-container","dark-accent-container"],["dark-on-fab","dark-fab"],["dark-on-chip-on","dark-chip-on"], + ["dark-on-secondary-pill","dark-secondary-pill"],["dark-on-stop","dark-stop"],["dark-accent","dark-card"],["dark-danger","dark-card"], + ["dark-text","dark-nav"],["dark-text","dark-nav-indicator"],["dark-border-input","dark-card","ui"], + ["ring-on","ring-bg"],["ring-on","ring-track"],["on-ring-thumb","ring-thumb","large"], + ["timer-progress","dark-card","ui"],["night-text","night-bg","large"] + ], + "font": { + "sans": "Roboto Flex (OFL), system fallback", + "display": "Roboto Flex, weight 500-600, tabular numbers, for alarm times", + "clock": "Roboto Flex, weight 300-400 for big clocks and the ringing screen", + "mono": "tabular figures (fontFeatureSettings tnum) instead of a mono font for the stopwatch" + }, + "type": { + "label-sm": {"size": 12, "line": 16, "weight": 500}, + "label": {"size": 14, "line": 20, "weight": 500}, + "body-sm": {"size": 14, "line": 20, "weight": 400}, + "body": {"size": 16, "line": 24, "weight": 400}, + "title": {"size": 22, "line": 28, "weight": 400}, + "headline": {"size": 28, "line": 36, "weight": 400}, + "alarm-time": {"size": 56, "line": 60, "weight": 600}, + "alarm-ampm": {"size": 20, "line": 24, "weight": 600}, + "timer-time": {"size": 44, "line": 48, "weight": 400}, + "clock-big": {"size": 88, "line": 92, "weight": 400}, + "stopwatch": {"size": 72, "line": 76, "weight": 500}, + "ringing": {"size": 104, "line": 108, "weight": 400} + }, + "space": [0, 2, 4, 8, 12, 16, 20, 24, 32, 48], + "radius": {"xs": 8, "sm": 12, "md": 16, "lg": 24, "card": 28, "pill": 999, "nav-indicator": 16}, + "shape": { + "fab": "circle 96dp (large, centred above the nav bar)", + "stopwatch-primary": "pill 72dp tall, full width", + "timer-ring": "circle stroke 6dp", + "day-chip": "circle 36dp" + }, + "motion": { + "card-expand": "spring dampingRatio 0.85 stiffness 380", + "slide-act": "spring back dampingRatio 0.7", + "fast": "120ms", "base": "250ms" + }, + "layout": {"gutter": 16, "card-gap": 8, "nav-height": 80, "fab-bottom": 16, "card-padding": 20} +}