diff --git a/apps/native/app/(tabs)/expenses.tsx b/apps/native/app/(tabs)/expenses.tsx index 5174e6ce..81bb1b73 100644 --- a/apps/native/app/(tabs)/expenses.tsx +++ b/apps/native/app/(tabs)/expenses.tsx @@ -31,11 +31,11 @@ export default function Expenses() { - + {(["all", "open", "settled"] as Filter[]).map((value) => { const on = filter === value; return setFilter(value)} - style={{ height: 32, paddingHorizontal: 14, borderRadius: 8, justifyContent: "center", backgroundColor: on ? c.frame : "transparent" }}> + style={{ height: 32, paddingHorizontal: 14, borderRadius: radius.pill, justifyContent: "center", backgroundColor: on ? c.frame : "transparent" }}> {value[0].toUpperCase() + value.slice(1)} ; })} diff --git a/apps/native/src/ui/kit.tsx b/apps/native/src/ui/kit.tsx index 38c44c56..398f3124 100644 --- a/apps/native/src/ui/kit.tsx +++ b/apps/native/src/ui/kit.tsx @@ -48,7 +48,7 @@ export function Button({ label, onPress, variant = "primary", size = "md", block disabled={disabled || busy} onPress={() => { tap(); onPress?.(); }} style={({ pressed }) => [{ - height, paddingHorizontal: size === "sm" ? 12 : size === "lg" ? 20 : 16, borderRadius: radius.control, backgroundColor: bg, + height, paddingHorizontal: size === "sm" ? 14 : size === "lg" ? 22 : 18, borderRadius: radius.pill, backgroundColor: bg, flexDirection: "row", alignItems: "center", justifyContent: "center", gap: 8, alignSelf: block ? "stretch" : "flex-start", opacity: disabled ? 0.45 : pressed ? 0.85 : 1, }, variant === "secondary" || variant === "danger" ? { borderWidth: 1, borderColor: variant === "danger" ? c.late : c.outline } : null]}> diff --git a/apps/web/index.html b/apps/web/index.html index 174ac66c..8eccca77 100644 --- a/apps/web/index.html +++ b/apps/web/index.html @@ -6,7 +6,7 @@ - + Commune h2, +.ops-board-head > span, +.a-content :is(.a-panel, .a-card) h2, +.c-home-head h2, +.c-hub .c-hub-identity h2 { + font-family: var(--font-display); + font-weight: 400; +} +.a-window-bar h2, +.c-product-dialog h2 { + font-size: 22px; + letter-spacing: -0.03em; +} +.a-content :is(.a-panel, .a-card) h2, +.c-home-head h2, +.ops-board-head > span { + font-size: 20px; + letter-spacing: -0.025em; +} +.c-hub .c-hub-identity h2 { + letter-spacing: -0.04em; +} +.ops-yours > h2 { + font-size: 30px; + letter-spacing: -0.035em; +} +.c-home-display { + font-weight: 400; + letter-spacing: -0.04em; +} +.c-home-square .c-home-display { + letter-spacing: -0.045em; +} + +/* The website's white highlight, behind the amounts Home opens on. It + carries its own ink, so it reads on the phone's navy band too. */ +.c-home-standing .c-home-display em { + padding: 0 0.16em 0.04em; + border-radius: 12px; + background: var(--home-surface); + color: var(--home-ink); + -webkit-box-decoration-break: clone; + box-decoration-break: clone; +} + +/* The website's small label: uppercase, 12px, spaced. */ +.c-home-over, +.c-home-group h3 { + font-size: 12px; + font-weight: 500; + letter-spacing: 0.08em; + text-transform: uppercase; +} + +/* Buttons: the website's round shape, in the app's green. */ +.a-body :is(.a-btn, .c-home-btn, .ops-add, .c-hub-button) { + border-radius: var(--radius-pill); +} +.a-body .a-btn:not(.ghost, .danger, .nudge), +.a-body .ops-add, +.a-body :is(.a-seg, .a-note-types) button.is-on, +.a-body .c-split-methods button[aria-pressed=true] { + background: var(--action); + color: var(--on-action); +} +.a-body .a-btn:not(.ghost, .danger, .nudge):hover:not(:disabled), +.a-body .ops-add:hover:not(:disabled) { + background: var(--action-hover); +} +.c-home { + --home-action: var(--action); + --home-action-hover: var(--action-hover); + --home-on-action: var(--on-action); +} + +/* Rounder cards, as on the website. */ +.a-card { + border-radius: var(--radius-surface); +} + +/* This Hub: the same green pills and display headings. Links that + used the green action colour keep green for text, as on the website. */ +.c-hub { + --hub-action: var(--action); + --hub-action-text: var(--on-action); + --hub-action-hover: var(--action-hover); +} +.c-hub-link, +.c-hub-note-row:hover strong { + color: var(--text-green); +} +.c-hub :is(.c-hub-attention-end button, .c-hub-invite, .c-hub-settings) { + border-radius: var(--radius-pill); +} +.c-hub-section-title { + font-family: var(--font-display); + font-weight: 400; + font-size: 20px; + letter-spacing: -0.025em; +} + +/* Home on wider screens: the sentence and the next step share one pale + green panel (the landing page's green), the white next step held inside + it, so the two read as one shape, as the navy band and the next step do + on a phone (Edem, 13 October). */ +.c-home { + --home-panel: #dce8d3; +} +.a-body.is-dark .c-home { + --home-panel: #253b34; +} +@media (min-width: 561px) { + .c-home-top { + gap: 0; + padding: 28px 16px 16px; + border-radius: var(--radius-surface); + background: var(--home-panel); + } + .c-home-top > .c-home-standing { padding: 0 16px 28px; } +} diff --git a/apps/web/src/styles/index.css b/apps/web/src/styles/index.css index 2f580294..9aca9c7e 100644 --- a/apps/web/src/styles/index.css +++ b/apps/web/src/styles/index.css @@ -20,5 +20,6 @@ @import "./overrides.css" layer(overrides); @import "./design-system.css" layer(overrides); @import "./motion.css" layer(overrides); +@import "./harmony.css" layer(overrides); /* Font faces only, which layers do not affect. */ @import "./fonts.css"; diff --git a/apps/web/src/styles/shared.css b/apps/web/src/styles/shared.css index 199f3e14..fb4f6537 100644 --- a/apps/web/src/styles/shared.css +++ b/apps/web/src/styles/shared.css @@ -15,9 +15,9 @@ --focus-ring: var(--sage); --danger: #9b2c2c; --danger-soft: #fde8e4; - --navy: #1a1e2b; - --navy-2: #1f2330; - --navy-3: #21262f; + --navy: #14243b; + --navy-2: #1b2d47; + --navy-3: #203450; --ink: #171b24; --ink-soft: #667085; --peach: #efdccf; @@ -33,11 +33,13 @@ --shadow-sm: 0 1px 3px rgba(23, 27, 36, 0.06), 0 1px 2px rgba(23, 27, 36, 0.04); --shadow: 0 4px 12px rgba(23, 27, 36, 0.07), 0 1px 3px rgba(23, 27, 36, 0.05); --radius-control: 9px; - --radius-surface: 15px; + --radius-surface: 24px; --radius-pill: 999px; --radius: var(--radius-surface); --radius-sm: var(--radius-control); --font: Inter, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; + /* The website's display face, for headlines (harmony.css). */ + --font-display: "DM Sans", var(--font); /* Motion (design panel, 11 October 2026, medium direction). Every duration and curve in the app is one of these. Things arrive over the medium time and leave over the short one; the long one is for the all-square diff --git a/apps/web/tests/design-tokens.test.ts b/apps/web/tests/design-tokens.test.ts index 4ff6311d..aadedb9f 100644 --- a/apps/web/tests/design-tokens.test.ts +++ b/apps/web/tests/design-tokens.test.ts @@ -27,8 +27,8 @@ describe("shared page header contract", () => { expect(styles.hub).not.toMatch(/\.hub-editorial\s+\.a-header(?:\b|[-\s])/); expect(declarations("app", ".a-header")).toContain("padding: 12px 28px;"); expect(declarations("app", ".a-header")).toContain("color: var(--ink);"); - expect(declarations("primitives", ".a-header-title h1")).toContain("font-size: 21px;"); - expect(declarations("primitives", ".a-header-title h1")).toContain("font-weight: 550;"); + expect(declarations("primitives", ".a-header-title h1")).toContain("font-size: 24px;"); + expect(declarations("primitives", ".a-header-title h1")).toContain("font-weight: 400;"); }); }); @@ -46,7 +46,7 @@ describe("Settle share action alignment", () => { describe("shared corner contracts", () => { it("defines a single control/surface family with legacy aliases", () => { expect(styles.shared).toContain("--radius-control: 9px;"); - expect(styles.shared).toContain("--radius-surface: 15px;"); + expect(styles.shared).toContain("--radius-surface: 24px;"); expect(styles.shared).toContain("--radius: var(--radius-surface);"); expect(styles.shared).toContain("--radius-sm: var(--radius-control);"); expect(styles.primitives).not.toMatch(/--radius(?:-sm)?:\s*\d/); diff --git a/apps/web/tests/stylesheets.test.ts b/apps/web/tests/stylesheets.test.ts index 341ea5fc..cf0a9453 100644 --- a/apps/web/tests/stylesheets.test.ts +++ b/apps/web/tests/stylesheets.test.ts @@ -54,11 +54,12 @@ describe("cascade layers", () => { '@import "./overrides.css" layer(overrides);', '@import "./design-system.css" layer(overrides);', '@import "./motion.css" layer(overrides);', + '@import "./harmony.css" layer(overrides);', '@import "./fonts.css";', ]); expect(stylesheets(resolve(src, "styles")).sort()).toEqual([ "styles/app.css", "styles/auth.css", "styles/calendar-date.css", "styles/design-system.css", - "styles/fonts.css", "styles/index.css", "styles/legal.css", "styles/motion.css", "styles/overrides.css", "styles/shared.css", + "styles/fonts.css", "styles/harmony.css", "styles/index.css", "styles/legal.css", "styles/motion.css", "styles/overrides.css", "styles/shared.css", ]); expect(topLevel(read("styles/fonts.css")).every((block) => block === "@font-face")).toBe(true); }); @@ -95,7 +96,7 @@ describe("colours", () => { const fixed = new Map([...definitions] .filter(([, values]) => values.length === 1 && /^#([0-9a-f]{3}|[0-9a-f]{6})$/i.test(values[0])) .map(([name, [value]]) => [hex(value), name])); - expect(fixed.get("1a1e2b")).toBe("--navy"); + expect(fixed.get("14243b")).toBe("--navy"); expect(fixed.get("2d6a4f")).toBe("--sage"); const literals = css.flatMap(([path, text]) => [...text.replace(/--[\w-]+\s*:[^;}]+/g, "").matchAll(/#([0-9a-f]{6}|[0-9a-f]{3})\b/gi)] .filter(([match]) => fixed.has(hex(match)))