diff --git a/replica/rechner/design/Tokens.kt b/replica/rechner/design/Tokens.kt new file mode 100644 index 0000000..1cb2c55 --- /dev/null +++ b/replica/rechner/design/Tokens.kt @@ -0,0 +1,75 @@ +// Generated from tokens.json by gen_theme.py. Do not edit by hand. +package app.rechner.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(0xFFF9F9FF) + val display = Color(0xFFE1E5EE) + val sheet = Color(0xFFE8EBF2) + val text = Color(0xFF191C20) + val textMuted = Color(0xFF43474E) + val result = Color(0xFF5A5F68) + val keyDigit = Color(0xFFEEF0F5) + val onKeyDigit = Color(0xFF191C20) + val keyOp = Color(0xFFD6E3F5) + val onKeyOp = Color(0xFF0E1D2E) + val keyAc = Color(0xFFEAD9FF) + val onKeyAc = Color(0xFF25113D) + val keyEq = Color(0xFFCDE3FB) + val onKeyEq = Color(0xFF06233D) + val keyFn = Color(0xFFDDE3EC) + val onKeyFn = Color(0xFF191C20) + val error = Color(0xFFBA1A1A) + val darkBg = Color(0xFF0B1414) + val darkDisplay = Color(0xFF283535) + val darkSheet = Color(0xFF1E2A2A) + val darkText = Color(0xFFE0E6E6) + val darkTextMuted = Color(0xFFBEC8C8) + val darkResult = Color(0xFF9FB0B0) + val darkKeyDigit = Color(0xFF172121) + val darkOnKeyDigit = Color(0xFFDDE4E4) + val darkKeyOp = Color(0xFFA3D1D3) + val darkOnKeyOp = Color(0xFF002F31) + val darkKeyAc = Color(0xFF8FD5E8) + val darkOnKeyAc = Color(0xFF00303B) + val darkKeyEq = Color(0xFF00E0CC) + val darkOnKeyEq = Color(0xFF00332E) + val darkKeyFn = Color(0xFF253232) + val darkOnKeyFn = Color(0xFFDDE4E4) + val darkError = Color(0xFFFFB4AB) +} + +object WType { + val label = TextStyle(fontSize = 14.sp, lineHeight = 20.sp, fontWeight = FontWeight.W500) + val body = TextStyle(fontSize = 16.sp, lineHeight = 24.sp, fontWeight = FontWeight.W400) + val fn = TextStyle(fontSize = 22.sp, lineHeight = 28.sp, fontWeight = FontWeight.W400) + val key = TextStyle(fontSize = 34.sp, lineHeight = 40.sp, fontWeight = FontWeight.W400) + val result = TextStyle(fontSize = 36.sp, lineHeight = 44.sp, fontWeight = FontWeight.W400) + val expr = TextStyle(fontSize = 72.sp, lineHeight = 80.sp, fontWeight = FontWeight.W400) + val exprMin = TextStyle(fontSize = 36.sp, lineHeight = 44.sp, fontWeight = FontWeight.W400) + val historyExpr = TextStyle(fontSize = 18.sp, lineHeight = 24.sp, fontWeight = FontWeight.W400) + val historyResult = TextStyle(fontSize = 28.sp, lineHeight = 34.sp, fontWeight = FontWeight.W400) +} + +object WSpace { + val s0 = 0.dp + val s4 = 4.dp + val s8 = 8.dp + val s12 = 12.dp + val s16 = 16.dp + val s24 = 24.dp + val s32 = 32.dp +} + +object WRadius { + val display = 32.dp + val key = 999.dp + val keyPressed = 24.dp + val menu = 24.dp + val pill = 999.dp +} diff --git a/replica/rechner/design/components.md b/replica/rechner/design/components.md new file mode 100644 index 0000000..a1ee756 --- /dev/null +++ b/replica/rechner/design/components.md @@ -0,0 +1,26 @@ +# Komponenten: Rechner + +``` +Display (S01) + expression right aligned, expr 72sp shrinking to expr-min as it grows, then scrolls horizontally; cursor; selectable (long press → S04) + result live preview below (result 36sp, muted); on "=" it animates up into the expression + states empty (blinking cursor), typing, final, error (error colour, message), overflow (scroll) + tokens display bg, radius display at the bottom corners, drag handle 32×4 at the bottom edge + +FunctionRow (S01) / ScientificPanel (S02) + collapsed √ π ^ ! as text keys + chevron button (circle) + expanded tonal pills: √ π ^ ! / Deg|Rad sin cos tan / Inv e ln log; Inv toggles labels (sin⁻¹ cos⁻¹ tan⁻¹ / eˣ 10ˣ x²) + motion expand with the keypad sliding down (sheet spring) + +Key + variants digit, op (÷ × − +, ( ), %), ac, eq, fn + shape circle at rest → squircle (key-pressed radius) while pressed, spring back; haptic KEYBOARD_TAP + a11y content descriptions in words ("geteilt durch", "Klammer", "Wurzel"), backspace long-press = clear + +HistorySheet (S03) + pull the display down (or ⏱ button) to reveal; groups by day ("Heute", "Gestern", date); row = expression muted + result large, right aligned; + tap inserts the result; "Aktuelle Eingabe" label above the live expression; empty state text; clear in the overflow menu + +ContextMenu (S04) + floating pill: Ausschneiden, Kopieren, Einfügen, MS, MR (when memory set) +``` diff --git a/replica/rechner/design/gen_theme.py b/replica/rechner/design/gen_theme.py new file mode 100644 index 0000000..6fcca1f --- /dev/null +++ b/replica/rechner/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.rechner.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/rechner/design/tokens.json b/replica/rechner/design/tokens.json new file mode 100644 index 0000000..4c8d61f --- /dev/null +++ b/replica/rechner/design/tokens.json @@ -0,0 +1,43 @@ +{ + "_about": "Rechner tokens from replica/rechner/screens/ref-01..07 (light, dark, Material You). Fallback palette; on Android 12+ dynamic colour drives the key roles. Check: python3 ~/.claude/skills/replica-design/contrast.py replica/rechner/design/tokens.json", + "color": { + "bg": "#f9f9ff", "display": "#e1e5ee", "sheet": "#e8ebf2", + "text": "#191c20", "text-muted": "#43474e", "result": "#5a5f68", + "key-digit": "#eef0f5", "on-key-digit": "#191c20", + "key-op": "#d6e3f5", "on-key-op": "#0e1d2e", + "key-ac": "#ead9ff", "on-key-ac": "#25113d", + "key-eq": "#cde3fb", "on-key-eq": "#06233d", + "key-fn": "#dde3ec", "on-key-fn": "#191c20", + "error": "#ba1a1a", + "dark-bg": "#0b1414", "dark-display": "#283535", "dark-sheet": "#1e2a2a", + "dark-text": "#e0e6e6", "dark-text-muted": "#bec8c8", "dark-result": "#9fb0b0", + "dark-key-digit": "#172121", "dark-on-key-digit": "#dde4e4", + "dark-key-op": "#a3d1d3", "dark-on-key-op": "#002f31", + "dark-key-ac": "#8fd5e8", "dark-on-key-ac": "#00303b", + "dark-key-eq": "#00e0cc", "dark-on-key-eq": "#00332e", + "dark-key-fn": "#253232", "dark-on-key-fn": "#dde4e4", + "dark-error": "#ffb4ab" + }, + "pairs": [ + ["text","display"],["text-muted","display"],["result","display"],["text","sheet"],["text-muted","sheet"],["text","bg"], + ["on-key-digit","key-digit"],["on-key-op","key-op"],["on-key-ac","key-ac"],["on-key-eq","key-eq"],["on-key-fn","key-fn"],["error","display"], + ["dark-text","dark-display"],["dark-text-muted","dark-display"],["dark-result","dark-display"],["dark-text","dark-sheet"],["dark-text-muted","dark-sheet"],["dark-text","dark-bg"], + ["dark-on-key-digit","dark-key-digit"],["dark-on-key-op","dark-key-op"],["dark-on-key-ac","dark-key-ac"],["dark-on-key-eq","dark-key-eq"],["dark-on-key-fn","dark-key-fn"],["dark-error","dark-display"] + ], + "font": {"sans": "Roboto Flex (OFL)", "display": "Roboto Flex, weight 400, tabular figures for expression and result"}, + "type": { + "label": {"size": 14, "line": 20, "weight": 500}, + "body": {"size": 16, "line": 24, "weight": 400}, + "fn": {"size": 22, "line": 28, "weight": 400}, + "key": {"size": 34, "line": 40, "weight": 400}, + "result": {"size": 36, "line": 44, "weight": 400}, + "expr": {"size": 72, "line": 80, "weight": 400}, + "expr-min": {"size": 36, "line": 44, "weight": 400}, + "history-expr": {"size": 18, "line": 24, "weight": 400}, + "history-result": {"size": 28, "line": 34, "weight": 400} + }, + "space": [0, 4, 8, 12, 16, 24, 32], + "radius": {"display": 32, "key": 999, "key-pressed": 24, "menu": 24, "pill": 999}, + "layout": {"key-gap": 12, "gutter": 16, "keypad-cols": 4, "display-min-height": "35%"}, + "motion": {"key-press": "spring dampingRatio 0.6 stiffness 800 (circle -> squircle)", "sheet": "spring 0.9 / 400"} +}