Dashboard Studio 1.14.0: new cards, kiosk announcements, guest mode, templates, sharing and phone app

- Seven new cards: parcels, air quality, water and gas usage, solar forecast, garage and gate, sports, Formula One
- Kiosk: announcements from automations, tab rotation, guest mode, click sound
- Editor: templates, widget presets, undo history, alignment guides, Lovelace import, share link and QR code, theme variants, suggestions, touch and hold actions
- Phones: installable app with shortcuts, offline states, tab swipe, pull to refresh, bottom sheets, haptics, phone/large visibility, landscape layout, editing on the phone, away tab
- Fixed: the weather forecast and to-do list could stay on Loading…

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-26 21:08:42 +01:00
parent 93bb42dcbb
commit 79d4a0f011
17 changed files with 2256 additions and 58 deletions
+242 -4
View File
@@ -1093,6 +1093,238 @@ body.kiosk #tabnav { top: 6px; max-height: calc(100vh - 20px); }
}
@container (max-height: 120px) { .tz.tz .tz-ctrls { display: none; } }
/* parcels */
.pc { display: flex; flex-direction: column; gap: 10px; min-height: 0; flex: 1; }
.pc-sum { font-size: 14px; color: var(--d-text-2); }
.pc-sum b { font-size: 22px; color: var(--d-text); margin-right: 2px; }
.pc-today { color: var(--d-accent); font-weight: 600; }
.pc-list { display: flex; flex-direction: column; gap: 6px; overflow-y: auto; min-height: 0; }
.pc-row { display: flex; align-items: center; gap: 10px; padding: 8px 10px; border-radius: 12px; background: var(--d-fill); color: inherit; text-decoration: none; min-width: 0; }
.pc-row.today { background: color-mix(in srgb, var(--d-accent) 14%, transparent); }
.pc-row.done { opacity: .6; }
.pc-ico { width: 32px; height: 32px; border-radius: 50%; display: grid; place-items: center; background: var(--d-tile); font-size: 17px; flex: none; }
.pc-row.today .pc-ico { color: var(--d-accent); }
.pc-row.done .pc-ico { color: var(--ok); }
.pc-text { flex: 1; min-width: 0; display: flex; flex-direction: column; }
.pc-name { font-size: 14px; font-weight: 600; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.pc-status { font-size: 12px; color: var(--d-text-2); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.pc-when { font-size: 12px; font-weight: 600; color: var(--d-text-2); white-space: nowrap; text-align: right; }
.pc-row.today .pc-when { color: var(--d-accent); }
/* air quality */
.aq { display: flex; flex-direction: column; gap: 6px; overflow-y: auto; min-height: 0; }
.aq-row { --aq: var(--ok); display: grid; grid-template-columns: auto minmax(0, 1fr) auto; align-items: center; gap: 10px; padding: 8px 10px; border-radius: 12px; background: var(--d-fill); }
.aq-row.lvl1 { --aq: var(--warn); } .aq-row.lvl2 { --aq: var(--crit); }
.aq-dot { width: 10px; height: 10px; border-radius: 50%; background: var(--aq); box-shadow: 0 0 0 4px color-mix(in srgb, var(--aq) 22%, transparent); }
.aq-room { display: flex; flex-direction: column; min-width: 0; }
.aq-room b { font-size: 14px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.aq-room span { font-size: 12px; color: var(--d-text-2); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.aq-row.lvl2 .aq-room span { color: var(--crit); font-weight: 600; }
.aq-vals { display: flex; gap: 4px; }
.aq-val { display: flex; flex-direction: column; align-items: center; min-width: 44px; padding: 3px 6px; border-radius: 9px; background: var(--d-tile); }
.aq-val small { font-size: 10px; color: var(--d-text-2); }
.aq-val span { font-size: 13px; font-weight: 600; }
.aq-val.lvl1 span { color: var(--warn); } .aq-val.lvl2 span { color: var(--crit); }
@container (max-width: 300px) { .aq-val small { display: none; } .aq-val { min-width: 36px; } }
/* water and gas */
.us-head { display: flex; align-items: center; gap: 8px; margin-bottom: 8px; min-width: 0; }
.us-head .name { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.us-ico { width: 30px; height: 30px; border-radius: 50%; display: grid; place-items: center; font-size: 16px; color: #0a84ff; background: color-mix(in srgb, #0a84ff 16%, transparent); flex: none; }
.us-ico.gas { color: #ff9f0a; background: color-mix(in srgb, #ff9f0a 16%, transparent); }
.us-ico.electricity { color: var(--ok); background: color-mix(in srgb, var(--ok) 16%, transparent); }
.us { display: flex; flex-direction: column; gap: 10px; flex: 1; min-height: 0; }
.us-now { display: flex; flex-direction: column; gap: 2px; }
.us-big { font-size: clamp(26px, 9cqw, 38px); font-weight: 700; letter-spacing: -0.02em; line-height: 1; }
.us-big small { font-size: .45em; font-weight: 600; color: var(--d-text-2); margin-left: 3px; }
.us-cmp { font-size: 12px; color: var(--d-text-2); }
.us-cmp .up { color: var(--warn); } .us-cmp .down { color: var(--ok); }
.us-bars { position: relative; flex: 1; min-height: 40px; display: flex; align-items: stretch; gap: 4px; }
.us-day { flex: 1; display: flex; flex-direction: column; align-items: center; justify-content: flex-end; gap: 4px; min-width: 0; }
.us-bar { width: 100%; max-width: 26px; border-radius: 5px 5px 2px 2px; background: color-mix(in srgb, var(--d-text) 18%, transparent); }
.us-day.today .us-bar { background: #0a84ff; }
.us.gas .us-day.today .us-bar { background: #ff9f0a; }
.us.electricity .us-day.today .us-bar { background: var(--ok); }
.us-lbl { font-size: 10px; color: var(--d-text-2); }
.us-avg { position: absolute; left: 0; right: 0; height: 0; border-top: 1px dashed var(--d-text-2); opacity: .6; margin-bottom: 18px; pointer-events: none; }
.us-foot { display: flex; justify-content: space-between; gap: 8px; font-size: 12px; color: var(--d-text-2); }
@container (max-height: 200px) { .us-bars, .us-foot { display: none; } }
/* solar forecast */
.sf { display: flex; flex-direction: column; gap: 12px; flex: 1; min-height: 0; }
.sf-top { display: flex; align-items: flex-end; gap: 18px; flex-wrap: wrap; }
.sf-top > div { display: flex; flex-direction: column; }
.sf-lbl { font-size: 12px; color: var(--d-text-2); }
.sf-big { font-size: clamp(28px, 10cqw, 42px); font-weight: 700; letter-spacing: -0.02em; line-height: 1; }
.sf-mid { font-size: 18px; font-weight: 600; }
.sf-big small, .sf-mid small { font-size: .5em; color: var(--d-text-2); margin-left: 3px; font-weight: 600; }
.sf-bars { flex: 1; min-height: 50px; display: flex; align-items: stretch; gap: 3px; padding-bottom: 14px; }
.sf-h { position: relative; flex: 1; display: flex; align-items: flex-end; }
.sf-h i { width: 100%; border-radius: 4px 4px 1px 1px; background: color-mix(in srgb, #ffcc00 55%, transparent); }
.sf-h.past i { opacity: .45; }
.sf-h.best i { background: #ff9f0a; }
.sf-h.now i { background: #ffcc00; box-shadow: 0 0 0 2px var(--d-tile), 0 0 0 3px #ffcc00; }
.sf-h span { position: absolute; bottom: -15px; left: 50%; transform: translateX(-50%); font-size: 10px; color: var(--d-text-2); }
.sf-best { display: flex; align-items: center; gap: 8px; font-size: 13px; font-weight: 500; padding: 8px 10px; border-radius: 12px; background: color-mix(in srgb, #ff9f0a 14%, transparent); }
.sf-best .i { color: #ff9f0a; font-size: 18px; flex: none; }
@container (max-height: 220px) { .sf-bars { display: none; } }
/* garage and gate */
.gt { display: grid; grid-template-rows: auto 1fr auto; gap: 8px; height: 100%; min-height: 0; }
.gt-head { display: flex; align-items: center; gap: 8px; min-width: 0; }
.gt-head .name { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.gt-state { font-size: 12px; font-weight: 600; color: var(--d-text-2); }
.gt.open .gt-state { color: var(--warn); }
.gt-btn { justify-self: center; align-self: center; display: flex; flex-direction: column; align-items: center; gap: 6px; min-height: 0; }
.gt-ring { width: clamp(64px, 34cqmin, 116px); aspect-ratio: 1; border-radius: 50%; display: grid; place-items: center; font-size: clamp(28px, 14cqmin, 50px);
background: var(--d-fill); box-shadow: inset 0 0 0 3px color-mix(in srgb, var(--d-text) 10%, transparent); transition: background-color .25s, color .25s, transform .15s; }
.gt-btn:active .gt-ring { transform: scale(.96); }
.gt.open .gt-ring { background: color-mix(in srgb, var(--warn) 20%, transparent); color: var(--warn); box-shadow: inset 0 0 0 3px color-mix(in srgb, var(--warn) 55%, transparent); }
.gt.moving .gt-ring { animation: gt-pulse 1.2s ease-in-out infinite; }
@keyframes gt-pulse { 50% { opacity: .55; } }
.gt-lbl { font-size: 14px; font-weight: 600; }
.gt-since { font-size: 12px; color: var(--d-text-2); text-align: center; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
@media (prefers-reduced-motion: reduce) { .gt.moving .gt-ring { animation: none; } }
/* sports */
.sp { display: flex; flex-direction: column; gap: 8px; height: 100%; justify-content: center; min-height: 0; }
.sp-league { font-size: 12px; font-weight: 600; color: var(--d-text-2); text-align: center; text-transform: uppercase; letter-spacing: .04em; }
.sp-match { display: grid; grid-template-columns: minmax(0, 1fr) auto minmax(0, 1fr); align-items: center; gap: 8px; }
.sp-team { display: flex; flex-direction: column; align-items: center; gap: 6px; min-width: 0; text-align: center; }
.sp-team img { width: clamp(36px, 16cqmin, 64px); aspect-ratio: 1; object-fit: contain; }
.sp-badge { width: clamp(36px, 16cqmin, 64px); aspect-ratio: 1; border-radius: 50%; display: grid; place-items: center; background: var(--d-fill); font-weight: 700; font-size: 13px; }
.sp-team > span:last-child { font-size: 13px; font-weight: 600; max-width: 100%; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.sp-team.win > span:last-child { color: var(--ok); }
.sp-mid { display: flex; flex-direction: column; align-items: center; gap: 2px; }
.sp-score { font-size: clamp(26px, 11cqmin, 40px); font-weight: 700; letter-spacing: -0.02em; }
.sp-score i { font-style: normal; color: var(--d-text-2); margin: 0 6px; }
.sp-time { font-size: clamp(22px, 9cqmin, 32px); font-weight: 700; }
.sp-sub { font-size: 12px; color: var(--d-text-2); white-space: nowrap; }
.sp-live { color: var(--crit); }
.sp-live::before { content: ""; display: inline-block; width: 7px; height: 7px; border-radius: 50%; background: var(--crit); margin-right: 4px; vertical-align: 1px; animation: tz-pulse 1.4s infinite; }
.sp-extra { font-size: 12px; color: var(--d-text-2); text-align: center; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
/* Formula One */
.f1 { display: flex; flex-direction: column; gap: 10px; height: 100%; min-height: 0; }
.f1-next { padding: 12px; border-radius: 14px; background: linear-gradient(135deg, color-mix(in srgb, #e10600 22%, var(--d-fill)), var(--d-fill)); }
.f1-top { display: flex; align-items: center; gap: 6px; font-size: 12px; color: var(--d-text-2); }
.f1-top .i { color: #e10600; font-size: 15px; }
.f1-top b { margin-left: auto; color: var(--d-text); font-weight: 600; }
.f1-race { font-size: 18px; font-weight: 700; margin-top: 4px; letter-spacing: -0.01em; }
.f1-place { font-size: 12px; color: var(--d-text-2); }
.f1-sessions { display: flex; flex-direction: column; gap: 2px; margin-top: 8px; font-size: 12px; }
.f1-sessions > div { display: flex; justify-content: space-between; gap: 8px; }
.f1-sessions .past { opacity: .5; }
.f1-sessions .next { font-weight: 700; color: var(--d-text); }
.f1-tabs { display: flex; gap: 4px; padding: 3px; border-radius: 10px; background: var(--d-fill); }
.f1-tabs button { flex: 1; padding: 5px 4px; border-radius: 8px; font-size: 12px; font-weight: 600; color: var(--d-text-2); }
.f1-tabs button[aria-selected="true"] { background: var(--d-tile); color: var(--d-text); box-shadow: 0 1px 3px rgba(0,0,0,.12); }
.f1-list { display: flex; flex-direction: column; overflow-y: auto; min-height: 0; }
.f1-row { display: grid; grid-template-columns: 26px minmax(0, 1fr) auto; align-items: center; gap: 8px; padding: 6px 2px; border-bottom: 1px solid var(--d-separator); }
.f1-row:last-child { border-bottom: 0; }
.f1-pos { font-weight: 700; color: var(--d-text-2); text-align: center; }
.f1-row:first-child .f1-pos { color: #e10600; }
.f1-name { display: flex; flex-direction: column; min-width: 0; }
.f1-name b { font-size: 14px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.f1-name span { font-size: 11px; color: var(--d-text-2); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.f1-val { font-size: 13px; font-weight: 600; }
@container (max-height: 380px) { .f1-sessions { display: none; } }
/* announcements */
#announce { position: fixed; inset: 0; z-index: 95; display: grid; place-items: center; padding: 24px; background: rgba(0, 0, 0, .72); backdrop-filter: blur(14px); -webkit-backdrop-filter: blur(14px); }
#announce[hidden] { display: none; }
.an-card { width: min(640px, 100%); display: flex; flex-direction: column; align-items: center; gap: 14px; text-align: center; color: #fff; }
.an-ico { width: 84px; height: 84px; border-radius: 50%; display: grid; place-items: center; font-size: 40px; background: var(--accent); color: #fff; animation: an-pop .5s cubic-bezier(.2, 1.4, .4, 1); }
@keyframes an-pop { from { transform: scale(.4); opacity: 0; } }
.an-title { font-size: 18px; font-weight: 600; opacity: .8; }
.an-msg { font-size: clamp(30px, 6vw, 56px); font-weight: 700; letter-spacing: -0.02em; line-height: 1.1; white-space: pre-line; }
.an-ok { min-height: 52px; min-width: 160px; font-size: 18px; margin-top: 10px; }
@media (prefers-reduced-motion: reduce) { .an-ico { animation: none; } }
#btn-guest[hidden] { display: none; }
#btn-guest { background: color-mix(in srgb, var(--accent) 16%, transparent); color: var(--accent); }
/* editor: alignment guides, presets, templates, undo history */
.guide { position: absolute; z-index: 6; pointer-events: none; background: #ff2d92; }
.guide.v { top: 0; bottom: 0; width: 1px; }
.guide.h { left: 0; right: 0; height: 1px; }
.pal-sub { grid-column: 1 / -1; font-size: 11px; font-weight: 600; text-transform: uppercase; letter-spacing: .04em; color: var(--text-2); margin: 6px 2px 0; }
.pal-preset { position: relative; display: grid; }
.pal-del { position: absolute; top: 4px; right: 4px; width: 22px; height: 22px; border-radius: 50%; display: none; place-items: center; font-size: 12px; background: var(--surface); color: var(--text-2); }
.pal-preset:hover .pal-del, .pal-del:focus-visible { display: grid; }
.modal-card:has(.tpl-grid) { width: 640px; }
.tpl-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(220px, 1fr)); gap: 8px; margin-top: 14px; max-height: 52vh; overflow-y: auto; }
.tpl { display: flex; gap: 12px; align-items: flex-start; padding: 12px; border-radius: 14px; border: 1px solid var(--separator); cursor: pointer; }
.tpl input { position: absolute; opacity: 0; pointer-events: none; }
.tpl:has(input:checked) { border-color: var(--accent); box-shadow: 0 0 0 1px var(--accent); background: color-mix(in srgb, var(--accent) 8%, transparent); }
.tpl:has(input:focus-visible) { outline: 2px solid var(--accent); outline-offset: 2px; }
.tpl-ico { width: 38px; height: 38px; border-radius: 10px; display: grid; place-items: center; font-size: 20px; background: var(--fill); flex: none; }
.tpl > span:last-child { display: flex; flex-direction: column; gap: 2px; font-size: 13px; color: var(--text-2); }
.tpl b { color: var(--text); font-size: 14px; }
.undo-row { width: 100%; text-align: left; border-radius: 10px; }
.undo-row:hover { background: var(--fill); }
/* share */
.share-qr { width: 220px; max-width: 70%; margin: 14px auto 12px; border-radius: 12px; overflow: hidden; box-shadow: 0 0 0 1px var(--separator); }
.share-qr svg { display: block; width: 100%; height: auto; }
.share-row { display: flex; gap: 8px; }
.share-row .input { flex: 1; min-width: 0; font-size: 13px; }
.share-row .btn { flex: none; }
/* offline note: visible without the top bar too (phones, kiosk) */
#offline-note { position: fixed; z-index: 85; left: 50%; top: calc(10px + env(safe-area-inset-top)); transform: translateX(-50%); padding: 6px 14px; border-radius: 99px;
font-size: 13px; font-weight: 600; color: #fff; background: rgba(28, 28, 30, .86); backdrop-filter: blur(12px); -webkit-backdrop-filter: blur(12px); box-shadow: 0 4px 16px rgba(0, 0, 0, .25); max-width: calc(100% - 32px); text-align: center; }
#offline-note[hidden] { display: none; }
body.editing #offline-note { display: none; }
/* pull to refresh */
#ptr { position: fixed; z-index: 86; left: 50%; top: calc(8px + env(safe-area-inset-top)); width: 38px; height: 38px; border-radius: 50%; display: grid; place-items: center;
background: var(--surface); color: var(--text-2); box-shadow: 0 4px 16px rgba(0, 0, 0, .2); transform: translate(-50%, 0); pointer-events: none; }
#ptr[hidden] { display: none; }
#ptr svg { width: 20px; height: 20px; fill: none; stroke: currentColor; stroke-width: 2; stroke-linecap: round; stroke-linejoin: round; }
#ptr.ready { color: var(--accent); }
#ptr.spin svg { animation: ptr-spin .8s linear infinite; }
@keyframes ptr-spin { to { transform: rotate(360deg); } }
/* popups as bottom sheets on phones */
@media (max-width: 600px) {
.modal { align-items: flex-end; padding: 0; }
.modal-card, .modal-card:has(.tpl-grid) { width: 100%; max-height: 90vh; border-radius: 20px 20px 0 0; padding: 26px 18px calc(18px + env(safe-area-inset-bottom)); transition: transform .25s ease; animation: sheet-up .3s cubic-bezier(.2, .9, .3, 1); }
.modal-card::before { content: ""; position: sticky; top: -18px; display: block; width: 38px; height: 5px; border-radius: 3px; background: var(--separator); margin: -14px auto 12px; }
.modal-actions { flex-wrap: wrap; }
.modal-actions .btn { flex: 1 1 auto; min-height: 46px; }
}
@keyframes sheet-up { from { transform: translateY(100%); } }
@media (prefers-reduced-motion: reduce) { .modal-card { animation: none !important; } }
/* phone editing: the bar with "Add widget" and the settings */
#edit-bar { display: none; position: fixed; z-index: 63; left: 50%; bottom: calc(14px + env(safe-area-inset-bottom)); transform: translateX(-50%); gap: 8px; padding: 6px;
border-radius: 99px; background: var(--glass); border: 1px solid var(--glass-edge); box-shadow: 0 8px 30px rgba(0, 0, 0, .25); backdrop-filter: blur(20px); -webkit-backdrop-filter: blur(20px); }
#edit-bar { max-width: calc(100% - 24px); }
#edit-bar .btn { min-height: 44px; border-radius: 99px; padding: 0 18px; white-space: nowrap; }
#sheet-backdrop { position: fixed; inset: 0; z-index: 61; background: rgba(0, 0, 0, .35); }
#sheet-backdrop[hidden] { display: none; }
.sheet-close { display: none; position: sticky; top: 0; float: right; width: 34px; height: 34px; border-radius: 50%; place-items: center; font-size: 20px; z-index: 2; }
body:not(.editing) #edit-bar { display: none !important; }
@media (pointer: coarse) {
body.editing .w.selected { touch-action: none; }
body.editing .w.selected .w-resize { width: 34px; height: 34px; }
body.editing .w-tools button { width: 38px; height: 38px; }
}
@media (prefers-reduced-motion: reduce) { body.editing #palette, body.editing #inspector { transition: none; } }
/* suggestions */
.pal-sug { grid-column: 1 / -1; display: flex; align-items: center; gap: 8px; padding: 9px 10px; border-radius: 12px; text-align: left; font-size: 13px; font-weight: 500;
color: var(--accent); background: color-mix(in srgb, var(--accent) 12%, transparent); }
.pal-sug .i { font-size: 17px; flex: none; }
.sg-row { display: flex; align-items: center; gap: 10px; padding: 8px 2px; border-bottom: 1px solid var(--separator); }
.sg-row:last-child { border-bottom: 0; }
.sg-row .grow { display: flex; flex-direction: column; min-width: 0; }
.sg-row b { font-size: 14px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.sg-row .muted { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.sg-ico { width: 32px; height: 32px; border-radius: 50%; display: grid; place-items: center; background: var(--fill); font-size: 17px; flex: none; }
.sg-row .btn { flex: none; }
body:not(.editing) .w { -webkit-touch-callout: none; }
/* ============================================================ phone dashboards */
body.phone-ui:not(.peek) #topbar { display: none; }
body.phone-ui #stage { padding-top: calc(env(safe-area-inset-top) + 6px); }
@@ -1160,10 +1392,16 @@ body.kiosk.idle, body.kiosk.idle * { cursor: none !important; }
body.editing #stage { flex-direction: column; align-items: stretch; }
#workspace { flex-direction: column; }
body.editing #stage { order: 1; flex: 1; }
body.editing #palette { order: 2; width: auto; max-height: 120px; padding: 10px; border: 0; border-top: 1px solid var(--separator); }
body.editing #palette .panel-title, body.editing #palette .hint { display: none; }
body.editing #palette-list { grid-template-columns: repeat(4, 1fr); }
body.editing #inspector { order: 3; width: auto; max-height: 40vh; padding: 14px; border: 0; border-top: 1px solid var(--separator); }
/* editing on a phone: the widget list and the settings slide up as sheets */
body.editing #palette, body.editing #inspector { position: fixed; left: 0; right: 0; bottom: 0; z-index: 62; width: auto; height: 72vh; max-height: none; overflow-y: auto;
padding: 16px 16px calc(84px + env(safe-area-inset-bottom)); border: 0; border-radius: 20px 20px 0 0; background: var(--surface); box-shadow: 0 -10px 40px rgba(0, 0, 0, .3);
transform: translateY(105%); transition: transform .3s cubic-bezier(.2, .9, .3, 1); }
body.editing.sheet-palette #palette, body.editing.sheet-inspector #inspector { transform: none; }
body.editing #palette .hint { display: none; }
body.editing #palette-list { grid-template-columns: repeat(3, 1fr); }
body.editing #edit-bar { display: flex; }
body.editing .sheet-close { display: grid; }
body.editing #canvas { padding-bottom: 90px; }
.btn.label-wide span { display: none; }
}