Dashboard Studio 1.9.0: real swiping, tap for next photo, bigger vacuum card

- Swipe card: pointer-driven drag for mouse and touch with resistance
  at the ends and flick detection, keyboard arrows, cards fill the whole
  card, page dots float and hide after a few seconds; updates wait until
  a swipe ends and the release never presses a button underneath
- Tapping the clock background or a Photos card skips to the next photo
- Media card: removing players works inside scroll/swipe cards, and
  pre-1.5 single-player cards are migrated into the list
- Vacuum card grows with its size: robot with battery ring, cleaning
  pulse and charging badge, stat tiles, suction power and labelled
  buttons; compact layout on small cards

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-25 23:55:56 +01:00
parent e73c688017
commit aa75a572c5
7 changed files with 214 additions and 68 deletions
+59 -17
View File
@@ -260,6 +260,7 @@ textarea.input { resize: vertical; min-height: 76px; font-family: ui-monospace,
.w-body { position: absolute; inset: 0; padding: 16px; display: flex; flex-direction: column; min-height: 0; }
.w-body.flush { padding: 0; }
.w.transparent .w-body, .surface-plain .w .w-body { padding: 6px 4px; }
.w.transparent .w-body.flush, .surface-plain .w .w-body.flush { padding: 0; }
.pad { padding: 16px; height: 100%; display: flex; }
/* Lit: a light that is on turns its tile into lamplight — the signature moment. */
@@ -532,14 +533,23 @@ body.kiosk #tabnav { top: 6px; max-height: calc(100vh - 20px); }
.hs.at-start .hs-nav.prev, .hs.at-end .hs-nav.next { opacity: 0; pointer-events: none; }
@media (hover: none) { .hs-nav { display: none; } }
.i.back { transform: rotate(90deg); } .i.fwd { transform: rotate(-90deg); }
.sw { height: 100%; display: flex; flex-direction: column; gap: 8px; }
.sw-track { flex: 1; min-height: 0; display: flex; overflow-x: auto; overflow-y: hidden; scroll-snap-type: x mandatory; scrollbar-width: none; overscroll-behavior-x: contain; border-radius: var(--d-radius, 20px); }
.sw-track::-webkit-scrollbar { display: none; }
.sw-track > .w.child { width: 100%; scroll-snap-align: center; scroll-snap-stop: always; }
.sw-dots { flex: none; display: flex; justify-content: center; gap: 2px; }
.sw { position: relative; height: 100%; overflow: hidden; border-radius: var(--d-radius, 20px); touch-action: pan-y; }
.sw-track { display: flex; height: 100%; will-change: transform; user-select: none; -webkit-user-select: none; outline: none; }
.sw-track > .w.child { flex: 0 0 100%; width: 100%; }
.sw.dragging .sw-track { cursor: grabbing; }
.sw.dragging .w.child { pointer-events: none; }
.sw-track:focus-visible { box-shadow: inset var(--focus); }
/* Dots float over the cards and only show while swiping, hovering or focused. */
.sw-dots {
position: absolute; left: 50%; bottom: 10px; z-index: 4; transform: translate(-50%, 6px);
display: flex; gap: 0; padding: 2px 6px; border-radius: 99px;
background: var(--glass); backdrop-filter: blur(16px) saturate(1.3); -webkit-backdrop-filter: blur(16px) saturate(1.3);
box-shadow: 0 4px 14px rgba(0, 0, 0, 0.18); opacity: 0; pointer-events: none; transition: opacity .25s, transform .25s;
}
.sw.dots-on .sw-dots, .sw:has(:focus-visible) .sw-dots { opacity: 1; transform: translate(-50%, 0); pointer-events: auto; }
.sw-dot { width: 22px; height: 22px; display: grid; place-items: center; }
.sw-dot::after { content: ""; width: 7px; height: 7px; border-radius: 50%; background: var(--d-text-2); opacity: .45; transition: width .2s, opacity .2s; }
.sw-dot[aria-selected="true"]::after { width: 18px; border-radius: 4px; opacity: 1; background: var(--d-text); }
.sw-dot::after { content: ""; width: 7px; height: 7px; border-radius: 50%; background: var(--text); opacity: .35; transition: width .2s, opacity .2s; }
.sw-dot[aria-selected="true"]::after { width: 18px; border-radius: 4px; opacity: 1; }
.btn.back { margin: 0 0 10px; padding-left: 8px; }
.child-list { display: flex; flex-direction: column; gap: 6px; }
.child-item { display: flex; gap: 4px; align-items: center; }
@@ -560,6 +570,7 @@ body.kiosk #tabnav { top: 6px; max-height: calc(100vh - 20px); }
.clock.pos-top-right, .clock.pos-right, .clock.pos-bottom-right { align-items: flex-end; text-align: right; }
.clock-bg { position: absolute; inset: 0; margin: -16px; z-index: 0; border-radius: inherit; }
.w.transparent .clock-bg, .surface-plain .clock-bg { margin: -6px -4px; }
.clock.on-image, .photos[data-act] { cursor: pointer; }
.clock-dim { position: absolute; inset: 0; margin: -16px; z-index: 0; background: linear-gradient(to bottom, rgba(0, 0, 0, calc(var(--dim) * 0.8)), rgba(0, 0, 0, var(--dim))); }
.clock.on-image { color: #fff; --d-text-2: rgba(255, 255, 255, 0.86); text-shadow: 0 1px 12px rgba(0, 0, 0, 0.45); }
.clock.on-image .clock-wx { background: rgba(0, 0, 0, 0.32); backdrop-filter: blur(12px); -webkit-backdrop-filter: blur(12px); }
@@ -573,17 +584,48 @@ body.kiosk #tabnav { top: 6px; max-height: calc(100vh - 20px); }
.light-members { flex: 1; min-height: 0; margin: 0 -6px; }
.light:has(.light-members) { justify-content: flex-start; }
/* vacuum */
.vac { display: flex; flex-direction: column; height: 100%; gap: 10px; }
.vac-batt { margin-left: auto; display: inline-flex; align-items: center; gap: 4px; font-size: 14px; font-weight: 600; color: var(--d-text-2); }
/* 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-map { flex: 1; min-height: 0; width: 100%; object-fit: contain; border-radius: 12px; background: var(--d-fill); }
.vac-spacer { flex: 1; }
.vac .ctrls { display: flex; gap: 8px; align-items: center; }
.vac .round:disabled { opacity: .35; cursor: default; }
.vac-hint { margin-left: 4px; }
.vac.is-error .state { color: var(--crit); font-weight: 600; }
@container (max-height: 220px) { .vac-map { display: none; } .vac-spacer { display: none; } }
.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; }