diff --git a/docs/agent-guide.md b/docs/agent-guide.md index c918be8..c232cbf 100644 --- a/docs/agent-guide.md +++ b/docs/agent-guide.md @@ -348,9 +348,10 @@ await home.call('lighting.set', { on: true }); // rejects with a closed code Do not edit the bridge; a newer CLI's starter carries any change to it. **Make it beautiful from the first build.** Run `miakapp docs design` before you -draw anything: Miakapp ships design tokens, four themes (Minuit, Lumière, Miel, -Craie), CSS recipes, an embedded font and example screens. The starter's -`app/theme.ts` already uses them. They are a starting point you may change +draw anything, and follow its "Make it beautiful" recipe: Miakapp ships design +tokens, four themes (Minuit, Lumière, Miel, Craie), CSS recipes, line icons, an +embedded font and example screens. The starter's `app/theme.ts` already uses +them. They are a starting point you may change entirely; the Miakapp bar at the bottom, outside your frame, is not yours to redraw, but `home.bar.setButtons()` can add one or two of your own screens to it. diff --git a/packages/cli/README.md b/packages/cli/README.md index 114c6ce..3903268 100644 --- a/packages/cli/README.md +++ b/packages/cli/README.md @@ -54,10 +54,12 @@ publish. ## Design `miakapp docs design` prints a short design guide and the directory of the -files it describes: `tokens.css` (four themes: Minuit, Lumière, Miel, Craie), -`components.css` (glass, device tile, scene, switch, level slider, stale -banner), `fonts.css` (Manrope as a `data:` URL, since the frame has no network) -and `gallery.html`, example screens to open in a browser. They are a starting +files it describes: `tokens.css` (four themes: Minuit, Lumière, Miel, Craie, +and a hue per place), `components.css` (screen, entry tiles, orb, state, +held and primary actions, device tile, scene, switch, level slider, stale +banner), `icons.svg` (line icons), `fonts.css` (Manrope as a `data:` URL, since +the frame has no network) and `gallery.html`, example screens to open in a +browser. They are a starting point, not a framework: copy, change or ignore them. Maintainers: the generated files (`fonts.css`, `gallery.html`, the starter's diff --git a/packages/cli/assets/agent-guide.md b/packages/cli/assets/agent-guide.md index c918be8..c232cbf 100644 --- a/packages/cli/assets/agent-guide.md +++ b/packages/cli/assets/agent-guide.md @@ -348,9 +348,10 @@ await home.call('lighting.set', { on: true }); // rejects with a closed code Do not edit the bridge; a newer CLI's starter carries any change to it. **Make it beautiful from the first build.** Run `miakapp docs design` before you -draw anything: Miakapp ships design tokens, four themes (Minuit, Lumière, Miel, -Craie), CSS recipes, an embedded font and example screens. The starter's -`app/theme.ts` already uses them. They are a starting point you may change +draw anything, and follow its "Make it beautiful" recipe: Miakapp ships design +tokens, four themes (Minuit, Lumière, Miel, Craie), CSS recipes, line icons, an +embedded font and example screens. The starter's `app/theme.ts` already uses +them. They are a starting point you may change entirely; the Miakapp bar at the bottom, outside your frame, is not yours to redraw, but `home.bar.setButtons()` can add one or two of your own screens to it. diff --git a/packages/cli/assets/design/components.css b/packages/cli/assets/design/components.css index 807da1e..7a811f6 100644 --- a/packages/cli/assets/design/components.css +++ b/packages/cli/assets/design/components.css @@ -10,6 +10,7 @@ color: var(--mk-ink); background: var(--mk-bg-art), var(--mk-bg); background-attachment: fixed; + margin: 0; -webkit-font-smoothing: antialiased; font-variant-numeric: tabular-nums; } @@ -58,58 +59,168 @@ font-size: var(--mk-text-sm); font-weight: 600; white-space: nowrap; } -/* Entry tile: the home screen opens pages, it does not show values */ +/* Icons: line icons from icons.svg / ICONS, never emoji (they clash with every palette) */ +.mk-i { width: 24px; height: 24px; flex: none; fill: none; stroke: currentColor; stroke-width: 1.8; stroke-linecap: round; stroke-linejoin: round; } + +/* Screen: a home screen or a page fills the phone down to the Miakapp bar */ +.mk-screen { + box-sizing: border-box; min-height: 100vh; min-height: 100dvh; + max-width: 560px; margin: 0 auto; + display: flex; flex-direction: column; gap: var(--mk-4); + padding: var(--mk-6) var(--mk-4) 112px; + container-type: inline-size; +} +.mk-eyebrow { font-size: var(--mk-text-xs); font-weight: 700; letter-spacing: .14em; text-transform: uppercase; color: var(--mk-ink-3); } +.mk-headline { margin: 0; font-size: var(--mk-display); font-weight: 800; letter-spacing: -.045em; line-height: 1.02; } +.mk-headline em { font-style: normal; background: var(--mk-accent-gradient); -webkit-background-clip: text; background-clip: text; color: transparent; } +.mk-headline em.is-alert { background: linear-gradient(110deg, color-mix(in srgb, var(--mk-warn) 70%, #fff), var(--mk-warn) 55%, var(--mk-heat)); -webkit-background-clip: text; background-clip: text; } + +/* Entry tile: the home screen opens pages. Tiles of different sizes fill the screen */ +.mk-entries { flex: 1; display: grid; grid-template-columns: 1fr 1fr; grid-auto-rows: minmax(108px, 1fr); gap: var(--mk-3); } +@container (min-width: 600px) { .mk-entries { grid-template-columns: repeat(3, 1fr); } } .mk-entry { - position: relative; display: grid; place-items: center; align-content: center; gap: var(--mk-2); - min-height: 132px; padding: var(--mk-4); - border-radius: var(--mk-radius); border: 1px solid var(--mk-rim); - background: var(--mk-surface); box-shadow: var(--mk-shadow); + position: relative; display: flex; flex-direction: column; justify-content: space-between; align-items: flex-start; gap: var(--mk-3); + min-height: 108px; padding: var(--mk-4); box-sizing: border-box; + border-radius: var(--mk-radius-lg); + border: 1px solid color-mix(in srgb, var(--tint, var(--mk-accent)) calc(var(--glow, 0%) + 4%), var(--mk-rim)); + background: radial-gradient(120% 90% at 0% 0%, color-mix(in srgb, var(--tint, var(--mk-accent)) var(--glow, 0%), transparent), transparent 65%), var(--mk-surface); + box-shadow: var(--mk-shadow); -webkit-backdrop-filter: blur(var(--mk-blur)) saturate(var(--mk-saturate)); backdrop-filter: blur(var(--mk-blur)) saturate(var(--mk-saturate)); - color: inherit; font: 700 var(--mk-text-lg)/1.15 var(--mk-font); text-align: center; cursor: pointer; + color: inherit; font: inherit; text-align: start; cursor: pointer; transition: transform var(--mk-fast) var(--mk-spring); } -.mk-entry:active { transform: scale(.96); } -.mk-entry .glyph { font-size: 40px; line-height: 1; } -.mk-entry.is-wide { grid-column: 1 / -1; min-height: 104px; } -.mk-entry .hint { margin-top: -2px; font-size: var(--mk-text-xs); font-weight: 600; color: var(--mk-ink-3); } -.mk-entry.has-attention .hint { color: var(--mk-warn); } -/* One small signal when something needs attention, never a full value */ +.mk-entry:active { transform: scale(.97); } +.mk-entry .mk-icon { + width: 46px; height: 46px; border-radius: 16px; display: grid; place-items: center; + color: var(--tint, var(--mk-accent)); + background: color-mix(in srgb, var(--tint, var(--mk-accent)) 22%, transparent); + border: 1px solid color-mix(in srgb, var(--tint, var(--mk-accent)) 40%, transparent); +} +.mk-entry .name { display: block; font-size: 20px; font-weight: 800; letter-spacing: -.02em; line-height: 1.1; } +/* The discreet state of the one thing a tile opens: a word or two, never a reading */ +.mk-entry .hint { display: block; margin-top: 3px; font-size: var(--mk-text-sm); font-weight: 600; color: var(--mk-ink-2); } +/* One big number a tile is about (the living-room temperature), in the tile's hue */ +.mk-entry .big { display: block; font-size: 48px; font-weight: 800; letter-spacing: -.05em; line-height: 1; color: var(--tint, var(--mk-accent)); } +.mk-entry.is-wide { grid-column: 1 / -1; flex-direction: row; align-items: center; gap: var(--mk-4); } +.mk-entry.is-tall { grid-row: span 2; } +/* The featured tile: the one most reached for (the gate, the front door). Gold, wide, first */ +.mk-entry.is-featured { + grid-column: 1 / -1; flex-direction: row; align-items: center; gap: var(--mk-4); padding: var(--mk-5); + background: linear-gradient(140deg, color-mix(in srgb, var(--mk-accent) 26%, transparent), color-mix(in srgb, var(--mk-accent) 8%, transparent) 45%, transparent), var(--mk-surface); + border-color: color-mix(in srgb, var(--mk-accent) 38%, transparent); +} +.mk-entry.is-featured .mk-icon { width: 64px; height: 64px; border-radius: 22px; border: 0; background: var(--mk-on); color: var(--mk-on-ink); box-shadow: var(--mk-on-glow); } +.mk-entry.is-featured .mk-icon .mk-i { width: 32px; height: 32px; stroke-width: 2; } +.mk-entry.is-featured .hint { font-size: var(--mk-text); font-weight: 700; margin: 0 0 2px; } +.mk-entry.is-featured .now { display: block; font-size: 34px; font-weight: 800; letter-spacing: -.04em; line-height: 1.05; } +.mk-entry .grow { flex: 1; min-width: 0; } +.mk-entry .name, .mk-entry .hint, .mk-entry .now { white-space: nowrap; overflow: hidden; text-overflow: ellipsis; max-width: 100%; } +/* Something needs looking at (the gate is open): one small dot, or the featured tile turns warm */ .mk-entry.has-attention::after { - content: ""; position: absolute; top: 14px; right: 14px; width: 12px; height: 12px; border-radius: 50%; - background: var(--mk-warn); box-shadow: 0 0 0 4px color-mix(in srgb, var(--mk-warn) 30%, transparent); + content: ""; position: absolute; top: 16px; right: 16px; width: 10px; height: 10px; border-radius: 50%; + background: var(--mk-warn); box-shadow: 0 0 0 4px color-mix(in srgb, var(--mk-warn) 28%, transparent); +} +.mk-entry.is-featured.has-attention::after { content: none; } +.mk-entry.is-featured.has-attention { + background: linear-gradient(140deg, color-mix(in srgb, var(--mk-heat) 48%, transparent), color-mix(in srgb, var(--mk-warn) 14%, transparent) 50%, transparent), var(--mk-surface); + border-color: color-mix(in srgb, var(--mk-heat) 55%, transparent); +} +.mk-entry.is-featured.has-attention .mk-icon { background: linear-gradient(150deg, var(--mk-warn), var(--mk-heat)); } +.mk-entry.is-featured.has-attention .now, .mk-entry.has-attention .hint { color: var(--mk-warn); } +/* A tile that carries a safe shortcut (Close) is a group: a stretched link plus the shortcut */ +.mk-entry .mk-open { all: unset; position: absolute; inset: 0; border-radius: inherit; cursor: pointer; } +.mk-entry .mk-open:focus-visible { outline: 3px solid var(--mk-accent); outline-offset: 2px; } +.mk-shortcut { + position: relative; z-index: 1; display: inline-flex; align-items: center; gap: 6px; height: 46px; padding: 0 16px; + border: 0; border-radius: var(--mk-pill); background: var(--mk-on); color: var(--mk-on-ink); box-shadow: var(--mk-on-glow); + font: 800 var(--mk-text)/1 var(--mk-font); cursor: pointer; +} +.mk-shortcut .mk-i { width: 18px; height: 18px; stroke-width: 2.2; } + +/* Page header: a round back button and the page's name */ +.mk-topbar { display: flex; align-items: center; gap: var(--mk-3); font-size: var(--mk-text-lg); font-weight: 800; } +.mk-back { + width: 44px; height: 44px; border-radius: 50%; display: grid; place-items: center; padding: 0; color: inherit; cursor: pointer; + background: var(--mk-surface); border: 1px solid var(--mk-rim); box-shadow: var(--mk-shadow); + -webkit-backdrop-filter: blur(var(--mk-blur)); backdrop-filter: blur(var(--mk-blur)); } -.mk-entries { display: grid; grid-template-columns: 1fr 1fr; gap: var(--mk-3); } -@media (min-width: 720px) { .mk-entries { grid-template-columns: repeat(3, 1fr); } } + +/* The orb: the device, big, in a halo. Gold when calm, warm when it needs attention */ +.mk-orb { + --tint: var(--mk-accent); + position: relative; flex: none; width: 220px; height: 220px; margin: var(--mk-5) auto var(--mk-3); border-radius: 50%; display: grid; place-items: center; + color: var(--tint); + background: radial-gradient(circle at 50% 35%, var(--mk-surface-strong), transparent 72%); + border: 1px solid var(--mk-rim); + box-shadow: inset 0 1px 0 var(--mk-rim), + 0 0 0 14px color-mix(in srgb, var(--tint) 6%, transparent), + 0 0 0 28px color-mix(in srgb, var(--tint) 3%, transparent), + 0 30px 80px -20px color-mix(in srgb, var(--tint) 40%, transparent); +} +.mk-orb::after { + content: ""; position: absolute; inset: -2px; border-radius: 50%; padding: 2px; + background: conic-gradient(from 200deg, transparent, var(--tint) 30%, color-mix(in srgb, var(--tint) 60%, var(--mk-heat)) 50%, transparent 70%); + -webkit-mask: linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0); -webkit-mask-composite: xor; mask-composite: exclude; +} +.mk-orb .mk-i { width: 112px; height: 112px; stroke-width: 1.4; filter: drop-shadow(0 0 18px color-mix(in srgb, var(--tint) 55%, transparent)); } +.mk-orb.is-open, .mk-orb.is-alert { --tint: var(--mk-warn); } + +/* A hue for a place or a kind of device, on a tile or an orb */ +.mk-tint-lilac { --tint: var(--mk-lilac); --glow: 34%; } +.mk-tint-sky { --tint: var(--mk-sky); --glow: 34%; } +.mk-tint-peach { --tint: var(--mk-peach); --glow: 34%; } +.mk-tint-rose { --tint: var(--mk-rose); --glow: 34%; } +.mk-tint-mint { --tint: var(--mk-mint); --glow: 34%; } +.mk-tint-honey { --tint: var(--mk-honey); --glow: 34%; } /* State header: see first, big, then act */ -.mk-state { display: grid; gap: var(--mk-1); padding: var(--mk-5) var(--mk-2) var(--mk-4); text-align: center; } -.mk-state .now { font-size: var(--mk-display); font-weight: 800; letter-spacing: var(--mk-tight); line-height: 1; } -.mk-state .since { font-size: var(--mk-text-sm); color: var(--mk-ink-2); } +.mk-state { display: grid; gap: var(--mk-2); padding: var(--mk-2) var(--mk-2) var(--mk-4); text-align: center; } +.mk-state .now { font-size: 56px; font-weight: 800; letter-spacing: -.05em; line-height: 1; } +.mk-state .since { display: inline-flex; justify-content: center; align-items: center; gap: var(--mk-2); font-size: var(--mk-text-sm); font-weight: 600; color: var(--mk-ink-2); } +.mk-state .since.is-live::before { content: ""; width: 8px; height: 8px; border-radius: 50%; background: var(--mk-ok); box-shadow: 0 0 0 4px color-mix(in srgb, var(--mk-ok) 22%, transparent); } .mk-state.is-open .now, .mk-state.is-alert .now { color: var(--mk-warn); } -/* Action button: do. Disabled when it makes no sense, pending until confirmed */ -.mk-actions { display: grid; grid-template-columns: 1fr 1fr; gap: var(--mk-3); } +/* Actions: under the thumb, at the bottom. Risky direction first and held; safe direction alone below */ +.mk-actions { margin-top: auto; display: grid; grid-template-columns: 1fr 1fr; gap: var(--mk-3); } .mk-action { - position: relative; display: grid; place-items: center; gap: var(--mk-2); - min-height: 112px; padding: var(--mk-4); - border: 0; border-radius: var(--mk-radius); - background: var(--mk-button); color: var(--mk-button-ink); - font: 800 var(--mk-text-lg)/1 var(--mk-font); cursor: pointer; + position: relative; overflow: hidden; display: flex; align-items: center; justify-content: center; gap: var(--mk-3); + min-height: 68px; padding: 0 var(--mk-4); + border-radius: 22px; border: 1px solid var(--mk-rim); + background: var(--mk-surface); box-shadow: var(--mk-shadow); color: var(--mk-ink); + -webkit-backdrop-filter: blur(var(--mk-blur)) saturate(var(--mk-saturate)); + backdrop-filter: blur(var(--mk-blur)) saturate(var(--mk-saturate)); + font: 800 var(--mk-text-lg)/1.1 var(--mk-font); cursor: pointer; transition: transform var(--mk-fast) var(--mk-spring), opacity var(--mk-slow) var(--mk-ease); } -.mk-action .glyph { font-size: 30px; } -.mk-action:active { transform: scale(.96); } +.mk-action > .mk-i { color: var(--mk-accent); } +.mk-action .how { display: block; margin-top: 3px; font-size: 11px; font-weight: 700; letter-spacing: .06em; text-transform: uppercase; color: var(--mk-ink-3); } +.mk-action:active { transform: scale(.97); } .mk-action.is-wide { grid-column: 1 / -1; } -.mk-action:disabled { opacity: .35; cursor: default; transform: none; } +/* The one action that matters now (Close while the gate is open): gold, full width */ +.mk-action.is-primary { + grid-column: 1 / -1; min-height: 72px; border: 0; + background: var(--mk-button); color: var(--mk-button-ink); + box-shadow: 0 18px 40px -14px color-mix(in srgb, var(--mk-accent) 70%, transparent), inset 0 1px 0 rgb(255 255 255 / .5); +} +.mk-action.is-primary > .mk-i { color: inherit; } +/* Hold to confirm: for actions nobody should trigger by accident (open the gate). --hold goes 0 → 1 */ +.mk-action.is-hold { justify-content: flex-start; text-align: start; min-height: 76px; touch-action: none; user-select: none; -webkit-user-select: none; -webkit-touch-callout: none; } +.mk-action.is-hold::before { + content: ""; position: absolute; inset: 0; transform-origin: left; transform: scaleX(var(--hold, 0)); + background: linear-gradient(90deg, color-mix(in srgb, var(--mk-accent) 10%, transparent), color-mix(in srgb, var(--mk-accent) 34%, transparent)); + border-right: 2px solid var(--mk-accent); pointer-events: none; +} +.mk-action:disabled { opacity: .38; cursor: default; transform: none; } +.mk-action:disabled::before { content: none; } .mk-action.is-pending { opacity: .7; } .mk-action.is-pending::after { content: ""; position: absolute; top: 14px; right: 14px; width: 16px; height: 16px; border-radius: 50%; - border: 2px solid rgb(255 255 255 / .35); border-top-color: currentColor; animation: mk-spin .8s linear infinite; + border: 2px solid var(--mk-hairline); border-top-color: currentColor; animation: mk-spin .8s linear infinite; } /* Rare actions live apart, lower and quieter */ -.mk-rare { margin-top: var(--mk-6); padding: var(--mk-4); display: grid; gap: var(--mk-3); } -.mk-rare h2 { margin: 0; font-size: var(--mk-text); font-weight: 700; color: var(--mk-ink-2); } +.mk-rare { margin-top: var(--mk-2); text-align: center; } +.mk-rare button, .mk-rare a { all: unset; cursor: pointer; padding: var(--mk-2) var(--mk-3); font-size: var(--mk-text-sm); font-weight: 700; color: var(--mk-ink-3); } /* Device tile: off, on, pending, stale */ .mk-tile { diff --git a/packages/cli/assets/design/gallery.html b/packages/cli/assets/design/gallery.html index 78467c0..32a91ef 100644 --- a/packages/cli/assets/design/gallery.html +++ b/packages/cli/assets/design/gallery.html @@ -22,7 +22,7 @@ * same variable names, so a screen written once works in all of them. * day, glass on peach, mint and lilac (default) * warm honey, cosy evenings - * night blue and gold, wall tablets after dark + * living night blue and gold: the house favourite, dark mode * flat and high-contrast, no blur: old tablets, accessibility * * The frame has no network: no web font is fetched. "Manrope" is used when the @@ -76,6 +76,14 @@ --mk-ok: #23a26d; --mk-warn: #e58a12; --mk-alert: #e5484d; + + /* One hue per place or kind of device: a discreet way to tell tiles apart */ + --mk-lilac: #7a5cff; + --mk-sky: #2f6bff; + --mk-peach: #f0612a; + --mk-rose: #d6407f; + --mk-mint: #179a62; + --mk-honey: #c98400; } [data-theme="miel"] { @@ -105,22 +113,25 @@ [data-theme="minuit"] { color-scheme: dark; - --mk-bg: #0a1024; + /* A living night: blue and violet aurora above, a warm golden glow below */ + --mk-bg: #070b1c; --mk-bg-art: - radial-gradient(70% 50% at 50% -10%, rgb(76 107 214 / .55) 0%, transparent 70%), - radial-gradient(50% 40% at 100% 100%, rgb(29 43 87 / .9) 0%, transparent 70%), - radial-gradient(40% 35% at 0% 80%, rgb(255 166 59 / .10) 0%, transparent 70%); + radial-gradient(90% 55% at 10% -5%, rgb(82 112 235 / .75), transparent 62%), + radial-gradient(70% 45% at 105% 35%, rgb(122 92 255 / .42), transparent 65%), + radial-gradient(80% 50% at 30% 108%, rgb(255 166 59 / .34), transparent 62%), + radial-gradient(50% 30% at 95% 95%, rgb(255 110 140 / .22), transparent 70%), + linear-gradient(180deg, #121c44, #070b1c 70%); --mk-ink: #f4f1e8; --mk-ink-2: rgb(244 241 232 / .76); --mk-ink-3: rgb(244 241 232 / .58); --mk-hairline: rgb(255 255 255 / .14); - --mk-surface: linear-gradient(160deg, rgb(255 255 255 / .16), rgb(255 255 255 / .07)); + --mk-surface: linear-gradient(155deg, rgb(255 255 255 / .17), rgb(255 255 255 / .05) 60%); --mk-surface-strong: rgb(255 255 255 / .16); --mk-surface-sunken: rgb(255 255 255 / .11); --mk-selected: rgb(255 255 255 / .2); --mk-rim: rgb(255 255 255 / .22); - --mk-saturate: 1.4; - --mk-shadow: 0 1px 0 rgb(255 255 255 / .12) inset, 0 20px 50px -20px rgb(0 0 0 / .7); + --mk-saturate: 1.6; + --mk-shadow: 0 1px 0 rgb(255 255 255 / .22) inset, 0 24px 50px -26px rgb(0 0 0 / .75); --mk-accent: #ffdd6e; --mk-accent-gradient: linear-gradient(110deg, #fff1b8, #ffdd6e 40%, #ffa63b); --mk-on: linear-gradient(150deg, #ffe48a, #ffa63b); @@ -133,6 +144,12 @@ --mk-ok: #4fd39a; --mk-warn: #ffbf47; --mk-alert: #ff6b6f; + --mk-lilac: #b9a6ff; + --mk-sky: #8fb4ff; + --mk-peach: #ffb38a; + --mk-rose: #ff9cc8; + --mk-mint: #8fe3b8; + --mk-honey: #ffd56a; } [data-theme="craie"] { @@ -161,6 +178,12 @@ --mk-ok: #12804f; --mk-warn: #a85d00; --mk-alert: #c4282d; + --mk-lilac: #5b3fd9; + --mk-sky: #1d4fd6; + --mk-peach: #c44a12; + --mk-rose: #b42a63; + --mk-mint: #0f7a4b; + --mk-honey: #946200; } :root { @@ -213,6 +236,7 @@ color: var(--mk-ink); background: var(--mk-bg-art), var(--mk-bg); background-attachment: fixed; + margin: 0; -webkit-font-smoothing: antialiased; font-variant-numeric: tabular-nums; } @@ -261,58 +285,168 @@ font-size: var(--mk-text-sm); font-weight: 600; white-space: nowrap; } -/* Entry tile: the home screen opens pages, it does not show values */ +/* Icons: line icons from icons.svg / ICONS, never emoji (they clash with every palette) */ +.mk-i { width: 24px; height: 24px; flex: none; fill: none; stroke: currentColor; stroke-width: 1.8; stroke-linecap: round; stroke-linejoin: round; } + +/* Screen: a home screen or a page fills the phone down to the Miakapp bar */ +.mk-screen { + box-sizing: border-box; min-height: 100vh; min-height: 100dvh; + max-width: 560px; margin: 0 auto; + display: flex; flex-direction: column; gap: var(--mk-4); + padding: var(--mk-6) var(--mk-4) 112px; + container-type: inline-size; +} +.mk-eyebrow { font-size: var(--mk-text-xs); font-weight: 700; letter-spacing: .14em; text-transform: uppercase; color: var(--mk-ink-3); } +.mk-headline { margin: 0; font-size: var(--mk-display); font-weight: 800; letter-spacing: -.045em; line-height: 1.02; } +.mk-headline em { font-style: normal; background: var(--mk-accent-gradient); -webkit-background-clip: text; background-clip: text; color: transparent; } +.mk-headline em.is-alert { background: linear-gradient(110deg, color-mix(in srgb, var(--mk-warn) 70%, #fff), var(--mk-warn) 55%, var(--mk-heat)); -webkit-background-clip: text; background-clip: text; } + +/* Entry tile: the home screen opens pages. Tiles of different sizes fill the screen */ +.mk-entries { flex: 1; display: grid; grid-template-columns: 1fr 1fr; grid-auto-rows: minmax(108px, 1fr); gap: var(--mk-3); } +@container (min-width: 600px) { .mk-entries { grid-template-columns: repeat(3, 1fr); } } .mk-entry { - position: relative; display: grid; place-items: center; align-content: center; gap: var(--mk-2); - min-height: 132px; padding: var(--mk-4); - border-radius: var(--mk-radius); border: 1px solid var(--mk-rim); - background: var(--mk-surface); box-shadow: var(--mk-shadow); + position: relative; display: flex; flex-direction: column; justify-content: space-between; align-items: flex-start; gap: var(--mk-3); + min-height: 108px; padding: var(--mk-4); box-sizing: border-box; + border-radius: var(--mk-radius-lg); + border: 1px solid color-mix(in srgb, var(--tint, var(--mk-accent)) calc(var(--glow, 0%) + 4%), var(--mk-rim)); + background: radial-gradient(120% 90% at 0% 0%, color-mix(in srgb, var(--tint, var(--mk-accent)) var(--glow, 0%), transparent), transparent 65%), var(--mk-surface); + box-shadow: var(--mk-shadow); -webkit-backdrop-filter: blur(var(--mk-blur)) saturate(var(--mk-saturate)); backdrop-filter: blur(var(--mk-blur)) saturate(var(--mk-saturate)); - color: inherit; font: 700 var(--mk-text-lg)/1.15 var(--mk-font); text-align: center; cursor: pointer; + color: inherit; font: inherit; text-align: start; cursor: pointer; transition: transform var(--mk-fast) var(--mk-spring); } -.mk-entry:active { transform: scale(.96); } -.mk-entry .glyph { font-size: 40px; line-height: 1; } -.mk-entry.is-wide { grid-column: 1 / -1; min-height: 104px; } -.mk-entry .hint { margin-top: -2px; font-size: var(--mk-text-xs); font-weight: 600; color: var(--mk-ink-3); } -.mk-entry.has-attention .hint { color: var(--mk-warn); } -/* One small signal when something needs attention, never a full value */ +.mk-entry:active { transform: scale(.97); } +.mk-entry .mk-icon { + width: 46px; height: 46px; border-radius: 16px; display: grid; place-items: center; + color: var(--tint, var(--mk-accent)); + background: color-mix(in srgb, var(--tint, var(--mk-accent)) 22%, transparent); + border: 1px solid color-mix(in srgb, var(--tint, var(--mk-accent)) 40%, transparent); +} +.mk-entry .name { display: block; font-size: 20px; font-weight: 800; letter-spacing: -.02em; line-height: 1.1; } +/* The discreet state of the one thing a tile opens: a word or two, never a reading */ +.mk-entry .hint { display: block; margin-top: 3px; font-size: var(--mk-text-sm); font-weight: 600; color: var(--mk-ink-2); } +/* One big number a tile is about (the living-room temperature), in the tile's hue */ +.mk-entry .big { display: block; font-size: 48px; font-weight: 800; letter-spacing: -.05em; line-height: 1; color: var(--tint, var(--mk-accent)); } +.mk-entry.is-wide { grid-column: 1 / -1; flex-direction: row; align-items: center; gap: var(--mk-4); } +.mk-entry.is-tall { grid-row: span 2; } +/* The featured tile: the one most reached for (the gate, the front door). Gold, wide, first */ +.mk-entry.is-featured { + grid-column: 1 / -1; flex-direction: row; align-items: center; gap: var(--mk-4); padding: var(--mk-5); + background: linear-gradient(140deg, color-mix(in srgb, var(--mk-accent) 26%, transparent), color-mix(in srgb, var(--mk-accent) 8%, transparent) 45%, transparent), var(--mk-surface); + border-color: color-mix(in srgb, var(--mk-accent) 38%, transparent); +} +.mk-entry.is-featured .mk-icon { width: 64px; height: 64px; border-radius: 22px; border: 0; background: var(--mk-on); color: var(--mk-on-ink); box-shadow: var(--mk-on-glow); } +.mk-entry.is-featured .mk-icon .mk-i { width: 32px; height: 32px; stroke-width: 2; } +.mk-entry.is-featured .hint { font-size: var(--mk-text); font-weight: 700; margin: 0 0 2px; } +.mk-entry.is-featured .now { display: block; font-size: 34px; font-weight: 800; letter-spacing: -.04em; line-height: 1.05; } +.mk-entry .grow { flex: 1; min-width: 0; } +.mk-entry .name, .mk-entry .hint, .mk-entry .now { white-space: nowrap; overflow: hidden; text-overflow: ellipsis; max-width: 100%; } +/* Something needs looking at (the gate is open): one small dot, or the featured tile turns warm */ .mk-entry.has-attention::after { - content: ""; position: absolute; top: 14px; right: 14px; width: 12px; height: 12px; border-radius: 50%; - background: var(--mk-warn); box-shadow: 0 0 0 4px color-mix(in srgb, var(--mk-warn) 30%, transparent); + content: ""; position: absolute; top: 16px; right: 16px; width: 10px; height: 10px; border-radius: 50%; + background: var(--mk-warn); box-shadow: 0 0 0 4px color-mix(in srgb, var(--mk-warn) 28%, transparent); +} +.mk-entry.is-featured.has-attention::after { content: none; } +.mk-entry.is-featured.has-attention { + background: linear-gradient(140deg, color-mix(in srgb, var(--mk-heat) 48%, transparent), color-mix(in srgb, var(--mk-warn) 14%, transparent) 50%, transparent), var(--mk-surface); + border-color: color-mix(in srgb, var(--mk-heat) 55%, transparent); +} +.mk-entry.is-featured.has-attention .mk-icon { background: linear-gradient(150deg, var(--mk-warn), var(--mk-heat)); } +.mk-entry.is-featured.has-attention .now, .mk-entry.has-attention .hint { color: var(--mk-warn); } +/* A tile that carries a safe shortcut (Close) is a group: a stretched link plus the shortcut */ +.mk-entry .mk-open { all: unset; position: absolute; inset: 0; border-radius: inherit; cursor: pointer; } +.mk-entry .mk-open:focus-visible { outline: 3px solid var(--mk-accent); outline-offset: 2px; } +.mk-shortcut { + position: relative; z-index: 1; display: inline-flex; align-items: center; gap: 6px; height: 46px; padding: 0 16px; + border: 0; border-radius: var(--mk-pill); background: var(--mk-on); color: var(--mk-on-ink); box-shadow: var(--mk-on-glow); + font: 800 var(--mk-text)/1 var(--mk-font); cursor: pointer; +} +.mk-shortcut .mk-i { width: 18px; height: 18px; stroke-width: 2.2; } + +/* Page header: a round back button and the page's name */ +.mk-topbar { display: flex; align-items: center; gap: var(--mk-3); font-size: var(--mk-text-lg); font-weight: 800; } +.mk-back { + width: 44px; height: 44px; border-radius: 50%; display: grid; place-items: center; padding: 0; color: inherit; cursor: pointer; + background: var(--mk-surface); border: 1px solid var(--mk-rim); box-shadow: var(--mk-shadow); + -webkit-backdrop-filter: blur(var(--mk-blur)); backdrop-filter: blur(var(--mk-blur)); +} + +/* The orb: the device, big, in a halo. Gold when calm, warm when it needs attention */ +.mk-orb { + --tint: var(--mk-accent); + position: relative; flex: none; width: 220px; height: 220px; margin: var(--mk-5) auto var(--mk-3); border-radius: 50%; display: grid; place-items: center; + color: var(--tint); + background: radial-gradient(circle at 50% 35%, var(--mk-surface-strong), transparent 72%); + border: 1px solid var(--mk-rim); + box-shadow: inset 0 1px 0 var(--mk-rim), + 0 0 0 14px color-mix(in srgb, var(--tint) 6%, transparent), + 0 0 0 28px color-mix(in srgb, var(--tint) 3%, transparent), + 0 30px 80px -20px color-mix(in srgb, var(--tint) 40%, transparent); } -.mk-entries { display: grid; grid-template-columns: 1fr 1fr; gap: var(--mk-3); } -@media (min-width: 720px) { .mk-entries { grid-template-columns: repeat(3, 1fr); } } +.mk-orb::after { + content: ""; position: absolute; inset: -2px; border-radius: 50%; padding: 2px; + background: conic-gradient(from 200deg, transparent, var(--tint) 30%, color-mix(in srgb, var(--tint) 60%, var(--mk-heat)) 50%, transparent 70%); + -webkit-mask: linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0); -webkit-mask-composite: xor; mask-composite: exclude; +} +.mk-orb .mk-i { width: 112px; height: 112px; stroke-width: 1.4; filter: drop-shadow(0 0 18px color-mix(in srgb, var(--tint) 55%, transparent)); } +.mk-orb.is-open, .mk-orb.is-alert { --tint: var(--mk-warn); } + +/* A hue for a place or a kind of device, on a tile or an orb */ +.mk-tint-lilac { --tint: var(--mk-lilac); --glow: 34%; } +.mk-tint-sky { --tint: var(--mk-sky); --glow: 34%; } +.mk-tint-peach { --tint: var(--mk-peach); --glow: 34%; } +.mk-tint-rose { --tint: var(--mk-rose); --glow: 34%; } +.mk-tint-mint { --tint: var(--mk-mint); --glow: 34%; } +.mk-tint-honey { --tint: var(--mk-honey); --glow: 34%; } /* State header: see first, big, then act */ -.mk-state { display: grid; gap: var(--mk-1); padding: var(--mk-5) var(--mk-2) var(--mk-4); text-align: center; } -.mk-state .now { font-size: var(--mk-display); font-weight: 800; letter-spacing: var(--mk-tight); line-height: 1; } -.mk-state .since { font-size: var(--mk-text-sm); color: var(--mk-ink-2); } +.mk-state { display: grid; gap: var(--mk-2); padding: var(--mk-2) var(--mk-2) var(--mk-4); text-align: center; } +.mk-state .now { font-size: 56px; font-weight: 800; letter-spacing: -.05em; line-height: 1; } +.mk-state .since { display: inline-flex; justify-content: center; align-items: center; gap: var(--mk-2); font-size: var(--mk-text-sm); font-weight: 600; color: var(--mk-ink-2); } +.mk-state .since.is-live::before { content: ""; width: 8px; height: 8px; border-radius: 50%; background: var(--mk-ok); box-shadow: 0 0 0 4px color-mix(in srgb, var(--mk-ok) 22%, transparent); } .mk-state.is-open .now, .mk-state.is-alert .now { color: var(--mk-warn); } -/* Action button: do. Disabled when it makes no sense, pending until confirmed */ -.mk-actions { display: grid; grid-template-columns: 1fr 1fr; gap: var(--mk-3); } +/* Actions: under the thumb, at the bottom. Risky direction first and held; safe direction alone below */ +.mk-actions { margin-top: auto; display: grid; grid-template-columns: 1fr 1fr; gap: var(--mk-3); } .mk-action { - position: relative; display: grid; place-items: center; gap: var(--mk-2); - min-height: 112px; padding: var(--mk-4); - border: 0; border-radius: var(--mk-radius); - background: var(--mk-button); color: var(--mk-button-ink); - font: 800 var(--mk-text-lg)/1 var(--mk-font); cursor: pointer; + position: relative; overflow: hidden; display: flex; align-items: center; justify-content: center; gap: var(--mk-3); + min-height: 68px; padding: 0 var(--mk-4); + border-radius: 22px; border: 1px solid var(--mk-rim); + background: var(--mk-surface); box-shadow: var(--mk-shadow); color: var(--mk-ink); + -webkit-backdrop-filter: blur(var(--mk-blur)) saturate(var(--mk-saturate)); + backdrop-filter: blur(var(--mk-blur)) saturate(var(--mk-saturate)); + font: 800 var(--mk-text-lg)/1.1 var(--mk-font); cursor: pointer; transition: transform var(--mk-fast) var(--mk-spring), opacity var(--mk-slow) var(--mk-ease); } -.mk-action .glyph { font-size: 30px; } -.mk-action:active { transform: scale(.96); } +.mk-action > .mk-i { color: var(--mk-accent); } +.mk-action .how { display: block; margin-top: 3px; font-size: 11px; font-weight: 700; letter-spacing: .06em; text-transform: uppercase; color: var(--mk-ink-3); } +.mk-action:active { transform: scale(.97); } .mk-action.is-wide { grid-column: 1 / -1; } -.mk-action:disabled { opacity: .35; cursor: default; transform: none; } +/* The one action that matters now (Close while the gate is open): gold, full width */ +.mk-action.is-primary { + grid-column: 1 / -1; min-height: 72px; border: 0; + background: var(--mk-button); color: var(--mk-button-ink); + box-shadow: 0 18px 40px -14px color-mix(in srgb, var(--mk-accent) 70%, transparent), inset 0 1px 0 rgb(255 255 255 / .5); +} +.mk-action.is-primary > .mk-i { color: inherit; } +/* Hold to confirm: for actions nobody should trigger by accident (open the gate). --hold goes 0 → 1 */ +.mk-action.is-hold { justify-content: flex-start; text-align: start; min-height: 76px; touch-action: none; user-select: none; -webkit-user-select: none; -webkit-touch-callout: none; } +.mk-action.is-hold::before { + content: ""; position: absolute; inset: 0; transform-origin: left; transform: scaleX(var(--hold, 0)); + background: linear-gradient(90deg, color-mix(in srgb, var(--mk-accent) 10%, transparent), color-mix(in srgb, var(--mk-accent) 34%, transparent)); + border-right: 2px solid var(--mk-accent); pointer-events: none; +} +.mk-action:disabled { opacity: .38; cursor: default; transform: none; } +.mk-action:disabled::before { content: none; } .mk-action.is-pending { opacity: .7; } .mk-action.is-pending::after { content: ""; position: absolute; top: 14px; right: 14px; width: 16px; height: 16px; border-radius: 50%; - border: 2px solid rgb(255 255 255 / .35); border-top-color: currentColor; animation: mk-spin .8s linear infinite; + border: 2px solid var(--mk-hairline); border-top-color: currentColor; animation: mk-spin .8s linear infinite; } /* Rare actions live apart, lower and quieter */ -.mk-rare { margin-top: var(--mk-6); padding: var(--mk-4); display: grid; gap: var(--mk-3); } -.mk-rare h2 { margin: 0; font-size: var(--mk-text); font-weight: 700; color: var(--mk-ink-2); } +.mk-rare { margin-top: var(--mk-2); text-align: center; } +.mk-rare button, .mk-rare a { all: unset; cursor: pointer; padding: var(--mk-2) var(--mk-3); font-size: var(--mk-text-sm); font-weight: 700; color: var(--mk-ink-3); } /* Device tile: off, on, pending, stale */ .mk-tile { @@ -409,10 +543,12 @@ /* Stage: the screens take the selected theme */ .stage { border-radius: 36px; padding: 34px 24px; display: flex; flex-wrap: wrap; gap: 30px; justify-content: center; align-items: flex-start; color: var(--mk-ink); background: var(--mk-bg-art), var(--mk-bg); font-family: var(--mk-font); font-variant-numeric: tabular-nums; transition: background .4s; } -.phone { width: 340px; height: 720px; border-radius: 46px; padding: 10px; background: #0b0b0f; box-shadow: 0 40px 80px -30px rgb(0 0 0 / .45); flex: none; } +.phone { width: 390px; height: 844px; border-radius: 46px; padding: 10px; background: #0b0b0f; box-shadow: 0 40px 80px -30px rgb(0 0 0 / .45); flex: none; } .screen { position: relative; width: 100%; height: 100%; border-radius: 37px; overflow: hidden; background: var(--mk-bg-art), var(--mk-bg); color: var(--mk-ink); } .screen .scroll { position: absolute; inset: 0; overflow: auto; padding: 46px 16px 100px; scrollbar-width: none; } .screen .status { position: absolute; top: 0; left: 0; right: 0; height: 40px; display: flex; justify-content: space-between; align-items: center; padding: 6px 26px 0; font-size: 13px; font-weight: 700; z-index: 3; } +.screen .mk-screen { position: absolute; inset: 0; overflow: auto; min-height: 0; padding: 50px 14px 96px; gap: 14px; scrollbar-width: none; } +.screen .mk-headline { font-size: 34px; } .screen .tabdock { position: absolute; left: 14px; right: 14px; bottom: 14px; z-index: 3; } .caption { text-align: center; margin-top: 12px; font-size: 13px; font-weight: 700; color: var(--mk-ink-2); } .tablet { width: 860px; max-width: 100%; aspect-ratio: 16 / 9.4; border-radius: 34px; padding: 12px; background: #0b0b0f; box-shadow: 0 40px 80px -30px rgb(0 0 0 / .45); } @@ -480,7 +616,7 @@ body.mk-app { padding: 18px 12px 60px; } .switch-themes { margin-left: 0; width: 100%; overflow-x: auto; } .stage { padding: 18px 8px; border-radius: 24px; } - .phone { width: 100%; max-width: 340px; } + .phone { width: 100%; max-width: 390px; } .tablet { aspect-ratio: auto; height: 900px; } .wall { grid-template-columns: 1fr; overflow: auto; } } @@ -488,21 +624,47 @@
@@ -523,47 +685,108 @@

Miakapp inspirations

Example screens · tap the tiles

- +
21:47●●● 87 %
-
-
Good evening
-
The gate is open
+
+
+
Friday, November 14
+

Good evening.
All closed.

+
- - - - - - + + + + + +
Home · opens, never shows
- - +
21:47●●● 87 %
-
-
- -
-
Gate open
for 4 min
+
+
Gate
+
+
Closed
Live
- - - + + +
-

Adjust the recorded state

+
+
-
A device page
+
A device page · hold “Open”, then tap “Close”
+
+ +
+
+
21:47●●● 87 %
+
+
+
Friday, November 14
+

Good evening.
Gate open.

+
+
+
+ + + GateOpenfor 4 min + +
+ + + + + +
+
+ +
+
Home · something to close stands out
-
@@ -575,10 +798,10 @@

Example screens · tap the tiles

Living room
- - - - + + + +
@@ -590,12 +813,12 @@

Example screens · tap the tiles

-
Blinds60 % open
+
Blinds60 % open
TVvia infrared
-
+
@@ -645,10 +868,10 @@

Example screens · tap the tiles

Scenes
- - - - + + + +
Heating
@@ -659,7 +882,7 @@

Example screens · tap the tiles

Front door
LIVE
- +
@@ -681,24 +904,26 @@

Themes · click to try

States an interface must show

-
Off
-
On
-
Command sent
-
Stale value
+
Off
+
On
+
Command sent
+
Stale value

Principles

-
A remote, not a dashboardThe home screen opens pages and fits one screen. One page per thing. Upkeep (batteries, silent sensors) goes elsewhere.
-
Open, see, doA tile opens. A page shows the state first, big, then large action buttons, then the rare ones apart. Never mixed.
-
Room or device type, never both“Bedrooms” or “Shutters”, not a rooms section next to a temperatures section repeating them. Words residents use.
-
What is on glowsOne strong signal: the golden glow of something that is on. Everything else stays calm.
-
One gradient per screenThe gradient is a signature: one title or one key number, not everywhere.
+
A remote, not a dashboardThe home screen opens pages and fills one screen, no more. One page per thing. Upkeep (batteries, silent sensors) goes elsewhere.
+
A living backgroundKeep the theme’s halos (--mk-bg-art): glass needs light behind it. A flat colour makes every tile look dead.
+
One featured tile, then sizesThe thing most reached for is wide and gold, first. Others vary: a tall tile for a big number, regular ones, a wide one to close the grid.
+
A hue per place, gold for the starA discreet tint per place or kind of device (.mk-tint-*) tells tiles apart. One hue per tile, never more.
+
Line icons, never emojiEmoji bring their own colours and styles. Use the shipped icons (icons.svg, icon()).
+
Open: never by accidentActions that open, unlock or start sit together on the first row and are held to confirm. Never from the home screen.
+
Close: never forgottenAlone on its row, one tap. While something is open it becomes the gold button, and the home tile turns warm with a Close shortcut.
+
Buttons never moveOpen or closed, each button keeps its place; only its emphasis changes. The thumb learns where to tap.
Say when the home goes quietDim stale values, show an honest banner, disable controls. Never a fake “up to date”.
-
Big fingers, little text44 px targets at least, tabular digits, three text sizes per screen.
-
Free to change everythingThe tokens are a starting point. Make the home’s own theme with its residents.
+