replica(uhr): design tokens and component specs (39 pairs AA)
Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
@@ -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
|
||||
}
|
||||
@@ -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)
|
||||
```
|
||||
@@ -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)
|
||||
@@ -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}
|
||||
}
|
||||
Reference in New Issue
Block a user