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