Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
51 changes: 51 additions & 0 deletions packages/cli/assets/design/components.css
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,57 @@
font-size: var(--mk-text-sm); font-weight: 600; white-space: nowrap;
}

/* Entry tile: the home screen opens pages, it does not show values */
.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);
-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;
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; }
/* One small signal when something needs attention, never a full value */
.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);
}
.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); } }

/* 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.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); }
.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;
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.is-wide { grid-column: 1 / -1; }
.mk-action:disabled { opacity: .35; cursor: default; transform: 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;
}
/* 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); }

/* Device tile: off, on, pending, stale */
.mk-tile {
position: relative; display: grid; gap: var(--mk-1); align-content: space-between;
Expand Down
125 changes: 99 additions & 26 deletions packages/cli/assets/design/gallery.html
Original file line number Diff line number Diff line change
Expand Up @@ -261,6 +261,57 @@
font-size: var(--mk-text-sm); font-weight: 600; white-space: nowrap;
}

/* Entry tile: the home screen opens pages, it does not show values */
.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);
-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;
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; }
/* One small signal when something needs attention, never a full value */
.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);
}
.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); } }

/* 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.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); }
.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;
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.is-wide { grid-column: 1 / -1; }
.mk-action:disabled { opacity: .35; cursor: default; transform: 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;
}
/* 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); }

/* Device tile: off, on, pending, stale */
.mk-tile {
position: relative; display: grid; gap: var(--mk-1); align-content: space-between;
Expand Down Expand Up @@ -470,39 +521,45 @@ <h1>Miakapp <span class="mk-gradient-text">inspirations</span></h1>
<h2 class="sec">Example screens · tap the tiles</h2>
<div class="stage" id="stage" data-theme="minuit">

<!-- 1. Accueil -->
<!-- 1. Accueil : it opens pages, it does not show values. No scrolling. -->
<div>
<div class="phone"><div class="screen">
<div class="status"><span>21:47</span><span>●●● 87 %</span></div>
<div class="scroll">
<div class="row between" style="margin:6px 2px 2px">
<span class="mk-meta" style="font-weight:700">Friday, November 14</span>
<span class="mk-chip"><svg class="i" style="width:16px;height:16px"><use href="#cloud"/></svg>54° · Drizzle</span>
</div>
<div class="mk-display" style="margin:10px 2px 4px">Good evening,<br><span class="mk-gradient-text">the house</span> is on watch.</div>
<div class="hscroll" style="margin-top:12px">
<span class="mk-chip"><svg class="i" style="width:16px;height:16px"><use href="#bulb"/></svg>3 lights on</span>
<span class="mk-chip"><svg class="i" style="width:16px;height:16px"><use href="#thermo"/></svg>71 °F inside</span>
<span class="mk-chip"><svg class="i" style="width:16px;height:16px"><use href="#lock"/></svg>All locked</span>
<div class="mk-display" style="margin:10px 2px 6px">Good evening</div>
<div class="mk-stale-banner" style="margin:0 0 16px">The gate is open</div>
<div class="mk-entries">
<button class="mk-entry has-attention"><span class="glyph">🚗</span>Gate</button>
<button class="mk-entry"><span class="glyph">🏡</span>House</button>
<button class="mk-entry"><span class="glyph">🪟</span>Shutters</button>
<button class="mk-entry"><span class="glyph">🛏️</span>Bedrooms</button>
<button class="mk-entry"><span class="glyph">🌡️</span>Heating</button>
<button class="mk-entry"><span class="glyph">📷</span>Cameras</button>
</div>
<div class="sechead"><h3>Scenes</h3><a href="#">Edit</a></div>
<div class="hscroll">
<button class="mk-scene"><span class="emoji">🛋️</span>Evening</button>
<button class="mk-scene"><span class="emoji">🎬</span>Movie</button>
<button class="mk-scene"><span class="emoji">🌙</span>Bedtime</button>
<button class="mk-scene"><span class="emoji">👋</span>Leaving</button>
</div>
<div class="tabdock"><nav class="mk-tabbar mk-glass miakapp-bar" aria-label="Miakapp bar"><a aria-current="page" href="#"><svg class="i"><use href="#home"/></svg>Home</a><span class="agent-slot"><a href="#"><svg class="i"><use href="#grid"/></svg>Rooms</a><a href="#"><svg class="i"><use href="#bolt"/></svg>Energy</a></span><a href="#" class="me"><span class="avatar">A</span>Me</a></nav></div>
</div></div>
<div class="caption">Home · opens, never shows</div>
</div>

<!-- 1b. Une page d'appareil : state first, then actions, rare things apart -->
<div>
<div class="phone"><div class="screen">
<div class="status"><span>21:47</span><span>●●● 87 %</span></div>
<div class="scroll">
<div class="row between" style="margin:4px 0 0">
<span class="iconbtn mk-glass"><svg class="i"><use href="#back"/></svg></span>
</div>
<div class="sechead"><h3>Rooms</h3><a href="#">See all</a></div>
<div class="grid2">
<div class="mk-glass room"><div class="row between"><span class="t">Living room</span><svg class="i"><use href="#tv"/></svg></div><div><div class="mk-label">71° · 2 lights on</div><div class="dots" style="margin-top:8px"><span class="on"></span><span class="on"></span><span></span><span></span></div></div></div>
<div class="mk-glass room"><div class="row between"><span class="t">Kitchen</span><svg class="i"><use href="#plug"/></svg></div><div><div class="mk-label">69° · 1 light on</div><div class="dots" style="margin-top:8px"><span class="on"></span><span></span><span></span></div></div></div>
<div class="mk-glass room"><div class="row between"><span class="t">Bedroom</span><svg class="i"><use href="#moon"/></svg></div><div><div class="mk-label">66° · blinds closed</div><div class="dots" style="margin-top:8px"><span></span><span></span></div></div></div>
<div class="mk-glass room"><div class="row between"><span class="t">Office</span><svg class="i"><use href="#bolt"/></svg></div><div><div class="mk-label">68° · all off</div><div class="dots" style="margin-top:8px"><span></span><span></span><span></span></div></div></div>
<div class="mk-state is-open" id="gate-state"><div class="now">Gate open</div><div class="since">for 4 min</div></div>
<div class="mk-actions">
<button class="mk-action" data-gate="open" disabled><span class="glyph">🚗</span>Open</button>
<button class="mk-action" data-gate="walk"><span class="glyph">🚶</span>Pedestrian</button>
<button class="mk-action is-wide" data-gate="close"><span class="glyph">🔒</span>Close</button>
</div>
<div class="mk-glass mk-rare"><h2>Adjust the recorded state</h2><button class="mk-button is-quiet">The gate is actually closed</button></div>
</div>
<div class="tabdock"><nav class="mk-tabbar mk-glass miakapp-bar" aria-label="Miakapp bar"><a aria-current="page" href="#"><svg class="i"><use href="#home"/></svg>Home</a><span class="agent-slot"><a href="#"><svg class="i"><use href="#grid"/></svg>Rooms</a><a href="#"><svg class="i"><use href="#bolt"/></svg>Energy</a></span><a href="#" class="me"><span class="avatar">A</span>Me</a></nav></div>
</div></div>
<div class="caption">Home</div>
<div class="caption">A device page</div>
</div>

<!-- 2. Pièce -->
Expand Down Expand Up @@ -540,7 +597,7 @@ <h2 class="sec">Example screens · tap the tiles</h2>
</div>
</div>
</div></div>
<div class="caption">A room</div>
<div class="caption">A room (grouped by room)</div>
</div>

<!-- 3. Énergie -->
Expand Down Expand Up @@ -630,7 +687,9 @@ <h2 class="sec">States an interface must show</h2>

<h2 class="sec">Principles</h2>
<div class="rules">
<div class="mk-glass"><b>The home first, not the devices</b>Organize by room and by moment of the day. Words residents use, never a technical identifier.</div>
<div class="mk-glass"><b>A remote, not a dashboard</b>The home screen opens pages and fits one screen. One page per thing. Upkeep (batteries, silent sensors) goes elsewhere.</div>
<div class="mk-glass"><b>Open, see, do</b>A tile opens. A page shows the state first, big, then large action buttons, then the rare ones apart. Never mixed.</div>
<div class="mk-glass"><b>Room or device type, never both</b>“Bedrooms” or “Shutters”, not a rooms section next to a temperatures section repeating them. Words residents use.</div>
<div class="mk-glass"><b>What is on glows</b>One strong signal: the golden glow of something that is on. Everything else stays calm.</div>
<div class="mk-glass"><b>One gradient per screen</b>The gradient is a signature: one title or one key number, not everywhere.</div>
<div class="mk-glass"><b>Say when the home goes quiet</b>Dim stale values, show an honest banner, disable controls. Never a fake “up to date”.</div>
Expand Down Expand Up @@ -658,6 +717,20 @@ <h2 class="sec">Principles</h2>
const v = t.querySelector('.value'); v.textContent = on ? v.dataset.on : v.dataset.off;
}, 550);
}));
document.querySelectorAll('[data-gate]').forEach(b => b.addEventListener('click', () => {
const all = document.querySelectorAll('[data-gate]');
if (b.disabled || b.classList.contains('is-pending')) return;
b.classList.add('is-pending');
setTimeout(() => {
b.classList.remove('is-pending');
const open = b.dataset.gate !== 'close';
const st = document.getElementById('gate-state');
st.classList.toggle('is-open', open);
st.querySelector('.now').textContent = open ? 'Gate open' : 'Gate closed';
st.querySelector('.since').textContent = 'just now';
all.forEach(x => { x.disabled = open ? x.dataset.gate !== 'close' : x.dataset.gate === 'close'; });
}, 700);
}));
document.querySelectorAll('.mk-level').forEach(r => r.addEventListener('input', () => r.style.setProperty('--level', r.value + '%')));
const kwh = [.18,.15,.14,.13,.14,.16,.32,.48,.36,.22,.2,.24,.38,.3,.22,.2,.26,.42,.62,.71,.55];
const max = Math.max(...kwh), bars = document.getElementById('bars'), now = kwh.length - 1;
Expand Down
52 changes: 47 additions & 5 deletions packages/cli/assets/design/guide.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,9 @@ The files sit next to this guide, in the design directory printed at the end:
| File | What it is |
|---|---|
| `tokens.css` | `--mk-*` variables (color, glass, radius, type, space, motion) for four themes |
| `components.css` | Recipes on top of the tokens: glass, device tile, scene, switch, level slider, stale banner |
| `components.css` | Recipes on top of the tokens: glass, entry tile, state header, action button, device tile, scene, switch, level slider, stale banner |
| `fonts.css` | Manrope as a `data:` URL, ready for the frame, which has no network (OFL, see `OFL-Manrope.txt`) |
| `gallery.html` | Example screens built only with the files above: home, room, energy, wall tablet. Open it in a browser, switch themes, read its source |
| `gallery.html` | Example screens built only with the files above: home, a device page, a room, energy, wall tablet. Open it in a browser, switch themes, read its source |

`miakapp init --starter app` writes all three stylesheets into `app/theme.ts`
(`THEME_CSS`) and already uses them, so the first build looks like Miakapp.
Expand All @@ -38,11 +38,53 @@ A home's own theme is one more `[data-theme="…"]` block redefining the same
variables. Keep text contrast at 4.5:1 or more, including secondary text on
glass; check dark themes twice (dark blue on darker blue is easy to miss).

## How a home app works

A home app is a remote control, not a dashboard: people open it, do one thing
and close it. Lay it out that way.

1. **The home screen opens, it does not show.** Four to eight large entry tiles
(`.mk-entry`): the places and devices people actually reach for, the most
used first (in most houses, the gate or the front door). It fits on one
phone screen: no scrolling. At most one line above the tiles, and only when
something needs attention ("The gate is open", "Alarm on").
2. **One page per tile, about one thing.** Never put unrelated information on
the same page: the living-room temperature has nothing to do with the
garage door.
3. **Group by room or by type of device, never both.** "Bedrooms" or
"Shutters" are both fine entries; a "Rooms" section next to a "Temperatures"
section that repeats the same rooms is not.
4. **Open, see, do: never mixed.** A tile opens a page. On that page, the
state comes first and big (`.mk-state`: "Gate closed"), then the actions as
large buttons (`.mk-action`: Open, Pedestrian, Close), then, separately and
lower, the rare ones (adjust the recorded state, calibration). A value is
not a button and a button is not a value.
5. **Actions that make no sense are disabled.** "Close" is greyed while the
gate is closed. While a command runs, its button shows `is-pending` until
the next state confirms it.
6. **Upkeep stays off the main path.** A dead battery, a sensor silent for
months, a device that never answered: send a notification or put it in an
"Upkeep" page. Never next to the living-room temperature, and never as a
value of the room it belongs to.
7. **Hierarchy, not repetition.** On each page one thing stands out (the
state, a big number); everything else is smaller and calmer. Use colour for
meaning (on, open, alert), not to decorate. If every block has the same size
and colour, the page has no hierarchy.
8. **Phone first.** Design at 360 px wide with the thumb at the bottom; then
check a wall tablet and a desktop. Do not line up six cards because the
desktop is wide.
9. **Each resident gets their own home screen.** A guest allowed only the
gate sees one tile, or lands straight on the gate page.

An entry tile may carry one small signal when something needs attention
(`.mk-entry.has-attention`, e.g. the gate is open): never a full value.

## Principles

1. **The home first, not the devices.** Organize by room and by moment of the
day (evening, leaving, bedtime). Use the words residents use; never show a
state path or a device identifier.
1. **The home first, not the devices.** Organize by room or by type of
device (never both), plus scenes for moments of the day (evening, leaving,
bedtime). Use the words residents use; never show a state path or a device
identifier.
2. **What is on glows.** One strong signal per screen: the golden `--mk-on`
glow of a light, plug or scene that is on. Everything else stays calm.
3. **One gradient per screen.** `--mk-accent-gradient` is a signature for one
Expand Down
Loading
Loading