Dashboard Studio 2.2.0: guest invites, personal looks, more themes, Haven widgets and tiles
- Vacuum card: the robot drawn beside its dock by state, last clean, maintenance - Formula One card: the official F1 look with a user-supplied logo - Nine more themes; per-person theme, text size and language on Admin, People - Guest invites: an expiring link and QR code for one dashboard, served on a LAN-only guest port, with the Home Assistant connection filtered both ways - Haven 2.2: home screen widgets, three quick settings tiles, several homes, and self-updates from the Haven that Dashboard Studio serves Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
+116
-84
@@ -908,47 +908,7 @@ body.kiosk #tabnav { top: 6px; max-height: calc(100vh - 20px); }
|
||||
@container (max-height: 200px) { .room-devices { display: none; } }
|
||||
|
||||
/* vacuum: compact on small cards, a robot with stats on bigger ones */
|
||||
.vac { display: flex; flex-direction: column; height: 100%; gap: 12px; }
|
||||
.vac-head { display: flex; align-items: center; gap: 12px; min-width: 0; }
|
||||
.vac-batt { margin-left: auto; display: inline-flex; align-items: center; gap: 4px; font-size: 14px; font-weight: 600; color: var(--d-text-2); white-space: nowrap; }
|
||||
.vac-batt .i { font-size: 18px; }
|
||||
.vac-hero { flex: 1; min-height: 0; display: flex; align-items: center; justify-content: center; gap: 18px; }
|
||||
.vac-map { width: 100%; height: 100%; object-fit: contain; border-radius: 14px; background: var(--d-fill); }
|
||||
.vac-robot { position: relative; width: clamp(84px, 32cqh, 150px); aspect-ratio: 1; flex: none; display: grid; place-items: center; }
|
||||
.vac-ring { position: absolute; inset: 0; transform: rotate(-90deg); }
|
||||
.vac-ring circle { fill: none; stroke-width: 5; }
|
||||
.vac-ring .track { stroke: var(--d-fill); }
|
||||
.vac-ring .fill { stroke: var(--ok); stroke-linecap: round; transition: stroke-dasharray .6s; }
|
||||
.vac-ring .fill.low { stroke: var(--crit); }
|
||||
.vac-robot-ico { position: relative; z-index: 1; width: 62%; aspect-ratio: 1; border-radius: 50%; display: grid; place-items: center; font-size: clamp(30px, 12cqh, 54px); background: var(--d-fill); }
|
||||
.state-cleaning .vac-robot-ico, .state-returning .vac-robot-ico { background: var(--accent-w); color: var(--d-on-accent, #fff); }
|
||||
.vac-pulse { position: absolute; inset: 19%; border-radius: 50%; }
|
||||
.vac-robot.is-cleaning .vac-pulse { animation: vac-pulse 1.8s ease-out infinite; border: 2px solid var(--accent-w); }
|
||||
@keyframes vac-pulse { from { transform: scale(1); opacity: .7; } to { transform: scale(1.45); opacity: 0; } }
|
||||
.vac-charge { position: absolute; z-index: 2; right: 8%; bottom: 8%; width: 28%; aspect-ratio: 1; border-radius: 50%; display: grid; place-items: center; background: var(--ok); color: #04210f; font-size: clamp(14px, 4cqh, 20px); box-shadow: 0 0 0 3px var(--w-bg, var(--d-tile)); }
|
||||
.vac-hero-text { display: flex; flex-direction: column; gap: 2px; min-width: 0; }
|
||||
.vac-headline { font-size: clamp(18px, 6cqh, 26px); font-weight: 700; line-height: 1.15; }
|
||||
.vac-stats { display: grid; grid-template-columns: repeat(auto-fit, minmax(96px, 1fr)); gap: 8px; flex: none; }
|
||||
.vac-stat { display: grid; grid-template-columns: auto 1fr; grid-template-rows: auto auto; column-gap: 8px; align-items: center; padding: 10px 12px; border-radius: 14px; background: var(--d-fill); min-width: 0; }
|
||||
.vac-stat .i { grid-row: span 2; font-size: 18px; color: var(--d-text-2); }
|
||||
.vac-stat-v { font-size: 17px; font-weight: 700; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
|
||||
.vac-stat-l { font-size: 12px; color: var(--d-text-2); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
|
||||
.vac-fan { flex: none; background: var(--d-fill); }
|
||||
.vac-fan button { color: inherit; display: grid; place-items: center; }
|
||||
.vac-fan button[aria-pressed="true"] { background: var(--d-tile); color: var(--d-text); }
|
||||
.vac-actions { display: flex; gap: 8px; flex: none; }
|
||||
.vac-btn { flex: 1; display: inline-flex; align-items: center; justify-content: center; gap: 8px; min-height: 44px; padding: 0 12px; border-radius: 14px; background: var(--d-fill); font-size: 14px; font-weight: 600; }
|
||||
.vac-btn .i { font-size: 18px; flex: none; }
|
||||
.vac-btn.primary { background: var(--d-text); color: var(--d-tile); }
|
||||
.vac-btn:disabled { opacity: .4; cursor: default; }
|
||||
.vac.state-error .state { color: var(--crit); font-weight: 600; }
|
||||
/* the more room, the more it shows */
|
||||
@container (max-width: 330px) { .vac-btn span { display: none; } .vac-hero-text { display: none; } }
|
||||
@container (max-height: 370px) { .vac-stats { display: none; } }
|
||||
@container (max-height: 330px) { .vac-fan { display: none; } }
|
||||
@container (max-height: 250px) { .vac-hero { display: none; } }
|
||||
@container (min-height: 251px) { .vac-head .vac-batt, .vac-head .state { display: none; } }
|
||||
@container (min-height: 251px) and (max-width: 330px) { .vac-head .vac-batt { display: inline-flex; } }
|
||||
|
||||
/* photos */
|
||||
.photos { position: absolute; inset: 0; background: #000; overflow: hidden; }
|
||||
@@ -1210,30 +1170,6 @@ body.kiosk #tabnav { top: 6px; max-height: calc(100vh - 20px); }
|
||||
.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; }
|
||||
.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); }
|
||||
@@ -1697,18 +1633,6 @@ body.in-haven #btn-new { display: none; }
|
||||
.room-dev.on { background: color-mix(in srgb, var(--d-text) 88%, transparent); }
|
||||
|
||||
/* vacuum: the state in words, the battery as a bar */
|
||||
.vac-hero { justify-content: flex-start; align-items: center; }
|
||||
.vac-hero-text { gap: 8px; width: 100%; }
|
||||
.vac-headline { font-size: clamp(26px, 9cqh, 40px); line-height: 1.1; }
|
||||
.vac-meter { display: block; height: 6px; border-radius: 3px; background: var(--d-fill); overflow: hidden; max-width: 320px; }
|
||||
.vac-meter i { display: block; height: 100%; border-radius: 3px; background: var(--ok); }
|
||||
.vac-meter.low i { background: var(--crit); }
|
||||
.vac-stat { padding: 10px 12px; border-radius: var(--c-r); grid-template-columns: 1fr; }
|
||||
.vac-stat .i { display: none; }
|
||||
.vac-stat-v { font-size: var(--c-fs-mid); font-weight: 400; }
|
||||
.vac-stat-l { font-size: var(--c-fs-meta); }
|
||||
.vac-btn { min-height: var(--c-ctl); border-radius: var(--c-r); font-size: var(--c-fs-body); }
|
||||
@container (max-width: 330px) { .vac-hero-text { display: flex; } }
|
||||
|
||||
/* appliance: a thin progress ring with a plain icon inside */
|
||||
.appl-ring circle { stroke-width: 4; }
|
||||
@@ -1783,14 +1707,6 @@ body.in-haven #btn-new { display: none; }
|
||||
.sp-sub, .sp-extra { font-size: var(--c-fs-meta); }
|
||||
|
||||
/* Formula One: the next race on one flat step, no gradient */
|
||||
.f1-next { background: var(--d-fill); border-radius: var(--c-r); }
|
||||
.f1-race { font-weight: 600; font-size: var(--c-fs-mid); }
|
||||
.f1-place, .f1-top, .f1-sessions { font-size: var(--c-fs-meta); }
|
||||
.f1-name span { font-size: 12px; }
|
||||
.f1-name b { font-size: var(--c-fs-body); font-weight: 600; }
|
||||
.f1-tabs button { font-size: var(--c-fs-meta); min-height: 30px; }
|
||||
.f1-tabs button[aria-selected="true"] { box-shadow: none; }
|
||||
.f1-pos { font-weight: 500; }
|
||||
|
||||
/* calendar */
|
||||
.cal-sum { font-size: var(--c-fs-body); }
|
||||
@@ -1928,3 +1844,119 @@ body:not([style*="--night-dim: 0."]):not([style*="--night-dim: 1"]) #night-dim {
|
||||
.climate .round { width: 38px; height: 38px; }
|
||||
}
|
||||
.tm-time { font-size: clamp(20px, min(14cqh, 15cqw), 44px); }
|
||||
|
||||
/* vacuum: the robot beside its dock, the last clean, maintenance */
|
||||
.vac { display: flex; flex-direction: column; height: 100%; gap: 12px; min-height: 0; }
|
||||
.vac-head { display: flex; align-items: flex-start; gap: 12px; min-width: 0; }
|
||||
.vac-title { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: 2px; }
|
||||
.vac-title .name { font-size: var(--c-fs-title); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
|
||||
.vac-status { font-size: clamp(22px, 8cqw, 30px); font-weight: 400; letter-spacing: -0.02em; line-height: 1.15; display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden; }
|
||||
.vac.state-error .vac-status { color: var(--crit); }
|
||||
.vac-batt { flex: none; display: flex; flex-direction: column; align-items: flex-end; gap: 6px; padding-top: 2px; }
|
||||
.vac-batt-v { font-size: var(--c-fs-mid); line-height: 1; }
|
||||
.vac-batt-v small { font-size: .6em; color: var(--d-text-2); margin-left: 1px; }
|
||||
.vac-meter { display: block; width: 64px; height: 5px; border-radius: 3px; background: var(--d-fill); overflow: hidden; }
|
||||
.vac-meter i { display: block; height: 100%; border-radius: 3px; background: var(--d-text); }
|
||||
.charging .vac-meter i { background: var(--ok); }
|
||||
.vac-meter.low i { background: var(--crit); }
|
||||
.vac-art { flex: 1; min-height: 64px; display: grid; place-items: center; }
|
||||
.vac-map { width: 100%; height: 100%; object-fit: contain; border-radius: var(--c-r); background: var(--d-fill); }
|
||||
.vac-scene { width: 100%; height: 100%; max-height: 132px; overflow: visible; }
|
||||
.vs-dock { fill: color-mix(in srgb, var(--d-text) 12%, var(--w-bg, var(--d-tile))); }
|
||||
.vs-plate { fill: color-mix(in srgb, var(--d-text) 30%, transparent); }
|
||||
.vs-led { fill: color-mix(in srgb, var(--d-text) 30%, transparent); }
|
||||
.vs-led.on { fill: var(--ok); }
|
||||
.vs-trail { fill: none; stroke: var(--d-accent); stroke-width: 2; stroke-linecap: round; stroke-dasharray: 1 8; opacity: .8; }
|
||||
.vs-robot { transition: transform .8s cubic-bezier(.3, .7, .3, 1); }
|
||||
.vs-body { fill: color-mix(in srgb, var(--d-text) 7%, var(--w-bg, var(--d-tile))); stroke: color-mix(in srgb, var(--d-text) 26%, transparent); stroke-width: 2; }
|
||||
.vs-bumper { fill: none; stroke: color-mix(in srgb, var(--d-text) 45%, transparent); stroke-width: 3; stroke-linecap: round; }
|
||||
.vs-lidar { fill: color-mix(in srgb, var(--d-text) 20%, var(--w-bg, var(--d-tile))); stroke: color-mix(in srgb, var(--d-text) 35%, transparent); stroke-width: 1.5; }
|
||||
.vs-brush line { stroke: color-mix(in srgb, var(--d-text) 40%, transparent); stroke-width: 1.5; stroke-linecap: round; }
|
||||
.vs-robot.busy .vs-brush { animation: vs-spin .5s linear infinite; transform-box: fill-box; transform-origin: center; }
|
||||
.vs-robot.busy .vs-lidar { stroke: var(--d-accent); }
|
||||
.vs-robot.err .vs-body { stroke: var(--crit); }
|
||||
@keyframes vs-spin { to { rotate: 360deg; } }
|
||||
@media (prefers-reduced-motion: reduce) { .vs-robot.busy .vs-brush { animation: none; } .vs-robot { transition: none; } }
|
||||
.vac-facts { display: flex; gap: 24px; flex-wrap: wrap; }
|
||||
.vac-fact { display: flex; flex-direction: column; gap: 1px; }
|
||||
.vac-fv { font-size: var(--c-fs-mid); line-height: 1.1; }
|
||||
.vac-fl { font-size: var(--c-fs-meta); color: var(--d-text-2); }
|
||||
.vac-maint { display: flex; flex-direction: column; gap: 6px; }
|
||||
.vac-mrow { display: grid; grid-template-columns: minmax(0, 7em) minmax(0, 1fr) 3em; align-items: center; gap: 10px; font-size: var(--c-fs-meta); }
|
||||
.vac-ml { color: var(--d-text-2); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
|
||||
.vac-mbar { height: 4px; border-radius: 2px; background: var(--d-fill); overflow: hidden; }
|
||||
.vac-mbar i { display: block; height: 100%; background: color-mix(in srgb, var(--d-text) 55%, transparent); }
|
||||
.vac-mrow.low .vac-mbar i { background: var(--warn); }
|
||||
.vac-mrow.low .vac-mv { color: var(--warn); }
|
||||
.vac-mv { text-align: right; }
|
||||
.vac-fan { flex: none; }
|
||||
.vac-actions { display: flex; gap: 8px; flex: none; }
|
||||
.vac-btn { flex: 1; display: inline-flex; align-items: center; justify-content: center; gap: 8px; min-height: var(--c-ctl); padding: 0 12px; border-radius: var(--c-r); background: var(--d-fill); font-size: var(--c-fs-body); font-weight: 600; }
|
||||
.vac-btn .i { font-size: 18px; flex: none; }
|
||||
.vac-btn.primary { background: var(--d-text); color: var(--d-tile); }
|
||||
.vac-btn:disabled { opacity: .4; cursor: default; }
|
||||
@media (hover: hover) { .vac-btn:not(.primary):not(:disabled):hover { background: color-mix(in srgb, var(--d-text) 14%, transparent); } }
|
||||
/* the more room, the more it shows */
|
||||
@container (max-width: 330px) { .vac-btn span { display: none; } }
|
||||
@container (max-height: 460px) { .vac-maint { display: none; } }
|
||||
@container (max-height: 380px) { .vac-facts { display: none; } }
|
||||
@container (max-height: 320px) { .vac-fan { display: none; } }
|
||||
@container (max-height: 240px) { .vac-art { display: none; } }
|
||||
@media (max-width: 640px) { .vac-btn span { display: none; } }
|
||||
|
||||
/* Formula One: the official look (carbon black, F1 red, white), or the dashboard's theme */
|
||||
.f1 { display: flex; flex-direction: column; gap: 12px; height: 100%; min-height: 0; --f1-red: #e10600; --f1-ink: var(--d-text); --f1-mute: var(--d-text-2); --f1-step: var(--d-fill); }
|
||||
.f1.f1-official { margin: calc(-1 * var(--c-pad)); padding: var(--c-pad); height: calc(100% + 2 * var(--c-pad)); border-radius: inherit;
|
||||
background: #15151e; color: #fff; --f1-ink: #fff; --f1-mute: #b5b5c0; --f1-step: #26262f; --d-separator: #2c2c36; }
|
||||
.f1-brand { display: flex; align-items: center; gap: 10px; min-height: 22px; }
|
||||
.f1-logo { height: 20px; width: auto; max-width: 96px; object-fit: contain; }
|
||||
.f1-mark { width: 30px; height: 8px; border-radius: 2px; background: var(--f1-red); transform: skewX(-24deg); }
|
||||
.f1-round { font-size: var(--c-fs-meta); color: var(--f1-mute); margin-left: auto; }
|
||||
.f1-next { display: flex; flex-direction: column; gap: 4px; }
|
||||
.f1-race { font-size: clamp(20px, 7.5cqw, 30px); font-weight: 800; letter-spacing: -0.02em; line-height: 1.05; }
|
||||
.f1-place { font-size: var(--c-fs-meta); color: var(--f1-mute); }
|
||||
.f1-clock { display: flex; gap: 6px; margin-top: 8px; }
|
||||
.f1-cd { display: flex; flex-direction: column; align-items: center; min-width: 52px; padding: 6px 8px; border-radius: 6px; background: var(--f1-step); }
|
||||
.f1-cd b { font-size: var(--c-fs-mid); font-weight: 700; line-height: 1.1; }
|
||||
.f1-cd small { font-size: 12px; color: var(--f1-mute); }
|
||||
.f1-sessions { display: flex; flex-direction: column; margin-top: 8px; }
|
||||
.f1-s { display: grid; grid-template-columns: 4.6em minmax(0, 1fr) auto; align-items: center; gap: 8px; min-height: 28px; padding: 0 8px; font-size: var(--c-fs-meta); border-left: 3px solid transparent; }
|
||||
.f1-sd { color: var(--f1-mute); }
|
||||
.f1-sn { white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
|
||||
.f1-s.past { opacity: .45; }
|
||||
.f1-s.next { border-left-color: var(--f1-red); background: var(--f1-step); font-weight: 700; }
|
||||
.f1-tabs { display: flex; gap: 16px; border-bottom: 1px solid var(--d-separator); flex: none; }
|
||||
.f1-tabs button { padding: 8px 0; font-size: var(--c-fs-meta); font-weight: 600; color: var(--f1-mute); border-bottom: 3px solid transparent; margin-bottom: -1px; }
|
||||
.f1-tabs button[aria-selected="true"] { color: var(--f1-ink); border-bottom-color: var(--f1-red); }
|
||||
.f1-list { display: flex; flex-direction: column; overflow-y: auto; min-height: 0; }
|
||||
.f1-row { display: grid; grid-template-columns: 22px 4px minmax(0, 1fr) auto; align-items: center; gap: 10px; min-height: 40px; border-bottom: 1px solid var(--d-separator); }
|
||||
.f1-row:last-child { border-bottom: 0; }
|
||||
.f1-pos { font-weight: 700; text-align: right; }
|
||||
.f1-stripe { display: block; width: 4px; height: 22px; border-radius: 1px; background: var(--team); }
|
||||
.f1-name { display: flex; flex-direction: column; min-width: 0; }
|
||||
.f1-name b { font-size: var(--c-fs-body); font-weight: 500; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
||||
.f1-code { font-weight: 800; }
|
||||
.f1-name > span { font-size: 12px; color: var(--f1-mute); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
||||
.f1-val { font-size: var(--c-fs-body); font-weight: 700; text-align: right; }
|
||||
.f1-val small { font-size: 12px; font-weight: 400; color: var(--f1-mute); margin-left: 3px; }
|
||||
@container (max-height: 520px) { .f1-sessions { display: none; } }
|
||||
@container (max-height: 380px) { .f1-clock { display: none; } }
|
||||
|
||||
/* a person's text size, chosen by an admin */
|
||||
body.text-large #grid { --c-fs-title: 17px; --c-fs-body: 17px; --c-fs-name: 18px; --c-fs-meta: 15px; --c-fs-mid: 25px; --c-ctl: 48px; --c-row: 52px; }
|
||||
body.text-xl #grid { --c-fs-title: 19px; --c-fs-body: 19px; --c-fs-name: 21px; --c-fs-meta: 17px; --c-fs-mid: 28px; --c-ctl: 54px; --c-row: 58px; }
|
||||
.p-look { margin: 12px 0 0 52px; display: flex; flex-wrap: wrap; gap: 8px 20px; }
|
||||
.p-look .field { display: flex; align-items: center; gap: 8px; margin: 0; font-size: 13px; color: var(--text-2); }
|
||||
.p-look .input { width: auto; min-width: 120px; }
|
||||
@media (max-width: 760px) { .p-look { margin-left: 0; } }
|
||||
|
||||
/* guests */
|
||||
body.guest .guest-hide { display: none; }
|
||||
.inv-form { display: grid; grid-template-columns: 1.2fr 1fr 1fr auto; gap: 10px; align-items: end; margin-top: 12px; }
|
||||
.inv-form .field { margin: 0; display: flex; flex-direction: column; gap: 6px; font-size: 13px; color: var(--text-2); }
|
||||
.inv-host { margin: 14px 0 4px; display: flex; flex-direction: column; gap: 6px; font-size: 13px; color: var(--text-2); max-width: 360px; }
|
||||
.inv-fresh { margin-top: 16px; max-width: 420px; }
|
||||
.inv-fresh .share-qr { margin-left: 0; }
|
||||
.inv-row.ended { opacity: .6; }
|
||||
.adm-panel.notice { background: color-mix(in srgb, var(--warn) 10%, var(--surface)); }
|
||||
@media (max-width: 760px) { .inv-form { grid-template-columns: 1fr; } }
|
||||
|
||||
Reference in New Issue
Block a user