${Yt}
@@ -1152,18 +1190,18 @@ class Xt extends v {
`;
}
}
-customElements.define("nestquest-party-board-card", Xt);
-W({
+customElements.define("nestquest-party-board-card", te);
+V({
type: "nestquest-party-board-card",
name: "NestQuest Party Board",
description: "Kids' attract screen for present and away adventurers."
});
-class te extends v {
+class ee extends v {
static properties = {
hass: { attribute: !1 },
_config: { state: !0 }
};
- static styles = [$(gt)];
+ static styles = [$(yt)];
hass;
_config;
setConfig(t) {
@@ -1178,19 +1216,19 @@ class te extends v {
return g`
NestQuest Quest Log
`;
}
}
-customElements.define("nestquest-quest-log-card", te);
-W({
+customElements.define("nestquest-quest-log-card", ee);
+V({
type: "nestquest-quest-log-card",
name: "NestQuest Quest Log",
description: "One child's daily quests, grouped by window."
});
-const ee = ":host{--nq-brand-blue: #3B3AB8;--nq-brand-purple: #A035CC;--nq-brand-gradient: linear-gradient(135deg, var(--nq-brand-blue), var(--nq-brand-purple));--nq-brand-gradient-h: linear-gradient(90deg, var(--nq-brand-blue), var(--nq-brand-purple));--nq-a-page: #F4F4F6;--nq-a-surface: #FFFFFF;--nq-a-surface-subtle: #FAFAFA;--nq-a-selected: #EBEBF8;--nq-a-border: #E4E4EA;--nq-a-divider: #EDEDF1;--nq-a-ink: #18181D;--nq-a-ink-secondary: #52525E;--nq-a-ink-tertiary: #70707E;--nq-a-success: #15803D;--nq-a-danger: #DC2626;--nq-a-danger-strong: #B91C1C;--nq-a-danger-bg: #FEE2E2;--nq-a-warning-ink: #92400E;--nq-a-warning-ink-2: #78350F;--nq-a-warning-bg: #FEF3C7;--nq-a-warning-border: rgba(217,119,6,.35);--nq-a-info-bg: #DBEAFE;--nq-a-info-ink: #1E3A8A;--nq-a-info-icon: #1D4ED8;--nq-a-reversal: #7D28A0;--nq-a-font: Nunito, system-ui, sans-serif;--nq-a-size-screen: 24px;--nq-a-size-hero: 32px;--nq-a-size-card-title: 19px;--nq-a-size-stat: 22px;--nq-a-size-row: 13.5px;--nq-a-size-meta: 11px;--nq-a-size-label: 10px;--nq-a-track-label: .1em;--nq-a-density-page-pad: 14px 16px 92px;--nq-a-density-gap: 10px;--nq-a-density-card-pad: 13px;--nq-a-density-row-pad: 11px 13px;--nq-a-density-radius: 8px;--nq-a-density-shadow: none;--nq-a-tabbar-height: 70px;--nq-a-tap-min: 44px;--nq-a-radius-pill: 9999px;--nq-a-radius-sheet: 20px 20px 0 0;--nq-a-sheet-shadow: 0 -8px 28px rgba(9,9,11,.18);--nq-a-progress-height: 6px;--nq-ease-out: cubic-bezier(0, 0, .2, 1);--nq-dur-micro: .12s;--nq-dur-base: .2s;--nq-dur-modal: .35s}";
-class ne extends v {
+const ne = ":host{--nq-brand-blue: #3B3AB8;--nq-brand-purple: #A035CC;--nq-brand-gradient: linear-gradient(135deg, var(--nq-brand-blue), var(--nq-brand-purple));--nq-brand-gradient-h: linear-gradient(90deg, var(--nq-brand-blue), var(--nq-brand-purple));--nq-a-page: #F4F4F6;--nq-a-surface: #FFFFFF;--nq-a-surface-subtle: #FAFAFA;--nq-a-selected: #EBEBF8;--nq-a-border: #E4E4EA;--nq-a-divider: #EDEDF1;--nq-a-ink: #18181D;--nq-a-ink-secondary: #52525E;--nq-a-ink-tertiary: #70707E;--nq-a-success: #15803D;--nq-a-danger: #DC2626;--nq-a-danger-strong: #B91C1C;--nq-a-danger-bg: #FEE2E2;--nq-a-warning-ink: #92400E;--nq-a-warning-ink-2: #78350F;--nq-a-warning-bg: #FEF3C7;--nq-a-warning-border: rgba(217,119,6,.35);--nq-a-info-bg: #DBEAFE;--nq-a-info-ink: #1E3A8A;--nq-a-info-icon: #1D4ED8;--nq-a-reversal: #7D28A0;--nq-a-font: Nunito, system-ui, sans-serif;--nq-a-size-screen: 24px;--nq-a-size-hero: 32px;--nq-a-size-card-title: 19px;--nq-a-size-stat: 22px;--nq-a-size-row: 13.5px;--nq-a-size-meta: 11px;--nq-a-size-label: 10px;--nq-a-track-label: .1em;--nq-a-density-page-pad: 14px 16px 92px;--nq-a-density-gap: 10px;--nq-a-density-card-pad: 13px;--nq-a-density-row-pad: 11px 13px;--nq-a-density-radius: 8px;--nq-a-density-shadow: none;--nq-a-tabbar-height: 70px;--nq-a-tap-min: 44px;--nq-a-radius-pill: 9999px;--nq-a-radius-sheet: 20px 20px 0 0;--nq-a-sheet-shadow: 0 -8px 28px rgba(9,9,11,.18);--nq-a-progress-height: 6px;--nq-ease-out: cubic-bezier(0, 0, .2, 1);--nq-dur-micro: .12s;--nq-dur-base: .2s;--nq-dur-modal: .35s}";
+class se extends v {
static properties = {
hass: { attribute: !1 },
_config: { state: !0 }
};
- static styles = [$(ee)];
+ static styles = [$(ne)];
hass;
_config;
setConfig(t) {
@@ -1205,14 +1243,14 @@ class ne extends v {
return g`
NestQuest Admin
`;
}
}
-customElements.define("nestquest-admin-card", ne);
-W({
+customElements.define("nestquest-admin-card", se);
+V({
type: "nestquest-admin-card",
name: "NestQuest Admin",
description: "Parent phone view for today, tasks, schedule, and history."
});
-const se = "/nestquest-static/nestquest-fonts.css";
+const ie = "/nestquest-static/nestquest-fonts.css";
if (!document.querySelector('link[data-nq-fonts=""]')) {
const s = document.createElement("link");
- s.rel = "stylesheet", s.href = se, s.dataset.nqFonts = "", document.head.appendChild(s);
+ s.rel = "stylesheet", s.href = ie, s.dataset.nqFonts = "", document.head.appendChild(s);
}
diff --git a/frontend/src/cards/party-board.ts b/frontend/src/cards/party-board.ts
index 7fd428d..f9e7e5d 100644
--- a/frontend/src/cards/party-board.ts
+++ b/frontend/src/cards/party-board.ts
@@ -26,16 +26,52 @@ type DockWeather = {
low: number | null;
};
-const WEEKDAY_FORMAT = new Intl.DateTimeFormat("en-US", { weekday: "long" });
-const MONTH_DAY_FORMAT = new Intl.DateTimeFormat("en-US", {
- month: "short",
- day: "numeric",
-});
-const TIME_FORMAT = new Intl.DateTimeFormat("en-US", {
- hour: "numeric",
- minute: "2-digit",
- hour12: true,
-});
+const FORMATTER_CACHE = new Map
();
+
+function zonedFormatter(
+ timeZone: string | undefined,
+ options: Intl.DateTimeFormatOptions
+): Intl.DateTimeFormat {
+ const key = `${timeZone ?? "local"}|${JSON.stringify(options)}`;
+ const cached = FORMATTER_CACHE.get(key);
+ if (cached) {
+ return cached;
+ }
+ let formatter: Intl.DateTimeFormat;
+ try {
+ formatter = new Intl.DateTimeFormat("en-US", {
+ ...options,
+ ...(timeZone ? { timeZone } : {}),
+ });
+ } catch {
+ formatter = new Intl.DateTimeFormat("en-US", options);
+ }
+ FORMATTER_CACHE.set(key, formatter);
+ return formatter;
+}
+
+function hassTimeZone(hass: HassLike | undefined): string | undefined {
+ const config = hass?.config;
+ if (!config || typeof config !== "object") {
+ return undefined;
+ }
+ const timeZone = (config as Record).time_zone;
+ return typeof timeZone === "string" && timeZone.trim()
+ ? timeZone.trim()
+ : undefined;
+}
+
+function formatDay(date: Date, timeZone: string | undefined): string {
+ return `${zonedFormatter(timeZone, { weekday: "long" }).format(date)}, ${zonedFormatter(timeZone, { month: "short", day: "numeric" }).format(date)}`;
+}
+
+function formatTime(date: Date, timeZone: string | undefined): string {
+ return zonedFormatter(timeZone, {
+ hour: "numeric",
+ minute: "2-digit",
+ hour12: true,
+ }).format(date);
+}
const CONDITION_LABELS: Record = {
"clear-night": "Clear",
@@ -120,10 +156,6 @@ function parseIsoDate(value: unknown): Date | null {
return Number.isNaN(date.getTime()) ? null : date;
}
-function formatDay(date: Date): string {
- return `${WEEKDAY_FORMAT.format(date)}, ${MONTH_DAY_FORMAT.format(date)}`;
-}
-
function conditionLabel(condition: string): string {
const known = CONDITION_LABELS[condition];
if (known) {
@@ -160,8 +192,8 @@ const boardStyles = css`
display: flex;
flex-direction: column;
align-items: center;
- height: 100vh;
- padding: 48px 110px 150px;
+ height: 1080px;
+ padding: 0;
overflow: hidden;
background: var(--nq-p-parchment);
box-shadow: var(--nq-p-vignette);
@@ -186,6 +218,10 @@ const boardStyles = css`
}
.wordmark-row {
+ position: absolute;
+ top: 64px;
+ left: 0;
+ right: 0;
display: flex;
align-items: center;
justify-content: center;
@@ -233,7 +269,10 @@ const boardStyles = css`
}
.kicker {
- margin: 14px 0 0;
+ position: absolute;
+ top: 154px;
+ left: 0;
+ right: 0;
font-family: var(--nq-p-font-heading);
font-size: 22px;
font-weight: 600;
@@ -245,8 +284,10 @@ const boardStyles = css`
}
.plates {
- width: 100%;
- margin-top: 42px;
+ position: absolute;
+ top: 262px;
+ left: 110px;
+ right: 110px;
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 56px;
@@ -258,9 +299,9 @@ const boardStyles = css`
flex-direction: column;
align-items: center;
justify-content: center;
- gap: 20px;
- height: 560px;
- padding: 36px 28px 32px;
+ gap: 26px;
+ height: 600px;
+ padding: 44px 28px 40px;
border: 2px solid var(--nq-p-panel-border);
border-radius: var(--nq-p-radius-panel);
background: var(--nq-p-card-panel);
@@ -294,8 +335,8 @@ const boardStyles = css`
.crest {
position: relative;
flex: none;
- width: 168px;
- height: 192px;
+ width: 188px;
+ height: 214px;
padding: 4px;
clip-path: ${unsafeCSS(SHIELD_CLIP)};
background: rgba(255, 255, 255, 0.22);
@@ -393,7 +434,10 @@ const boardStyles = css`
}
.hint {
- margin: 26px 0 0;
+ position: absolute;
+ top: 930px;
+ left: 0;
+ right: 0;
font-family: var(--nq-p-font-body);
font-size: 24px;
font-weight: 600;
@@ -569,7 +613,7 @@ export class NestQuestPartyBoardCard extends LitElement {
-