From edca25affa6cab1dc08b3574433995cf6bd38aea Mon Sep 17 00:00:00 2001 From: Joshua Date: Sun, 20 Sep 2026 10:08:38 -0400 Subject: [PATCH 01/11] Add the Lit card toolchain and auto-register the Lovelace bundle --- .gitignore | 1 + README.md | 14 + custom_components/nestquest/__init__.py | 3 + custom_components/nestquest/frontend.py | 35 + custom_components/nestquest/manifest.json | 4 + .../nestquest/www/nestquest-cards.js | 572 ++++++++ frontend/package-lock.json | 1232 +++++++++++++++++ frontend/package.json | 15 + frontend/src/cards/admin.ts | 35 + frontend/src/cards/party-board.ts | 35 + frontend/src/cards/quest-log.ts | 35 + frontend/src/nestquest-cards.ts | 3 + frontend/src/types.ts | 20 + frontend/tsconfig.json | 13 + frontend/vite.config.ts | 19 + tests/conftest.py | 38 + tests/test_frontend.py | 93 ++ 17 files changed, 2167 insertions(+) create mode 100644 custom_components/nestquest/frontend.py create mode 100644 custom_components/nestquest/www/nestquest-cards.js create mode 100644 frontend/package-lock.json create mode 100644 frontend/package.json create mode 100644 frontend/src/cards/admin.ts create mode 100644 frontend/src/cards/party-board.ts create mode 100644 frontend/src/cards/quest-log.ts create mode 100644 frontend/src/nestquest-cards.ts create mode 100644 frontend/src/types.ts create mode 100644 frontend/tsconfig.json create mode 100644 frontend/vite.config.ts create mode 100644 tests/test_frontend.py diff --git a/.gitignore b/.gitignore index 7b33e7f..cc771bf 100644 --- a/.gitignore +++ b/.gitignore @@ -4,3 +4,4 @@ __pycache__/ .venv/ .pytest_cache/ *.metadata.json +frontend/node_modules/ diff --git a/README.md b/README.md index 5f54fdd..7887abe 100644 --- a/README.md +++ b/README.md @@ -172,6 +172,20 @@ To set up the development environment and install dependencies: uv sync ``` +### Frontend cards + +The Lovelace cards live in `frontend/` (TypeScript + Lit) and build to a +single bundle at `custom_components/nestquest/www/nestquest-cards.js`. The +integration registers that bundle as a Lovelace resource automatically on +setup — do not add it by hand. Card YAML is documented in +`design/ENTITIES-AND-SERVICES.md` §4. + +```bash +cd frontend +npm install +npm run build +``` + ### Running Tests Run the test suite with `pytest`: diff --git a/custom_components/nestquest/__init__.py b/custom_components/nestquest/__init__.py index 143e9e0..53e395d 100644 --- a/custom_components/nestquest/__init__.py +++ b/custom_components/nestquest/__init__.py @@ -28,6 +28,7 @@ from .const import ( LOGGER, PLATFORMS, ) +from .frontend import async_register_frontend from .coordinator import NestQuestCoordinator from .db import NestQuestDatabase from .materialize import materialize as _materialize_run @@ -410,11 +411,13 @@ def _deregister_services(hass: HomeAssistant) -> None: async def async_setup(hass: HomeAssistant, config: dict[str, Any]) -> bool: """Set up the NestQuest integration. YAML configuration is not used, returns True.""" + await async_register_frontend(hass) return True async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: """Set up NestQuest from a config entry.""" + await async_register_frontend(hass) hass.data.setdefault(DOMAIN, {}) existing = hass.data[DOMAIN].get(entry.entry_id) if existing is not None: diff --git a/custom_components/nestquest/frontend.py b/custom_components/nestquest/frontend.py new file mode 100644 index 0000000..c46ccbc --- /dev/null +++ b/custom_components/nestquest/frontend.py @@ -0,0 +1,35 @@ +"""Lovelace card bundle registration for NestQuest.""" +from __future__ import annotations + +from pathlib import Path + +from homeassistant.components.frontend import add_extra_js_url +from homeassistant.components.http import StaticPathConfig +from homeassistant.core import HomeAssistant + +WWW_DIR = Path(__file__).parent / "www" +FRONTEND_URL_BASE = "/nestquest-static" +BUNDLE_FILENAME = "nestquest-cards.js" +BUNDLE_URL = f"{FRONTEND_URL_BASE}/{BUNDLE_FILENAME}" +_REGISTERED_ATTR = "_nestquest_frontend_registered" + + +async def async_register_frontend(hass: HomeAssistant) -> None: + """Serve the card bundle and inject it into every Lovelace page. + + Idempotent across config entries: static paths and extra JS URLs are + process-global, so a second entry must not register them again. + """ + if getattr(hass, _REGISTERED_ATTR, False): + return + await hass.http.async_register_static_paths( + [ + StaticPathConfig( + FRONTEND_URL_BASE, + str(WWW_DIR), + cache_headers=False, + ) + ] + ) + add_extra_js_url(hass, BUNDLE_URL) + setattr(hass, _REGISTERED_ATTR, True) diff --git a/custom_components/nestquest/manifest.json b/custom_components/nestquest/manifest.json index ffef4b2..a85ae11 100644 --- a/custom_components/nestquest/manifest.json +++ b/custom_components/nestquest/manifest.json @@ -5,6 +5,10 @@ "@overseer" ], "config_flow": true, + "dependencies": [ + "frontend", + "http" + ], "documentation": "https://github.com/talon2king/NestQuest", "integration_type": "hub", "iot_class": "local_polling", diff --git a/custom_components/nestquest/www/nestquest-cards.js b/custom_components/nestquest/www/nestquest-cards.js new file mode 100644 index 0000000..8f85055 --- /dev/null +++ b/custom_components/nestquest/www/nestquest-cards.js @@ -0,0 +1,572 @@ +const U = globalThis, k = U.ShadowRoot && (U.ShadyCSS === void 0 || U.ShadyCSS.nativeShadow) && "adoptedStyleSheets" in Document.prototype && "replace" in CSSStyleSheet.prototype, Y = /* @__PURE__ */ Symbol(), I = /* @__PURE__ */ new WeakMap(); +let nt = class { + constructor(t, e, s) { + if (this._$cssResult$ = !0, s !== Y) throw Error("CSSResult is not constructable. Use `unsafeCSS` or `css` instead."); + this.cssText = t, this.t = e; + } + get styleSheet() { + let t = this.o; + const e = this.t; + if (k && t === void 0) { + const s = e !== void 0 && e.length === 1; + s && (t = I.get(e)), t === void 0 && ((this.o = t = new CSSStyleSheet()).replaceSync(this.cssText), s && I.set(e, t)); + } + return t; + } + toString() { + return this.cssText; + } +}; +const ot = (r) => new nt(typeof r == "string" ? r : r + "", void 0, Y), ht = (r, t) => { + if (k) r.adoptedStyleSheets = t.map((e) => e instanceof CSSStyleSheet ? e : e.styleSheet); + else for (const e of t) { + const s = document.createElement("style"), i = U.litNonce; + i !== void 0 && s.setAttribute("nonce", i), s.textContent = e.cssText, r.appendChild(s); + } +}, q = k ? (r) => r : (r) => r instanceof CSSStyleSheet ? ((t) => { + let e = ""; + for (const s of t.cssRules) e += s.cssText; + return ot(e); +})(r) : r; +const { is: at, defineProperty: lt, getOwnPropertyDescriptor: ct, getOwnPropertyNames: dt, getOwnPropertySymbols: pt, getPrototypeOf: ut } = Object, H = globalThis, Q = H.trustedTypes, $t = Q ? Q.emptyScript : "", _t = H.reactiveElementPolyfillSupport, b = (r, t) => r, R = { toAttribute(r, t) { + switch (t) { + case Boolean: + r = r ? $t : null; + break; + case Object: + case Array: + r = r == null ? r : JSON.stringify(r); + } + return r; +}, fromAttribute(r, t) { + let e = r; + switch (t) { + case Boolean: + e = r !== null; + break; + case Number: + e = r === null ? null : Number(r); + break; + case Object: + case Array: + try { + e = JSON.parse(r); + } catch { + e = null; + } + } + return e; +} }, tt = (r, t) => !at(r, t), W = { attribute: !0, type: String, converter: R, reflect: !1, useDefault: !1, hasChanged: tt }; +Symbol.metadata ??= /* @__PURE__ */ Symbol("metadata"), H.litPropertyMetadata ??= /* @__PURE__ */ new WeakMap(); +let y = class extends HTMLElement { + static addInitializer(t) { + this._$Ei(), (this.l ??= []).push(t); + } + static get observedAttributes() { + return this.finalize(), this._$Eh && [...this._$Eh.keys()]; + } + static createProperty(t, e = W) { + if (e.state && (e.attribute = !1), this._$Ei(), this.prototype.hasOwnProperty(t) && ((e = Object.create(e)).wrapped = !0), this.elementProperties.set(t, e), !e.noAccessor) { + const s = /* @__PURE__ */ Symbol(), i = this.getPropertyDescriptor(t, s, e); + i !== void 0 && lt(this.prototype, t, i); + } + } + static getPropertyDescriptor(t, e, s) { + const { get: i, set: n } = ct(this.prototype, t) ?? { get() { + return this[e]; + }, set(o) { + this[e] = o; + } }; + return { get: i, set(o) { + const l = i?.call(this); + n?.call(this, o), this.requestUpdate(t, l, s); + }, configurable: !0, enumerable: !0 }; + } + static getPropertyOptions(t) { + return this.elementProperties.get(t) ?? W; + } + static _$Ei() { + if (this.hasOwnProperty(b("elementProperties"))) return; + const t = ut(this); + t.finalize(), t.l !== void 0 && (this.l = [...t.l]), this.elementProperties = new Map(t.elementProperties); + } + static finalize() { + if (this.hasOwnProperty(b("finalized"))) return; + if (this.finalized = !0, this._$Ei(), this.hasOwnProperty(b("properties"))) { + const e = this.properties, s = [...dt(e), ...pt(e)]; + for (const i of s) this.createProperty(i, e[i]); + } + const t = this[Symbol.metadata]; + if (t !== null) { + const e = litPropertyMetadata.get(t); + if (e !== void 0) for (const [s, i] of e) this.elementProperties.set(s, i); + } + this._$Eh = /* @__PURE__ */ new Map(); + for (const [e, s] of this.elementProperties) { + const i = this._$Eu(e, s); + i !== void 0 && this._$Eh.set(i, e); + } + this.elementStyles = this.finalizeStyles(this.styles); + } + static finalizeStyles(t) { + const e = []; + if (Array.isArray(t)) { + const s = new Set(t.flat(1 / 0).reverse()); + for (const i of s) e.unshift(q(i)); + } else t !== void 0 && e.push(q(t)); + return e; + } + static _$Eu(t, e) { + const s = e.attribute; + return s === !1 ? void 0 : typeof s == "string" ? s : typeof t == "string" ? t.toLowerCase() : void 0; + } + constructor() { + super(), this._$Ep = void 0, this.isUpdatePending = !1, this.hasUpdated = !1, this._$Em = null, this._$Ev(); + } + _$Ev() { + this._$ES = new Promise((t) => this.enableUpdating = t), this._$AL = /* @__PURE__ */ new Map(), this._$E_(), this.requestUpdate(), this.constructor.l?.forEach((t) => t(this)); + } + addController(t) { + (this._$EO ??= /* @__PURE__ */ new Set()).add(t), this.renderRoot !== void 0 && this.isConnected && t.hostConnected?.(); + } + removeController(t) { + this._$EO?.delete(t); + } + _$E_() { + const t = /* @__PURE__ */ new Map(), e = this.constructor.elementProperties; + for (const s of e.keys()) this.hasOwnProperty(s) && (t.set(s, this[s]), delete this[s]); + t.size > 0 && (this._$Ep = t); + } + createRenderRoot() { + const t = this.shadowRoot ?? this.attachShadow(this.constructor.shadowRootOptions); + return ht(t, this.constructor.elementStyles), t; + } + connectedCallback() { + this.renderRoot ??= this.createRenderRoot(), this.enableUpdating(!0), this._$EO?.forEach((t) => t.hostConnected?.()); + } + enableUpdating(t) { + } + disconnectedCallback() { + this._$EO?.forEach((t) => t.hostDisconnected?.()); + } + attributeChangedCallback(t, e, s) { + this._$AK(t, s); + } + _$ET(t, e) { + const s = this.constructor.elementProperties.get(t), i = this.constructor._$Eu(t, s); + if (i !== void 0 && s.reflect === !0) { + const n = (s.converter?.toAttribute !== void 0 ? s.converter : R).toAttribute(e, s.type); + this._$Em = t, n == null ? this.removeAttribute(i) : this.setAttribute(i, n), this._$Em = null; + } + } + _$AK(t, e) { + const s = this.constructor, i = s._$Eh.get(t); + if (i !== void 0 && this._$Em !== i) { + const n = s.getPropertyOptions(i), o = typeof n.converter == "function" ? { fromAttribute: n.converter } : n.converter?.fromAttribute !== void 0 ? n.converter : R; + this._$Em = i; + const l = o.fromAttribute(e, n.type); + this[i] = l ?? this._$Ej?.get(i) ?? l, this._$Em = null; + } + } + requestUpdate(t, e, s, i = !1, n) { + if (t !== void 0) { + const o = this.constructor; + if (i === !1 && (n = this[t]), s ??= o.getPropertyOptions(t), !((s.hasChanged ?? tt)(n, e) || s.useDefault && s.reflect && n === this._$Ej?.get(t) && !this.hasAttribute(o._$Eu(t, s)))) return; + this.C(t, e, s); + } + this.isUpdatePending === !1 && (this._$ES = this._$EP()); + } + C(t, e, { useDefault: s, reflect: i, wrapped: n }, o) { + s && !(this._$Ej ??= /* @__PURE__ */ new Map()).has(t) && (this._$Ej.set(t, o ?? e ?? this[t]), n !== !0 || o !== void 0) || (this._$AL.has(t) || (this.hasUpdated || s || (e = void 0), this._$AL.set(t, e)), i === !0 && this._$Em !== t && (this._$Eq ??= /* @__PURE__ */ new Set()).add(t)); + } + async _$EP() { + this.isUpdatePending = !0; + try { + await this._$ES; + } catch (e) { + Promise.reject(e); + } + const t = this.scheduleUpdate(); + return t != null && await t, !this.isUpdatePending; + } + scheduleUpdate() { + return this.performUpdate(); + } + performUpdate() { + if (!this.isUpdatePending) return; + if (!this.hasUpdated) { + if (this.renderRoot ??= this.createRenderRoot(), this._$Ep) { + for (const [i, n] of this._$Ep) this[i] = n; + this._$Ep = void 0; + } + const s = this.constructor.elementProperties; + if (s.size > 0) for (const [i, n] of s) { + const { wrapped: o } = n, l = this[i]; + o !== !0 || this._$AL.has(i) || l === void 0 || this.C(i, void 0, n, l); + } + } + let t = !1; + const e = this._$AL; + try { + t = this.shouldUpdate(e), t ? (this.willUpdate(e), this._$EO?.forEach((s) => s.hostUpdate?.()), this.update(e)) : this._$EM(); + } catch (s) { + throw t = !1, this._$EM(), s; + } + t && this._$AE(e); + } + willUpdate(t) { + } + _$AE(t) { + this._$EO?.forEach((e) => e.hostUpdated?.()), this.hasUpdated || (this.hasUpdated = !0, this.firstUpdated(t)), this.updated(t); + } + _$EM() { + this._$AL = /* @__PURE__ */ new Map(), this.isUpdatePending = !1; + } + get updateComplete() { + return this.getUpdateComplete(); + } + getUpdateComplete() { + return this._$ES; + } + shouldUpdate(t) { + return !0; + } + update(t) { + this._$Eq &&= this._$Eq.forEach((e) => this._$ET(e, this[e])), this._$EM(); + } + updated(t) { + } + firstUpdated(t) { + } +}; +y.elementStyles = [], y.shadowRootOptions = { mode: "open" }, y[b("elementProperties")] = /* @__PURE__ */ new Map(), y[b("finalized")] = /* @__PURE__ */ new Map(), _t?.({ ReactiveElement: y }), (H.reactiveElementVersions ??= []).push("2.1.2"); +const L = globalThis, V = (r) => r, O = L.trustedTypes, K = O ? O.createPolicy("lit-html", { createHTML: (r) => r }) : void 0, et = "$lit$", _ = `lit$${Math.random().toFixed(9).slice(2)}$`, st = "?" + _, ft = `<${st}>`, g = document, w = () => g.createComment(""), C = (r) => r === null || typeof r != "object" && typeof r != "function", z = Array.isArray, At = (r) => z(r) || typeof r?.[Symbol.iterator] == "function", T = `[ +\f\r]`, S = /<(?:(!--|\/[^a-zA-Z])|(\/?[a-zA-Z][^>\s]*)|(\/?$))/g, Z = /-->/g, J = />/g, f = RegExp(`>|${T}(?:([^\\s"'>=/]+)(${T}*=${T}*(?:[^ +\f\r"'\`<>=]|("|')|))|$)`, "g"), F = /'/g, G = /"/g, it = /^(?:script|style|textarea|title)$/i, mt = (r) => (t, ...e) => ({ _$litType$: r, strings: t, values: e }), j = mt(1), v = /* @__PURE__ */ Symbol.for("lit-noChange"), d = /* @__PURE__ */ Symbol.for("lit-nothing"), X = /* @__PURE__ */ new WeakMap(), A = g.createTreeWalker(g, 129); +function rt(r, t) { + if (!z(r) || !r.hasOwnProperty("raw")) throw Error("invalid template strings array"); + return K !== void 0 ? K.createHTML(t) : t; +} +const gt = (r, t) => { + const e = r.length - 1, s = []; + let i, n = t === 2 ? "" : t === 3 ? "" : "", o = S; + for (let l = 0; l < e; l++) { + const h = r[l]; + let c, p, a = -1, u = 0; + for (; u < h.length && (o.lastIndex = u, p = o.exec(h), p !== null); ) u = o.lastIndex, o === S ? p[1] === "!--" ? o = Z : p[1] !== void 0 ? o = J : p[2] !== void 0 ? (it.test(p[2]) && (i = RegExp("" ? (o = i ?? S, a = -1) : p[1] === void 0 ? a = -2 : (a = o.lastIndex - p[2].length, c = p[1], o = p[3] === void 0 ? f : p[3] === '"' ? G : F) : o === G || o === F ? o = f : o === Z || o === J ? o = S : (o = f, i = void 0); + const $ = o === f && r[l + 1].startsWith("/>") ? " " : ""; + n += o === S ? h + ft : a >= 0 ? (s.push(c), h.slice(0, a) + et + h.slice(a) + _ + $) : h + _ + (a === -2 ? l : $); + } + return [rt(r, n + (r[e] || "") + (t === 2 ? "" : t === 3 ? "" : "")), s]; +}; +class P { + constructor({ strings: t, _$litType$: e }, s) { + let i; + this.parts = []; + let n = 0, o = 0; + const l = t.length - 1, h = this.parts, [c, p] = gt(t, e); + if (this.el = P.createElement(c, s), A.currentNode = this.el.content, e === 2 || e === 3) { + const a = this.el.content.firstChild; + a.replaceWith(...a.childNodes); + } + for (; (i = A.nextNode()) !== null && h.length < l; ) { + if (i.nodeType === 1) { + if (i.hasAttributes()) for (const a of i.getAttributeNames()) if (a.endsWith(et)) { + const u = p[o++], $ = i.getAttribute(a).split(_), N = /([.?@])?(.*)/.exec(u); + h.push({ type: 1, index: n, name: N[2], strings: $, ctor: N[1] === "." ? vt : N[1] === "?" ? Et : N[1] === "@" ? St : M }), i.removeAttribute(a); + } else a.startsWith(_) && (h.push({ type: 6, index: n }), i.removeAttribute(a)); + if (it.test(i.tagName)) { + const a = i.textContent.split(_), u = a.length - 1; + if (u > 0) { + i.textContent = O ? O.emptyScript : ""; + for (let $ = 0; $ < u; $++) i.append(a[$], w()), A.nextNode(), h.push({ type: 2, index: ++n }); + i.append(a[u], w()); + } + } + } else if (i.nodeType === 8) if (i.data === st) h.push({ type: 2, index: n }); + else { + let a = -1; + for (; (a = i.data.indexOf(_, a + 1)) !== -1; ) h.push({ type: 7, index: n }), a += _.length - 1; + } + n++; + } + } + static createElement(t, e) { + const s = g.createElement("template"); + return s.innerHTML = t, s; + } +} +function E(r, t, e = r, s) { + if (t === v) return t; + let i = s !== void 0 ? e._$Co?.[s] : e._$Cl; + const n = C(t) ? void 0 : t._$litDirective$; + return i?.constructor !== n && (i?._$AO?.(!1), n === void 0 ? i = void 0 : (i = new n(r), i._$AT(r, e, s)), s !== void 0 ? (e._$Co ??= [])[s] = i : e._$Cl = i), i !== void 0 && (t = E(r, i._$AS(r, t.values), i, s)), t; +} +class yt { + constructor(t, e) { + this._$AV = [], this._$AN = void 0, this._$AD = t, this._$AM = e; + } + get parentNode() { + return this._$AM.parentNode; + } + get _$AU() { + return this._$AM._$AU; + } + u(t) { + const { el: { content: e }, parts: s } = this._$AD, i = (t?.creationScope ?? g).importNode(e, !0); + A.currentNode = i; + let n = A.nextNode(), o = 0, l = 0, h = s[0]; + for (; h !== void 0; ) { + if (o === h.index) { + let c; + h.type === 2 ? c = new x(n, n.nextSibling, this, t) : h.type === 1 ? c = new h.ctor(n, h.name, h.strings, this, t) : h.type === 6 && (c = new bt(n, this, t)), this._$AV.push(c), h = s[++l]; + } + o !== h?.index && (n = A.nextNode(), o++); + } + return A.currentNode = g, i; + } + p(t) { + let e = 0; + for (const s of this._$AV) s !== void 0 && (s.strings !== void 0 ? (s._$AI(t, s, e), e += s.strings.length - 2) : s._$AI(t[e])), e++; + } +} +class x { + get _$AU() { + return this._$AM?._$AU ?? this._$Cv; + } + constructor(t, e, s, i) { + this.type = 2, this._$AH = d, this._$AN = void 0, this._$AA = t, this._$AB = e, this._$AM = s, this.options = i, this._$Cv = i?.isConnected ?? !0; + } + get parentNode() { + let t = this._$AA.parentNode; + const e = this._$AM; + return e !== void 0 && t?.nodeType === 11 && (t = e.parentNode), t; + } + get startNode() { + return this._$AA; + } + get endNode() { + return this._$AB; + } + _$AI(t, e = this) { + t = E(this, t, e), C(t) ? t === d || t == null || t === "" ? (this._$AH !== d && this._$AR(), this._$AH = d) : t !== this._$AH && t !== v && this._(t) : t._$litType$ !== void 0 ? this.$(t) : t.nodeType !== void 0 ? this.T(t) : At(t) ? this.k(t) : this._(t); + } + O(t) { + return this._$AA.parentNode.insertBefore(t, this._$AB); + } + T(t) { + this._$AH !== t && (this._$AR(), this._$AH = this.O(t)); + } + _(t) { + this._$AH !== d && C(this._$AH) ? this._$AA.nextSibling.data = t : this.T(g.createTextNode(t)), this._$AH = t; + } + $(t) { + const { values: e, _$litType$: s } = t, i = typeof s == "number" ? this._$AC(t) : (s.el === void 0 && (s.el = P.createElement(rt(s.h, s.h[0]), this.options)), s); + if (this._$AH?._$AD === i) this._$AH.p(e); + else { + const n = new yt(i, this), o = n.u(this.options); + n.p(e), this.T(o), this._$AH = n; + } + } + _$AC(t) { + let e = X.get(t.strings); + return e === void 0 && X.set(t.strings, e = new P(t)), e; + } + k(t) { + z(this._$AH) || (this._$AH = [], this._$AR()); + const e = this._$AH; + let s, i = 0; + for (const n of t) i === e.length ? e.push(s = new x(this.O(w()), this.O(w()), this, this.options)) : s = e[i], s._$AI(n), i++; + i < e.length && (this._$AR(s && s._$AB.nextSibling, i), e.length = i); + } + _$AR(t = this._$AA.nextSibling, e) { + for (this._$AP?.(!1, !0, e); t !== this._$AB; ) { + const s = V(t).nextSibling; + V(t).remove(), t = s; + } + } + setConnected(t) { + this._$AM === void 0 && (this._$Cv = t, this._$AP?.(t)); + } +} +class M { + get tagName() { + return this.element.tagName; + } + get _$AU() { + return this._$AM._$AU; + } + constructor(t, e, s, i, n) { + this.type = 1, this._$AH = d, this._$AN = void 0, this.element = t, this.name = e, this._$AM = i, this.options = n, s.length > 2 || s[0] !== "" || s[1] !== "" ? (this._$AH = Array(s.length - 1).fill(new String()), this.strings = s) : this._$AH = d; + } + _$AI(t, e = this, s, i) { + const n = this.strings; + let o = !1; + if (n === void 0) t = E(this, t, e, 0), o = !C(t) || t !== this._$AH && t !== v, o && (this._$AH = t); + else { + const l = t; + let h, c; + for (t = n[0], h = 0; h < n.length - 1; h++) c = E(this, l[s + h], e, h), c === v && (c = this._$AH[h]), o ||= !C(c) || c !== this._$AH[h], c === d ? t = d : t !== d && (t += (c ?? "") + n[h + 1]), this._$AH[h] = c; + } + o && !i && this.j(t); + } + j(t) { + t === d ? this.element.removeAttribute(this.name) : this.element.setAttribute(this.name, t ?? ""); + } +} +class vt extends M { + constructor() { + super(...arguments), this.type = 3; + } + j(t) { + this.element[this.name] = t === d ? void 0 : t; + } +} +class Et extends M { + constructor() { + super(...arguments), this.type = 4; + } + j(t) { + this.element.toggleAttribute(this.name, !!t && t !== d); + } +} +class St extends M { + constructor(t, e, s, i, n) { + super(t, e, s, i, n), this.type = 5; + } + _$AI(t, e = this) { + if ((t = E(this, t, e, 0) ?? d) === v) return; + const s = this._$AH, i = t === d && s !== d || t.capture !== s.capture || t.once !== s.once || t.passive !== s.passive, n = t !== d && (s === d || i); + i && this.element.removeEventListener(this.name, this, s), n && this.element.addEventListener(this.name, this, t), this._$AH = t; + } + handleEvent(t) { + typeof this._$AH == "function" ? this._$AH.call(this.options?.host ?? this.element, t) : this._$AH.handleEvent(t); + } +} +class bt { + constructor(t, e, s) { + this.element = t, this.type = 6, this._$AN = void 0, this._$AM = e, this.options = s; + } + get _$AU() { + return this._$AM._$AU; + } + _$AI(t) { + E(this, t); + } +} +const wt = L.litHtmlPolyfillSupport; +wt?.(P, x), (L.litHtmlVersions ??= []).push("3.3.3"); +const Ct = (r, t, e) => { + const s = e?.renderBefore ?? t; + let i = s._$litPart$; + if (i === void 0) { + const n = e?.renderBefore ?? null; + s._$litPart$ = i = new x(t.insertBefore(w(), n), n, void 0, e ?? {}); + } + return i._$AI(r), i; +}; +const D = globalThis; +class m extends y { + constructor() { + super(...arguments), this.renderOptions = { host: this }, this._$Do = void 0; + } + createRenderRoot() { + const t = super.createRenderRoot(); + return this.renderOptions.renderBefore ??= t.firstChild, t; + } + update(t) { + const e = this.render(); + this.hasUpdated || (this.renderOptions.isConnected = this.isConnected), super.update(t), this._$Do = Ct(e, this.renderRoot, this.renderOptions); + } + connectedCallback() { + super.connectedCallback(), this._$Do?.setConnected(!0); + } + disconnectedCallback() { + super.disconnectedCallback(), this._$Do?.setConnected(!1); + } + render() { + return v; + } +} +m._$litElement$ = !0, m.finalized = !0, D.litElementHydrateSupport?.({ LitElement: m }); +const Pt = D.litElementPolyfillSupport; +Pt?.({ LitElement: m }); +(D.litElementVersions ??= []).push("4.2.2"); +function B(r) { + window.customCards = window.customCards ?? [], window.customCards.push(r); +} +class xt extends m { + static properties = { + hass: { attribute: !1 }, + _config: { state: !0 } + }; + hass; + _config; + setConfig(t) { + if (!t || typeof t != "object") + throw new Error("Invalid configuration"); + this._config = t; + } + getCardSize() { + return 6; + } + render() { + return j`
NestQuest Party Board
`; + } +} +customElements.define("nestquest-party-board-card", xt); +B({ + type: "nestquest-party-board-card", + name: "NestQuest Party Board", + description: "Kids' attract screen for present and away adventurers." +}); +class Nt extends m { + static properties = { + hass: { attribute: !1 }, + _config: { state: !0 } + }; + hass; + _config; + setConfig(t) { + if (!t || typeof t != "object") + throw new Error("Invalid configuration"); + this._config = t; + } + getCardSize() { + return 8; + } + render() { + return j`
NestQuest Quest Log
`; + } +} +customElements.define("nestquest-quest-log-card", Nt); +B({ + type: "nestquest-quest-log-card", + name: "NestQuest Quest Log", + description: "One child's daily quests, grouped by window." +}); +class Ut extends m { + static properties = { + hass: { attribute: !1 }, + _config: { state: !0 } + }; + hass; + _config; + setConfig(t) { + if (!t || typeof t != "object") + throw new Error("Invalid configuration"); + this._config = t; + } + getCardSize() { + return 8; + } + render() { + return j`
NestQuest Admin
`; + } +} +customElements.define("nestquest-admin-card", Ut); +B({ + type: "nestquest-admin-card", + name: "NestQuest Admin", + description: "Parent phone view for today, tasks, schedule, and history." +}); diff --git a/frontend/package-lock.json b/frontend/package-lock.json new file mode 100644 index 0000000..2a21a54 --- /dev/null +++ b/frontend/package-lock.json @@ -0,0 +1,1232 @@ +{ + "name": "nestquest-cards", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "nestquest-cards", + "dependencies": { + "lit": "^3.3.0" + }, + "devDependencies": { + "typescript": "^5.9.2", + "vite": "^7.1.5" + } + }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.28.2.tgz", + "integrity": "sha512-XExcO+dvLKvVtNTibSTBej1NCAbaGhWn9Ww1ZPx80qsahhPFe/8jgWP0IchNe0F3HwkU7n8ejhH8bjonqht8mQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.28.2.tgz", + "integrity": "sha512-kXXoiPVVGQcnIYGOeaovwOURpniDBpSq4A03qkQ+BMQqtGG6HYap3xne9C1O1yo4TR3qxlCX5IqqmX6fFo2Lqg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.28.2.tgz", + "integrity": "sha512-5YfKeeI8qWfBZIX+u2xZC3Zlb3Os/gLS2sbEKM+I4ZOcsWmHS2WLysCcQZDAFRslDUU5Oiq44gf6PYN1vGwG5A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.28.2.tgz", + "integrity": "sha512-O387ite7SzUyCcy3JQX4P4bLtEA7bLLkx+esve5JHnyYfNTxcVpXZo9jhdB0lTKN44gztELTdU7nS8Nr16Fs1Q==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.28.2.tgz", + "integrity": "sha512-n4KqkOQrraxHJcgjM1RvwbigfQKIKJVpM7xp+KsxiyUSrRdIXnt73VhrPAx0fV44hgfmIVKjxMN9J1t5jySVkw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.28.2.tgz", + "integrity": "sha512-uq6suIWYP37qzGddBKPw5QEQPi6HiLGsO7UmkpfyaYNQ3D+rN6w6WfwH+nuqcGXWvawGwxOEroO4YGnFh95azw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.28.2.tgz", + "integrity": "sha512-n+I0BTSRIoy+d6RPKnEVwql5UwBJolytvY4mAOIEJorKlqgPII8ix6slVVrfZ5Tnj7glIZvloylbB/EJPMWEXw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.28.2.tgz", + "integrity": "sha512-78XJTJkvPs0kz2w61301PJjXl4g7q3JqiYMZ/M/yVI73EHBrCRTgkhu9oqG7vPqq+a/yadEW8aD+agKlk5xrmg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.28.2.tgz", + "integrity": "sha512-XlDnu2q5yoqems+xay6wSAcg9DDD7K9RLKZEBOMZm3ckNpJBvOX20tSfby8KfrrhINDyv9V2YVZKY/SpoGJI8w==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.28.2.tgz", + "integrity": "sha512-pW4AC0P3it8c7do9MVM4p51FzHzdM/TZrerurgRcHJ2WTa1VQ1CIq18xncfpBJw4ojkiZZrKW2yIBWBP92j6Ug==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.28.2.tgz", + "integrity": "sha512-CYbnj78HsIeA+DhgUKgFCfvNsTHFhMMrinUrMZpDXJXKN8T3XViTZ/+wtHeVxEWY8ewSzTFN+nRmSwO2tZaLUQ==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.28.2.tgz", + "integrity": "sha512-buwkd8nsph4R+ajRvw0qM5Hja/TXQow3ptzWO2EbG/cqcIkHloRrdlBtQlshyYGTNFvfkfJ5tpPLVkY4DtsPfQ==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.28.2.tgz", + "integrity": "sha512-ZVykbDyk7519VwiNb9Lcj9m8XM6v5V9uKPvrEMkkEedVewf+0itkhahp4HDpgERXhwLRpWFypsGbG/J8s0QjJA==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.28.2.tgz", + "integrity": "sha512-CAXl+Dtd9UUuJd8pKKdwh6MLm3MUMiqMPmhZ3tTSXPqfyQ3vDl6R5hZdZ/kYojK4ofXtdfSv1tFq8XzWx3heNQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.28.2.tgz", + "integrity": "sha512-GeXCej4IQtU1B+QlDV8W/RRvbzI3O/Stss+/bCXv4lZls5WGRtu2a+3JkA3i4qIUlMXpcHebWpF8AkJhATowuA==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.28.2.tgz", + "integrity": "sha512-3H1weTYZPxt/WOhByszQZybS9w5lKzUn1FDMsgEChbHWQwHYQQRfBxgCcZvPhjHfKyJjIievvMmEUawJrdY9Dg==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.28.2.tgz", + "integrity": "sha512-4xTZr1FUmSoQW4XIWmit3tzQrUTZM+N3P0XV8xROKYF50XfI7xeO90+1bZvNwxIufQ9hDQVRJH5YhgPVF8A/HQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-arm64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.28.2.tgz", + "integrity": "sha512-sSATRjPeDBg3pdgHoQfoYBob11Kk1FGa9lui5RIHZCoCkJa9QKlvl3/vKz2usCmYYjs7ymJR/2Nnsqe+Hjt5nw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.28.2.tgz", + "integrity": "sha512-lqnzCV+mM0gIADaKihiCg6ifgfU2L3h5E33rNQBN1Y4MaVGnzryzmvvf7UHxprpQdE8hpqLolJ9Rl+SkIRDpyw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-arm64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.28.2.tgz", + "integrity": "sha512-AL2qJILH7lNjrDmCQDvdxMfAUIv8KMNZOvrwAQ8i8//ntL9FflhOyMJ8OZSMBb8/AWXe3/5v5S20y3zCoZWKoQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.28.2.tgz", + "integrity": "sha512-QtiuPytchRyC4rwUKhexJdQKvDuZ6hWloi3igqPQNUJCS1/v9EiO3UTOXR6A3FoMo4fnAKbWJdqaIwhOzh8qEw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openharmony-arm64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.28.2.tgz", + "integrity": "sha512-WkhYDmpTjLvGlScA1rwjRUmhl4k8oXR3cIbtqWmELgU/dFeHHlEllxDvdWcNJV9rbzCexB5vz8gtNewWLgCT7Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.28.2.tgz", + "integrity": "sha512-GPMSkTOtMnv2U2F8gxe4Io6qmVs+YKyp832Etqqxr0hFngmXQ3rzwytelm3GIn7T4VviRUlf3sOgBOiTdvaf7g==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.28.2.tgz", + "integrity": "sha512-PIhhEkE9uPBleRBrQEJpUn7MBnibZzbGzYWPmY3x+YoVg/95zbjB4CxPPOQ8l5tYYM4mMaCthF8/1DIfBQQyWQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.28.2.tgz", + "integrity": "sha512-YmJbfTlvU7Sdn9BB+4PRES4oB6pxgS37MAONj+hBr/cpXS1aBPKXxNnDbu+QCWPj0o9dgyxeq79g6c5P8KeuYA==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.28.2.tgz", + "integrity": "sha512-5ebpxr3nWMzrL/rnUI755Jkuee0bHL/Gq0WTF9lvcpv73wAp5eu8MfBUgWK9bhWvZjj7yX8etf/8tI8Ney695g==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@lit-labs/ssr-dom-shim": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/@lit-labs/ssr-dom-shim/-/ssr-dom-shim-1.6.0.tgz", + "integrity": "sha512-VHb0ALPMTlgKjM6yIxxoQNnpKyUKLD04VzeQdsiXkMqkvYlAHxq9glGLmgbb889/1GsohSOAjvQYoiBppXFqrQ==", + "license": "BSD-3-Clause" + }, + "node_modules/@lit/reactive-element": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/@lit/reactive-element/-/reactive-element-2.1.2.tgz", + "integrity": "sha512-pbCDiVMnne1lYUIaYNN5wrwQXDtHaYtg7YEFPeW+hws6U47WeFvISGUWekPGKWOP1ygrs0ef0o1VJMk1exos5A==", + "license": "BSD-3-Clause", + "dependencies": { + "@lit-labs/ssr-dom-shim": "^1.5.0" + } + }, + "node_modules/@napi-rs/lzma-linux-x64-gnu": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/@napi-rs/lzma-linux-x64-gnu/-/lzma-linux-x64-gnu-1.5.1.tgz", + "integrity": "sha512-oTXEIha4SsuXdTA4Iyskj0kpdx2yVXdhd75c2v3xGrHFfVMsbhTPZU/nMPL4sWKo4pBHm3aucLaqGlF696dTyQ==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^22.20 || ^24.12 || >=25" + } + }, + "node_modules/@rollup/rollup-android-arm-eabi": { + "version": "4.63.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.63.4.tgz", + "integrity": "sha512-I+BSHzTAhKN2n7ZwGZsegGcZjDpLqFOMAtJz/u6uFGe0pUFbq56dEHjqJV/ZUdRJtNXNxA+hREUatZBvMR3Oiw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-android-arm64": { + "version": "4.63.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.63.4.tgz", + "integrity": "sha512-pu3BdjS2LtEzRu2elmGzS3fIeWSZy4BMDIaLNwjorO76+k2d0LMluijhsDx3KQyQBQ/lLUZCQA9/s6csvUfuhw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-darwin-arm64": { + "version": "4.63.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.63.4.tgz", + "integrity": "sha512-xfSrj9MHnWK9GaSqT9U0ImHtH/N8WZlHLx4cZHiuLcqs640hvZ3hLPd5UR2AZS57FaE8HrRUSpltbZdWRxHiDA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-darwin-x64": { + "version": "4.63.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.63.4.tgz", + "integrity": "sha512-bqU99PLJb/dqb3S0GIMdeuyAEETSUgZBoqXYd3Sd+WCsV+MmPhnN6JrotWyir31+QgH7EvvE5/mwGJlEoci8Fw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-freebsd-arm64": { + "version": "4.63.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.63.4.tgz", + "integrity": "sha512-JinsFZ5G40oXQb+sUuiA5x689vhr6dDYK0H0NL+rwKdL6CqnmYN8PE4ZwfRSoIjrCxqTQG/SLfTtSvHeGxoVlw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-freebsd-x64": { + "version": "4.63.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.63.4.tgz", + "integrity": "sha512-GAdA4UxpiNm27cLHr2GqXBpAD0x9FqwYBY7/YSP0Ss0/PNi4k8gbviqpIpYbVSRBaS2ZcegXEzgTQMbRNCwxCw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-linux-arm-gnueabihf": { + "version": "4.63.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.63.4.tgz", + "integrity": "sha512-qDd6NoA1znaLjp4jR5U/KWCdLAKDJNB8W9ChbbDaKbo0xA+Atln5HK6LFCZ4oJQpemtRZA288DCirFRjrspptw==", + "cpu": [ + "arm" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm-musleabihf": { + "version": "4.63.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.63.4.tgz", + "integrity": "sha512-WtB5Tz5KTNINb8ZA+8sQ7bmjuS1JrRT7YverYIhUGdWWDlpzVWmIwuZE+jidkEXUn1l0zrEkaIMa8dHF3NGcsA==", + "cpu": [ + "arm" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-gnu": { + "version": "4.63.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.63.4.tgz", + "integrity": "sha512-VcQ3L1tjnkKzWjryAVaFhHEWcqOfICX9uxVVoDzm2t0DpgKRHd2zOpVrJc0xsWeBZcBFyYROCIBdyR/fS174pg==", + "cpu": [ + "arm64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-musl": { + "version": "4.63.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.63.4.tgz", + "integrity": "sha512-6+ZQX6P5s0cMDN2Ypb8Lbm2+/sZYmZjdaYny992ujUU9UKi/4CWoJWsl1pNvjWJHNHGK51m+jKGLlh1ylb2ifQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-gnu": { + "version": "4.63.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.63.4.tgz", + "integrity": "sha512-D72ZnvkFkBXOfzMMQLcwfPLyGkKb7HZ9/mf97B7v6/P5Lbv4oFOtSY/uHbS8lH6uKUOxoKiuokdb50XZSzzbJw==", + "cpu": [ + "loong64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-musl": { + "version": "4.63.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.63.4.tgz", + "integrity": "sha512-piU6BxeqA3O9KSu3kRCIQQtNqFFaTu21SEV4FwaRZowpnj3bLaWPZHw+xFqCs0XlJ+aOH3PTRWGoglH+mKA/OA==", + "cpu": [ + "loong64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-gnu": { + "version": "4.63.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.63.4.tgz", + "integrity": "sha512-/5PGpHwqt2EEEOUs1XwzubE/ucr0dWDQ+to3zqi4Ds7EWpwtQ79wXc4JBoxqj/OwpawTsKWzJxHfSuBOq3DrWA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-musl": { + "version": "4.63.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.63.4.tgz", + "integrity": "sha512-cX3beZDLWt7G2oJF+nhChiT+qtaihs+S2xi7ziGmVB+2pwPng6D0Ed0HmElQOgv2UsUmSJJLGwpBao/3TDx3VA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-gnu": { + "version": "4.63.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.63.4.tgz", + "integrity": "sha512-1uz2mGWHyptR7DgHHrlbdRAjXK7v7elGZ9lMja910/RP+ZYbX6xAmCiU9UZSX4hqmgtHMv6lr5l3kq1HIOpcag==", + "cpu": [ + "riscv64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-musl": { + "version": "4.63.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.63.4.tgz", + "integrity": "sha512-nLS8topojxyz7SRpKR2IODRpQ0XPZ+xaOXvT3+hqK/Uy8Lo5HFgkkIBiIrCu5tL5YqzTvgovGw55PwpahTAGig==", + "cpu": [ + "riscv64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-s390x-gnu": { + "version": "4.63.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.63.4.tgz", + "integrity": "sha512-gs7DRKotr3l3q+jGPQBjH0ng1FjlEDm5ueQrkw5JtQvtLyEIcLASqAEaor56BhkKRzk+IcQzrcanBdb/bBQn8g==", + "cpu": [ + "s390x" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-gnu": { + "version": "4.63.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.63.4.tgz", + "integrity": "sha512-791ET7W17NnScOZM7h4dX5hYspxE28htPFsb1awY/NRR8+PRNkS53e475rDdxXXDrP+kwnCcNWg9CX5ztn/Aqw==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-musl": { + "version": "4.63.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.63.4.tgz", + "integrity": "sha512-iwZQRcmj7g88g3tzefIrQY7qvmuA/cfYwhrDtTBhsmukO4U2huVO5W+86XacUMRvdSFVAc6kZUZy21JaRwiB9w==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-openbsd-x64": { + "version": "4.63.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.63.4.tgz", + "integrity": "sha512-dVHFp9gRWrdTpnqQuGfCwd7hOQDatK1VCP2iWhLY/cGrOQs/ucFzJ6A5SRqbXX12ZDI8EUuejSM5kwg+ja7Png==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ] + }, + "node_modules/@rollup/rollup-openharmony-arm64": { + "version": "4.63.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.63.4.tgz", + "integrity": "sha512-t3NlauOW6gxZVVFcBEnO62Cb4wbyDFL416gTg1uFI/2tgqYQlf69FbSE115Ajre9I+c26Lk4mcmdFUsS/DGifQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ] + }, + "node_modules/@rollup/rollup-win32-arm64-msvc": { + "version": "4.63.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.63.4.tgz", + "integrity": "sha512-xWuIaSye5FWZF8+UYtVEcHtRJDN5kN9Kfgxx3Kq8XIov9KSKbc1fiqQCm90SKrgQbUXZelbnUhnlUJmfSE7P9A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-ia32-msvc": { + "version": "4.63.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.63.4.tgz", + "integrity": "sha512-9ALJJUOg/ZflMJepVo2PlgsGxSaxN7SQ4Z8GoZfVlarWr6r3rkHUNsd/zAio7p4YMtChSMXPionxej4Hkf6CXQ==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-gnu": { + "version": "4.63.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.63.4.tgz", + "integrity": "sha512-blj9z5qx/Pv4WU0W1NMFDB97e0JH5ed+aZGywW8WCvp/NhWX/4PFAq5uu6Q0AebNn+Vo6KzUYDT++JzTT5ojlQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-msvc": { + "version": "4.63.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.63.4.tgz", + "integrity": "sha512-Erx822VRBwLa124shbj+wNXe//BOgMEctDV0m1aqTQdNO1S69DgNUCFKC1RCeZfixs1J31l6igk1ziyXErbigQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@types/estree": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.9.tgz", + "integrity": "sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/trusted-types": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/@types/trusted-types/-/trusted-types-2.0.7.tgz", + "integrity": "sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw==", + "license": "MIT" + }, + "node_modules/esbuild": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.28.2.tgz", + "integrity": "sha512-HKVLS8dvII+xoKW9kmqxbRKrnWEXfJJr/FZhhJmiqIB0e053QNYFqOBouTMO/k5sID4MvCiUCvv8b9M4h32wIA==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.28.2", + "@esbuild/android-arm": "0.28.2", + "@esbuild/android-arm64": "0.28.2", + "@esbuild/android-x64": "0.28.2", + "@esbuild/darwin-arm64": "0.28.2", + "@esbuild/darwin-x64": "0.28.2", + "@esbuild/freebsd-arm64": "0.28.2", + "@esbuild/freebsd-x64": "0.28.2", + "@esbuild/linux-arm": "0.28.2", + "@esbuild/linux-arm64": "0.28.2", + "@esbuild/linux-ia32": "0.28.2", + "@esbuild/linux-loong64": "0.28.2", + "@esbuild/linux-mips64el": "0.28.2", + "@esbuild/linux-ppc64": "0.28.2", + "@esbuild/linux-riscv64": "0.28.2", + "@esbuild/linux-s390x": "0.28.2", + "@esbuild/linux-x64": "0.28.2", + "@esbuild/netbsd-arm64": "0.28.2", + "@esbuild/netbsd-x64": "0.28.2", + "@esbuild/openbsd-arm64": "0.28.2", + "@esbuild/openbsd-x64": "0.28.2", + "@esbuild/openharmony-arm64": "0.28.2", + "@esbuild/sunos-x64": "0.28.2", + "@esbuild/win32-arm64": "0.28.2", + "@esbuild/win32-ia32": "0.28.2", + "@esbuild/win32-x64": "0.28.2" + } + }, + "node_modules/fdir": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/lit": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/lit/-/lit-3.3.3.tgz", + "integrity": "sha512-fycuvZg/hkpozL00lm1pEJH5nN/lr9ZXd6mJI2HSN4+Bzc+LDNdEApJ6HFbPkdFNHLvOplIIuJvxkS4XUxqirw==", + "license": "BSD-3-Clause", + "dependencies": { + "@lit/reactive-element": "^2.1.0", + "lit-element": "^4.2.0", + "lit-html": "^3.3.0" + } + }, + "node_modules/lit-element": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/lit-element/-/lit-element-4.2.2.tgz", + "integrity": "sha512-aFKhNToWxoyhkNDmWZwEva2SlQia+jfG0fjIWV//YeTaWrVnOxD89dPKfigCUspXFmjzOEUQpOkejH5Ly6sG0w==", + "license": "BSD-3-Clause", + "dependencies": { + "@lit-labs/ssr-dom-shim": "^1.5.0", + "@lit/reactive-element": "^2.1.0", + "lit-html": "^3.3.0" + } + }, + "node_modules/lit-html": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/lit-html/-/lit-html-3.3.3.tgz", + "integrity": "sha512-el8M6jK2o3RXBnrSHX3ZKrsN8zEV63pSExTO1wYJz7QndGYZ8353e2a5PPX+qHe2aGayfnchQmkAojaWAREOIA==", + "license": "BSD-3-Clause", + "dependencies": { + "@types/trusted-types": "^2.0.2" + } + }, + "node_modules/nanoid": { + "version": "3.3.19", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.19.tgz", + "integrity": "sha512-Y2tUNy4ouw6tq5oDSKeQYGOyhkUBhNOcGV/02KC+6kd9eDGqdZd++mjMiIDilrBYvjEnCYvVtsuHCuP+okSfug==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "dev": true, + "license": "ISC" + }, + "node_modules/picomatch": { + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.7.tgz", + "integrity": "sha512-qcJu88Q2IWqJsDD529JKMdwGm/dvInW4HvQnRwiH9JtihJvzGOscDtHE3x1pBKeUOTysQ8kVmLnJ2kJu7yhcGA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/postcss": { + "version": "8.5.28", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.28.tgz", + "integrity": "sha512-RRuzqDtt5Y9h3quz5hWhK+TPnsmVs6WwSU6LkJMeY4HstUEDuYTG8UJSdawMRzmzAtV+KEoG8N3Qg2qLy5vM/A==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "nanoid": "^3.3.18", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/rollup": { + "version": "4.63.4", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.63.4.tgz", + "integrity": "sha512-4U0liVayNIoLp3GFl1FcI8561WepLnZ1rqfraGh7S9B3Ur5F9S283y8Futii7RUU2C/97tOBmBy7nYvhoiOpbQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "1.0.9" + }, + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=18.0.0", + "npm": ">=8.0.0" + }, + "optionalDependencies": { + "@napi-rs/lzma-linux-x64-gnu": "1.5.1", + "@rollup/rollup-android-arm-eabi": "4.63.4", + "@rollup/rollup-android-arm64": "4.63.4", + "@rollup/rollup-darwin-arm64": "4.63.4", + "@rollup/rollup-darwin-x64": "4.63.4", + "@rollup/rollup-freebsd-arm64": "4.63.4", + "@rollup/rollup-freebsd-x64": "4.63.4", + "@rollup/rollup-linux-arm-gnueabihf": "4.63.4", + "@rollup/rollup-linux-arm-musleabihf": "4.63.4", + "@rollup/rollup-linux-arm64-gnu": "4.63.4", + "@rollup/rollup-linux-arm64-musl": "4.63.4", + "@rollup/rollup-linux-loong64-gnu": "4.63.4", + "@rollup/rollup-linux-loong64-musl": "4.63.4", + "@rollup/rollup-linux-ppc64-gnu": "4.63.4", + "@rollup/rollup-linux-ppc64-musl": "4.63.4", + "@rollup/rollup-linux-riscv64-gnu": "4.63.4", + "@rollup/rollup-linux-riscv64-musl": "4.63.4", + "@rollup/rollup-linux-s390x-gnu": "4.63.4", + "@rollup/rollup-linux-x64-gnu": "4.63.4", + "@rollup/rollup-linux-x64-musl": "4.63.4", + "@rollup/rollup-openbsd-x64": "4.63.4", + "@rollup/rollup-openharmony-arm64": "4.63.4", + "@rollup/rollup-win32-arm64-msvc": "4.63.4", + "@rollup/rollup-win32-ia32-msvc": "4.63.4", + "@rollup/rollup-win32-x64-gnu": "4.63.4", + "@rollup/rollup-win32-x64-msvc": "4.63.4", + "fsevents": "~2.3.2" + } + }, + "node_modules/source-map-js": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/tinyglobby": { + "version": "0.2.17", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.17.tgz", + "integrity": "sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g==", + "dev": true, + "license": "MIT", + "dependencies": { + "fdir": "^6.5.0", + "picomatch": "^4.0.4" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/SuperchupuDev" + } + }, + "node_modules/typescript": { + "version": "5.9.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", + "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/vite": { + "version": "7.3.6", + "resolved": "https://registry.npmjs.org/vite/-/vite-7.3.6.tgz", + "integrity": "sha512-4XP60spRGjSZFf1qYH+dJIkK2znL3zQfl9KkOV9MkkRR/3Dls0dxaBsQPTloEc5BLXWPL9vsOxopxyKoMmDueg==", + "dev": true, + "license": "MIT", + "dependencies": { + "esbuild": "^0.27.0 || ^0.28.0", + "fdir": "^6.5.0", + "picomatch": "^4.0.3", + "postcss": "^8.5.6", + "rollup": "^4.43.0", + "tinyglobby": "^0.2.15" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^20.19.0 || >=22.12.0", + "jiti": ">=1.21.0", + "less": "^4.0.0", + "lightningcss": "^1.21.0", + "sass": "^1.70.0", + "sass-embedded": "^1.70.0", + "stylus": ">=0.54.8", + "sugarss": "^5.0.0", + "terser": "^5.16.0", + "tsx": "^4.8.1", + "yaml": "^2.4.2" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "jiti": { + "optional": true + }, + "less": { + "optional": true + }, + "lightningcss": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + }, + "tsx": { + "optional": true + }, + "yaml": { + "optional": true + } + } + } + } +} diff --git a/frontend/package.json b/frontend/package.json new file mode 100644 index 0000000..7e6dc9b --- /dev/null +++ b/frontend/package.json @@ -0,0 +1,15 @@ +{ + "name": "nestquest-cards", + "private": true, + "type": "module", + "scripts": { + "build": "vite build" + }, + "dependencies": { + "lit": "^3.3.0" + }, + "devDependencies": { + "typescript": "^5.9.2", + "vite": "^7.1.5" + } +} diff --git a/frontend/src/cards/admin.ts b/frontend/src/cards/admin.ts new file mode 100644 index 0000000..b15993b --- /dev/null +++ b/frontend/src/cards/admin.ts @@ -0,0 +1,35 @@ +import { LitElement, html } from "lit"; + +import { registerCustomCard, type CardConfig, type HassLike } from "../types"; + +export class NestQuestAdminCard extends LitElement { + static properties = { + hass: { attribute: false }, + _config: { state: true }, + }; + + hass?: HassLike; + _config?: CardConfig; + + setConfig(config: CardConfig): void { + if (!config || typeof config !== "object") { + throw new Error("Invalid configuration"); + } + this._config = config; + } + + getCardSize(): number { + return 8; + } + + render() { + return html`
NestQuest Admin
`; + } +} + +customElements.define("nestquest-admin-card", NestQuestAdminCard); +registerCustomCard({ + type: "nestquest-admin-card", + name: "NestQuest Admin", + description: "Parent phone view for today, tasks, schedule, and history.", +}); diff --git a/frontend/src/cards/party-board.ts b/frontend/src/cards/party-board.ts new file mode 100644 index 0000000..d19bf8b --- /dev/null +++ b/frontend/src/cards/party-board.ts @@ -0,0 +1,35 @@ +import { LitElement, html } from "lit"; + +import { registerCustomCard, type CardConfig, type HassLike } from "../types"; + +export class NestQuestPartyBoardCard extends LitElement { + static properties = { + hass: { attribute: false }, + _config: { state: true }, + }; + + hass?: HassLike; + _config?: CardConfig; + + setConfig(config: CardConfig): void { + if (!config || typeof config !== "object") { + throw new Error("Invalid configuration"); + } + this._config = config; + } + + getCardSize(): number { + return 6; + } + + render() { + return html`
NestQuest Party Board
`; + } +} + +customElements.define("nestquest-party-board-card", NestQuestPartyBoardCard); +registerCustomCard({ + type: "nestquest-party-board-card", + name: "NestQuest Party Board", + description: "Kids' attract screen for present and away adventurers.", +}); diff --git a/frontend/src/cards/quest-log.ts b/frontend/src/cards/quest-log.ts new file mode 100644 index 0000000..d56a788 --- /dev/null +++ b/frontend/src/cards/quest-log.ts @@ -0,0 +1,35 @@ +import { LitElement, html } from "lit"; + +import { registerCustomCard, type CardConfig, type HassLike } from "../types"; + +export class NestQuestQuestLogCard extends LitElement { + static properties = { + hass: { attribute: false }, + _config: { state: true }, + }; + + hass?: HassLike; + _config?: CardConfig; + + setConfig(config: CardConfig): void { + if (!config || typeof config !== "object") { + throw new Error("Invalid configuration"); + } + this._config = config; + } + + getCardSize(): number { + return 8; + } + + render() { + return html`
NestQuest Quest Log
`; + } +} + +customElements.define("nestquest-quest-log-card", NestQuestQuestLogCard); +registerCustomCard({ + type: "nestquest-quest-log-card", + name: "NestQuest Quest Log", + description: "One child's daily quests, grouped by window.", +}); diff --git a/frontend/src/nestquest-cards.ts b/frontend/src/nestquest-cards.ts new file mode 100644 index 0000000..3d08e0e --- /dev/null +++ b/frontend/src/nestquest-cards.ts @@ -0,0 +1,3 @@ +import "./cards/party-board"; +import "./cards/quest-log"; +import "./cards/admin"; diff --git a/frontend/src/types.ts b/frontend/src/types.ts new file mode 100644 index 0000000..4854ed6 --- /dev/null +++ b/frontend/src/types.ts @@ -0,0 +1,20 @@ +export type HassLike = Record; + +export type CardConfig = Record; + +export type CustomCardInfo = { + type: string; + name: string; + description: string; +}; + +declare global { + interface Window { + customCards?: CustomCardInfo[]; + } +} + +export function registerCustomCard(info: CustomCardInfo): void { + window.customCards = window.customCards ?? []; + window.customCards.push(info); +} diff --git a/frontend/tsconfig.json b/frontend/tsconfig.json new file mode 100644 index 0000000..8963332 --- /dev/null +++ b/frontend/tsconfig.json @@ -0,0 +1,13 @@ +{ + "compilerOptions": { + "target": "ES2022", + "module": "ESNext", + "moduleResolution": "bundler", + "lib": ["ES2022", "DOM"], + "strict": true, + "skipLibCheck": true, + "noEmit": true, + "isolatedModules": true + }, + "include": ["src"] +} diff --git a/frontend/vite.config.ts b/frontend/vite.config.ts new file mode 100644 index 0000000..1c3265e --- /dev/null +++ b/frontend/vite.config.ts @@ -0,0 +1,19 @@ +import { defineConfig } from "vite"; + +export default defineConfig({ + build: { + outDir: "../custom_components/nestquest/www", + emptyOutDir: false, + sourcemap: false, + lib: { + entry: "src/nestquest-cards.ts", + formats: ["es"], + fileName: () => "nestquest-cards.js", + }, + rollupOptions: { + output: { + inlineDynamicImports: true, + }, + }, + }, +}); diff --git a/tests/conftest.py b/tests/conftest.py index e2dfd3d..6d6c9c4 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -51,6 +51,8 @@ _HA_MODULES = ( "homeassistant.components", "homeassistant.components.sensor", "homeassistant.components.binary_sensor", + "homeassistant.components.http", + "homeassistant.components.frontend", ) def _find_spec(name: str): @@ -96,6 +98,8 @@ for _parent, _child in ( ("homeassistant", "components"), ("homeassistant.components", "sensor"), ("homeassistant.components", "binary_sensor"), + ("homeassistant.components", "http"), + ("homeassistant.components", "frontend"), ): setattr(sys.modules[_parent], _child, sys.modules[f"{_parent}.{_child}"]) @@ -429,6 +433,28 @@ _sensor_mock.SensorStateClass = SensorStateClass _binary_sensor_mock = _ha_mock("homeassistant.components.binary_sensor") _binary_sensor_mock.BinarySensorEntity = BinarySensorEntity + +@dataclass(frozen=True) +class StaticPathConfig: + """Stand-in mirroring homeassistant.components.http.StaticPathConfig.""" + + url_path: str + path: str + cache_headers: bool = True + + +def _add_extra_js_url(hass, url, es5=False): + """Stand-in mirroring homeassistant.components.frontend.add_extra_js_url.""" + urls = getattr(hass, "extra_js_urls", None) + if urls is None: + hass.extra_js_urls = [] + urls = hass.extra_js_urls + urls.append(url) + + +_ha_mock("homeassistant.components.http").StaticPathConfig = StaticPathConfig +_ha_mock("homeassistant.components.frontend").add_extra_js_url = _add_extra_js_url + import voluptuous as vol @@ -549,6 +575,16 @@ def _async_track_time_change(hass, action, hour=None, minute=None, second=None): _ha_mock("homeassistant.helpers.event").async_track_time_change = _async_track_time_change +class HttpRegistry: + """Models HA's ``hass.http`` static-path registration.""" + + def __init__(self): + self.static_paths: list[StaticPathConfig] = [] + + async def async_register_static_paths(self, configs): + self.static_paths.extend(list(configs)) + + class EventBus: """Models HA's ``hass.bus`` event bus. @@ -697,6 +733,8 @@ def make_hass() -> tuple: hass.time_change = TimeChangeRegistry(hass) hass.services = ServiceRegistry(hass) hass.bus = EventBus() + hass.http = HttpRegistry() + hass.extra_js_urls: list[str] = [] # A valid IANA time zone (the day-rollover listener reads it to # compute "today" in HA local time). hass.config.time_zone = "UTC" diff --git a/tests/test_frontend.py b/tests/test_frontend.py new file mode 100644 index 0000000..74e51a0 --- /dev/null +++ b/tests/test_frontend.py @@ -0,0 +1,93 @@ +"""Tests for the Lovelace card toolchain and auto-registered resource.""" +from __future__ import annotations + +import json +from pathlib import Path + +from conftest import wire_entry_to_registry + +from custom_components.nestquest import async_setup, async_setup_entry +from custom_components.nestquest.frontend import ( + BUNDLE_FILENAME, + BUNDLE_URL, + FRONTEND_URL_BASE, + WWW_DIR, + async_register_frontend, +) + +REPO_ROOT = Path(__file__).parent.parent +FRONTEND_DIR = REPO_ROOT / "frontend" +BUNDLE_PATH = WWW_DIR / BUNDLE_FILENAME +CARD_TYPES = ( + "nestquest-party-board-card", + "nestquest-quest-log-card", + "nestquest-admin-card", +) + + +def test_frontend_package_documents_vite_lit_build() -> None: + package = json.loads((FRONTEND_DIR / "package.json").read_text(encoding="utf-8")) + assert package["scripts"]["build"] == "vite build" + assert "lit" in package["dependencies"] + assert "typescript" in package["devDependencies"] + assert "vite" in package["devDependencies"] + + +def test_card_modules_exist_as_separate_sources() -> None: + src = FRONTEND_DIR / "src" + assert (src / "nestquest-cards.ts").is_file() + assert (src / "cards" / "party-board.ts").is_file() + assert (src / "cards" / "quest-log.ts").is_file() + assert (src / "cards" / "admin.ts").is_file() + entry = (src / "nestquest-cards.ts").read_text(encoding="utf-8") + assert "./cards/party-board" in entry + assert "./cards/quest-log" in entry + assert "./cards/admin" in entry + + +def test_card_sources_have_no_visual_editor() -> None: + for path in (FRONTEND_DIR / "src" / "cards").glob("*.ts"): + text = path.read_text(encoding="utf-8") + assert "getConfigElement" not in text + assert "setConfig" in text + assert "getCardSize" in text + + +def test_built_bundle_contains_all_three_cards() -> None: + assert BUNDLE_PATH.is_file() + bundle = BUNDLE_PATH.read_text(encoding="utf-8") + for card_type in CARD_TYPES: + assert card_type in bundle + + +async def test_setup_registers_static_path_and_extra_js(hass, make_entry) -> None: + entry = wire_entry_to_registry(make_entry(), hass.registry) + assert await async_setup_entry(hass, entry) is True + assert len(hass.http.static_paths) == 1 + registered = hass.http.static_paths[0] + assert registered.url_path == FRONTEND_URL_BASE + assert registered.path == str(WWW_DIR) + assert hass.extra_js_urls == [BUNDLE_URL] + + +async def test_async_setup_registers_frontend_without_a_config_entry(hass) -> None: + assert await async_setup(hass, {}) is True + assert hass.extra_js_urls == [BUNDLE_URL] + assert hass.http.static_paths[0].url_path == FRONTEND_URL_BASE + + +async def test_frontend_registration_is_idempotent(hass, make_entry) -> None: + await async_register_frontend(hass) + await async_register_frontend(hass) + entry_a = wire_entry_to_registry(make_entry(entry_id="a"), hass.registry) + entry_b = wire_entry_to_registry(make_entry(entry_id="b"), hass.registry) + assert await async_setup_entry(hass, entry_a) is True + assert await async_setup_entry(hass, entry_b) is True + assert len(hass.http.static_paths) == 1 + assert hass.extra_js_urls == [BUNDLE_URL] + + +def test_readme_documents_npm_build() -> None: + readme = (REPO_ROOT / "README.md").read_text(encoding="utf-8") + assert "npm run build" in readme + assert "nestquest-cards.js" in readme From 89f517b3ad3d7e67b2a8143a52b4e4c083c2cff6 Mon Sep 17 00:00:00 2001 From: grok <24+grok@code.cubecraftcreations.com> Date: Sun, 20 Sep 2026 11:16:17 -0400 Subject: [PATCH 02/11] Add bundled fonts, token CSS, and local font-face loading Add bundled fonts, token CSS, and local font-face loading Co-authored-by: grok <24+grok@code.cubecraftcreations.com> Co-committed-by: grok <24+grok@code.cubecraftcreations.com> --- .../cinzel-decorative-latin-700-normal.woff2 | Bin 0 -> 15488 bytes .../cinzel-decorative-latin-900-normal.woff2 | Bin 0 -> 14568 bytes .../www/fonts/cinzel-latin-600-normal.woff2 | Bin 0 -> 15204 bytes .../www/fonts/cinzel-latin-700-normal.woff2 | Bin 0 -> 15184 bytes .../www/fonts/cinzel-latin-900-normal.woff2 | Bin 0 -> 14804 bytes .../www/fonts/nunito-latin-400-normal.woff2 | Bin 0 -> 16316 bytes .../www/fonts/nunito-latin-600-normal.woff2 | Bin 0 -> 16556 bytes .../www/fonts/nunito-latin-700-normal.woff2 | Bin 0 -> 16228 bytes .../www/fonts/nunito-latin-800-normal.woff2 | Bin 0 -> 16520 bytes .../nestquest/www/nestquest-admin-tokens.css | 76 ++++ .../nestquest/www/nestquest-cards.js | 376 +++++++++--------- .../nestquest/www/nestquest-fonts.css | 71 ++++ .../nestquest/www/nestquest-panel-tokens.css | 94 +++++ frontend/src/cards/admin.ts | 5 +- frontend/src/cards/party-board.ts | 5 +- frontend/src/cards/quest-log.ts | 5 +- frontend/src/nestquest-cards.ts | 9 + frontend/src/vite-env.d.ts | 1 + tests/test_frontend.py | 64 +++ 19 files changed, 520 insertions(+), 186 deletions(-) create mode 100644 custom_components/nestquest/www/fonts/cinzel-decorative-latin-700-normal.woff2 create mode 100644 custom_components/nestquest/www/fonts/cinzel-decorative-latin-900-normal.woff2 create mode 100644 custom_components/nestquest/www/fonts/cinzel-latin-600-normal.woff2 create mode 100644 custom_components/nestquest/www/fonts/cinzel-latin-700-normal.woff2 create mode 100644 custom_components/nestquest/www/fonts/cinzel-latin-900-normal.woff2 create mode 100644 custom_components/nestquest/www/fonts/nunito-latin-400-normal.woff2 create mode 100644 custom_components/nestquest/www/fonts/nunito-latin-600-normal.woff2 create mode 100644 custom_components/nestquest/www/fonts/nunito-latin-700-normal.woff2 create mode 100644 custom_components/nestquest/www/fonts/nunito-latin-800-normal.woff2 create mode 100644 custom_components/nestquest/www/nestquest-admin-tokens.css create mode 100644 custom_components/nestquest/www/nestquest-fonts.css create mode 100644 custom_components/nestquest/www/nestquest-panel-tokens.css create mode 100644 frontend/src/vite-env.d.ts diff --git a/custom_components/nestquest/www/fonts/cinzel-decorative-latin-700-normal.woff2 b/custom_components/nestquest/www/fonts/cinzel-decorative-latin-700-normal.woff2 new file mode 100644 index 0000000000000000000000000000000000000000..0e184e30c458aa9b5f5258938722281be3e7f7cb GIT binary patch literal 15488 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 literal 0 HcmV?d00001 diff --git a/custom_components/nestquest/www/fonts/cinzel-latin-700-normal.woff2 b/custom_components/nestquest/www/fonts/cinzel-latin-700-normal.woff2 new file mode 100644 index 0000000000000000000000000000000000000000..a13fb080feada1f051a039ce619a6085ee6d8dc9 GIT binary patch literal 15184 zcmV-WJFmodPew8T0RR9106S0s5C8xG0EEy006Odd0RR9100000000000000000000 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 literal 0 HcmV?d00001 diff --git a/custom_components/nestquest/www/fonts/nunito-latin-400-normal.woff2 b/custom_components/nestquest/www/fonts/nunito-latin-400-normal.woff2 new file mode 100644 index 0000000000000000000000000000000000000000..8c68e5744e4d08f9d720f9607d42d683c125f9fd GIT binary patch literal 16316 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 literal 0 HcmV?d00001 diff --git a/custom_components/nestquest/www/fonts/nunito-latin-600-normal.woff2 b/custom_components/nestquest/www/fonts/nunito-latin-600-normal.woff2 new file mode 100644 index 0000000000000000000000000000000000000000..dac52ff90d4d35049fb62e9703259ddd48d26007 GIT binary patch literal 16556 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 OD_83@4G(X(2mk;RyE=FP literal 0 HcmV?d00001 diff --git a/custom_components/nestquest/www/fonts/nunito-latin-800-normal.woff2 b/custom_components/nestquest/www/fonts/nunito-latin-800-normal.woff2 new file mode 100644 index 0000000000000000000000000000000000000000..21502e09358f34ff5107052b8fe4ea20a1d98721 GIT binary patch literal 16520 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 new nt(typeof r == "string" ? r : r + "", void 0, Y), ht = (r, t) => { - if (k) r.adoptedStyleSheets = t.map((e) => e instanceof CSSStyleSheet ? e : e.styleSheet); +const U = (i) => new ot(typeof i == "string" ? i : i + "", void 0, tt), ht = (i, t) => { + if (M) i.adoptedStyleSheets = t.map((e) => e instanceof CSSStyleSheet ? e : e.styleSheet); else for (const e of t) { - const s = document.createElement("style"), i = U.litNonce; - i !== void 0 && s.setAttribute("nonce", i), s.textContent = e.cssText, r.appendChild(s); + const s = document.createElement("style"), n = k.litNonce; + n !== void 0 && s.setAttribute("nonce", n), s.textContent = e.cssText, i.appendChild(s); } -}, q = k ? (r) => r : (r) => r instanceof CSSStyleSheet ? ((t) => { +}, I = M ? (i) => i : (i) => i instanceof CSSStyleSheet ? ((t) => { let e = ""; for (const s of t.cssRules) e += s.cssText; - return ot(e); -})(r) : r; -const { is: at, defineProperty: lt, getOwnPropertyDescriptor: ct, getOwnPropertyNames: dt, getOwnPropertySymbols: pt, getPrototypeOf: ut } = Object, H = globalThis, Q = H.trustedTypes, $t = Q ? Q.emptyScript : "", _t = H.reactiveElementPolyfillSupport, b = (r, t) => r, R = { toAttribute(r, t) { + return U(e); +})(i) : i; +const { is: dt, defineProperty: pt, getOwnPropertyDescriptor: lt, getOwnPropertyNames: ct, getOwnPropertySymbols: ut, getPrototypeOf: $t } = Object, z = globalThis, Q = z.trustedTypes, ft = Q ? Q.emptyScript : "", gt = z.reactiveElementPolyfillSupport, E = (i, t) => i, T = { toAttribute(i, t) { switch (t) { case Boolean: - r = r ? $t : null; + i = i ? ft : null; break; case Object: case Array: - r = r == null ? r : JSON.stringify(r); + i = i == null ? i : JSON.stringify(i); } - return r; -}, fromAttribute(r, t) { - let e = r; + return i; +}, fromAttribute(i, t) { + let e = i; switch (t) { case Boolean: - e = r !== null; + e = i !== null; break; case Number: - e = r === null ? null : Number(r); + e = i === null ? null : Number(i); break; case Object: case Array: try { - e = JSON.parse(r); + e = JSON.parse(i); } catch { e = null; } } return e; -} }, tt = (r, t) => !at(r, t), W = { attribute: !0, type: String, converter: R, reflect: !1, useDefault: !1, hasChanged: tt }; -Symbol.metadata ??= /* @__PURE__ */ Symbol("metadata"), H.litPropertyMetadata ??= /* @__PURE__ */ new WeakMap(); -let y = class extends HTMLElement { +} }, et = (i, t) => !dt(i, t), W = { attribute: !0, type: String, converter: T, reflect: !1, useDefault: !1, hasChanged: et }; +Symbol.metadata ??= /* @__PURE__ */ Symbol("metadata"), z.litPropertyMetadata ??= /* @__PURE__ */ new WeakMap(); +let b = class extends HTMLElement { static addInitializer(t) { this._$Ei(), (this.l ??= []).push(t); } @@ -67,44 +67,44 @@ let y = class extends HTMLElement { } static createProperty(t, e = W) { if (e.state && (e.attribute = !1), this._$Ei(), this.prototype.hasOwnProperty(t) && ((e = Object.create(e)).wrapped = !0), this.elementProperties.set(t, e), !e.noAccessor) { - const s = /* @__PURE__ */ Symbol(), i = this.getPropertyDescriptor(t, s, e); - i !== void 0 && lt(this.prototype, t, i); + const s = /* @__PURE__ */ Symbol(), n = this.getPropertyDescriptor(t, s, e); + n !== void 0 && pt(this.prototype, t, n); } } static getPropertyDescriptor(t, e, s) { - const { get: i, set: n } = ct(this.prototype, t) ?? { get() { + const { get: n, set: r } = lt(this.prototype, t) ?? { get() { return this[e]; - }, set(o) { - this[e] = o; + }, set(a) { + this[e] = a; } }; - return { get: i, set(o) { - const l = i?.call(this); - n?.call(this, o), this.requestUpdate(t, l, s); + return { get: n, set(a) { + const d = n?.call(this); + r?.call(this, a), this.requestUpdate(t, d, s); }, configurable: !0, enumerable: !0 }; } static getPropertyOptions(t) { return this.elementProperties.get(t) ?? W; } static _$Ei() { - if (this.hasOwnProperty(b("elementProperties"))) return; - const t = ut(this); + if (this.hasOwnProperty(E("elementProperties"))) return; + const t = $t(this); t.finalize(), t.l !== void 0 && (this.l = [...t.l]), this.elementProperties = new Map(t.elementProperties); } static finalize() { - if (this.hasOwnProperty(b("finalized"))) return; - if (this.finalized = !0, this._$Ei(), this.hasOwnProperty(b("properties"))) { - const e = this.properties, s = [...dt(e), ...pt(e)]; - for (const i of s) this.createProperty(i, e[i]); + if (this.hasOwnProperty(E("finalized"))) return; + if (this.finalized = !0, this._$Ei(), this.hasOwnProperty(E("properties"))) { + const e = this.properties, s = [...ct(e), ...ut(e)]; + for (const n of s) this.createProperty(n, e[n]); } const t = this[Symbol.metadata]; if (t !== null) { const e = litPropertyMetadata.get(t); - if (e !== void 0) for (const [s, i] of e) this.elementProperties.set(s, i); + if (e !== void 0) for (const [s, n] of e) this.elementProperties.set(s, n); } this._$Eh = /* @__PURE__ */ new Map(); for (const [e, s] of this.elementProperties) { - const i = this._$Eu(e, s); - i !== void 0 && this._$Eh.set(i, e); + const n = this._$Eu(e, s); + n !== void 0 && this._$Eh.set(n, e); } this.elementStyles = this.finalizeStyles(this.styles); } @@ -112,8 +112,8 @@ let y = class extends HTMLElement { const e = []; if (Array.isArray(t)) { const s = new Set(t.flat(1 / 0).reverse()); - for (const i of s) e.unshift(q(i)); - } else t !== void 0 && e.push(q(t)); + for (const n of s) e.unshift(I(n)); + } else t !== void 0 && e.push(I(t)); return e; } static _$Eu(t, e) { @@ -153,31 +153,31 @@ let y = class extends HTMLElement { this._$AK(t, s); } _$ET(t, e) { - const s = this.constructor.elementProperties.get(t), i = this.constructor._$Eu(t, s); - if (i !== void 0 && s.reflect === !0) { - const n = (s.converter?.toAttribute !== void 0 ? s.converter : R).toAttribute(e, s.type); - this._$Em = t, n == null ? this.removeAttribute(i) : this.setAttribute(i, n), this._$Em = null; + const s = this.constructor.elementProperties.get(t), n = this.constructor._$Eu(t, s); + if (n !== void 0 && s.reflect === !0) { + const r = (s.converter?.toAttribute !== void 0 ? s.converter : T).toAttribute(e, s.type); + this._$Em = t, r == null ? this.removeAttribute(n) : this.setAttribute(n, r), this._$Em = null; } } _$AK(t, e) { - const s = this.constructor, i = s._$Eh.get(t); - if (i !== void 0 && this._$Em !== i) { - const n = s.getPropertyOptions(i), o = typeof n.converter == "function" ? { fromAttribute: n.converter } : n.converter?.fromAttribute !== void 0 ? n.converter : R; - this._$Em = i; - const l = o.fromAttribute(e, n.type); - this[i] = l ?? this._$Ej?.get(i) ?? l, this._$Em = null; + const s = this.constructor, n = s._$Eh.get(t); + if (n !== void 0 && this._$Em !== n) { + const r = s.getPropertyOptions(n), a = typeof r.converter == "function" ? { fromAttribute: r.converter } : r.converter?.fromAttribute !== void 0 ? r.converter : T; + this._$Em = n; + const d = a.fromAttribute(e, r.type); + this[n] = d ?? this._$Ej?.get(n) ?? d, this._$Em = null; } } - requestUpdate(t, e, s, i = !1, n) { + requestUpdate(t, e, s, n = !1, r) { if (t !== void 0) { - const o = this.constructor; - if (i === !1 && (n = this[t]), s ??= o.getPropertyOptions(t), !((s.hasChanged ?? tt)(n, e) || s.useDefault && s.reflect && n === this._$Ej?.get(t) && !this.hasAttribute(o._$Eu(t, s)))) return; + const a = this.constructor; + if (n === !1 && (r = this[t]), s ??= a.getPropertyOptions(t), !((s.hasChanged ?? et)(r, e) || s.useDefault && s.reflect && r === this._$Ej?.get(t) && !this.hasAttribute(a._$Eu(t, s)))) return; this.C(t, e, s); } this.isUpdatePending === !1 && (this._$ES = this._$EP()); } - C(t, e, { useDefault: s, reflect: i, wrapped: n }, o) { - s && !(this._$Ej ??= /* @__PURE__ */ new Map()).has(t) && (this._$Ej.set(t, o ?? e ?? this[t]), n !== !0 || o !== void 0) || (this._$AL.has(t) || (this.hasUpdated || s || (e = void 0), this._$AL.set(t, e)), i === !0 && this._$Em !== t && (this._$Eq ??= /* @__PURE__ */ new Set()).add(t)); + C(t, e, { useDefault: s, reflect: n, wrapped: r }, a) { + s && !(this._$Ej ??= /* @__PURE__ */ new Map()).has(t) && (this._$Ej.set(t, a ?? e ?? this[t]), r !== !0 || a !== void 0) || (this._$AL.has(t) || (this.hasUpdated || s || (e = void 0), this._$AL.set(t, e)), n === !0 && this._$Em !== t && (this._$Eq ??= /* @__PURE__ */ new Set()).add(t)); } async _$EP() { this.isUpdatePending = !0; @@ -196,13 +196,13 @@ let y = class extends HTMLElement { if (!this.isUpdatePending) return; if (!this.hasUpdated) { if (this.renderRoot ??= this.createRenderRoot(), this._$Ep) { - for (const [i, n] of this._$Ep) this[i] = n; + for (const [n, r] of this._$Ep) this[n] = r; this._$Ep = void 0; } const s = this.constructor.elementProperties; - if (s.size > 0) for (const [i, n] of s) { - const { wrapped: o } = n, l = this[i]; - o !== !0 || this._$AL.has(i) || l === void 0 || this.C(i, void 0, n, l); + if (s.size > 0) for (const [n, r] of s) { + const { wrapped: a } = r, d = this[n]; + a !== !0 || this._$AL.has(n) || d === void 0 || this.C(n, void 0, r, d); } } let t = !1; @@ -239,70 +239,70 @@ let y = class extends HTMLElement { firstUpdated(t) { } }; -y.elementStyles = [], y.shadowRootOptions = { mode: "open" }, y[b("elementProperties")] = /* @__PURE__ */ new Map(), y[b("finalized")] = /* @__PURE__ */ new Map(), _t?.({ ReactiveElement: y }), (H.reactiveElementVersions ??= []).push("2.1.2"); -const L = globalThis, V = (r) => r, O = L.trustedTypes, K = O ? O.createPolicy("lit-html", { createHTML: (r) => r }) : void 0, et = "$lit$", _ = `lit$${Math.random().toFixed(9).slice(2)}$`, st = "?" + _, ft = `<${st}>`, g = document, w = () => g.createComment(""), C = (r) => r === null || typeof r != "object" && typeof r != "function", z = Array.isArray, At = (r) => z(r) || typeof r?.[Symbol.iterator] == "function", T = `[ -\f\r]`, S = /<(?:(!--|\/[^a-zA-Z])|(\/?[a-zA-Z][^>\s]*)|(\/?$))/g, Z = /-->/g, J = />/g, f = RegExp(`>|${T}(?:([^\\s"'>=/]+)(${T}*=${T}*(?:[^ -\f\r"'\`<>=]|("|')|))|$)`, "g"), F = /'/g, G = /"/g, it = /^(?:script|style|textarea|title)$/i, mt = (r) => (t, ...e) => ({ _$litType$: r, strings: t, values: e }), j = mt(1), v = /* @__PURE__ */ Symbol.for("lit-noChange"), d = /* @__PURE__ */ Symbol.for("lit-nothing"), X = /* @__PURE__ */ new WeakMap(), A = g.createTreeWalker(g, 129); -function rt(r, t) { - if (!z(r) || !r.hasOwnProperty("raw")) throw Error("invalid template strings array"); +b.elementStyles = [], b.shadowRootOptions = { mode: "open" }, b[E("elementProperties")] = /* @__PURE__ */ new Map(), b[E("finalized")] = /* @__PURE__ */ new Map(), gt?.({ ReactiveElement: b }), (z.reactiveElementVersions ??= []).push("2.1.2"); +const R = globalThis, V = (i) => i, N = R.trustedTypes, K = N ? N.createPolicy("lit-html", { createHTML: (i) => i }) : void 0, st = "$lit$", f = `lit$${Math.random().toFixed(9).slice(2)}$`, nt = "?" + f, _t = `<${nt}>`, y = document, x = () => y.createComment(""), w = (i) => i === null || typeof i != "object" && typeof i != "function", D = Array.isArray, mt = (i) => D(i) || typeof i?.[Symbol.iterator] == "function", H = `[ +\f\r]`, v = /<(?:(!--|\/[^a-zA-Z])|(\/?[a-zA-Z][^>\s]*)|(\/?$))/g, Z = /-->/g, J = />/g, g = RegExp(`>|${H}(?:([^\\s"'>=/]+)(${H}*=${H}*(?:[^ +\f\r"'\`<>=]|("|')|))|$)`, "g"), G = /'/g, X = /"/g, it = /^(?:script|style|textarea|title)$/i, yt = (i) => (t, ...e) => ({ _$litType$: i, strings: t, values: e }), B = yt(1), A = /* @__PURE__ */ Symbol.for("lit-noChange"), l = /* @__PURE__ */ Symbol.for("lit-nothing"), Y = /* @__PURE__ */ new WeakMap(), _ = y.createTreeWalker(y, 129); +function rt(i, t) { + if (!D(i) || !i.hasOwnProperty("raw")) throw Error("invalid template strings array"); return K !== void 0 ? K.createHTML(t) : t; } -const gt = (r, t) => { - const e = r.length - 1, s = []; - let i, n = t === 2 ? "" : t === 3 ? "" : "", o = S; - for (let l = 0; l < e; l++) { - const h = r[l]; - let c, p, a = -1, u = 0; - for (; u < h.length && (o.lastIndex = u, p = o.exec(h), p !== null); ) u = o.lastIndex, o === S ? p[1] === "!--" ? o = Z : p[1] !== void 0 ? o = J : p[2] !== void 0 ? (it.test(p[2]) && (i = RegExp("" ? (o = i ?? S, a = -1) : p[1] === void 0 ? a = -2 : (a = o.lastIndex - p[2].length, c = p[1], o = p[3] === void 0 ? f : p[3] === '"' ? G : F) : o === G || o === F ? o = f : o === Z || o === J ? o = S : (o = f, i = void 0); - const $ = o === f && r[l + 1].startsWith("/>") ? " " : ""; - n += o === S ? h + ft : a >= 0 ? (s.push(c), h.slice(0, a) + et + h.slice(a) + _ + $) : h + _ + (a === -2 ? l : $); +const bt = (i, t) => { + const e = i.length - 1, s = []; + let n, r = t === 2 ? "" : t === 3 ? "" : "", a = v; + for (let d = 0; d < e; d++) { + const o = i[d]; + let p, c, h = -1, u = 0; + for (; u < o.length && (a.lastIndex = u, c = a.exec(o), c !== null); ) u = a.lastIndex, a === v ? c[1] === "!--" ? a = Z : c[1] !== void 0 ? a = J : c[2] !== void 0 ? (it.test(c[2]) && (n = RegExp("" ? (a = n ?? v, h = -1) : c[1] === void 0 ? h = -2 : (h = a.lastIndex - c[2].length, p = c[1], a = c[3] === void 0 ? g : c[3] === '"' ? X : G) : a === X || a === G ? a = g : a === Z || a === J ? a = v : (a = g, n = void 0); + const $ = a === g && i[d + 1].startsWith("/>") ? " " : ""; + r += a === v ? o + _t : h >= 0 ? (s.push(p), o.slice(0, h) + st + o.slice(h) + f + $) : o + f + (h === -2 ? d : $); } - return [rt(r, n + (r[e] || "") + (t === 2 ? "" : t === 3 ? "" : "")), s]; + return [rt(i, r + (i[e] || "") + (t === 2 ? "" : t === 3 ? "" : "")), s]; }; -class P { +class S { constructor({ strings: t, _$litType$: e }, s) { - let i; + let n; this.parts = []; - let n = 0, o = 0; - const l = t.length - 1, h = this.parts, [c, p] = gt(t, e); - if (this.el = P.createElement(c, s), A.currentNode = this.el.content, e === 2 || e === 3) { - const a = this.el.content.firstChild; - a.replaceWith(...a.childNodes); + let r = 0, a = 0; + const d = t.length - 1, o = this.parts, [p, c] = bt(t, e); + if (this.el = S.createElement(p, s), _.currentNode = this.el.content, e === 2 || e === 3) { + const h = this.el.content.firstChild; + h.replaceWith(...h.childNodes); } - for (; (i = A.nextNode()) !== null && h.length < l; ) { - if (i.nodeType === 1) { - if (i.hasAttributes()) for (const a of i.getAttributeNames()) if (a.endsWith(et)) { - const u = p[o++], $ = i.getAttribute(a).split(_), N = /([.?@])?(.*)/.exec(u); - h.push({ type: 1, index: n, name: N[2], strings: $, ctor: N[1] === "." ? vt : N[1] === "?" ? Et : N[1] === "@" ? St : M }), i.removeAttribute(a); - } else a.startsWith(_) && (h.push({ type: 6, index: n }), i.removeAttribute(a)); - if (it.test(i.tagName)) { - const a = i.textContent.split(_), u = a.length - 1; + for (; (n = _.nextNode()) !== null && o.length < d; ) { + if (n.nodeType === 1) { + if (n.hasAttributes()) for (const h of n.getAttributeNames()) if (h.endsWith(st)) { + const u = c[a++], $ = n.getAttribute(h).split(f), P = /([.?@])?(.*)/.exec(u); + o.push({ type: 1, index: r, name: P[2], strings: $, ctor: P[1] === "." ? qt : P[1] === "?" ? vt : P[1] === "@" ? Et : O }), n.removeAttribute(h); + } else h.startsWith(f) && (o.push({ type: 6, index: r }), n.removeAttribute(h)); + if (it.test(n.tagName)) { + const h = n.textContent.split(f), u = h.length - 1; if (u > 0) { - i.textContent = O ? O.emptyScript : ""; - for (let $ = 0; $ < u; $++) i.append(a[$], w()), A.nextNode(), h.push({ type: 2, index: ++n }); - i.append(a[u], w()); + n.textContent = N ? N.emptyScript : ""; + for (let $ = 0; $ < u; $++) n.append(h[$], x()), _.nextNode(), o.push({ type: 2, index: ++r }); + n.append(h[u], x()); } } - } else if (i.nodeType === 8) if (i.data === st) h.push({ type: 2, index: n }); + } else if (n.nodeType === 8) if (n.data === nt) o.push({ type: 2, index: r }); else { - let a = -1; - for (; (a = i.data.indexOf(_, a + 1)) !== -1; ) h.push({ type: 7, index: n }), a += _.length - 1; + let h = -1; + for (; (h = n.data.indexOf(f, h + 1)) !== -1; ) o.push({ type: 7, index: r }), h += f.length - 1; } - n++; + r++; } } static createElement(t, e) { - const s = g.createElement("template"); + const s = y.createElement("template"); return s.innerHTML = t, s; } } -function E(r, t, e = r, s) { - if (t === v) return t; - let i = s !== void 0 ? e._$Co?.[s] : e._$Cl; - const n = C(t) ? void 0 : t._$litDirective$; - return i?.constructor !== n && (i?._$AO?.(!1), n === void 0 ? i = void 0 : (i = new n(r), i._$AT(r, e, s)), s !== void 0 ? (e._$Co ??= [])[s] = i : e._$Cl = i), i !== void 0 && (t = E(r, i._$AS(r, t.values), i, s)), t; +function q(i, t, e = i, s) { + if (t === A) return t; + let n = s !== void 0 ? e._$Co?.[s] : e._$Cl; + const r = w(t) ? void 0 : t._$litDirective$; + return n?.constructor !== r && (n?._$AO?.(!1), r === void 0 ? n = void 0 : (n = new r(i), n._$AT(i, e, s)), s !== void 0 ? (e._$Co ??= [])[s] = n : e._$Cl = n), n !== void 0 && (t = q(i, n._$AS(i, t.values), n, s)), t; } -class yt { +class At { constructor(t, e) { this._$AV = [], this._$AN = void 0, this._$AD = t, this._$AM = e; } @@ -313,29 +313,29 @@ class yt { return this._$AM._$AU; } u(t) { - const { el: { content: e }, parts: s } = this._$AD, i = (t?.creationScope ?? g).importNode(e, !0); - A.currentNode = i; - let n = A.nextNode(), o = 0, l = 0, h = s[0]; - for (; h !== void 0; ) { - if (o === h.index) { - let c; - h.type === 2 ? c = new x(n, n.nextSibling, this, t) : h.type === 1 ? c = new h.ctor(n, h.name, h.strings, this, t) : h.type === 6 && (c = new bt(n, this, t)), this._$AV.push(c), h = s[++l]; + const { el: { content: e }, parts: s } = this._$AD, n = (t?.creationScope ?? y).importNode(e, !0); + _.currentNode = n; + let r = _.nextNode(), a = 0, d = 0, o = s[0]; + for (; o !== void 0; ) { + if (a === o.index) { + let p; + o.type === 2 ? p = new C(r, r.nextSibling, this, t) : o.type === 1 ? p = new o.ctor(r, o.name, o.strings, this, t) : o.type === 6 && (p = new xt(r, this, t)), this._$AV.push(p), o = s[++d]; } - o !== h?.index && (n = A.nextNode(), o++); + a !== o?.index && (r = _.nextNode(), a++); } - return A.currentNode = g, i; + return _.currentNode = y, n; } p(t) { let e = 0; for (const s of this._$AV) s !== void 0 && (s.strings !== void 0 ? (s._$AI(t, s, e), e += s.strings.length - 2) : s._$AI(t[e])), e++; } } -class x { +class C { get _$AU() { return this._$AM?._$AU ?? this._$Cv; } - constructor(t, e, s, i) { - this.type = 2, this._$AH = d, this._$AN = void 0, this._$AA = t, this._$AB = e, this._$AM = s, this.options = i, this._$Cv = i?.isConnected ?? !0; + constructor(t, e, s, n) { + this.type = 2, this._$AH = l, this._$AN = void 0, this._$AA = t, this._$AB = e, this._$AM = s, this.options = n, this._$Cv = n?.isConnected ?? !0; } get parentNode() { let t = this._$AA.parentNode; @@ -349,7 +349,7 @@ class x { return this._$AB; } _$AI(t, e = this) { - t = E(this, t, e), C(t) ? t === d || t == null || t === "" ? (this._$AH !== d && this._$AR(), this._$AH = d) : t !== this._$AH && t !== v && this._(t) : t._$litType$ !== void 0 ? this.$(t) : t.nodeType !== void 0 ? this.T(t) : At(t) ? this.k(t) : this._(t); + t = q(this, t, e), w(t) ? t === l || t == null || t === "" ? (this._$AH !== l && this._$AR(), this._$AH = l) : t !== this._$AH && t !== A && this._(t) : t._$litType$ !== void 0 ? this.$(t) : t.nodeType !== void 0 ? this.T(t) : mt(t) ? this.k(t) : this._(t); } O(t) { return this._$AA.parentNode.insertBefore(t, this._$AB); @@ -358,26 +358,26 @@ class x { this._$AH !== t && (this._$AR(), this._$AH = this.O(t)); } _(t) { - this._$AH !== d && C(this._$AH) ? this._$AA.nextSibling.data = t : this.T(g.createTextNode(t)), this._$AH = t; + this._$AH !== l && w(this._$AH) ? this._$AA.nextSibling.data = t : this.T(y.createTextNode(t)), this._$AH = t; } $(t) { - const { values: e, _$litType$: s } = t, i = typeof s == "number" ? this._$AC(t) : (s.el === void 0 && (s.el = P.createElement(rt(s.h, s.h[0]), this.options)), s); - if (this._$AH?._$AD === i) this._$AH.p(e); + const { values: e, _$litType$: s } = t, n = typeof s == "number" ? this._$AC(t) : (s.el === void 0 && (s.el = S.createElement(rt(s.h, s.h[0]), this.options)), s); + if (this._$AH?._$AD === n) this._$AH.p(e); else { - const n = new yt(i, this), o = n.u(this.options); - n.p(e), this.T(o), this._$AH = n; + const r = new At(n, this), a = r.u(this.options); + r.p(e), this.T(a), this._$AH = r; } } _$AC(t) { - let e = X.get(t.strings); - return e === void 0 && X.set(t.strings, e = new P(t)), e; + let e = Y.get(t.strings); + return e === void 0 && Y.set(t.strings, e = new S(t)), e; } k(t) { - z(this._$AH) || (this._$AH = [], this._$AR()); + D(this._$AH) || (this._$AH = [], this._$AR()); const e = this._$AH; - let s, i = 0; - for (const n of t) i === e.length ? e.push(s = new x(this.O(w()), this.O(w()), this, this.options)) : s = e[i], s._$AI(n), i++; - i < e.length && (this._$AR(s && s._$AB.nextSibling, i), e.length = i); + let s, n = 0; + for (const r of t) n === e.length ? e.push(s = new C(this.O(x()), this.O(x()), this, this.options)) : s = e[n], s._$AI(r), n++; + n < e.length && (this._$AR(s && s._$AB.nextSibling, n), e.length = n); } _$AR(t = this._$AA.nextSibling, e) { for (this._$AP?.(!1, !0, e); t !== this._$AB; ) { @@ -389,61 +389,61 @@ class x { this._$AM === void 0 && (this._$Cv = t, this._$AP?.(t)); } } -class M { +class O { get tagName() { return this.element.tagName; } get _$AU() { return this._$AM._$AU; } - constructor(t, e, s, i, n) { - this.type = 1, this._$AH = d, this._$AN = void 0, this.element = t, this.name = e, this._$AM = i, this.options = n, s.length > 2 || s[0] !== "" || s[1] !== "" ? (this._$AH = Array(s.length - 1).fill(new String()), this.strings = s) : this._$AH = d; + constructor(t, e, s, n, r) { + this.type = 1, this._$AH = l, this._$AN = void 0, this.element = t, this.name = e, this._$AM = n, this.options = r, s.length > 2 || s[0] !== "" || s[1] !== "" ? (this._$AH = Array(s.length - 1).fill(new String()), this.strings = s) : this._$AH = l; } - _$AI(t, e = this, s, i) { - const n = this.strings; - let o = !1; - if (n === void 0) t = E(this, t, e, 0), o = !C(t) || t !== this._$AH && t !== v, o && (this._$AH = t); + _$AI(t, e = this, s, n) { + const r = this.strings; + let a = !1; + if (r === void 0) t = q(this, t, e, 0), a = !w(t) || t !== this._$AH && t !== A, a && (this._$AH = t); else { - const l = t; - let h, c; - for (t = n[0], h = 0; h < n.length - 1; h++) c = E(this, l[s + h], e, h), c === v && (c = this._$AH[h]), o ||= !C(c) || c !== this._$AH[h], c === d ? t = d : t !== d && (t += (c ?? "") + n[h + 1]), this._$AH[h] = c; + const d = t; + let o, p; + for (t = r[0], o = 0; o < r.length - 1; o++) p = q(this, d[s + o], e, o), p === A && (p = this._$AH[o]), a ||= !w(p) || p !== this._$AH[o], p === l ? t = l : t !== l && (t += (p ?? "") + r[o + 1]), this._$AH[o] = p; } - o && !i && this.j(t); + a && !n && this.j(t); } j(t) { - t === d ? this.element.removeAttribute(this.name) : this.element.setAttribute(this.name, t ?? ""); + t === l ? this.element.removeAttribute(this.name) : this.element.setAttribute(this.name, t ?? ""); } } -class vt extends M { +class qt extends O { constructor() { super(...arguments), this.type = 3; } j(t) { - this.element[this.name] = t === d ? void 0 : t; + this.element[this.name] = t === l ? void 0 : t; } } -class Et extends M { +class vt extends O { constructor() { super(...arguments), this.type = 4; } j(t) { - this.element.toggleAttribute(this.name, !!t && t !== d); + this.element.toggleAttribute(this.name, !!t && t !== l); } } -class St extends M { - constructor(t, e, s, i, n) { - super(t, e, s, i, n), this.type = 5; +class Et extends O { + constructor(t, e, s, n, r) { + super(t, e, s, n, r), this.type = 5; } _$AI(t, e = this) { - if ((t = E(this, t, e, 0) ?? d) === v) return; - const s = this._$AH, i = t === d && s !== d || t.capture !== s.capture || t.once !== s.once || t.passive !== s.passive, n = t !== d && (s === d || i); - i && this.element.removeEventListener(this.name, this, s), n && this.element.addEventListener(this.name, this, t), this._$AH = t; + if ((t = q(this, t, e, 0) ?? l) === A) return; + const s = this._$AH, n = t === l && s !== l || t.capture !== s.capture || t.once !== s.once || t.passive !== s.passive, r = t !== l && (s === l || n); + n && this.element.removeEventListener(this.name, this, s), r && this.element.addEventListener(this.name, this, t), this._$AH = t; } handleEvent(t) { typeof this._$AH == "function" ? this._$AH.call(this.options?.host ?? this.element, t) : this._$AH.handleEvent(t); } } -class bt { +class xt { constructor(t, e, s) { this.element = t, this.type = 6, this._$AN = void 0, this._$AM = e, this.options = s; } @@ -451,22 +451,22 @@ class bt { return this._$AM._$AU; } _$AI(t) { - E(this, t); + q(this, t); } } -const wt = L.litHtmlPolyfillSupport; -wt?.(P, x), (L.litHtmlVersions ??= []).push("3.3.3"); -const Ct = (r, t, e) => { +const wt = R.litHtmlPolyfillSupport; +wt?.(S, C), (R.litHtmlVersions ??= []).push("3.3.3"); +const St = (i, t, e) => { const s = e?.renderBefore ?? t; - let i = s._$litPart$; - if (i === void 0) { - const n = e?.renderBefore ?? null; - s._$litPart$ = i = new x(t.insertBefore(w(), n), n, void 0, e ?? {}); + let n = s._$litPart$; + if (n === void 0) { + const r = e?.renderBefore ?? null; + s._$litPart$ = n = new C(t.insertBefore(x(), r), r, void 0, e ?? {}); } - return i._$AI(r), i; + return n._$AI(i), n; }; -const D = globalThis; -class m extends y { +const F = globalThis; +class m extends b { constructor() { super(...arguments), this.renderOptions = { host: this }, this._$Do = void 0; } @@ -476,7 +476,7 @@ class m extends y { } update(t) { const e = this.render(); - this.hasUpdated || (this.renderOptions.isConnected = this.isConnected), super.update(t), this._$Do = Ct(e, this.renderRoot, this.renderOptions); + this.hasUpdated || (this.renderOptions.isConnected = this.isConnected), super.update(t), this._$Do = St(e, this.renderRoot, this.renderOptions); } connectedCallback() { super.connectedCallback(), this._$Do?.setConnected(!0); @@ -485,21 +485,23 @@ class m extends y { super.disconnectedCallback(), this._$Do?.setConnected(!1); } render() { - return v; + return A; } } -m._$litElement$ = !0, m.finalized = !0, D.litElementHydrateSupport?.({ LitElement: m }); -const Pt = D.litElementPolyfillSupport; -Pt?.({ LitElement: m }); -(D.litElementVersions ??= []).push("4.2.2"); -function B(r) { - window.customCards = window.customCards ?? [], window.customCards.push(r); +m._$litElement$ = !0, m.finalized = !0, F.litElementHydrateSupport?.({ LitElement: m }); +const Ct = F.litElementPolyfillSupport; +Ct?.({ LitElement: m }); +(F.litElementVersions ??= []).push("4.2.2"); +const at = ':host{--nq-p-parchment-top: #f0e4c7;--nq-p-parchment-bottom: #ddcca3;--nq-p-parchment: radial-gradient(ellipse at 25% 10%, rgba(255,255,255,.5), transparent 55%), radial-gradient(ellipse at 85% 90%, rgba(120,86,44,.28), transparent 60%), repeating-linear-gradient(93deg, rgba(150,115,70,.05) 0 2px, transparent 2px 6px), repeating-linear-gradient(2deg, rgba(150,115,70,.04) 0 3px, transparent 3px 7px), linear-gradient(var(--nq-p-parchment-top), var(--nq-p-parchment-bottom));--nq-p-vignette: inset 0 0 200px rgba(80,52,20,.35);--nq-p-card-open: linear-gradient(#fdf7e6, #f2e7c9);--nq-p-card-done: linear-gradient(#f0e8d3, #e6dcc2);--nq-p-card-panel: linear-gradient(#fcf6e6, #f1e6ca);--nq-p-card-border: rgba(120,88,48,.38);--nq-p-panel-border: rgba(92,62,26,.4);--nq-p-card-shadow: 0 3px 0 rgba(120,88,48,.2), inset 0 1px 0 rgba(255,255,255,.7);--nq-p-panel-shadow: 0 6px 0 rgba(92,62,26,.18), inset 0 2px 0 rgba(255,255,255,.7);--nq-p-frame-outer: 2px solid rgba(92,62,26,.45);--nq-p-frame-inner: 1px solid rgba(92,62,26,.28);--nq-p-rule: 2px solid rgba(92,62,26,.35);--nq-p-ink: #2b1f14;--nq-p-ink-secondary: #5c452a;--nq-p-ink-muted: #6f6455;--nq-p-ink-away: #4d4433;--nq-p-ink-late: #8f1526;--nq-brand-blue: #3B3AB8;--nq-brand-purple: #A035CC;--nq-brand-gradient: linear-gradient(135deg, var(--nq-brand-blue), var(--nq-brand-purple));--nq-p-icon-tile: var(--nq-brand-gradient);--nq-p-icon-tile-done: rgba(92,62,26,.16);--nq-p-crest: var(--nq-brand-gradient);--nq-p-crest-away: linear-gradient(135deg, #6b6b7a, #7a7286);--nq-p-seal: radial-gradient(circle at 35% 30%, #a8283a, #6d1322);--nq-p-seal-shadow: 0 4px 10px rgba(60,10,20,.4), inset 0 0 0 4px rgba(255,255,255,.14);--nq-p-seal-size: 78px;--nq-p-dock-bg: rgba(43,31,20,.9);--nq-p-dock-ink: #f7efdb;--nq-p-dock-ink-secondary: #d8c9a6;--nq-p-dock-divider: rgba(233,220,189,.3);--nq-p-dock-height: 84px;--nq-p-font-display: "Cinzel Decorative", Cinzel, serif;--nq-p-font-heading: Cinzel, serif;--nq-p-font-body: Nunito, system-ui, sans-serif;--nq-p-size-hero: 86px;--nq-p-size-wordmark: 66px;--nq-p-size-title: 56px;--nq-p-size-name: 48px;--nq-p-size-section: 32px;--nq-p-size-quest: 30px;--nq-p-size-body: 22px;--nq-p-size-label: 21px;--nq-p-track-label: .14em;--nq-p-track-kicker: .3em;--nq-p-quest-min-height: 116px;--nq-p-quest-gap: 24px;--nq-p-button-height: 72px;--nq-p-button-min-width: 150px;--nq-p-confirm-button: 108px;--nq-p-radius-card: 12px;--nq-p-radius-panel: 20px;--nq-p-radius-pill: 9999px;--nq-p-page-inset: 62px;--nq-p-frame-inset: 26px;--nq-ease-out: cubic-bezier(0, 0, .2, 1);--nq-dur-micro: .12s;--nq-dur-base: .2s;--nq-dur-modal: .35s}'; +function L(i) { + window.customCards = window.customCards ?? [], window.customCards.push(i); } -class xt extends m { +class Pt extends m { static properties = { hass: { attribute: !1 }, _config: { state: !0 } }; + static styles = [U(at)]; hass; _config; setConfig(t) { @@ -511,20 +513,21 @@ class xt extends m { return 6; } render() { - return j`

    NestQuest Party Board
    `; + return B`
    NestQuest Party Board
    `; } } -customElements.define("nestquest-party-board-card", xt); -B({ +customElements.define("nestquest-party-board-card", Pt); +L({ type: "nestquest-party-board-card", name: "NestQuest Party Board", description: "Kids' attract screen for present and away adventurers." }); -class Nt extends m { +class kt extends m { static properties = { hass: { attribute: !1 }, _config: { state: !0 } }; + static styles = [U(at)]; hass; _config; setConfig(t) { @@ -536,20 +539,22 @@ class Nt extends m { return 8; } render() { - return j`
    NestQuest Quest Log
    `; + return B`
    NestQuest Quest Log
    `; } } -customElements.define("nestquest-quest-log-card", Nt); -B({ +customElements.define("nestquest-quest-log-card", kt); +L({ type: "nestquest-quest-log-card", name: "NestQuest Quest Log", description: "One child's daily quests, grouped by window." }); +const Nt = ":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 Ut extends m { static properties = { hass: { attribute: !1 }, _config: { state: !0 } }; + static styles = [U(Nt)]; hass; _config; setConfig(t) { @@ -561,12 +566,17 @@ class Ut extends m { return 8; } render() { - return j`
    NestQuest Admin
    `; + return B`
    NestQuest Admin
    `; } } customElements.define("nestquest-admin-card", Ut); -B({ +L({ type: "nestquest-admin-card", name: "NestQuest Admin", description: "Parent phone view for today, tasks, schedule, and history." }); +const zt = "/nestquest-static/nestquest-fonts.css"; +if (!document.querySelector('link[data-nq-fonts=""]')) { + const i = document.createElement("link"); + i.rel = "stylesheet", i.href = zt, i.dataset.nqFonts = "", document.head.appendChild(i); +} diff --git a/custom_components/nestquest/www/nestquest-fonts.css b/custom_components/nestquest/www/nestquest-fonts.css new file mode 100644 index 0000000..72382dd --- /dev/null +++ b/custom_components/nestquest/www/nestquest-fonts.css @@ -0,0 +1,71 @@ +@font-face { + font-family: "Nunito"; + font-style: normal; + font-weight: 400; + font-display: swap; + src: url("./fonts/nunito-latin-400-normal.woff2") format("woff2"); +} + +@font-face { + font-family: "Nunito"; + font-style: normal; + font-weight: 600; + font-display: swap; + src: url("./fonts/nunito-latin-600-normal.woff2") format("woff2"); +} + +@font-face { + font-family: "Nunito"; + font-style: normal; + font-weight: 700; + font-display: swap; + src: url("./fonts/nunito-latin-700-normal.woff2") format("woff2"); +} + +@font-face { + font-family: "Nunito"; + font-style: normal; + font-weight: 800; + font-display: swap; + src: url("./fonts/nunito-latin-800-normal.woff2") format("woff2"); +} + +@font-face { + font-family: "Cinzel"; + font-style: normal; + font-weight: 600; + font-display: swap; + src: url("./fonts/cinzel-latin-600-normal.woff2") format("woff2"); +} + +@font-face { + font-family: "Cinzel"; + font-style: normal; + font-weight: 700; + font-display: swap; + src: url("./fonts/cinzel-latin-700-normal.woff2") format("woff2"); +} + +@font-face { + font-family: "Cinzel"; + font-style: normal; + font-weight: 900; + font-display: swap; + src: url("./fonts/cinzel-latin-900-normal.woff2") format("woff2"); +} + +@font-face { + font-family: "Cinzel Decorative"; + font-style: normal; + font-weight: 700; + font-display: swap; + src: url("./fonts/cinzel-decorative-latin-700-normal.woff2") format("woff2"); +} + +@font-face { + font-family: "Cinzel Decorative"; + font-style: normal; + font-weight: 900; + font-display: swap; + src: url("./fonts/cinzel-decorative-latin-900-normal.woff2") format("woff2"); +} diff --git a/custom_components/nestquest/www/nestquest-panel-tokens.css b/custom_components/nestquest/www/nestquest-panel-tokens.css new file mode 100644 index 0000000..f997486 --- /dev/null +++ b/custom_components/nestquest/www/nestquest-panel-tokens.css @@ -0,0 +1,94 @@ +/* ============================================================ + NestQuest — Kids' panel card tokens + Surface: DAKboard DAKOS wall panel, 1920x1080 landscape, + viewed from ~3 ft, no login, read/complete only. + Scope these on the card host: nestquest-quest-log-card, + nestquest-party-board-card. + Derived from the approved mockups (panel screens 1a, 1b, 1c, 1j). + ============================================================ */ + +:host { + /* ── Parchment ground ─────────────────────────────────── */ + --nq-p-parchment-top: #f0e4c7; + --nq-p-parchment-bottom: #ddcca3; + --nq-p-parchment: radial-gradient(ellipse at 25% 10%, rgba(255,255,255,.5), transparent 55%), + radial-gradient(ellipse at 85% 90%, rgba(120,86,44,.28), transparent 60%), + repeating-linear-gradient(93deg, rgba(150,115,70,.05) 0 2px, transparent 2px 6px), + repeating-linear-gradient(2deg, rgba(150,115,70,.04) 0 3px, transparent 3px 7px), + linear-gradient(var(--nq-p-parchment-top), var(--nq-p-parchment-bottom)); + --nq-p-vignette: inset 0 0 200px rgba(80,52,20,.35); + + /* Card surfaces sitting on parchment */ + --nq-p-card-open: linear-gradient(#fdf7e6, #f2e7c9); /* incomplete quest */ + --nq-p-card-done: linear-gradient(#f0e8d3, #e6dcc2); /* sealed quest */ + --nq-p-card-panel: linear-gradient(#fcf6e6, #f1e6ca); /* adventurer plate, stat block */ + --nq-p-card-border: rgba(120,88,48,.38); + --nq-p-panel-border: rgba(92,62,26,.40); + --nq-p-card-shadow: 0 3px 0 rgba(120,88,48,.20), inset 0 1px 0 rgba(255,255,255,.70); + --nq-p-panel-shadow: 0 6px 0 rgba(92,62,26,.18), inset 0 2px 0 rgba(255,255,255,.70); + --nq-p-frame-outer: 2px solid rgba(92,62,26,.45); + --nq-p-frame-inner: 1px solid rgba(92,62,26,.28); + --nq-p-rule: 2px solid rgba(92,62,26,.35); + + /* ── Ink (all values >= 4.5:1 on parchment) ───────────── */ + --nq-p-ink: #2b1f14; /* titles, quest names */ + --nq-p-ink-secondary: #5c452a; /* meta, window labels, counts */ + --nq-p-ink-muted: #6f6455; /* sealed quest titles only */ + --nq-p-ink-away: #4d4433; /* away adventurer name */ + --nq-p-ink-late: #8f1526; /* overdue line */ + + /* ── Brand (CubeCraft, toned down: accents only) ──────── */ + --nq-brand-blue: #3B3AB8; + --nq-brand-purple: #A035CC; + --nq-brand-gradient: linear-gradient(135deg, var(--nq-brand-blue), var(--nq-brand-purple)); + --nq-p-icon-tile: var(--nq-brand-gradient); /* open quest icon tile */ + --nq-p-icon-tile-done: rgba(92,62,26,.16); + --nq-p-crest: var(--nq-brand-gradient); + --nq-p-crest-away: linear-gradient(135deg, #6b6b7a, #7a7286); + + /* ── Wax seal (completed marker) ──────────────────────── */ + --nq-p-seal: radial-gradient(circle at 35% 30%, #a8283a, #6d1322); + --nq-p-seal-shadow: 0 4px 10px rgba(60,10,20,.40), inset 0 0 0 4px rgba(255,255,255,.14); + --nq-p-seal-size: 78px; /* 168px on the quest-complete screen */ + + /* ── Dock (bottom strip, non-interactive) ─────────────── */ + --nq-p-dock-bg: rgba(43,31,20,.90); + --nq-p-dock-ink: #f7efdb; + --nq-p-dock-ink-secondary: #d8c9a6; + --nq-p-dock-divider: rgba(233,220,189,.30); + --nq-p-dock-height: 84px; + + /* ── Type ─────────────────────────────────────────────── */ + --nq-p-font-display: 'Cinzel Decorative', Cinzel, serif; /* NestQuest, names, Quest complete */ + --nq-p-font-heading: Cinzel, serif; /* section heads, quest titles, numerals */ + --nq-p-font-body: Nunito, system-ui, sans-serif; /* meta lines, dock */ + + --nq-p-size-hero: 86px; /* 900 weight, "Quest complete" */ + --nq-p-size-wordmark: 66px; /* 900 weight */ + --nq-p-size-title: 56px; /* 700, "Declan's Quest Log" */ + --nq-p-size-name: 48px; /* 700, adventurer name */ + --nq-p-size-section: 32px; /* 700, Morning / Afternoon / Evening */ + --nq-p-size-quest: 30px; /* 700, quest title */ + --nq-p-size-body: 22px; /* 600 Nunito, meta — PANEL MINIMUM */ + --nq-p-size-label: 21px; /* 600 Cinzel, uppercase, tracked */ + --nq-p-track-label: .14em; + --nq-p-track-kicker: .30em; + + /* ── Metrics: touch ───────────────────────────────────── */ + --nq-p-quest-min-height: 116px; /* whole card is the tap target */ + --nq-p-quest-gap: 24px; /* separation between tap targets */ + --nq-p-button-height: 72px; /* "Complete" button on a quest card */ + --nq-p-button-min-width: 150px; + --nq-p-confirm-button: 108px; /* confirm dialog buttons */ + --nq-p-radius-card: 12px; + --nq-p-radius-panel: 20px; + --nq-p-radius-pill: 9999px; + --nq-p-page-inset: 62px; + --nq-p-frame-inset: 26px; + + /* ── Motion (CubeCraft easing) ────────────────────────── */ + --nq-ease-out: cubic-bezier(0, 0, .2, 1); + --nq-dur-micro: 120ms; + --nq-dur-base: 200ms; + --nq-dur-modal: 350ms; +} diff --git a/frontend/src/cards/admin.ts b/frontend/src/cards/admin.ts index b15993b..580dd78 100644 --- a/frontend/src/cards/admin.ts +++ b/frontend/src/cards/admin.ts @@ -1,4 +1,5 @@ -import { LitElement, html } from "lit"; +import { LitElement, html, unsafeCSS } from "lit"; +import adminTokens from "../../../custom_components/nestquest/www/nestquest-admin-tokens.css?inline"; import { registerCustomCard, type CardConfig, type HassLike } from "../types"; @@ -8,6 +9,8 @@ export class NestQuestAdminCard extends LitElement { _config: { state: true }, }; + static styles = [unsafeCSS(adminTokens)]; + hass?: HassLike; _config?: CardConfig; diff --git a/frontend/src/cards/party-board.ts b/frontend/src/cards/party-board.ts index d19bf8b..e6fbb38 100644 --- a/frontend/src/cards/party-board.ts +++ b/frontend/src/cards/party-board.ts @@ -1,4 +1,5 @@ -import { LitElement, html } from "lit"; +import { LitElement, html, unsafeCSS } from "lit"; +import panelTokens from "../../../custom_components/nestquest/www/nestquest-panel-tokens.css?inline"; import { registerCustomCard, type CardConfig, type HassLike } from "../types"; @@ -8,6 +9,8 @@ export class NestQuestPartyBoardCard extends LitElement { _config: { state: true }, }; + static styles = [unsafeCSS(panelTokens)]; + hass?: HassLike; _config?: CardConfig; diff --git a/frontend/src/cards/quest-log.ts b/frontend/src/cards/quest-log.ts index d56a788..b51929a 100644 --- a/frontend/src/cards/quest-log.ts +++ b/frontend/src/cards/quest-log.ts @@ -1,4 +1,5 @@ -import { LitElement, html } from "lit"; +import { LitElement, html, unsafeCSS } from "lit"; +import panelTokens from "../../../custom_components/nestquest/www/nestquest-panel-tokens.css?inline"; import { registerCustomCard, type CardConfig, type HassLike } from "../types"; @@ -8,6 +9,8 @@ export class NestQuestQuestLogCard extends LitElement { _config: { state: true }, }; + static styles = [unsafeCSS(panelTokens)]; + hass?: HassLike; _config?: CardConfig; diff --git a/frontend/src/nestquest-cards.ts b/frontend/src/nestquest-cards.ts index 3d08e0e..02316e3 100644 --- a/frontend/src/nestquest-cards.ts +++ b/frontend/src/nestquest-cards.ts @@ -1,3 +1,12 @@ import "./cards/party-board"; import "./cards/quest-log"; import "./cards/admin"; + +const FONTS_HREF = "/nestquest-static/nestquest-fonts.css"; +if (!document.querySelector('link[data-nq-fonts=""]')) { + const link = document.createElement("link"); + link.rel = "stylesheet"; + link.href = FONTS_HREF; + link.dataset.nqFonts = ""; + document.head.appendChild(link); +} diff --git a/frontend/src/vite-env.d.ts b/frontend/src/vite-env.d.ts new file mode 100644 index 0000000..11f02fe --- /dev/null +++ b/frontend/src/vite-env.d.ts @@ -0,0 +1 @@ +/// diff --git a/tests/test_frontend.py b/tests/test_frontend.py index 74e51a0..e32b1db 100644 --- a/tests/test_frontend.py +++ b/tests/test_frontend.py @@ -91,3 +91,67 @@ def test_readme_documents_npm_build() -> None: readme = (REPO_ROOT / "README.md").read_text(encoding="utf-8") assert "npm run build" in readme assert "nestquest-cards.js" in readme + + +WOFF2_FILES = ( + "nunito-latin-400-normal.woff2", + "nunito-latin-600-normal.woff2", + "nunito-latin-700-normal.woff2", + "nunito-latin-800-normal.woff2", + "cinzel-latin-600-normal.woff2", + "cinzel-latin-700-normal.woff2", + "cinzel-latin-900-normal.woff2", + "cinzel-decorative-latin-700-normal.woff2", + "cinzel-decorative-latin-900-normal.woff2", +) +CDN_MARKERS = ( + "fonts.googleapis.com", + "fonts.gstatic.com", + "cdn.jsdelivr.net", + "unpkg.com", + "cdnjs.cloudflare.com", +) + + +def test_www_contains_required_woff2_fonts() -> None: + fonts_dir = WWW_DIR / "fonts" + for name in WOFF2_FILES: + path = fonts_dir / name + assert path.is_file(), name + assert path.read_bytes()[:4] == b"wOF2" + + +def test_font_css_declares_swap_and_local_urls() -> None: + css = (WWW_DIR / "nestquest-fonts.css").read_text(encoding="utf-8") + assert css.count("font-display: swap") == 9 + assert css.count("@font-face") == 9 + assert 'font-family: "Nunito"' in css + assert 'font-family: "Cinzel"' in css + assert 'font-family: "Cinzel Decorative"' in css + for name in WOFF2_FILES: + assert f"./fonts/{name}" in css + assert "http://" not in css + assert "https://" not in css + + +def test_token_css_copied_verbatim_from_design() -> None: + design = REPO_ROOT / "design" / "tokens" + for name in ("nestquest-panel-tokens.css", "nestquest-admin-tokens.css"): + expected = (design / name).read_bytes() + actual = (WWW_DIR / name).read_bytes() + assert actual == expected + + +def test_frontend_and_www_have_no_cdn_font_references() -> None: + roots = (FRONTEND_DIR, WWW_DIR) + for root in roots: + for path in root.rglob("*"): + if not path.is_file(): + continue + if "node_modules" in path.parts: + continue + if path.suffix.lower() in {".woff2", ".png", ".jpg"}: + continue + text = path.read_text(encoding="utf-8", errors="ignore") + for marker in CDN_MARKERS: + assert marker not in text, f"{path}: {marker}" From 1911b2bf6eb9af051be6ad056b8c24b2d27f897a Mon Sep 17 00:00:00 2001 From: dev_agent <17+dev_agent@code.cubecraftcreations.com> Date: Sun, 20 Sep 2026 12:39:47 -0400 Subject: [PATCH 03/11] Add the party board card (1a) read-only Add the party board card (1a) read-only --- custom_components/nestquest/binary_sensor.py | 11 + custom_components/nestquest/coordinator.py | 30 +- .../nestquest/www/nestquest-cards.js | 1121 ++++++++++++++--- frontend/src/cards/party-board.ts | 862 ++++++++++++- tests/test_binary_sensors.py | 54 +- tests/test_coordinator.py | 65 + 6 files changed, 1923 insertions(+), 220 deletions(-) diff --git a/custom_components/nestquest/binary_sensor.py b/custom_components/nestquest/binary_sensor.py index 7cd45b3..7c7fe10 100644 --- a/custom_components/nestquest/binary_sensor.py +++ b/custom_components/nestquest/binary_sensor.py @@ -135,6 +135,17 @@ class NestQuestPresentTodaySensor(_NestQuestChildBinarySensor): return None return child.present + @property + def extra_state_attributes(self) -> dict[str, Any] | None: + """Base attributes plus, for a child away today, the ISO date + of their next present day (the panel's away plate renders + ``Returns , `` from it).""" + base = super().extra_state_attributes + if base is None: + return None + child = self._child + return {**base, "next_present": child.next_present if child else None} + def _child_binary_sensors( coordinator: NestQuestCoordinator, child: ChildDaySnapshot diff --git a/custom_components/nestquest/coordinator.py b/custom_components/nestquest/coordinator.py index 08f9ad9..39dc777 100644 --- a/custom_components/nestquest/coordinator.py +++ b/custom_components/nestquest/coordinator.py @@ -50,6 +50,7 @@ from .dao_rules import QuestDefinitionsDao from .dao_presence import PresenceOverridesDao, PresenceSchedulesDao from .db import NestQuestDatabase from .presence import ( + MAX_PREVIEW_SCAN_DAYS, PresenceEngine, PresenceOverride, PresenceSchedule, @@ -86,6 +87,11 @@ class ChildDaySnapshot: completed_today: int remaining_today: int completion_pct: int + #: For a child away today: the ISO date of their next present day + #: (the panel's away plate renders ``Returns , `` + #: from it). ``None`` when the child is present today or no + #: present day exists within the presence engine's scan cap. + next_present: str | None = None @dataclass(frozen=True) @@ -162,10 +168,14 @@ class NestQuestCoordinator(DataUpdateCoordinator): schedules[child.id] = PresenceSchedule.decode( child.id, schedule.anchor_date, schedule.pattern ) - todays = await PresenceOverridesDao( + window_end = today + datetime.timedelta( + days=MAX_PREVIEW_SCAN_DAYS + ) + window_end_iso = window_end.isoformat() + ranged = await PresenceOverridesDao( self.database - ).list_by_child_and_range(child.id, today_iso, today_iso) - if todays: + ).list_by_child_and_range(child.id, today_iso, window_end_iso) + if ranged: # The engine validates MODEL objects, not DAO records; # each record converts losslessly (the schema guarantees # end >= start and a real is_present). @@ -177,7 +187,7 @@ class NestQuestCoordinator(DataUpdateCoordinator): record.is_present, record.note, ) - for record in todays + for record in ranged ] engine = PresenceEngine(schedules, overrides) @@ -231,18 +241,28 @@ class NestQuestCoordinator(DataUpdateCoordinator): due = len(views) completed = sum(1 for view in views if view.state == "done") remaining = due - completed + child_present = engine.is_present(child.id, today) + next_present: str | None = None + if not child_present: + try: + next_present = engine.next_present_dates( + child.id, today, 1 + )[0].isoformat() + except ValueError: + next_present = None snapshots.append( ChildDaySnapshot( child_id=child.id, child_name=child.display_name, instances=tuple(views), - present=engine.is_present(child.id, today), + present=child_present, due_today=due, completed_today=completed, remaining_today=remaining, completion_pct=( round(completed * 100 / due) if due else 100 ), + next_present=next_present, ) ) diff --git a/custom_components/nestquest/www/nestquest-cards.js b/custom_components/nestquest/www/nestquest-cards.js index db966a0..747ba7f 100644 --- a/custom_components/nestquest/www/nestquest-cards.js +++ b/custom_components/nestquest/www/nestquest-cards.js @@ -1,15 +1,15 @@ -const k = globalThis, M = k.ShadowRoot && (k.ShadyCSS === void 0 || k.ShadyCSS.nativeShadow) && "adoptedStyleSheets" in Document.prototype && "replace" in CSSStyleSheet.prototype, tt = /* @__PURE__ */ Symbol(), j = /* @__PURE__ */ new WeakMap(); -let ot = class { - constructor(t, e, s) { - if (this._$cssResult$ = !0, s !== tt) throw Error("CSSResult is not constructable. Use `unsafeCSS` or `css` instead."); +const T = globalThis, L = T.ShadowRoot && (T.ShadyCSS === void 0 || T.ShadyCSS.nativeShadow) && "adoptedStyleSheets" in Document.prototype && "replace" in CSSStyleSheet.prototype, j = /* @__PURE__ */ Symbol(), K = /* @__PURE__ */ new WeakMap(); +let ct = class { + constructor(t, e, n) { + if (this._$cssResult$ = !0, n !== j) throw Error("CSSResult is not constructable. Use `unsafeCSS` or `css` instead."); this.cssText = t, this.t = e; } get styleSheet() { let t = this.o; const e = this.t; - if (M && t === void 0) { - const s = e !== void 0 && e.length === 1; - s && (t = j.get(e)), t === void 0 && ((this.o = t = new CSSStyleSheet()).replaceSync(this.cssText), s && j.set(e, t)); + if (L && t === void 0) { + const n = e !== void 0 && e.length === 1; + n && (t = K.get(e)), t === void 0 && ((this.o = t = new CSSStyleSheet()).replaceSync(this.cssText), n && K.set(e, t)); } return t; } @@ -17,108 +17,115 @@ let ot = class { return this.cssText; } }; -const U = (i) => new ot(typeof i == "string" ? i : i + "", void 0, tt), ht = (i, t) => { - if (M) i.adoptedStyleSheets = t.map((e) => e instanceof CSSStyleSheet ? e : e.styleSheet); +const $ = (s) => new ct(typeof s == "string" ? s : s + "", void 0, j), _t = (s, ...t) => { + const e = s.length === 1 ? s[0] : t.reduce((n, i, r) => n + ((a) => { + if (a._$cssResult$ === !0) return a.cssText; + if (typeof a == "number") return a; + throw Error("Value passed to 'css' function must be a 'css' function result: " + a + ". Use 'unsafeCSS' to pass non-literal values, but take care to ensure page security."); + })(i) + s[r + 1], s[0]); + return new ct(e, s, j); +}, $t = (s, t) => { + if (L) s.adoptedStyleSheets = t.map((e) => e instanceof CSSStyleSheet ? e : e.styleSheet); else for (const e of t) { - const s = document.createElement("style"), n = k.litNonce; - n !== void 0 && s.setAttribute("nonce", n), s.textContent = e.cssText, i.appendChild(s); + const n = document.createElement("style"), i = T.litNonce; + i !== void 0 && n.setAttribute("nonce", i), n.textContent = e.cssText, s.appendChild(n); } -}, I = M ? (i) => i : (i) => i instanceof CSSStyleSheet ? ((t) => { +}, J = L ? (s) => s : (s) => s instanceof CSSStyleSheet ? ((t) => { let e = ""; - for (const s of t.cssRules) e += s.cssText; - return U(e); -})(i) : i; -const { is: dt, defineProperty: pt, getOwnPropertyDescriptor: lt, getOwnPropertyNames: ct, getOwnPropertySymbols: ut, getPrototypeOf: $t } = Object, z = globalThis, Q = z.trustedTypes, ft = Q ? Q.emptyScript : "", gt = z.reactiveElementPolyfillSupport, E = (i, t) => i, T = { toAttribute(i, t) { + for (const n of t.cssRules) e += n.cssText; + return $(e); +})(s) : s; +const { is: vt, defineProperty: qt, getOwnPropertyDescriptor: xt, getOwnPropertyNames: wt, getOwnPropertySymbols: At, getPrototypeOf: kt } = Object, M = globalThis, G = M.trustedTypes, Et = G ? G.emptyScript : "", Ct = M.reactiveElementPolyfillSupport, E = (s, t) => s, F = { toAttribute(s, t) { switch (t) { case Boolean: - i = i ? ft : null; + s = s ? Et : null; break; case Object: case Array: - i = i == null ? i : JSON.stringify(i); + s = s == null ? s : JSON.stringify(s); } - return i; -}, fromAttribute(i, t) { - let e = i; + return s; +}, fromAttribute(s, t) { + let e = s; switch (t) { case Boolean: - e = i !== null; + e = s !== null; break; case Number: - e = i === null ? null : Number(i); + e = s === null ? null : Number(s); break; case Object: case Array: try { - e = JSON.parse(i); + e = JSON.parse(s); } catch { e = null; } } return e; -} }, et = (i, t) => !dt(i, t), W = { attribute: !0, type: String, converter: T, reflect: !1, useDefault: !1, hasChanged: et }; -Symbol.metadata ??= /* @__PURE__ */ Symbol("metadata"), z.litPropertyMetadata ??= /* @__PURE__ */ new WeakMap(); -let b = class extends HTMLElement { +} }, ht = (s, t) => !vt(s, t), X = { attribute: !0, type: String, converter: F, reflect: !1, useDefault: !1, hasChanged: ht }; +Symbol.metadata ??= /* @__PURE__ */ Symbol("metadata"), M.litPropertyMetadata ??= /* @__PURE__ */ new WeakMap(); +let x = class extends HTMLElement { static addInitializer(t) { this._$Ei(), (this.l ??= []).push(t); } static get observedAttributes() { return this.finalize(), this._$Eh && [...this._$Eh.keys()]; } - static createProperty(t, e = W) { + static createProperty(t, e = X) { if (e.state && (e.attribute = !1), this._$Ei(), this.prototype.hasOwnProperty(t) && ((e = Object.create(e)).wrapped = !0), this.elementProperties.set(t, e), !e.noAccessor) { - const s = /* @__PURE__ */ Symbol(), n = this.getPropertyDescriptor(t, s, e); - n !== void 0 && pt(this.prototype, t, n); + const n = /* @__PURE__ */ Symbol(), i = this.getPropertyDescriptor(t, n, e); + i !== void 0 && qt(this.prototype, t, i); } } - static getPropertyDescriptor(t, e, s) { - const { get: n, set: r } = lt(this.prototype, t) ?? { get() { + static getPropertyDescriptor(t, e, n) { + const { get: i, set: r } = xt(this.prototype, t) ?? { get() { return this[e]; }, set(a) { this[e] = a; } }; - return { get: n, set(a) { - const d = n?.call(this); - r?.call(this, a), this.requestUpdate(t, d, s); + return { get: i, set(a) { + const l = i?.call(this); + r?.call(this, a), this.requestUpdate(t, l, n); }, configurable: !0, enumerable: !0 }; } static getPropertyOptions(t) { - return this.elementProperties.get(t) ?? W; + return this.elementProperties.get(t) ?? X; } static _$Ei() { if (this.hasOwnProperty(E("elementProperties"))) return; - const t = $t(this); + const t = kt(this); t.finalize(), t.l !== void 0 && (this.l = [...t.l]), this.elementProperties = new Map(t.elementProperties); } static finalize() { if (this.hasOwnProperty(E("finalized"))) return; if (this.finalized = !0, this._$Ei(), this.hasOwnProperty(E("properties"))) { - const e = this.properties, s = [...ct(e), ...ut(e)]; - for (const n of s) this.createProperty(n, e[n]); + const e = this.properties, n = [...wt(e), ...At(e)]; + for (const i of n) this.createProperty(i, e[i]); } const t = this[Symbol.metadata]; if (t !== null) { const e = litPropertyMetadata.get(t); - if (e !== void 0) for (const [s, n] of e) this.elementProperties.set(s, n); + if (e !== void 0) for (const [n, i] of e) this.elementProperties.set(n, i); } this._$Eh = /* @__PURE__ */ new Map(); - for (const [e, s] of this.elementProperties) { - const n = this._$Eu(e, s); - n !== void 0 && this._$Eh.set(n, e); + for (const [e, n] of this.elementProperties) { + const i = this._$Eu(e, n); + i !== void 0 && this._$Eh.set(i, e); } this.elementStyles = this.finalizeStyles(this.styles); } static finalizeStyles(t) { const e = []; if (Array.isArray(t)) { - const s = new Set(t.flat(1 / 0).reverse()); - for (const n of s) e.unshift(I(n)); - } else t !== void 0 && e.push(I(t)); + const n = new Set(t.flat(1 / 0).reverse()); + for (const i of n) e.unshift(J(i)); + } else t !== void 0 && e.push(J(t)); return e; } static _$Eu(t, e) { - const s = e.attribute; - return s === !1 ? void 0 : typeof s == "string" ? s : typeof t == "string" ? t.toLowerCase() : void 0; + const n = e.attribute; + return n === !1 ? void 0 : typeof n == "string" ? n : typeof t == "string" ? t.toLowerCase() : void 0; } constructor() { super(), this._$Ep = void 0, this.isUpdatePending = !1, this.hasUpdated = !1, this._$Em = null, this._$Ev(); @@ -134,12 +141,12 @@ let b = class extends HTMLElement { } _$E_() { const t = /* @__PURE__ */ new Map(), e = this.constructor.elementProperties; - for (const s of e.keys()) this.hasOwnProperty(s) && (t.set(s, this[s]), delete this[s]); + for (const n of e.keys()) this.hasOwnProperty(n) && (t.set(n, this[n]), delete this[n]); t.size > 0 && (this._$Ep = t); } createRenderRoot() { const t = this.shadowRoot ?? this.attachShadow(this.constructor.shadowRootOptions); - return ht(t, this.constructor.elementStyles), t; + return $t(t, this.constructor.elementStyles), t; } connectedCallback() { this.renderRoot ??= this.createRenderRoot(), this.enableUpdating(!0), this._$EO?.forEach((t) => t.hostConnected?.()); @@ -149,35 +156,35 @@ let b = class extends HTMLElement { disconnectedCallback() { this._$EO?.forEach((t) => t.hostDisconnected?.()); } - attributeChangedCallback(t, e, s) { - this._$AK(t, s); + attributeChangedCallback(t, e, n) { + this._$AK(t, n); } _$ET(t, e) { - const s = this.constructor.elementProperties.get(t), n = this.constructor._$Eu(t, s); - if (n !== void 0 && s.reflect === !0) { - const r = (s.converter?.toAttribute !== void 0 ? s.converter : T).toAttribute(e, s.type); - this._$Em = t, r == null ? this.removeAttribute(n) : this.setAttribute(n, r), this._$Em = null; + const n = this.constructor.elementProperties.get(t), i = this.constructor._$Eu(t, n); + if (i !== void 0 && n.reflect === !0) { + const r = (n.converter?.toAttribute !== void 0 ? n.converter : F).toAttribute(e, n.type); + this._$Em = t, r == null ? this.removeAttribute(i) : this.setAttribute(i, r), this._$Em = null; } } _$AK(t, e) { - const s = this.constructor, n = s._$Eh.get(t); - if (n !== void 0 && this._$Em !== n) { - const r = s.getPropertyOptions(n), a = typeof r.converter == "function" ? { fromAttribute: r.converter } : r.converter?.fromAttribute !== void 0 ? r.converter : T; - this._$Em = n; - const d = a.fromAttribute(e, r.type); - this[n] = d ?? this._$Ej?.get(n) ?? d, this._$Em = null; + const n = this.constructor, i = n._$Eh.get(t); + if (i !== void 0 && this._$Em !== i) { + const r = n.getPropertyOptions(i), a = typeof r.converter == "function" ? { fromAttribute: r.converter } : r.converter?.fromAttribute !== void 0 ? r.converter : F; + this._$Em = i; + const l = a.fromAttribute(e, r.type); + this[i] = l ?? this._$Ej?.get(i) ?? l, this._$Em = null; } } - requestUpdate(t, e, s, n = !1, r) { + requestUpdate(t, e, n, i = !1, r) { if (t !== void 0) { const a = this.constructor; - if (n === !1 && (r = this[t]), s ??= a.getPropertyOptions(t), !((s.hasChanged ?? et)(r, e) || s.useDefault && s.reflect && r === this._$Ej?.get(t) && !this.hasAttribute(a._$Eu(t, s)))) return; - this.C(t, e, s); + if (i === !1 && (r = this[t]), n ??= a.getPropertyOptions(t), !((n.hasChanged ?? ht)(r, e) || n.useDefault && n.reflect && r === this._$Ej?.get(t) && !this.hasAttribute(a._$Eu(t, n)))) return; + this.C(t, e, n); } this.isUpdatePending === !1 && (this._$ES = this._$EP()); } - C(t, e, { useDefault: s, reflect: n, wrapped: r }, a) { - s && !(this._$Ej ??= /* @__PURE__ */ new Map()).has(t) && (this._$Ej.set(t, a ?? e ?? this[t]), r !== !0 || a !== void 0) || (this._$AL.has(t) || (this.hasUpdated || s || (e = void 0), this._$AL.set(t, e)), n === !0 && this._$Em !== t && (this._$Eq ??= /* @__PURE__ */ new Set()).add(t)); + C(t, e, { useDefault: n, reflect: i, wrapped: r }, a) { + n && !(this._$Ej ??= /* @__PURE__ */ new Map()).has(t) && (this._$Ej.set(t, a ?? e ?? this[t]), r !== !0 || a !== void 0) || (this._$AL.has(t) || (this.hasUpdated || n || (e = void 0), this._$AL.set(t, e)), i === !0 && this._$Em !== t && (this._$Eq ??= /* @__PURE__ */ new Set()).add(t)); } async _$EP() { this.isUpdatePending = !0; @@ -196,21 +203,21 @@ let b = class extends HTMLElement { if (!this.isUpdatePending) return; if (!this.hasUpdated) { if (this.renderRoot ??= this.createRenderRoot(), this._$Ep) { - for (const [n, r] of this._$Ep) this[n] = r; + for (const [i, r] of this._$Ep) this[i] = r; this._$Ep = void 0; } - const s = this.constructor.elementProperties; - if (s.size > 0) for (const [n, r] of s) { - const { wrapped: a } = r, d = this[n]; - a !== !0 || this._$AL.has(n) || d === void 0 || this.C(n, void 0, r, d); + const n = this.constructor.elementProperties; + if (n.size > 0) for (const [i, r] of n) { + const { wrapped: a } = r, l = this[i]; + a !== !0 || this._$AL.has(i) || l === void 0 || this.C(i, void 0, r, l); } } let t = !1; const e = this._$AL; try { - t = this.shouldUpdate(e), t ? (this.willUpdate(e), this._$EO?.forEach((s) => s.hostUpdate?.()), this.update(e)) : this._$EM(); - } catch (s) { - throw t = !1, this._$EM(), s; + t = this.shouldUpdate(e), t ? (this.willUpdate(e), this._$EO?.forEach((n) => n.hostUpdate?.()), this.update(e)) : this._$EM(); + } catch (n) { + throw t = !1, this._$EM(), n; } t && this._$AE(e); } @@ -239,70 +246,70 @@ let b = class extends HTMLElement { firstUpdated(t) { } }; -b.elementStyles = [], b.shadowRootOptions = { mode: "open" }, b[E("elementProperties")] = /* @__PURE__ */ new Map(), b[E("finalized")] = /* @__PURE__ */ new Map(), gt?.({ ReactiveElement: b }), (z.reactiveElementVersions ??= []).push("2.1.2"); -const R = globalThis, V = (i) => i, N = R.trustedTypes, K = N ? N.createPolicy("lit-html", { createHTML: (i) => i }) : void 0, st = "$lit$", f = `lit$${Math.random().toFixed(9).slice(2)}$`, nt = "?" + f, _t = `<${nt}>`, y = document, x = () => y.createComment(""), w = (i) => i === null || typeof i != "object" && typeof i != "function", D = Array.isArray, mt = (i) => D(i) || typeof i?.[Symbol.iterator] == "function", H = `[ -\f\r]`, v = /<(?:(!--|\/[^a-zA-Z])|(\/?[a-zA-Z][^>\s]*)|(\/?$))/g, Z = /-->/g, J = />/g, g = RegExp(`>|${H}(?:([^\\s"'>=/]+)(${H}*=${H}*(?:[^ -\f\r"'\`<>=]|("|')|))|$)`, "g"), G = /'/g, X = /"/g, it = /^(?:script|style|textarea|title)$/i, yt = (i) => (t, ...e) => ({ _$litType$: i, strings: t, values: e }), B = yt(1), A = /* @__PURE__ */ Symbol.for("lit-noChange"), l = /* @__PURE__ */ Symbol.for("lit-nothing"), Y = /* @__PURE__ */ new WeakMap(), _ = y.createTreeWalker(y, 129); -function rt(i, t) { - if (!D(i) || !i.hasOwnProperty("raw")) throw Error("invalid template strings array"); - return K !== void 0 ? K.createHTML(t) : t; +x.elementStyles = [], x.shadowRootOptions = { mode: "open" }, x[E("elementProperties")] = /* @__PURE__ */ new Map(), x[E("finalized")] = /* @__PURE__ */ new Map(), Ct?.({ ReactiveElement: x }), (M.reactiveElementVersions ??= []).push("2.1.2"); +const B = globalThis, Y = (s) => s, z = B.trustedTypes, tt = z ? z.createPolicy("lit-html", { createHTML: (s) => s }) : void 0, ut = "$lit$", y = `lit$${Math.random().toFixed(9).slice(2)}$`, ft = "?" + y, St = `<${ft}>`, q = document, C = () => q.createComment(""), S = (s) => s === null || typeof s != "object" && typeof s != "function", Q = Array.isArray, Nt = (s) => Q(s) || typeof s?.[Symbol.iterator] == "function", D = `[ +\f\r]`, k = /<(?:(!--|\/[^a-zA-Z])|(\/?[a-zA-Z][^>\s]*)|(\/?$))/g, et = /-->/g, nt = />/g, b = RegExp(`>|${D}(?:([^\\s"'>=/]+)(${D}*=${D}*(?:[^ +\f\r"'\`<>=]|("|')|))|$)`, "g"), st = /'/g, it = /"/g, gt = /^(?:script|style|textarea|title)$/i, Pt = (s) => (t, ...e) => ({ _$litType$: s, strings: t, values: e }), g = Pt(1), w = /* @__PURE__ */ Symbol.for("lit-noChange"), c = /* @__PURE__ */ Symbol.for("lit-nothing"), rt = /* @__PURE__ */ new WeakMap(), _ = q.createTreeWalker(q, 129); +function mt(s, t) { + if (!Q(s) || !s.hasOwnProperty("raw")) throw Error("invalid template strings array"); + return tt !== void 0 ? tt.createHTML(t) : t; } -const bt = (i, t) => { - const e = i.length - 1, s = []; - let n, r = t === 2 ? "" : t === 3 ? "" : "", a = v; - for (let d = 0; d < e; d++) { - const o = i[d]; - let p, c, h = -1, u = 0; - for (; u < o.length && (a.lastIndex = u, c = a.exec(o), c !== null); ) u = a.lastIndex, a === v ? c[1] === "!--" ? a = Z : c[1] !== void 0 ? a = J : c[2] !== void 0 ? (it.test(c[2]) && (n = RegExp("" ? (a = n ?? v, h = -1) : c[1] === void 0 ? h = -2 : (h = a.lastIndex - c[2].length, p = c[1], a = c[3] === void 0 ? g : c[3] === '"' ? X : G) : a === X || a === G ? a = g : a === Z || a === J ? a = v : (a = g, n = void 0); - const $ = a === g && i[d + 1].startsWith("/>") ? " " : ""; - r += a === v ? o + _t : h >= 0 ? (s.push(p), o.slice(0, h) + st + o.slice(h) + f + $) : o + f + (h === -2 ? d : $); +const Tt = (s, t) => { + const e = s.length - 1, n = []; + let i, r = t === 2 ? "" : t === 3 ? "" : "", a = k; + for (let l = 0; l < e; l++) { + const o = s[l]; + let d, h, p = -1, f = 0; + for (; f < o.length && (a.lastIndex = f, h = a.exec(o), h !== null); ) f = a.lastIndex, a === k ? h[1] === "!--" ? a = et : h[1] !== void 0 ? a = nt : h[2] !== void 0 ? (gt.test(h[2]) && (i = RegExp("" ? (a = i ?? k, p = -1) : h[1] === void 0 ? p = -2 : (p = a.lastIndex - h[2].length, d = h[1], a = h[3] === void 0 ? b : h[3] === '"' ? it : st) : a === it || a === st ? a = b : a === et || a === nt ? a = k : (a = b, i = void 0); + const u = a === b && s[l + 1].startsWith("/>") ? " " : ""; + r += a === k ? o + St : p >= 0 ? (n.push(d), o.slice(0, p) + ut + o.slice(p) + y + u) : o + y + (p === -2 ? l : u); } - return [rt(i, r + (i[e] || "") + (t === 2 ? "" : t === 3 ? "" : "")), s]; + return [mt(s, r + (s[e] || "") + (t === 2 ? "" : t === 3 ? "" : "")), n]; }; -class S { - constructor({ strings: t, _$litType$: e }, s) { - let n; +class N { + constructor({ strings: t, _$litType$: e }, n) { + let i; this.parts = []; let r = 0, a = 0; - const d = t.length - 1, o = this.parts, [p, c] = bt(t, e); - if (this.el = S.createElement(p, s), _.currentNode = this.el.content, e === 2 || e === 3) { - const h = this.el.content.firstChild; - h.replaceWith(...h.childNodes); + const l = t.length - 1, o = this.parts, [d, h] = Tt(t, e); + if (this.el = N.createElement(d, n), _.currentNode = this.el.content, e === 2 || e === 3) { + const p = this.el.content.firstChild; + p.replaceWith(...p.childNodes); } - for (; (n = _.nextNode()) !== null && o.length < d; ) { - if (n.nodeType === 1) { - if (n.hasAttributes()) for (const h of n.getAttributeNames()) if (h.endsWith(st)) { - const u = c[a++], $ = n.getAttribute(h).split(f), P = /([.?@])?(.*)/.exec(u); - o.push({ type: 1, index: r, name: P[2], strings: $, ctor: P[1] === "." ? qt : P[1] === "?" ? vt : P[1] === "@" ? Et : O }), n.removeAttribute(h); - } else h.startsWith(f) && (o.push({ type: 6, index: r }), n.removeAttribute(h)); - if (it.test(n.tagName)) { - const h = n.textContent.split(f), u = h.length - 1; - if (u > 0) { - n.textContent = N ? N.emptyScript : ""; - for (let $ = 0; $ < u; $++) n.append(h[$], x()), _.nextNode(), o.push({ type: 2, index: ++r }); - n.append(h[u], x()); + for (; (i = _.nextNode()) !== null && o.length < l; ) { + if (i.nodeType === 1) { + if (i.hasAttributes()) for (const p of i.getAttributeNames()) if (p.endsWith(ut)) { + const f = h[a++], u = i.getAttribute(p).split(y), m = /([.?@])?(.*)/.exec(f); + o.push({ type: 1, index: r, name: m[2], strings: u, ctor: m[1] === "." ? Ot : m[1] === "?" ? Mt : m[1] === "@" ? Ut : U }), i.removeAttribute(p); + } else p.startsWith(y) && (o.push({ type: 6, index: r }), i.removeAttribute(p)); + if (gt.test(i.tagName)) { + const p = i.textContent.split(y), f = p.length - 1; + if (f > 0) { + i.textContent = z ? z.emptyScript : ""; + for (let u = 0; u < f; u++) i.append(p[u], C()), _.nextNode(), o.push({ type: 2, index: ++r }); + i.append(p[f], C()); } } - } else if (n.nodeType === 8) if (n.data === nt) o.push({ type: 2, index: r }); + } else if (i.nodeType === 8) if (i.data === ft) o.push({ type: 2, index: r }); else { - let h = -1; - for (; (h = n.data.indexOf(f, h + 1)) !== -1; ) o.push({ type: 7, index: r }), h += f.length - 1; + let p = -1; + for (; (p = i.data.indexOf(y, p + 1)) !== -1; ) o.push({ type: 7, index: r }), p += y.length - 1; } r++; } } static createElement(t, e) { - const s = y.createElement("template"); - return s.innerHTML = t, s; + const n = q.createElement("template"); + return n.innerHTML = t, n; } } -function q(i, t, e = i, s) { - if (t === A) return t; - let n = s !== void 0 ? e._$Co?.[s] : e._$Cl; - const r = w(t) ? void 0 : t._$litDirective$; - return n?.constructor !== r && (n?._$AO?.(!1), r === void 0 ? n = void 0 : (n = new r(i), n._$AT(i, e, s)), s !== void 0 ? (e._$Co ??= [])[s] = n : e._$Cl = n), n !== void 0 && (t = q(i, n._$AS(i, t.values), n, s)), t; +function A(s, t, e = s, n) { + if (t === w) return t; + let i = n !== void 0 ? e._$Co?.[n] : e._$Cl; + const r = S(t) ? void 0 : t._$litDirective$; + return i?.constructor !== r && (i?._$AO?.(!1), r === void 0 ? i = void 0 : (i = new r(s), i._$AT(s, e, n)), n !== void 0 ? (e._$Co ??= [])[n] = i : e._$Cl = i), i !== void 0 && (t = A(s, i._$AS(s, t.values), i, n)), t; } -class At { +class zt { constructor(t, e) { this._$AV = [], this._$AN = void 0, this._$AD = t, this._$AM = e; } @@ -313,29 +320,29 @@ class At { return this._$AM._$AU; } u(t) { - const { el: { content: e }, parts: s } = this._$AD, n = (t?.creationScope ?? y).importNode(e, !0); - _.currentNode = n; - let r = _.nextNode(), a = 0, d = 0, o = s[0]; + const { el: { content: e }, parts: n } = this._$AD, i = (t?.creationScope ?? q).importNode(e, !0); + _.currentNode = i; + let r = _.nextNode(), a = 0, l = 0, o = n[0]; for (; o !== void 0; ) { if (a === o.index) { - let p; - o.type === 2 ? p = new C(r, r.nextSibling, this, t) : o.type === 1 ? p = new o.ctor(r, o.name, o.strings, this, t) : o.type === 6 && (p = new xt(r, this, t)), this._$AV.push(p), o = s[++d]; + let d; + o.type === 2 ? d = new P(r, r.nextSibling, this, t) : o.type === 1 ? d = new o.ctor(r, o.name, o.strings, this, t) : o.type === 6 && (d = new Ht(r, this, t)), this._$AV.push(d), o = n[++l]; } a !== o?.index && (r = _.nextNode(), a++); } - return _.currentNode = y, n; + return _.currentNode = q, i; } p(t) { let e = 0; - for (const s of this._$AV) s !== void 0 && (s.strings !== void 0 ? (s._$AI(t, s, e), e += s.strings.length - 2) : s._$AI(t[e])), e++; + for (const n of this._$AV) n !== void 0 && (n.strings !== void 0 ? (n._$AI(t, n, e), e += n.strings.length - 2) : n._$AI(t[e])), e++; } } -class C { +class P { get _$AU() { return this._$AM?._$AU ?? this._$Cv; } - constructor(t, e, s, n) { - this.type = 2, this._$AH = l, this._$AN = void 0, this._$AA = t, this._$AB = e, this._$AM = s, this.options = n, this._$Cv = n?.isConnected ?? !0; + constructor(t, e, n, i) { + this.type = 2, this._$AH = c, this._$AN = void 0, this._$AA = t, this._$AB = e, this._$AM = n, this.options = i, this._$Cv = i?.isConnected ?? !0; } get parentNode() { let t = this._$AA.parentNode; @@ -349,7 +356,7 @@ class C { return this._$AB; } _$AI(t, e = this) { - t = q(this, t, e), w(t) ? t === l || t == null || t === "" ? (this._$AH !== l && this._$AR(), this._$AH = l) : t !== this._$AH && t !== A && this._(t) : t._$litType$ !== void 0 ? this.$(t) : t.nodeType !== void 0 ? this.T(t) : mt(t) ? this.k(t) : this._(t); + t = A(this, t, e), S(t) ? t === c || t == null || t === "" ? (this._$AH !== c && this._$AR(), this._$AH = c) : t !== this._$AH && t !== w && this._(t) : t._$litType$ !== void 0 ? this.$(t) : t.nodeType !== void 0 ? this.T(t) : Nt(t) ? this.k(t) : this._(t); } O(t) { return this._$AA.parentNode.insertBefore(t, this._$AB); @@ -358,115 +365,115 @@ class C { this._$AH !== t && (this._$AR(), this._$AH = this.O(t)); } _(t) { - this._$AH !== l && w(this._$AH) ? this._$AA.nextSibling.data = t : this.T(y.createTextNode(t)), this._$AH = t; + this._$AH !== c && S(this._$AH) ? this._$AA.nextSibling.data = t : this.T(q.createTextNode(t)), this._$AH = t; } $(t) { - const { values: e, _$litType$: s } = t, n = typeof s == "number" ? this._$AC(t) : (s.el === void 0 && (s.el = S.createElement(rt(s.h, s.h[0]), this.options)), s); - if (this._$AH?._$AD === n) this._$AH.p(e); + const { values: e, _$litType$: n } = t, i = typeof n == "number" ? this._$AC(t) : (n.el === void 0 && (n.el = N.createElement(mt(n.h, n.h[0]), this.options)), n); + if (this._$AH?._$AD === i) this._$AH.p(e); else { - const r = new At(n, this), a = r.u(this.options); + const r = new zt(i, this), a = r.u(this.options); r.p(e), this.T(a), this._$AH = r; } } _$AC(t) { - let e = Y.get(t.strings); - return e === void 0 && Y.set(t.strings, e = new S(t)), e; + let e = rt.get(t.strings); + return e === void 0 && rt.set(t.strings, e = new N(t)), e; } k(t) { - D(this._$AH) || (this._$AH = [], this._$AR()); + Q(this._$AH) || (this._$AH = [], this._$AR()); const e = this._$AH; - let s, n = 0; - for (const r of t) n === e.length ? e.push(s = new C(this.O(x()), this.O(x()), this, this.options)) : s = e[n], s._$AI(r), n++; - n < e.length && (this._$AR(s && s._$AB.nextSibling, n), e.length = n); + let n, i = 0; + for (const r of t) i === e.length ? e.push(n = new P(this.O(C()), this.O(C()), this, this.options)) : n = e[i], n._$AI(r), i++; + i < e.length && (this._$AR(n && n._$AB.nextSibling, i), e.length = i); } _$AR(t = this._$AA.nextSibling, e) { for (this._$AP?.(!1, !0, e); t !== this._$AB; ) { - const s = V(t).nextSibling; - V(t).remove(), t = s; + const n = Y(t).nextSibling; + Y(t).remove(), t = n; } } setConnected(t) { this._$AM === void 0 && (this._$Cv = t, this._$AP?.(t)); } } -class O { +class U { get tagName() { return this.element.tagName; } get _$AU() { return this._$AM._$AU; } - constructor(t, e, s, n, r) { - this.type = 1, this._$AH = l, this._$AN = void 0, this.element = t, this.name = e, this._$AM = n, this.options = r, s.length > 2 || s[0] !== "" || s[1] !== "" ? (this._$AH = Array(s.length - 1).fill(new String()), this.strings = s) : this._$AH = l; + constructor(t, e, n, i, r) { + this.type = 1, this._$AH = c, this._$AN = void 0, this.element = t, this.name = e, this._$AM = i, this.options = r, n.length > 2 || n[0] !== "" || n[1] !== "" ? (this._$AH = Array(n.length - 1).fill(new String()), this.strings = n) : this._$AH = c; } - _$AI(t, e = this, s, n) { + _$AI(t, e = this, n, i) { const r = this.strings; let a = !1; - if (r === void 0) t = q(this, t, e, 0), a = !w(t) || t !== this._$AH && t !== A, a && (this._$AH = t); + if (r === void 0) t = A(this, t, e, 0), a = !S(t) || t !== this._$AH && t !== w, a && (this._$AH = t); else { - const d = t; - let o, p; - for (t = r[0], o = 0; o < r.length - 1; o++) p = q(this, d[s + o], e, o), p === A && (p = this._$AH[o]), a ||= !w(p) || p !== this._$AH[o], p === l ? t = l : t !== l && (t += (p ?? "") + r[o + 1]), this._$AH[o] = p; + const l = t; + let o, d; + for (t = r[0], o = 0; o < r.length - 1; o++) d = A(this, l[n + o], e, o), d === w && (d = this._$AH[o]), a ||= !S(d) || d !== this._$AH[o], d === c ? t = c : t !== c && (t += (d ?? "") + r[o + 1]), this._$AH[o] = d; } - a && !n && this.j(t); + a && !i && this.j(t); } j(t) { - t === l ? this.element.removeAttribute(this.name) : this.element.setAttribute(this.name, t ?? ""); + t === c ? this.element.removeAttribute(this.name) : this.element.setAttribute(this.name, t ?? ""); } } -class qt extends O { +class Ot extends U { constructor() { super(...arguments), this.type = 3; } j(t) { - this.element[this.name] = t === l ? void 0 : t; + this.element[this.name] = t === c ? void 0 : t; } } -class vt extends O { +class Mt extends U { constructor() { super(...arguments), this.type = 4; } j(t) { - this.element.toggleAttribute(this.name, !!t && t !== l); + this.element.toggleAttribute(this.name, !!t && t !== c); } } -class Et extends O { - constructor(t, e, s, n, r) { - super(t, e, s, n, r), this.type = 5; +class Ut extends U { + constructor(t, e, n, i, r) { + super(t, e, n, i, r), this.type = 5; } _$AI(t, e = this) { - if ((t = q(this, t, e, 0) ?? l) === A) return; - const s = this._$AH, n = t === l && s !== l || t.capture !== s.capture || t.once !== s.once || t.passive !== s.passive, r = t !== l && (s === l || n); - n && this.element.removeEventListener(this.name, this, s), r && this.element.addEventListener(this.name, this, t), this._$AH = t; + if ((t = A(this, t, e, 0) ?? c) === w) return; + const n = this._$AH, i = t === c && n !== c || t.capture !== n.capture || t.once !== n.once || t.passive !== n.passive, r = t !== c && (n === c || i); + i && this.element.removeEventListener(this.name, this, n), r && this.element.addEventListener(this.name, this, t), this._$AH = t; } handleEvent(t) { typeof this._$AH == "function" ? this._$AH.call(this.options?.host ?? this.element, t) : this._$AH.handleEvent(t); } } -class xt { - constructor(t, e, s) { - this.element = t, this.type = 6, this._$AN = void 0, this._$AM = e, this.options = s; +class Ht { + constructor(t, e, n) { + this.element = t, this.type = 6, this._$AN = void 0, this._$AM = e, this.options = n; } get _$AU() { return this._$AM._$AU; } _$AI(t) { - q(this, t); + A(this, t); } } -const wt = R.litHtmlPolyfillSupport; -wt?.(S, C), (R.litHtmlVersions ??= []).push("3.3.3"); -const St = (i, t, e) => { - const s = e?.renderBefore ?? t; - let n = s._$litPart$; - if (n === void 0) { +const Dt = B.litHtmlPolyfillSupport; +Dt?.(N, P), (B.litHtmlVersions ??= []).push("3.3.3"); +const Rt = (s, t, e) => { + const n = e?.renderBefore ?? t; + let i = n._$litPart$; + if (i === void 0) { const r = e?.renderBefore ?? null; - s._$litPart$ = n = new C(t.insertBefore(x(), r), r, void 0, e ?? {}); + n._$litPart$ = i = new P(t.insertBefore(C(), r), r, void 0, e ?? {}); } - return n._$AI(i), n; + return i._$AI(s), i; }; -const F = globalThis; -class m extends b { +const W = globalThis; +class v extends x { constructor() { super(...arguments), this.renderOptions = { host: this }, this._$Do = void 0; } @@ -476,7 +483,7 @@ class m extends b { } update(t) { const e = this.render(); - this.hasUpdated || (this.renderOptions.isConnected = this.isConnected), super.update(t), this._$Do = St(e, this.renderRoot, this.renderOptions); + this.hasUpdated || (this.renderOptions.isConnected = this.isConnected), super.update(t), this._$Do = Rt(e, this.renderRoot, this.renderOptions); } connectedCallback() { super.connectedCallback(), this._$Do?.setConnected(!0); @@ -485,49 +492,745 @@ class m extends b { super.disconnectedCallback(), this._$Do?.setConnected(!1); } render() { - return A; + return w; } } -m._$litElement$ = !0, m.finalized = !0, F.litElementHydrateSupport?.({ LitElement: m }); -const Ct = F.litElementPolyfillSupport; -Ct?.({ LitElement: m }); -(F.litElementVersions ??= []).push("4.2.2"); -const at = ':host{--nq-p-parchment-top: #f0e4c7;--nq-p-parchment-bottom: #ddcca3;--nq-p-parchment: radial-gradient(ellipse at 25% 10%, rgba(255,255,255,.5), transparent 55%), radial-gradient(ellipse at 85% 90%, rgba(120,86,44,.28), transparent 60%), repeating-linear-gradient(93deg, rgba(150,115,70,.05) 0 2px, transparent 2px 6px), repeating-linear-gradient(2deg, rgba(150,115,70,.04) 0 3px, transparent 3px 7px), linear-gradient(var(--nq-p-parchment-top), var(--nq-p-parchment-bottom));--nq-p-vignette: inset 0 0 200px rgba(80,52,20,.35);--nq-p-card-open: linear-gradient(#fdf7e6, #f2e7c9);--nq-p-card-done: linear-gradient(#f0e8d3, #e6dcc2);--nq-p-card-panel: linear-gradient(#fcf6e6, #f1e6ca);--nq-p-card-border: rgba(120,88,48,.38);--nq-p-panel-border: rgba(92,62,26,.4);--nq-p-card-shadow: 0 3px 0 rgba(120,88,48,.2), inset 0 1px 0 rgba(255,255,255,.7);--nq-p-panel-shadow: 0 6px 0 rgba(92,62,26,.18), inset 0 2px 0 rgba(255,255,255,.7);--nq-p-frame-outer: 2px solid rgba(92,62,26,.45);--nq-p-frame-inner: 1px solid rgba(92,62,26,.28);--nq-p-rule: 2px solid rgba(92,62,26,.35);--nq-p-ink: #2b1f14;--nq-p-ink-secondary: #5c452a;--nq-p-ink-muted: #6f6455;--nq-p-ink-away: #4d4433;--nq-p-ink-late: #8f1526;--nq-brand-blue: #3B3AB8;--nq-brand-purple: #A035CC;--nq-brand-gradient: linear-gradient(135deg, var(--nq-brand-blue), var(--nq-brand-purple));--nq-p-icon-tile: var(--nq-brand-gradient);--nq-p-icon-tile-done: rgba(92,62,26,.16);--nq-p-crest: var(--nq-brand-gradient);--nq-p-crest-away: linear-gradient(135deg, #6b6b7a, #7a7286);--nq-p-seal: radial-gradient(circle at 35% 30%, #a8283a, #6d1322);--nq-p-seal-shadow: 0 4px 10px rgba(60,10,20,.4), inset 0 0 0 4px rgba(255,255,255,.14);--nq-p-seal-size: 78px;--nq-p-dock-bg: rgba(43,31,20,.9);--nq-p-dock-ink: #f7efdb;--nq-p-dock-ink-secondary: #d8c9a6;--nq-p-dock-divider: rgba(233,220,189,.3);--nq-p-dock-height: 84px;--nq-p-font-display: "Cinzel Decorative", Cinzel, serif;--nq-p-font-heading: Cinzel, serif;--nq-p-font-body: Nunito, system-ui, sans-serif;--nq-p-size-hero: 86px;--nq-p-size-wordmark: 66px;--nq-p-size-title: 56px;--nq-p-size-name: 48px;--nq-p-size-section: 32px;--nq-p-size-quest: 30px;--nq-p-size-body: 22px;--nq-p-size-label: 21px;--nq-p-track-label: .14em;--nq-p-track-kicker: .3em;--nq-p-quest-min-height: 116px;--nq-p-quest-gap: 24px;--nq-p-button-height: 72px;--nq-p-button-min-width: 150px;--nq-p-confirm-button: 108px;--nq-p-radius-card: 12px;--nq-p-radius-panel: 20px;--nq-p-radius-pill: 9999px;--nq-p-page-inset: 62px;--nq-p-frame-inset: 26px;--nq-ease-out: cubic-bezier(0, 0, .2, 1);--nq-dur-micro: .12s;--nq-dur-base: .2s;--nq-dur-modal: .35s}'; -function L(i) { - window.customCards = window.customCards ?? [], window.customCards.push(i); +v._$litElement$ = !0, v.finalized = !0, W.litElementHydrateSupport?.({ LitElement: v }); +const It = W.litElementPolyfillSupport; +It?.({ LitElement: v }); +(W.litElementVersions ??= []).push("4.2.2"); +const yt = ':host{--nq-p-parchment-top: #f0e4c7;--nq-p-parchment-bottom: #ddcca3;--nq-p-parchment: radial-gradient(ellipse at 25% 10%, rgba(255,255,255,.5), transparent 55%), radial-gradient(ellipse at 85% 90%, rgba(120,86,44,.28), transparent 60%), repeating-linear-gradient(93deg, rgba(150,115,70,.05) 0 2px, transparent 2px 6px), repeating-linear-gradient(2deg, rgba(150,115,70,.04) 0 3px, transparent 3px 7px), linear-gradient(var(--nq-p-parchment-top), var(--nq-p-parchment-bottom));--nq-p-vignette: inset 0 0 200px rgba(80,52,20,.35);--nq-p-card-open: linear-gradient(#fdf7e6, #f2e7c9);--nq-p-card-done: linear-gradient(#f0e8d3, #e6dcc2);--nq-p-card-panel: linear-gradient(#fcf6e6, #f1e6ca);--nq-p-card-border: rgba(120,88,48,.38);--nq-p-panel-border: rgba(92,62,26,.4);--nq-p-card-shadow: 0 3px 0 rgba(120,88,48,.2), inset 0 1px 0 rgba(255,255,255,.7);--nq-p-panel-shadow: 0 6px 0 rgba(92,62,26,.18), inset 0 2px 0 rgba(255,255,255,.7);--nq-p-frame-outer: 2px solid rgba(92,62,26,.45);--nq-p-frame-inner: 1px solid rgba(92,62,26,.28);--nq-p-rule: 2px solid rgba(92,62,26,.35);--nq-p-ink: #2b1f14;--nq-p-ink-secondary: #5c452a;--nq-p-ink-muted: #6f6455;--nq-p-ink-away: #4d4433;--nq-p-ink-late: #8f1526;--nq-brand-blue: #3B3AB8;--nq-brand-purple: #A035CC;--nq-brand-gradient: linear-gradient(135deg, var(--nq-brand-blue), var(--nq-brand-purple));--nq-p-icon-tile: var(--nq-brand-gradient);--nq-p-icon-tile-done: rgba(92,62,26,.16);--nq-p-crest: var(--nq-brand-gradient);--nq-p-crest-away: linear-gradient(135deg, #6b6b7a, #7a7286);--nq-p-seal: radial-gradient(circle at 35% 30%, #a8283a, #6d1322);--nq-p-seal-shadow: 0 4px 10px rgba(60,10,20,.4), inset 0 0 0 4px rgba(255,255,255,.14);--nq-p-seal-size: 78px;--nq-p-dock-bg: rgba(43,31,20,.9);--nq-p-dock-ink: #f7efdb;--nq-p-dock-ink-secondary: #d8c9a6;--nq-p-dock-divider: rgba(233,220,189,.3);--nq-p-dock-height: 84px;--nq-p-font-display: "Cinzel Decorative", Cinzel, serif;--nq-p-font-heading: Cinzel, serif;--nq-p-font-body: Nunito, system-ui, sans-serif;--nq-p-size-hero: 86px;--nq-p-size-wordmark: 66px;--nq-p-size-title: 56px;--nq-p-size-name: 48px;--nq-p-size-section: 32px;--nq-p-size-quest: 30px;--nq-p-size-body: 22px;--nq-p-size-label: 21px;--nq-p-track-label: .14em;--nq-p-track-kicker: .3em;--nq-p-quest-min-height: 116px;--nq-p-quest-gap: 24px;--nq-p-button-height: 72px;--nq-p-button-min-width: 150px;--nq-p-confirm-button: 108px;--nq-p-radius-card: 12px;--nq-p-radius-panel: 20px;--nq-p-radius-pill: 9999px;--nq-p-page-inset: 62px;--nq-p-frame-inset: 26px;--nq-ease-out: cubic-bezier(0, 0, .2, 1);--nq-dur-micro: .12s;--nq-dur-base: .2s;--nq-dur-modal: .35s}'; +function V(s) { + window.customCards = window.customCards ?? [], window.customCards.push(s); } -class Pt extends m { +const at = /* @__PURE__ */ new Map(); +function O(s, t) { + const e = `${s ?? "local"}|${JSON.stringify(t)}`, n = at.get(e); + if (n) + return n; + let i; + try { + i = new Intl.DateTimeFormat("en-US", { + ...t, + ...s ? { timeZone: s } : {} + }); + } catch { + i = new Intl.DateTimeFormat("en-US", t); + } + return at.set(e, i), i; +} +function Ft(s) { + const t = s?.config; + if (!t || typeof t != "object") + return; + const e = t.time_zone; + return typeof e == "string" && e.trim() ? e.trim() : void 0; +} +function R(s, t) { + return `${O(t, { weekday: "long" }).format(s)}, ${O(t, { month: "short", day: "numeric" }).format(s)}`; +} +function Lt(s, t) { + return O(t, { + hour: "numeric", + minute: "2-digit", + hour12: !0 + }).format(s); +} +const jt = { + "clear-night": "Clear", + cloudy: "Cloudy", + exceptional: "Clear", + fog: "Foggy", + hail: "Hail", + lightning: "Storms", + "lightning-rimmed": "Storms", + partlycloudy: "Partly cloudy", + pouring: "Heavy rain", + rainy: "Rain", + snowy: "Snow", + "snowy-rainy": "Sleet", + sunny: "Sunny", + windy: "Windy", + "windy-variant": "Windy" +}, Bt = { + "clear-night": "Clear night skies", + cloudy: "Grey skies today", + exceptional: "A striking day", + fog: "Mist on the road", + hail: "Ice from the sky", + lightning: "Storms may roll in", + "lightning-rimmed": "Storms may roll in", + partlycloudy: "Sun between clouds", + pouring: "Heavy rain outside", + rainy: "Rain on the walls", + snowy: "Snow on the peaks", + "snowy-rainy": "Sleet may fall", + sunny: "Clear skies today", + windy: "A blustery day", + "windy-variant": "A blustery day" +}; +function ot(s) { + return typeof s == "string" ? s.trim() : ""; +} +function I(s, t) { + if (s == null || s === "") + return t; + const e = typeof s == "number" ? s : Number(s); + return Number.isFinite(e) ? e : t; +} +function lt(s) { + return Number.isFinite(s) ? Math.min(100, Math.max(0, s)) : 0; +} +function pt(s) { + return Math.max(0, Math.round(s)); +} +function Qt(s) { + return s.split(/[-_]+/).filter(Boolean).map((t) => t.charAt(0).toUpperCase() + t.slice(1)).join(" "); +} +function Wt(s, t) { + const e = O(t, { + year: "numeric", + month: "2-digit", + day: "2-digit", + hour: "2-digit", + minute: "2-digit", + second: "2-digit", + hourCycle: "h23" + }).formatToParts(new Date(s)), n = (r) => { + const a = e.find((l) => l.type === r); + return a ? Number(a.value) : Number.NaN; + }, i = Date.UTC( + n("year"), + n("month") - 1, + n("day"), + n("hour"), + n("minute"), + n("second") + ); + return Number.isFinite(i) ? i - s : 0; +} +function Vt(s, t) { + if (typeof s != "string") + return null; + const e = s.split("-"); + if (e.length !== 3) + return null; + const [n, i, r] = e.map((o) => Number(o)); + if (!n || !i || !r) + return null; + if (!t) { + const o = new Date(n, i - 1, r); + return Number.isNaN(o.getTime()) ? null : o; + } + const a = Date.UTC(n, i - 1, r), l = new Date(a - Wt(a, t)); + return Number.isNaN(l.getTime()) ? null : l; +} +function Zt(s) { + const t = jt[s]; + return t || s.charAt(0).toUpperCase() + s.slice(1); +} +function Kt(s, t) { + return Bt[s] ?? t; +} +const dt = "polygon(0 0, 100% 0, 100% 62%, 50% 100%, 0 62%)", Jt = "polygon(50% 0, 93% 25%, 93% 75%, 50% 100%, 7% 75%, 7% 25%)", Gt = _t` + * { + box-sizing: border-box; + } + + :host { + display: block; + height: 100%; + --nq-brand-gradient-h: linear-gradient( + 90deg, + var(--nq-brand-blue), + var(--nq-brand-purple) + ); + } + + .board { + position: relative; + display: flex; + flex-direction: column; + align-items: center; + height: 1080px; + padding: 0; + overflow: hidden; + background: var(--nq-p-parchment); + box-shadow: var(--nq-p-vignette); + font-family: var(--nq-p-font-body); + } + + .frame { + position: absolute; + pointer-events: none; + } + + .frame-outer { + inset: 26px; + border: var(--nq-p-frame-outer); + border-radius: 14px; + } + + .frame-inner { + inset: 36px; + border: var(--nq-p-frame-inner); + border-radius: 8px; + } + + .wordmark-row { + position: absolute; + top: 64px; + left: 0; + right: 0; + display: flex; + align-items: center; + justify-content: center; + gap: 26px; + width: 100%; + } + + .rule { + flex: 1 1 0; + max-width: 340px; + height: 2px; + border-radius: 1px; + background: rgba(92, 62, 26, 0.35); + } + + .d20 { + flex: none; + display: inline-flex; + align-items: center; + justify-content: center; + width: 34px; + height: 34px; + clip-path: ${$(Jt)}; + background: var(--nq-brand-gradient); + } + + .d20-numeral { + font-family: var(--nq-p-font-heading); + font-size: 15px; + font-weight: 700; + line-height: 1; + color: #ffffff; + } + + .wordmark { + font-family: var(--nq-p-font-display); + font-size: var(--nq-p-size-wordmark); + font-weight: 900; + line-height: 1.15; + background: var(--nq-brand-gradient); + -webkit-background-clip: text; + background-clip: text; + color: transparent; + -webkit-text-fill-color: transparent; + } + + .kicker { + position: absolute; + top: 154px; + left: 0; + right: 0; + font-family: var(--nq-p-font-heading); + font-size: 22px; + font-weight: 600; + letter-spacing: var(--nq-p-track-kicker); + text-transform: uppercase; + line-height: 1.2; + text-align: center; + color: var(--nq-p-ink-secondary); + } + + .plates { + position: absolute; + top: 262px; + left: 110px; + right: 110px; + display: grid; + grid-template-columns: repeat(3, 1fr); + gap: 56px; + } + + .plate { + position: relative; + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + 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); + box-shadow: var(--nq-p-panel-shadow); + font: inherit; + } + + button.plate { + width: 100%; + color: inherit; + cursor: pointer; + -webkit-tap-highlight-color: transparent; + transition: transform var(--nq-dur-micro) var(--nq-ease-out); + } + + button.plate:active { + transform: scale(0.98); + } + + button.plate:focus-visible { + outline: 3px solid var(--nq-brand-purple); + outline-offset: 4px; + } + + .plate.away { + border: 2px dashed var(--nq-p-panel-border); + background: linear-gradient(#f2ecdd, #e6dcc6); + box-shadow: none; + } + + .crest { + position: relative; + flex: none; + width: 188px; + height: 214px; + padding: 4px; + clip-path: ${$(dt)}; + background: rgba(255, 255, 255, 0.22); + } + + .crest-face { + width: 100%; + height: 100%; + display: flex; + align-items: center; + justify-content: center; + padding-bottom: 70px; + clip-path: ${$(dt)}; + background: var(--nq-p-crest); + } + + .crest.away .crest-face { + background: var(--nq-p-crest-away); + } + + .crest .initial { + font-family: var(--nq-p-font-display); + font-size: 82px; + font-weight: 700; + line-height: 1; + color: #ffffff; + } + + .name { + font-family: var(--nq-p-font-heading); + font-size: var(--nq-p-size-name); + font-weight: 700; + letter-spacing: 0.02em; + line-height: 1.1; + color: var(--nq-p-ink); + } + + .plate.away .name { + color: var(--nq-p-ink-away); + } + + .pill { + display: inline-flex; + align-items: center; + gap: 10px; + padding: 8px 20px; + border-radius: var(--nq-p-radius-pill); + background: rgba(22, 120, 60, 0.35); + font-family: var(--nq-p-font-heading); + font-size: 22px; + font-weight: 600; + letter-spacing: 0.08em; + text-transform: uppercase; + line-height: 1; + color: #146b36; + } + + .pill svg { + width: 22px; + height: 22px; + color: #157a3c; + } + + .pill.away { + background: rgba(92, 62, 26, 0.16); + color: var(--nq-p-ink-away); + } + + .pill.away svg { + color: var(--nq-p-ink-away); + } + + .progress-line { + font-family: var(--nq-p-font-heading); + font-size: 24px; + font-weight: 700; + line-height: 1.2; + color: var(--nq-p-ink-secondary); + } + + .bar { + width: 100%; + height: 16px; + border: 1px solid rgba(92, 62, 26, 0.35); + border-radius: var(--nq-p-radius-pill); + background: rgba(92, 62, 26, 0.18); + overflow: hidden; + } + + .bar .fill { + display: block; + height: 100%; + border-radius: inherit; + background: var(--nq-brand-gradient-h); + } + + .hint { + position: absolute; + top: 930px; + left: 0; + right: 0; + font-family: var(--nq-p-font-body); + font-size: 24px; + font-weight: 600; + line-height: 1.4; + text-align: center; + color: var(--nq-p-ink-secondary); + } + + .spacer { + flex: 1 1 0; + } + + .dock { + position: absolute; + left: 46px; + right: 46px; + bottom: 46px; + height: var(--nq-p-dock-height); + display: flex; + align-items: center; + justify-content: center; + gap: 24px; + padding: 0 40px; + border-radius: 14px; + background: var(--nq-p-dock-bg); + pointer-events: none; + user-select: none; + color: var(--nq-p-dock-ink); + } + + .dock svg { + flex: none; + width: 40px; + height: 40px; + color: var(--nq-p-dock-ink); + } + + .dock .divider { + flex: none; + width: 1px; + height: 40px; + background: var(--nq-p-dock-divider); + } + + .dock .temp { + font-family: var(--nq-p-font-body); + font-size: 38px; + font-weight: 800; + line-height: 1; + } + + .dock .condition { + font-family: var(--nq-p-font-body); + font-size: 26px; + font-weight: 600; + line-height: 1.2; + color: var(--nq-p-dock-ink-secondary); + } + + .dock .date { + font-family: var(--nq-p-font-body); + font-size: 26px; + font-weight: 600; + line-height: 1.2; + color: var(--nq-p-dock-ink-secondary); + } + + .dock .clock { + font-family: var(--nq-p-font-body); + font-size: 38px; + font-weight: 800; + line-height: 1; + } +`, Xt = g``, Yt = g``, te = g``; +class ee extends v { static properties = { hass: { attribute: !1 }, - _config: { state: !0 } + _config: { state: !0 }, + _now: { state: !0 } }; - static styles = [U(at)]; + static styles = [$(yt), Gt]; hass; _config; + _now = /* @__PURE__ */ new Date(); + _clockTimer; setConfig(t) { if (!t || typeof t != "object") throw new Error("Invalid configuration"); this._config = t; } getCardSize() { - return 6; + return 22; + } + connectedCallback() { + super.connectedCallback(), this._now = /* @__PURE__ */ new Date(), this._clockTimer = window.setInterval(() => { + this._now = /* @__PURE__ */ new Date(); + }, 1e3); + } + disconnectedCallback() { + this._clockTimer !== void 0 && (window.clearInterval(this._clockTimer), this._clockTimer = void 0), super.disconnectedCallback(); } render() { - return B`
    NestQuest Party Board
    `; + return g` +
    +
    +
    +
    + + + NestQuest + + +
    +

    The Party · ${R(this._now, this._timeZone())}

    +
    + ${this._plates().map((t) => this._renderPlate(t))} +
    +

    Tap your crest to open your Quest Log

    +
    + ${this._renderDock()} +
    + `; + } + _timeZone() { + return Ft(this.hass); + } + _state(t) { + const n = this.hass?.states?.[t]; + return n && typeof n == "object" ? n : null; + } + _childSlugs() { + if (!this._config) + return []; + const t = this._config.child_order; + return Array.isArray(t) ? t.filter( + (e) => typeof e == "string" && e.trim().length > 0 + ).map((e) => e.trim()) : []; + } + _plates() { + return this._childSlugs().map((t) => this._plate(t)); + } + _plate(t) { + const e = this._state(`sensor.nestquest_${t}_quests_due_today`), n = this._state( + `sensor.nestquest_${t}_quests_completed_today` + ), i = this._state( + `sensor.nestquest_${t}_completion_pct_today` + ), r = this._state( + `binary_sensor.nestquest_${t}_present_today` + ), a = e?.attributes ?? {}, l = pt(I(e?.state, 0)), o = pt(I(n?.state, 0)), d = i ? I(i.state, Number.NaN) : Number.NaN, h = Number.isFinite(d) ? lt(d) : l > 0 ? lt(o / l * 100) : 0; + let p = !0; + const f = a.present; + typeof f == "boolean" ? p = f : r && (p = String(r.state ?? "").trim().toLowerCase() === "on"); + let u = ""; + const m = a.child_name; + if (typeof m == "string" && m.trim()) + u = m.trim(); + else { + const H = r?.attributes?.child_name; + typeof H == "string" && H.trim() && (u = H.trim()); + } + u || (u = Qt(t)); + const Z = Vt( + r?.attributes?.next_present, + this._timeZone() + ), bt = Z ? `Returns ${R(Z, this._timeZone())}` : null; + return { + slug: t, + name: u, + initial: (u.charAt(0) || "?").toUpperCase(), + present: p, + completed: o, + due: l, + pct: h, + returns: bt + }; + } + _renderPlate(t) { + return t.present ? g` + + ` : g` +
    + + + ${t.initial} + + + ${t.name} + + ${Yt} + On travels + + ${t.returns ?? "Returns —"} + +
    + `; + } + _openQuestLog(t) { + const e = ot(this._config?.quest_log_path); + e && (window.history.pushState(null, "", `${e.replace(/\/+$/, "")}/${t}`), window.dispatchEvent(new Event("location-changed"))); + } + _dockWeather() { + const t = ot(this._config?.weather_entity); + if (!t) + return null; + const e = this._state(t); + if (!e) + return null; + const n = String(e.state ?? "").trim(); + if (!n || n === "unavailable" || n === "unknown") + return null; + const i = e.attributes ?? {}; + let r = null, a = null; + const l = i.forecast; + if (Array.isArray(l) && l.length > 0) { + const o = l[0]; + if (o && typeof o == "object") { + const d = o; + r = this._optionalNumber(d.temperature), a = this._optionalNumber(d.templow); + } + } + return { + condition: n, + temperature: this._optionalNumber(i.temperature), + high: r, + low: a + }; + } + _optionalNumber(t) { + if (t == null || t === "") + return null; + const e = typeof t == "number" ? t : Number(t); + return Number.isFinite(e) ? e : null; + } + _renderDock() { + const t = this._dockWeather(); + if (!t) + return g` +
    + ${R(this._now, this._timeZone())} + + ${Lt(this._now, this._timeZone())} +
    + `; + const e = Zt(t.condition), n = Kt(t.condition, e), i = t.temperature === null ? c : g`${Math.round(t.temperature)}°`, r = t.high === null || t.low === null ? null : `${Math.round(t.high)}° / ${Math.round(t.low)}°`; + return g` +
    + ${te} + ${i} + + + ${r === null ? e : `${e} · ${r}`} + + + ${n} +
    + `; } } -customElements.define("nestquest-party-board-card", Pt); -L({ +customElements.define("nestquest-party-board-card", ee); +V({ type: "nestquest-party-board-card", name: "NestQuest Party Board", description: "Kids' attract screen for present and away adventurers." }); -class kt extends m { +class ne extends v { static properties = { hass: { attribute: !1 }, _config: { state: !0 } }; - static styles = [U(at)]; + static styles = [$(yt)]; hass; _config; setConfig(t) { @@ -539,22 +1242,22 @@ class kt extends m { return 8; } render() { - return B`
    NestQuest Quest Log
    `; + return g`
    NestQuest Quest Log
    `; } } -customElements.define("nestquest-quest-log-card", kt); -L({ +customElements.define("nestquest-quest-log-card", ne); +V({ type: "nestquest-quest-log-card", name: "NestQuest Quest Log", description: "One child's daily quests, grouped by window." }); -const Nt = ":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 Ut extends m { +const se = ":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 ie extends v { static properties = { hass: { attribute: !1 }, _config: { state: !0 } }; - static styles = [U(Nt)]; + static styles = [$(se)]; hass; _config; setConfig(t) { @@ -566,17 +1269,17 @@ class Ut extends m { return 8; } render() { - return B`
    NestQuest Admin
    `; + return g`
    NestQuest Admin
    `; } } -customElements.define("nestquest-admin-card", Ut); -L({ +customElements.define("nestquest-admin-card", ie); +V({ type: "nestquest-admin-card", name: "NestQuest Admin", description: "Parent phone view for today, tasks, schedule, and history." }); -const zt = "/nestquest-static/nestquest-fonts.css"; +const re = "/nestquest-static/nestquest-fonts.css"; if (!document.querySelector('link[data-nq-fonts=""]')) { - const i = document.createElement("link"); - i.rel = "stylesheet", i.href = zt, i.dataset.nqFonts = "", document.head.appendChild(i); + const s = document.createElement("link"); + s.rel = "stylesheet", s.href = re, s.dataset.nqFonts = "", document.head.appendChild(s); } diff --git a/frontend/src/cards/party-board.ts b/frontend/src/cards/party-board.ts index e6fbb38..891c368 100644 --- a/frontend/src/cards/party-board.ts +++ b/frontend/src/cards/party-board.ts @@ -1,18 +1,604 @@ -import { LitElement, html, unsafeCSS } from "lit"; +import { LitElement, css, html, nothing, unsafeCSS } from "lit"; import panelTokens from "../../../custom_components/nestquest/www/nestquest-panel-tokens.css?inline"; import { registerCustomCard, type CardConfig, type HassLike } from "../types"; +type StateObject = { + state: unknown; + attributes?: Record | null; +}; + +type ChildPlate = { + slug: string; + name: string; + initial: string; + present: boolean; + completed: number; + due: number; + pct: number; + returns: string | null; +}; + +type DockWeather = { + condition: string; + temperature: number | null; + high: number | null; + low: number | null; +}; + +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", + cloudy: "Cloudy", + exceptional: "Clear", + fog: "Foggy", + hail: "Hail", + lightning: "Storms", + "lightning-rimmed": "Storms", + partlycloudy: "Partly cloudy", + pouring: "Heavy rain", + rainy: "Rain", + snowy: "Snow", + "snowy-rainy": "Sleet", + sunny: "Sunny", + windy: "Windy", + "windy-variant": "Windy", +}; + +const CONDITION_PHRASES: Record = { + "clear-night": "Clear night skies", + cloudy: "Grey skies today", + exceptional: "A striking day", + fog: "Mist on the road", + hail: "Ice from the sky", + lightning: "Storms may roll in", + "lightning-rimmed": "Storms may roll in", + partlycloudy: "Sun between clouds", + pouring: "Heavy rain outside", + rainy: "Rain on the walls", + snowy: "Snow on the peaks", + "snowy-rainy": "Sleet may fall", + sunny: "Clear skies today", + windy: "A blustery day", + "windy-variant": "A blustery day", +}; + +function asString(value: unknown): string { + return typeof value === "string" ? value.trim() : ""; +} + +function asNumber(value: unknown, fallback: number): number { + if (value === null || value === undefined || value === "") { + return fallback; + } + const parsed = typeof value === "number" ? value : Number(value); + return Number.isFinite(parsed) ? parsed : fallback; +} + +function clampPercent(value: number): number { + if (!Number.isFinite(value)) { + return 0; + } + return Math.min(100, Math.max(0, value)); +} + +function clampCount(value: number): number { + return Math.max(0, Math.round(value)); +} + +function titleCaseSlug(slug: string): string { + return slug + .split(/[-_]+/) + .filter(Boolean) + .map((word) => word.charAt(0).toUpperCase() + word.slice(1)) + .join(" "); +} + +function zoneOffsetMs(instant: number, timeZone: string): number { + const parts = zonedFormatter(timeZone, { + year: "numeric", + month: "2-digit", + day: "2-digit", + hour: "2-digit", + minute: "2-digit", + second: "2-digit", + hourCycle: "h23", + }).formatToParts(new Date(instant)); + const read = (type: Intl.DateTimeFormatPartTypes): number => { + const part = parts.find((entry) => entry.type === type); + return part ? Number(part.value) : Number.NaN; + }; + const asUtc = Date.UTC( + read("year"), + read("month") - 1, + read("day"), + read("hour"), + read("minute"), + read("second") + ); + return Number.isFinite(asUtc) ? asUtc - instant : 0; +} + +function parseIsoDate(value: unknown, timeZone?: string): Date | null { + if (typeof value !== "string") { + return null; + } + const parts = value.split("-"); + if (parts.length !== 3) { + return null; + } + const [year, month, day] = parts.map((part) => Number(part)); + if (!year || !month || !day) { + return null; + } + if (!timeZone) { + const local = new Date(year, month - 1, day); + return Number.isNaN(local.getTime()) ? null : local; + } + const utcMs = Date.UTC(year, month - 1, day); + const date = new Date(utcMs - zoneOffsetMs(utcMs, timeZone)); + return Number.isNaN(date.getTime()) ? null : date; +} + +function conditionLabel(condition: string): string { + const known = CONDITION_LABELS[condition]; + if (known) { + return known; + } + return condition.charAt(0).toUpperCase() + condition.slice(1); +} + +function conditionPhrase(condition: string, label: string): string { + return CONDITION_PHRASES[condition] ?? label; +} + +const SHIELD_CLIP = "polygon(0 0, 100% 0, 100% 62%, 50% 100%, 0 62%)"; +const HEXAGON_CLIP = + "polygon(50% 0, 93% 25%, 93% 75%, 50% 100%, 7% 75%, 7% 25%)"; + +const boardStyles = css` + * { + box-sizing: border-box; + } + + :host { + display: block; + height: 100%; + --nq-brand-gradient-h: linear-gradient( + 90deg, + var(--nq-brand-blue), + var(--nq-brand-purple) + ); + } + + .board { + position: relative; + display: flex; + flex-direction: column; + align-items: center; + height: 1080px; + padding: 0; + overflow: hidden; + background: var(--nq-p-parchment); + box-shadow: var(--nq-p-vignette); + font-family: var(--nq-p-font-body); + } + + .frame { + position: absolute; + pointer-events: none; + } + + .frame-outer { + inset: 26px; + border: var(--nq-p-frame-outer); + border-radius: 14px; + } + + .frame-inner { + inset: 36px; + border: var(--nq-p-frame-inner); + border-radius: 8px; + } + + .wordmark-row { + position: absolute; + top: 64px; + left: 0; + right: 0; + display: flex; + align-items: center; + justify-content: center; + gap: 26px; + width: 100%; + } + + .rule { + flex: 1 1 0; + max-width: 340px; + height: 2px; + border-radius: 1px; + background: rgba(92, 62, 26, 0.35); + } + + .d20 { + flex: none; + display: inline-flex; + align-items: center; + justify-content: center; + width: 34px; + height: 34px; + clip-path: ${unsafeCSS(HEXAGON_CLIP)}; + background: var(--nq-brand-gradient); + } + + .d20-numeral { + font-family: var(--nq-p-font-heading); + font-size: 15px; + font-weight: 700; + line-height: 1; + color: #ffffff; + } + + .wordmark { + font-family: var(--nq-p-font-display); + font-size: var(--nq-p-size-wordmark); + font-weight: 900; + line-height: 1.15; + background: var(--nq-brand-gradient); + -webkit-background-clip: text; + background-clip: text; + color: transparent; + -webkit-text-fill-color: transparent; + } + + .kicker { + position: absolute; + top: 154px; + left: 0; + right: 0; + font-family: var(--nq-p-font-heading); + font-size: 22px; + font-weight: 600; + letter-spacing: var(--nq-p-track-kicker); + text-transform: uppercase; + line-height: 1.2; + text-align: center; + color: var(--nq-p-ink-secondary); + } + + .plates { + position: absolute; + top: 262px; + left: 110px; + right: 110px; + display: grid; + grid-template-columns: repeat(3, 1fr); + gap: 56px; + } + + .plate { + position: relative; + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + 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); + box-shadow: var(--nq-p-panel-shadow); + font: inherit; + } + + button.plate { + width: 100%; + color: inherit; + cursor: pointer; + -webkit-tap-highlight-color: transparent; + transition: transform var(--nq-dur-micro) var(--nq-ease-out); + } + + button.plate:active { + transform: scale(0.98); + } + + button.plate:focus-visible { + outline: 3px solid var(--nq-brand-purple); + outline-offset: 4px; + } + + .plate.away { + border: 2px dashed var(--nq-p-panel-border); + background: linear-gradient(#f2ecdd, #e6dcc6); + box-shadow: none; + } + + .crest { + position: relative; + flex: none; + width: 188px; + height: 214px; + padding: 4px; + clip-path: ${unsafeCSS(SHIELD_CLIP)}; + background: rgba(255, 255, 255, 0.22); + } + + .crest-face { + width: 100%; + height: 100%; + display: flex; + align-items: center; + justify-content: center; + padding-bottom: 70px; + clip-path: ${unsafeCSS(SHIELD_CLIP)}; + background: var(--nq-p-crest); + } + + .crest.away .crest-face { + background: var(--nq-p-crest-away); + } + + .crest .initial { + font-family: var(--nq-p-font-display); + font-size: 82px; + font-weight: 700; + line-height: 1; + color: #ffffff; + } + + .name { + font-family: var(--nq-p-font-heading); + font-size: var(--nq-p-size-name); + font-weight: 700; + letter-spacing: 0.02em; + line-height: 1.1; + color: var(--nq-p-ink); + } + + .plate.away .name { + color: var(--nq-p-ink-away); + } + + .pill { + display: inline-flex; + align-items: center; + gap: 10px; + padding: 8px 20px; + border-radius: var(--nq-p-radius-pill); + background: rgba(22, 120, 60, 0.35); + font-family: var(--nq-p-font-heading); + font-size: 22px; + font-weight: 600; + letter-spacing: 0.08em; + text-transform: uppercase; + line-height: 1; + color: #146b36; + } + + .pill svg { + width: 22px; + height: 22px; + color: #157a3c; + } + + .pill.away { + background: rgba(92, 62, 26, 0.16); + color: var(--nq-p-ink-away); + } + + .pill.away svg { + color: var(--nq-p-ink-away); + } + + .progress-line { + font-family: var(--nq-p-font-heading); + font-size: 24px; + font-weight: 700; + line-height: 1.2; + color: var(--nq-p-ink-secondary); + } + + .bar { + width: 100%; + height: 16px; + border: 1px solid rgba(92, 62, 26, 0.35); + border-radius: var(--nq-p-radius-pill); + background: rgba(92, 62, 26, 0.18); + overflow: hidden; + } + + .bar .fill { + display: block; + height: 100%; + border-radius: inherit; + background: var(--nq-brand-gradient-h); + } + + .hint { + position: absolute; + top: 930px; + left: 0; + right: 0; + font-family: var(--nq-p-font-body); + font-size: 24px; + font-weight: 600; + line-height: 1.4; + text-align: center; + color: var(--nq-p-ink-secondary); + } + + .spacer { + flex: 1 1 0; + } + + .dock { + position: absolute; + left: 46px; + right: 46px; + bottom: 46px; + height: var(--nq-p-dock-height); + display: flex; + align-items: center; + justify-content: center; + gap: 24px; + padding: 0 40px; + border-radius: 14px; + background: var(--nq-p-dock-bg); + pointer-events: none; + user-select: none; + color: var(--nq-p-dock-ink); + } + + .dock svg { + flex: none; + width: 40px; + height: 40px; + color: var(--nq-p-dock-ink); + } + + .dock .divider { + flex: none; + width: 1px; + height: 40px; + background: var(--nq-p-dock-divider); + } + + .dock .temp { + font-family: var(--nq-p-font-body); + font-size: 38px; + font-weight: 800; + line-height: 1; + } + + .dock .condition { + font-family: var(--nq-p-font-body); + font-size: 26px; + font-weight: 600; + line-height: 1.2; + color: var(--nq-p-dock-ink-secondary); + } + + .dock .date { + font-family: var(--nq-p-font-body); + font-size: 26px; + font-weight: 600; + line-height: 1.2; + color: var(--nq-p-dock-ink-secondary); + } + + .dock .clock { + font-family: var(--nq-p-font-body); + font-size: 38px; + font-weight: 800; + line-height: 1; + } +`; + +const ICON_CHECK_CIRCLE = html``; + +const ICON_TENT = html``; + +const ICON_CLOUD_SUN = html``; + export class NestQuestPartyBoardCard extends LitElement { static properties = { hass: { attribute: false }, _config: { state: true }, + _now: { state: true }, }; - static styles = [unsafeCSS(panelTokens)]; + static styles = [unsafeCSS(panelTokens), boardStyles]; hass?: HassLike; _config?: CardConfig; + _now = new Date(); + private _clockTimer?: number; setConfig(config: CardConfig): void { if (!config || typeof config !== "object") { @@ -22,11 +608,279 @@ export class NestQuestPartyBoardCard extends LitElement { } getCardSize(): number { - return 6; + return 22; + } + + connectedCallback(): void { + super.connectedCallback(); + this._now = new Date(); + this._clockTimer = window.setInterval(() => { + this._now = new Date(); + }, 1000); + } + + disconnectedCallback(): void { + if (this._clockTimer !== undefined) { + window.clearInterval(this._clockTimer); + this._clockTimer = undefined; + } + super.disconnectedCallback(); } render() { - return html`
    NestQuest Party Board
    `; + return html` +
    +
    +
    +
    + + + NestQuest + + +
    +

    The Party · ${formatDay(this._now, this._timeZone())}

    +
    + ${this._plates().map((plate) => this._renderPlate(plate))} +
    +

    Tap your crest to open your Quest Log

    +
    + ${this._renderDock()} +
    + `; + } + + private _timeZone(): string | undefined { + return hassTimeZone(this.hass); + } + + private _state(entityId: string): StateObject | null { + const states = this.hass?.states as + | Record + | undefined; + const stateObj = states?.[entityId]; + return stateObj && typeof stateObj === "object" ? stateObj : null; + } + + private _childSlugs(): string[] { + if (!this._config) { + return []; + } + const order = this._config.child_order; + if (!Array.isArray(order)) { + return []; + } + return order + .filter( + (entry): entry is string => + typeof entry === "string" && entry.trim().length > 0 + ) + .map((entry) => entry.trim()); + } + + private _plates(): ChildPlate[] { + return this._childSlugs().map((slug) => this._plate(slug)); + } + + private _plate(slug: string): ChildPlate { + const dueSensor = this._state(`sensor.nestquest_${slug}_quests_due_today`); + const doneSensor = this._state( + `sensor.nestquest_${slug}_quests_completed_today` + ); + const pctSensor = this._state( + `sensor.nestquest_${slug}_completion_pct_today` + ); + const presence = this._state( + `binary_sensor.nestquest_${slug}_present_today` + ); + const dueAttrs = dueSensor?.attributes ?? {}; + + const due = clampCount(asNumber(dueSensor?.state, 0)); + const completed = clampCount(asNumber(doneSensor?.state, 0)); + const rawPct = pctSensor + ? asNumber(pctSensor.state, Number.NaN) + : Number.NaN; + const pct = Number.isFinite(rawPct) + ? clampPercent(rawPct) + : due > 0 + ? clampPercent((completed / due) * 100) + : 0; + + let present = true; + const presentAttr = dueAttrs.present; + if (typeof presentAttr === "boolean") { + present = presentAttr; + } else if (presence) { + present = String(presence.state ?? "").trim().toLowerCase() === "on"; + } + + let name = ""; + const dueName = dueAttrs.child_name; + if (typeof dueName === "string" && dueName.trim()) { + name = dueName.trim(); + } else { + const presenceName = presence?.attributes?.child_name; + if (typeof presenceName === "string" && presenceName.trim()) { + name = presenceName.trim(); + } + } + if (!name) { + name = titleCaseSlug(slug); + } + + const returnsDate = parseIsoDate( + presence?.attributes?.next_present, + this._timeZone() + ); + const returns = returnsDate + ? `Returns ${formatDay(returnsDate, this._timeZone())}` + : null; + + return { + slug, + name, + initial: (name.charAt(0) || "?").toUpperCase(), + present, + completed, + due, + pct, + returns, + }; + } + + private _renderPlate(plate: ChildPlate) { + if (plate.present) { + return html` + + `; + } + return html` +
    + + + ${plate.initial} + + + ${plate.name} + + ${ICON_TENT} + On travels + + ${plate.returns ?? "Returns —"} + +
    + `; + } + + private _openQuestLog(slug: string): void { + const path = asString(this._config?.quest_log_path); + if (!path) { + return; + } + window.history.pushState(null, "", `${path.replace(/\/+$/, "")}/${slug}`); + window.dispatchEvent(new Event("location-changed")); + } + + private _dockWeather(): DockWeather | null { + const entityId = asString(this._config?.weather_entity); + if (!entityId) { + return null; + } + const stateObj = this._state(entityId); + if (!stateObj) { + return null; + } + const condition = String(stateObj.state ?? "").trim(); + if (!condition || condition === "unavailable" || condition === "unknown") { + return null; + } + const attrs = stateObj.attributes ?? {}; + let high: number | null = null; + let low: number | null = null; + const forecast = attrs.forecast; + if (Array.isArray(forecast) && forecast.length > 0) { + const entry = forecast[0]; + if (entry && typeof entry === "object") { + const typed = entry as Record; + high = this._optionalNumber(typed.temperature); + low = this._optionalNumber(typed.templow); + } + } + return { + condition, + temperature: this._optionalNumber(attrs.temperature), + high, + low, + }; + } + + private _optionalNumber(value: unknown): number | null { + if (value === null || value === undefined || value === "") { + return null; + } + const parsed = typeof value === "number" ? value : Number(value); + return Number.isFinite(parsed) ? parsed : null; + } + + private _renderDock() { + const weather = this._dockWeather(); + if (!weather) { + return html` +
    + ${formatDay(this._now, this._timeZone())} + + ${formatTime(this._now, this._timeZone())} +
    + `; + } + const label = conditionLabel(weather.condition); + const phrase = conditionPhrase(weather.condition, label); + const temperature = + weather.temperature === null + ? nothing + : html`${Math.round(weather.temperature)}°`; + const hiLo = + weather.high === null || weather.low === null + ? null + : `${Math.round(weather.high)}° / ${Math.round(weather.low)}°`; + return html` +
    + ${ICON_CLOUD_SUN} + ${temperature} + + + ${hiLo === null ? label : `${label} · ${hiLo}`} + + + ${phrase} +
    + `; } } diff --git a/tests/test_binary_sensors.py b/tests/test_binary_sensors.py index 2c31abf..a4a31e2 100644 --- a/tests/test_binary_sensors.py +++ b/tests/test_binary_sensors.py @@ -6,7 +6,7 @@ import datetime from conftest import wire_entry_to_registry from custom_components.nestquest import async_setup_entry -from custom_components.nestquest.children import create_child +from custom_components.nestquest.children import create_child, list_children from custom_components.nestquest.const import DOMAIN from custom_components.nestquest.dao_presence import PresenceSchedulesDao from custom_components.nestquest.materialize import materialize @@ -128,7 +128,57 @@ async def test_binary_sensor_identity_and_attributes(hass, make_entry) -> None: assert present.name == "NestQuest Ada present today" for entity in (all_done, present): assert entity.device_info.name == "NestQuest Ada" - assert entity.extra_state_attributes == { + assert all_done.extra_state_attributes == { "child_id": ada.id, "child_name": "Ada", } + assert present.extra_state_attributes == { + "child_id": ada.id, + "child_name": "Ada", + "next_present": None, + } + + +async def test_present_today_carries_next_present_when_away( + hass, make_entry +) -> None: + """A child away today carries the ISO date of their next present + day (the panel's away plate renders ``Returns , `` + from it); an always-absent pattern carries ``None``.""" + entry = await _setup_entry(hass, make_entry) + coordinator = entry.runtime_data.coordinator + database = entry.runtime_data.database + ada, _bo = await _seed_two_children(database) + await create_child(database, "Cory") + today = datetime.date.today() + pattern = ",".join( + str(day) for day in range(7) if day != today.weekday() + ) + await PresenceSchedulesDao(database).upsert_by_child( + ada.id, 1, today.isoformat(), pattern + ) + await PresenceSchedulesDao(database).upsert_by_child( + await _cory_id(database), 1, today.isoformat(), "" + ) + await coordinator.async_refresh() + + assert _binary(hass, ada.id, "present_today").is_on is False + assert _binary(hass, ada.id, "present_today").extra_state_attributes[ + "next_present" + ] == (today + datetime.timedelta(days=1)).isoformat() + + cory = [ + child for child in coordinator.data.children + if child.child_name == "Cory" + ][0] + assert _binary(hass, cory.child_id, "present_today").is_on is False + assert _binary(hass, cory.child_id, "present_today").extra_state_attributes[ + "next_present" + ] is None + + +async def _cory_id(database) -> int: + for child in await list_children(database): + if child.display_name == "Cory": + return child.id + raise AssertionError("Cory was not created") diff --git a/tests/test_coordinator.py b/tests/test_coordinator.py index 745c53a..415318e 100644 --- a/tests/test_coordinator.py +++ b/tests/test_coordinator.py @@ -78,9 +78,43 @@ async def test_coordinator_snapshot_counts_and_states(hass, make_entry) -> None: assert instance.title == "Brush teeth" assert instance.window == "morning" assert ada.present is True, "no schedule means present every day" + assert ada.next_present is None, "present child has no returns date" assert snapshot.cycle_day == 0, "no schedule anywhere" +async def test_coordinator_next_present_for_away_child( + hass, make_entry +) -> None: + """A child absent today carries the ISO date of their next present + day; an always-absent pattern carries None instead.""" + entry = wire_entry_to_registry(make_entry(), hass.registry) + assert await async_setup_entry(hass, entry) is True + coordinator = entry.runtime_data.coordinator + database = entry.runtime_data.database + await create_child(database, "Ada") + ada = (await list_children(database))[0] + today = datetime.date.today() + pattern = ",".join( + str(day) for day in range(7) if day != today.weekday() + ) + await PresenceSchedulesDao(database).upsert_by_child( + ada.id, 1, today.isoformat(), pattern + ) + await create_child(database, "Bo") + bo = (await list_children(database))[1] + await PresenceSchedulesDao(database).upsert_by_child( + bo.id, 1, today.isoformat(), "" + ) + snapshot = await coordinator._async_update_data() + by_name = {child.child_name: child for child in snapshot.children} + assert by_name["Ada"].present is False + assert by_name["Ada"].next_present == ( + today + datetime.timedelta(days=1) + ).isoformat() + assert by_name["Bo"].present is False + assert by_name["Bo"].next_present is None + + async def test_coordinator_reflects_completion_and_uncompletion( hass, make_entry ) -> None: @@ -184,6 +218,37 @@ async def test_coordinator_presence_resolves_schedule_and_override( assert snapshot.children[0].present is True +async def test_coordinator_next_present_honours_future_override( + hass, make_entry +) -> None: + entry = wire_entry_to_registry(make_entry(), hass.registry) + assert await async_setup_entry(hass, entry) is True + coordinator = entry.runtime_data.coordinator + database = entry.runtime_data.database + await create_child(database, "Ada") + ada = (await list_children(database))[0] + today = datetime.date.today() + tomorrow = today + datetime.timedelta(days=1) + # Scheduled present tomorrow (single-day cycle, weekday set set below + # covers every day), but overridden absent tomorrow. + await PresenceSchedulesDao(database).upsert_by_child( + ada.id, 1, today.isoformat(), "0,1,2,3,4,5,6" + ) + from custom_components.nestquest.dao_presence import ( + PresenceOverridesDao, + ) + + await PresenceOverridesDao(database).create( + ada.id, tomorrow.isoformat(), tomorrow.isoformat(), False + ) + snapshot = await coordinator._async_update_data() + child = snapshot.children[0] + assert child.present is True + assert child.next_present != tomorrow.isoformat(), ( + "a future absent override must remove tomorrow from the preview" + ) + + async def test_coordinator_derives_missed_for_past_due_open( hass, make_entry ) -> None: From 0d2e0d6f26a5be705da4e66ce65e12b44ae74874 Mon Sep 17 00:00:00 2001 From: dev_agent <17+dev_agent@code.cubecraftcreations.com> Date: Sun, 20 Sep 2026 13:54:14 -0400 Subject: [PATCH 04/11] Add the quest log card (1b) read-only Add the quest log card (1b) read-only --- .../nestquest/www/nestquest-cards.js | 1839 ++++++++++++++--- .../nestquest/www/nestquest-panel-tokens.css | 2 + design/tokens/nestquest-panel-tokens.css | 2 + frontend/src/cards/quest-log.ts | 1417 ++++++++++++- 4 files changed, 2958 insertions(+), 302 deletions(-) diff --git a/custom_components/nestquest/www/nestquest-cards.js b/custom_components/nestquest/www/nestquest-cards.js index 747ba7f..85e4f59 100644 --- a/custom_components/nestquest/www/nestquest-cards.js +++ b/custom_components/nestquest/www/nestquest-cards.js @@ -1,15 +1,15 @@ -const T = globalThis, L = T.ShadowRoot && (T.ShadyCSS === void 0 || T.ShadyCSS.nativeShadow) && "adoptedStyleSheets" in Document.prototype && "replace" in CSSStyleSheet.prototype, j = /* @__PURE__ */ Symbol(), K = /* @__PURE__ */ new WeakMap(); -let ct = class { +const L = globalThis, Y = L.ShadowRoot && (L.ShadyCSS === void 0 || L.ShadyCSS.nativeShadow) && "adoptedStyleSheets" in Document.prototype && "replace" in CSSStyleSheet.prototype, tt = /* @__PURE__ */ Symbol(), rt = /* @__PURE__ */ new WeakMap(); +let Nt = class { constructor(t, e, n) { - if (this._$cssResult$ = !0, n !== j) throw Error("CSSResult is not constructable. Use `unsafeCSS` or `css` instead."); + if (this._$cssResult$ = !0, n !== tt) throw Error("CSSResult is not constructable. Use `unsafeCSS` or `css` instead."); this.cssText = t, this.t = e; } get styleSheet() { let t = this.o; const e = this.t; - if (L && t === void 0) { + if (Y && t === void 0) { const n = e !== void 0 && e.length === 1; - n && (t = K.get(e)), t === void 0 && ((this.o = t = new CSSStyleSheet()).replaceSync(this.cssText), n && K.set(e, t)); + n && (t = rt.get(e)), t === void 0 && ((this.o = t = new CSSStyleSheet()).replaceSync(this.cssText), n && rt.set(e, t)); } return t; } @@ -17,101 +17,101 @@ let ct = class { return this.cssText; } }; -const $ = (s) => new ct(typeof s == "string" ? s : s + "", void 0, j), _t = (s, ...t) => { - const e = s.length === 1 ? s[0] : t.reduce((n, i, r) => n + ((a) => { - if (a._$cssResult$ === !0) return a.cssText; - if (typeof a == "number") return a; - throw Error("Value passed to 'css' function must be a 'css' function result: " + a + ". Use 'unsafeCSS' to pass non-literal values, but take care to ensure page security."); - })(i) + s[r + 1], s[0]); - return new ct(e, s, j); -}, $t = (s, t) => { - if (L) s.adoptedStyleSheets = t.map((e) => e instanceof CSSStyleSheet ? e : e.styleSheet); +const b = (i) => new Nt(typeof i == "string" ? i : i + "", void 0, tt), Tt = (i, ...t) => { + const e = i.length === 1 ? i[0] : t.reduce((n, s, r) => n + ((o) => { + if (o._$cssResult$ === !0) return o.cssText; + if (typeof o == "number") return o; + throw Error("Value passed to 'css' function must be a 'css' function result: " + o + ". Use 'unsafeCSS' to pass non-literal values, but take care to ensure page security."); + })(s) + i[r + 1], i[0]); + return new Nt(e, i, tt); +}, Dt = (i, t) => { + if (Y) i.adoptedStyleSheets = t.map((e) => e instanceof CSSStyleSheet ? e : e.styleSheet); else for (const e of t) { - const n = document.createElement("style"), i = T.litNonce; - i !== void 0 && n.setAttribute("nonce", i), n.textContent = e.cssText, s.appendChild(n); + const n = document.createElement("style"), s = L.litNonce; + s !== void 0 && n.setAttribute("nonce", s), n.textContent = e.cssText, i.appendChild(n); } -}, J = L ? (s) => s : (s) => s instanceof CSSStyleSheet ? ((t) => { +}, ot = Y ? (i) => i : (i) => i instanceof CSSStyleSheet ? ((t) => { let e = ""; for (const n of t.cssRules) e += n.cssText; - return $(e); -})(s) : s; -const { is: vt, defineProperty: qt, getOwnPropertyDescriptor: xt, getOwnPropertyNames: wt, getOwnPropertySymbols: At, getPrototypeOf: kt } = Object, M = globalThis, G = M.trustedTypes, Et = G ? G.emptyScript : "", Ct = M.reactiveElementPolyfillSupport, E = (s, t) => s, F = { toAttribute(s, t) { + return b(e); +})(i) : i; +const { is: Ht, defineProperty: jt, getOwnPropertyDescriptor: Lt, getOwnPropertyNames: Rt, getOwnPropertySymbols: Bt, getPrototypeOf: Ft } = Object, F = globalThis, at = F.trustedTypes, Qt = at ? at.emptyScript : "", Wt = F.reactiveElementPolyfillSupport, U = (i, t) => i, X = { toAttribute(i, t) { switch (t) { case Boolean: - s = s ? Et : null; + i = i ? Qt : null; break; case Object: case Array: - s = s == null ? s : JSON.stringify(s); + i = i == null ? i : JSON.stringify(i); } - return s; -}, fromAttribute(s, t) { - let e = s; + return i; +}, fromAttribute(i, t) { + let e = i; switch (t) { case Boolean: - e = s !== null; + e = i !== null; break; case Number: - e = s === null ? null : Number(s); + e = i === null ? null : Number(i); break; case Object: case Array: try { - e = JSON.parse(s); + e = JSON.parse(i); } catch { e = null; } } return e; -} }, ht = (s, t) => !vt(s, t), X = { attribute: !0, type: String, converter: F, reflect: !1, useDefault: !1, hasChanged: ht }; -Symbol.metadata ??= /* @__PURE__ */ Symbol("metadata"), M.litPropertyMetadata ??= /* @__PURE__ */ new WeakMap(); -let x = class extends HTMLElement { +} }, Mt = (i, t) => !Ht(i, t), lt = { attribute: !0, type: String, converter: X, reflect: !1, useDefault: !1, hasChanged: Mt }; +Symbol.metadata ??= /* @__PURE__ */ Symbol("metadata"), F.litPropertyMetadata ??= /* @__PURE__ */ new WeakMap(); +let T = class extends HTMLElement { static addInitializer(t) { this._$Ei(), (this.l ??= []).push(t); } static get observedAttributes() { return this.finalize(), this._$Eh && [...this._$Eh.keys()]; } - static createProperty(t, e = X) { + static createProperty(t, e = lt) { if (e.state && (e.attribute = !1), this._$Ei(), this.prototype.hasOwnProperty(t) && ((e = Object.create(e)).wrapped = !0), this.elementProperties.set(t, e), !e.noAccessor) { - const n = /* @__PURE__ */ Symbol(), i = this.getPropertyDescriptor(t, n, e); - i !== void 0 && qt(this.prototype, t, i); + const n = /* @__PURE__ */ Symbol(), s = this.getPropertyDescriptor(t, n, e); + s !== void 0 && jt(this.prototype, t, s); } } static getPropertyDescriptor(t, e, n) { - const { get: i, set: r } = xt(this.prototype, t) ?? { get() { + const { get: s, set: r } = Lt(this.prototype, t) ?? { get() { return this[e]; - }, set(a) { - this[e] = a; + }, set(o) { + this[e] = o; } }; - return { get: i, set(a) { - const l = i?.call(this); - r?.call(this, a), this.requestUpdate(t, l, n); + return { get: s, set(o) { + const l = s?.call(this); + r?.call(this, o), this.requestUpdate(t, l, n); }, configurable: !0, enumerable: !0 }; } static getPropertyOptions(t) { - return this.elementProperties.get(t) ?? X; + return this.elementProperties.get(t) ?? lt; } static _$Ei() { - if (this.hasOwnProperty(E("elementProperties"))) return; - const t = kt(this); + if (this.hasOwnProperty(U("elementProperties"))) return; + const t = Ft(this); t.finalize(), t.l !== void 0 && (this.l = [...t.l]), this.elementProperties = new Map(t.elementProperties); } static finalize() { - if (this.hasOwnProperty(E("finalized"))) return; - if (this.finalized = !0, this._$Ei(), this.hasOwnProperty(E("properties"))) { - const e = this.properties, n = [...wt(e), ...At(e)]; - for (const i of n) this.createProperty(i, e[i]); + if (this.hasOwnProperty(U("finalized"))) return; + if (this.finalized = !0, this._$Ei(), this.hasOwnProperty(U("properties"))) { + const e = this.properties, n = [...Rt(e), ...Bt(e)]; + for (const s of n) this.createProperty(s, e[s]); } const t = this[Symbol.metadata]; if (t !== null) { const e = litPropertyMetadata.get(t); - if (e !== void 0) for (const [n, i] of e) this.elementProperties.set(n, i); + if (e !== void 0) for (const [n, s] of e) this.elementProperties.set(n, s); } this._$Eh = /* @__PURE__ */ new Map(); for (const [e, n] of this.elementProperties) { - const i = this._$Eu(e, n); - i !== void 0 && this._$Eh.set(i, e); + const s = this._$Eu(e, n); + s !== void 0 && this._$Eh.set(s, e); } this.elementStyles = this.finalizeStyles(this.styles); } @@ -119,8 +119,8 @@ let x = class extends HTMLElement { const e = []; if (Array.isArray(t)) { const n = new Set(t.flat(1 / 0).reverse()); - for (const i of n) e.unshift(J(i)); - } else t !== void 0 && e.push(J(t)); + for (const s of n) e.unshift(ot(s)); + } else t !== void 0 && e.push(ot(t)); return e; } static _$Eu(t, e) { @@ -146,7 +146,7 @@ let x = class extends HTMLElement { } createRenderRoot() { const t = this.shadowRoot ?? this.attachShadow(this.constructor.shadowRootOptions); - return $t(t, this.constructor.elementStyles), t; + return Dt(t, this.constructor.elementStyles), t; } connectedCallback() { this.renderRoot ??= this.createRenderRoot(), this.enableUpdating(!0), this._$EO?.forEach((t) => t.hostConnected?.()); @@ -160,31 +160,31 @@ let x = class extends HTMLElement { this._$AK(t, n); } _$ET(t, e) { - const n = this.constructor.elementProperties.get(t), i = this.constructor._$Eu(t, n); - if (i !== void 0 && n.reflect === !0) { - const r = (n.converter?.toAttribute !== void 0 ? n.converter : F).toAttribute(e, n.type); - this._$Em = t, r == null ? this.removeAttribute(i) : this.setAttribute(i, r), this._$Em = null; + const n = this.constructor.elementProperties.get(t), s = this.constructor._$Eu(t, n); + if (s !== void 0 && n.reflect === !0) { + const r = (n.converter?.toAttribute !== void 0 ? n.converter : X).toAttribute(e, n.type); + this._$Em = t, r == null ? this.removeAttribute(s) : this.setAttribute(s, r), this._$Em = null; } } _$AK(t, e) { - const n = this.constructor, i = n._$Eh.get(t); - if (i !== void 0 && this._$Em !== i) { - const r = n.getPropertyOptions(i), a = typeof r.converter == "function" ? { fromAttribute: r.converter } : r.converter?.fromAttribute !== void 0 ? r.converter : F; - this._$Em = i; - const l = a.fromAttribute(e, r.type); - this[i] = l ?? this._$Ej?.get(i) ?? l, this._$Em = null; + const n = this.constructor, s = n._$Eh.get(t); + if (s !== void 0 && this._$Em !== s) { + const r = n.getPropertyOptions(s), o = typeof r.converter == "function" ? { fromAttribute: r.converter } : r.converter?.fromAttribute !== void 0 ? r.converter : X; + this._$Em = s; + const l = o.fromAttribute(e, r.type); + this[s] = l ?? this._$Ej?.get(s) ?? l, this._$Em = null; } } - requestUpdate(t, e, n, i = !1, r) { + requestUpdate(t, e, n, s = !1, r) { if (t !== void 0) { - const a = this.constructor; - if (i === !1 && (r = this[t]), n ??= a.getPropertyOptions(t), !((n.hasChanged ?? ht)(r, e) || n.useDefault && n.reflect && r === this._$Ej?.get(t) && !this.hasAttribute(a._$Eu(t, n)))) return; + const o = this.constructor; + if (s === !1 && (r = this[t]), n ??= o.getPropertyOptions(t), !((n.hasChanged ?? Mt)(r, e) || n.useDefault && n.reflect && r === this._$Ej?.get(t) && !this.hasAttribute(o._$Eu(t, n)))) return; this.C(t, e, n); } this.isUpdatePending === !1 && (this._$ES = this._$EP()); } - C(t, e, { useDefault: n, reflect: i, wrapped: r }, a) { - n && !(this._$Ej ??= /* @__PURE__ */ new Map()).has(t) && (this._$Ej.set(t, a ?? e ?? this[t]), r !== !0 || a !== void 0) || (this._$AL.has(t) || (this.hasUpdated || n || (e = void 0), this._$AL.set(t, e)), i === !0 && this._$Em !== t && (this._$Eq ??= /* @__PURE__ */ new Set()).add(t)); + C(t, e, { useDefault: n, reflect: s, wrapped: r }, o) { + n && !(this._$Ej ??= /* @__PURE__ */ new Map()).has(t) && (this._$Ej.set(t, o ?? e ?? this[t]), r !== !0 || o !== void 0) || (this._$AL.has(t) || (this.hasUpdated || n || (e = void 0), this._$AL.set(t, e)), s === !0 && this._$Em !== t && (this._$Eq ??= /* @__PURE__ */ new Set()).add(t)); } async _$EP() { this.isUpdatePending = !0; @@ -203,13 +203,13 @@ let x = class extends HTMLElement { if (!this.isUpdatePending) return; if (!this.hasUpdated) { if (this.renderRoot ??= this.createRenderRoot(), this._$Ep) { - for (const [i, r] of this._$Ep) this[i] = r; + for (const [s, r] of this._$Ep) this[s] = r; this._$Ep = void 0; } const n = this.constructor.elementProperties; - if (n.size > 0) for (const [i, r] of n) { - const { wrapped: a } = r, l = this[i]; - a !== !0 || this._$AL.has(i) || l === void 0 || this.C(i, void 0, r, l); + if (n.size > 0) for (const [s, r] of n) { + const { wrapped: o } = r, l = this[s]; + o !== !0 || this._$AL.has(s) || l === void 0 || this.C(s, void 0, r, l); } } let t = !1; @@ -246,70 +246,70 @@ let x = class extends HTMLElement { firstUpdated(t) { } }; -x.elementStyles = [], x.shadowRootOptions = { mode: "open" }, x[E("elementProperties")] = /* @__PURE__ */ new Map(), x[E("finalized")] = /* @__PURE__ */ new Map(), Ct?.({ ReactiveElement: x }), (M.reactiveElementVersions ??= []).push("2.1.2"); -const B = globalThis, Y = (s) => s, z = B.trustedTypes, tt = z ? z.createPolicy("lit-html", { createHTML: (s) => s }) : void 0, ut = "$lit$", y = `lit$${Math.random().toFixed(9).slice(2)}$`, ft = "?" + y, St = `<${ft}>`, q = document, C = () => q.createComment(""), S = (s) => s === null || typeof s != "object" && typeof s != "function", Q = Array.isArray, Nt = (s) => Q(s) || typeof s?.[Symbol.iterator] == "function", D = `[ -\f\r]`, k = /<(?:(!--|\/[^a-zA-Z])|(\/?[a-zA-Z][^>\s]*)|(\/?$))/g, et = /-->/g, nt = />/g, b = RegExp(`>|${D}(?:([^\\s"'>=/]+)(${D}*=${D}*(?:[^ -\f\r"'\`<>=]|("|')|))|$)`, "g"), st = /'/g, it = /"/g, gt = /^(?:script|style|textarea|title)$/i, Pt = (s) => (t, ...e) => ({ _$litType$: s, strings: t, values: e }), g = Pt(1), w = /* @__PURE__ */ Symbol.for("lit-noChange"), c = /* @__PURE__ */ Symbol.for("lit-nothing"), rt = /* @__PURE__ */ new WeakMap(), _ = q.createTreeWalker(q, 129); -function mt(s, t) { - if (!Q(s) || !s.hasOwnProperty("raw")) throw Error("invalid template strings array"); - return tt !== void 0 ? tt.createHTML(t) : t; +T.elementStyles = [], T.shadowRootOptions = { mode: "open" }, T[U("elementProperties")] = /* @__PURE__ */ new Map(), T[U("finalized")] = /* @__PURE__ */ new Map(), Wt?.({ ReactiveElement: T }), (F.reactiveElementVersions ??= []).push("2.1.2"); +const et = globalThis, dt = (i) => i, R = et.trustedTypes, pt = R ? R.createPolicy("lit-html", { createHTML: (i) => i }) : void 0, zt = "$lit$", x = `lit$${Math.random().toFixed(9).slice(2)}$`, Pt = "?" + x, Zt = `<${Pt}>`, S = document, I = () => S.createComment(""), D = (i) => i === null || typeof i != "object" && typeof i != "function", nt = Array.isArray, Vt = (i) => nt(i) || typeof i?.[Symbol.iterator] == "function", W = `[ +\f\r]`, P = /<(?:(!--|\/[^a-zA-Z])|(\/?[a-zA-Z][^>\s]*)|(\/?$))/g, ct = /-->/g, ht = />/g, q = RegExp(`>|${W}(?:([^\\s"'>=/]+)(${W}*=${W}*(?:[^ +\f\r"'\`<>=]|("|')|))|$)`, "g"), ut = /'/g, ft = /"/g, Ot = /^(?:script|style|textarea|title)$/i, Kt = (i) => (t, ...e) => ({ _$litType$: i, strings: t, values: e }), g = Kt(1), E = /* @__PURE__ */ Symbol.for("lit-noChange"), m = /* @__PURE__ */ Symbol.for("lit-nothing"), mt = /* @__PURE__ */ new WeakMap(), A = S.createTreeWalker(S, 129); +function Ut(i, t) { + if (!nt(i) || !i.hasOwnProperty("raw")) throw Error("invalid template strings array"); + return pt !== void 0 ? pt.createHTML(t) : t; } -const Tt = (s, t) => { - const e = s.length - 1, n = []; - let i, r = t === 2 ? "" : t === 3 ? "" : "", a = k; +const Jt = (i, t) => { + const e = i.length - 1, n = []; + let s, r = t === 2 ? "" : t === 3 ? "" : "", o = P; for (let l = 0; l < e; l++) { - const o = s[l]; - let d, h, p = -1, f = 0; - for (; f < o.length && (a.lastIndex = f, h = a.exec(o), h !== null); ) f = a.lastIndex, a === k ? h[1] === "!--" ? a = et : h[1] !== void 0 ? a = nt : h[2] !== void 0 ? (gt.test(h[2]) && (i = RegExp("" ? (a = i ?? k, p = -1) : h[1] === void 0 ? p = -2 : (p = a.lastIndex - h[2].length, d = h[1], a = h[3] === void 0 ? b : h[3] === '"' ? it : st) : a === it || a === st ? a = b : a === et || a === nt ? a = k : (a = b, i = void 0); - const u = a === b && s[l + 1].startsWith("/>") ? " " : ""; - r += a === k ? o + St : p >= 0 ? (n.push(d), o.slice(0, p) + ut + o.slice(p) + y + u) : o + y + (p === -2 ? l : u); + const a = i[l]; + let p, u, d = -1, h = 0; + for (; h < a.length && (o.lastIndex = h, u = o.exec(a), u !== null); ) h = o.lastIndex, o === P ? u[1] === "!--" ? o = ct : u[1] !== void 0 ? o = ht : u[2] !== void 0 ? (Ot.test(u[2]) && (s = RegExp("" ? (o = s ?? P, d = -1) : u[1] === void 0 ? d = -2 : (d = o.lastIndex - u[2].length, p = u[1], o = u[3] === void 0 ? q : u[3] === '"' ? ft : ut) : o === ft || o === ut ? o = q : o === ct || o === ht ? o = P : (o = q, s = void 0); + const c = o === q && i[l + 1].startsWith("/>") ? " " : ""; + r += o === P ? a + Zt : d >= 0 ? (n.push(p), a.slice(0, d) + zt + a.slice(d) + x + c) : a + x + (d === -2 ? l : c); } - return [mt(s, r + (s[e] || "") + (t === 2 ? "" : t === 3 ? "" : "")), n]; + return [Ut(i, r + (i[e] || "") + (t === 2 ? "" : t === 3 ? "" : "")), n]; }; -class N { +class H { constructor({ strings: t, _$litType$: e }, n) { - let i; + let s; this.parts = []; - let r = 0, a = 0; - const l = t.length - 1, o = this.parts, [d, h] = Tt(t, e); - if (this.el = N.createElement(d, n), _.currentNode = this.el.content, e === 2 || e === 3) { - const p = this.el.content.firstChild; - p.replaceWith(...p.childNodes); + let r = 0, o = 0; + const l = t.length - 1, a = this.parts, [p, u] = Jt(t, e); + if (this.el = H.createElement(p, n), A.currentNode = this.el.content, e === 2 || e === 3) { + const d = this.el.content.firstChild; + d.replaceWith(...d.childNodes); } - for (; (i = _.nextNode()) !== null && o.length < l; ) { - if (i.nodeType === 1) { - if (i.hasAttributes()) for (const p of i.getAttributeNames()) if (p.endsWith(ut)) { - const f = h[a++], u = i.getAttribute(p).split(y), m = /([.?@])?(.*)/.exec(f); - o.push({ type: 1, index: r, name: m[2], strings: u, ctor: m[1] === "." ? Ot : m[1] === "?" ? Mt : m[1] === "@" ? Ut : U }), i.removeAttribute(p); - } else p.startsWith(y) && (o.push({ type: 6, index: r }), i.removeAttribute(p)); - if (gt.test(i.tagName)) { - const p = i.textContent.split(y), f = p.length - 1; - if (f > 0) { - i.textContent = z ? z.emptyScript : ""; - for (let u = 0; u < f; u++) i.append(p[u], C()), _.nextNode(), o.push({ type: 2, index: ++r }); - i.append(p[f], C()); + for (; (s = A.nextNode()) !== null && a.length < l; ) { + if (s.nodeType === 1) { + if (s.hasAttributes()) for (const d of s.getAttributeNames()) if (d.endsWith(zt)) { + const h = u[o++], c = s.getAttribute(d).split(x), f = /([.?@])?(.*)/.exec(h); + a.push({ type: 1, index: r, name: f[2], strings: c, ctor: f[1] === "." ? Xt : f[1] === "?" ? Yt : f[1] === "@" ? te : Q }), s.removeAttribute(d); + } else d.startsWith(x) && (a.push({ type: 6, index: r }), s.removeAttribute(d)); + if (Ot.test(s.tagName)) { + const d = s.textContent.split(x), h = d.length - 1; + if (h > 0) { + s.textContent = R ? R.emptyScript : ""; + for (let c = 0; c < h; c++) s.append(d[c], I()), A.nextNode(), a.push({ type: 2, index: ++r }); + s.append(d[h], I()); } } - } else if (i.nodeType === 8) if (i.data === ft) o.push({ type: 2, index: r }); + } else if (s.nodeType === 8) if (s.data === Pt) a.push({ type: 2, index: r }); else { - let p = -1; - for (; (p = i.data.indexOf(y, p + 1)) !== -1; ) o.push({ type: 7, index: r }), p += y.length - 1; + let d = -1; + for (; (d = s.data.indexOf(x, d + 1)) !== -1; ) a.push({ type: 7, index: r }), d += x.length - 1; } r++; } } static createElement(t, e) { - const n = q.createElement("template"); + const n = S.createElement("template"); return n.innerHTML = t, n; } } -function A(s, t, e = s, n) { - if (t === w) return t; - let i = n !== void 0 ? e._$Co?.[n] : e._$Cl; - const r = S(t) ? void 0 : t._$litDirective$; - return i?.constructor !== r && (i?._$AO?.(!1), r === void 0 ? i = void 0 : (i = new r(s), i._$AT(s, e, n)), n !== void 0 ? (e._$Co ??= [])[n] = i : e._$Cl = i), i !== void 0 && (t = A(s, i._$AS(s, t.values), i, n)), t; +function M(i, t, e = i, n) { + if (t === E) return t; + let s = n !== void 0 ? e._$Co?.[n] : e._$Cl; + const r = D(t) ? void 0 : t._$litDirective$; + return s?.constructor !== r && (s?._$AO?.(!1), r === void 0 ? s = void 0 : (s = new r(i), s._$AT(i, e, n)), n !== void 0 ? (e._$Co ??= [])[n] = s : e._$Cl = s), s !== void 0 && (t = M(i, s._$AS(i, t.values), s, n)), t; } -class zt { +class Gt { constructor(t, e) { this._$AV = [], this._$AN = void 0, this._$AD = t, this._$AM = e; } @@ -320,29 +320,29 @@ class zt { return this._$AM._$AU; } u(t) { - const { el: { content: e }, parts: n } = this._$AD, i = (t?.creationScope ?? q).importNode(e, !0); - _.currentNode = i; - let r = _.nextNode(), a = 0, l = 0, o = n[0]; - for (; o !== void 0; ) { - if (a === o.index) { - let d; - o.type === 2 ? d = new P(r, r.nextSibling, this, t) : o.type === 1 ? d = new o.ctor(r, o.name, o.strings, this, t) : o.type === 6 && (d = new Ht(r, this, t)), this._$AV.push(d), o = n[++l]; + const { el: { content: e }, parts: n } = this._$AD, s = (t?.creationScope ?? S).importNode(e, !0); + A.currentNode = s; + let r = A.nextNode(), o = 0, l = 0, a = n[0]; + for (; a !== void 0; ) { + if (o === a.index) { + let p; + a.type === 2 ? p = new z(r, r.nextSibling, this, t) : a.type === 1 ? p = new a.ctor(r, a.name, a.strings, this, t) : a.type === 6 && (p = new ee(r, this, t)), this._$AV.push(p), a = n[++l]; } - a !== o?.index && (r = _.nextNode(), a++); + o !== a?.index && (r = A.nextNode(), o++); } - return _.currentNode = q, i; + return A.currentNode = S, s; } p(t) { let e = 0; for (const n of this._$AV) n !== void 0 && (n.strings !== void 0 ? (n._$AI(t, n, e), e += n.strings.length - 2) : n._$AI(t[e])), e++; } } -class P { +class z { get _$AU() { return this._$AM?._$AU ?? this._$Cv; } - constructor(t, e, n, i) { - this.type = 2, this._$AH = c, this._$AN = void 0, this._$AA = t, this._$AB = e, this._$AM = n, this.options = i, this._$Cv = i?.isConnected ?? !0; + constructor(t, e, n, s) { + this.type = 2, this._$AH = m, this._$AN = void 0, this._$AA = t, this._$AB = e, this._$AM = n, this.options = s, this._$Cv = s?.isConnected ?? !0; } get parentNode() { let t = this._$AA.parentNode; @@ -356,7 +356,7 @@ class P { return this._$AB; } _$AI(t, e = this) { - t = A(this, t, e), S(t) ? t === c || t == null || t === "" ? (this._$AH !== c && this._$AR(), this._$AH = c) : t !== this._$AH && t !== w && this._(t) : t._$litType$ !== void 0 ? this.$(t) : t.nodeType !== void 0 ? this.T(t) : Nt(t) ? this.k(t) : this._(t); + t = M(this, t, e), D(t) ? t === m || t == null || t === "" ? (this._$AH !== m && this._$AR(), this._$AH = m) : t !== this._$AH && t !== E && this._(t) : t._$litType$ !== void 0 ? this.$(t) : t.nodeType !== void 0 ? this.T(t) : Vt(t) ? this.k(t) : this._(t); } O(t) { return this._$AA.parentNode.insertBefore(t, this._$AB); @@ -365,92 +365,92 @@ class P { this._$AH !== t && (this._$AR(), this._$AH = this.O(t)); } _(t) { - this._$AH !== c && S(this._$AH) ? this._$AA.nextSibling.data = t : this.T(q.createTextNode(t)), this._$AH = t; + this._$AH !== m && D(this._$AH) ? this._$AA.nextSibling.data = t : this.T(S.createTextNode(t)), this._$AH = t; } $(t) { - const { values: e, _$litType$: n } = t, i = typeof n == "number" ? this._$AC(t) : (n.el === void 0 && (n.el = N.createElement(mt(n.h, n.h[0]), this.options)), n); - if (this._$AH?._$AD === i) this._$AH.p(e); + const { values: e, _$litType$: n } = t, s = typeof n == "number" ? this._$AC(t) : (n.el === void 0 && (n.el = H.createElement(Ut(n.h, n.h[0]), this.options)), n); + if (this._$AH?._$AD === s) this._$AH.p(e); else { - const r = new zt(i, this), a = r.u(this.options); - r.p(e), this.T(a), this._$AH = r; + const r = new Gt(s, this), o = r.u(this.options); + r.p(e), this.T(o), this._$AH = r; } } _$AC(t) { - let e = rt.get(t.strings); - return e === void 0 && rt.set(t.strings, e = new N(t)), e; + let e = mt.get(t.strings); + return e === void 0 && mt.set(t.strings, e = new H(t)), e; } k(t) { - Q(this._$AH) || (this._$AH = [], this._$AR()); + nt(this._$AH) || (this._$AH = [], this._$AR()); const e = this._$AH; - let n, i = 0; - for (const r of t) i === e.length ? e.push(n = new P(this.O(C()), this.O(C()), this, this.options)) : n = e[i], n._$AI(r), i++; - i < e.length && (this._$AR(n && n._$AB.nextSibling, i), e.length = i); + let n, s = 0; + for (const r of t) s === e.length ? e.push(n = new z(this.O(I()), this.O(I()), this, this.options)) : n = e[s], n._$AI(r), s++; + s < e.length && (this._$AR(n && n._$AB.nextSibling, s), e.length = s); } _$AR(t = this._$AA.nextSibling, e) { for (this._$AP?.(!1, !0, e); t !== this._$AB; ) { - const n = Y(t).nextSibling; - Y(t).remove(), t = n; + const n = dt(t).nextSibling; + dt(t).remove(), t = n; } } setConnected(t) { this._$AM === void 0 && (this._$Cv = t, this._$AP?.(t)); } } -class U { +class Q { get tagName() { return this.element.tagName; } get _$AU() { return this._$AM._$AU; } - constructor(t, e, n, i, r) { - this.type = 1, this._$AH = c, this._$AN = void 0, this.element = t, this.name = e, this._$AM = i, this.options = r, n.length > 2 || n[0] !== "" || n[1] !== "" ? (this._$AH = Array(n.length - 1).fill(new String()), this.strings = n) : this._$AH = c; + constructor(t, e, n, s, r) { + this.type = 1, this._$AH = m, this._$AN = void 0, this.element = t, this.name = e, this._$AM = s, this.options = r, n.length > 2 || n[0] !== "" || n[1] !== "" ? (this._$AH = Array(n.length - 1).fill(new String()), this.strings = n) : this._$AH = m; } - _$AI(t, e = this, n, i) { + _$AI(t, e = this, n, s) { const r = this.strings; - let a = !1; - if (r === void 0) t = A(this, t, e, 0), a = !S(t) || t !== this._$AH && t !== w, a && (this._$AH = t); + let o = !1; + if (r === void 0) t = M(this, t, e, 0), o = !D(t) || t !== this._$AH && t !== E, o && (this._$AH = t); else { const l = t; - let o, d; - for (t = r[0], o = 0; o < r.length - 1; o++) d = A(this, l[n + o], e, o), d === w && (d = this._$AH[o]), a ||= !S(d) || d !== this._$AH[o], d === c ? t = c : t !== c && (t += (d ?? "") + r[o + 1]), this._$AH[o] = d; + let a, p; + for (t = r[0], a = 0; a < r.length - 1; a++) p = M(this, l[n + a], e, a), p === E && (p = this._$AH[a]), o ||= !D(p) || p !== this._$AH[a], p === m ? t = m : t !== m && (t += (p ?? "") + r[a + 1]), this._$AH[a] = p; } - a && !i && this.j(t); + o && !s && this.j(t); } j(t) { - t === c ? this.element.removeAttribute(this.name) : this.element.setAttribute(this.name, t ?? ""); + t === m ? this.element.removeAttribute(this.name) : this.element.setAttribute(this.name, t ?? ""); } } -class Ot extends U { +class Xt extends Q { constructor() { super(...arguments), this.type = 3; } j(t) { - this.element[this.name] = t === c ? void 0 : t; + this.element[this.name] = t === m ? void 0 : t; } } -class Mt extends U { +class Yt extends Q { constructor() { super(...arguments), this.type = 4; } j(t) { - this.element.toggleAttribute(this.name, !!t && t !== c); + this.element.toggleAttribute(this.name, !!t && t !== m); } } -class Ut extends U { - constructor(t, e, n, i, r) { - super(t, e, n, i, r), this.type = 5; +class te extends Q { + constructor(t, e, n, s, r) { + super(t, e, n, s, r), this.type = 5; } _$AI(t, e = this) { - if ((t = A(this, t, e, 0) ?? c) === w) return; - const n = this._$AH, i = t === c && n !== c || t.capture !== n.capture || t.once !== n.once || t.passive !== n.passive, r = t !== c && (n === c || i); - i && this.element.removeEventListener(this.name, this, n), r && this.element.addEventListener(this.name, this, t), this._$AH = t; + if ((t = M(this, t, e, 0) ?? m) === E) return; + const n = this._$AH, s = t === m && n !== m || t.capture !== n.capture || t.once !== n.once || t.passive !== n.passive, r = t !== m && (n === m || s); + s && this.element.removeEventListener(this.name, this, n), r && this.element.addEventListener(this.name, this, t), this._$AH = t; } handleEvent(t) { typeof this._$AH == "function" ? this._$AH.call(this.options?.host ?? this.element, t) : this._$AH.handleEvent(t); } } -class Ht { +class ee { constructor(t, e, n) { this.element = t, this.type = 6, this._$AN = void 0, this._$AM = e, this.options = n; } @@ -458,22 +458,22 @@ class Ht { return this._$AM._$AU; } _$AI(t) { - A(this, t); + M(this, t); } } -const Dt = B.litHtmlPolyfillSupport; -Dt?.(N, P), (B.litHtmlVersions ??= []).push("3.3.3"); -const Rt = (s, t, e) => { +const ne = { I: z }, ie = et.litHtmlPolyfillSupport; +ie?.(H, z), (et.litHtmlVersions ??= []).push("3.3.3"); +const se = (i, t, e) => { const n = e?.renderBefore ?? t; - let i = n._$litPart$; - if (i === void 0) { + let s = n._$litPart$; + if (s === void 0) { const r = e?.renderBefore ?? null; - n._$litPart$ = i = new P(t.insertBefore(C(), r), r, void 0, e ?? {}); + n._$litPart$ = s = new z(t.insertBefore(I(), r), r, void 0, e ?? {}); } - return i._$AI(s), i; + return s._$AI(i), s; }; -const W = globalThis; -class v extends x { +const it = globalThis; +let C = class extends T { constructor() { super(...arguments), this.renderOptions = { host: this }, this._$Do = void 0; } @@ -483,7 +483,7 @@ class v extends x { } update(t) { const e = this.render(); - this.hasUpdated || (this.renderOptions.isConnected = this.isConnected), super.update(t), this._$Do = Rt(e, this.renderRoot, this.renderOptions); + this.hasUpdated || (this.renderOptions.isConnected = this.isConnected), super.update(t), this._$Do = se(e, this.renderRoot, this.renderOptions); } connectedCallback() { super.connectedCallback(), this._$Do?.setConnected(!0); @@ -492,51 +492,51 @@ class v extends x { super.disconnectedCallback(), this._$Do?.setConnected(!1); } render() { - return w; + return E; } +}; +C._$litElement$ = !0, C.finalized = !0, it.litElementHydrateSupport?.({ LitElement: C }); +const re = it.litElementPolyfillSupport; +re?.({ LitElement: C }); +(it.litElementVersions ??= []).push("4.2.2"); +const It = ':host{--nq-p-parchment-top: #f0e4c7;--nq-p-parchment-bottom: #ddcca3;--nq-p-parchment: radial-gradient(ellipse at 25% 10%, rgba(255,255,255,.5), transparent 55%), radial-gradient(ellipse at 85% 90%, rgba(120,86,44,.28), transparent 60%), repeating-linear-gradient(93deg, rgba(150,115,70,.05) 0 2px, transparent 2px 6px), repeating-linear-gradient(2deg, rgba(150,115,70,.04) 0 3px, transparent 3px 7px), linear-gradient(var(--nq-p-parchment-top), var(--nq-p-parchment-bottom));--nq-p-vignette: inset 0 0 200px rgba(80,52,20,.35);--nq-p-card-open: linear-gradient(#fdf7e6, #f2e7c9);--nq-p-card-done: linear-gradient(#f0e8d3, #e6dcc2);--nq-p-card-panel: linear-gradient(#fcf6e6, #f1e6ca);--nq-p-card-border: rgba(120,88,48,.38);--nq-p-panel-border: rgba(92,62,26,.4);--nq-p-card-shadow: 0 3px 0 rgba(120,88,48,.2), inset 0 1px 0 rgba(255,255,255,.7);--nq-p-panel-shadow: 0 6px 0 rgba(92,62,26,.18), inset 0 2px 0 rgba(255,255,255,.7);--nq-p-frame-outer: 2px solid rgba(92,62,26,.45);--nq-p-frame-inner: 1px solid rgba(92,62,26,.28);--nq-p-rule: 2px solid rgba(92,62,26,.35);--nq-p-ink: #2b1f14;--nq-p-ink-secondary: #5c452a;--nq-p-ink-muted: #6f6455;--nq-p-ink-away: #4d4433;--nq-p-ink-late: #8f1526;--nq-brand-blue: #3B3AB8;--nq-brand-purple: #A035CC;--nq-brand-gradient: linear-gradient(135deg, var(--nq-brand-blue), var(--nq-brand-purple));--nq-p-icon-tile: var(--nq-brand-gradient);--nq-p-icon-tile-done: rgba(92,62,26,.16);--nq-p-crest: var(--nq-brand-gradient);--nq-p-crest-away: linear-gradient(135deg, #6b6b7a, #7a7286);--nq-p-seal: radial-gradient(circle at 35% 30%, #a8283a, #6d1322);--nq-p-seal-shadow: 0 4px 10px rgba(60,10,20,.4), inset 0 0 0 4px rgba(255,255,255,.14);--nq-p-seal-size: 78px;--nq-p-dock-bg: rgba(43,31,20,.9);--nq-p-dock-ink: #f7efdb;--nq-p-dock-ink-secondary: #d8c9a6;--nq-p-dock-divider: rgba(233,220,189,.3);--nq-p-dock-height: 84px;--nq-p-font-display: "Cinzel Decorative", Cinzel, serif;--nq-p-font-heading: Cinzel, serif;--nq-p-font-body: Nunito, system-ui, sans-serif;--nq-p-size-hero: 86px;--nq-p-size-wordmark: 66px;--nq-p-size-title: 56px;--nq-p-size-name: 48px;--nq-p-size-section: 32px;--nq-p-size-quest: 30px;--nq-p-size-body: 22px;--nq-p-size-label: 21px;--nq-p-track-label: .14em;--nq-p-track-kicker: .3em;--nq-p-quest-min-height: 116px;--nq-p-quest-gap: 24px;--nq-p-quest-pad: 22px;--nq-p-tile-size: 64px;--nq-p-button-height: 72px;--nq-p-button-min-width: 150px;--nq-p-confirm-button: 108px;--nq-p-radius-card: 12px;--nq-p-radius-panel: 20px;--nq-p-radius-pill: 9999px;--nq-p-page-inset: 62px;--nq-p-frame-inset: 26px;--nq-ease-out: cubic-bezier(0, 0, .2, 1);--nq-dur-micro: .12s;--nq-dur-base: .2s;--nq-dur-modal: .35s}'; +function st(i) { + window.customCards = window.customCards ?? [], window.customCards.push(i); } -v._$litElement$ = !0, v.finalized = !0, W.litElementHydrateSupport?.({ LitElement: v }); -const It = W.litElementPolyfillSupport; -It?.({ LitElement: v }); -(W.litElementVersions ??= []).push("4.2.2"); -const yt = ':host{--nq-p-parchment-top: #f0e4c7;--nq-p-parchment-bottom: #ddcca3;--nq-p-parchment: radial-gradient(ellipse at 25% 10%, rgba(255,255,255,.5), transparent 55%), radial-gradient(ellipse at 85% 90%, rgba(120,86,44,.28), transparent 60%), repeating-linear-gradient(93deg, rgba(150,115,70,.05) 0 2px, transparent 2px 6px), repeating-linear-gradient(2deg, rgba(150,115,70,.04) 0 3px, transparent 3px 7px), linear-gradient(var(--nq-p-parchment-top), var(--nq-p-parchment-bottom));--nq-p-vignette: inset 0 0 200px rgba(80,52,20,.35);--nq-p-card-open: linear-gradient(#fdf7e6, #f2e7c9);--nq-p-card-done: linear-gradient(#f0e8d3, #e6dcc2);--nq-p-card-panel: linear-gradient(#fcf6e6, #f1e6ca);--nq-p-card-border: rgba(120,88,48,.38);--nq-p-panel-border: rgba(92,62,26,.4);--nq-p-card-shadow: 0 3px 0 rgba(120,88,48,.2), inset 0 1px 0 rgba(255,255,255,.7);--nq-p-panel-shadow: 0 6px 0 rgba(92,62,26,.18), inset 0 2px 0 rgba(255,255,255,.7);--nq-p-frame-outer: 2px solid rgba(92,62,26,.45);--nq-p-frame-inner: 1px solid rgba(92,62,26,.28);--nq-p-rule: 2px solid rgba(92,62,26,.35);--nq-p-ink: #2b1f14;--nq-p-ink-secondary: #5c452a;--nq-p-ink-muted: #6f6455;--nq-p-ink-away: #4d4433;--nq-p-ink-late: #8f1526;--nq-brand-blue: #3B3AB8;--nq-brand-purple: #A035CC;--nq-brand-gradient: linear-gradient(135deg, var(--nq-brand-blue), var(--nq-brand-purple));--nq-p-icon-tile: var(--nq-brand-gradient);--nq-p-icon-tile-done: rgba(92,62,26,.16);--nq-p-crest: var(--nq-brand-gradient);--nq-p-crest-away: linear-gradient(135deg, #6b6b7a, #7a7286);--nq-p-seal: radial-gradient(circle at 35% 30%, #a8283a, #6d1322);--nq-p-seal-shadow: 0 4px 10px rgba(60,10,20,.4), inset 0 0 0 4px rgba(255,255,255,.14);--nq-p-seal-size: 78px;--nq-p-dock-bg: rgba(43,31,20,.9);--nq-p-dock-ink: #f7efdb;--nq-p-dock-ink-secondary: #d8c9a6;--nq-p-dock-divider: rgba(233,220,189,.3);--nq-p-dock-height: 84px;--nq-p-font-display: "Cinzel Decorative", Cinzel, serif;--nq-p-font-heading: Cinzel, serif;--nq-p-font-body: Nunito, system-ui, sans-serif;--nq-p-size-hero: 86px;--nq-p-size-wordmark: 66px;--nq-p-size-title: 56px;--nq-p-size-name: 48px;--nq-p-size-section: 32px;--nq-p-size-quest: 30px;--nq-p-size-body: 22px;--nq-p-size-label: 21px;--nq-p-track-label: .14em;--nq-p-track-kicker: .3em;--nq-p-quest-min-height: 116px;--nq-p-quest-gap: 24px;--nq-p-button-height: 72px;--nq-p-button-min-width: 150px;--nq-p-confirm-button: 108px;--nq-p-radius-card: 12px;--nq-p-radius-panel: 20px;--nq-p-radius-pill: 9999px;--nq-p-page-inset: 62px;--nq-p-frame-inset: 26px;--nq-ease-out: cubic-bezier(0, 0, .2, 1);--nq-dur-micro: .12s;--nq-dur-base: .2s;--nq-dur-modal: .35s}'; -function V(s) { - window.customCards = window.customCards ?? [], window.customCards.push(s); -} -const at = /* @__PURE__ */ new Map(); -function O(s, t) { - const e = `${s ?? "local"}|${JSON.stringify(t)}`, n = at.get(e); +const gt = /* @__PURE__ */ new Map(); +function B(i, t) { + const e = `${i ?? "local"}|${JSON.stringify(t)}`, n = gt.get(e); if (n) return n; - let i; + let s; try { - i = new Intl.DateTimeFormat("en-US", { + s = new Intl.DateTimeFormat("en-US", { ...t, - ...s ? { timeZone: s } : {} + ...i ? { timeZone: i } : {} }); } catch { - i = new Intl.DateTimeFormat("en-US", t); + s = new Intl.DateTimeFormat("en-US", t); } - return at.set(e, i), i; + return gt.set(e, s), s; } -function Ft(s) { - const t = s?.config; +function oe(i) { + const t = i?.config; if (!t || typeof t != "object") return; const e = t.time_zone; return typeof e == "string" && e.trim() ? e.trim() : void 0; } -function R(s, t) { - return `${O(t, { weekday: "long" }).format(s)}, ${O(t, { month: "short", day: "numeric" }).format(s)}`; +function Z(i, t) { + return `${B(t, { weekday: "long" }).format(i)}, ${B(t, { month: "short", day: "numeric" }).format(i)}`; } -function Lt(s, t) { - return O(t, { +function ae(i, t) { + return B(t, { hour: "numeric", minute: "2-digit", hour12: !0 - }).format(s); + }).format(i); } -const jt = { +const le = { "clear-night": "Clear", cloudy: "Cloudy", exceptional: "Clear", @@ -552,7 +552,7 @@ const jt = { sunny: "Sunny", windy: "Windy", "windy-variant": "Windy" -}, Bt = { +}, de = { "clear-night": "Clear night skies", cloudy: "Grey skies today", exceptional: "A striking day", @@ -569,26 +569,26 @@ const jt = { windy: "A blustery day", "windy-variant": "A blustery day" }; -function ot(s) { - return typeof s == "string" ? s.trim() : ""; +function _t(i) { + return typeof i == "string" ? i.trim() : ""; } -function I(s, t) { - if (s == null || s === "") +function V(i, t) { + if (i == null || i === "") return t; - const e = typeof s == "number" ? s : Number(s); + const e = typeof i == "number" ? i : Number(i); return Number.isFinite(e) ? e : t; } -function lt(s) { - return Number.isFinite(s) ? Math.min(100, Math.max(0, s)) : 0; +function yt(i) { + return Number.isFinite(i) ? Math.min(100, Math.max(0, i)) : 0; } -function pt(s) { - return Math.max(0, Math.round(s)); +function bt(i) { + return Math.max(0, Math.round(i)); } -function Qt(s) { - return s.split(/[-_]+/).filter(Boolean).map((t) => t.charAt(0).toUpperCase() + t.slice(1)).join(" "); +function pe(i) { + return i.split(/[-_]+/).filter(Boolean).map((t) => t.charAt(0).toUpperCase() + t.slice(1)).join(" "); } -function Wt(s, t) { - const e = O(t, { +function ce(i, t) { + const e = B(t, { year: "numeric", month: "2-digit", day: "2-digit", @@ -596,10 +596,10 @@ function Wt(s, t) { minute: "2-digit", second: "2-digit", hourCycle: "h23" - }).formatToParts(new Date(s)), n = (r) => { - const a = e.find((l) => l.type === r); - return a ? Number(a.value) : Number.NaN; - }, i = Date.UTC( + }).formatToParts(new Date(i)), n = (r) => { + const o = e.find((l) => l.type === r); + return o ? Number(o.value) : Number.NaN; + }, s = Date.UTC( n("year"), n("month") - 1, n("day"), @@ -607,32 +607,32 @@ function Wt(s, t) { n("minute"), n("second") ); - return Number.isFinite(i) ? i - s : 0; + return Number.isFinite(s) ? s - i : 0; } -function Vt(s, t) { - if (typeof s != "string") +function he(i, t) { + if (typeof i != "string") return null; - const e = s.split("-"); + const e = i.split("-"); if (e.length !== 3) return null; - const [n, i, r] = e.map((o) => Number(o)); - if (!n || !i || !r) + const [n, s, r] = e.map((a) => Number(a)); + if (!n || !s || !r) return null; if (!t) { - const o = new Date(n, i - 1, r); - return Number.isNaN(o.getTime()) ? null : o; + const a = new Date(n, s - 1, r); + return Number.isNaN(a.getTime()) ? null : a; } - const a = Date.UTC(n, i - 1, r), l = new Date(a - Wt(a, t)); + const o = Date.UTC(n, s - 1, r), l = new Date(o - ce(o, t)); return Number.isNaN(l.getTime()) ? null : l; } -function Zt(s) { - const t = jt[s]; - return t || s.charAt(0).toUpperCase() + s.slice(1); +function ue(i) { + const t = le[i]; + return t || i.charAt(0).toUpperCase() + i.slice(1); } -function Kt(s, t) { - return Bt[s] ?? t; +function fe(i, t) { + return de[i] ?? t; } -const dt = "polygon(0 0, 100% 0, 100% 62%, 50% 100%, 0 62%)", Jt = "polygon(50% 0, 93% 25%, 93% 75%, 50% 100%, 7% 75%, 7% 25%)", Gt = _t` +const vt = "polygon(0 0, 100% 0, 100% 62%, 50% 100%, 0 62%)", me = "polygon(50% 0, 93% 25%, 93% 75%, 50% 100%, 7% 75%, 7% 25%)", ge = Tt` * { box-sizing: border-box; } @@ -704,7 +704,7 @@ const dt = "polygon(0 0, 100% 0, 100% 62%, 50% 100%, 0 62%)", Jt = "polygon(50% justify-content: center; width: 34px; height: 34px; - clip-path: ${$(Jt)}; + clip-path: ${b(me)}; background: var(--nq-brand-gradient); } @@ -798,7 +798,7 @@ const dt = "polygon(0 0, 100% 0, 100% 62%, 50% 100%, 0 62%)", Jt = "polygon(50% width: 188px; height: 214px; padding: 4px; - clip-path: ${$(dt)}; + clip-path: ${b(vt)}; background: rgba(255, 255, 255, 0.22); } @@ -809,7 +809,7 @@ const dt = "polygon(0 0, 100% 0, 100% 62%, 50% 100%, 0 62%)", Jt = "polygon(50% align-items: center; justify-content: center; padding-bottom: 70px; - clip-path: ${$(dt)}; + clip-path: ${b(vt)}; background: var(--nq-p-crest); } @@ -971,7 +971,7 @@ const dt = "polygon(0 0, 100% 0, 100% 62%, 50% 100%, 0 62%)", Jt = "polygon(50% font-weight: 800; line-height: 1; } -`, Xt = g` -`, Yt = g``, ye = g` -`, te = g``, be = g` `; -class ee extends v { +class ve extends C { static properties = { hass: { attribute: !1 }, _config: { state: !0 }, _now: { state: !0 } }; - static styles = [$(yt), Gt]; + static styles = [b(It), ge]; hass; _config; _now = /* @__PURE__ */ new Date(); @@ -1052,7 +1052,7 @@ class ee extends v { -

    The Party · ${R(this._now, this._timeZone())}

    +

    The Party · ${Z(this._now, this._timeZone())}

    ${this._plates().map((t) => this._renderPlate(t))}
    @@ -1063,7 +1063,7 @@ class ee extends v { `; } _timeZone() { - return Ft(this.hass); + return oe(this.hass); } _state(t) { const n = this.hass?.states?.[t]; @@ -1083,36 +1083,36 @@ class ee extends v { _plate(t) { const e = this._state(`sensor.nestquest_${t}_quests_due_today`), n = this._state( `sensor.nestquest_${t}_quests_completed_today` - ), i = this._state( + ), s = this._state( `sensor.nestquest_${t}_completion_pct_today` ), r = this._state( `binary_sensor.nestquest_${t}_present_today` - ), a = e?.attributes ?? {}, l = pt(I(e?.state, 0)), o = pt(I(n?.state, 0)), d = i ? I(i.state, Number.NaN) : Number.NaN, h = Number.isFinite(d) ? lt(d) : l > 0 ? lt(o / l * 100) : 0; - let p = !0; - const f = a.present; - typeof f == "boolean" ? p = f : r && (p = String(r.state ?? "").trim().toLowerCase() === "on"); - let u = ""; - const m = a.child_name; - if (typeof m == "string" && m.trim()) - u = m.trim(); + ), o = e?.attributes ?? {}, l = bt(V(e?.state, 0)), a = bt(V(n?.state, 0)), p = s ? V(s.state, Number.NaN) : Number.NaN, u = Number.isFinite(p) ? yt(p) : l > 0 ? yt(a / l * 100) : 0; + let d = !0; + const h = o.present; + typeof h == "boolean" ? d = h : r && (d = String(r.state ?? "").trim().toLowerCase() === "on"); + let c = ""; + const f = o.child_name; + if (typeof f == "string" && f.trim()) + c = f.trim(); else { - const H = r?.attributes?.child_name; - typeof H == "string" && H.trim() && (u = H.trim()); + const N = r?.attributes?.child_name; + typeof N == "string" && N.trim() && (c = N.trim()); } - u || (u = Qt(t)); - const Z = Vt( + c || (c = pe(t)); + const _ = he( r?.attributes?.next_present, this._timeZone() - ), bt = Z ? `Returns ${R(Z, this._timeZone())}` : null; + ), v = _ ? `Returns ${Z(_, this._timeZone())}` : null; return { slug: t, - name: u, - initial: (u.charAt(0) || "?").toUpperCase(), - present: p, - completed: o, + name: c, + initial: (c.charAt(0) || "?").toUpperCase(), + present: d, + completed: a, due: l, - pct: h, - returns: bt + pct: u, + returns: v }; } _renderPlate(t) { @@ -1130,7 +1130,7 @@ class ee extends v { ${t.name} - ${Xt} + ${_e} Home today @@ -1149,7 +1149,7 @@ class ee extends v { ${t.name} - ${Yt} + ${ye} On travels ${t.returns ?? "Returns —"} @@ -1158,11 +1158,11 @@ class ee extends v { `; } _openQuestLog(t) { - const e = ot(this._config?.quest_log_path); + const e = _t(this._config?.quest_log_path); e && (window.history.pushState(null, "", `${e.replace(/\/+$/, "")}/${t}`), window.dispatchEvent(new Event("location-changed"))); } _dockWeather() { - const t = ot(this._config?.weather_entity); + const t = _t(this._config?.weather_entity); if (!t) return null; const e = this._state(t); @@ -1171,21 +1171,21 @@ class ee extends v { const n = String(e.state ?? "").trim(); if (!n || n === "unavailable" || n === "unknown") return null; - const i = e.attributes ?? {}; - let r = null, a = null; - const l = i.forecast; + const s = e.attributes ?? {}; + let r = null, o = null; + const l = s.forecast; if (Array.isArray(l) && l.length > 0) { - const o = l[0]; - if (o && typeof o == "object") { - const d = o; - r = this._optionalNumber(d.temperature), a = this._optionalNumber(d.templow); + const a = l[0]; + if (a && typeof a == "object") { + const p = a; + r = this._optionalNumber(p.temperature), o = this._optionalNumber(p.templow); } } return { condition: n, - temperature: this._optionalNumber(i.temperature), + temperature: this._optionalNumber(s.temperature), high: r, - low: a + low: o }; } _optionalNumber(t) { @@ -1199,16 +1199,16 @@ class ee extends v { if (!t) return g`
    - ${R(this._now, this._timeZone())} + ${Z(this._now, this._timeZone())} - ${Lt(this._now, this._timeZone())} + ${ae(this._now, this._timeZone())}
    `; - const e = Zt(t.condition), n = Kt(t.condition, e), i = t.temperature === null ? c : g`${Math.round(t.temperature)}°`, r = t.high === null || t.low === null ? null : `${Math.round(t.high)}° / ${Math.round(t.low)}°`; + const e = ue(t.condition), n = fe(t.condition, e), s = t.temperature === null ? m : g`${Math.round(t.temperature)}°`, r = t.high === null || t.low === null ? null : `${Math.round(t.high)}° / ${Math.round(t.low)}°`; return g`
    - ${te} - ${i} + ${be} + ${s} ${r === null ? e : `${e} · ${r}`} @@ -1219,45 +1219,1288 @@ class ee extends v { `; } } -customElements.define("nestquest-party-board-card", ee); -V({ +customElements.define("nestquest-party-board-card", ve); +st({ type: "nestquest-party-board-card", name: "NestQuest Party Board", description: "Kids' attract screen for present and away adventurers." }); -class ne extends v { +const $e = { CHILD: 2 }, xe = (i) => (...t) => ({ _$litDirective$: i, values: t }); +let qe = class { + constructor(t) { + } + get _$AU() { + return this._$AM._$AU; + } + _$AT(t, e, n) { + this._$Ct = t, this._$AM = e, this._$Ci = n; + } + _$AS(t, e) { + return this.update(t, e); + } + update(t, e) { + return this.render(...e); + } +}; +const { I: we } = ne, $t = (i) => i, xt = () => document.createComment(""), O = (i, t, e) => { + const n = i._$AA.parentNode, s = t === void 0 ? i._$AB : t._$AA; + if (e === void 0) { + const r = n.insertBefore(xt(), s), o = n.insertBefore(xt(), s); + e = new we(r, o, i, i.options); + } else { + const r = e._$AB.nextSibling, o = e._$AM, l = o !== i; + if (l) { + let a; + e._$AQ?.(i), e._$AM = i, e._$AP !== void 0 && (a = i._$AU) !== o._$AU && e._$AP(a); + } + if (r !== s || l) { + let a = e._$AA; + for (; a !== r; ) { + const p = $t(a).nextSibling; + $t(n).insertBefore(a, s), a = p; + } + } + } + return e; +}, w = (i, t, e = i) => (i._$AI(t, e), i), ke = {}, Ae = (i, t = ke) => i._$AH = t, Ce = (i) => i._$AH, K = (i) => { + i._$AR(), i._$AA.remove(); +}; +const qt = (i, t, e) => { + const n = /* @__PURE__ */ new Map(); + for (let s = t; s <= e; s++) n.set(i[s], s); + return n; +}, wt = xe(class extends qe { + constructor(i) { + if (super(i), i.type !== $e.CHILD) throw Error("repeat() can only be used in text expressions"); + } + dt(i, t, e) { + let n; + e === void 0 ? e = t : t !== void 0 && (n = t); + const s = [], r = []; + let o = 0; + for (const l of i) s[o] = n ? n(l, o) : o, r[o] = e(l, o), o++; + return { values: r, keys: s }; + } + render(i, t, e) { + return this.dt(i, t, e).values; + } + update(i, [t, e, n]) { + const s = Ce(i), { values: r, keys: o } = this.dt(t, e, n); + if (!Array.isArray(s)) return this.ut = o, r; + const l = this.ut ??= [], a = []; + let p, u, d = 0, h = s.length - 1, c = 0, f = r.length - 1; + for (; d <= h && c <= f; ) if (s[d] === null) d++; + else if (s[h] === null) h--; + else if (l[d] === o[c]) a[c] = w(s[d], r[c]), d++, c++; + else if (l[h] === o[f]) a[f] = w(s[h], r[f]), h--, f--; + else if (l[d] === o[f]) a[f] = w(s[d], r[f]), O(i, a[f + 1], s[d]), d++, f--; + else if (l[h] === o[c]) a[c] = w(s[h], r[c]), O(i, s[d], s[h]), h--, c++; + else if (p === void 0 && (p = qt(o, c, f), u = qt(l, d, h)), p.has(l[d])) if (p.has(l[h])) { + const _ = u.get(o[c]), v = _ !== void 0 ? s[_] : null; + if (v === null) { + const N = O(i, s[d]); + w(N, r[c]), a[c] = N; + } else a[c] = w(v, r[c]), O(i, s[d], v), s[_] = null; + c++; + } else K(s[h]), h--; + else K(s[d]), d++; + for (; c <= f; ) { + const _ = O(i, a[f + 1]); + w(_, r[c]), a[c++] = _; + } + for (; d <= h; ) { + const _ = s[d++]; + _ !== null && K(_); + } + return this.ut = o, Ae(i, a), E; + } +}), kt = /* @__PURE__ */ new Map(); +function j(i, t) { + const e = `${i ?? "local"}|${JSON.stringify(t)}`, n = kt.get(e); + if (n) + return n; + let s; + try { + s = new Intl.DateTimeFormat("en-US", { + ...t, + ...i ? { timeZone: i } : {} + }); + } catch { + s = new Intl.DateTimeFormat("en-US", t); + } + return kt.set(e, s), s; +} +function Se(i) { + const t = i?.config; + if (!t || typeof t != "object") + return; + const e = t.time_zone; + return typeof e == "string" && e.trim() ? e.trim() : void 0; +} +function Ee(i, t) { + return `${j(t, { weekday: "long" }).format(i)}, ${j(t, { month: "short", day: "numeric" }).format(i)}`; +} +function Ne(i, t) { + return j(t, { weekday: "long" }).format(i); +} +function Te(i, t) { + const e = new Date(i); + return Number.isNaN(e.getTime()) ? "" : j(t, { + hour: "numeric", + minute: "2-digit", + hour12: !0 + }).format(e); +} +function Me(i) { + if (!i) + return null; + const t = /^(\d{1,2}):(\d{2})$/.exec(i.trim()); + if (!t) + return null; + const e = Number(t[1]); + if (e > 23) + return null; + const n = e >= 12 ? "PM" : "AM"; + return `${e % 12 === 0 ? 12 : e % 12}:${t[2]} ${n}`; +} +function k(i) { + return typeof i == "string" ? i.trim() : ""; +} +function y(i, t) { + if (i == null || i === "") + return t; + const e = typeof i == "number" ? i : Number(i); + return Number.isFinite(e) ? e : t; +} +function $(i) { + return Math.max(0, Math.round(i)); +} +function At(i) { + return i.split(/[-_]+/).filter(Boolean).map((t) => t.charAt(0).toUpperCase() + t.slice(1)).join(" "); +} +function ze(i, t) { + const e = j(t, { + year: "numeric", + month: "2-digit", + day: "2-digit", + hour: "2-digit", + minute: "2-digit", + second: "2-digit", + hourCycle: "h23" + }).formatToParts(new Date(i)), n = (r) => { + const o = e.find((l) => l.type === r); + return o ? Number(o.value) : Number.NaN; + }, s = Date.UTC( + n("year"), + n("month") - 1, + n("day"), + n("hour"), + n("minute"), + n("second") + ); + return Number.isFinite(s) ? s - i : 0; +} +function Pe(i, t) { + if (typeof i != "string") + return null; + const e = i.split("-"); + if (e.length !== 3) + return null; + const [n, s, r] = e.map((a) => Number(a)); + if (!n || !s || !r) + return null; + if (!t) { + const a = new Date(n, s - 1, r); + return Number.isNaN(a.getTime()) ? null : a; + } + const o = Date.UTC(n, s - 1, r), l = new Date(o - ze(o, t)); + return Number.isNaN(l.getTime()) ? null : l; +} +function Oe() { + const i = window.location.pathname.split("/").filter(Boolean); + return i.length > 0 ? i[i.length - 1].toLowerCase() : ""; +} +function Ue(i) { + if (!i || typeof i != "object") + return null; + const t = i, e = k(t.state).toLowerCase(); + if (e !== "open" && e !== "completed") + return null; + const n = y(t.id, 0); + return n ? { + id: n, + title: k(t.title) || "Quest", + icon: k(t.icon) || null, + window: k(t.window).toLowerCase(), + due_time: k(t.due_time) || null, + state: e, + overdue: t.overdue === !0, + completed_at: k(t.completed_at) || null + } : null; +} +const Ct = "polygon(0 0, 100% 0, 100% 62%, 50% 100%, 0 62%)", Ie = "polygon(50% 0, 93% 25%, 93% 75%, 50% 100%, 7% 75%, 7% 25%)", St = [-9, 6, -4], De = Tt` + * { + box-sizing: border-box; + } + + :host { + display: block; + height: 100%; + --nq-brand-gradient-h: linear-gradient( + 90deg, + var(--nq-brand-blue), + var(--nq-brand-purple) + ); + } + + .board { + position: relative; + display: flex; + flex-direction: column; + height: 1080px; + padding: 0; + overflow: hidden; + background: var(--nq-p-parchment); + box-shadow: var(--nq-p-vignette); + font-family: var(--nq-p-font-body); + } + + .frame { + position: absolute; + pointer-events: none; + } + + .frame-outer { + inset: 26px; + border: var(--nq-p-frame-outer); + border-radius: 14px; + } + + .frame-inner { + inset: 36px; + border: var(--nq-p-frame-inner); + border-radius: 8px; + } + + .header { + position: absolute; + top: 56px; + left: var(--nq-p-page-inset); + right: var(--nq-p-page-inset); + display: flex; + align-items: center; + gap: 28px; + } + + .crest { + position: relative; + flex: none; + width: 96px; + height: 110px; + padding: 3px; + clip-path: ${b(Ct)}; + background: rgba(255, 255, 255, 0.22); + } + + .crest-face { + width: 100%; + height: 100%; + display: flex; + align-items: center; + justify-content: center; + padding-bottom: 32px; + clip-path: ${b(Ct)}; + background: var(--nq-p-crest); + } + + .crest .initial { + font-family: var(--nq-p-font-display); + font-size: 44px; + font-weight: 700; + line-height: 1; + color: #ffffff; + } + + .titles { + display: flex; + flex-direction: column; + gap: 8px; + min-width: 0; + } + + .title { + margin: 0; + font-family: var(--nq-p-font-display); + font-size: var(--nq-p-size-title); + font-weight: 700; + line-height: 1.1; + color: var(--nq-p-ink); + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + } + + .sub { + margin: 0; + font-family: var(--nq-p-font-heading); + font-size: 20px; + font-weight: 600; + letter-spacing: 0.24em; + text-transform: uppercase; + line-height: 1.2; + color: var(--nq-p-ink-secondary); + } + + .remaining { + margin-left: auto; + flex: none; + display: flex; + align-items: center; + gap: 20px; + padding: 18px 28px; + border: 2px solid var(--nq-p-panel-border); + border-radius: 14px; + background: var(--nq-p-card-panel); + box-shadow: var(--nq-p-panel-shadow); + } + + .d20 { + flex: none; + display: inline-flex; + align-items: center; + justify-content: center; + width: 44px; + height: 44px; + clip-path: ${b(Ie)}; + background: var(--nq-brand-gradient); + } + + .d20-numeral { + font-family: var(--nq-p-font-heading); + font-size: 19px; + font-weight: 700; + line-height: 1; + color: #ffffff; + } + + .remaining-text { + display: flex; + flex-direction: column; + gap: 4px; + } + + .remaining-count { + font-family: var(--nq-p-font-heading); + font-size: 28px; + font-weight: 700; + line-height: 1.1; + color: var(--nq-p-ink); + } + + .party-line { + font-family: var(--nq-p-font-body); + font-size: var(--nq-p-size-body); + font-weight: 600; + line-height: 1.2; + color: var(--nq-p-ink-secondary); + } + + .columns { + position: absolute; + top: 236px; + left: var(--nq-p-page-inset); + right: var(--nq-p-page-inset); + bottom: 130px; + display: grid; + grid-template-columns: repeat(3, 1fr); + gap: 34px; + } + + .column { + display: flex; + flex-direction: column; + min-height: 0; + } + + .column-head { + flex: none; + display: flex; + align-items: center; + gap: 14px; + padding-bottom: 14px; + border-bottom: var(--nq-p-rule); + } + + .column-head svg { + flex: none; + width: 34px; + height: 34px; + color: var(--nq-p-ink-secondary); + } + + .column-names { + display: flex; + flex-direction: column; + gap: 2px; + min-width: 0; + } + + .column-name { + font-family: var(--nq-p-font-heading); + font-size: var(--nq-p-size-section); + font-weight: 700; + line-height: 1.1; + color: var(--nq-p-ink); + } + + .column-range { + font-family: var(--nq-p-font-heading); + font-size: var(--nq-p-size-label); + font-weight: 600; + letter-spacing: var(--nq-p-track-label); + text-transform: uppercase; + line-height: 1.2; + color: var(--nq-p-ink-secondary); + } + + .column-count { + margin-left: auto; + flex: none; + font-family: var(--nq-p-font-heading); + font-size: 26px; + font-weight: 700; + line-height: 1.1; + color: var(--nq-p-ink-secondary); + } + + .stack { + display: flex; + flex-direction: column; + flex: 1; + min-height: 0; + overflow: visible; + gap: var(--nq-p-quest-gap); + margin-top: 22px; + } + + .quest { + position: relative; + display: flex; + align-items: center; + gap: 22px; + min-height: var(--nq-p-quest-min-height); + padding: var(--nq-p-quest-pad); + border: 1px solid var(--nq-p-card-border); + border-radius: var(--nq-p-radius-card); + background: var(--nq-p-card-open); + box-shadow: var(--nq-p-card-shadow); + -webkit-tap-highlight-color: transparent; + transition: transform var(--nq-dur-micro) var(--nq-ease-out); + } + + .quest.tappable { + cursor: pointer; + } + + .quest.tappable:active { + transform: scale(0.98); + } + + .quest.sealed { + background: var(--nq-p-card-done); + opacity: 0.82; + box-shadow: none; + } + + .tile { + flex: none; + display: inline-flex; + align-items: center; + justify-content: center; + width: var(--nq-p-tile-size); + height: var(--nq-p-tile-size); + border-radius: 10px; + background: var(--nq-p-icon-tile); + color: #ffffff; + } + + .tile svg { + width: 34px; + height: 34px; + } + + .sealed .tile { + background: var(--nq-p-icon-tile-done); + color: var(--nq-p-ink-secondary); + } + + .quest .body { + flex: 1 1 0; + min-width: 0; + display: flex; + flex-direction: column; + gap: 6px; + } + + .quest .quest-title { + font-family: var(--nq-p-font-heading); + font-size: var(--nq-p-size-quest); + font-weight: 700; + line-height: 1.15; + color: var(--nq-p-ink); + overflow-wrap: anywhere; + } + + .sealed .quest-title { + color: var(--nq-p-ink-muted); + text-decoration: line-through; + } + + .quest .meta { + font-family: var(--nq-p-font-body); + font-size: var(--nq-p-size-body); + font-weight: 600; + line-height: 1.2; + color: var(--nq-p-ink-secondary); + } + + .quest .meta.late { + font-family: var(--nq-p-font-heading); + font-weight: 700; + letter-spacing: 0.1em; + text-transform: uppercase; + color: var(--nq-p-ink-late); + } + + button.complete { + flex: none; + display: inline-flex; + align-items: center; + justify-content: center; + gap: 10px; + height: var(--nq-p-button-height); + min-width: var(--nq-p-button-min-width); + border: none; + border-radius: 10px; + background: var(--nq-brand-gradient); + color: #ffffff; + font-family: var(--nq-p-font-heading); + font-size: 24px; + font-weight: 700; + line-height: 1; + box-shadow: 0 3px 0 rgba(40, 20, 60, 0.35); + cursor: pointer; + -webkit-tap-highlight-color: transparent; + transition: transform var(--nq-dur-micro) var(--nq-ease-out); + } + + button.complete:active { + transform: scale(0.98); + } + + button.complete svg { + width: 30px; + height: 30px; + } + + button:focus-visible { + outline: 3px solid var(--nq-brand-purple); + outline-offset: 4px; + } + + .seal { + position: absolute; + right: 16px; + top: -12px; + display: inline-flex; + align-items: center; + justify-content: center; + width: var(--nq-p-seal-size); + height: var(--nq-p-seal-size); + border-radius: var(--nq-p-radius-pill); + background: var(--nq-p-seal); + box-shadow: var(--nq-p-seal-shadow); + color: rgba(255, 235, 235, 0.95); + transform: rotate(var(--seal-rot, 0deg)); + animation: seal-in var(--nq-dur-base) var(--nq-ease-out); + pointer-events: none; + } + + .seal svg { + width: 38px; + height: 38px; + } + + @keyframes seal-in { + from { + opacity: 0; + transform: scale(1.15) rotate(var(--seal-rot, 0deg)); + } + to { + opacity: 1; + transform: scale(1) rotate(var(--seal-rot, 0deg)); + } + } + + .rollup { + border: 2px dashed var(--nq-p-panel-border); + border-radius: 14px; + padding: 16px 22px; + } + + .rollup-label { + display: block; + font-family: var(--nq-p-font-heading); + font-size: 18px; + font-weight: 600; + letter-spacing: var(--nq-p-track-label); + text-transform: uppercase; + line-height: 1.2; + color: var(--nq-p-ink-secondary); + } + + .rollup-body { + margin: 6px 0 0; + font-family: var(--nq-p-font-body); + font-size: 23px; + font-weight: 600; + line-height: 1.35; + color: var(--nq-p-ink-secondary); + } + + .scrim { + position: absolute; + inset: 0; + z-index: 10; + background: rgba(30, 20, 10, 0.62); + } + + .dialog { + position: absolute; + top: 196px; + left: 460px; + width: 1000px; + border: 3px solid rgba(92, 62, 26, 0.5); + border-radius: 20px; + background: var(--nq-p-card-panel); + box-shadow: 0 26px 60px rgba(20, 10, 0, 0.5); + padding: 64px 68px 56px; + animation: dialog-in var(--nq-dur-modal) var(--nq-ease-out); + } + + @keyframes dialog-in { + from { + opacity: 0; + transform: scale(0.96); + } + to { + opacity: 1; + transform: scale(1); + } + } + + .dialog-header { + display: flex; + align-items: center; + gap: 24px; + } + + .dialog-tile { + flex: none; + display: inline-flex; + align-items: center; + justify-content: center; + width: 104px; + height: 104px; + border-radius: 12px; + background: var(--nq-p-icon-tile); + color: #ffffff; + } + + .dialog-tile svg { + width: 56px; + height: 56px; + } + + .dialog-titles { + display: flex; + flex-direction: column; + gap: 8px; + min-width: 0; + } + + .dialog-kicker { + font-family: var(--nq-p-font-heading); + font-size: var(--nq-p-size-label); + font-weight: 600; + letter-spacing: 0.24em; + text-transform: uppercase; + line-height: 1.2; + color: var(--nq-p-ink-secondary); + } + + .dialog-quest-title { + font-family: var(--nq-p-font-display); + font-size: 58px; + font-weight: 700; + line-height: 1.1; + color: var(--nq-p-ink); + overflow-wrap: anywhere; + } + + .dialog-body { + margin: 28px 0 0; + font-family: var(--nq-p-font-body); + font-size: 32px; + font-weight: 600; + line-height: 1.35; + color: #3f2f1c; + } + + .dialog-buttons { + display: flex; + gap: 24px; + margin-top: 40px; + } + + button.confirm-button { + display: inline-flex; + align-items: center; + justify-content: center; + gap: 12px; + height: var(--nq-p-confirm-button); + border-radius: 10px; + font-family: var(--nq-p-font-heading); + font-weight: 700; + line-height: 1; + cursor: pointer; + -webkit-tap-highlight-color: transparent; + transition: transform var(--nq-dur-micro) var(--nq-ease-out); + } + + button.confirm-button:active { + transform: scale(0.98); + } + + button.not-yet { + flex: 1; + border: 2px solid rgba(92, 62, 26, 0.5); + background: rgba(92, 62, 26, 0.1); + color: var(--nq-p-ink); + font-size: 30px; + } + + button.not-yet svg { + width: 34px; + height: 34px; + } + + button.do-complete { + flex: 2; + border: none; + background: var(--nq-brand-gradient); + color: #ffffff; + font-size: 32px; + } + + button.do-complete svg { + width: 38px; + height: 38px; + } +`, J = g``, He = g``, G = g``, je = g``, Le = g``, Re = g``, Et = [ + { key: "morning", name: "Morning", range: "Until 11:59 AM", icon: je }, + { key: "afternoon", name: "Afternoon", range: "12:00–5:00 PM", icon: Le }, + { key: "evening", name: "Evening", range: "5:00–9:00 PM", icon: Re } +]; +class Be extends C { static properties = { hass: { attribute: !1 }, - _config: { state: !0 } + _config: { state: !0 }, + _now: { state: !0 }, + _confirm: { state: !0 } }; - static styles = [$(yt)]; + static styles = [b(It), De]; hass; _config; + _now = /* @__PURE__ */ new Date(); + _confirm = null; + _clockTimer; + _idleTimer; + _confirmTimer; setConfig(t) { if (!t || typeof t != "object") throw new Error("Invalid configuration"); this._config = t; } getCardSize() { - return 8; + return 22; + } + connectedCallback() { + super.connectedCallback(), this._now = /* @__PURE__ */ new Date(), this._clockTimer = window.setInterval(() => { + this._now = /* @__PURE__ */ new Date(); + }, 6e4), window.addEventListener("pointerdown", this._onActivity, !0), window.addEventListener("touchstart", this._onActivity, !0), window.addEventListener("keydown", this._onActivity, !0), this._armIdle(); + } + disconnectedCallback() { + this._stopClock(), this._clearIdle(), this._clearConfirmTimer(), window.removeEventListener("pointerdown", this._onActivity, !0), window.removeEventListener("touchstart", this._onActivity, !0), window.removeEventListener("keydown", this._onActivity, !0), super.disconnectedCallback(); } render() { - return g`
    NestQuest Quest Log
    `; + return g` +
    +
    +
    + ${this._renderHeader()} +
    + ${Et.map((t) => this._renderColumn(t))} +
    + ${this._renderConfirm()} +
    + `; } + _timeZone() { + return Se(this.hass); + } + _state(t) { + const n = this.hass?.states?.[t]; + return n && typeof n == "object" ? n : null; + } + _childSlug() { + return Oe(); + } + _boardPath() { + return k(this._config?.board_path).replace(/\/+$/, ""); + } + _idleSeconds() { + const t = y(this._config?.idle_return_seconds, 40); + return t > 0 ? t : 40; + } + _confirmSeconds() { + const t = y(this._config?.confirm_timeout_seconds, 15); + return t > 0 ? t : 15; + } + _childName(t) { + if (!t) + return null; + const e = this._state(`sensor.nestquest_${t}_quests_due_today`)?.attributes?.child_name; + return typeof e == "string" && e.trim() ? e.trim() : null; + } + _childCounts(t) { + const e = this._state(`sensor.nestquest_${t}_quests_due_today`), n = this._state( + `sensor.nestquest_${t}_quests_completed_today` + ), s = $(y(e?.state, 0)), r = $(y(n?.state, 0)); + return { due: s, completed: r }; + } + _instances() { + const t = this._childSlug(); + if (!t) + return []; + const e = this._state(`sensor.nestquest_${t}_quests_due_today`)?.attributes?.instances; + return Array.isArray(e) ? e.map(Ue).filter((n) => n !== null) : []; + } + _remaining() { + const t = this._childSlug(); + if (!t) + return 0; + const e = this._state( + `sensor.nestquest_${t}_quests_remaining_today` + ); + if (e) + return $(y(e.state, 0)); + const { due: n, completed: s } = this._childCounts(t); + return $(n - s); + } + _partyCounts() { + const t = this._state( + "sensor.nestquest_household_quests_due_today" + ), e = this._state( + "sensor.nestquest_household_quests_completed_today" + ); + if (t || e) + return { + completed: $(y(e?.state, 0)), + due: $(y(t?.state, 0)) + }; + const n = this._childSlug(), { due: s, completed: r } = this._childCounts(n); + return { completed: r, due: s }; + } + _otherChildren() { + const t = this.hass?.states; + if (!t) + return []; + const e = this._childSlug(), n = this._timeZone(), s = []; + for (const r of Object.keys(t)) { + const o = /^sensor\.nestquest_(.+)_quests_due_today$/.exec(r); + if (!o || o[1] === "household" || o[1] === e) + continue; + const l = o[1], a = t[r]?.attributes ?? {}; + let p = ""; + typeof a.child_name == "string" && a.child_name.trim() ? p = a.child_name.trim() : p = At(l); + let u = !0; + if (typeof a.present == "boolean") + u = a.present; + else { + const _ = this._state( + `binary_sensor.nestquest_${l}_present_today` + ); + u = _ === null || String(_.state ?? "").trim().toLowerCase() !== "off"; + } + const d = this._state( + `sensor.nestquest_${l}_quests_completed_today` + ), h = $(y(t[r]?.state, 0)), c = $(y(d?.state, 0)); + let f = null; + if (!u) { + const _ = this._state( + `binary_sensor.nestquest_${l}_present_today` + ), v = Pe( + _?.attributes?.next_present, + n + ); + f = v ? Ne(v, n) : null; + } + s.push({ + slug: l, + name: p, + present: u, + due: h, + completed: c, + returnsWeekday: f + }); + } + return s.sort((r, o) => r.name.localeCompare(o.name)); + } + _renderHeader() { + const t = this._childSlug(), n = this._childName(t) ?? (t ? At(t) : null), { due: s, completed: r } = this._childCounts(t), o = this._remaining(), l = this._partyCounts(), a = n ? `${n}'s Quest Log` : "Quest Log", p = (n?.charAt(0) || "?").toUpperCase(); + return g` +
    + +
    +

    ${a}

    +

    + ${Ee(this._now, this._timeZone())} · ${r} of ${s} + claimed +

    +
    +
    + +
    + + ${o} quest${o === 1 ? "" : "s"} left + + + Party progress · ${l.completed} of ${l.due} today + +
    +
    +
    + `; + } + _renderColumn(t) { + const e = this._instances().filter( + (l) => l.window === t.key + ), n = e.filter((l) => l.state === "open").sort( + (l, a) => (l.due_time ?? "99:99").localeCompare(a.due_time ?? "99:99") + ), s = e.filter( + (l) => l.state === "completed" + ), r = e.length; + let o = m; + if (r > 0) { + const a = 644 - (t.key === "afternoon" && this._otherChildren().length > 0 ? 160 : 0), p = Math.min( + 24, + Math.max(8, Math.floor((a - r * 74) / Math.max(r - 1, 1))) + ), u = Math.min( + 116, + Math.max(58, Math.floor((a - (r - 1) * p) / r)) + ), d = Math.max(56, Math.min(72, u - 2)); + let h = `--nq-p-quest-gap: ${p}px; --nq-p-quest-min-height: ${u}px; --nq-p-button-height: ${d}px`; + if (r >= 5) { + const c = Math.max(0, Math.min(22, Math.floor((u - d - 2) / 2))), f = Math.max(44, Math.min(64, u - 2 * c - 2)); + h += `; --nq-p-quest-pad: ${c}px; --nq-p-tile-size: ${f}px`; + } + o = h; + } + return g` +
    +
    + ${t.icon} +
    + ${t.name} + ${t.range} +
    + ${s.length}/${e.length} +
    +
    + ${wt( + n, + (l) => l.id, + (l) => this._renderQuest(l, !1, 0) + )} + ${wt( + s, + (l) => l.id, + (l, a) => this._renderQuest(l, !0, a) + )} + ${t.key === "afternoon" ? this._renderRollup() : m} +
    +
    + `; + } + _renderQuest(t, e, n) { + if (e) { + const o = St[n % St.length], l = t.completed_at ? Te(t.completed_at, this._timeZone()) : ""; + return g` +
    + +
    + ${t.title} + + ${l ? `Claimed ${l}` : "Claimed"} + +
    + +
    + `; + } + const s = Me(t.due_time), r = t.overdue ? { + text: s ? `Overdue · due ${s}` : "Overdue", + late: !0 + } : { + text: s ? `Due by ${s}` : "Due today", + late: !1 + }; + return g` +
    this._openConfirm(t.id)} + @keydown=${(o) => { + (o.key === "Enter" || o.key === " ") && (o.preventDefault(), this._openConfirm(t.id)); + }} + > + +
    + ${t.title} + ${r.text} +
    + +
    + `; + } + _renderRollup() { + const t = this._otherChildren(); + if (t.length === 0) + return m; + const e = t.slice(0, 2).map((n) => n.present ? n.due > 0 && n.completed >= n.due ? `${n.name} has finished their log.` : n.due > n.completed ? `${n.name} has ${n.due - n.completed} quests left.` : `${n.name} has no quests today.` : n.returnsWeekday ? `${n.name} is on travels until ${n.returnsWeekday}.` : `${n.name} is on travels.`); + return g` +
    + Party roll-up +

    ${e.join(" ")}

    +
    + `; + } + _renderConfirm() { + if (this._confirm === null) + return m; + const t = this._instances().find( + (n) => n.id === this._confirm + ); + if (!t) + return m; + const e = Et.find((n) => n.key === t.window); + return g` +
    this._closeConfirm()}> + +
    + `; + } + _openConfirm(t) { + this._confirm = t, this._armConfirmTimer(); + } + _closeConfirm() { + this._clearConfirmTimer(), this._confirm = null; + } + _confirmComplete() { + this._closeConfirm(); + } + _armConfirmTimer() { + this._clearConfirmTimer(), this._confirmTimer = window.setTimeout(() => { + this._confirmTimer = void 0, this._closeConfirm(); + }, this._confirmSeconds() * 1e3); + } + _clearConfirmTimer() { + this._confirmTimer !== void 0 && (window.clearTimeout(this._confirmTimer), this._confirmTimer = void 0); + } + _armIdle() { + this._clearIdle(), this._idleTimer = window.setTimeout(() => { + this._idleTimer = void 0, this._returnToBoard(); + }, this._idleSeconds() * 1e3); + } + _clearIdle() { + this._idleTimer !== void 0 && (window.clearTimeout(this._idleTimer), this._idleTimer = void 0); + } + _returnToBoard() { + const t = this._boardPath(); + t && (window.history.pushState(null, "", t), window.dispatchEvent(new Event("location-changed"))); + } + _stopClock() { + this._clockTimer !== void 0 && (window.clearInterval(this._clockTimer), this._clockTimer = void 0); + } + _onActivity = () => { + this._armIdle(), this._confirm !== null && this._armConfirmTimer(); + }; } -customElements.define("nestquest-quest-log-card", ne); -V({ +customElements.define("nestquest-quest-log-card", Be); +st({ type: "nestquest-quest-log-card", name: "NestQuest Quest Log", description: "One child's daily quests, grouped by window." }); -const se = ":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 ie extends v { +const Fe = ":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 Qe extends C { static properties = { hass: { attribute: !1 }, _config: { state: !0 } }; - static styles = [$(se)]; + static styles = [b(Fe)]; hass; _config; setConfig(t) { @@ -1272,14 +2515,14 @@ class ie extends v { return g`
    NestQuest Admin
    `; } } -customElements.define("nestquest-admin-card", ie); -V({ +customElements.define("nestquest-admin-card", Qe); +st({ type: "nestquest-admin-card", name: "NestQuest Admin", description: "Parent phone view for today, tasks, schedule, and history." }); -const re = "/nestquest-static/nestquest-fonts.css"; +const We = "/nestquest-static/nestquest-fonts.css"; if (!document.querySelector('link[data-nq-fonts=""]')) { - const s = document.createElement("link"); - s.rel = "stylesheet", s.href = re, s.dataset.nqFonts = "", document.head.appendChild(s); + const i = document.createElement("link"); + i.rel = "stylesheet", i.href = We, i.dataset.nqFonts = "", document.head.appendChild(i); } diff --git a/custom_components/nestquest/www/nestquest-panel-tokens.css b/custom_components/nestquest/www/nestquest-panel-tokens.css index f997486..9f51ab6 100644 --- a/custom_components/nestquest/www/nestquest-panel-tokens.css +++ b/custom_components/nestquest/www/nestquest-panel-tokens.css @@ -77,6 +77,8 @@ /* ── Metrics: touch ───────────────────────────────────── */ --nq-p-quest-min-height: 116px; /* whole card is the tap target */ --nq-p-quest-gap: 24px; /* separation between tap targets */ + --nq-p-quest-pad: 22px; /* padding inside a quest card */ + --nq-p-tile-size: 64px; /* quest icon tile */ --nq-p-button-height: 72px; /* "Complete" button on a quest card */ --nq-p-button-min-width: 150px; --nq-p-confirm-button: 108px; /* confirm dialog buttons */ diff --git a/design/tokens/nestquest-panel-tokens.css b/design/tokens/nestquest-panel-tokens.css index f997486..9f51ab6 100644 --- a/design/tokens/nestquest-panel-tokens.css +++ b/design/tokens/nestquest-panel-tokens.css @@ -77,6 +77,8 @@ /* ── Metrics: touch ───────────────────────────────────── */ --nq-p-quest-min-height: 116px; /* whole card is the tap target */ --nq-p-quest-gap: 24px; /* separation between tap targets */ + --nq-p-quest-pad: 22px; /* padding inside a quest card */ + --nq-p-tile-size: 64px; /* quest icon tile */ --nq-p-button-height: 72px; /* "Complete" button on a quest card */ --nq-p-button-min-width: 150px; --nq-p-confirm-button: 108px; /* confirm dialog buttons */ diff --git a/frontend/src/cards/quest-log.ts b/frontend/src/cards/quest-log.ts index b51929a..ed27510 100644 --- a/frontend/src/cards/quest-log.ts +++ b/frontend/src/cards/quest-log.ts @@ -1,18 +1,905 @@ -import { LitElement, html, unsafeCSS } from "lit"; +import { LitElement, css, html, nothing, unsafeCSS, type TemplateResult } from "lit"; +import { repeat } from "lit/directives/repeat.js"; import panelTokens from "../../../custom_components/nestquest/www/nestquest-panel-tokens.css?inline"; import { registerCustomCard, type CardConfig, type HassLike } from "../types"; +type StateObject = { + state: unknown; + attributes?: Record | null; +}; + +type PanelInstance = { + id: number; + title: string; + icon: string | null; + window: string; + due_time: string | null; + state: string; + overdue: boolean; + completed_at: string | null; +}; + +type WindowDef = { + key: string; + name: string; + range: string; + icon: TemplateResult; +}; + +type OtherChild = { + slug: string; + name: string; + present: boolean; + due: number; + completed: number; + returnsWeekday: string | null; +}; + +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 formatWeekday(date: Date, timeZone: string | undefined): string { + return zonedFormatter(timeZone, { weekday: "long" }).format(date); +} + +function formatInstant(value: string, timeZone: string | undefined): string { + const parsed = new Date(value); + if (Number.isNaN(parsed.getTime())) { + return ""; + } + return zonedFormatter(timeZone, { + hour: "numeric", + minute: "2-digit", + hour12: true, + }).format(parsed); +} + +function formatWallClock(value: string | null): string | null { + if (!value) { + return null; + } + const match = /^(\d{1,2}):(\d{2})$/.exec(value.trim()); + if (!match) { + return null; + } + const hours = Number(match[1]); + if (hours > 23) { + return null; + } + const suffix = hours >= 12 ? "PM" : "AM"; + const hour12 = hours % 12 === 0 ? 12 : hours % 12; + return `${hour12}:${match[2]} ${suffix}`; +} + +function asString(value: unknown): string { + return typeof value === "string" ? value.trim() : ""; +} + +function asNumber(value: unknown, fallback: number): number { + if (value === null || value === undefined || value === "") { + return fallback; + } + const parsed = typeof value === "number" ? value : Number(value); + return Number.isFinite(parsed) ? parsed : fallback; +} + +function clampCount(value: number): number { + return Math.max(0, Math.round(value)); +} + +function titleCaseSlug(slug: string): string { + return slug + .split(/[-_]+/) + .filter(Boolean) + .map((word) => word.charAt(0).toUpperCase() + word.slice(1)) + .join(" "); +} + +function zoneOffsetMs(instant: number, timeZone: string): number { + const parts = zonedFormatter(timeZone, { + year: "numeric", + month: "2-digit", + day: "2-digit", + hour: "2-digit", + minute: "2-digit", + second: "2-digit", + hourCycle: "h23", + }).formatToParts(new Date(instant)); + const read = (type: Intl.DateTimeFormatPartTypes): number => { + const part = parts.find((entry) => entry.type === type); + return part ? Number(part.value) : Number.NaN; + }; + const asUtc = Date.UTC( + read("year"), + read("month") - 1, + read("day"), + read("hour"), + read("minute"), + read("second") + ); + return Number.isFinite(asUtc) ? asUtc - instant : 0; +} + +function parseIsoDate(value: unknown, timeZone?: string): Date | null { + if (typeof value !== "string") { + return null; + } + const parts = value.split("-"); + if (parts.length !== 3) { + return null; + } + const [year, month, day] = parts.map((part) => Number(part)); + if (!year || !month || !day) { + return null; + } + if (!timeZone) { + const local = new Date(year, month - 1, day); + return Number.isNaN(local.getTime()) ? null : local; + } + const utcMs = Date.UTC(year, month - 1, day); + const date = new Date(utcMs - zoneOffsetMs(utcMs, timeZone)); + return Number.isNaN(date.getTime()) ? null : date; +} + +function childSlugFromPath(): string { + const segments = window.location.pathname.split("/").filter(Boolean); + return segments.length > 0 + ? segments[segments.length - 1].toLowerCase() + : ""; +} + +function toPanelInstance(value: unknown): PanelInstance | null { + if (!value || typeof value !== "object") { + return null; + } + const row = value as Record; + const state = asString(row.state).toLowerCase(); + if (state !== "open" && state !== "completed") { + return null; + } + const id = asNumber(row.id, 0); + if (!id) { + return null; + } + return { + id, + title: asString(row.title) || "Quest", + icon: asString(row.icon) || null, + window: asString(row.window).toLowerCase(), + due_time: asString(row.due_time) || null, + state, + overdue: row.overdue === true, + completed_at: asString(row.completed_at) || null, + }; +} + +const SHIELD_CLIP = "polygon(0 0, 100% 0, 100% 62%, 50% 100%, 0 62%)"; +const HEXAGON_CLIP = + "polygon(50% 0, 93% 25%, 93% 75%, 50% 100%, 7% 75%, 7% 25%)"; + +const SEAL_ROTATIONS = [-9, 6, -4]; + +const logStyles = css` + * { + box-sizing: border-box; + } + + :host { + display: block; + height: 100%; + --nq-brand-gradient-h: linear-gradient( + 90deg, + var(--nq-brand-blue), + var(--nq-brand-purple) + ); + } + + .board { + position: relative; + display: flex; + flex-direction: column; + height: 1080px; + padding: 0; + overflow: hidden; + background: var(--nq-p-parchment); + box-shadow: var(--nq-p-vignette); + font-family: var(--nq-p-font-body); + } + + .frame { + position: absolute; + pointer-events: none; + } + + .frame-outer { + inset: 26px; + border: var(--nq-p-frame-outer); + border-radius: 14px; + } + + .frame-inner { + inset: 36px; + border: var(--nq-p-frame-inner); + border-radius: 8px; + } + + .header { + position: absolute; + top: 56px; + left: var(--nq-p-page-inset); + right: var(--nq-p-page-inset); + display: flex; + align-items: center; + gap: 28px; + } + + .crest { + position: relative; + flex: none; + width: 96px; + height: 110px; + padding: 3px; + clip-path: ${unsafeCSS(SHIELD_CLIP)}; + background: rgba(255, 255, 255, 0.22); + } + + .crest-face { + width: 100%; + height: 100%; + display: flex; + align-items: center; + justify-content: center; + padding-bottom: 32px; + clip-path: ${unsafeCSS(SHIELD_CLIP)}; + background: var(--nq-p-crest); + } + + .crest .initial { + font-family: var(--nq-p-font-display); + font-size: 44px; + font-weight: 700; + line-height: 1; + color: #ffffff; + } + + .titles { + display: flex; + flex-direction: column; + gap: 8px; + min-width: 0; + } + + .title { + margin: 0; + font-family: var(--nq-p-font-display); + font-size: var(--nq-p-size-title); + font-weight: 700; + line-height: 1.1; + color: var(--nq-p-ink); + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + } + + .sub { + margin: 0; + font-family: var(--nq-p-font-heading); + font-size: 20px; + font-weight: 600; + letter-spacing: 0.24em; + text-transform: uppercase; + line-height: 1.2; + color: var(--nq-p-ink-secondary); + } + + .remaining { + margin-left: auto; + flex: none; + display: flex; + align-items: center; + gap: 20px; + padding: 18px 28px; + border: 2px solid var(--nq-p-panel-border); + border-radius: 14px; + background: var(--nq-p-card-panel); + box-shadow: var(--nq-p-panel-shadow); + } + + .d20 { + flex: none; + display: inline-flex; + align-items: center; + justify-content: center; + width: 44px; + height: 44px; + clip-path: ${unsafeCSS(HEXAGON_CLIP)}; + background: var(--nq-brand-gradient); + } + + .d20-numeral { + font-family: var(--nq-p-font-heading); + font-size: 19px; + font-weight: 700; + line-height: 1; + color: #ffffff; + } + + .remaining-text { + display: flex; + flex-direction: column; + gap: 4px; + } + + .remaining-count { + font-family: var(--nq-p-font-heading); + font-size: 28px; + font-weight: 700; + line-height: 1.1; + color: var(--nq-p-ink); + } + + .party-line { + font-family: var(--nq-p-font-body); + font-size: var(--nq-p-size-body); + font-weight: 600; + line-height: 1.2; + color: var(--nq-p-ink-secondary); + } + + .columns { + position: absolute; + top: 236px; + left: var(--nq-p-page-inset); + right: var(--nq-p-page-inset); + bottom: 130px; + display: grid; + grid-template-columns: repeat(3, 1fr); + gap: 34px; + } + + .column { + display: flex; + flex-direction: column; + min-height: 0; + } + + .column-head { + flex: none; + display: flex; + align-items: center; + gap: 14px; + padding-bottom: 14px; + border-bottom: var(--nq-p-rule); + } + + .column-head svg { + flex: none; + width: 34px; + height: 34px; + color: var(--nq-p-ink-secondary); + } + + .column-names { + display: flex; + flex-direction: column; + gap: 2px; + min-width: 0; + } + + .column-name { + font-family: var(--nq-p-font-heading); + font-size: var(--nq-p-size-section); + font-weight: 700; + line-height: 1.1; + color: var(--nq-p-ink); + } + + .column-range { + font-family: var(--nq-p-font-heading); + font-size: var(--nq-p-size-label); + font-weight: 600; + letter-spacing: var(--nq-p-track-label); + text-transform: uppercase; + line-height: 1.2; + color: var(--nq-p-ink-secondary); + } + + .column-count { + margin-left: auto; + flex: none; + font-family: var(--nq-p-font-heading); + font-size: 26px; + font-weight: 700; + line-height: 1.1; + color: var(--nq-p-ink-secondary); + } + + .stack { + display: flex; + flex-direction: column; + flex: 1; + min-height: 0; + overflow: visible; + gap: var(--nq-p-quest-gap); + margin-top: 22px; + } + + .quest { + position: relative; + display: flex; + align-items: center; + gap: 22px; + min-height: var(--nq-p-quest-min-height); + padding: var(--nq-p-quest-pad); + border: 1px solid var(--nq-p-card-border); + border-radius: var(--nq-p-radius-card); + background: var(--nq-p-card-open); + box-shadow: var(--nq-p-card-shadow); + -webkit-tap-highlight-color: transparent; + transition: transform var(--nq-dur-micro) var(--nq-ease-out); + } + + .quest.tappable { + cursor: pointer; + } + + .quest.tappable:active { + transform: scale(0.98); + } + + .quest.sealed { + background: var(--nq-p-card-done); + opacity: 0.82; + box-shadow: none; + } + + .tile { + flex: none; + display: inline-flex; + align-items: center; + justify-content: center; + width: var(--nq-p-tile-size); + height: var(--nq-p-tile-size); + border-radius: 10px; + background: var(--nq-p-icon-tile); + color: #ffffff; + } + + .tile svg { + width: 34px; + height: 34px; + } + + .sealed .tile { + background: var(--nq-p-icon-tile-done); + color: var(--nq-p-ink-secondary); + } + + .quest .body { + flex: 1 1 0; + min-width: 0; + display: flex; + flex-direction: column; + gap: 6px; + } + + .quest .quest-title { + font-family: var(--nq-p-font-heading); + font-size: var(--nq-p-size-quest); + font-weight: 700; + line-height: 1.15; + color: var(--nq-p-ink); + overflow-wrap: anywhere; + } + + .sealed .quest-title { + color: var(--nq-p-ink-muted); + text-decoration: line-through; + } + + .quest .meta { + font-family: var(--nq-p-font-body); + font-size: var(--nq-p-size-body); + font-weight: 600; + line-height: 1.2; + color: var(--nq-p-ink-secondary); + } + + .quest .meta.late { + font-family: var(--nq-p-font-heading); + font-weight: 700; + letter-spacing: 0.1em; + text-transform: uppercase; + color: var(--nq-p-ink-late); + } + + button.complete { + flex: none; + display: inline-flex; + align-items: center; + justify-content: center; + gap: 10px; + height: var(--nq-p-button-height); + min-width: var(--nq-p-button-min-width); + border: none; + border-radius: 10px; + background: var(--nq-brand-gradient); + color: #ffffff; + font-family: var(--nq-p-font-heading); + font-size: 24px; + font-weight: 700; + line-height: 1; + box-shadow: 0 3px 0 rgba(40, 20, 60, 0.35); + cursor: pointer; + -webkit-tap-highlight-color: transparent; + transition: transform var(--nq-dur-micro) var(--nq-ease-out); + } + + button.complete:active { + transform: scale(0.98); + } + + button.complete svg { + width: 30px; + height: 30px; + } + + button:focus-visible { + outline: 3px solid var(--nq-brand-purple); + outline-offset: 4px; + } + + .seal { + position: absolute; + right: 16px; + top: -12px; + display: inline-flex; + align-items: center; + justify-content: center; + width: var(--nq-p-seal-size); + height: var(--nq-p-seal-size); + border-radius: var(--nq-p-radius-pill); + background: var(--nq-p-seal); + box-shadow: var(--nq-p-seal-shadow); + color: rgba(255, 235, 235, 0.95); + transform: rotate(var(--seal-rot, 0deg)); + animation: seal-in var(--nq-dur-base) var(--nq-ease-out); + pointer-events: none; + } + + .seal svg { + width: 38px; + height: 38px; + } + + @keyframes seal-in { + from { + opacity: 0; + transform: scale(1.15) rotate(var(--seal-rot, 0deg)); + } + to { + opacity: 1; + transform: scale(1) rotate(var(--seal-rot, 0deg)); + } + } + + .rollup { + border: 2px dashed var(--nq-p-panel-border); + border-radius: 14px; + padding: 16px 22px; + } + + .rollup-label { + display: block; + font-family: var(--nq-p-font-heading); + font-size: 18px; + font-weight: 600; + letter-spacing: var(--nq-p-track-label); + text-transform: uppercase; + line-height: 1.2; + color: var(--nq-p-ink-secondary); + } + + .rollup-body { + margin: 6px 0 0; + font-family: var(--nq-p-font-body); + font-size: 23px; + font-weight: 600; + line-height: 1.35; + color: var(--nq-p-ink-secondary); + } + + .scrim { + position: absolute; + inset: 0; + z-index: 10; + background: rgba(30, 20, 10, 0.62); + } + + .dialog { + position: absolute; + top: 196px; + left: 460px; + width: 1000px; + border: 3px solid rgba(92, 62, 26, 0.5); + border-radius: 20px; + background: var(--nq-p-card-panel); + box-shadow: 0 26px 60px rgba(20, 10, 0, 0.5); + padding: 64px 68px 56px; + animation: dialog-in var(--nq-dur-modal) var(--nq-ease-out); + } + + @keyframes dialog-in { + from { + opacity: 0; + transform: scale(0.96); + } + to { + opacity: 1; + transform: scale(1); + } + } + + .dialog-header { + display: flex; + align-items: center; + gap: 24px; + } + + .dialog-tile { + flex: none; + display: inline-flex; + align-items: center; + justify-content: center; + width: 104px; + height: 104px; + border-radius: 12px; + background: var(--nq-p-icon-tile); + color: #ffffff; + } + + .dialog-tile svg { + width: 56px; + height: 56px; + } + + .dialog-titles { + display: flex; + flex-direction: column; + gap: 8px; + min-width: 0; + } + + .dialog-kicker { + font-family: var(--nq-p-font-heading); + font-size: var(--nq-p-size-label); + font-weight: 600; + letter-spacing: 0.24em; + text-transform: uppercase; + line-height: 1.2; + color: var(--nq-p-ink-secondary); + } + + .dialog-quest-title { + font-family: var(--nq-p-font-display); + font-size: 58px; + font-weight: 700; + line-height: 1.1; + color: var(--nq-p-ink); + overflow-wrap: anywhere; + } + + .dialog-body { + margin: 28px 0 0; + font-family: var(--nq-p-font-body); + font-size: 32px; + font-weight: 600; + line-height: 1.35; + color: #3f2f1c; + } + + .dialog-buttons { + display: flex; + gap: 24px; + margin-top: 40px; + } + + button.confirm-button { + display: inline-flex; + align-items: center; + justify-content: center; + gap: 12px; + height: var(--nq-p-confirm-button); + border-radius: 10px; + font-family: var(--nq-p-font-heading); + font-weight: 700; + line-height: 1; + cursor: pointer; + -webkit-tap-highlight-color: transparent; + transition: transform var(--nq-dur-micro) var(--nq-ease-out); + } + + button.confirm-button:active { + transform: scale(0.98); + } + + button.not-yet { + flex: 1; + border: 2px solid rgba(92, 62, 26, 0.5); + background: rgba(92, 62, 26, 0.1); + color: var(--nq-p-ink); + font-size: 30px; + } + + button.not-yet svg { + width: 34px; + height: 34px; + } + + button.do-complete { + flex: 2; + border: none; + background: var(--nq-brand-gradient); + color: #ffffff; + font-size: 32px; + } + + button.do-complete svg { + width: 38px; + height: 38px; + } +`; + +const ICON_CHECK = html``; + +const ICON_X = html``; + +const ICON_STAR = html``; + +const ICON_SUNRISE = html``; + +const ICON_SUN = html``; + +const ICON_MOON = html``; + +const WINDOW_DEFS: WindowDef[] = [ + { key: "morning", name: "Morning", range: "Until 11:59 AM", icon: ICON_SUNRISE }, + { key: "afternoon", name: "Afternoon", range: "12:00–5:00 PM", icon: ICON_SUN }, + { key: "evening", name: "Evening", range: "5:00–9:00 PM", icon: ICON_MOON }, +]; + export class NestQuestQuestLogCard extends LitElement { static properties = { hass: { attribute: false }, _config: { state: true }, + _now: { state: true }, + _confirm: { state: true }, }; - static styles = [unsafeCSS(panelTokens)]; + static styles = [unsafeCSS(panelTokens), logStyles]; hass?: HassLike; _config?: CardConfig; + _now = new Date(); + _confirm: number | null = null; + private _clockTimer?: number; + private _idleTimer?: number; + private _confirmTimer?: number; setConfig(config: CardConfig): void { if (!config || typeof config !== "object") { @@ -22,12 +909,534 @@ export class NestQuestQuestLogCard extends LitElement { } getCardSize(): number { - return 8; + return 22; + } + + connectedCallback(): void { + super.connectedCallback(); + this._now = new Date(); + this._clockTimer = window.setInterval(() => { + this._now = new Date(); + }, 60000); + window.addEventListener("pointerdown", this._onActivity, true); + window.addEventListener("touchstart", this._onActivity, true); + window.addEventListener("keydown", this._onActivity, true); + this._armIdle(); + } + + disconnectedCallback(): void { + this._stopClock(); + this._clearIdle(); + this._clearConfirmTimer(); + window.removeEventListener("pointerdown", this._onActivity, true); + window.removeEventListener("touchstart", this._onActivity, true); + window.removeEventListener("keydown", this._onActivity, true); + super.disconnectedCallback(); } render() { - return html`
    NestQuest Quest Log
    `; + return html` +
    +
    +
    + ${this._renderHeader()} +
    + ${WINDOW_DEFS.map((windowDef) => this._renderColumn(windowDef))} +
    + ${this._renderConfirm()} +
    + `; } + + private _timeZone(): string | undefined { + return hassTimeZone(this.hass); + } + + private _state(entityId: string): StateObject | null { + const states = this.hass?.states as Record | undefined; + const stateObj = states?.[entityId]; + return stateObj && typeof stateObj === "object" ? stateObj : null; + } + + private _childSlug(): string { + return childSlugFromPath(); + } + + private _boardPath(): string { + return asString(this._config?.board_path).replace(/\/+$/, ""); + } + + private _idleSeconds(): number { + const seconds = asNumber(this._config?.idle_return_seconds, 40); + return seconds > 0 ? seconds : 40; + } + + private _confirmSeconds(): number { + const seconds = asNumber(this._config?.confirm_timeout_seconds, 15); + return seconds > 0 ? seconds : 15; + } + + private _childName(slug: string): string | null { + if (!slug) { + return null; + } + const name = this._state(`sensor.nestquest_${slug}_quests_due_today`) + ?.attributes?.child_name; + if (typeof name === "string" && name.trim()) { + return name.trim(); + } + return null; + } + + private _childCounts(slug: string): { due: number; completed: number } { + const dueSensor = this._state(`sensor.nestquest_${slug}_quests_due_today`); + const completedSensor = this._state( + `sensor.nestquest_${slug}_quests_completed_today` + ); + const due = clampCount(asNumber(dueSensor?.state, 0)); + const completed = clampCount(asNumber(completedSensor?.state, 0)); + return { due, completed }; + } + + private _instances(): PanelInstance[] { + const slug = this._childSlug(); + if (!slug) { + return []; + } + const payload = this._state(`sensor.nestquest_${slug}_quests_due_today`) + ?.attributes?.instances; + if (!Array.isArray(payload)) { + return []; + } + return payload + .map(toPanelInstance) + .filter((instance): instance is PanelInstance => instance !== null); + } + + private _remaining(): number { + const slug = this._childSlug(); + if (!slug) { + return 0; + } + const sensor = this._state( + `sensor.nestquest_${slug}_quests_remaining_today` + ); + if (sensor) { + return clampCount(asNumber(sensor.state, 0)); + } + const { due, completed } = this._childCounts(slug); + return clampCount(due - completed); + } + + private _partyCounts(): { completed: number; due: number } { + const householdDue = this._state( + "sensor.nestquest_household_quests_due_today" + ); + const householdCompleted = this._state( + "sensor.nestquest_household_quests_completed_today" + ); + if (householdDue || householdCompleted) { + return { + completed: clampCount(asNumber(householdCompleted?.state, 0)), + due: clampCount(asNumber(householdDue?.state, 0)), + }; + } + const slug = this._childSlug(); + const { due, completed } = this._childCounts(slug); + return { completed, due }; + } + + private _otherChildren(): OtherChild[] { + const states = this.hass?.states as + | Record + | undefined; + if (!states) { + return []; + } + const slug = this._childSlug(); + const timeZone = this._timeZone(); + const result: OtherChild[] = []; + for (const entityId of Object.keys(states)) { + const match = /^sensor\.nestquest_(.+)_quests_due_today$/.exec(entityId); + if (!match || match[1] === "household" || match[1] === slug) { + continue; + } + const childSlug = match[1]; + const attrs = states[entityId]?.attributes ?? {}; + let name = ""; + if (typeof attrs.child_name === "string" && attrs.child_name.trim()) { + name = attrs.child_name.trim(); + } else { + name = titleCaseSlug(childSlug); + } + let present = true; + if (typeof attrs.present === "boolean") { + present = attrs.present; + } else { + const presence = this._state( + `binary_sensor.nestquest_${childSlug}_present_today` + ); + present = + presence === null || + String(presence.state ?? "").trim().toLowerCase() !== "off"; + } + const completedSensor = this._state( + `sensor.nestquest_${childSlug}_quests_completed_today` + ); + const due = clampCount(asNumber(states[entityId]?.state, 0)); + const completed = clampCount(asNumber(completedSensor?.state, 0)); + let returnsWeekday: string | null = null; + if (!present) { + const presence = this._state( + `binary_sensor.nestquest_${childSlug}_present_today` + ); + const nextPresent = parseIsoDate( + presence?.attributes?.next_present, + timeZone + ); + returnsWeekday = nextPresent + ? formatWeekday(nextPresent, timeZone) + : null; + } + result.push({ + slug: childSlug, + name, + present, + due, + completed, + returnsWeekday, + }); + } + return result.sort((a, b) => a.name.localeCompare(b.name)); + } + + private _renderHeader() { + const slug = this._childSlug(); + const name = this._childName(slug); + const displayName = name ?? (slug ? titleCaseSlug(slug) : null); + const { due, completed } = this._childCounts(slug); + const remaining = this._remaining(); + const party = this._partyCounts(); + const title = displayName ? `${displayName}'s Quest Log` : "Quest Log"; + const initial = (displayName?.charAt(0) || "?").toUpperCase(); + return html` +
    + +
    +

    ${title}

    +

    + ${formatDay(this._now, this._timeZone())} · ${completed} of ${due} + claimed +

    +
    +
    + +
    + + ${remaining} quest${remaining === 1 ? "" : "s"} left + + + Party progress · ${party.completed} of ${party.due} today + +
    +
    +
    + `; + } + + private _renderColumn(windowDef: WindowDef) { + const instances = this._instances().filter( + (instance) => instance.window === windowDef.key + ); + const open = instances + .filter((instance) => instance.state === "open") + .sort((a, b) => + (a.due_time ?? "99:99").localeCompare(b.due_time ?? "99:99") + ); + const sealed = instances.filter( + (instance) => instance.state === "completed" + ); + const count = instances.length; + let stackStyle: string | typeof nothing = nothing; + if (count > 0) { + const rollupReserve = + windowDef.key === "afternoon" && this._otherChildren().length > 0 + ? 160 + : 0; + const available = 644 - rollupReserve; + const gap = Math.min( + 24, + Math.max(8, Math.floor((available - count * 74) / Math.max(count - 1, 1))) + ); + const minHeight = Math.min( + 116, + Math.max(58, Math.floor((available - (count - 1) * gap) / count)) + ); + const buttonHeight = Math.max(56, Math.min(72, minHeight - 2)); + let stackVars = `--nq-p-quest-gap: ${gap}px; --nq-p-quest-min-height: ${minHeight}px; --nq-p-button-height: ${buttonHeight}px`; + if (count >= 5) { + const pad = Math.max(0, Math.min(22, Math.floor((minHeight - buttonHeight - 2) / 2))); + const tile = Math.max(44, Math.min(64, minHeight - 2 * pad - 2)); + stackVars += `; --nq-p-quest-pad: ${pad}px; --nq-p-tile-size: ${tile}px`; + } + stackStyle = stackVars; + } + return html` +
    +
    + ${windowDef.icon} +
    + ${windowDef.name} + ${windowDef.range} +
    + ${sealed.length}/${instances.length} +
    +
    + ${repeat( + open, + (instance) => instance.id, + (instance) => this._renderQuest(instance, false, 0) + )} + ${repeat( + sealed, + (instance) => instance.id, + (instance, index) => this._renderQuest(instance, true, index) + )} + ${windowDef.key === "afternoon" ? this._renderRollup() : nothing} +
    +
    + `; + } + + private _renderQuest( + instance: PanelInstance, + sealed: boolean, + sealedIndex: number + ) { + if (sealed) { + const rotation = SEAL_ROTATIONS[sealedIndex % SEAL_ROTATIONS.length]; + const claimedAt = instance.completed_at + ? formatInstant(instance.completed_at, this._timeZone()) + : ""; + return html` +
    + +
    + ${instance.title} + + ${claimedAt ? `Claimed ${claimedAt}` : "Claimed"} + +
    + +
    + `; + } + const dueAt = formatWallClock(instance.due_time); + const meta = instance.overdue + ? { + text: dueAt ? `Overdue · due ${dueAt}` : "Overdue", + late: true, + } + : { + text: dueAt ? `Due by ${dueAt}` : "Due today", + late: false, + }; + return html` +
    this._openConfirm(instance.id)} + @keydown=${(event: KeyboardEvent) => { + if (event.key === "Enter" || event.key === " ") { + event.preventDefault(); + this._openConfirm(instance.id); + } + }} + > + +
    + ${instance.title} + ${meta.text} +
    + +
    + `; + } + + private _renderRollup() { + const others = this._otherChildren(); + if (others.length === 0) { + return nothing; + } + const sentences = others.slice(0, 2).map((child) => { + if (!child.present) { + return child.returnsWeekday + ? `${child.name} is on travels until ${child.returnsWeekday}.` + : `${child.name} is on travels.`; + } + if (child.due > 0 && child.completed >= child.due) { + return `${child.name} has finished their log.`; + } + if (child.due > child.completed) { + return `${child.name} has ${child.due - child.completed} quests left.`; + } + return `${child.name} has no quests today.`; + }); + return html` +
    + Party roll-up +

    ${sentences.join(" ")}

    +
    + `; + } + + private _renderConfirm() { + if (this._confirm === null) { + return nothing; + } + const instance = this._instances().find( + (candidate) => candidate.id === this._confirm + ); + if (!instance) { + return nothing; + } + const windowDef = WINDOW_DEFS.find((entry) => entry.key === instance.window); + return html` +
    this._closeConfirm()}> + +
    + `; + } + + private _openConfirm(instanceId: number): void { + this._confirm = instanceId; + this._armConfirmTimer(); + } + + private _closeConfirm(): void { + this._clearConfirmTimer(); + this._confirm = null; + } + + private _confirmComplete(): void { + this._closeConfirm(); + } + + private _armConfirmTimer(): void { + this._clearConfirmTimer(); + this._confirmTimer = window.setTimeout(() => { + this._confirmTimer = undefined; + this._closeConfirm(); + }, this._confirmSeconds() * 1000); + } + + private _clearConfirmTimer(): void { + if (this._confirmTimer !== undefined) { + window.clearTimeout(this._confirmTimer); + this._confirmTimer = undefined; + } + } + + private _armIdle(): void { + this._clearIdle(); + this._idleTimer = window.setTimeout(() => { + this._idleTimer = undefined; + this._returnToBoard(); + }, this._idleSeconds() * 1000); + } + + private _clearIdle(): void { + if (this._idleTimer !== undefined) { + window.clearTimeout(this._idleTimer); + this._idleTimer = undefined; + } + } + + private _returnToBoard(): void { + const path = this._boardPath(); + if (!path) { + return; + } + window.history.pushState(null, "", path); + window.dispatchEvent(new Event("location-changed")); + } + + private _stopClock(): void { + if (this._clockTimer !== undefined) { + window.clearInterval(this._clockTimer); + this._clockTimer = undefined; + } + } + + private _onActivity = (): void => { + this._armIdle(); + if (this._confirm !== null) { + this._armConfirmTimer(); + } + }; } customElements.define("nestquest-quest-log-card", NestQuestQuestLogCard); From 028215fa3892c8b3cd5252af5274427c4c068410 Mon Sep 17 00:00:00 2001 From: dev_agent <17+dev_agent@code.cubecraftcreations.com> Date: Sun, 20 Sep 2026 14:04:58 -0400 Subject: [PATCH 05/11] Define and implement the panel's empty and error states Define and implement the panel's empty and error states --- .../nestquest/www/nestquest-cards.js | 1877 ++++++++++------- design/PANEL-EMPTY-STATES.md | 104 + frontend/src/cards/party-board.ts | 194 +- frontend/src/cards/quest-log.ts | 366 +++- 4 files changed, 1768 insertions(+), 773 deletions(-) create mode 100644 design/PANEL-EMPTY-STATES.md diff --git a/custom_components/nestquest/www/nestquest-cards.js b/custom_components/nestquest/www/nestquest-cards.js index 85e4f59..6b9d8d9 100644 --- a/custom_components/nestquest/www/nestquest-cards.js +++ b/custom_components/nestquest/www/nestquest-cards.js @@ -1,200 +1,200 @@ -const L = globalThis, Y = L.ShadowRoot && (L.ShadyCSS === void 0 || L.ShadyCSS.nativeShadow) && "adoptedStyleSheets" in Document.prototype && "replace" in CSSStyleSheet.prototype, tt = /* @__PURE__ */ Symbol(), rt = /* @__PURE__ */ new WeakMap(); -let Nt = class { - constructor(t, e, n) { - if (this._$cssResult$ = !0, n !== tt) throw Error("CSSResult is not constructable. Use `unsafeCSS` or `css` instead."); - this.cssText = t, this.t = e; +const L = globalThis, te = L.ShadowRoot && (L.ShadyCSS === void 0 || L.ShadyCSS.nativeShadow) && "adoptedStyleSheets" in Document.prototype && "replace" in CSSStyleSheet.prototype, ne = /* @__PURE__ */ Symbol(), ae = /* @__PURE__ */ new WeakMap(); +let ze = class { + constructor(e, t, n) { + if (this._$cssResult$ = !0, n !== ne) throw Error("CSSResult is not constructable. Use `unsafeCSS` or `css` instead."); + this.cssText = e, this.t = t; } get styleSheet() { - let t = this.o; - const e = this.t; - if (Y && t === void 0) { - const n = e !== void 0 && e.length === 1; - n && (t = rt.get(e)), t === void 0 && ((this.o = t = new CSSStyleSheet()).replaceSync(this.cssText), n && rt.set(e, t)); + let e = this.o; + const t = this.t; + if (te && e === void 0) { + const n = t !== void 0 && t.length === 1; + n && (e = ae.get(t)), e === void 0 && ((this.o = e = new CSSStyleSheet()).replaceSync(this.cssText), n && ae.set(t, e)); } - return t; + return e; } toString() { return this.cssText; } }; -const b = (i) => new Nt(typeof i == "string" ? i : i + "", void 0, tt), Tt = (i, ...t) => { - const e = i.length === 1 ? i[0] : t.reduce((n, s, r) => n + ((o) => { +const v = (s) => new ze(typeof s == "string" ? s : s + "", void 0, ne), Pe = (s, ...e) => { + const t = s.length === 1 ? s[0] : e.reduce((n, i, r) => n + ((o) => { if (o._$cssResult$ === !0) return o.cssText; if (typeof o == "number") return o; throw Error("Value passed to 'css' function must be a 'css' function result: " + o + ". Use 'unsafeCSS' to pass non-literal values, but take care to ensure page security."); - })(s) + i[r + 1], i[0]); - return new Nt(e, i, tt); -}, Dt = (i, t) => { - if (Y) i.adoptedStyleSheets = t.map((e) => e instanceof CSSStyleSheet ? e : e.styleSheet); - else for (const e of t) { - const n = document.createElement("style"), s = L.litNonce; - s !== void 0 && n.setAttribute("nonce", s), n.textContent = e.cssText, i.appendChild(n); + })(i) + s[r + 1], s[0]); + return new ze(t, s, ne); +}, Be = (s, e) => { + if (te) s.adoptedStyleSheets = e.map((t) => t instanceof CSSStyleSheet ? t : t.styleSheet); + else for (const t of e) { + const n = document.createElement("style"), i = L.litNonce; + i !== void 0 && n.setAttribute("nonce", i), n.textContent = t.cssText, s.appendChild(n); } -}, ot = Y ? (i) => i : (i) => i instanceof CSSStyleSheet ? ((t) => { - let e = ""; - for (const n of t.cssRules) e += n.cssText; - return b(e); -})(i) : i; -const { is: Ht, defineProperty: jt, getOwnPropertyDescriptor: Lt, getOwnPropertyNames: Rt, getOwnPropertySymbols: Bt, getPrototypeOf: Ft } = Object, F = globalThis, at = F.trustedTypes, Qt = at ? at.emptyScript : "", Wt = F.reactiveElementPolyfillSupport, U = (i, t) => i, X = { toAttribute(i, t) { - switch (t) { +}, le = te ? (s) => s : (s) => s instanceof CSSStyleSheet ? ((e) => { + let t = ""; + for (const n of e.cssRules) t += n.cssText; + return v(t); +})(s) : s; +const { is: Fe, defineProperty: Qe, getOwnPropertyDescriptor: We, getOwnPropertyNames: Ze, getOwnPropertySymbols: Ve, getPrototypeOf: Ge } = Object, Q = globalThis, de = Q.trustedTypes, Ke = de ? de.emptyScript : "", Je = Q.reactiveElementPolyfillSupport, P = (s, e) => s, ee = { toAttribute(s, e) { + switch (e) { case Boolean: - i = i ? Qt : null; + s = s ? Ke : null; break; case Object: case Array: - i = i == null ? i : JSON.stringify(i); + s = s == null ? s : JSON.stringify(s); } - return i; -}, fromAttribute(i, t) { - let e = i; - switch (t) { + return s; +}, fromAttribute(s, e) { + let t = s; + switch (e) { case Boolean: - e = i !== null; + t = s !== null; break; case Number: - e = i === null ? null : Number(i); + t = s === null ? null : Number(s); break; case Object: case Array: try { - e = JSON.parse(i); + t = JSON.parse(s); } catch { - e = null; + t = null; } } - return e; -} }, Mt = (i, t) => !Ht(i, t), lt = { attribute: !0, type: String, converter: X, reflect: !1, useDefault: !1, hasChanged: Mt }; -Symbol.metadata ??= /* @__PURE__ */ Symbol("metadata"), F.litPropertyMetadata ??= /* @__PURE__ */ new WeakMap(); -let T = class extends HTMLElement { - static addInitializer(t) { - this._$Ei(), (this.l ??= []).push(t); + return t; +} }, Ie = (s, e) => !Fe(s, e), ce = { attribute: !0, type: String, converter: ee, reflect: !1, useDefault: !1, hasChanged: Ie }; +Symbol.metadata ??= /* @__PURE__ */ Symbol("metadata"), Q.litPropertyMetadata ??= /* @__PURE__ */ new WeakMap(); +let N = class extends HTMLElement { + static addInitializer(e) { + this._$Ei(), (this.l ??= []).push(e); } static get observedAttributes() { return this.finalize(), this._$Eh && [...this._$Eh.keys()]; } - static createProperty(t, e = lt) { - if (e.state && (e.attribute = !1), this._$Ei(), this.prototype.hasOwnProperty(t) && ((e = Object.create(e)).wrapped = !0), this.elementProperties.set(t, e), !e.noAccessor) { - const n = /* @__PURE__ */ Symbol(), s = this.getPropertyDescriptor(t, n, e); - s !== void 0 && jt(this.prototype, t, s); + static createProperty(e, t = ce) { + if (t.state && (t.attribute = !1), this._$Ei(), this.prototype.hasOwnProperty(e) && ((t = Object.create(t)).wrapped = !0), this.elementProperties.set(e, t), !t.noAccessor) { + const n = /* @__PURE__ */ Symbol(), i = this.getPropertyDescriptor(e, n, t); + i !== void 0 && Qe(this.prototype, e, i); } } - static getPropertyDescriptor(t, e, n) { - const { get: s, set: r } = Lt(this.prototype, t) ?? { get() { - return this[e]; + static getPropertyDescriptor(e, t, n) { + const { get: i, set: r } = We(this.prototype, e) ?? { get() { + return this[t]; }, set(o) { - this[e] = o; + this[t] = o; } }; - return { get: s, set(o) { - const l = s?.call(this); - r?.call(this, o), this.requestUpdate(t, l, n); + return { get: i, set(o) { + const l = i?.call(this); + r?.call(this, o), this.requestUpdate(e, l, n); }, configurable: !0, enumerable: !0 }; } - static getPropertyOptions(t) { - return this.elementProperties.get(t) ?? lt; + static getPropertyOptions(e) { + return this.elementProperties.get(e) ?? ce; } static _$Ei() { - if (this.hasOwnProperty(U("elementProperties"))) return; - const t = Ft(this); - t.finalize(), t.l !== void 0 && (this.l = [...t.l]), this.elementProperties = new Map(t.elementProperties); + if (this.hasOwnProperty(P("elementProperties"))) return; + const e = Ge(this); + e.finalize(), e.l !== void 0 && (this.l = [...e.l]), this.elementProperties = new Map(e.elementProperties); } static finalize() { - if (this.hasOwnProperty(U("finalized"))) return; - if (this.finalized = !0, this._$Ei(), this.hasOwnProperty(U("properties"))) { - const e = this.properties, n = [...Rt(e), ...Bt(e)]; - for (const s of n) this.createProperty(s, e[s]); + if (this.hasOwnProperty(P("finalized"))) return; + if (this.finalized = !0, this._$Ei(), this.hasOwnProperty(P("properties"))) { + const t = this.properties, n = [...Ze(t), ...Ve(t)]; + for (const i of n) this.createProperty(i, t[i]); } - const t = this[Symbol.metadata]; - if (t !== null) { - const e = litPropertyMetadata.get(t); - if (e !== void 0) for (const [n, s] of e) this.elementProperties.set(n, s); + const e = this[Symbol.metadata]; + if (e !== null) { + const t = litPropertyMetadata.get(e); + if (t !== void 0) for (const [n, i] of t) this.elementProperties.set(n, i); } this._$Eh = /* @__PURE__ */ new Map(); - for (const [e, n] of this.elementProperties) { - const s = this._$Eu(e, n); - s !== void 0 && this._$Eh.set(s, e); + for (const [t, n] of this.elementProperties) { + const i = this._$Eu(t, n); + i !== void 0 && this._$Eh.set(i, t); } this.elementStyles = this.finalizeStyles(this.styles); } - static finalizeStyles(t) { - const e = []; - if (Array.isArray(t)) { - const n = new Set(t.flat(1 / 0).reverse()); - for (const s of n) e.unshift(ot(s)); - } else t !== void 0 && e.push(ot(t)); - return e; + static finalizeStyles(e) { + const t = []; + if (Array.isArray(e)) { + const n = new Set(e.flat(1 / 0).reverse()); + for (const i of n) t.unshift(le(i)); + } else e !== void 0 && t.push(le(e)); + return t; } - static _$Eu(t, e) { - const n = e.attribute; - return n === !1 ? void 0 : typeof n == "string" ? n : typeof t == "string" ? t.toLowerCase() : void 0; + static _$Eu(e, t) { + const n = t.attribute; + return n === !1 ? void 0 : typeof n == "string" ? n : typeof e == "string" ? e.toLowerCase() : void 0; } constructor() { super(), this._$Ep = void 0, this.isUpdatePending = !1, this.hasUpdated = !1, this._$Em = null, this._$Ev(); } _$Ev() { - this._$ES = new Promise((t) => this.enableUpdating = t), this._$AL = /* @__PURE__ */ new Map(), this._$E_(), this.requestUpdate(), this.constructor.l?.forEach((t) => t(this)); + this._$ES = new Promise((e) => this.enableUpdating = e), this._$AL = /* @__PURE__ */ new Map(), this._$E_(), this.requestUpdate(), this.constructor.l?.forEach((e) => e(this)); } - addController(t) { - (this._$EO ??= /* @__PURE__ */ new Set()).add(t), this.renderRoot !== void 0 && this.isConnected && t.hostConnected?.(); + addController(e) { + (this._$EO ??= /* @__PURE__ */ new Set()).add(e), this.renderRoot !== void 0 && this.isConnected && e.hostConnected?.(); } - removeController(t) { - this._$EO?.delete(t); + removeController(e) { + this._$EO?.delete(e); } _$E_() { - const t = /* @__PURE__ */ new Map(), e = this.constructor.elementProperties; - for (const n of e.keys()) this.hasOwnProperty(n) && (t.set(n, this[n]), delete this[n]); - t.size > 0 && (this._$Ep = t); + const e = /* @__PURE__ */ new Map(), t = this.constructor.elementProperties; + for (const n of t.keys()) this.hasOwnProperty(n) && (e.set(n, this[n]), delete this[n]); + e.size > 0 && (this._$Ep = e); } createRenderRoot() { - const t = this.shadowRoot ?? this.attachShadow(this.constructor.shadowRootOptions); - return Dt(t, this.constructor.elementStyles), t; + const e = this.shadowRoot ?? this.attachShadow(this.constructor.shadowRootOptions); + return Be(e, this.constructor.elementStyles), e; } connectedCallback() { - this.renderRoot ??= this.createRenderRoot(), this.enableUpdating(!0), this._$EO?.forEach((t) => t.hostConnected?.()); + this.renderRoot ??= this.createRenderRoot(), this.enableUpdating(!0), this._$EO?.forEach((e) => e.hostConnected?.()); } - enableUpdating(t) { + enableUpdating(e) { } disconnectedCallback() { - this._$EO?.forEach((t) => t.hostDisconnected?.()); + this._$EO?.forEach((e) => e.hostDisconnected?.()); } - attributeChangedCallback(t, e, n) { - this._$AK(t, n); + attributeChangedCallback(e, t, n) { + this._$AK(e, n); } - _$ET(t, e) { - const n = this.constructor.elementProperties.get(t), s = this.constructor._$Eu(t, n); - if (s !== void 0 && n.reflect === !0) { - const r = (n.converter?.toAttribute !== void 0 ? n.converter : X).toAttribute(e, n.type); - this._$Em = t, r == null ? this.removeAttribute(s) : this.setAttribute(s, r), this._$Em = null; + _$ET(e, t) { + const n = this.constructor.elementProperties.get(e), i = this.constructor._$Eu(e, n); + if (i !== void 0 && n.reflect === !0) { + const r = (n.converter?.toAttribute !== void 0 ? n.converter : ee).toAttribute(t, n.type); + this._$Em = e, r == null ? this.removeAttribute(i) : this.setAttribute(i, r), this._$Em = null; } } - _$AK(t, e) { - const n = this.constructor, s = n._$Eh.get(t); - if (s !== void 0 && this._$Em !== s) { - const r = n.getPropertyOptions(s), o = typeof r.converter == "function" ? { fromAttribute: r.converter } : r.converter?.fromAttribute !== void 0 ? r.converter : X; - this._$Em = s; - const l = o.fromAttribute(e, r.type); - this[s] = l ?? this._$Ej?.get(s) ?? l, this._$Em = null; + _$AK(e, t) { + const n = this.constructor, i = n._$Eh.get(e); + if (i !== void 0 && this._$Em !== i) { + const r = n.getPropertyOptions(i), o = typeof r.converter == "function" ? { fromAttribute: r.converter } : r.converter?.fromAttribute !== void 0 ? r.converter : ee; + this._$Em = i; + const l = o.fromAttribute(t, r.type); + this[i] = l ?? this._$Ej?.get(i) ?? l, this._$Em = null; } } - requestUpdate(t, e, n, s = !1, r) { - if (t !== void 0) { + requestUpdate(e, t, n, i = !1, r) { + if (e !== void 0) { const o = this.constructor; - if (s === !1 && (r = this[t]), n ??= o.getPropertyOptions(t), !((n.hasChanged ?? Mt)(r, e) || n.useDefault && n.reflect && r === this._$Ej?.get(t) && !this.hasAttribute(o._$Eu(t, n)))) return; - this.C(t, e, n); + if (i === !1 && (r = this[e]), n ??= o.getPropertyOptions(e), !((n.hasChanged ?? Ie)(r, t) || n.useDefault && n.reflect && r === this._$Ej?.get(e) && !this.hasAttribute(o._$Eu(e, n)))) return; + this.C(e, t, n); } this.isUpdatePending === !1 && (this._$ES = this._$EP()); } - C(t, e, { useDefault: n, reflect: s, wrapped: r }, o) { - n && !(this._$Ej ??= /* @__PURE__ */ new Map()).has(t) && (this._$Ej.set(t, o ?? e ?? this[t]), r !== !0 || o !== void 0) || (this._$AL.has(t) || (this.hasUpdated || n || (e = void 0), this._$AL.set(t, e)), s === !0 && this._$Em !== t && (this._$Eq ??= /* @__PURE__ */ new Set()).add(t)); + C(e, t, { useDefault: n, reflect: i, wrapped: r }, o) { + n && !(this._$Ej ??= /* @__PURE__ */ new Map()).has(e) && (this._$Ej.set(e, o ?? t ?? this[e]), r !== !0 || o !== void 0) || (this._$AL.has(e) || (this.hasUpdated || n || (t = void 0), this._$AL.set(e, t)), i === !0 && this._$Em !== e && (this._$Eq ??= /* @__PURE__ */ new Set()).add(e)); } async _$EP() { this.isUpdatePending = !0; try { await this._$ES; - } catch (e) { - Promise.reject(e); + } catch (t) { + Promise.reject(t); } - const t = this.scheduleUpdate(); - return t != null && await t, !this.isUpdatePending; + const e = this.scheduleUpdate(); + return e != null && await e, !this.isUpdatePending; } scheduleUpdate() { return this.performUpdate(); @@ -203,28 +203,28 @@ let T = class extends HTMLElement { if (!this.isUpdatePending) return; if (!this.hasUpdated) { if (this.renderRoot ??= this.createRenderRoot(), this._$Ep) { - for (const [s, r] of this._$Ep) this[s] = r; + for (const [i, r] of this._$Ep) this[i] = r; this._$Ep = void 0; } const n = this.constructor.elementProperties; - if (n.size > 0) for (const [s, r] of n) { - const { wrapped: o } = r, l = this[s]; - o !== !0 || this._$AL.has(s) || l === void 0 || this.C(s, void 0, r, l); + if (n.size > 0) for (const [i, r] of n) { + const { wrapped: o } = r, l = this[i]; + o !== !0 || this._$AL.has(i) || l === void 0 || this.C(i, void 0, r, l); } } - let t = !1; - const e = this._$AL; + let e = !1; + const t = this._$AL; try { - t = this.shouldUpdate(e), t ? (this.willUpdate(e), this._$EO?.forEach((n) => n.hostUpdate?.()), this.update(e)) : this._$EM(); + e = this.shouldUpdate(t), e ? (this.willUpdate(t), this._$EO?.forEach((n) => n.hostUpdate?.()), this.update(t)) : this._$EM(); } catch (n) { - throw t = !1, this._$EM(), n; + throw e = !1, this._$EM(), n; } - t && this._$AE(e); + e && this._$AE(t); } - willUpdate(t) { + willUpdate(e) { } - _$AE(t) { - this._$EO?.forEach((e) => e.hostUpdated?.()), this.hasUpdated || (this.hasUpdated = !0, this.firstUpdated(t)), this.updated(t); + _$AE(e) { + this._$EO?.forEach((t) => t.hostUpdated?.()), this.hasUpdated || (this.hasUpdated = !0, this.firstUpdated(e)), this.updated(e); } _$EM() { this._$AL = /* @__PURE__ */ new Map(), this.isUpdatePending = !1; @@ -235,83 +235,83 @@ let T = class extends HTMLElement { getUpdateComplete() { return this._$ES; } - shouldUpdate(t) { + shouldUpdate(e) { return !0; } - update(t) { - this._$Eq &&= this._$Eq.forEach((e) => this._$ET(e, this[e])), this._$EM(); + update(e) { + this._$Eq &&= this._$Eq.forEach((t) => this._$ET(t, this[t])), this._$EM(); } - updated(t) { + updated(e) { } - firstUpdated(t) { + firstUpdated(e) { } }; -T.elementStyles = [], T.shadowRootOptions = { mode: "open" }, T[U("elementProperties")] = /* @__PURE__ */ new Map(), T[U("finalized")] = /* @__PURE__ */ new Map(), Wt?.({ ReactiveElement: T }), (F.reactiveElementVersions ??= []).push("2.1.2"); -const et = globalThis, dt = (i) => i, R = et.trustedTypes, pt = R ? R.createPolicy("lit-html", { createHTML: (i) => i }) : void 0, zt = "$lit$", x = `lit$${Math.random().toFixed(9).slice(2)}$`, Pt = "?" + x, Zt = `<${Pt}>`, S = document, I = () => S.createComment(""), D = (i) => i === null || typeof i != "object" && typeof i != "function", nt = Array.isArray, Vt = (i) => nt(i) || typeof i?.[Symbol.iterator] == "function", W = `[ -\f\r]`, P = /<(?:(!--|\/[^a-zA-Z])|(\/?[a-zA-Z][^>\s]*)|(\/?$))/g, ct = /-->/g, ht = />/g, q = RegExp(`>|${W}(?:([^\\s"'>=/]+)(${W}*=${W}*(?:[^ -\f\r"'\`<>=]|("|')|))|$)`, "g"), ut = /'/g, ft = /"/g, Ot = /^(?:script|style|textarea|title)$/i, Kt = (i) => (t, ...e) => ({ _$litType$: i, strings: t, values: e }), g = Kt(1), E = /* @__PURE__ */ Symbol.for("lit-noChange"), m = /* @__PURE__ */ Symbol.for("lit-nothing"), mt = /* @__PURE__ */ new WeakMap(), A = S.createTreeWalker(S, 129); -function Ut(i, t) { - if (!nt(i) || !i.hasOwnProperty("raw")) throw Error("invalid template strings array"); - return pt !== void 0 ? pt.createHTML(t) : t; +N.elementStyles = [], N.shadowRootOptions = { mode: "open" }, N[P("elementProperties")] = /* @__PURE__ */ new Map(), N[P("finalized")] = /* @__PURE__ */ new Map(), Je?.({ ReactiveElement: N }), (Q.reactiveElementVersions ??= []).push("2.1.2"); +const se = globalThis, pe = (s) => s, B = se.trustedTypes, he = B ? B.createPolicy("lit-html", { createHTML: (s) => s }) : void 0, Ue = "$lit$", x = `lit$${Math.random().toFixed(9).slice(2)}$`, De = "?" + x, Xe = `<${De}>`, S = document, I = () => S.createComment(""), U = (s) => s === null || typeof s != "object" && typeof s != "function", ie = Array.isArray, Ye = (s) => ie(s) || typeof s?.[Symbol.iterator] == "function", V = `[ +\f\r]`, O = /<(?:(!--|\/[^a-zA-Z])|(\/?[a-zA-Z][^>\s]*)|(\/?$))/g, ue = /-->/g, fe = />/g, q = RegExp(`>|${V}(?:([^\\s"'>=/]+)(${V}*=${V}*(?:[^ +\f\r"'\`<>=]|("|')|))|$)`, "g"), me = /'/g, ge = /"/g, He = /^(?:script|style|textarea|title)$/i, et = (s) => (e, ...t) => ({ _$litType$: s, strings: e, values: t }), p = et(1), E = /* @__PURE__ */ Symbol.for("lit-noChange"), g = /* @__PURE__ */ Symbol.for("lit-nothing"), ye = /* @__PURE__ */ new WeakMap(), A = S.createTreeWalker(S, 129); +function je(s, e) { + if (!ie(s) || !s.hasOwnProperty("raw")) throw Error("invalid template strings array"); + return he !== void 0 ? he.createHTML(e) : e; } -const Jt = (i, t) => { - const e = i.length - 1, n = []; - let s, r = t === 2 ? "" : t === 3 ? "" : "", o = P; - for (let l = 0; l < e; l++) { - const a = i[l]; - let p, u, d = -1, h = 0; - for (; h < a.length && (o.lastIndex = h, u = o.exec(a), u !== null); ) h = o.lastIndex, o === P ? u[1] === "!--" ? o = ct : u[1] !== void 0 ? o = ht : u[2] !== void 0 ? (Ot.test(u[2]) && (s = RegExp("" ? (o = s ?? P, d = -1) : u[1] === void 0 ? d = -2 : (d = o.lastIndex - u[2].length, p = u[1], o = u[3] === void 0 ? q : u[3] === '"' ? ft : ut) : o === ft || o === ut ? o = q : o === ct || o === ht ? o = P : (o = q, s = void 0); - const c = o === q && i[l + 1].startsWith("/>") ? " " : ""; - r += o === P ? a + Zt : d >= 0 ? (n.push(p), a.slice(0, d) + zt + a.slice(d) + x + c) : a + x + (d === -2 ? l : c); +const tt = (s, e) => { + const t = s.length - 1, n = []; + let i, r = e === 2 ? "" : e === 3 ? "" : "", o = O; + for (let l = 0; l < t; l++) { + const a = s[l]; + let c, u, d = -1, f = 0; + for (; f < a.length && (o.lastIndex = f, u = o.exec(a), u !== null); ) f = o.lastIndex, o === O ? u[1] === "!--" ? o = ue : u[1] !== void 0 ? o = fe : u[2] !== void 0 ? (He.test(u[2]) && (i = RegExp("" ? (o = i ?? O, d = -1) : u[1] === void 0 ? d = -2 : (d = o.lastIndex - u[2].length, c = u[1], o = u[3] === void 0 ? q : u[3] === '"' ? ge : me) : o === ge || o === me ? o = q : o === ue || o === fe ? o = O : (o = q, i = void 0); + const h = o === q && s[l + 1].startsWith("/>") ? " " : ""; + r += o === O ? a + Xe : d >= 0 ? (n.push(c), a.slice(0, d) + Ue + a.slice(d) + x + h) : a + x + (d === -2 ? l : h); } - return [Ut(i, r + (i[e] || "") + (t === 2 ? "" : t === 3 ? "" : "")), n]; + return [je(s, r + (s[t] || "") + (e === 2 ? "" : e === 3 ? "" : "")), n]; }; -class H { - constructor({ strings: t, _$litType$: e }, n) { - let s; +class D { + constructor({ strings: e, _$litType$: t }, n) { + let i; this.parts = []; let r = 0, o = 0; - const l = t.length - 1, a = this.parts, [p, u] = Jt(t, e); - if (this.el = H.createElement(p, n), A.currentNode = this.el.content, e === 2 || e === 3) { + const l = e.length - 1, a = this.parts, [c, u] = tt(e, t); + if (this.el = D.createElement(c, n), A.currentNode = this.el.content, t === 2 || t === 3) { const d = this.el.content.firstChild; d.replaceWith(...d.childNodes); } - for (; (s = A.nextNode()) !== null && a.length < l; ) { - if (s.nodeType === 1) { - if (s.hasAttributes()) for (const d of s.getAttributeNames()) if (d.endsWith(zt)) { - const h = u[o++], c = s.getAttribute(d).split(x), f = /([.?@])?(.*)/.exec(h); - a.push({ type: 1, index: r, name: f[2], strings: c, ctor: f[1] === "." ? Xt : f[1] === "?" ? Yt : f[1] === "@" ? te : Q }), s.removeAttribute(d); - } else d.startsWith(x) && (a.push({ type: 6, index: r }), s.removeAttribute(d)); - if (Ot.test(s.tagName)) { - const d = s.textContent.split(x), h = d.length - 1; - if (h > 0) { - s.textContent = R ? R.emptyScript : ""; - for (let c = 0; c < h; c++) s.append(d[c], I()), A.nextNode(), a.push({ type: 2, index: ++r }); - s.append(d[h], I()); + for (; (i = A.nextNode()) !== null && a.length < l; ) { + if (i.nodeType === 1) { + if (i.hasAttributes()) for (const d of i.getAttributeNames()) if (d.endsWith(Ue)) { + const f = u[o++], h = i.getAttribute(d).split(x), m = /([.?@])?(.*)/.exec(f); + a.push({ type: 1, index: r, name: m[2], strings: h, ctor: m[1] === "." ? st : m[1] === "?" ? it : m[1] === "@" ? rt : W }), i.removeAttribute(d); + } else d.startsWith(x) && (a.push({ type: 6, index: r }), i.removeAttribute(d)); + if (He.test(i.tagName)) { + const d = i.textContent.split(x), f = d.length - 1; + if (f > 0) { + i.textContent = B ? B.emptyScript : ""; + for (let h = 0; h < f; h++) i.append(d[h], I()), A.nextNode(), a.push({ type: 2, index: ++r }); + i.append(d[f], I()); } } - } else if (s.nodeType === 8) if (s.data === Pt) a.push({ type: 2, index: r }); + } else if (i.nodeType === 8) if (i.data === De) a.push({ type: 2, index: r }); else { let d = -1; - for (; (d = s.data.indexOf(x, d + 1)) !== -1; ) a.push({ type: 7, index: r }), d += x.length - 1; + for (; (d = i.data.indexOf(x, d + 1)) !== -1; ) a.push({ type: 7, index: r }), d += x.length - 1; } r++; } } - static createElement(t, e) { + static createElement(e, t) { const n = S.createElement("template"); - return n.innerHTML = t, n; + return n.innerHTML = e, n; } } -function M(i, t, e = i, n) { - if (t === E) return t; - let s = n !== void 0 ? e._$Co?.[n] : e._$Cl; - const r = D(t) ? void 0 : t._$litDirective$; - return s?.constructor !== r && (s?._$AO?.(!1), r === void 0 ? s = void 0 : (s = new r(i), s._$AT(i, e, n)), n !== void 0 ? (e._$Co ??= [])[n] = s : e._$Cl = s), s !== void 0 && (t = M(i, s._$AS(i, t.values), s, n)), t; +function T(s, e, t = s, n) { + if (e === E) return e; + let i = n !== void 0 ? t._$Co?.[n] : t._$Cl; + const r = U(e) ? void 0 : e._$litDirective$; + return i?.constructor !== r && (i?._$AO?.(!1), r === void 0 ? i = void 0 : (i = new r(s), i._$AT(s, t, n)), n !== void 0 ? (t._$Co ??= [])[n] = i : t._$Cl = i), i !== void 0 && (e = T(s, i._$AS(s, e.values), i, n)), e; } -class Gt { - constructor(t, e) { - this._$AV = [], this._$AN = void 0, this._$AD = t, this._$AM = e; +class nt { + constructor(e, t) { + this._$AV = [], this._$AN = void 0, this._$AD = e, this._$AM = t; } get parentNode() { return this._$AM.parentNode; @@ -319,35 +319,35 @@ class Gt { get _$AU() { return this._$AM._$AU; } - u(t) { - const { el: { content: e }, parts: n } = this._$AD, s = (t?.creationScope ?? S).importNode(e, !0); - A.currentNode = s; + u(e) { + const { el: { content: t }, parts: n } = this._$AD, i = (e?.creationScope ?? S).importNode(t, !0); + A.currentNode = i; let r = A.nextNode(), o = 0, l = 0, a = n[0]; for (; a !== void 0; ) { if (o === a.index) { - let p; - a.type === 2 ? p = new z(r, r.nextSibling, this, t) : a.type === 1 ? p = new a.ctor(r, a.name, a.strings, this, t) : a.type === 6 && (p = new ee(r, this, t)), this._$AV.push(p), a = n[++l]; + let c; + a.type === 2 ? c = new M(r, r.nextSibling, this, e) : a.type === 1 ? c = new a.ctor(r, a.name, a.strings, this, e) : a.type === 6 && (c = new ot(r, this, e)), this._$AV.push(c), a = n[++l]; } o !== a?.index && (r = A.nextNode(), o++); } - return A.currentNode = S, s; + return A.currentNode = S, i; } - p(t) { - let e = 0; - for (const n of this._$AV) n !== void 0 && (n.strings !== void 0 ? (n._$AI(t, n, e), e += n.strings.length - 2) : n._$AI(t[e])), e++; + p(e) { + let t = 0; + for (const n of this._$AV) n !== void 0 && (n.strings !== void 0 ? (n._$AI(e, n, t), t += n.strings.length - 2) : n._$AI(e[t])), t++; } } -class z { +class M { get _$AU() { return this._$AM?._$AU ?? this._$Cv; } - constructor(t, e, n, s) { - this.type = 2, this._$AH = m, this._$AN = void 0, this._$AA = t, this._$AB = e, this._$AM = n, this.options = s, this._$Cv = s?.isConnected ?? !0; + constructor(e, t, n, i) { + this.type = 2, this._$AH = g, this._$AN = void 0, this._$AA = e, this._$AB = t, this._$AM = n, this.options = i, this._$Cv = i?.isConnected ?? !0; } get parentNode() { - let t = this._$AA.parentNode; - const e = this._$AM; - return e !== void 0 && t?.nodeType === 11 && (t = e.parentNode), t; + let e = this._$AA.parentNode; + const t = this._$AM; + return t !== void 0 && e?.nodeType === 11 && (e = t.parentNode), e; } get startNode() { return this._$AA; @@ -355,135 +355,135 @@ class z { get endNode() { return this._$AB; } - _$AI(t, e = this) { - t = M(this, t, e), D(t) ? t === m || t == null || t === "" ? (this._$AH !== m && this._$AR(), this._$AH = m) : t !== this._$AH && t !== E && this._(t) : t._$litType$ !== void 0 ? this.$(t) : t.nodeType !== void 0 ? this.T(t) : Vt(t) ? this.k(t) : this._(t); + _$AI(e, t = this) { + e = T(this, e, t), U(e) ? e === g || e == null || e === "" ? (this._$AH !== g && this._$AR(), this._$AH = g) : e !== this._$AH && e !== E && this._(e) : e._$litType$ !== void 0 ? this.$(e) : e.nodeType !== void 0 ? this.T(e) : Ye(e) ? this.k(e) : this._(e); } - O(t) { - return this._$AA.parentNode.insertBefore(t, this._$AB); + O(e) { + return this._$AA.parentNode.insertBefore(e, this._$AB); } - T(t) { - this._$AH !== t && (this._$AR(), this._$AH = this.O(t)); + T(e) { + this._$AH !== e && (this._$AR(), this._$AH = this.O(e)); } - _(t) { - this._$AH !== m && D(this._$AH) ? this._$AA.nextSibling.data = t : this.T(S.createTextNode(t)), this._$AH = t; + _(e) { + this._$AH !== g && U(this._$AH) ? this._$AA.nextSibling.data = e : this.T(S.createTextNode(e)), this._$AH = e; } - $(t) { - const { values: e, _$litType$: n } = t, s = typeof n == "number" ? this._$AC(t) : (n.el === void 0 && (n.el = H.createElement(Ut(n.h, n.h[0]), this.options)), n); - if (this._$AH?._$AD === s) this._$AH.p(e); + $(e) { + const { values: t, _$litType$: n } = e, i = typeof n == "number" ? this._$AC(e) : (n.el === void 0 && (n.el = D.createElement(je(n.h, n.h[0]), this.options)), n); + if (this._$AH?._$AD === i) this._$AH.p(t); else { - const r = new Gt(s, this), o = r.u(this.options); - r.p(e), this.T(o), this._$AH = r; + const r = new nt(i, this), o = r.u(this.options); + r.p(t), this.T(o), this._$AH = r; } } - _$AC(t) { - let e = mt.get(t.strings); - return e === void 0 && mt.set(t.strings, e = new H(t)), e; + _$AC(e) { + let t = ye.get(e.strings); + return t === void 0 && ye.set(e.strings, t = new D(e)), t; } - k(t) { - nt(this._$AH) || (this._$AH = [], this._$AR()); - const e = this._$AH; - let n, s = 0; - for (const r of t) s === e.length ? e.push(n = new z(this.O(I()), this.O(I()), this, this.options)) : n = e[s], n._$AI(r), s++; - s < e.length && (this._$AR(n && n._$AB.nextSibling, s), e.length = s); + k(e) { + ie(this._$AH) || (this._$AH = [], this._$AR()); + const t = this._$AH; + let n, i = 0; + for (const r of e) i === t.length ? t.push(n = new M(this.O(I()), this.O(I()), this, this.options)) : n = t[i], n._$AI(r), i++; + i < t.length && (this._$AR(n && n._$AB.nextSibling, i), t.length = i); } - _$AR(t = this._$AA.nextSibling, e) { - for (this._$AP?.(!1, !0, e); t !== this._$AB; ) { - const n = dt(t).nextSibling; - dt(t).remove(), t = n; + _$AR(e = this._$AA.nextSibling, t) { + for (this._$AP?.(!1, !0, t); e !== this._$AB; ) { + const n = pe(e).nextSibling; + pe(e).remove(), e = n; } } - setConnected(t) { - this._$AM === void 0 && (this._$Cv = t, this._$AP?.(t)); + setConnected(e) { + this._$AM === void 0 && (this._$Cv = e, this._$AP?.(e)); } } -class Q { +class W { get tagName() { return this.element.tagName; } get _$AU() { return this._$AM._$AU; } - constructor(t, e, n, s, r) { - this.type = 1, this._$AH = m, this._$AN = void 0, this.element = t, this.name = e, this._$AM = s, this.options = r, n.length > 2 || n[0] !== "" || n[1] !== "" ? (this._$AH = Array(n.length - 1).fill(new String()), this.strings = n) : this._$AH = m; + constructor(e, t, n, i, r) { + this.type = 1, this._$AH = g, this._$AN = void 0, this.element = e, this.name = t, this._$AM = i, this.options = r, n.length > 2 || n[0] !== "" || n[1] !== "" ? (this._$AH = Array(n.length - 1).fill(new String()), this.strings = n) : this._$AH = g; } - _$AI(t, e = this, n, s) { + _$AI(e, t = this, n, i) { const r = this.strings; let o = !1; - if (r === void 0) t = M(this, t, e, 0), o = !D(t) || t !== this._$AH && t !== E, o && (this._$AH = t); + if (r === void 0) e = T(this, e, t, 0), o = !U(e) || e !== this._$AH && e !== E, o && (this._$AH = e); else { - const l = t; - let a, p; - for (t = r[0], a = 0; a < r.length - 1; a++) p = M(this, l[n + a], e, a), p === E && (p = this._$AH[a]), o ||= !D(p) || p !== this._$AH[a], p === m ? t = m : t !== m && (t += (p ?? "") + r[a + 1]), this._$AH[a] = p; + const l = e; + let a, c; + for (e = r[0], a = 0; a < r.length - 1; a++) c = T(this, l[n + a], t, a), c === E && (c = this._$AH[a]), o ||= !U(c) || c !== this._$AH[a], c === g ? e = g : e !== g && (e += (c ?? "") + r[a + 1]), this._$AH[a] = c; } - o && !s && this.j(t); + o && !i && this.j(e); } - j(t) { - t === m ? this.element.removeAttribute(this.name) : this.element.setAttribute(this.name, t ?? ""); + j(e) { + e === g ? this.element.removeAttribute(this.name) : this.element.setAttribute(this.name, e ?? ""); } } -class Xt extends Q { +class st extends W { constructor() { super(...arguments), this.type = 3; } - j(t) { - this.element[this.name] = t === m ? void 0 : t; + j(e) { + this.element[this.name] = e === g ? void 0 : e; } } -class Yt extends Q { +class it extends W { constructor() { super(...arguments), this.type = 4; } - j(t) { - this.element.toggleAttribute(this.name, !!t && t !== m); + j(e) { + this.element.toggleAttribute(this.name, !!e && e !== g); } } -class te extends Q { - constructor(t, e, n, s, r) { - super(t, e, n, s, r), this.type = 5; +class rt extends W { + constructor(e, t, n, i, r) { + super(e, t, n, i, r), this.type = 5; } - _$AI(t, e = this) { - if ((t = M(this, t, e, 0) ?? m) === E) return; - const n = this._$AH, s = t === m && n !== m || t.capture !== n.capture || t.once !== n.once || t.passive !== n.passive, r = t !== m && (n === m || s); - s && this.element.removeEventListener(this.name, this, n), r && this.element.addEventListener(this.name, this, t), this._$AH = t; + _$AI(e, t = this) { + if ((e = T(this, e, t, 0) ?? g) === E) return; + const n = this._$AH, i = e === g && n !== g || e.capture !== n.capture || e.once !== n.once || e.passive !== n.passive, r = e !== g && (n === g || i); + i && this.element.removeEventListener(this.name, this, n), r && this.element.addEventListener(this.name, this, e), this._$AH = e; } - handleEvent(t) { - typeof this._$AH == "function" ? this._$AH.call(this.options?.host ?? this.element, t) : this._$AH.handleEvent(t); + handleEvent(e) { + typeof this._$AH == "function" ? this._$AH.call(this.options?.host ?? this.element, e) : this._$AH.handleEvent(e); } } -class ee { - constructor(t, e, n) { - this.element = t, this.type = 6, this._$AN = void 0, this._$AM = e, this.options = n; +class ot { + constructor(e, t, n) { + this.element = e, this.type = 6, this._$AN = void 0, this._$AM = t, this.options = n; } get _$AU() { return this._$AM._$AU; } - _$AI(t) { - M(this, t); + _$AI(e) { + T(this, e); } } -const ne = { I: z }, ie = et.litHtmlPolyfillSupport; -ie?.(H, z), (et.litHtmlVersions ??= []).push("3.3.3"); -const se = (i, t, e) => { - const n = e?.renderBefore ?? t; - let s = n._$litPart$; - if (s === void 0) { - const r = e?.renderBefore ?? null; - n._$litPart$ = s = new z(t.insertBefore(I(), r), r, void 0, e ?? {}); +const at = { I: M }, lt = se.litHtmlPolyfillSupport; +lt?.(D, M), (se.litHtmlVersions ??= []).push("3.3.3"); +const dt = (s, e, t) => { + const n = t?.renderBefore ?? e; + let i = n._$litPart$; + if (i === void 0) { + const r = t?.renderBefore ?? null; + n._$litPart$ = i = new M(e.insertBefore(I(), r), r, void 0, t ?? {}); } - return s._$AI(i), s; + return i._$AI(s), i; }; -const it = globalThis; -let C = class extends T { +const re = globalThis; +let C = class extends N { constructor() { super(...arguments), this.renderOptions = { host: this }, this._$Do = void 0; } createRenderRoot() { - const t = super.createRenderRoot(); - return this.renderOptions.renderBefore ??= t.firstChild, t; + const e = super.createRenderRoot(); + return this.renderOptions.renderBefore ??= e.firstChild, e; } - update(t) { - const e = this.render(); - this.hasUpdated || (this.renderOptions.isConnected = this.isConnected), super.update(t), this._$Do = se(e, this.renderRoot, this.renderOptions); + update(e) { + const t = this.render(); + this.hasUpdated || (this.renderOptions.isConnected = this.isConnected), super.update(e), this._$Do = dt(t, this.renderRoot, this.renderOptions); } connectedCallback() { super.connectedCallback(), this._$Do?.setConnected(!0); @@ -495,48 +495,48 @@ let C = class extends T { return E; } }; -C._$litElement$ = !0, C.finalized = !0, it.litElementHydrateSupport?.({ LitElement: C }); -const re = it.litElementPolyfillSupport; -re?.({ LitElement: C }); -(it.litElementVersions ??= []).push("4.2.2"); -const It = ':host{--nq-p-parchment-top: #f0e4c7;--nq-p-parchment-bottom: #ddcca3;--nq-p-parchment: radial-gradient(ellipse at 25% 10%, rgba(255,255,255,.5), transparent 55%), radial-gradient(ellipse at 85% 90%, rgba(120,86,44,.28), transparent 60%), repeating-linear-gradient(93deg, rgba(150,115,70,.05) 0 2px, transparent 2px 6px), repeating-linear-gradient(2deg, rgba(150,115,70,.04) 0 3px, transparent 3px 7px), linear-gradient(var(--nq-p-parchment-top), var(--nq-p-parchment-bottom));--nq-p-vignette: inset 0 0 200px rgba(80,52,20,.35);--nq-p-card-open: linear-gradient(#fdf7e6, #f2e7c9);--nq-p-card-done: linear-gradient(#f0e8d3, #e6dcc2);--nq-p-card-panel: linear-gradient(#fcf6e6, #f1e6ca);--nq-p-card-border: rgba(120,88,48,.38);--nq-p-panel-border: rgba(92,62,26,.4);--nq-p-card-shadow: 0 3px 0 rgba(120,88,48,.2), inset 0 1px 0 rgba(255,255,255,.7);--nq-p-panel-shadow: 0 6px 0 rgba(92,62,26,.18), inset 0 2px 0 rgba(255,255,255,.7);--nq-p-frame-outer: 2px solid rgba(92,62,26,.45);--nq-p-frame-inner: 1px solid rgba(92,62,26,.28);--nq-p-rule: 2px solid rgba(92,62,26,.35);--nq-p-ink: #2b1f14;--nq-p-ink-secondary: #5c452a;--nq-p-ink-muted: #6f6455;--nq-p-ink-away: #4d4433;--nq-p-ink-late: #8f1526;--nq-brand-blue: #3B3AB8;--nq-brand-purple: #A035CC;--nq-brand-gradient: linear-gradient(135deg, var(--nq-brand-blue), var(--nq-brand-purple));--nq-p-icon-tile: var(--nq-brand-gradient);--nq-p-icon-tile-done: rgba(92,62,26,.16);--nq-p-crest: var(--nq-brand-gradient);--nq-p-crest-away: linear-gradient(135deg, #6b6b7a, #7a7286);--nq-p-seal: radial-gradient(circle at 35% 30%, #a8283a, #6d1322);--nq-p-seal-shadow: 0 4px 10px rgba(60,10,20,.4), inset 0 0 0 4px rgba(255,255,255,.14);--nq-p-seal-size: 78px;--nq-p-dock-bg: rgba(43,31,20,.9);--nq-p-dock-ink: #f7efdb;--nq-p-dock-ink-secondary: #d8c9a6;--nq-p-dock-divider: rgba(233,220,189,.3);--nq-p-dock-height: 84px;--nq-p-font-display: "Cinzel Decorative", Cinzel, serif;--nq-p-font-heading: Cinzel, serif;--nq-p-font-body: Nunito, system-ui, sans-serif;--nq-p-size-hero: 86px;--nq-p-size-wordmark: 66px;--nq-p-size-title: 56px;--nq-p-size-name: 48px;--nq-p-size-section: 32px;--nq-p-size-quest: 30px;--nq-p-size-body: 22px;--nq-p-size-label: 21px;--nq-p-track-label: .14em;--nq-p-track-kicker: .3em;--nq-p-quest-min-height: 116px;--nq-p-quest-gap: 24px;--nq-p-quest-pad: 22px;--nq-p-tile-size: 64px;--nq-p-button-height: 72px;--nq-p-button-min-width: 150px;--nq-p-confirm-button: 108px;--nq-p-radius-card: 12px;--nq-p-radius-panel: 20px;--nq-p-radius-pill: 9999px;--nq-p-page-inset: 62px;--nq-p-frame-inset: 26px;--nq-ease-out: cubic-bezier(0, 0, .2, 1);--nq-dur-micro: .12s;--nq-dur-base: .2s;--nq-dur-modal: .35s}'; -function st(i) { - window.customCards = window.customCards ?? [], window.customCards.push(i); +C._$litElement$ = !0, C.finalized = !0, re.litElementHydrateSupport?.({ LitElement: C }); +const ct = re.litElementPolyfillSupport; +ct?.({ LitElement: C }); +(re.litElementVersions ??= []).push("4.2.2"); +const Re = ':host{--nq-p-parchment-top: #f0e4c7;--nq-p-parchment-bottom: #ddcca3;--nq-p-parchment: radial-gradient(ellipse at 25% 10%, rgba(255,255,255,.5), transparent 55%), radial-gradient(ellipse at 85% 90%, rgba(120,86,44,.28), transparent 60%), repeating-linear-gradient(93deg, rgba(150,115,70,.05) 0 2px, transparent 2px 6px), repeating-linear-gradient(2deg, rgba(150,115,70,.04) 0 3px, transparent 3px 7px), linear-gradient(var(--nq-p-parchment-top), var(--nq-p-parchment-bottom));--nq-p-vignette: inset 0 0 200px rgba(80,52,20,.35);--nq-p-card-open: linear-gradient(#fdf7e6, #f2e7c9);--nq-p-card-done: linear-gradient(#f0e8d3, #e6dcc2);--nq-p-card-panel: linear-gradient(#fcf6e6, #f1e6ca);--nq-p-card-border: rgba(120,88,48,.38);--nq-p-panel-border: rgba(92,62,26,.4);--nq-p-card-shadow: 0 3px 0 rgba(120,88,48,.2), inset 0 1px 0 rgba(255,255,255,.7);--nq-p-panel-shadow: 0 6px 0 rgba(92,62,26,.18), inset 0 2px 0 rgba(255,255,255,.7);--nq-p-frame-outer: 2px solid rgba(92,62,26,.45);--nq-p-frame-inner: 1px solid rgba(92,62,26,.28);--nq-p-rule: 2px solid rgba(92,62,26,.35);--nq-p-ink: #2b1f14;--nq-p-ink-secondary: #5c452a;--nq-p-ink-muted: #6f6455;--nq-p-ink-away: #4d4433;--nq-p-ink-late: #8f1526;--nq-brand-blue: #3B3AB8;--nq-brand-purple: #A035CC;--nq-brand-gradient: linear-gradient(135deg, var(--nq-brand-blue), var(--nq-brand-purple));--nq-p-icon-tile: var(--nq-brand-gradient);--nq-p-icon-tile-done: rgba(92,62,26,.16);--nq-p-crest: var(--nq-brand-gradient);--nq-p-crest-away: linear-gradient(135deg, #6b6b7a, #7a7286);--nq-p-seal: radial-gradient(circle at 35% 30%, #a8283a, #6d1322);--nq-p-seal-shadow: 0 4px 10px rgba(60,10,20,.4), inset 0 0 0 4px rgba(255,255,255,.14);--nq-p-seal-size: 78px;--nq-p-dock-bg: rgba(43,31,20,.9);--nq-p-dock-ink: #f7efdb;--nq-p-dock-ink-secondary: #d8c9a6;--nq-p-dock-divider: rgba(233,220,189,.3);--nq-p-dock-height: 84px;--nq-p-font-display: "Cinzel Decorative", Cinzel, serif;--nq-p-font-heading: Cinzel, serif;--nq-p-font-body: Nunito, system-ui, sans-serif;--nq-p-size-hero: 86px;--nq-p-size-wordmark: 66px;--nq-p-size-title: 56px;--nq-p-size-name: 48px;--nq-p-size-section: 32px;--nq-p-size-quest: 30px;--nq-p-size-body: 22px;--nq-p-size-label: 21px;--nq-p-track-label: .14em;--nq-p-track-kicker: .3em;--nq-p-quest-min-height: 116px;--nq-p-quest-gap: 24px;--nq-p-quest-pad: 22px;--nq-p-tile-size: 64px;--nq-p-button-height: 72px;--nq-p-button-min-width: 150px;--nq-p-confirm-button: 108px;--nq-p-radius-card: 12px;--nq-p-radius-panel: 20px;--nq-p-radius-pill: 9999px;--nq-p-page-inset: 62px;--nq-p-frame-inset: 26px;--nq-ease-out: cubic-bezier(0, 0, .2, 1);--nq-dur-micro: .12s;--nq-dur-base: .2s;--nq-dur-modal: .35s}'; +function oe(s) { + window.customCards = window.customCards ?? [], window.customCards.push(s); } -const gt = /* @__PURE__ */ new Map(); -function B(i, t) { - const e = `${i ?? "local"}|${JSON.stringify(t)}`, n = gt.get(e); +const _e = /* @__PURE__ */ new Map(); +function F(s, e) { + const t = `${s ?? "local"}|${JSON.stringify(e)}`, n = _e.get(t); if (n) return n; - let s; + let i; try { - s = new Intl.DateTimeFormat("en-US", { - ...t, - ...i ? { timeZone: i } : {} + i = new Intl.DateTimeFormat("en-US", { + ...e, + ...s ? { timeZone: s } : {} }); } catch { - s = new Intl.DateTimeFormat("en-US", t); + i = new Intl.DateTimeFormat("en-US", e); } - return gt.set(e, s), s; + return _e.set(t, i), i; } -function oe(i) { - const t = i?.config; - if (!t || typeof t != "object") +function pt(s) { + const e = s?.config; + if (!e || typeof e != "object") return; - const e = t.time_zone; - return typeof e == "string" && e.trim() ? e.trim() : void 0; + const t = e.time_zone; + return typeof t == "string" && t.trim() ? t.trim() : void 0; } -function Z(i, t) { - return `${B(t, { weekday: "long" }).format(i)}, ${B(t, { month: "short", day: "numeric" }).format(i)}`; +function G(s, e) { + return `${F(e, { weekday: "long" }).format(s)}, ${F(e, { month: "short", day: "numeric" }).format(s)}`; } -function ae(i, t) { - return B(t, { +function ht(s, e) { + return F(e, { hour: "numeric", minute: "2-digit", hour12: !0 - }).format(i); + }).format(s); } -const le = { +const ut = { "clear-night": "Clear", cloudy: "Cloudy", exceptional: "Clear", @@ -552,7 +552,7 @@ const le = { sunny: "Sunny", windy: "Windy", "windy-variant": "Windy" -}, de = { +}, ft = { "clear-night": "Clear night skies", cloudy: "Grey skies today", exceptional: "A striking day", @@ -569,26 +569,26 @@ const le = { windy: "A blustery day", "windy-variant": "A blustery day" }; -function _t(i) { - return typeof i == "string" ? i.trim() : ""; +function ve(s) { + return typeof s == "string" ? s.trim() : ""; } -function V(i, t) { - if (i == null || i === "") - return t; - const e = typeof i == "number" ? i : Number(i); - return Number.isFinite(e) ? e : t; +function K(s, e) { + if (s == null || s === "") + return e; + const t = typeof s == "number" ? s : Number(s); + return Number.isFinite(t) ? t : e; } -function yt(i) { - return Number.isFinite(i) ? Math.min(100, Math.max(0, i)) : 0; +function be(s) { + return Number.isFinite(s) ? Math.min(100, Math.max(0, s)) : 0; } -function bt(i) { - return Math.max(0, Math.round(i)); +function $e(s) { + return Math.max(0, Math.round(s)); } -function pe(i) { - return i.split(/[-_]+/).filter(Boolean).map((t) => t.charAt(0).toUpperCase() + t.slice(1)).join(" "); +function mt(s) { + return s.split(/[-_]+/).filter(Boolean).map((e) => e.charAt(0).toUpperCase() + e.slice(1)).join(" "); } -function ce(i, t) { - const e = B(t, { +function gt(s, e) { + const t = F(e, { year: "numeric", month: "2-digit", day: "2-digit", @@ -596,10 +596,10 @@ function ce(i, t) { minute: "2-digit", second: "2-digit", hourCycle: "h23" - }).formatToParts(new Date(i)), n = (r) => { - const o = e.find((l) => l.type === r); + }).formatToParts(new Date(s)), n = (r) => { + const o = t.find((l) => l.type === r); return o ? Number(o.value) : Number.NaN; - }, s = Date.UTC( + }, i = Date.UTC( n("year"), n("month") - 1, n("day"), @@ -607,32 +607,32 @@ function ce(i, t) { n("minute"), n("second") ); - return Number.isFinite(s) ? s - i : 0; + return Number.isFinite(i) ? i - s : 0; } -function he(i, t) { - if (typeof i != "string") +function yt(s, e) { + if (typeof s != "string") return null; - const e = i.split("-"); - if (e.length !== 3) + const t = s.split("-"); + if (t.length !== 3) return null; - const [n, s, r] = e.map((a) => Number(a)); - if (!n || !s || !r) + const [n, i, r] = t.map((a) => Number(a)); + if (!n || !i || !r) return null; - if (!t) { - const a = new Date(n, s - 1, r); + if (!e) { + const a = new Date(n, i - 1, r); return Number.isNaN(a.getTime()) ? null : a; } - const o = Date.UTC(n, s - 1, r), l = new Date(o - ce(o, t)); + const o = Date.UTC(n, i - 1, r), l = new Date(o - gt(o, e)); return Number.isNaN(l.getTime()) ? null : l; } -function ue(i) { - const t = le[i]; - return t || i.charAt(0).toUpperCase() + i.slice(1); +function _t(s) { + const e = ut[s]; + return e || s.charAt(0).toUpperCase() + s.slice(1); } -function fe(i, t) { - return de[i] ?? t; +function vt(s, e) { + return ft[s] ?? e; } -const vt = "polygon(0 0, 100% 0, 100% 62%, 50% 100%, 0 62%)", me = "polygon(50% 0, 93% 25%, 93% 75%, 50% 100%, 7% 75%, 7% 25%)", ge = Tt` +const xe = "polygon(0 0, 100% 0, 100% 62%, 50% 100%, 0 62%)", bt = "polygon(50% 0, 93% 25%, 93% 75%, 50% 100%, 7% 75%, 7% 25%)", $t = Pe` * { box-sizing: border-box; } @@ -704,7 +704,7 @@ const vt = "polygon(0 0, 100% 0, 100% 62%, 50% 100%, 0 62%)", me = "polygon(50% justify-content: center; width: 34px; height: 34px; - clip-path: ${b(me)}; + clip-path: ${v(bt)}; background: var(--nq-brand-gradient); } @@ -798,7 +798,7 @@ const vt = "polygon(0 0, 100% 0, 100% 62%, 50% 100%, 0 62%)", me = "polygon(50% width: 188px; height: 214px; padding: 4px; - clip-path: ${b(vt)}; + clip-path: ${v(xe)}; background: rgba(255, 255, 255, 0.22); } @@ -809,7 +809,7 @@ const vt = "polygon(0 0, 100% 0, 100% 62%, 50% 100%, 0 62%)", me = "polygon(50% align-items: center; justify-content: center; padding-bottom: 70px; - clip-path: ${b(vt)}; + clip-path: ${v(xe)}; background: var(--nq-p-crest); } @@ -906,6 +906,62 @@ const vt = "polygon(0 0, 100% 0, 100% 62%, 50% 100%, 0 62%)", me = "polygon(50% color: var(--nq-p-ink-secondary); } + .plate.unknown { + border: 2px dashed var(--nq-p-panel-border); + background: linear-gradient(#f2ecdd, #e6dcc6); + box-shadow: none; + } + + .notice-wrap { + position: absolute; + top: 262px; + left: 110px; + right: 110px; + bottom: 190px; + display: flex; + align-items: center; + justify-content: center; + } + + .notice { + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + gap: 20px; + width: 100%; + max-width: 980px; + padding: 56px 64px; + border: 2px solid var(--nq-p-panel-border); + border-radius: var(--nq-p-radius-panel); + background: var(--nq-p-card-panel); + box-shadow: var(--nq-p-panel-shadow); + text-align: center; + } + + .notice svg { + width: 46px; + height: 46px; + color: var(--nq-p-ink-secondary); + } + + .notice-headline { + font-family: var(--nq-p-font-heading); + font-size: 40px; + font-weight: 700; + line-height: 1.15; + color: var(--nq-p-ink); + } + + .notice-body { + margin: 0; + font-family: var(--nq-p-font-body); + font-size: 24px; + font-weight: 600; + line-height: 1.4; + color: var(--nq-p-ink-secondary); + } + .spacer { flex: 1 1 0; } @@ -971,7 +1027,7 @@ const vt = "polygon(0 0, 100% 0, 100% 62%, 50% 100%, 0 62%)", me = "polygon(50% font-weight: 800; line-height: 1; } -`, _e = g` -`, ye = g``, qt = p` -`, be = g``, wt = p``, kt = p``, At = p``, qe = { + icon: kt, + headline: "NestQuest is not set up yet", + body: "A parent needs to finish setting up NestQuest before the party can gather." +}, Ct = { + icon: At, + headline: "The records cannot be reached", + body: "The party's records are quiet right now. NestQuest will return shortly." +}, St = p` `; -class ve extends C { +class Et extends C { static properties = { hass: { attribute: !1 }, _config: { state: !0 }, _now: { state: !0 } }; - static styles = [b(It), ge]; + static styles = [v(Re), $t]; hass; _config; _now = /* @__PURE__ */ new Date(); _clockTimer; - setConfig(t) { - if (!t || typeof t != "object") + setConfig(e) { + if (!e || typeof e != "object") throw new Error("Invalid configuration"); - this._config = t; + this._config = e; } getCardSize() { return 22; @@ -1037,7 +1135,8 @@ class ve extends C { this._clockTimer !== void 0 && (window.clearInterval(this._clockTimer), this._clockTimer = void 0), super.disconnectedCallback(); } render() { - return g` + const e = this._boardNotice(); + return p`
    @@ -1052,166 +1151,210 @@ class ve extends C {
    -

    The Party · ${Z(this._now, this._timeZone())}

    -
    - ${this._plates().map((t) => this._renderPlate(t))} -
    -

    Tap your crest to open your Quest Log

    +

    The Party · ${G(this._now, this._timeZone())}

    + ${e ? this._renderNotice(e) : p` +
    + ${this._plates().map((t) => this._renderPlate(t))} +
    +

    Tap your crest to open your Quest Log

    + `}
    ${this._renderDock()}
    `; } _timeZone() { - return oe(this.hass); + return pt(this.hass); } - _state(t) { - const n = this.hass?.states?.[t]; + _state(e) { + const n = this.hass?.states?.[e]; return n && typeof n == "object" ? n : null; } _childSlugs() { if (!this._config) return []; - const t = this._config.child_order; - return Array.isArray(t) ? t.filter( - (e) => typeof e == "string" && e.trim().length > 0 - ).map((e) => e.trim()) : []; + const e = this._config.child_order; + return Array.isArray(e) ? e.filter( + (t) => typeof t == "string" && t.trim().length > 0 + ).map((t) => t.trim()) : []; } _plates() { - return this._childSlugs().map((t) => this._plate(t)); + return this._childSlugs().map((e) => this._plate(e)); } - _plate(t) { - const e = this._state(`sensor.nestquest_${t}_quests_due_today`), n = this._state( - `sensor.nestquest_${t}_quests_completed_today` - ), s = this._state( - `sensor.nestquest_${t}_completion_pct_today` + _plate(e) { + const t = this._dueSensorResolution(e), n = this._state(`sensor.nestquest_${e}_quests_due_today`), i = this._state( + `sensor.nestquest_${e}_quests_completed_today` ), r = this._state( - `binary_sensor.nestquest_${t}_present_today` - ), o = e?.attributes ?? {}, l = bt(V(e?.state, 0)), a = bt(V(n?.state, 0)), p = s ? V(s.state, Number.NaN) : Number.NaN, u = Number.isFinite(p) ? yt(p) : l > 0 ? yt(a / l * 100) : 0; - let d = !0; - const h = o.present; - typeof h == "boolean" ? d = h : r && (d = String(r.state ?? "").trim().toLowerCase() === "on"); - let c = ""; - const f = o.child_name; - if (typeof f == "string" && f.trim()) - c = f.trim(); + `sensor.nestquest_${e}_completion_pct_today` + ), o = this._state( + `binary_sensor.nestquest_${e}_present_today` + ), l = n?.attributes ?? {}, a = $e(K(n?.state, 0)), c = $e(K(i?.state, 0)), u = r ? K(r.state, Number.NaN) : Number.NaN, d = Number.isFinite(u) ? be(u) : a > 0 ? be(c / a * 100) : 0; + let f = !0; + const h = l.present; + typeof h == "boolean" ? f = h : o && (f = String(o.state ?? "").trim().toLowerCase() === "on"); + let m = ""; + const y = l.child_name; + if (typeof y == "string" && y.trim()) + m = y.trim(); else { - const N = r?.attributes?.child_name; - typeof N == "string" && N.trim() && (c = N.trim()); + const Z = o?.attributes?.child_name; + typeof Z == "string" && Z.trim() && (m = Z.trim()); } - c || (c = pe(t)); - const _ = he( - r?.attributes?.next_present, + m || (m = mt(e)); + const b = yt( + o?.attributes?.next_present, this._timeZone() - ), v = _ ? `Returns ${Z(_, this._timeZone())}` : null; + ), j = b ? `Returns ${G(b, this._timeZone())}` : null; return { - slug: t, - name: c, - initial: (c.charAt(0) || "?").toUpperCase(), - present: d, - completed: a, - due: l, - pct: u, - returns: v + slug: e, + name: m, + initial: (m.charAt(0) || "?").toUpperCase(), + present: t === null ? f : !1, + completed: c, + due: a, + pct: d, + returns: j, + unresolved: t }; } - _renderPlate(t) { - return t.present ? g` + /** "missing" when the due sensor does not exist (integration not + * configured), "stale" when it answers unavailable/unknown (backend + * unreachable or the child is absent from the snapshot), null when + * it resolves. */ + _dueSensorResolution(e) { + const t = this._state(`sensor.nestquest_${e}_quests_due_today`); + if (!t) + return "missing"; + const n = String(t.state ?? "").trim().toLowerCase(); + return !n || n === "unavailable" || n === "unknown" ? "stale" : null; + } + _boardNotice() { + const e = this._plates(); + return e.length === 0 ? qe : e.every((t) => t.unresolved !== null) ? e.some((t) => t.unresolved === "stale") ? Ct : qe : null; + } + _renderNotice(e) { + return p` +
    +
    + ${e.icon} + ${e.headline} +

    ${e.body}

    +
    +
    + `; + } + _renderPlate(e) { + return e.unresolved ? p` +
    + + + ${e.initial} + + + ${e.name} + + ${wt} + Unknown + + — + +
    + ` : e.present ? p` - ` : g` + ` : p`
    - ${t.initial} + ${e.initial} - ${t.name} + ${e.name} - ${ye} + ${qt} On travels - ${t.returns ?? "Returns —"} + ${e.returns ?? "Returns —"}
    `; } - _openQuestLog(t) { - const e = _t(this._config?.quest_log_path); - e && (window.history.pushState(null, "", `${e.replace(/\/+$/, "")}/${t}`), window.dispatchEvent(new Event("location-changed"))); + _openQuestLog(e) { + const t = ve(this._config?.quest_log_path); + t && (window.history.pushState(null, "", `${t.replace(/\/+$/, "")}/${e}`), window.dispatchEvent(new Event("location-changed"))); } _dockWeather() { - const t = _t(this._config?.weather_entity); - if (!t) - return null; - const e = this._state(t); + const e = ve(this._config?.weather_entity); if (!e) return null; - const n = String(e.state ?? "").trim(); + const t = this._state(e); + if (!t) + return null; + const n = String(t.state ?? "").trim(); if (!n || n === "unavailable" || n === "unknown") return null; - const s = e.attributes ?? {}; + const i = t.attributes ?? {}; let r = null, o = null; - const l = s.forecast; + const l = i.forecast; if (Array.isArray(l) && l.length > 0) { const a = l[0]; if (a && typeof a == "object") { - const p = a; - r = this._optionalNumber(p.temperature), o = this._optionalNumber(p.templow); + const c = a; + r = this._optionalNumber(c.temperature), o = this._optionalNumber(c.templow); } } return { condition: n, - temperature: this._optionalNumber(s.temperature), + temperature: this._optionalNumber(i.temperature), high: r, low: o }; } - _optionalNumber(t) { - if (t == null || t === "") + _optionalNumber(e) { + if (e == null || e === "") return null; - const e = typeof t == "number" ? t : Number(t); - return Number.isFinite(e) ? e : null; + const t = typeof e == "number" ? e : Number(e); + return Number.isFinite(t) ? t : null; } _renderDock() { - const t = this._dockWeather(); - if (!t) - return g` + const e = this._dockWeather(); + if (!e) + return p`
    - ${Z(this._now, this._timeZone())} + ${G(this._now, this._timeZone())} - ${ae(this._now, this._timeZone())} + ${ht(this._now, this._timeZone())}
    `; - const e = ue(t.condition), n = fe(t.condition, e), s = t.temperature === null ? m : g`${Math.round(t.temperature)}°`, r = t.high === null || t.low === null ? null : `${Math.round(t.high)}° / ${Math.round(t.low)}°`; - return g` + const t = _t(e.condition), n = vt(e.condition, t), i = e.temperature === null ? g : p`${Math.round(e.temperature)}°`, r = e.high === null || e.low === null ? null : `${Math.round(e.high)}° / ${Math.round(e.low)}°`; + return p`
    - ${be} - ${s} + ${St} + ${i} - ${r === null ? e : `${e} · ${r}`} + ${r === null ? t : `${t} · ${r}`} ${n} @@ -1219,167 +1362,167 @@ class ve extends C { `; } } -customElements.define("nestquest-party-board-card", ve); -st({ +customElements.define("nestquest-party-board-card", Et); +oe({ type: "nestquest-party-board-card", name: "NestQuest Party Board", description: "Kids' attract screen for present and away adventurers." }); -const $e = { CHILD: 2 }, xe = (i) => (...t) => ({ _$litDirective$: i, values: t }); -let qe = class { - constructor(t) { +const Nt = { CHILD: 2 }, Tt = (s) => (...e) => ({ _$litDirective$: s, values: e }); +let Mt = class { + constructor(e) { } get _$AU() { return this._$AM._$AU; } - _$AT(t, e, n) { - this._$Ct = t, this._$AM = e, this._$Ci = n; + _$AT(e, t, n) { + this._$Ct = e, this._$AM = t, this._$Ci = n; } - _$AS(t, e) { - return this.update(t, e); + _$AS(e, t) { + return this.update(e, t); } - update(t, e) { - return this.render(...e); + update(e, t) { + return this.render(...t); } }; -const { I: we } = ne, $t = (i) => i, xt = () => document.createComment(""), O = (i, t, e) => { - const n = i._$AA.parentNode, s = t === void 0 ? i._$AB : t._$AA; - if (e === void 0) { - const r = n.insertBefore(xt(), s), o = n.insertBefore(xt(), s); - e = new we(r, o, i, i.options); +const { I: Ot } = at, we = (s) => s, ke = () => document.createComment(""), z = (s, e, t) => { + const n = s._$AA.parentNode, i = e === void 0 ? s._$AB : e._$AA; + if (t === void 0) { + const r = n.insertBefore(ke(), i), o = n.insertBefore(ke(), i); + t = new Ot(r, o, s, s.options); } else { - const r = e._$AB.nextSibling, o = e._$AM, l = o !== i; + const r = t._$AB.nextSibling, o = t._$AM, l = o !== s; if (l) { let a; - e._$AQ?.(i), e._$AM = i, e._$AP !== void 0 && (a = i._$AU) !== o._$AU && e._$AP(a); + t._$AQ?.(s), t._$AM = s, t._$AP !== void 0 && (a = s._$AU) !== o._$AU && t._$AP(a); } - if (r !== s || l) { - let a = e._$AA; + if (r !== i || l) { + let a = t._$AA; for (; a !== r; ) { - const p = $t(a).nextSibling; - $t(n).insertBefore(a, s), a = p; + const c = we(a).nextSibling; + we(n).insertBefore(a, i), a = c; } } } - return e; -}, w = (i, t, e = i) => (i._$AI(t, e), i), ke = {}, Ae = (i, t = ke) => i._$AH = t, Ce = (i) => i._$AH, K = (i) => { - i._$AR(), i._$AA.remove(); + return t; +}, w = (s, e, t = s) => (s._$AI(e, t), s), zt = {}, Pt = (s, e = zt) => s._$AH = e, It = (s) => s._$AH, J = (s) => { + s._$AR(), s._$AA.remove(); }; -const qt = (i, t, e) => { +const Ae = (s, e, t) => { const n = /* @__PURE__ */ new Map(); - for (let s = t; s <= e; s++) n.set(i[s], s); + for (let i = e; i <= t; i++) n.set(s[i], i); return n; -}, wt = xe(class extends qe { - constructor(i) { - if (super(i), i.type !== $e.CHILD) throw Error("repeat() can only be used in text expressions"); +}, Ce = Tt(class extends Mt { + constructor(s) { + if (super(s), s.type !== Nt.CHILD) throw Error("repeat() can only be used in text expressions"); } - dt(i, t, e) { + dt(s, e, t) { let n; - e === void 0 ? e = t : t !== void 0 && (n = t); - const s = [], r = []; + t === void 0 ? t = e : e !== void 0 && (n = e); + const i = [], r = []; let o = 0; - for (const l of i) s[o] = n ? n(l, o) : o, r[o] = e(l, o), o++; - return { values: r, keys: s }; + for (const l of s) i[o] = n ? n(l, o) : o, r[o] = t(l, o), o++; + return { values: r, keys: i }; } - render(i, t, e) { - return this.dt(i, t, e).values; + render(s, e, t) { + return this.dt(s, e, t).values; } - update(i, [t, e, n]) { - const s = Ce(i), { values: r, keys: o } = this.dt(t, e, n); - if (!Array.isArray(s)) return this.ut = o, r; + update(s, [e, t, n]) { + const i = It(s), { values: r, keys: o } = this.dt(e, t, n); + if (!Array.isArray(i)) return this.ut = o, r; const l = this.ut ??= [], a = []; - let p, u, d = 0, h = s.length - 1, c = 0, f = r.length - 1; - for (; d <= h && c <= f; ) if (s[d] === null) d++; - else if (s[h] === null) h--; - else if (l[d] === o[c]) a[c] = w(s[d], r[c]), d++, c++; - else if (l[h] === o[f]) a[f] = w(s[h], r[f]), h--, f--; - else if (l[d] === o[f]) a[f] = w(s[d], r[f]), O(i, a[f + 1], s[d]), d++, f--; - else if (l[h] === o[c]) a[c] = w(s[h], r[c]), O(i, s[d], s[h]), h--, c++; - else if (p === void 0 && (p = qt(o, c, f), u = qt(l, d, h)), p.has(l[d])) if (p.has(l[h])) { - const _ = u.get(o[c]), v = _ !== void 0 ? s[_] : null; - if (v === null) { - const N = O(i, s[d]); - w(N, r[c]), a[c] = N; - } else a[c] = w(v, r[c]), O(i, s[d], v), s[_] = null; - c++; - } else K(s[h]), h--; - else K(s[d]), d++; - for (; c <= f; ) { - const _ = O(i, a[f + 1]); - w(_, r[c]), a[c++] = _; + let c, u, d = 0, f = i.length - 1, h = 0, m = r.length - 1; + for (; d <= f && h <= m; ) if (i[d] === null) d++; + else if (i[f] === null) f--; + else if (l[d] === o[h]) a[h] = w(i[d], r[h]), d++, h++; + else if (l[f] === o[m]) a[m] = w(i[f], r[m]), f--, m--; + else if (l[d] === o[m]) a[m] = w(i[d], r[m]), z(s, a[m + 1], i[d]), d++, m--; + else if (l[f] === o[h]) a[h] = w(i[f], r[h]), z(s, i[d], i[f]), f--, h++; + else if (c === void 0 && (c = Ae(o, h, m), u = Ae(l, d, f)), c.has(l[d])) if (c.has(l[f])) { + const y = u.get(o[h]), b = y !== void 0 ? i[y] : null; + if (b === null) { + const j = z(s, i[d]); + w(j, r[h]), a[h] = j; + } else a[h] = w(b, r[h]), z(s, i[d], b), i[y] = null; + h++; + } else J(i[f]), f--; + else J(i[d]), d++; + for (; h <= m; ) { + const y = z(s, a[m + 1]); + w(y, r[h]), a[h++] = y; } - for (; d <= h; ) { - const _ = s[d++]; - _ !== null && K(_); + for (; d <= f; ) { + const y = i[d++]; + y !== null && J(y); } - return this.ut = o, Ae(i, a), E; + return this.ut = o, Pt(s, a), E; } -}), kt = /* @__PURE__ */ new Map(); -function j(i, t) { - const e = `${i ?? "local"}|${JSON.stringify(t)}`, n = kt.get(e); +}), Se = /* @__PURE__ */ new Map(); +function H(s, e) { + const t = `${s ?? "local"}|${JSON.stringify(e)}`, n = Se.get(t); if (n) return n; - let s; + let i; try { - s = new Intl.DateTimeFormat("en-US", { - ...t, - ...i ? { timeZone: i } : {} + i = new Intl.DateTimeFormat("en-US", { + ...e, + ...s ? { timeZone: s } : {} }); } catch { - s = new Intl.DateTimeFormat("en-US", t); + i = new Intl.DateTimeFormat("en-US", e); } - return kt.set(e, s), s; + return Se.set(t, i), i; } -function Se(i) { - const t = i?.config; - if (!t || typeof t != "object") +function Ut(s) { + const e = s?.config; + if (!e || typeof e != "object") return; - const e = t.time_zone; - return typeof e == "string" && e.trim() ? e.trim() : void 0; + const t = e.time_zone; + return typeof t == "string" && t.trim() ? t.trim() : void 0; } -function Ee(i, t) { - return `${j(t, { weekday: "long" }).format(i)}, ${j(t, { month: "short", day: "numeric" }).format(i)}`; +function Ee(s, e) { + return `${H(e, { weekday: "long" }).format(s)}, ${H(e, { month: "short", day: "numeric" }).format(s)}`; } -function Ne(i, t) { - return j(t, { weekday: "long" }).format(i); +function Dt(s, e) { + return H(e, { weekday: "long" }).format(s); } -function Te(i, t) { - const e = new Date(i); - return Number.isNaN(e.getTime()) ? "" : j(t, { +function Ht(s, e) { + const t = new Date(s); + return Number.isNaN(t.getTime()) ? "" : H(e, { hour: "numeric", minute: "2-digit", hour12: !0 - }).format(e); + }).format(t); } -function Me(i) { - if (!i) +function jt(s) { + if (!s) return null; - const t = /^(\d{1,2}):(\d{2})$/.exec(i.trim()); - if (!t) + const e = /^(\d{1,2}):(\d{2})$/.exec(s.trim()); + if (!e) return null; - const e = Number(t[1]); - if (e > 23) + const t = Number(e[1]); + if (t > 23) return null; - const n = e >= 12 ? "PM" : "AM"; - return `${e % 12 === 0 ? 12 : e % 12}:${t[2]} ${n}`; + const n = t >= 12 ? "PM" : "AM"; + return `${t % 12 === 0 ? 12 : t % 12}:${e[2]} ${n}`; } -function k(i) { - return typeof i == "string" ? i.trim() : ""; +function k(s) { + return typeof s == "string" ? s.trim() : ""; } -function y(i, t) { - if (i == null || i === "") - return t; - const e = typeof i == "number" ? i : Number(i); - return Number.isFinite(e) ? e : t; +function _(s, e) { + if (s == null || s === "") + return e; + const t = typeof s == "number" ? s : Number(s); + return Number.isFinite(t) ? t : e; } -function $(i) { - return Math.max(0, Math.round(i)); +function $(s) { + return Math.max(0, Math.round(s)); } -function At(i) { - return i.split(/[-_]+/).filter(Boolean).map((t) => t.charAt(0).toUpperCase() + t.slice(1)).join(" "); +function X(s) { + return s.split(/[-_]+/).filter(Boolean).map((e) => e.charAt(0).toUpperCase() + e.slice(1)).join(" "); } -function ze(i, t) { - const e = j(t, { +function Rt(s, e) { + const t = H(e, { year: "numeric", month: "2-digit", day: "2-digit", @@ -1387,10 +1530,10 @@ function ze(i, t) { minute: "2-digit", second: "2-digit", hourCycle: "h23" - }).formatToParts(new Date(i)), n = (r) => { - const o = e.find((l) => l.type === r); + }).formatToParts(new Date(s)), n = (r) => { + const o = t.find((l) => l.type === r); return o ? Number(o.value) : Number.NaN; - }, s = Date.UTC( + }, i = Date.UTC( n("year"), n("month") - 1, n("day"), @@ -1398,47 +1541,47 @@ function ze(i, t) { n("minute"), n("second") ); - return Number.isFinite(s) ? s - i : 0; + return Number.isFinite(i) ? i - s : 0; } -function Pe(i, t) { - if (typeof i != "string") +function Ne(s, e) { + if (typeof s != "string") return null; - const e = i.split("-"); - if (e.length !== 3) + const t = s.split("-"); + if (t.length !== 3) return null; - const [n, s, r] = e.map((a) => Number(a)); - if (!n || !s || !r) + const [n, i, r] = t.map((a) => Number(a)); + if (!n || !i || !r) return null; - if (!t) { - const a = new Date(n, s - 1, r); + if (!e) { + const a = new Date(n, i - 1, r); return Number.isNaN(a.getTime()) ? null : a; } - const o = Date.UTC(n, s - 1, r), l = new Date(o - ze(o, t)); + const o = Date.UTC(n, i - 1, r), l = new Date(o - Rt(o, e)); return Number.isNaN(l.getTime()) ? null : l; } -function Oe() { - const i = window.location.pathname.split("/").filter(Boolean); - return i.length > 0 ? i[i.length - 1].toLowerCase() : ""; +function Lt() { + const s = window.location.pathname.split("/").filter(Boolean); + return s.length > 0 ? s[s.length - 1].toLowerCase() : ""; } -function Ue(i) { - if (!i || typeof i != "object") +function Bt(s) { + if (!s || typeof s != "object") return null; - const t = i, e = k(t.state).toLowerCase(); - if (e !== "open" && e !== "completed") + const e = s, t = k(e.state).toLowerCase(); + if (t !== "open" && t !== "completed") return null; - const n = y(t.id, 0); + const n = _(e.id, 0); return n ? { id: n, - title: k(t.title) || "Quest", - icon: k(t.icon) || null, - window: k(t.window).toLowerCase(), - due_time: k(t.due_time) || null, - state: e, - overdue: t.overdue === !0, - completed_at: k(t.completed_at) || null + title: k(e.title) || "Quest", + icon: k(e.icon) || null, + window: k(e.window).toLowerCase(), + due_time: k(e.due_time) || null, + state: t, + overdue: e.overdue === !0, + completed_at: k(e.completed_at) || null } : null; } -const Ct = "polygon(0 0, 100% 0, 100% 62%, 50% 100%, 0 62%)", Ie = "polygon(50% 0, 93% 25%, 93% 75%, 50% 100%, 7% 75%, 7% 25%)", St = [-9, 6, -4], De = Tt` +const Te = "polygon(0 0, 100% 0, 100% 62%, 50% 100%, 0 62%)", Ft = "polygon(50% 0, 93% 25%, 93% 75%, 50% 100%, 7% 75%, 7% 25%)", Me = [-9, 6, -4], Qt = Pe` * { box-sizing: border-box; } @@ -1498,7 +1641,7 @@ const Ct = "polygon(0 0, 100% 0, 100% 62%, 50% 100%, 0 62%)", Ie = "polygon(50% width: 96px; height: 110px; padding: 3px; - clip-path: ${b(Ct)}; + clip-path: ${v(Te)}; background: rgba(255, 255, 255, 0.22); } @@ -1509,7 +1652,7 @@ const Ct = "polygon(0 0, 100% 0, 100% 62%, 50% 100%, 0 62%)", Ie = "polygon(50% align-items: center; justify-content: center; padding-bottom: 32px; - clip-path: ${b(Ct)}; + clip-path: ${v(Te)}; background: var(--nq-p-crest); } @@ -1521,6 +1664,10 @@ const Ct = "polygon(0 0, 100% 0, 100% 62%, 50% 100%, 0 62%)", Ie = "polygon(50% color: #ffffff; } + .crest.away .crest-face { + background: var(--nq-p-crest-away); + } + .titles { display: flex; flex-direction: column; @@ -1571,7 +1718,7 @@ const Ct = "polygon(0 0, 100% 0, 100% 62%, 50% 100%, 0 62%)", Ie = "polygon(50% justify-content: center; width: 44px; height: 44px; - clip-path: ${b(Ie)}; + clip-path: ${v(Ft)}; background: var(--nq-brand-gradient); } @@ -1868,6 +2015,81 @@ const Ct = "polygon(0 0, 100% 0, 100% 62%, 50% 100%, 0 62%)", Ie = "polygon(50% color: var(--nq-p-ink-secondary); } + .notice-wrap { + position: absolute; + top: 236px; + left: var(--nq-p-page-inset); + right: var(--nq-p-page-inset); + bottom: 130px; + display: flex; + align-items: center; + justify-content: center; + } + + .notice { + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + gap: 20px; + width: 100%; + max-width: 980px; + padding: 56px 64px; + border: 2px solid var(--nq-p-panel-border); + border-radius: 20px; + background: var(--nq-p-card-panel); + box-shadow: var(--nq-p-panel-shadow); + text-align: center; + } + + .notice svg { + width: 46px; + height: 46px; + color: var(--nq-p-ink-secondary); + } + + .notice.away { + background: linear-gradient(#f2ecdd, #e6dcc6); + border-style: dashed; + box-shadow: none; + } + + .notice-headline { + font-family: var(--nq-p-font-heading); + font-size: 40px; + font-weight: 700; + line-height: 1.15; + color: var(--nq-p-ink); + } + + .notice-body { + margin: 0; + font-family: var(--nq-p-font-body); + font-size: 24px; + font-weight: 600; + line-height: 1.4; + color: var(--nq-p-ink-secondary); + } + + .notice-seal { + display: inline-flex; + align-items: center; + justify-content: center; + width: 78px; + height: 78px; + border-radius: var(--nq-p-radius-pill); + background: var(--nq-p-seal); + box-shadow: var(--nq-p-seal-shadow); + color: rgba(255, 235, 235, 0.95); + transform: rotate(-6deg); + } + + .notice-seal svg { + width: 38px; + height: 38px; + color: rgba(255, 235, 235, 0.95); + } + .scrim { position: absolute; inset: 0; @@ -2007,7 +2229,7 @@ const Ct = "polygon(0 0, 100% 0, 100% 62%, 50% 100%, 0 62%)", Ie = "polygon(50% width: 38px; height: 38px; } -`, J = g` -`, He = g``, Wt = p` -`, G = g``, Y = p` -`, je = g``, Zt = p` -`, Le = g``, Le = p` -`, Re = g``, Vt = p` -`, Et = [ - { key: "morning", name: "Morning", range: "Until 11:59 AM", icon: je }, +`, Gt = p``, Kt = p``, Jt = p``, Xt = p``, Yt = { + icon: Kt, + headline: "No adventurer chosen", + body: "Open The Party and tap your crest to open your quest log." +}, en = { + icon: Jt, + headline: "NestQuest is not set up yet", + body: "A parent needs to finish setting up NestQuest." +}, tn = { + icon: Xt, + headline: "The records cannot be reached", + body: "The party's records are quiet right now. NestQuest will return shortly." +}, Oe = [ + { key: "morning", name: "Morning", range: "Until 11:59 AM", icon: Zt }, { key: "afternoon", name: "Afternoon", range: "12:00–5:00 PM", icon: Le }, - { key: "evening", name: "Evening", range: "5:00–9:00 PM", icon: Re } + { key: "evening", name: "Evening", range: "5:00–9:00 PM", icon: Vt } ]; -class Be extends C { +class nn extends C { static properties = { hass: { attribute: !1 }, _config: { state: !0 }, _now: { state: !0 }, _confirm: { state: !0 } }; - static styles = [b(It), De]; + static styles = [v(Re), Qt]; hass; _config; _now = /* @__PURE__ */ new Date(); @@ -2105,10 +2384,10 @@ class Be extends C { _clockTimer; _idleTimer; _confirmTimer; - setConfig(t) { - if (!t || typeof t != "object") + setConfig(e) { + if (!e || typeof e != "object") throw new Error("Invalid configuration"); - this._config = t; + this._config = e; } getCardSize() { return 22; @@ -2122,223 +2401,337 @@ class Be extends C { this._stopClock(), this._clearIdle(), this._clearConfirmTimer(), window.removeEventListener("pointerdown", this._onActivity, !0), window.removeEventListener("touchstart", this._onActivity, !0), window.removeEventListener("keydown", this._onActivity, !0), super.disconnectedCallback(); } render() { - return g` + const e = this._logView(); + return p`
    - ${this._renderHeader()} -
    - ${Et.map((t) => this._renderColumn(t))} -
    + ${this._renderMain(e)} ${this._renderConfirm()}
    `; } - _timeZone() { - return Se(this.hass); + _renderMain(e) { + switch (e.kind) { + case "no-adventurer": + return this._renderNotice(Yt); + case "not-set-up": + return this._renderNotice(en); + case "unreachable": + return this._renderNotice(tn); + case "away": { + const t = ["The quest log unlocks when they return."]; + return e.returns && t.push(`Returns ${e.returns}`), p` + ${this._renderHeader(e)} +
    +
    + ${Gt} + ${e.name} is on travels + ${t.map((n) => p`

    ${n}

    `)} +
    +
    + `; + } + case "empty-day": + return p` + ${this._renderHeader(e)} +
    +
    + ${Le} + No quests today +

    + ${`Nothing was posted for today, ${e.name}. Enjoy the day's rest!`} +

    +
    +
    + `; + case "complete-day": + return p` + ${this._renderHeader(e)} +
    +
    + ${R} + Quest complete +

    + ${`Every quest is claimed, ${e.name}. The seal is set for today.`} +

    +

    Returning to The Party shortly.

    +
    +
    + `; + default: + return p` + ${this._renderHeader(e)} +
    + ${Oe.map((t) => this._renderColumn(t))} +
    + `; + } } - _state(t) { - const n = this.hass?.states?.[t]; + /** Resolve the log's single render state; the priority order is + * design/PANEL-EMPTY-STATES.md §2. */ + _logView() { + const e = this._childSlug(); + if (!e) + return { kind: "no-adventurer" }; + const t = this._dueSensorResolution(e); + if (t === "missing") + return { kind: "not-set-up" }; + if (t === "stale") + return { kind: "unreachable" }; + const n = this._childName(e) ?? X(e); + if (!this._childPresent(e)) + return { kind: "away", name: n, returns: this._awayReturns(e) }; + const i = this._state(`sensor.nestquest_${e}_quests_due_today`); + return $(_(i?.state, 0)) === 0 ? { kind: "empty-day", name: n } : this._remaining() === 0 ? { kind: "complete-day", name: n } : { kind: "normal" }; + } + /** "missing" when the due sensor does not exist (integration not + * configured), "stale" when it answers unavailable/unknown (backend + * unreachable or the child is absent from the snapshot), null when + * it resolves. */ + _dueSensorResolution(e) { + const t = this._state(`sensor.nestquest_${e}_quests_due_today`); + if (!t) + return "missing"; + const n = String(t.state ?? "").trim().toLowerCase(); + return !n || n === "unavailable" || n === "unknown" ? "stale" : null; + } + _childPresent(e) { + const n = this._state(`sensor.nestquest_${e}_quests_due_today`)?.attributes?.present; + if (typeof n == "boolean") + return n; + const i = this._state( + `binary_sensor.nestquest_${e}_present_today` + ); + return i ? String(i.state ?? "").trim().toLowerCase() === "on" : !0; + } + _awayReturns(e) { + const t = this._timeZone(), n = this._state( + `binary_sensor.nestquest_${e}_present_today` + ), i = Ne( + n?.attributes?.next_present, + t + ); + return i ? Ee(i, t) : null; + } + _renderNotice(e) { + return p` +
    +
    + ${e.icon} + ${e.headline} +

    ${e.body}

    +
    +
    + `; + } + _timeZone() { + return Ut(this.hass); + } + _state(e) { + const n = this.hass?.states?.[e]; return n && typeof n == "object" ? n : null; } _childSlug() { - return Oe(); + return Lt(); } _boardPath() { return k(this._config?.board_path).replace(/\/+$/, ""); } _idleSeconds() { - const t = y(this._config?.idle_return_seconds, 40); - return t > 0 ? t : 40; + const e = _(this._config?.idle_return_seconds, 40); + return e > 0 ? e : 40; } _confirmSeconds() { - const t = y(this._config?.confirm_timeout_seconds, 15); - return t > 0 ? t : 15; + const e = _(this._config?.confirm_timeout_seconds, 15); + return e > 0 ? e : 15; } - _childName(t) { - if (!t) + _childName(e) { + if (!e) return null; - const e = this._state(`sensor.nestquest_${t}_quests_due_today`)?.attributes?.child_name; - return typeof e == "string" && e.trim() ? e.trim() : null; + const t = this._state(`sensor.nestquest_${e}_quests_due_today`)?.attributes?.child_name; + return typeof t == "string" && t.trim() ? t.trim() : null; } - _childCounts(t) { - const e = this._state(`sensor.nestquest_${t}_quests_due_today`), n = this._state( - `sensor.nestquest_${t}_quests_completed_today` - ), s = $(y(e?.state, 0)), r = $(y(n?.state, 0)); - return { due: s, completed: r }; + _childCounts(e) { + const t = this._state(`sensor.nestquest_${e}_quests_due_today`), n = this._state( + `sensor.nestquest_${e}_quests_completed_today` + ), i = $(_(t?.state, 0)), r = $(_(n?.state, 0)); + return { due: i, completed: r }; } _instances() { - const t = this._childSlug(); - if (!t) + const e = this._childSlug(); + if (!e) return []; - const e = this._state(`sensor.nestquest_${t}_quests_due_today`)?.attributes?.instances; - return Array.isArray(e) ? e.map(Ue).filter((n) => n !== null) : []; + const t = this._state(`sensor.nestquest_${e}_quests_due_today`)?.attributes?.instances; + return Array.isArray(t) ? t.map(Bt).filter((n) => n !== null) : []; } _remaining() { - const t = this._childSlug(); - if (!t) + const e = this._childSlug(); + if (!e) return 0; - const e = this._state( - `sensor.nestquest_${t}_quests_remaining_today` + const t = this._state( + `sensor.nestquest_${e}_quests_remaining_today` ); - if (e) - return $(y(e.state, 0)); - const { due: n, completed: s } = this._childCounts(t); - return $(n - s); + if (t) + return $(_(t.state, 0)); + const { due: n, completed: i } = this._childCounts(e); + return $(n - i); } _partyCounts() { - const t = this._state( + const e = this._state( "sensor.nestquest_household_quests_due_today" - ), e = this._state( + ), t = this._state( "sensor.nestquest_household_quests_completed_today" ); - if (t || e) + if (e || t) return { - completed: $(y(e?.state, 0)), - due: $(y(t?.state, 0)) + completed: $(_(t?.state, 0)), + due: $(_(e?.state, 0)) }; - const n = this._childSlug(), { due: s, completed: r } = this._childCounts(n); - return { completed: r, due: s }; + const n = this._childSlug(), { due: i, completed: r } = this._childCounts(n); + return { completed: r, due: i }; } _otherChildren() { - const t = this.hass?.states; - if (!t) + const e = this.hass?.states; + if (!e) return []; - const e = this._childSlug(), n = this._timeZone(), s = []; - for (const r of Object.keys(t)) { + const t = this._childSlug(), n = this._timeZone(), i = []; + for (const r of Object.keys(e)) { const o = /^sensor\.nestquest_(.+)_quests_due_today$/.exec(r); - if (!o || o[1] === "household" || o[1] === e) + if (!o || o[1] === "household" || o[1] === t) continue; - const l = o[1], a = t[r]?.attributes ?? {}; - let p = ""; - typeof a.child_name == "string" && a.child_name.trim() ? p = a.child_name.trim() : p = At(l); + const l = o[1], a = e[r]?.attributes ?? {}; + let c = ""; + typeof a.child_name == "string" && a.child_name.trim() ? c = a.child_name.trim() : c = X(l); let u = !0; if (typeof a.present == "boolean") u = a.present; else { - const _ = this._state( + const y = this._state( `binary_sensor.nestquest_${l}_present_today` ); - u = _ === null || String(_.state ?? "").trim().toLowerCase() !== "off"; + u = y === null || String(y.state ?? "").trim().toLowerCase() !== "off"; } const d = this._state( `sensor.nestquest_${l}_quests_completed_today` - ), h = $(y(t[r]?.state, 0)), c = $(y(d?.state, 0)); - let f = null; + ), f = $(_(e[r]?.state, 0)), h = $(_(d?.state, 0)); + let m = null; if (!u) { - const _ = this._state( + const y = this._state( `binary_sensor.nestquest_${l}_present_today` - ), v = Pe( - _?.attributes?.next_present, + ), b = Ne( + y?.attributes?.next_present, n ); - f = v ? Ne(v, n) : null; + m = b ? Dt(b, n) : null; } - s.push({ + i.push({ slug: l, - name: p, + name: c, present: u, - due: h, - completed: c, - returnsWeekday: f + due: f, + completed: h, + returnsWeekday: m }); } - return s.sort((r, o) => r.name.localeCompare(o.name)); + return i.sort((r, o) => r.name.localeCompare(o.name)); } - _renderHeader() { - const t = this._childSlug(), n = this._childName(t) ?? (t ? At(t) : null), { due: s, completed: r } = this._childCounts(t), o = this._remaining(), l = this._partyCounts(), a = n ? `${n}'s Quest Log` : "Quest Log", p = (n?.charAt(0) || "?").toUpperCase(); - return g` + _renderHeader(e) { + const t = this._childSlug(), n = e.kind === "away", i = n ? e.name : this._childName(t) ?? (t ? X(t) : null), { due: r, completed: o } = this._childCounts(t), l = this._remaining(), a = this._partyCounts(), c = i ? `${i}'s Quest Log` : "Quest Log", u = (i?.charAt(0) || "?").toUpperCase(); + return p`
    -
    `; } - _renderColumn(t) { - const e = this._instances().filter( - (l) => l.window === t.key - ), n = e.filter((l) => l.state === "open").sort( + _renderColumn(e) { + const t = this._instances().filter( + (l) => l.window === e.key + ), n = t.filter((l) => l.state === "open").sort( (l, a) => (l.due_time ?? "99:99").localeCompare(a.due_time ?? "99:99") - ), s = e.filter( + ), i = t.filter( (l) => l.state === "completed" - ), r = e.length; - let o = m; + ), r = t.length; + let o = g; if (r > 0) { - const a = 644 - (t.key === "afternoon" && this._otherChildren().length > 0 ? 160 : 0), p = Math.min( + const a = 644 - (e.key === "afternoon" && this._otherChildren().length > 0 ? 160 : 0), c = Math.min( 24, Math.max(8, Math.floor((a - r * 74) / Math.max(r - 1, 1))) ), u = Math.min( 116, - Math.max(58, Math.floor((a - (r - 1) * p) / r)) + Math.max(58, Math.floor((a - (r - 1) * c) / r)) ), d = Math.max(56, Math.min(72, u - 2)); - let h = `--nq-p-quest-gap: ${p}px; --nq-p-quest-min-height: ${u}px; --nq-p-button-height: ${d}px`; + let f = `--nq-p-quest-gap: ${c}px; --nq-p-quest-min-height: ${u}px; --nq-p-button-height: ${d}px`; if (r >= 5) { - const c = Math.max(0, Math.min(22, Math.floor((u - d - 2) / 2))), f = Math.max(44, Math.min(64, u - 2 * c - 2)); - h += `; --nq-p-quest-pad: ${c}px; --nq-p-tile-size: ${f}px`; + const h = Math.max(0, Math.min(22, Math.floor((u - d - 2) / 2))), m = Math.max(44, Math.min(64, u - 2 * h - 2)); + f += `; --nq-p-quest-pad: ${h}px; --nq-p-tile-size: ${m}px`; } - o = h; + o = f; } - return g` + return p`
    - ${t.icon} + ${e.icon}
    - ${t.name} - ${t.range} + ${e.name} + ${e.range}
    - ${s.length}/${e.length} + ${i.length}/${t.length}
    - ${wt( + ${Ce( n, (l) => l.id, (l) => this._renderQuest(l, !1, 0) )} - ${wt( - s, + ${Ce( + i, (l) => l.id, (l, a) => this._renderQuest(l, !0, a) )} - ${t.key === "afternoon" ? this._renderRollup() : m} + ${e.key === "afternoon" ? this._renderRollup() : g}
    `; } - _renderQuest(t, e, n) { - if (e) { - const o = St[n % St.length], l = t.completed_at ? Te(t.completed_at, this._timeZone()) : ""; - return g` -
    - + _renderQuest(e, t, n) { + if (t) { + const o = Me[n % Me.length], l = e.completed_at ? Ht(e.completed_at, this._timeZone()) : ""; + return p` +
    +
    - ${t.title} + ${e.title} ${l ? `Claimed ${l}` : "Claimed"} @@ -2348,68 +2741,68 @@ class Be extends C { aria-hidden="true" style="--seal-rot: ${o}deg" > - ${J} + ${R}
    `; } - const s = Me(t.due_time), r = t.overdue ? { - text: s ? `Overdue · due ${s}` : "Overdue", + const i = jt(e.due_time), r = e.overdue ? { + text: i ? `Overdue · due ${i}` : "Overdue", late: !0 } : { - text: s ? `Due by ${s}` : "Due today", + text: i ? `Due by ${i}` : "Due today", late: !1 }; - return g` + return p`
    this._openConfirm(t.id)} + data-instance-id=${e.id} + aria-label="Complete ${e.title}" + @click=${() => this._openConfirm(e.id)} @keydown=${(o) => { - (o.key === "Enter" || o.key === " ") && (o.preventDefault(), this._openConfirm(t.id)); + (o.key === "Enter" || o.key === " ") && (o.preventDefault(), this._openConfirm(e.id)); }} > - +
    - ${t.title} - ${r.text} + ${e.title} + ${r.text}
    `; } _renderRollup() { - const t = this._otherChildren(); - if (t.length === 0) - return m; - const e = t.slice(0, 2).map((n) => n.present ? n.due > 0 && n.completed >= n.due ? `${n.name} has finished their log.` : n.due > n.completed ? `${n.name} has ${n.due - n.completed} quests left.` : `${n.name} has no quests today.` : n.returnsWeekday ? `${n.name} is on travels until ${n.returnsWeekday}.` : `${n.name} is on travels.`); - return g` + const e = this._otherChildren(); + if (e.length === 0) + return g; + const t = e.slice(0, 2).map((n) => n.present ? n.due > 0 && n.completed >= n.due ? `${n.name} has finished their log.` : n.due > n.completed ? `${n.name} has ${n.due - n.completed} quests left.` : `${n.name} has no quests today.` : n.returnsWeekday ? `${n.name} is on travels until ${n.returnsWeekday}.` : `${n.name} is on travels.`); + return p`
    Party roll-up -

    ${e.join(" ")}

    +

    ${t.join(" ")}

    `; } _renderConfirm() { if (this._confirm === null) - return m; - const t = this._instances().find( + return g; + const e = this._instances().find( (n) => n.id === this._confirm ); - if (!t) - return m; - const e = Et.find((n) => n.key === t.window); - return g` + if (!e) + return g; + const t = Oe.find((n) => n.key === e.window); + return p`
    this._closeConfirm()}>
    n.stopPropagation()} >
    - +
    - ${e?.name ?? "Quest"} + ${t?.name ?? "Quest"} - ${t.title} + ${e.title}

    @@ -2436,7 +2829,7 @@ class Be extends C { type="button" @click=${() => this._closeConfirm()} > - ${He} + ${Wt} Not yet

    @@ -2452,8 +2845,8 @@ class Be extends C {
    `; } - _openConfirm(t) { - this._confirm = t, this._armConfirmTimer(); + _openConfirm(e) { + this._confirm = e, this._armConfirmTimer(); } _closeConfirm() { this._clearConfirmTimer(), this._confirm = null; @@ -2478,8 +2871,8 @@ class Be extends C { this._idleTimer !== void 0 && (window.clearTimeout(this._idleTimer), this._idleTimer = void 0); } _returnToBoard() { - const t = this._boardPath(); - t && (window.history.pushState(null, "", t), window.dispatchEvent(new Event("location-changed"))); + const e = this._boardPath(); + e && (window.history.pushState(null, "", e), window.dispatchEvent(new Event("location-changed"))); } _stopClock() { this._clockTimer !== void 0 && (window.clearInterval(this._clockTimer), this._clockTimer = void 0); @@ -2488,41 +2881,41 @@ class Be extends C { this._armIdle(), this._confirm !== null && this._armConfirmTimer(); }; } -customElements.define("nestquest-quest-log-card", Be); -st({ +customElements.define("nestquest-quest-log-card", nn); +oe({ type: "nestquest-quest-log-card", name: "NestQuest Quest Log", description: "One child's daily quests, grouped by window." }); -const Fe = ":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 Qe extends C { +const sn = ":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 rn extends C { static properties = { hass: { attribute: !1 }, _config: { state: !0 } }; - static styles = [b(Fe)]; + static styles = [v(sn)]; hass; _config; - setConfig(t) { - if (!t || typeof t != "object") + setConfig(e) { + if (!e || typeof e != "object") throw new Error("Invalid configuration"); - this._config = t; + this._config = e; } getCardSize() { return 8; } render() { - return g`
    NestQuest Admin
    `; + return p`
    NestQuest Admin
    `; } } -customElements.define("nestquest-admin-card", Qe); -st({ +customElements.define("nestquest-admin-card", rn); +oe({ type: "nestquest-admin-card", name: "NestQuest Admin", description: "Parent phone view for today, tasks, schedule, and history." }); -const We = "/nestquest-static/nestquest-fonts.css"; +const on = "/nestquest-static/nestquest-fonts.css"; if (!document.querySelector('link[data-nq-fonts=""]')) { - const i = document.createElement("link"); - i.rel = "stylesheet", i.href = We, i.dataset.nqFonts = "", document.head.appendChild(i); + const s = document.createElement("link"); + s.rel = "stylesheet", s.href = on, s.dataset.nqFonts = "", document.head.appendChild(s); } diff --git a/design/PANEL-EMPTY-STATES.md b/design/PANEL-EMPTY-STATES.md new file mode 100644 index 0000000..87bc76f --- /dev/null +++ b/design/PANEL-EMPTY-STATES.md @@ -0,0 +1,104 @@ +# Panel empty & error states — design addendum + +Closes the gap DESIGN-HANDOFF.md lists under "Open questions": *"Empty and error +states are unspecified by request (child with no quests, everyone away, integration +unconfigured, DB unreachable). Panel must not render a blank parchment — decide +before shipping the card."* + +All decisions below reuse PANEL-SPEC.md's approved aesthetic — parchment ground, +double frame, dock, panel tokens, no text below 22px, no alpha-muted ink — and add +one shared component per card: a **notice panel** (radius 20px, `--nq-p-card-panel`, +2px `--nq-p-panel-border`, `--nq-p-panel-shadow`, centred icon + headline + body). +The frame, wordmark row, kicker and dock always render; only the content area +changes. **No state renders a blank parchment.** + +## 1. What the data can say + +The cards read `sensor.nestquest__quests_due_today` (plus siblings). One of +four things comes back, and the card must tell them apart: + +| Backend condition | What the card sees | Card's interpretation | +|---|---|---| +| Resolving (DB fine) | numeric state + attributes (`child_name`, `present`, `instances`) | normal, all states below | +| Integration not configured | entity does not exist at all | **not set up** | +| Backend unreachable (DB locked, refresh failed) | state `unavailable` | **records unreachable** | +| Child deactivated / absent from snapshot | state `unknown` | **records unreachable** (same panel voice: the records do not answer) | + +An away child (presence `present: false`, from the due-sensor attributes with the +presence binary sensor as fallback) is *not* an error: it is a first-class panel +state ("On travels"). Away children get no instances (materialiser skips them), so +away is detected from the presence flag, never from a zero quest count. + +## 2. Quest log — state priority + +`nestquest-quest-log-card` resolves exactly one state, in this order: + +1. **No adventurer in the path** (direct navigation to a URL with no child slug — + misconfigured `board_path`/`quest_log_path`) → notice *"No adventurer chosen"*, + body *"Open The Party and tap your crest to open your quest log."* +2. **Not set up** (entity missing) → notice *"NestQuest is not set up yet"*, body + *"A parent needs to finish setting up NestQuest."* +3. **Records unreachable** (`unavailable`/`unknown`) → notice *"The records cannot + be reached"*, body *"The party's records are quiet right now. NestQuest will + return shortly."* +4. **Away** (present flag false) → away notice: dashed muted panel (the away-plate + gradient), tent icon, *" is on travels"*, *"The quest log unlocks when + they return."* and, when `next_present` is known, *"Returns , "*. + Direct navigation is the one path that can reach this state (the board's away + plate is not tappable); the log answers it honestly instead of showing an empty + column set. Header renders with the crest in away colours and the sub line + `… · On travels`; the remaining-count block is hidden (it is meaningless while + away). +5. **No quests today** (present, due = 0) → all-clear notice, sun icon, *"No quests + today"*, body *"Nothing was posted for today, . Enjoy the day's rest!"* — + never three bare empty columns. The header and remaining block stay (0 quests + left, party progress), so the log still reads like the log. +6. **All quests done** (due > 0, remaining = 0) → the fallback celebration that + shows until the 1c quest-complete screen takes over: centred wax-seal mark (the + `--nq-p-seal` tokens at 78px, check glyph, −6°) with headline *"Quest complete"* + — the same phrase screen 1c uses — and body *"Every quest is claimed, . + The seal is set for today. Returning to The Party shortly."* The idle return + (§3.5) is what actually navigates back; 1c remains the designed takeover. +7. **Normal** → the three-window column layout of PANEL-SPEC §3.1 unchanged. A + window with no quests still renders its head alone, exactly as the spec says. + +Notices sit in the columns' content area, vertically centred. Error notices (states +1–3) replace the header entirely — there is no honest name to headline when the +records do not answer — while states 4–6 keep the full header. + +## 3. Party board — state priority + +`nestquest-party-board-card` resolves per configured child, then at board level: + +1. **No children configured** (`child_order` empty) → board notice *"NestQuest is + not set up yet"*, body *"A parent needs to finish setting up NestQuest before + the party can gather."* +2. **Every configured child unresolvable** → one board notice instead of a grid of + broken plates: *"NestQuest is not set up yet"* when the entities are missing, + *"The records cannot be reached"* when they answer `unavailable`/`unknown`. +3. **Partial** (some children resolve, some do not) → the board still renders the + resolving plates; an unresolvable child renders a muted, dashed plate in the + away geometry with a compass icon and the pill label **"Unknown"**, progress + line `—`, empty bar, **not tappable**. It must not claim "Home today" (0 of 0) + and must not lie "On travels": the records simply do not say. +4. **Normal** → the plates grid of PANEL-SPEC §2 unchanged (present + away plates). + +The hint line ("Tap your crest…") is hidden in board-level notice states — there +is nothing to tap. + +## 4. Shared rules + +- **Never a blank parchment.** Every render path emits the frames, wordmark, + kicker, dock, and either content or a notice panel. The dock already degrades to + date + time when weather is unset (PANEL-SPEC §2.3); that behaviour is unchanged. +- **Notice panel type scale:** icon 46px, headline 40px/700 Cinzel + (`--nq-p-font-heading`, `--nq-p-ink`), body 24px/600 Nunito + (`--nq-p-ink-secondary`), `role="status"` so assistive tech announces it. +- **Tone.** The panel speaks tabletop (PANEL-SPEC vocabulary) but never lies: an + error says the records cannot be reached, not that everything is fine. Error + copy names the actor who can fix it (a parent) or promises the return of the + data — no stack traces, no entity IDs. +- **No interaction in error/away states.** No confirm dialog, no idle return + changes, no navigation attempts. Notices are static. +- Both cards implement the same resolution rules, so the board and the log never + disagree about whether the integration is up. diff --git a/frontend/src/cards/party-board.ts b/frontend/src/cards/party-board.ts index 891c368..13a022d 100644 --- a/frontend/src/cards/party-board.ts +++ b/frontend/src/cards/party-board.ts @@ -1,4 +1,4 @@ -import { LitElement, css, html, nothing, unsafeCSS } from "lit"; +import { LitElement, css, html, nothing, unsafeCSS, type TemplateResult } from "lit"; import panelTokens from "../../../custom_components/nestquest/www/nestquest-panel-tokens.css?inline"; import { registerCustomCard, type CardConfig, type HassLike } from "../types"; @@ -17,6 +17,14 @@ type ChildPlate = { due: number; pct: number; returns: string | null; + /** Set when the child's sensors cannot resolve; the plate shows "Unknown". */ + unresolved: "missing" | "stale" | null; +}; + +type BoardNotice = { + icon: TemplateResult; + headline: string; + body: string; }; type DockWeather = { @@ -476,6 +484,62 @@ const boardStyles = css` color: var(--nq-p-ink-secondary); } + .plate.unknown { + border: 2px dashed var(--nq-p-panel-border); + background: linear-gradient(#f2ecdd, #e6dcc6); + box-shadow: none; + } + + .notice-wrap { + position: absolute; + top: 262px; + left: 110px; + right: 110px; + bottom: 190px; + display: flex; + align-items: center; + justify-content: center; + } + + .notice { + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + gap: 20px; + width: 100%; + max-width: 980px; + padding: 56px 64px; + border: 2px solid var(--nq-p-panel-border); + border-radius: var(--nq-p-radius-panel); + background: var(--nq-p-card-panel); + box-shadow: var(--nq-p-panel-shadow); + text-align: center; + } + + .notice svg { + width: 46px; + height: 46px; + color: var(--nq-p-ink-secondary); + } + + .notice-headline { + font-family: var(--nq-p-font-heading); + font-size: 40px; + font-weight: 700; + line-height: 1.15; + color: var(--nq-p-ink); + } + + .notice-body { + margin: 0; + font-family: var(--nq-p-font-body); + font-size: 24px; + font-weight: 600; + line-height: 1.4; + color: var(--nq-p-ink-secondary); + } + .spacer { flex: 1 1 0; } @@ -569,6 +633,58 @@ const ICON_TENT = html` `; +const ICON_COMPASS = html``; + +const ICON_ALERT = html``; + +const ICON_CLOUD_OFF = html``; + +const BOARD_NOTICE_NOT_SET_UP: BoardNotice = { + icon: ICON_ALERT, + headline: "NestQuest is not set up yet", + body: "A parent needs to finish setting up NestQuest before the party can gather.", +}; + +const BOARD_NOTICE_UNREACHABLE: BoardNotice = { + icon: ICON_CLOUD_OFF, + headline: "The records cannot be reached", + body: "The party's records are quiet right now. NestQuest will return shortly.", +}; + const ICON_CLOUD_SUN = html`
    @@ -644,10 +761,14 @@ export class NestQuestPartyBoardCard extends LitElement {

    The Party · ${formatDay(this._now, this._timeZone())}

    -
    - ${this._plates().map((plate) => this._renderPlate(plate))} -
    -

    Tap your crest to open your Quest Log

    + ${notice + ? this._renderNotice(notice) + : html` +
    + ${this._plates().map((plate) => this._renderPlate(plate))} +
    +

    Tap your crest to open your Quest Log

    + `}
    ${this._renderDock()}
    @@ -687,6 +808,7 @@ export class NestQuestPartyBoardCard extends LitElement { } private _plate(slug: string): ChildPlate { + const unresolved = this._dueSensorResolution(slug); const dueSensor = this._state(`sensor.nestquest_${slug}_quests_due_today`); const doneSensor = this._state( `sensor.nestquest_${slug}_quests_completed_today` @@ -744,15 +866,75 @@ export class NestQuestPartyBoardCard extends LitElement { slug, name, initial: (name.charAt(0) || "?").toUpperCase(), - present, + present: unresolved === null ? present : false, completed, due, pct, returns, + unresolved, }; } + /** "missing" when the due sensor does not exist (integration not + * configured), "stale" when it answers unavailable/unknown (backend + * unreachable or the child is absent from the snapshot), null when + * it resolves. */ + private _dueSensorResolution(slug: string): "missing" | "stale" | null { + const sensor = this._state(`sensor.nestquest_${slug}_quests_due_today`); + if (!sensor) { + return "missing"; + } + const state = String(sensor.state ?? "").trim().toLowerCase(); + if (!state || state === "unavailable" || state === "unknown") { + return "stale"; + } + return null; + } + + private _boardNotice(): BoardNotice | null { + const plates = this._plates(); + if (plates.length === 0) { + return BOARD_NOTICE_NOT_SET_UP; + } + if (plates.every((plate) => plate.unresolved !== null)) { + return plates.some((plate) => plate.unresolved === "stale") + ? BOARD_NOTICE_UNREACHABLE + : BOARD_NOTICE_NOT_SET_UP; + } + return null; + } + + private _renderNotice(notice: BoardNotice) { + return html` +
    +
    + ${notice.icon} + ${notice.headline} +

    ${notice.body}

    +
    +
    + `; + } + private _renderPlate(plate: ChildPlate) { + if (plate.unresolved) { + return html` +
    + + + ${plate.initial} + + + ${plate.name} + + ${ICON_COMPASS} + Unknown + + — + +
    + `; + } if (plate.present) { return html` - ` : p` + ` : c`
    - ${e.initial} + ${t.initial} - ${e.name} + ${t.name} - ${qt} + ${we} On travels - ${e.returns ?? "Returns —"} + ${t.returns ?? "Returns —"}
    `; } - _openQuestLog(e) { - const t = ve(this._config?.quest_log_path); - t && (window.history.pushState(null, "", `${t.replace(/\/+$/, "")}/${e}`), window.dispatchEvent(new Event("location-changed"))); + _openQuestLog(t) { + const e = vt(this._config?.quest_log_path); + e && (window.history.pushState(null, "", `${e.replace(/\/+$/, "")}/${t}`), window.dispatchEvent(new Event("location-changed"))); } _dockWeather() { - const e = ve(this._config?.weather_entity); - if (!e) - return null; - const t = this._state(e); + const t = vt(this._config?.weather_entity); if (!t) return null; - const n = String(t.state ?? "").trim(); + const e = this._state(t); + if (!e) + return null; + const n = String(e.state ?? "").trim(); if (!n || n === "unavailable" || n === "unknown") return null; - const i = t.attributes ?? {}; + const i = e.attributes ?? {}; let r = null, o = null; const l = i.forecast; if (Array.isArray(l) && l.length > 0) { const a = l[0]; if (a && typeof a == "object") { - const c = a; - r = this._optionalNumber(c.temperature), o = this._optionalNumber(c.templow); + const p = a; + r = this._optionalNumber(p.temperature), o = this._optionalNumber(p.templow); } } return { @@ -1331,30 +1331,30 @@ class Et extends C { low: o }; } - _optionalNumber(e) { - if (e == null || e === "") + _optionalNumber(t) { + if (t == null || t === "") return null; - const t = typeof e == "number" ? e : Number(e); - return Number.isFinite(t) ? t : null; + const e = typeof t == "number" ? t : Number(t); + return Number.isFinite(e) ? e : null; } _renderDock() { - const e = this._dockWeather(); - if (!e) - return p` + const t = this._dockWeather(); + if (!t) + return c`
    ${G(this._now, this._timeZone())} - ${ht(this._now, this._timeZone())} + ${he(this._now, this._timeZone())}
    `; - const t = _t(e.condition), n = vt(e.condition, t), i = e.temperature === null ? g : p`${Math.round(e.temperature)}°`, r = e.high === null || e.low === null ? null : `${Math.round(e.high)}° / ${Math.round(e.low)}°`; - return p` + const e = ye(t.condition), n = ve(t.condition, e), i = t.temperature === null ? g : c`${Math.round(t.temperature)}°`, r = t.high === null || t.low === null ? null : `${Math.round(t.high)}° / ${Math.round(t.low)}°`; + return c`
    - ${St} + ${Se} ${i} - ${r === null ? t : `${t} · ${r}`} + ${r === null ? e : `${e} · ${r}`} ${n} @@ -1362,167 +1362,167 @@ class Et extends C { `; } } -customElements.define("nestquest-party-board-card", Et); -oe({ +customElements.define("nestquest-party-board-card", Ee); +ot({ type: "nestquest-party-board-card", name: "NestQuest Party Board", description: "Kids' attract screen for present and away adventurers." }); -const Nt = { CHILD: 2 }, Tt = (s) => (...e) => ({ _$litDirective$: s, values: e }); -let Mt = class { - constructor(e) { +const Ne = { CHILD: 2 }, Te = (s) => (...t) => ({ _$litDirective$: s, values: t }); +let Me = class { + constructor(t) { } get _$AU() { return this._$AM._$AU; } - _$AT(e, t, n) { - this._$Ct = e, this._$AM = t, this._$Ci = n; + _$AT(t, e, n) { + this._$Ct = t, this._$AM = e, this._$Ci = n; } - _$AS(e, t) { - return this.update(e, t); + _$AS(t, e) { + return this.update(t, e); } - update(e, t) { - return this.render(...t); + update(t, e) { + return this.render(...e); } }; -const { I: Ot } = at, we = (s) => s, ke = () => document.createComment(""), z = (s, e, t) => { - const n = s._$AA.parentNode, i = e === void 0 ? s._$AB : e._$AA; - if (t === void 0) { - const r = n.insertBefore(ke(), i), o = n.insertBefore(ke(), i); - t = new Ot(r, o, s, s.options); +const { I: Oe } = ae, qt = (s) => s, kt = () => document.createComment(""), z = (s, t, e) => { + const n = s._$AA.parentNode, i = t === void 0 ? s._$AB : t._$AA; + if (e === void 0) { + const r = n.insertBefore(kt(), i), o = n.insertBefore(kt(), i); + e = new Oe(r, o, s, s.options); } else { - const r = t._$AB.nextSibling, o = t._$AM, l = o !== s; + const r = e._$AB.nextSibling, o = e._$AM, l = o !== s; if (l) { let a; - t._$AQ?.(s), t._$AM = s, t._$AP !== void 0 && (a = s._$AU) !== o._$AU && t._$AP(a); + e._$AQ?.(s), e._$AM = s, e._$AP !== void 0 && (a = s._$AU) !== o._$AU && e._$AP(a); } if (r !== i || l) { - let a = t._$AA; + let a = e._$AA; for (; a !== r; ) { - const c = we(a).nextSibling; - we(n).insertBefore(a, i), a = c; + const p = qt(a).nextSibling; + qt(n).insertBefore(a, i), a = p; } } } - return t; -}, w = (s, e, t = s) => (s._$AI(e, t), s), zt = {}, Pt = (s, e = zt) => s._$AH = e, It = (s) => s._$AH, J = (s) => { + return e; +}, q = (s, t, e = s) => (s._$AI(t, e), s), ze = {}, Pe = (s, t = ze) => s._$AH = t, Ie = (s) => s._$AH, J = (s) => { s._$AR(), s._$AA.remove(); }; -const Ae = (s, e, t) => { +const At = (s, t, e) => { const n = /* @__PURE__ */ new Map(); - for (let i = e; i <= t; i++) n.set(s[i], i); + for (let i = t; i <= e; i++) n.set(s[i], i); return n; -}, Ce = Tt(class extends Mt { +}, Ct = Te(class extends Me { constructor(s) { - if (super(s), s.type !== Nt.CHILD) throw Error("repeat() can only be used in text expressions"); + if (super(s), s.type !== Ne.CHILD) throw Error("repeat() can only be used in text expressions"); } - dt(s, e, t) { + dt(s, t, e) { let n; - t === void 0 ? t = e : e !== void 0 && (n = e); + e === void 0 ? e = t : t !== void 0 && (n = t); const i = [], r = []; let o = 0; - for (const l of s) i[o] = n ? n(l, o) : o, r[o] = t(l, o), o++; + for (const l of s) i[o] = n ? n(l, o) : o, r[o] = e(l, o), o++; return { values: r, keys: i }; } - render(s, e, t) { - return this.dt(s, e, t).values; + render(s, t, e) { + return this.dt(s, t, e).values; } - update(s, [e, t, n]) { - const i = It(s), { values: r, keys: o } = this.dt(e, t, n); + update(s, [t, e, n]) { + const i = Ie(s), { values: r, keys: o } = this.dt(t, e, n); if (!Array.isArray(i)) return this.ut = o, r; const l = this.ut ??= [], a = []; - let c, u, d = 0, f = i.length - 1, h = 0, m = r.length - 1; + let p, u, d = 0, f = i.length - 1, h = 0, m = r.length - 1; for (; d <= f && h <= m; ) if (i[d] === null) d++; else if (i[f] === null) f--; - else if (l[d] === o[h]) a[h] = w(i[d], r[h]), d++, h++; - else if (l[f] === o[m]) a[m] = w(i[f], r[m]), f--, m--; - else if (l[d] === o[m]) a[m] = w(i[d], r[m]), z(s, a[m + 1], i[d]), d++, m--; - else if (l[f] === o[h]) a[h] = w(i[f], r[h]), z(s, i[d], i[f]), f--, h++; - else if (c === void 0 && (c = Ae(o, h, m), u = Ae(l, d, f)), c.has(l[d])) if (c.has(l[f])) { - const y = u.get(o[h]), b = y !== void 0 ? i[y] : null; + else if (l[d] === o[h]) a[h] = q(i[d], r[h]), d++, h++; + else if (l[f] === o[m]) a[m] = q(i[f], r[m]), f--, m--; + else if (l[d] === o[m]) a[m] = q(i[d], r[m]), z(s, a[m + 1], i[d]), d++, m--; + else if (l[f] === o[h]) a[h] = q(i[f], r[h]), z(s, i[d], i[f]), f--, h++; + else if (p === void 0 && (p = At(o, h, m), u = At(l, d, f)), p.has(l[d])) if (p.has(l[f])) { + const _ = u.get(o[h]), b = _ !== void 0 ? i[_] : null; if (b === null) { const j = z(s, i[d]); - w(j, r[h]), a[h] = j; - } else a[h] = w(b, r[h]), z(s, i[d], b), i[y] = null; + q(j, r[h]), a[h] = j; + } else a[h] = q(b, r[h]), z(s, i[d], b), i[_] = null; h++; } else J(i[f]), f--; else J(i[d]), d++; for (; h <= m; ) { - const y = z(s, a[m + 1]); - w(y, r[h]), a[h++] = y; + const _ = z(s, a[m + 1]); + q(_, r[h]), a[h++] = _; } for (; d <= f; ) { - const y = i[d++]; - y !== null && J(y); + const _ = i[d++]; + _ !== null && J(_); } - return this.ut = o, Pt(s, a), E; + return this.ut = o, Pe(s, a), E; } -}), Se = /* @__PURE__ */ new Map(); -function H(s, e) { - const t = `${s ?? "local"}|${JSON.stringify(e)}`, n = Se.get(t); +}), St = /* @__PURE__ */ new Map(); +function H(s, t) { + const e = `${s ?? "local"}|${JSON.stringify(t)}`, n = St.get(e); if (n) return n; let i; try { i = new Intl.DateTimeFormat("en-US", { - ...e, + ...t, ...s ? { timeZone: s } : {} }); } catch { - i = new Intl.DateTimeFormat("en-US", e); + i = new Intl.DateTimeFormat("en-US", t); } - return Se.set(t, i), i; + return St.set(e, i), i; } -function Ut(s) { - const e = s?.config; - if (!e || typeof e != "object") +function Ue(s) { + const t = s?.config; + if (!t || typeof t != "object") return; - const t = e.time_zone; - return typeof t == "string" && t.trim() ? t.trim() : void 0; + const e = t.time_zone; + return typeof e == "string" && e.trim() ? e.trim() : void 0; } -function Ee(s, e) { - return `${H(e, { weekday: "long" }).format(s)}, ${H(e, { month: "short", day: "numeric" }).format(s)}`; +function Et(s, t) { + return `${H(t, { weekday: "long" }).format(s)}, ${H(t, { month: "short", day: "numeric" }).format(s)}`; } -function Dt(s, e) { - return H(e, { weekday: "long" }).format(s); +function De(s, t) { + return H(t, { weekday: "long" }).format(s); } -function Ht(s, e) { - const t = new Date(s); - return Number.isNaN(t.getTime()) ? "" : H(e, { +function He(s, t) { + const e = new Date(s); + return Number.isNaN(e.getTime()) ? "" : H(t, { hour: "numeric", minute: "2-digit", hour12: !0 - }).format(t); + }).format(e); } -function jt(s) { +function je(s) { if (!s) return null; - const e = /^(\d{1,2}):(\d{2})$/.exec(s.trim()); - if (!e) + const t = /^(\d{1,2}):(\d{2})$/.exec(s.trim()); + if (!t) return null; - const t = Number(e[1]); - if (t > 23) + const e = Number(t[1]); + if (e > 23) return null; - const n = t >= 12 ? "PM" : "AM"; - return `${t % 12 === 0 ? 12 : t % 12}:${e[2]} ${n}`; + const n = e >= 12 ? "PM" : "AM"; + return `${e % 12 === 0 ? 12 : e % 12}:${t[2]} ${n}`; } function k(s) { return typeof s == "string" ? s.trim() : ""; } -function _(s, e) { +function y(s, t) { if (s == null || s === "") - return e; - const t = typeof s == "number" ? s : Number(s); - return Number.isFinite(t) ? t : e; + return t; + const e = typeof s == "number" ? s : Number(s); + return Number.isFinite(e) ? e : t; } -function $(s) { +function x(s) { return Math.max(0, Math.round(s)); } function X(s) { - return s.split(/[-_]+/).filter(Boolean).map((e) => e.charAt(0).toUpperCase() + e.slice(1)).join(" "); + return s.split(/[-_]+/).filter(Boolean).map((t) => t.charAt(0).toUpperCase() + t.slice(1)).join(" "); } -function Rt(s, e) { - const t = H(e, { +function Re(s, t) { + const e = H(t, { year: "numeric", month: "2-digit", day: "2-digit", @@ -1531,7 +1531,7 @@ function Rt(s, e) { second: "2-digit", hourCycle: "h23" }).formatToParts(new Date(s)), n = (r) => { - const o = t.find((l) => l.type === r); + const o = e.find((l) => l.type === r); return o ? Number(o.value) : Number.NaN; }, i = Date.UTC( n("year"), @@ -1543,45 +1543,49 @@ function Rt(s, e) { ); return Number.isFinite(i) ? i - s : 0; } -function Ne(s, e) { +function Nt(s, t) { if (typeof s != "string") return null; - const t = s.split("-"); - if (t.length !== 3) + const e = s.split("-"); + if (e.length !== 3) return null; - const [n, i, r] = t.map((a) => Number(a)); + const [n, i, r] = e.map((a) => Number(a)); if (!n || !i || !r) return null; - if (!e) { + if (!t) { const a = new Date(n, i - 1, r); return Number.isNaN(a.getTime()) ? null : a; } - const o = Date.UTC(n, i - 1, r), l = new Date(o - Rt(o, e)); + const o = Date.UTC(n, i - 1, r), l = new Date(o - Re(o, t)); return Number.isNaN(l.getTime()) ? null : l; } -function Lt() { +function Le() { const s = window.location.pathname.split("/").filter(Boolean); return s.length > 0 ? s[s.length - 1].toLowerCase() : ""; } -function Bt(s) { +function Be(s) { if (!s || typeof s != "object") return null; - const e = s, t = k(e.state).toLowerCase(); - if (t !== "open" && t !== "completed") + const t = s, e = k(t.state).toLowerCase(); + if (e !== "open" && e !== "completed") return null; - const n = _(e.id, 0); - return n ? { + const n = y(t.id, 0); + if (!n) + return null; + const i = y(t.child_id, 0); + return { id: n, - title: k(e.title) || "Quest", - icon: k(e.icon) || null, - window: k(e.window).toLowerCase(), - due_time: k(e.due_time) || null, - state: t, - overdue: e.overdue === !0, - completed_at: k(e.completed_at) || null - } : null; + child_id: i > 0 ? i : null, + title: k(t.title) || "Quest", + icon: k(t.icon) || null, + window: k(t.window).toLowerCase(), + due_time: k(t.due_time) || null, + state: e, + overdue: t.overdue === !0, + completed_at: k(t.completed_at) || null + }; } -const Te = "polygon(0 0, 100% 0, 100% 62%, 50% 100%, 0 62%)", Ft = "polygon(50% 0, 93% 25%, 93% 75%, 50% 100%, 7% 75%, 7% 25%)", Me = [-9, 6, -4], Qt = Pe` +const Tt = "polygon(0 0, 100% 0, 100% 62%, 50% 100%, 0 62%)", Fe = "polygon(50% 0, 93% 25%, 93% 75%, 50% 100%, 7% 75%, 7% 25%)", Mt = [-9, 6, -4], Qe = Pt` * { box-sizing: border-box; } @@ -1641,7 +1645,7 @@ const Te = "polygon(0 0, 100% 0, 100% 62%, 50% 100%, 0 62%)", Ft = "polygon(50% width: 96px; height: 110px; padding: 3px; - clip-path: ${v(Te)}; + clip-path: ${v(Tt)}; background: rgba(255, 255, 255, 0.22); } @@ -1652,7 +1656,7 @@ const Te = "polygon(0 0, 100% 0, 100% 62%, 50% 100%, 0 62%)", Ft = "polygon(50% align-items: center; justify-content: center; padding-bottom: 32px; - clip-path: ${v(Te)}; + clip-path: ${v(Tt)}; background: var(--nq-p-crest); } @@ -1718,7 +1722,7 @@ const Te = "polygon(0 0, 100% 0, 100% 62%, 50% 100%, 0 62%)", Ft = "polygon(50% justify-content: center; width: 44px; height: 44px; - clip-path: ${v(Ft)}; + clip-path: ${v(Fe)}; background: var(--nq-brand-gradient); } @@ -2229,7 +2233,42 @@ const Te = "polygon(0 0, 100% 0, 100% 62%, 50% 100%, 0 62%)", Ft = "polygon(50% width: 38px; height: 38px; } -`, R = p` -`, Wt = p``, We = c` -`, Y = p``, Y = c` -`, Zt = p``, Ze = c` -`, Le = p``, Lt = c` -`, Vt = p``, Ve = c` -`, Gt = p``, Ge = c` -`, Kt = p``, Ke = c` -`, Jt = p``, Je = c` -`, Xt = p``, Xe = c` -`, Yt = { - icon: Kt, +`, Ye = { + icon: Ke, headline: "No adventurer chosen", body: "Open The Party and tap your crest to open your quest log." -}, en = { - icon: Jt, +}, tn = { + icon: Je, headline: "NestQuest is not set up yet", body: "A parent needs to finish setting up NestQuest." -}, tn = { - icon: Xt, +}, en = { + icon: Xe, headline: "The records cannot be reached", body: "The party's records are quiet right now. NestQuest will return shortly." -}, Oe = [ - { key: "morning", name: "Morning", range: "Until 11:59 AM", icon: Zt }, - { key: "afternoon", name: "Afternoon", range: "12:00–5:00 PM", icon: Le }, - { key: "evening", name: "Evening", range: "5:00–9:00 PM", icon: Vt } +}, Ot = [ + { key: "morning", name: "Morning", range: "Until 11:59 AM", icon: Ze }, + { key: "afternoon", name: "Afternoon", range: "12:00–5:00 PM", icon: Lt }, + { key: "evening", name: "Evening", range: "5:00–9:00 PM", icon: Ve } ]; class nn extends C { static properties = { hass: { attribute: !1 }, _config: { state: !0 }, _now: { state: !0 }, - _confirm: { state: !0 } + _confirm: { state: !0 }, + _optimistic: { state: !0 }, + _toast: { state: !0 } }; - static styles = [v(Re), Qt]; + static styles = [v(Rt), Qe]; hass; _config; _now = /* @__PURE__ */ new Date(); _confirm = null; + /** instanceId → the completion instant stamped before the service has + * answered; the seal shows immediately and reverts on failure. */ + _optimistic = /* @__PURE__ */ new Map(); + _toast = null; _clockTimer; _idleTimer; _confirmTimer; - setConfig(e) { - if (!e || typeof e != "object") + _toastTimer; + setConfig(t) { + if (!t || typeof t != "object") throw new Error("Invalid configuration"); - this._config = e; + this._config = t; } getCardSize() { return 22; @@ -2398,72 +2444,78 @@ class nn extends C { }, 6e4), window.addEventListener("pointerdown", this._onActivity, !0), window.addEventListener("touchstart", this._onActivity, !0), window.addEventListener("keydown", this._onActivity, !0), this._armIdle(); } disconnectedCallback() { - this._stopClock(), this._clearIdle(), this._clearConfirmTimer(), window.removeEventListener("pointerdown", this._onActivity, !0), window.removeEventListener("touchstart", this._onActivity, !0), window.removeEventListener("keydown", this._onActivity, !0), super.disconnectedCallback(); + this._stopClock(), this._clearIdle(), this._clearConfirmTimer(), this._clearToastTimer(), window.removeEventListener("pointerdown", this._onActivity, !0), window.removeEventListener("touchstart", this._onActivity, !0), window.removeEventListener("keydown", this._onActivity, !0), super.disconnectedCallback(); } render() { - const e = this._logView(); - return p` + const t = this._logView(); + return c`
    - ${this._renderMain(e)} + ${this._renderMain(t)} ${this._renderConfirm()} + ${this._renderToast()}
    `; } - _renderMain(e) { - switch (e.kind) { + _renderToast() { + return this._toast === null ? g : c` +
    ${this._toast}
    + `; + } + _renderMain(t) { + switch (t.kind) { case "no-adventurer": - return this._renderNotice(Yt); + return this._renderNotice(Ye); case "not-set-up": - return this._renderNotice(en); - case "unreachable": return this._renderNotice(tn); + case "unreachable": + return this._renderNotice(en); case "away": { - const t = ["The quest log unlocks when they return."]; - return e.returns && t.push(`Returns ${e.returns}`), p` - ${this._renderHeader(e)} + const e = ["The quest log unlocks when they return."]; + return t.returns && e.push(`Returns ${t.returns}`), c` + ${this._renderHeader(t)}
    - ${Gt} - ${e.name} is on travels - ${t.map((n) => p`

    ${n}

    `)} + ${Ge} + ${t.name} is on travels + ${e.map((n) => c`

    ${n}

    `)}
    `; } case "empty-day": - return p` - ${this._renderHeader(e)} + return c` + ${this._renderHeader(t)}
    - ${Le} + ${Lt} No quests today

    - ${`Nothing was posted for today, ${e.name}. Enjoy the day's rest!`} + ${`Nothing was posted for today, ${t.name}. Enjoy the day's rest!`}

    `; case "complete-day": - return p` - ${this._renderHeader(e)} + return c` + ${this._renderHeader(t)}
    ${R} Quest complete

    - ${`Every quest is claimed, ${e.name}. The seal is set for today.`} + ${`Every quest is claimed, ${t.name}. The seal is set for today.`}

    Returning to The Party shortly.

    `; default: - return p` - ${this._renderHeader(e)} + return c` + ${this._renderHeader(t)}
    - ${Oe.map((t) => this._renderColumn(t))} + ${Ot.map((e) => this._renderColumn(e))}
    `; } @@ -2471,163 +2523,179 @@ class nn extends C { /** Resolve the log's single render state; the priority order is * design/PANEL-EMPTY-STATES.md §2. */ _logView() { - const e = this._childSlug(); - if (!e) + const t = this._childSlug(); + if (!t) return { kind: "no-adventurer" }; - const t = this._dueSensorResolution(e); - if (t === "missing") + const e = this._dueSensorResolution(t); + if (e === "missing") return { kind: "not-set-up" }; - if (t === "stale") + if (e === "stale") return { kind: "unreachable" }; - const n = this._childName(e) ?? X(e); - if (!this._childPresent(e)) - return { kind: "away", name: n, returns: this._awayReturns(e) }; - const i = this._state(`sensor.nestquest_${e}_quests_due_today`); - return $(_(i?.state, 0)) === 0 ? { kind: "empty-day", name: n } : this._remaining() === 0 ? { kind: "complete-day", name: n } : { kind: "normal" }; + const n = this._childName(t) ?? X(t); + if (!this._childPresent(t)) + return { kind: "away", name: n, returns: this._awayReturns(t) }; + const i = this._state(`sensor.nestquest_${t}_quests_due_today`); + return x(y(i?.state, 0)) === 0 ? { kind: "empty-day", name: n } : this._remaining() === 0 ? { kind: "complete-day", name: n } : { kind: "normal" }; } /** "missing" when the due sensor does not exist (integration not * configured), "stale" when it answers unavailable/unknown (backend * unreachable or the child is absent from the snapshot), null when * it resolves. */ - _dueSensorResolution(e) { - const t = this._state(`sensor.nestquest_${e}_quests_due_today`); - if (!t) + _dueSensorResolution(t) { + const e = this._state(`sensor.nestquest_${t}_quests_due_today`); + if (!e) return "missing"; - const n = String(t.state ?? "").trim().toLowerCase(); + const n = String(e.state ?? "").trim().toLowerCase(); return !n || n === "unavailable" || n === "unknown" ? "stale" : null; } - _childPresent(e) { - const n = this._state(`sensor.nestquest_${e}_quests_due_today`)?.attributes?.present; + _childPresent(t) { + const n = this._state(`sensor.nestquest_${t}_quests_due_today`)?.attributes?.present; if (typeof n == "boolean") return n; const i = this._state( - `binary_sensor.nestquest_${e}_present_today` + `binary_sensor.nestquest_${t}_present_today` ); return i ? String(i.state ?? "").trim().toLowerCase() === "on" : !0; } - _awayReturns(e) { - const t = this._timeZone(), n = this._state( - `binary_sensor.nestquest_${e}_present_today` - ), i = Ne( + _awayReturns(t) { + const e = this._timeZone(), n = this._state( + `binary_sensor.nestquest_${t}_present_today` + ), i = Nt( n?.attributes?.next_present, - t + e ); - return i ? Ee(i, t) : null; + return i ? Et(i, e) : null; } - _renderNotice(e) { - return p` + _renderNotice(t) { + return c`
    - ${e.icon} - ${e.headline} -

    ${e.body}

    + ${t.icon} + ${t.headline} +

    ${t.body}

    `; } _timeZone() { - return Ut(this.hass); + return Ue(this.hass); } - _state(e) { - const n = this.hass?.states?.[e]; + _state(t) { + const n = this.hass?.states?.[t]; return n && typeof n == "object" ? n : null; } _childSlug() { - return Lt(); + return Le(); } _boardPath() { return k(this._config?.board_path).replace(/\/+$/, ""); } _idleSeconds() { - const e = _(this._config?.idle_return_seconds, 40); - return e > 0 ? e : 40; + const t = y(this._config?.idle_return_seconds, 40); + return t > 0 ? t : 40; } _confirmSeconds() { - const e = _(this._config?.confirm_timeout_seconds, 15); - return e > 0 ? e : 15; + const t = y(this._config?.confirm_timeout_seconds, 15); + return t > 0 ? t : 15; } - _childName(e) { - if (!e) + _childName(t) { + if (!t) return null; - const t = this._state(`sensor.nestquest_${e}_quests_due_today`)?.attributes?.child_name; - return typeof t == "string" && t.trim() ? t.trim() : null; + const e = this._state(`sensor.nestquest_${t}_quests_due_today`)?.attributes?.child_name; + return typeof e == "string" && e.trim() ? e.trim() : null; } - _childCounts(e) { - const t = this._state(`sensor.nestquest_${e}_quests_due_today`), n = this._state( - `sensor.nestquest_${e}_quests_completed_today` - ), i = $(_(t?.state, 0)), r = $(_(n?.state, 0)); + _childCounts(t) { + const e = this._state(`sensor.nestquest_${t}_quests_due_today`), n = this._state( + `sensor.nestquest_${t}_quests_completed_today` + ), i = x(y(e?.state, 0)), r = x(y(n?.state, 0)); return { due: i, completed: r }; } _instances() { - const e = this._childSlug(); - if (!e) + const t = this._childSlug(); + if (!t) return []; - const t = this._state(`sensor.nestquest_${e}_quests_due_today`)?.attributes?.instances; - return Array.isArray(t) ? t.map(Bt).filter((n) => n !== null) : []; + const e = this._state(`sensor.nestquest_${t}_quests_due_today`)?.attributes?.instances; + if (!Array.isArray(e)) + return []; + const n = e.map(Be).filter((r) => r !== null); + if (this._optimistic.size === 0) + return n; + let i = !1; + for (const r of n) { + const o = this._optimistic.get(r.id); + if (o !== void 0) { + if (r.state === "completed") { + this._optimistic.delete(r.id), i = !0; + continue; + } + r.state = "completed", r.overdue = !1, r.completed_at = o; + } + } + return i && (this._optimistic = new Map(this._optimistic)), n; } _remaining() { - const e = this._childSlug(); - if (!e) + const t = this._childSlug(); + if (!t) return 0; - const t = this._state( - `sensor.nestquest_${e}_quests_remaining_today` + const e = this._state( + `sensor.nestquest_${t}_quests_remaining_today` ); - if (t) - return $(_(t.state, 0)); - const { due: n, completed: i } = this._childCounts(e); - return $(n - i); + if (e) + return x(y(e.state, 0)); + const { due: n, completed: i } = this._childCounts(t); + return x(n - i); } _partyCounts() { - const e = this._state( + const t = this._state( "sensor.nestquest_household_quests_due_today" - ), t = this._state( + ), e = this._state( "sensor.nestquest_household_quests_completed_today" ); - if (e || t) + if (t || e) return { - completed: $(_(t?.state, 0)), - due: $(_(e?.state, 0)) + completed: x(y(e?.state, 0)), + due: x(y(t?.state, 0)) }; const n = this._childSlug(), { due: i, completed: r } = this._childCounts(n); return { completed: r, due: i }; } _otherChildren() { - const e = this.hass?.states; - if (!e) + const t = this.hass?.states; + if (!t) return []; - const t = this._childSlug(), n = this._timeZone(), i = []; - for (const r of Object.keys(e)) { + const e = this._childSlug(), n = this._timeZone(), i = []; + for (const r of Object.keys(t)) { const o = /^sensor\.nestquest_(.+)_quests_due_today$/.exec(r); - if (!o || o[1] === "household" || o[1] === t) + if (!o || o[1] === "household" || o[1] === e) continue; - const l = o[1], a = e[r]?.attributes ?? {}; - let c = ""; - typeof a.child_name == "string" && a.child_name.trim() ? c = a.child_name.trim() : c = X(l); + const l = o[1], a = t[r]?.attributes ?? {}; + let p = ""; + typeof a.child_name == "string" && a.child_name.trim() ? p = a.child_name.trim() : p = X(l); let u = !0; if (typeof a.present == "boolean") u = a.present; else { - const y = this._state( + const _ = this._state( `binary_sensor.nestquest_${l}_present_today` ); - u = y === null || String(y.state ?? "").trim().toLowerCase() !== "off"; + u = _ === null || String(_.state ?? "").trim().toLowerCase() !== "off"; } const d = this._state( `sensor.nestquest_${l}_quests_completed_today` - ), f = $(_(e[r]?.state, 0)), h = $(_(d?.state, 0)); + ), f = x(y(t[r]?.state, 0)), h = x(y(d?.state, 0)); let m = null; if (!u) { - const y = this._state( + const _ = this._state( `binary_sensor.nestquest_${l}_present_today` - ), b = Ne( - y?.attributes?.next_present, + ), b = Nt( + _?.attributes?.next_present, n ); - m = b ? Dt(b, n) : null; + m = b ? De(b, n) : null; } i.push({ slug: l, - name: c, + name: p, present: u, due: f, completed: h, @@ -2636,9 +2704,9 @@ class nn extends C { } return i.sort((r, o) => r.name.localeCompare(o.name)); } - _renderHeader(e) { - const t = this._childSlug(), n = e.kind === "away", i = n ? e.name : this._childName(t) ?? (t ? X(t) : null), { due: r, completed: o } = this._childCounts(t), l = this._remaining(), a = this._partyCounts(), c = i ? `${i}'s Quest Log` : "Quest Log", u = (i?.charAt(0) || "?").toUpperCase(); - return p` + _renderHeader(t) { + const e = this._childSlug(), n = t.kind === "away", i = n ? t.name : this._childName(e) ?? (e ? X(e) : null), { due: r, completed: o } = this._childCounts(e), l = this._remaining(), a = this._partyCounts(), p = i ? `${i}'s Quest Log` : "Quest Log", u = (i?.charAt(0) || "?").toUpperCase(); + return c`
    -

    ${c}

    +

    ${p}

    - ${Ee(this._now, this._timeZone())} · + ${Et(this._now, this._timeZone())} · ${n ? "On travels" : `${o} of ${r} claimed`}

    - ${n ? g : p` + ${n ? g : c`
    `; } - _renderColumn(e) { - const t = this._instances().filter( - (l) => l.window === e.key - ), n = t.filter((l) => l.state === "open").sort( + _renderColumn(t) { + const e = this._instances().filter( + (l) => l.window === t.key + ), n = e.filter((l) => l.state === "open").sort( (l, a) => (l.due_time ?? "99:99").localeCompare(a.due_time ?? "99:99") - ), i = t.filter( + ), i = e.filter( (l) => l.state === "completed" - ), r = t.length; + ), r = e.length; let o = g; if (r > 0) { - const a = 644 - (e.key === "afternoon" && this._otherChildren().length > 0 ? 160 : 0), c = Math.min( + const a = 644 - (t.key === "afternoon" && this._otherChildren().length > 0 ? 160 : 0), p = Math.min( 24, Math.max(8, Math.floor((a - r * 74) / Math.max(r - 1, 1))) ), u = Math.min( 116, - Math.max(58, Math.floor((a - (r - 1) * c) / r)) + Math.max(58, Math.floor((a - (r - 1) * p) / r)) ), d = Math.max(56, Math.min(72, u - 2)); - let f = `--nq-p-quest-gap: ${c}px; --nq-p-quest-min-height: ${u}px; --nq-p-button-height: ${d}px`; + let f = `--nq-p-quest-gap: ${p}px; --nq-p-quest-min-height: ${u}px; --nq-p-button-height: ${d}px`; if (r >= 5) { const h = Math.max(0, Math.min(22, Math.floor((u - d - 2) / 2))), m = Math.max(44, Math.min(64, u - 2 * h - 2)); f += `; --nq-p-quest-pad: ${h}px; --nq-p-tile-size: ${m}px`; } o = f; } - return p` + return c`
    - ${e.icon} + ${t.icon}
    - ${e.name} - ${e.range} + ${t.name} + ${t.range}
    - ${i.length}/${t.length} + ${i.length}/${e.length}
    - ${Ce( + ${Ct( n, (l) => l.id, (l) => this._renderQuest(l, !1, 0) )} - ${Ce( + ${Ct( i, (l) => l.id, (l, a) => this._renderQuest(l, !0, a) )} - ${e.key === "afternoon" ? this._renderRollup() : g} + ${t.key === "afternoon" ? this._renderRollup() : g}
    `; } - _renderQuest(e, t, n) { - if (t) { - const o = Me[n % Me.length], l = e.completed_at ? Ht(e.completed_at, this._timeZone()) : ""; - return p` -
    + _renderQuest(t, e, n) { + if (e) { + const o = Mt[n % Mt.length], l = t.completed_at ? He(t.completed_at, this._timeZone()) : ""; + return c` +
    - ${e.title} + ${t.title} ${l ? `Claimed ${l}` : "Claimed"} @@ -2746,34 +2814,34 @@ class nn extends C {
    `; } - const i = jt(e.due_time), r = e.overdue ? { + const i = je(t.due_time), r = t.overdue ? { text: i ? `Overdue · due ${i}` : "Overdue", late: !0 } : { text: i ? `Due by ${i}` : "Due today", late: !1 }; - return p` + return c`
    this._openConfirm(e.id)} + data-instance-id=${t.id} + aria-label="Complete ${t.title}" + @click=${() => this._openConfirm(t.id)} @keydown=${(o) => { - (o.key === "Enter" || o.key === " ") && (o.preventDefault(), this._openConfirm(e.id)); + (o.key === "Enter" || o.key === " ") && (o.preventDefault(), this._openConfirm(t.id)); }} >
    - ${e.title} + ${t.title} ${r.text}
    `; } + private _renderToast(): TemplateResult | typeof nothing { + if (this._toast === null) { + return nothing; + } + return html` +
    ${this._toast}
    + `; + } + private _renderMain(view: LogView): TemplateResult { switch (view.kind) { case "no-adventurer": @@ -1318,9 +1374,32 @@ export class NestQuestQuestLogCard extends LitElement { if (!Array.isArray(payload)) { return []; } - return payload + const instances = payload .map(toPanelInstance) .filter((instance): instance is PanelInstance => instance !== null); + if (this._optimistic.size === 0) { + return instances; + } + let settled = false; + for (const instance of instances) { + const stampedAt = this._optimistic.get(instance.id); + if (stampedAt === undefined) { + continue; + } + if (instance.state === "completed") { + // the backend has caught up; the real record replaces the overlay + this._optimistic.delete(instance.id); + settled = true; + continue; + } + instance.state = "completed"; + instance.overdue = false; + instance.completed_at = stampedAt; + } + if (settled) { + this._optimistic = new Map(this._optimistic); + } + return instances; } private _remaining(): number { @@ -1698,7 +1777,94 @@ export class NestQuestQuestLogCard extends LitElement { } private _confirmComplete(): void { + const instance = + this._confirm === null + ? undefined + : this._instances().find( + (candidate) => candidate.id === this._confirm + ); this._closeConfirm(); + if (!instance || instance.state !== "open") { + return; + } + this._completeQuest(instance); + } + + /** Seal first, ask the service second: the seal is stamped and the column + * re-sorted immediately (PANEL-SPEC §4); a service failure reverts the + * card and raises the parchment toast. */ + private async _completeQuest(instance: PanelInstance): Promise { + if (this._optimistic.has(instance.id)) { + return; + } + const stampedAt = new Date().toISOString(); + this._optimistic = new Map(this._optimistic).set(instance.id, stampedAt); + try { + await this._callCompleteQuest(instance); + } catch { + const reverted = new Map(this._optimistic); + reverted.delete(instance.id); + this._optimistic = reverted; + this._showToast( + "NestQuest could not set the seal just now. Please try again." + ); + } + } + + private async _callCompleteQuest(instance: PanelInstance): Promise { + const hass = this.hass as + | { + callService?: ( + this: unknown, + domain: string, + service: string, + data?: Record + ) => Promise; + } + | undefined; + if (typeof hass?.callService !== "function") { + throw new Error("Home Assistant is not connected"); + } + const actorChildId = this._actorChildId(instance); + if (actorChildId === null) { + throw new Error("The tapped adventurer is unknown"); + } + await hass.callService.call(hass, "nestquest", "complete_quest", { + instance_id: instance.id, + actor: "panel", + actor_child_id: actorChildId, + }); + } + + /** The tapped profile (decision 9): the instance's own child_id, falling + * back to the due sensor's child-level attribute. */ + private _actorChildId(instance: PanelInstance): number | null { + if (instance.child_id !== null) { + return instance.child_id; + } + const slug = this._childSlug(); + const fromSensor = asNumber( + this._state(`sensor.nestquest_${slug}_quests_due_today`)?.attributes + ?.child_id, + 0 + ); + return fromSensor > 0 ? fromSensor : null; + } + + private _showToast(message: string): void { + this._toast = message; + this._clearToastTimer(); + this._toastTimer = window.setTimeout(() => { + this._toastTimer = undefined; + this._toast = null; + }, 6000); + } + + private _clearToastTimer(): void { + if (this._toastTimer !== undefined) { + window.clearTimeout(this._toastTimer); + this._toastTimer = undefined; + } } private _armConfirmTimer(): void { From a2c027deca1c4de528e1a7c3915231456ed08437 Mon Sep 17 00:00:00 2001 From: dev_agent <17+dev_agent@code.cubecraftcreations.com> Date: Sun, 20 Sep 2026 18:39:11 -0400 Subject: [PATCH 07/11] Add the quest complete screen (1c) Add the quest complete screen (1c) --- .../nestquest/www/nestquest-cards.js | 1181 +++++++++++------ design/ENTITIES-AND-SERVICES.md | 1 + frontend/src/cards/quest-log.ts | 463 ++++++- 3 files changed, 1214 insertions(+), 431 deletions(-) diff --git a/custom_components/nestquest/www/nestquest-cards.js b/custom_components/nestquest/www/nestquest-cards.js index e60d13e..a6aa494 100644 --- a/custom_components/nestquest/www/nestquest-cards.js +++ b/custom_components/nestquest/www/nestquest-cards.js @@ -1,15 +1,15 @@ -const L = globalThis, et = L.ShadowRoot && (L.ShadyCSS === void 0 || L.ShadyCSS.nativeShadow) && "adoptedStyleSheets" in Document.prototype && "replace" in CSSStyleSheet.prototype, nt = /* @__PURE__ */ Symbol(), at = /* @__PURE__ */ new WeakMap(); +const B = globalThis, nt = B.ShadowRoot && (B.ShadyCSS === void 0 || B.ShadyCSS.nativeShadow) && "adoptedStyleSheets" in Document.prototype && "replace" in CSSStyleSheet.prototype, it = /* @__PURE__ */ Symbol(), lt = /* @__PURE__ */ new WeakMap(); let zt = class { constructor(t, e, n) { - if (this._$cssResult$ = !0, n !== nt) throw Error("CSSResult is not constructable. Use `unsafeCSS` or `css` instead."); + if (this._$cssResult$ = !0, n !== it) throw Error("CSSResult is not constructable. Use `unsafeCSS` or `css` instead."); this.cssText = t, this.t = e; } get styleSheet() { let t = this.o; const e = this.t; - if (et && t === void 0) { + if (nt && t === void 0) { const n = e !== void 0 && e.length === 1; - n && (t = at.get(e)), t === void 0 && ((this.o = t = new CSSStyleSheet()).replaceSync(this.cssText), n && at.set(e, t)); + n && (t = lt.get(e)), t === void 0 && ((this.o = t = new CSSStyleSheet()).replaceSync(this.cssText), n && lt.set(e, t)); } return t; } @@ -17,101 +17,101 @@ let zt = class { return this.cssText; } }; -const v = (s) => new zt(typeof s == "string" ? s : s + "", void 0, nt), Pt = (s, ...t) => { - const e = s.length === 1 ? s[0] : t.reduce((n, i, r) => n + ((o) => { +const b = (i) => new zt(typeof i == "string" ? i : i + "", void 0, it), Pt = (i, ...t) => { + const e = i.length === 1 ? i[0] : t.reduce((n, s, r) => n + ((o) => { if (o._$cssResult$ === !0) return o.cssText; if (typeof o == "number") return o; throw Error("Value passed to 'css' function must be a 'css' function result: " + o + ". Use 'unsafeCSS' to pass non-literal values, but take care to ensure page security."); - })(i) + s[r + 1], s[0]); - return new zt(e, s, nt); -}, Bt = (s, t) => { - if (et) s.adoptedStyleSheets = t.map((e) => e instanceof CSSStyleSheet ? e : e.styleSheet); + })(s) + i[r + 1], i[0]); + return new zt(e, i, it); +}, Bt = (i, t) => { + if (nt) i.adoptedStyleSheets = t.map((e) => e instanceof CSSStyleSheet ? e : e.styleSheet); else for (const e of t) { - const n = document.createElement("style"), i = L.litNonce; - i !== void 0 && n.setAttribute("nonce", i), n.textContent = e.cssText, s.appendChild(n); + const n = document.createElement("style"), s = B.litNonce; + s !== void 0 && n.setAttribute("nonce", s), n.textContent = e.cssText, i.appendChild(n); } -}, lt = et ? (s) => s : (s) => s instanceof CSSStyleSheet ? ((t) => { +}, dt = nt ? (i) => i : (i) => i instanceof CSSStyleSheet ? ((t) => { let e = ""; for (const n of t.cssRules) e += n.cssText; - return v(e); -})(s) : s; -const { is: Ft, defineProperty: Qt, getOwnPropertyDescriptor: Wt, getOwnPropertyNames: Zt, getOwnPropertySymbols: Vt, getPrototypeOf: Gt } = Object, Q = globalThis, dt = Q.trustedTypes, Kt = dt ? dt.emptyScript : "", Jt = Q.reactiveElementPolyfillSupport, P = (s, t) => s, tt = { toAttribute(s, t) { + return b(e); +})(i) : i; +const { is: Ft, defineProperty: Qt, getOwnPropertyDescriptor: Wt, getOwnPropertyNames: Zt, getOwnPropertySymbols: Vt, getPrototypeOf: Gt } = Object, W = globalThis, ct = W.trustedTypes, Kt = ct ? ct.emptyScript : "", Jt = W.reactiveElementPolyfillSupport, I = (i, t) => i, et = { toAttribute(i, t) { switch (t) { case Boolean: - s = s ? Kt : null; + i = i ? Kt : null; break; case Object: case Array: - s = s == null ? s : JSON.stringify(s); + i = i == null ? i : JSON.stringify(i); } - return s; -}, fromAttribute(s, t) { - let e = s; + return i; +}, fromAttribute(i, t) { + let e = i; switch (t) { case Boolean: - e = s !== null; + e = i !== null; break; case Number: - e = s === null ? null : Number(s); + e = i === null ? null : Number(i); break; case Object: case Array: try { - e = JSON.parse(s); + e = JSON.parse(i); } catch { e = null; } } return e; -} }, It = (s, t) => !Ft(s, t), ct = { attribute: !0, type: String, converter: tt, reflect: !1, useDefault: !1, hasChanged: It }; -Symbol.metadata ??= /* @__PURE__ */ Symbol("metadata"), Q.litPropertyMetadata ??= /* @__PURE__ */ new WeakMap(); -let N = class extends HTMLElement { +} }, It = (i, t) => !Ft(i, t), pt = { attribute: !0, type: String, converter: et, reflect: !1, useDefault: !1, hasChanged: It }; +Symbol.metadata ??= /* @__PURE__ */ Symbol("metadata"), W.litPropertyMetadata ??= /* @__PURE__ */ new WeakMap(); +let T = class extends HTMLElement { static addInitializer(t) { this._$Ei(), (this.l ??= []).push(t); } static get observedAttributes() { return this.finalize(), this._$Eh && [...this._$Eh.keys()]; } - static createProperty(t, e = ct) { + static createProperty(t, e = pt) { if (e.state && (e.attribute = !1), this._$Ei(), this.prototype.hasOwnProperty(t) && ((e = Object.create(e)).wrapped = !0), this.elementProperties.set(t, e), !e.noAccessor) { - const n = /* @__PURE__ */ Symbol(), i = this.getPropertyDescriptor(t, n, e); - i !== void 0 && Qt(this.prototype, t, i); + const n = /* @__PURE__ */ Symbol(), s = this.getPropertyDescriptor(t, n, e); + s !== void 0 && Qt(this.prototype, t, s); } } static getPropertyDescriptor(t, e, n) { - const { get: i, set: r } = Wt(this.prototype, t) ?? { get() { + const { get: s, set: r } = Wt(this.prototype, t) ?? { get() { return this[e]; }, set(o) { this[e] = o; } }; - return { get: i, set(o) { - const l = i?.call(this); + return { get: s, set(o) { + const l = s?.call(this); r?.call(this, o), this.requestUpdate(t, l, n); }, configurable: !0, enumerable: !0 }; } static getPropertyOptions(t) { - return this.elementProperties.get(t) ?? ct; + return this.elementProperties.get(t) ?? pt; } static _$Ei() { - if (this.hasOwnProperty(P("elementProperties"))) return; + if (this.hasOwnProperty(I("elementProperties"))) return; const t = Gt(this); t.finalize(), t.l !== void 0 && (this.l = [...t.l]), this.elementProperties = new Map(t.elementProperties); } static finalize() { - if (this.hasOwnProperty(P("finalized"))) return; - if (this.finalized = !0, this._$Ei(), this.hasOwnProperty(P("properties"))) { + if (this.hasOwnProperty(I("finalized"))) return; + if (this.finalized = !0, this._$Ei(), this.hasOwnProperty(I("properties"))) { const e = this.properties, n = [...Zt(e), ...Vt(e)]; - for (const i of n) this.createProperty(i, e[i]); + for (const s of n) this.createProperty(s, e[s]); } const t = this[Symbol.metadata]; if (t !== null) { const e = litPropertyMetadata.get(t); - if (e !== void 0) for (const [n, i] of e) this.elementProperties.set(n, i); + if (e !== void 0) for (const [n, s] of e) this.elementProperties.set(n, s); } this._$Eh = /* @__PURE__ */ new Map(); for (const [e, n] of this.elementProperties) { - const i = this._$Eu(e, n); - i !== void 0 && this._$Eh.set(i, e); + const s = this._$Eu(e, n); + s !== void 0 && this._$Eh.set(s, e); } this.elementStyles = this.finalizeStyles(this.styles); } @@ -119,8 +119,8 @@ let N = class extends HTMLElement { const e = []; if (Array.isArray(t)) { const n = new Set(t.flat(1 / 0).reverse()); - for (const i of n) e.unshift(lt(i)); - } else t !== void 0 && e.push(lt(t)); + for (const s of n) e.unshift(dt(s)); + } else t !== void 0 && e.push(dt(t)); return e; } static _$Eu(t, e) { @@ -160,31 +160,31 @@ let N = class extends HTMLElement { this._$AK(t, n); } _$ET(t, e) { - const n = this.constructor.elementProperties.get(t), i = this.constructor._$Eu(t, n); - if (i !== void 0 && n.reflect === !0) { - const r = (n.converter?.toAttribute !== void 0 ? n.converter : tt).toAttribute(e, n.type); - this._$Em = t, r == null ? this.removeAttribute(i) : this.setAttribute(i, r), this._$Em = null; + const n = this.constructor.elementProperties.get(t), s = this.constructor._$Eu(t, n); + if (s !== void 0 && n.reflect === !0) { + const r = (n.converter?.toAttribute !== void 0 ? n.converter : et).toAttribute(e, n.type); + this._$Em = t, r == null ? this.removeAttribute(s) : this.setAttribute(s, r), this._$Em = null; } } _$AK(t, e) { - const n = this.constructor, i = n._$Eh.get(t); - if (i !== void 0 && this._$Em !== i) { - const r = n.getPropertyOptions(i), o = typeof r.converter == "function" ? { fromAttribute: r.converter } : r.converter?.fromAttribute !== void 0 ? r.converter : tt; - this._$Em = i; + const n = this.constructor, s = n._$Eh.get(t); + if (s !== void 0 && this._$Em !== s) { + const r = n.getPropertyOptions(s), o = typeof r.converter == "function" ? { fromAttribute: r.converter } : r.converter?.fromAttribute !== void 0 ? r.converter : et; + this._$Em = s; const l = o.fromAttribute(e, r.type); - this[i] = l ?? this._$Ej?.get(i) ?? l, this._$Em = null; + this[s] = l ?? this._$Ej?.get(s) ?? l, this._$Em = null; } } - requestUpdate(t, e, n, i = !1, r) { + requestUpdate(t, e, n, s = !1, r) { if (t !== void 0) { const o = this.constructor; - if (i === !1 && (r = this[t]), n ??= o.getPropertyOptions(t), !((n.hasChanged ?? It)(r, e) || n.useDefault && n.reflect && r === this._$Ej?.get(t) && !this.hasAttribute(o._$Eu(t, n)))) return; + if (s === !1 && (r = this[t]), n ??= o.getPropertyOptions(t), !((n.hasChanged ?? It)(r, e) || n.useDefault && n.reflect && r === this._$Ej?.get(t) && !this.hasAttribute(o._$Eu(t, n)))) return; this.C(t, e, n); } this.isUpdatePending === !1 && (this._$ES = this._$EP()); } - C(t, e, { useDefault: n, reflect: i, wrapped: r }, o) { - n && !(this._$Ej ??= /* @__PURE__ */ new Map()).has(t) && (this._$Ej.set(t, o ?? e ?? this[t]), r !== !0 || o !== void 0) || (this._$AL.has(t) || (this.hasUpdated || n || (e = void 0), this._$AL.set(t, e)), i === !0 && this._$Em !== t && (this._$Eq ??= /* @__PURE__ */ new Set()).add(t)); + C(t, e, { useDefault: n, reflect: s, wrapped: r }, o) { + n && !(this._$Ej ??= /* @__PURE__ */ new Map()).has(t) && (this._$Ej.set(t, o ?? e ?? this[t]), r !== !0 || o !== void 0) || (this._$AL.has(t) || (this.hasUpdated || n || (e = void 0), this._$AL.set(t, e)), s === !0 && this._$Em !== t && (this._$Eq ??= /* @__PURE__ */ new Set()).add(t)); } async _$EP() { this.isUpdatePending = !0; @@ -203,13 +203,13 @@ let N = class extends HTMLElement { if (!this.isUpdatePending) return; if (!this.hasUpdated) { if (this.renderRoot ??= this.createRenderRoot(), this._$Ep) { - for (const [i, r] of this._$Ep) this[i] = r; + for (const [s, r] of this._$Ep) this[s] = r; this._$Ep = void 0; } const n = this.constructor.elementProperties; - if (n.size > 0) for (const [i, r] of n) { - const { wrapped: o } = r, l = this[i]; - o !== !0 || this._$AL.has(i) || l === void 0 || this.C(i, void 0, r, l); + if (n.size > 0) for (const [s, r] of n) { + const { wrapped: o } = r, l = this[s]; + o !== !0 || this._$AL.has(s) || l === void 0 || this.C(s, void 0, r, l); } } let t = !1; @@ -246,54 +246,54 @@ let N = class extends HTMLElement { firstUpdated(t) { } }; -N.elementStyles = [], N.shadowRootOptions = { mode: "open" }, N[P("elementProperties")] = /* @__PURE__ */ new Map(), N[P("finalized")] = /* @__PURE__ */ new Map(), Jt?.({ ReactiveElement: N }), (Q.reactiveElementVersions ??= []).push("2.1.2"); -const st = globalThis, pt = (s) => s, B = st.trustedTypes, ht = B ? B.createPolicy("lit-html", { createHTML: (s) => s }) : void 0, Ut = "$lit$", $ = `lit$${Math.random().toFixed(9).slice(2)}$`, Dt = "?" + $, Xt = `<${Dt}>`, S = document, I = () => S.createComment(""), U = (s) => s === null || typeof s != "object" && typeof s != "function", it = Array.isArray, Yt = (s) => it(s) || typeof s?.[Symbol.iterator] == "function", V = `[ -\f\r]`, O = /<(?:(!--|\/[^a-zA-Z])|(\/?[a-zA-Z][^>\s]*)|(\/?$))/g, ut = /-->/g, ft = />/g, w = RegExp(`>|${V}(?:([^\\s"'>=/]+)(${V}*=${V}*(?:[^ -\f\r"'\`<>=]|("|')|))|$)`, "g"), mt = /'/g, gt = /"/g, Ht = /^(?:script|style|textarea|title)$/i, te = (s) => (t, ...e) => ({ _$litType$: s, strings: t, values: e }), c = te(1), E = /* @__PURE__ */ Symbol.for("lit-noChange"), g = /* @__PURE__ */ Symbol.for("lit-nothing"), _t = /* @__PURE__ */ new WeakMap(), A = S.createTreeWalker(S, 129); -function jt(s, t) { - if (!it(s) || !s.hasOwnProperty("raw")) throw Error("invalid template strings array"); - return ht !== void 0 ? ht.createHTML(t) : t; +T.elementStyles = [], T.shadowRootOptions = { mode: "open" }, T[I("elementProperties")] = /* @__PURE__ */ new Map(), T[I("finalized")] = /* @__PURE__ */ new Map(), Jt?.({ ReactiveElement: T }), (W.reactiveElementVersions ??= []).push("2.1.2"); +const st = globalThis, ht = (i) => i, F = st.trustedTypes, ut = F ? F.createPolicy("lit-html", { createHTML: (i) => i }) : void 0, Dt = "$lit$", w = `lit$${Math.random().toFixed(9).slice(2)}$`, Ut = "?" + w, Xt = `<${Ut}>`, S = document, D = () => S.createComment(""), U = (i) => i === null || typeof i != "object" && typeof i != "function", rt = Array.isArray, Yt = (i) => rt(i) || typeof i?.[Symbol.iterator] == "function", G = `[ +\f\r]`, z = /<(?:(!--|\/[^a-zA-Z])|(\/?[a-zA-Z][^>\s]*)|(\/?$))/g, ft = /-->/g, mt = />/g, q = RegExp(`>|${G}(?:([^\\s"'>=/]+)(${G}*=${G}*(?:[^ +\f\r"'\`<>=]|("|')|))|$)`, "g"), gt = /'/g, _t = /"/g, jt = /^(?:script|style|textarea|title)$/i, te = (i) => (t, ...e) => ({ _$litType$: i, strings: t, values: e }), c = te(1), N = /* @__PURE__ */ Symbol.for("lit-noChange"), g = /* @__PURE__ */ Symbol.for("lit-nothing"), yt = /* @__PURE__ */ new WeakMap(), C = S.createTreeWalker(S, 129); +function Ht(i, t) { + if (!rt(i) || !i.hasOwnProperty("raw")) throw Error("invalid template strings array"); + return ut !== void 0 ? ut.createHTML(t) : t; } -const ee = (s, t) => { - const e = s.length - 1, n = []; - let i, r = t === 2 ? "" : t === 3 ? "" : "", o = O; +const ee = (i, t) => { + const e = i.length - 1, n = []; + let s, r = t === 2 ? "" : t === 3 ? "" : "", o = z; for (let l = 0; l < e; l++) { - const a = s[l]; + const a = i[l]; let p, u, d = -1, f = 0; - for (; f < a.length && (o.lastIndex = f, u = o.exec(a), u !== null); ) f = o.lastIndex, o === O ? u[1] === "!--" ? o = ut : u[1] !== void 0 ? o = ft : u[2] !== void 0 ? (Ht.test(u[2]) && (i = RegExp("" ? (o = i ?? O, d = -1) : u[1] === void 0 ? d = -2 : (d = o.lastIndex - u[2].length, p = u[1], o = u[3] === void 0 ? w : u[3] === '"' ? gt : mt) : o === gt || o === mt ? o = w : o === ut || o === ft ? o = O : (o = w, i = void 0); - const h = o === w && s[l + 1].startsWith("/>") ? " " : ""; - r += o === O ? a + Xt : d >= 0 ? (n.push(p), a.slice(0, d) + Ut + a.slice(d) + $ + h) : a + $ + (d === -2 ? l : h); + for (; f < a.length && (o.lastIndex = f, u = o.exec(a), u !== null); ) f = o.lastIndex, o === z ? u[1] === "!--" ? o = ft : u[1] !== void 0 ? o = mt : u[2] !== void 0 ? (jt.test(u[2]) && (s = RegExp("" ? (o = s ?? z, d = -1) : u[1] === void 0 ? d = -2 : (d = o.lastIndex - u[2].length, p = u[1], o = u[3] === void 0 ? q : u[3] === '"' ? _t : gt) : o === _t || o === gt ? o = q : o === ft || o === mt ? o = z : (o = q, s = void 0); + const h = o === q && i[l + 1].startsWith("/>") ? " " : ""; + r += o === z ? a + Xt : d >= 0 ? (n.push(p), a.slice(0, d) + Dt + a.slice(d) + w + h) : a + w + (d === -2 ? l : h); } - return [jt(s, r + (s[e] || "") + (t === 2 ? "" : t === 3 ? "" : "")), n]; + return [Ht(i, r + (i[e] || "") + (t === 2 ? "" : t === 3 ? "" : "")), n]; }; -class D { +class j { constructor({ strings: t, _$litType$: e }, n) { - let i; + let s; this.parts = []; let r = 0, o = 0; const l = t.length - 1, a = this.parts, [p, u] = ee(t, e); - if (this.el = D.createElement(p, n), A.currentNode = this.el.content, e === 2 || e === 3) { + if (this.el = j.createElement(p, n), C.currentNode = this.el.content, e === 2 || e === 3) { const d = this.el.content.firstChild; d.replaceWith(...d.childNodes); } - for (; (i = A.nextNode()) !== null && a.length < l; ) { - if (i.nodeType === 1) { - if (i.hasAttributes()) for (const d of i.getAttributeNames()) if (d.endsWith(Ut)) { - const f = u[o++], h = i.getAttribute(d).split($), m = /([.?@])?(.*)/.exec(f); - a.push({ type: 1, index: r, name: m[2], strings: h, ctor: m[1] === "." ? se : m[1] === "?" ? ie : m[1] === "@" ? re : W }), i.removeAttribute(d); - } else d.startsWith($) && (a.push({ type: 6, index: r }), i.removeAttribute(d)); - if (Ht.test(i.tagName)) { - const d = i.textContent.split($), f = d.length - 1; + for (; (s = C.nextNode()) !== null && a.length < l; ) { + if (s.nodeType === 1) { + if (s.hasAttributes()) for (const d of s.getAttributeNames()) if (d.endsWith(Dt)) { + const f = u[o++], h = s.getAttribute(d).split(w), m = /([.?@])?(.*)/.exec(f); + a.push({ type: 1, index: r, name: m[2], strings: h, ctor: m[1] === "." ? ie : m[1] === "?" ? se : m[1] === "@" ? re : Z }), s.removeAttribute(d); + } else d.startsWith(w) && (a.push({ type: 6, index: r }), s.removeAttribute(d)); + if (jt.test(s.tagName)) { + const d = s.textContent.split(w), f = d.length - 1; if (f > 0) { - i.textContent = B ? B.emptyScript : ""; - for (let h = 0; h < f; h++) i.append(d[h], I()), A.nextNode(), a.push({ type: 2, index: ++r }); - i.append(d[f], I()); + s.textContent = F ? F.emptyScript : ""; + for (let h = 0; h < f; h++) s.append(d[h], D()), C.nextNode(), a.push({ type: 2, index: ++r }); + s.append(d[f], D()); } } - } else if (i.nodeType === 8) if (i.data === Dt) a.push({ type: 2, index: r }); + } else if (s.nodeType === 8) if (s.data === Ut) a.push({ type: 2, index: r }); else { let d = -1; - for (; (d = i.data.indexOf($, d + 1)) !== -1; ) a.push({ type: 7, index: r }), d += $.length - 1; + for (; (d = s.data.indexOf(w, d + 1)) !== -1; ) a.push({ type: 7, index: r }), d += w.length - 1; } r++; } @@ -303,11 +303,11 @@ class D { return n.innerHTML = t, n; } } -function T(s, t, e = s, n) { - if (t === E) return t; - let i = n !== void 0 ? e._$Co?.[n] : e._$Cl; +function E(i, t, e = i, n) { + if (t === N) return t; + let s = n !== void 0 ? e._$Co?.[n] : e._$Cl; const r = U(t) ? void 0 : t._$litDirective$; - return i?.constructor !== r && (i?._$AO?.(!1), r === void 0 ? i = void 0 : (i = new r(s), i._$AT(s, e, n)), n !== void 0 ? (e._$Co ??= [])[n] = i : e._$Cl = i), i !== void 0 && (t = T(s, i._$AS(s, t.values), i, n)), t; + return s?.constructor !== r && (s?._$AO?.(!1), r === void 0 ? s = void 0 : (s = new r(i), s._$AT(i, e, n)), n !== void 0 ? (e._$Co ??= [])[n] = s : e._$Cl = s), s !== void 0 && (t = E(i, s._$AS(i, t.values), s, n)), t; } class ne { constructor(t, e) { @@ -320,29 +320,29 @@ class ne { return this._$AM._$AU; } u(t) { - const { el: { content: e }, parts: n } = this._$AD, i = (t?.creationScope ?? S).importNode(e, !0); - A.currentNode = i; - let r = A.nextNode(), o = 0, l = 0, a = n[0]; + const { el: { content: e }, parts: n } = this._$AD, s = (t?.creationScope ?? S).importNode(e, !0); + C.currentNode = s; + let r = C.nextNode(), o = 0, l = 0, a = n[0]; for (; a !== void 0; ) { if (o === a.index) { let p; - a.type === 2 ? p = new M(r, r.nextSibling, this, t) : a.type === 1 ? p = new a.ctor(r, a.name, a.strings, this, t) : a.type === 6 && (p = new oe(r, this, t)), this._$AV.push(p), a = n[++l]; + a.type === 2 ? p = new O(r, r.nextSibling, this, t) : a.type === 1 ? p = new a.ctor(r, a.name, a.strings, this, t) : a.type === 6 && (p = new oe(r, this, t)), this._$AV.push(p), a = n[++l]; } - o !== a?.index && (r = A.nextNode(), o++); + o !== a?.index && (r = C.nextNode(), o++); } - return A.currentNode = S, i; + return C.currentNode = S, s; } p(t) { let e = 0; for (const n of this._$AV) n !== void 0 && (n.strings !== void 0 ? (n._$AI(t, n, e), e += n.strings.length - 2) : n._$AI(t[e])), e++; } } -class M { +class O { get _$AU() { return this._$AM?._$AU ?? this._$Cv; } - constructor(t, e, n, i) { - this.type = 2, this._$AH = g, this._$AN = void 0, this._$AA = t, this._$AB = e, this._$AM = n, this.options = i, this._$Cv = i?.isConnected ?? !0; + constructor(t, e, n, s) { + this.type = 2, this._$AH = g, this._$AN = void 0, this._$AA = t, this._$AB = e, this._$AM = n, this.options = s, this._$Cv = s?.isConnected ?? !0; } get parentNode() { let t = this._$AA.parentNode; @@ -356,7 +356,7 @@ class M { return this._$AB; } _$AI(t, e = this) { - t = T(this, t, e), U(t) ? t === g || t == null || t === "" ? (this._$AH !== g && this._$AR(), this._$AH = g) : t !== this._$AH && t !== E && this._(t) : t._$litType$ !== void 0 ? this.$(t) : t.nodeType !== void 0 ? this.T(t) : Yt(t) ? this.k(t) : this._(t); + t = E(this, t, e), U(t) ? t === g || t == null || t === "" ? (this._$AH !== g && this._$AR(), this._$AH = g) : t !== this._$AH && t !== N && this._(t) : t._$litType$ !== void 0 ? this.$(t) : t.nodeType !== void 0 ? this.T(t) : Yt(t) ? this.k(t) : this._(t); } O(t) { return this._$AA.parentNode.insertBefore(t, this._$AB); @@ -368,60 +368,60 @@ class M { this._$AH !== g && U(this._$AH) ? this._$AA.nextSibling.data = t : this.T(S.createTextNode(t)), this._$AH = t; } $(t) { - const { values: e, _$litType$: n } = t, i = typeof n == "number" ? this._$AC(t) : (n.el === void 0 && (n.el = D.createElement(jt(n.h, n.h[0]), this.options)), n); - if (this._$AH?._$AD === i) this._$AH.p(e); + const { values: e, _$litType$: n } = t, s = typeof n == "number" ? this._$AC(t) : (n.el === void 0 && (n.el = j.createElement(Ht(n.h, n.h[0]), this.options)), n); + if (this._$AH?._$AD === s) this._$AH.p(e); else { - const r = new ne(i, this), o = r.u(this.options); + const r = new ne(s, this), o = r.u(this.options); r.p(e), this.T(o), this._$AH = r; } } _$AC(t) { - let e = _t.get(t.strings); - return e === void 0 && _t.set(t.strings, e = new D(t)), e; + let e = yt.get(t.strings); + return e === void 0 && yt.set(t.strings, e = new j(t)), e; } k(t) { - it(this._$AH) || (this._$AH = [], this._$AR()); + rt(this._$AH) || (this._$AH = [], this._$AR()); const e = this._$AH; - let n, i = 0; - for (const r of t) i === e.length ? e.push(n = new M(this.O(I()), this.O(I()), this, this.options)) : n = e[i], n._$AI(r), i++; - i < e.length && (this._$AR(n && n._$AB.nextSibling, i), e.length = i); + let n, s = 0; + for (const r of t) s === e.length ? e.push(n = new O(this.O(D()), this.O(D()), this, this.options)) : n = e[s], n._$AI(r), s++; + s < e.length && (this._$AR(n && n._$AB.nextSibling, s), e.length = s); } _$AR(t = this._$AA.nextSibling, e) { for (this._$AP?.(!1, !0, e); t !== this._$AB; ) { - const n = pt(t).nextSibling; - pt(t).remove(), t = n; + const n = ht(t).nextSibling; + ht(t).remove(), t = n; } } setConnected(t) { this._$AM === void 0 && (this._$Cv = t, this._$AP?.(t)); } } -class W { +class Z { get tagName() { return this.element.tagName; } get _$AU() { return this._$AM._$AU; } - constructor(t, e, n, i, r) { - this.type = 1, this._$AH = g, this._$AN = void 0, this.element = t, this.name = e, this._$AM = i, this.options = r, n.length > 2 || n[0] !== "" || n[1] !== "" ? (this._$AH = Array(n.length - 1).fill(new String()), this.strings = n) : this._$AH = g; + constructor(t, e, n, s, r) { + this.type = 1, this._$AH = g, this._$AN = void 0, this.element = t, this.name = e, this._$AM = s, this.options = r, n.length > 2 || n[0] !== "" || n[1] !== "" ? (this._$AH = Array(n.length - 1).fill(new String()), this.strings = n) : this._$AH = g; } - _$AI(t, e = this, n, i) { + _$AI(t, e = this, n, s) { const r = this.strings; let o = !1; - if (r === void 0) t = T(this, t, e, 0), o = !U(t) || t !== this._$AH && t !== E, o && (this._$AH = t); + if (r === void 0) t = E(this, t, e, 0), o = !U(t) || t !== this._$AH && t !== N, o && (this._$AH = t); else { const l = t; let a, p; - for (t = r[0], a = 0; a < r.length - 1; a++) p = T(this, l[n + a], e, a), p === E && (p = this._$AH[a]), o ||= !U(p) || p !== this._$AH[a], p === g ? t = g : t !== g && (t += (p ?? "") + r[a + 1]), this._$AH[a] = p; + for (t = r[0], a = 0; a < r.length - 1; a++) p = E(this, l[n + a], e, a), p === N && (p = this._$AH[a]), o ||= !U(p) || p !== this._$AH[a], p === g ? t = g : t !== g && (t += (p ?? "") + r[a + 1]), this._$AH[a] = p; } - o && !i && this.j(t); + o && !s && this.j(t); } j(t) { t === g ? this.element.removeAttribute(this.name) : this.element.setAttribute(this.name, t ?? ""); } } -class se extends W { +class ie extends Z { constructor() { super(...arguments), this.type = 3; } @@ -429,7 +429,7 @@ class se extends W { this.element[this.name] = t === g ? void 0 : t; } } -class ie extends W { +class se extends Z { constructor() { super(...arguments), this.type = 4; } @@ -437,14 +437,14 @@ class ie extends W { this.element.toggleAttribute(this.name, !!t && t !== g); } } -class re extends W { - constructor(t, e, n, i, r) { - super(t, e, n, i, r), this.type = 5; +class re extends Z { + constructor(t, e, n, s, r) { + super(t, e, n, s, r), this.type = 5; } _$AI(t, e = this) { - if ((t = T(this, t, e, 0) ?? g) === E) return; - const n = this._$AH, i = t === g && n !== g || t.capture !== n.capture || t.once !== n.once || t.passive !== n.passive, r = t !== g && (n === g || i); - i && this.element.removeEventListener(this.name, this, n), r && this.element.addEventListener(this.name, this, t), this._$AH = t; + if ((t = E(this, t, e, 0) ?? g) === N) return; + const n = this._$AH, s = t === g && n !== g || t.capture !== n.capture || t.once !== n.once || t.passive !== n.passive, r = t !== g && (n === g || s); + s && this.element.removeEventListener(this.name, this, n), r && this.element.addEventListener(this.name, this, t), this._$AH = t; } handleEvent(t) { typeof this._$AH == "function" ? this._$AH.call(this.options?.host ?? this.element, t) : this._$AH.handleEvent(t); @@ -458,22 +458,22 @@ class oe { return this._$AM._$AU; } _$AI(t) { - T(this, t); + E(this, t); } } -const ae = { I: M }, le = st.litHtmlPolyfillSupport; -le?.(D, M), (st.litHtmlVersions ??= []).push("3.3.3"); -const de = (s, t, e) => { +const ae = { I: O }, le = st.litHtmlPolyfillSupport; +le?.(j, O), (st.litHtmlVersions ??= []).push("3.3.3"); +const de = (i, t, e) => { const n = e?.renderBefore ?? t; - let i = n._$litPart$; - if (i === void 0) { + let s = n._$litPart$; + if (s === void 0) { const r = e?.renderBefore ?? null; - n._$litPart$ = i = new M(t.insertBefore(I(), r), r, void 0, e ?? {}); + n._$litPart$ = s = new O(t.insertBefore(D(), r), r, void 0, e ?? {}); } - return i._$AI(s), i; + return s._$AI(i), s; }; -const rt = globalThis; -let C = class extends N { +const ot = globalThis; +let A = class extends T { constructor() { super(...arguments), this.renderOptions = { host: this }, this._$Do = void 0; } @@ -492,49 +492,49 @@ let C = class extends N { super.disconnectedCallback(), this._$Do?.setConnected(!1); } render() { - return E; + return N; } }; -C._$litElement$ = !0, C.finalized = !0, rt.litElementHydrateSupport?.({ LitElement: C }); -const ce = rt.litElementPolyfillSupport; -ce?.({ LitElement: C }); -(rt.litElementVersions ??= []).push("4.2.2"); -const Rt = ':host{--nq-p-parchment-top: #f0e4c7;--nq-p-parchment-bottom: #ddcca3;--nq-p-parchment: radial-gradient(ellipse at 25% 10%, rgba(255,255,255,.5), transparent 55%), radial-gradient(ellipse at 85% 90%, rgba(120,86,44,.28), transparent 60%), repeating-linear-gradient(93deg, rgba(150,115,70,.05) 0 2px, transparent 2px 6px), repeating-linear-gradient(2deg, rgba(150,115,70,.04) 0 3px, transparent 3px 7px), linear-gradient(var(--nq-p-parchment-top), var(--nq-p-parchment-bottom));--nq-p-vignette: inset 0 0 200px rgba(80,52,20,.35);--nq-p-card-open: linear-gradient(#fdf7e6, #f2e7c9);--nq-p-card-done: linear-gradient(#f0e8d3, #e6dcc2);--nq-p-card-panel: linear-gradient(#fcf6e6, #f1e6ca);--nq-p-card-border: rgba(120,88,48,.38);--nq-p-panel-border: rgba(92,62,26,.4);--nq-p-card-shadow: 0 3px 0 rgba(120,88,48,.2), inset 0 1px 0 rgba(255,255,255,.7);--nq-p-panel-shadow: 0 6px 0 rgba(92,62,26,.18), inset 0 2px 0 rgba(255,255,255,.7);--nq-p-frame-outer: 2px solid rgba(92,62,26,.45);--nq-p-frame-inner: 1px solid rgba(92,62,26,.28);--nq-p-rule: 2px solid rgba(92,62,26,.35);--nq-p-ink: #2b1f14;--nq-p-ink-secondary: #5c452a;--nq-p-ink-muted: #6f6455;--nq-p-ink-away: #4d4433;--nq-p-ink-late: #8f1526;--nq-brand-blue: #3B3AB8;--nq-brand-purple: #A035CC;--nq-brand-gradient: linear-gradient(135deg, var(--nq-brand-blue), var(--nq-brand-purple));--nq-p-icon-tile: var(--nq-brand-gradient);--nq-p-icon-tile-done: rgba(92,62,26,.16);--nq-p-crest: var(--nq-brand-gradient);--nq-p-crest-away: linear-gradient(135deg, #6b6b7a, #7a7286);--nq-p-seal: radial-gradient(circle at 35% 30%, #a8283a, #6d1322);--nq-p-seal-shadow: 0 4px 10px rgba(60,10,20,.4), inset 0 0 0 4px rgba(255,255,255,.14);--nq-p-seal-size: 78px;--nq-p-dock-bg: rgba(43,31,20,.9);--nq-p-dock-ink: #f7efdb;--nq-p-dock-ink-secondary: #d8c9a6;--nq-p-dock-divider: rgba(233,220,189,.3);--nq-p-dock-height: 84px;--nq-p-font-display: "Cinzel Decorative", Cinzel, serif;--nq-p-font-heading: Cinzel, serif;--nq-p-font-body: Nunito, system-ui, sans-serif;--nq-p-size-hero: 86px;--nq-p-size-wordmark: 66px;--nq-p-size-title: 56px;--nq-p-size-name: 48px;--nq-p-size-section: 32px;--nq-p-size-quest: 30px;--nq-p-size-body: 22px;--nq-p-size-label: 21px;--nq-p-track-label: .14em;--nq-p-track-kicker: .3em;--nq-p-quest-min-height: 116px;--nq-p-quest-gap: 24px;--nq-p-quest-pad: 22px;--nq-p-tile-size: 64px;--nq-p-button-height: 72px;--nq-p-button-min-width: 150px;--nq-p-confirm-button: 108px;--nq-p-radius-card: 12px;--nq-p-radius-panel: 20px;--nq-p-radius-pill: 9999px;--nq-p-page-inset: 62px;--nq-p-frame-inset: 26px;--nq-ease-out: cubic-bezier(0, 0, .2, 1);--nq-dur-micro: .12s;--nq-dur-base: .2s;--nq-dur-modal: .35s}'; -function ot(s) { - window.customCards = window.customCards ?? [], window.customCards.push(s); +A._$litElement$ = !0, A.finalized = !0, ot.litElementHydrateSupport?.({ LitElement: A }); +const ce = ot.litElementPolyfillSupport; +ce?.({ LitElement: A }); +(ot.litElementVersions ??= []).push("4.2.2"); +const Lt = ':host{--nq-p-parchment-top: #f0e4c7;--nq-p-parchment-bottom: #ddcca3;--nq-p-parchment: radial-gradient(ellipse at 25% 10%, rgba(255,255,255,.5), transparent 55%), radial-gradient(ellipse at 85% 90%, rgba(120,86,44,.28), transparent 60%), repeating-linear-gradient(93deg, rgba(150,115,70,.05) 0 2px, transparent 2px 6px), repeating-linear-gradient(2deg, rgba(150,115,70,.04) 0 3px, transparent 3px 7px), linear-gradient(var(--nq-p-parchment-top), var(--nq-p-parchment-bottom));--nq-p-vignette: inset 0 0 200px rgba(80,52,20,.35);--nq-p-card-open: linear-gradient(#fdf7e6, #f2e7c9);--nq-p-card-done: linear-gradient(#f0e8d3, #e6dcc2);--nq-p-card-panel: linear-gradient(#fcf6e6, #f1e6ca);--nq-p-card-border: rgba(120,88,48,.38);--nq-p-panel-border: rgba(92,62,26,.4);--nq-p-card-shadow: 0 3px 0 rgba(120,88,48,.2), inset 0 1px 0 rgba(255,255,255,.7);--nq-p-panel-shadow: 0 6px 0 rgba(92,62,26,.18), inset 0 2px 0 rgba(255,255,255,.7);--nq-p-frame-outer: 2px solid rgba(92,62,26,.45);--nq-p-frame-inner: 1px solid rgba(92,62,26,.28);--nq-p-rule: 2px solid rgba(92,62,26,.35);--nq-p-ink: #2b1f14;--nq-p-ink-secondary: #5c452a;--nq-p-ink-muted: #6f6455;--nq-p-ink-away: #4d4433;--nq-p-ink-late: #8f1526;--nq-brand-blue: #3B3AB8;--nq-brand-purple: #A035CC;--nq-brand-gradient: linear-gradient(135deg, var(--nq-brand-blue), var(--nq-brand-purple));--nq-p-icon-tile: var(--nq-brand-gradient);--nq-p-icon-tile-done: rgba(92,62,26,.16);--nq-p-crest: var(--nq-brand-gradient);--nq-p-crest-away: linear-gradient(135deg, #6b6b7a, #7a7286);--nq-p-seal: radial-gradient(circle at 35% 30%, #a8283a, #6d1322);--nq-p-seal-shadow: 0 4px 10px rgba(60,10,20,.4), inset 0 0 0 4px rgba(255,255,255,.14);--nq-p-seal-size: 78px;--nq-p-dock-bg: rgba(43,31,20,.9);--nq-p-dock-ink: #f7efdb;--nq-p-dock-ink-secondary: #d8c9a6;--nq-p-dock-divider: rgba(233,220,189,.3);--nq-p-dock-height: 84px;--nq-p-font-display: "Cinzel Decorative", Cinzel, serif;--nq-p-font-heading: Cinzel, serif;--nq-p-font-body: Nunito, system-ui, sans-serif;--nq-p-size-hero: 86px;--nq-p-size-wordmark: 66px;--nq-p-size-title: 56px;--nq-p-size-name: 48px;--nq-p-size-section: 32px;--nq-p-size-quest: 30px;--nq-p-size-body: 22px;--nq-p-size-label: 21px;--nq-p-track-label: .14em;--nq-p-track-kicker: .3em;--nq-p-quest-min-height: 116px;--nq-p-quest-gap: 24px;--nq-p-quest-pad: 22px;--nq-p-tile-size: 64px;--nq-p-button-height: 72px;--nq-p-button-min-width: 150px;--nq-p-confirm-button: 108px;--nq-p-radius-card: 12px;--nq-p-radius-panel: 20px;--nq-p-radius-pill: 9999px;--nq-p-page-inset: 62px;--nq-p-frame-inset: 26px;--nq-ease-out: cubic-bezier(0, 0, .2, 1);--nq-dur-micro: .12s;--nq-dur-base: .2s;--nq-dur-modal: .35s}'; +function at(i) { + window.customCards = window.customCards ?? [], window.customCards.push(i); } -const yt = /* @__PURE__ */ new Map(); -function F(s, t) { - const e = `${s ?? "local"}|${JSON.stringify(t)}`, n = yt.get(e); +const vt = /* @__PURE__ */ new Map(); +function Q(i, t) { + const e = `${i ?? "local"}|${JSON.stringify(t)}`, n = vt.get(e); if (n) return n; - let i; + let s; try { - i = new Intl.DateTimeFormat("en-US", { + s = new Intl.DateTimeFormat("en-US", { ...t, - ...s ? { timeZone: s } : {} + ...i ? { timeZone: i } : {} }); } catch { - i = new Intl.DateTimeFormat("en-US", t); + s = new Intl.DateTimeFormat("en-US", t); } - return yt.set(e, i), i; + return vt.set(e, s), s; } -function pe(s) { - const t = s?.config; +function pe(i) { + const t = i?.config; if (!t || typeof t != "object") return; const e = t.time_zone; return typeof e == "string" && e.trim() ? e.trim() : void 0; } -function G(s, t) { - return `${F(t, { weekday: "long" }).format(s)}, ${F(t, { month: "short", day: "numeric" }).format(s)}`; +function K(i, t) { + return `${Q(t, { weekday: "long" }).format(i)}, ${Q(t, { month: "short", day: "numeric" }).format(i)}`; } -function he(s, t) { - return F(t, { +function he(i, t) { + return Q(t, { hour: "numeric", minute: "2-digit", hour12: !0 - }).format(s); + }).format(i); } const ue = { "clear-night": "Clear", @@ -569,26 +569,26 @@ const ue = { windy: "A blustery day", "windy-variant": "A blustery day" }; -function vt(s) { - return typeof s == "string" ? s.trim() : ""; +function bt(i) { + return typeof i == "string" ? i.trim() : ""; } -function K(s, t) { - if (s == null || s === "") +function J(i, t) { + if (i == null || i === "") return t; - const e = typeof s == "number" ? s : Number(s); + const e = typeof i == "number" ? i : Number(i); return Number.isFinite(e) ? e : t; } -function bt(s) { - return Number.isFinite(s) ? Math.min(100, Math.max(0, s)) : 0; +function xt(i) { + return Number.isFinite(i) ? Math.min(100, Math.max(0, i)) : 0; } -function xt(s) { - return Math.max(0, Math.round(s)); +function $t(i) { + return Math.max(0, Math.round(i)); } -function me(s) { - return s.split(/[-_]+/).filter(Boolean).map((t) => t.charAt(0).toUpperCase() + t.slice(1)).join(" "); +function me(i) { + return i.split(/[-_]+/).filter(Boolean).map((t) => t.charAt(0).toUpperCase() + t.slice(1)).join(" "); } -function ge(s, t) { - const e = F(t, { +function ge(i, t) { + const e = Q(t, { year: "numeric", month: "2-digit", day: "2-digit", @@ -596,10 +596,10 @@ function ge(s, t) { minute: "2-digit", second: "2-digit", hourCycle: "h23" - }).formatToParts(new Date(s)), n = (r) => { + }).formatToParts(new Date(i)), n = (r) => { const o = e.find((l) => l.type === r); return o ? Number(o.value) : Number.NaN; - }, i = Date.UTC( + }, s = Date.UTC( n("year"), n("month") - 1, n("day"), @@ -607,32 +607,32 @@ function ge(s, t) { n("minute"), n("second") ); - return Number.isFinite(i) ? i - s : 0; + return Number.isFinite(s) ? s - i : 0; } -function _e(s, t) { - if (typeof s != "string") +function _e(i, t) { + if (typeof i != "string") return null; - const e = s.split("-"); + const e = i.split("-"); if (e.length !== 3) return null; - const [n, i, r] = e.map((a) => Number(a)); - if (!n || !i || !r) + const [n, s, r] = e.map((a) => Number(a)); + if (!n || !s || !r) return null; if (!t) { - const a = new Date(n, i - 1, r); + const a = new Date(n, s - 1, r); return Number.isNaN(a.getTime()) ? null : a; } - const o = Date.UTC(n, i - 1, r), l = new Date(o - ge(o, t)); + const o = Date.UTC(n, s - 1, r), l = new Date(o - ge(o, t)); return Number.isNaN(l.getTime()) ? null : l; } -function ye(s) { - const t = ue[s]; - return t || s.charAt(0).toUpperCase() + s.slice(1); +function ye(i) { + const t = ue[i]; + return t || i.charAt(0).toUpperCase() + i.slice(1); } -function ve(s, t) { - return fe[s] ?? t; +function ve(i, t) { + return fe[i] ?? t; } -const $t = "polygon(0 0, 100% 0, 100% 62%, 50% 100%, 0 62%)", be = "polygon(50% 0, 93% 25%, 93% 75%, 50% 100%, 7% 75%, 7% 25%)", xe = Pt` +const wt = "polygon(0 0, 100% 0, 100% 62%, 50% 100%, 0 62%)", be = "polygon(50% 0, 93% 25%, 93% 75%, 50% 100%, 7% 75%, 7% 25%)", xe = Pt` * { box-sizing: border-box; } @@ -704,7 +704,7 @@ const $t = "polygon(0 0, 100% 0, 100% 62%, 50% 100%, 0 62%)", be = "polygon(50% justify-content: center; width: 34px; height: 34px; - clip-path: ${v(be)}; + clip-path: ${b(be)}; background: var(--nq-brand-gradient); } @@ -798,7 +798,7 @@ const $t = "polygon(0 0, 100% 0, 100% 62%, 50% 100%, 0 62%)", be = "polygon(50% width: 188px; height: 214px; padding: 4px; - clip-path: ${v($t)}; + clip-path: ${b(wt)}; background: rgba(255, 255, 255, 0.22); } @@ -809,7 +809,7 @@ const $t = "polygon(0 0, 100% 0, 100% 62%, 50% 100%, 0 62%)", be = "polygon(50% align-items: center; justify-content: center; padding-bottom: 70px; - clip-path: ${v($t)}; + clip-path: ${b(wt)}; background: var(--nq-p-crest); } @@ -1072,7 +1072,7 @@ const $t = "polygon(0 0, 100% 0, 100% 62%, 50% 100%, 0 62%)", be = "polygon(50% -`, Ae = c``, Ce = c` -`, wt = { +`, qt = { icon: ke, headline: "NestQuest is not set up yet", body: "A parent needs to finish setting up NestQuest before the party can gather." -}, Ce = { - icon: Ae, +}, Ae = { + icon: Ce, headline: "The records cannot be reached", body: "The party's records are quiet right now. NestQuest will return shortly." }, Se = c` `; -class Ee extends C { +class Ne extends A { static properties = { hass: { attribute: !1 }, _config: { state: !0 }, _now: { state: !0 } }; - static styles = [v(Rt), xe]; + static styles = [b(Lt), xe]; hass; _config; _now = /* @__PURE__ */ new Date(); @@ -1151,7 +1151,7 @@ class Ee extends C {
    -

    The Party · ${G(this._now, this._timeZone())}

    +

    The Party · ${K(this._now, this._timeZone())}

    ${t ? this._renderNotice(t) : c`
    ${this._plates().map((e) => this._renderPlate(e))} @@ -1182,13 +1182,13 @@ class Ee extends C { return this._childSlugs().map((t) => this._plate(t)); } _plate(t) { - const e = this._dueSensorResolution(t), n = this._state(`sensor.nestquest_${t}_quests_due_today`), i = this._state( + const e = this._dueSensorResolution(t), n = this._state(`sensor.nestquest_${t}_quests_due_today`), s = this._state( `sensor.nestquest_${t}_quests_completed_today` ), r = this._state( `sensor.nestquest_${t}_completion_pct_today` ), o = this._state( `binary_sensor.nestquest_${t}_present_today` - ), l = n?.attributes ?? {}, a = xt(K(n?.state, 0)), p = xt(K(i?.state, 0)), u = r ? K(r.state, Number.NaN) : Number.NaN, d = Number.isFinite(u) ? bt(u) : a > 0 ? bt(p / a * 100) : 0; + ), l = n?.attributes ?? {}, a = $t(J(n?.state, 0)), p = $t(J(s?.state, 0)), u = r ? J(r.state, Number.NaN) : Number.NaN, d = Number.isFinite(u) ? xt(u) : a > 0 ? xt(p / a * 100) : 0; let f = !0; const h = l.present; typeof h == "boolean" ? f = h : o && (f = String(o.state ?? "").trim().toLowerCase() === "on"); @@ -1197,14 +1197,14 @@ class Ee extends C { if (typeof _ == "string" && _.trim()) m = _.trim(); else { - const Z = o?.attributes?.child_name; - typeof Z == "string" && Z.trim() && (m = Z.trim()); + const V = o?.attributes?.child_name; + typeof V == "string" && V.trim() && (m = V.trim()); } m || (m = me(t)); - const b = _e( + const x = _e( o?.attributes?.next_present, this._timeZone() - ), j = b ? `Returns ${G(b, this._timeZone())}` : null; + ), H = x ? `Returns ${K(x, this._timeZone())}` : null; return { slug: t, name: m, @@ -1213,7 +1213,7 @@ class Ee extends C { completed: p, due: a, pct: d, - returns: j, + returns: H, unresolved: e }; } @@ -1230,7 +1230,7 @@ class Ee extends C { } _boardNotice() { const t = this._plates(); - return t.length === 0 ? wt : t.every((e) => e.unresolved !== null) ? t.some((e) => e.unresolved === "stale") ? Ce : wt : null; + return t.length === 0 ? qt : t.every((e) => e.unresolved !== null) ? t.some((e) => e.unresolved === "stale") ? Ae : qt : null; } _renderNotice(t) { return c` @@ -1301,11 +1301,11 @@ class Ee extends C { `; } _openQuestLog(t) { - const e = vt(this._config?.quest_log_path); + const e = bt(this._config?.quest_log_path); e && (window.history.pushState(null, "", `${e.replace(/\/+$/, "")}/${t}`), window.dispatchEvent(new Event("location-changed"))); } _dockWeather() { - const t = vt(this._config?.weather_entity); + const t = bt(this._config?.weather_entity); if (!t) return null; const e = this._state(t); @@ -1314,9 +1314,9 @@ class Ee extends C { const n = String(e.state ?? "").trim(); if (!n || n === "unavailable" || n === "unknown") return null; - const i = e.attributes ?? {}; + const s = e.attributes ?? {}; let r = null, o = null; - const l = i.forecast; + const l = s.forecast; if (Array.isArray(l) && l.length > 0) { const a = l[0]; if (a && typeof a == "object") { @@ -1326,7 +1326,7 @@ class Ee extends C { } return { condition: n, - temperature: this._optionalNumber(i.temperature), + temperature: this._optionalNumber(s.temperature), high: r, low: o }; @@ -1342,16 +1342,16 @@ class Ee extends C { if (!t) return c`
    - ${G(this._now, this._timeZone())} + ${K(this._now, this._timeZone())} ${he(this._now, this._timeZone())}
    `; - const e = ye(t.condition), n = ve(t.condition, e), i = t.temperature === null ? g : c`${Math.round(t.temperature)}°`, r = t.high === null || t.low === null ? null : `${Math.round(t.high)}° / ${Math.round(t.low)}°`; + const e = ye(t.condition), n = ve(t.condition, e), s = t.temperature === null ? g : c`${Math.round(t.temperature)}°`, r = t.high === null || t.low === null ? null : `${Math.round(t.high)}° / ${Math.round(t.low)}°`; return c`
    ${Se} - ${i} + ${s} ${r === null ? e : `${e} · ${r}`} @@ -1362,13 +1362,13 @@ class Ee extends C { `; } } -customElements.define("nestquest-party-board-card", Ee); -ot({ +customElements.define("nestquest-party-board-card", Ne); +at({ type: "nestquest-party-board-card", name: "NestQuest Party Board", description: "Kids' attract screen for present and away adventurers." }); -const Ne = { CHILD: 2 }, Te = (s) => (...t) => ({ _$litDirective$: s, values: t }); +const Te = { CHILD: 2 }, Ee = (i) => (...t) => ({ _$litDirective$: i, values: t }); let Me = class { constructor(t) { } @@ -1385,119 +1385,126 @@ let Me = class { return this.render(...e); } }; -const { I: Oe } = ae, qt = (s) => s, kt = () => document.createComment(""), z = (s, t, e) => { - const n = s._$AA.parentNode, i = t === void 0 ? s._$AB : t._$AA; +const { I: Oe } = ae, kt = (i) => i, Ct = () => document.createComment(""), P = (i, t, e) => { + const n = i._$AA.parentNode, s = t === void 0 ? i._$AB : t._$AA; if (e === void 0) { - const r = n.insertBefore(kt(), i), o = n.insertBefore(kt(), i); - e = new Oe(r, o, s, s.options); + const r = n.insertBefore(Ct(), s), o = n.insertBefore(Ct(), s); + e = new Oe(r, o, i, i.options); } else { - const r = e._$AB.nextSibling, o = e._$AM, l = o !== s; + const r = e._$AB.nextSibling, o = e._$AM, l = o !== i; if (l) { let a; - e._$AQ?.(s), e._$AM = s, e._$AP !== void 0 && (a = s._$AU) !== o._$AU && e._$AP(a); + e._$AQ?.(i), e._$AM = i, e._$AP !== void 0 && (a = i._$AU) !== o._$AU && e._$AP(a); } - if (r !== i || l) { + if (r !== s || l) { let a = e._$AA; for (; a !== r; ) { - const p = qt(a).nextSibling; - qt(n).insertBefore(a, i), a = p; + const p = kt(a).nextSibling; + kt(n).insertBefore(a, s), a = p; } } } return e; -}, q = (s, t, e = s) => (s._$AI(t, e), s), ze = {}, Pe = (s, t = ze) => s._$AH = t, Ie = (s) => s._$AH, J = (s) => { - s._$AR(), s._$AA.remove(); +}, k = (i, t, e = i) => (i._$AI(t, e), i), ze = {}, Pe = (i, t = ze) => i._$AH = t, Ie = (i) => i._$AH, X = (i) => { + i._$AR(), i._$AA.remove(); }; -const At = (s, t, e) => { +const At = (i, t, e) => { const n = /* @__PURE__ */ new Map(); - for (let i = t; i <= e; i++) n.set(s[i], i); + for (let s = t; s <= e; s++) n.set(i[s], s); return n; -}, Ct = Te(class extends Me { - constructor(s) { - if (super(s), s.type !== Ne.CHILD) throw Error("repeat() can only be used in text expressions"); +}, St = Ee(class extends Me { + constructor(i) { + if (super(i), i.type !== Te.CHILD) throw Error("repeat() can only be used in text expressions"); } - dt(s, t, e) { + dt(i, t, e) { let n; e === void 0 ? e = t : t !== void 0 && (n = t); - const i = [], r = []; + const s = [], r = []; let o = 0; - for (const l of s) i[o] = n ? n(l, o) : o, r[o] = e(l, o), o++; - return { values: r, keys: i }; + for (const l of i) s[o] = n ? n(l, o) : o, r[o] = e(l, o), o++; + return { values: r, keys: s }; } - render(s, t, e) { - return this.dt(s, t, e).values; + render(i, t, e) { + return this.dt(i, t, e).values; } - update(s, [t, e, n]) { - const i = Ie(s), { values: r, keys: o } = this.dt(t, e, n); - if (!Array.isArray(i)) return this.ut = o, r; + update(i, [t, e, n]) { + const s = Ie(i), { values: r, keys: o } = this.dt(t, e, n); + if (!Array.isArray(s)) return this.ut = o, r; const l = this.ut ??= [], a = []; - let p, u, d = 0, f = i.length - 1, h = 0, m = r.length - 1; - for (; d <= f && h <= m; ) if (i[d] === null) d++; - else if (i[f] === null) f--; - else if (l[d] === o[h]) a[h] = q(i[d], r[h]), d++, h++; - else if (l[f] === o[m]) a[m] = q(i[f], r[m]), f--, m--; - else if (l[d] === o[m]) a[m] = q(i[d], r[m]), z(s, a[m + 1], i[d]), d++, m--; - else if (l[f] === o[h]) a[h] = q(i[f], r[h]), z(s, i[d], i[f]), f--, h++; + let p, u, d = 0, f = s.length - 1, h = 0, m = r.length - 1; + for (; d <= f && h <= m; ) if (s[d] === null) d++; + else if (s[f] === null) f--; + else if (l[d] === o[h]) a[h] = k(s[d], r[h]), d++, h++; + else if (l[f] === o[m]) a[m] = k(s[f], r[m]), f--, m--; + else if (l[d] === o[m]) a[m] = k(s[d], r[m]), P(i, a[m + 1], s[d]), d++, m--; + else if (l[f] === o[h]) a[h] = k(s[f], r[h]), P(i, s[d], s[f]), f--, h++; else if (p === void 0 && (p = At(o, h, m), u = At(l, d, f)), p.has(l[d])) if (p.has(l[f])) { - const _ = u.get(o[h]), b = _ !== void 0 ? i[_] : null; - if (b === null) { - const j = z(s, i[d]); - q(j, r[h]), a[h] = j; - } else a[h] = q(b, r[h]), z(s, i[d], b), i[_] = null; + const _ = u.get(o[h]), x = _ !== void 0 ? s[_] : null; + if (x === null) { + const H = P(i, s[d]); + k(H, r[h]), a[h] = H; + } else a[h] = k(x, r[h]), P(i, s[d], x), s[_] = null; h++; - } else J(i[f]), f--; - else J(i[d]), d++; + } else X(s[f]), f--; + else X(s[d]), d++; for (; h <= m; ) { - const _ = z(s, a[m + 1]); - q(_, r[h]), a[h++] = _; + const _ = P(i, a[m + 1]); + k(_, r[h]), a[h++] = _; } for (; d <= f; ) { - const _ = i[d++]; - _ !== null && J(_); + const _ = s[d++]; + _ !== null && X(_); } - return this.ut = o, Pe(s, a), E; + return this.ut = o, Pe(i, a), N; } -}), St = /* @__PURE__ */ new Map(); -function H(s, t) { - const e = `${s ?? "local"}|${JSON.stringify(t)}`, n = St.get(e); +}), Nt = /* @__PURE__ */ new Map(); +function M(i, t) { + const e = `${i ?? "local"}|${JSON.stringify(t)}`, n = Nt.get(e); if (n) return n; - let i; + let s; try { - i = new Intl.DateTimeFormat("en-US", { + s = new Intl.DateTimeFormat("en-US", { ...t, - ...s ? { timeZone: s } : {} + ...i ? { timeZone: i } : {} }); } catch { - i = new Intl.DateTimeFormat("en-US", t); + s = new Intl.DateTimeFormat("en-US", t); } - return St.set(e, i), i; + return Nt.set(e, s), s; } -function Ue(s) { - const t = s?.config; +function De(i) { + const t = i?.config; if (!t || typeof t != "object") return; const e = t.time_zone; return typeof e == "string" && e.trim() ? e.trim() : void 0; } -function Et(s, t) { - return `${H(t, { weekday: "long" }).format(s)}, ${H(t, { month: "short", day: "numeric" }).format(s)}`; +function L(i, t) { + return `${M(t, { weekday: "long" }).format(i)}, ${M(t, { month: "short", day: "numeric" }).format(i)}`; } -function De(s, t) { - return H(t, { weekday: "long" }).format(s); +function Ue(i, t) { + return M(t, { weekday: "long" }).format(i); } -function He(s, t) { - const e = new Date(s); - return Number.isNaN(e.getTime()) ? "" : H(t, { +function je(i, t) { + const e = new Date(i); + return Number.isNaN(e.getTime()) ? "" : M(t, { hour: "numeric", minute: "2-digit", hour12: !0 }).format(e); } -function je(s) { - if (!s) +function He(i, t) { + return M(t, { + hour: "numeric", + minute: "2-digit", + hour12: !0 + }).format(i); +} +function Le(i) { + if (!i) return null; - const t = /^(\d{1,2}):(\d{2})$/.exec(s.trim()); + const t = /^(\d{1,2}):(\d{2})$/.exec(i.trim()); if (!t) return null; const e = Number(t[1]); @@ -1506,23 +1513,23 @@ function je(s) { const n = e >= 12 ? "PM" : "AM"; return `${e % 12 === 0 ? 12 : e % 12}:${t[2]} ${n}`; } -function k(s) { - return typeof s == "string" ? s.trim() : ""; +function $(i) { + return typeof i == "string" ? i.trim() : ""; } -function y(s, t) { - if (s == null || s === "") +function y(i, t) { + if (i == null || i === "") return t; - const e = typeof s == "number" ? s : Number(s); + const e = typeof i == "number" ? i : Number(i); return Number.isFinite(e) ? e : t; } -function x(s) { - return Math.max(0, Math.round(s)); +function v(i) { + return Math.max(0, Math.round(i)); } -function X(s) { - return s.split(/[-_]+/).filter(Boolean).map((t) => t.charAt(0).toUpperCase() + t.slice(1)).join(" "); +function Y(i) { + return i.split(/[-_]+/).filter(Boolean).map((t) => t.charAt(0).toUpperCase() + t.slice(1)).join(" "); } -function Re(s, t) { - const e = H(t, { +function Re(i, t) { + const e = M(t, { year: "numeric", month: "2-digit", day: "2-digit", @@ -1530,10 +1537,10 @@ function Re(s, t) { minute: "2-digit", second: "2-digit", hourCycle: "h23" - }).formatToParts(new Date(s)), n = (r) => { + }).formatToParts(new Date(i)), n = (r) => { const o = e.find((l) => l.type === r); return o ? Number(o.value) : Number.NaN; - }, i = Date.UTC( + }, s = Date.UTC( n("year"), n("month") - 1, n("day"), @@ -1541,51 +1548,92 @@ function Re(s, t) { n("minute"), n("second") ); - return Number.isFinite(i) ? i - s : 0; + return Number.isFinite(s) ? s - i : 0; } -function Nt(s, t) { - if (typeof s != "string") +function Tt(i, t) { + if (typeof i != "string") return null; - const e = s.split("-"); + const e = i.split("-"); if (e.length !== 3) return null; - const [n, i, r] = e.map((a) => Number(a)); - if (!n || !i || !r) + const [n, s, r] = e.map((a) => Number(a)); + if (!n || !s || !r) return null; if (!t) { - const a = new Date(n, i - 1, r); + const a = new Date(n, s - 1, r); return Number.isNaN(a.getTime()) ? null : a; } - const o = Date.UTC(n, i - 1, r), l = new Date(o - Re(o, t)); + const o = Date.UTC(n, s - 1, r), l = new Date(o - Re(o, t)); return Number.isNaN(l.getTime()) ? null : l; } -function Le() { - const s = window.location.pathname.split("/").filter(Boolean); - return s.length > 0 ? s[s.length - 1].toLowerCase() : ""; +function Be() { + const i = window.location.pathname.split("/").filter(Boolean); + return i.length > 0 ? i[i.length - 1].toLowerCase() : ""; } -function Be(s) { - if (!s || typeof s != "object") +function Fe(i) { + if (!i || typeof i != "object") return null; - const t = s, e = k(t.state).toLowerCase(); + const t = i, e = $(t.state).toLowerCase(); if (e !== "open" && e !== "completed") return null; const n = y(t.id, 0); if (!n) return null; - const i = y(t.child_id, 0); + const s = y(t.child_id, 0); return { id: n, - child_id: i > 0 ? i : null, - title: k(t.title) || "Quest", - icon: k(t.icon) || null, - window: k(t.window).toLowerCase(), - due_time: k(t.due_time) || null, + child_id: s > 0 ? s : null, + title: $(t.title) || "Quest", + icon: $(t.icon) || null, + window: $(t.window).toLowerCase(), + due_time: $(t.due_time) || null, state: e, overdue: t.overdue === !0, - completed_at: k(t.completed_at) || null + completed_at: $(t.completed_at) || null, + on_time: t.on_time === !0 ? !0 : t.on_time === !1 ? !1 : null }; } -const Tt = "polygon(0 0, 100% 0, 100% 62%, 50% 100%, 0 62%)", Fe = "polygon(50% 0, 93% 25%, 93% 75%, 50% 100%, 7% 75%, 7% 25%)", Mt = [-9, 6, -4], Qe = Pt` +const Qe = { + "clear-night": "Clear", + cloudy: "Cloudy", + exceptional: "Clear", + fog: "Foggy", + hail: "Hail", + lightning: "Storms", + "lightning-rainy": "Storms", + partlycloudy: "Partly cloudy", + pouring: "Heavy rain", + rainy: "Rain", + snowy: "Snow", + "snowy-rainy": "Sleet", + sunny: "Sunny", + windy: "Windy", + "windy-variant": "Windy" +}, We = { + "clear-night": "Clear night skies", + cloudy: "Grey skies today", + exceptional: "A striking day", + fog: "Mist on the road", + hail: "Ice from the sky", + lightning: "Storms may roll in", + "lightning-rainy": "Storms may roll in", + partlycloudy: "Sun between clouds", + pouring: "Heavy rain outside", + rainy: "Rain on the walls", + snowy: "Snow on the peaks", + "snowy-rainy": "Sleet may fall", + sunny: "Clear skies today", + windy: "A blustery day", + "windy-variant": "A blustery day" +}; +function Ze(i) { + const t = Qe[i]; + return t || i.charAt(0).toUpperCase() + i.slice(1); +} +function Ve(i, t) { + return We[i] ?? t; +} +const Et = "polygon(0 0, 100% 0, 100% 62%, 50% 100%, 0 62%)", Ge = "polygon(50% 0, 93% 25%, 93% 75%, 50% 100%, 7% 75%, 7% 25%)", Mt = [-9, 6, -4], Ke = Pt` * { box-sizing: border-box; } @@ -1645,7 +1693,7 @@ const Tt = "polygon(0 0, 100% 0, 100% 62%, 50% 100%, 0 62%)", Fe = "polygon(50% width: 96px; height: 110px; padding: 3px; - clip-path: ${v(Tt)}; + clip-path: ${b(Et)}; background: rgba(255, 255, 255, 0.22); } @@ -1656,7 +1704,7 @@ const Tt = "polygon(0 0, 100% 0, 100% 62%, 50% 100%, 0 62%)", Fe = "polygon(50% align-items: center; justify-content: center; padding-bottom: 32px; - clip-path: ${v(Tt)}; + clip-path: ${b(Et)}; background: var(--nq-p-crest); } @@ -1722,7 +1770,7 @@ const Tt = "polygon(0 0, 100% 0, 100% 62%, 50% 100%, 0 62%)", Fe = "polygon(50% justify-content: center; width: 44px; height: 44px; - clip-path: ${v(Fe)}; + clip-path: ${b(Ge)}; background: var(--nq-brand-gradient); } @@ -2268,6 +2316,170 @@ const Tt = "polygon(0 0, 100% 0, 100% 62%, 50% 100%, 0 62%)", Fe = "polygon(50% transform: translate(-50%, 0); } } + + .complete-screen { + position: absolute; + inset: 0; + display: flex; + flex-direction: column; + align-items: center; + padding: 96px 62px 130px; + text-align: center; + } + + .complete-seal { + display: inline-flex; + align-items: center; + justify-content: center; + width: 168px; + height: 168px; + border-radius: var(--nq-p-radius-pill); + background: var(--nq-p-seal); + box-shadow: 0 10px 24px rgba(60, 10, 20, 0.42); + color: rgba(255, 235, 235, 0.95); + transform: rotate(-6deg); + } + + .complete-seal svg { + width: 86px; + height: 86px; + } + + .complete-title { + margin: 28px 0 0; + font-family: var(--nq-p-font-display); + font-size: 86px; + font-weight: 900; + line-height: 1.1; + background: var(--nq-brand-gradient-h); + -webkit-background-clip: text; + background-clip: text; + color: transparent; + -webkit-text-fill-color: transparent; + } + + .complete-sub { + margin: 16px 0 0; + max-width: 1000px; + font-family: var(--nq-p-font-body); + font-size: 34px; + font-weight: 600; + line-height: 1.35; + color: var(--nq-p-ink-secondary); + text-wrap: pretty; + } + + .complete-countdown { + margin: 12px 0 0; + font-family: var(--nq-p-font-body); + font-size: 25px; + font-weight: 600; + line-height: 1.2; + color: var(--nq-p-ink-secondary); + } + + .complete-stats { + position: absolute; + top: 540px; + left: 200px; + right: 200px; + display: grid; + grid-template-columns: repeat(4, 1fr); + gap: 26px; + } + + .complete-stat { + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + gap: 8px; + padding: 28px 16px; + border: 2px solid var(--nq-p-panel-border); + border-radius: 16px; + background: var(--nq-p-card-panel); + box-shadow: var(--nq-p-panel-shadow); + } + + .complete-stat .numeral { + font-family: var(--nq-p-font-heading); + font-size: 58px; + font-weight: 900; + line-height: 1; + color: var(--nq-p-ink); + } + + .complete-stat .label { + font-family: var(--nq-p-font-heading); + font-size: 17px; + font-weight: 600; + letter-spacing: 0.14em; + text-transform: uppercase; + line-height: 1.2; + color: var(--nq-p-ink-secondary); + } + + .dock { + position: absolute; + left: 46px; + right: 46px; + bottom: 46px; + height: var(--nq-p-dock-height); + display: flex; + align-items: center; + justify-content: center; + gap: 24px; + padding: 0 40px; + border-radius: 14px; + background: var(--nq-p-dock-bg); + pointer-events: none; + user-select: none; + color: var(--nq-p-dock-ink); + } + + .dock svg { + flex: none; + width: 40px; + height: 40px; + color: var(--nq-p-dock-ink); + } + + .dock .divider { + flex: none; + width: 1px; + height: 40px; + background: var(--nq-p-dock-divider); + } + + .dock .temp { + font-family: var(--nq-p-font-body); + font-size: 38px; + font-weight: 800; + line-height: 1; + } + + .dock .condition { + font-family: var(--nq-p-font-body); + font-size: 26px; + font-weight: 600; + line-height: 1.2; + color: var(--nq-p-dock-ink-secondary); + } + + .dock .date { + font-family: var(--nq-p-font-body); + font-size: 26px; + font-weight: 600; + line-height: 1.2; + color: var(--nq-p-dock-ink-secondary); + } + + .dock .clock { + font-family: var(--nq-p-font-body); + font-size: 38px; + font-weight: 800; + line-height: 1; + } `, R = c` -`, We = c``, Je = c` -`, Y = c``, tt = c` -`, Ze = c``, Xe = c` -`, Lt = c``, Rt = c` -`, Ve = c``, Ye = c` -`, Ge = c``, tn = c` -`, Ke = c``, en = c` -`, Je = c``, nn = c` -`, Xe = c``, sn = c` -`, Ye = { - icon: Ke, +`, rn = c``, on = { + icon: en, headline: "No adventurer chosen", body: "Open The Party and tap your crest to open your quest log." -}, tn = { - icon: Je, +}, an = { + icon: nn, headline: "NestQuest is not set up yet", body: "A parent needs to finish setting up NestQuest." -}, en = { - icon: Xe, +}, ln = { + icon: sn, headline: "The records cannot be reached", body: "The party's records are quiet right now. NestQuest will return shortly." }, Ot = [ - { key: "morning", name: "Morning", range: "Until 11:59 AM", icon: Ze }, - { key: "afternoon", name: "Afternoon", range: "12:00–5:00 PM", icon: Lt }, - { key: "evening", name: "Evening", range: "5:00–9:00 PM", icon: Ve } + { key: "morning", name: "Morning", range: "Until 11:59 AM", icon: Xe }, + { key: "afternoon", name: "Afternoon", range: "12:00–5:00 PM", icon: Rt }, + { key: "evening", name: "Evening", range: "5:00–9:00 PM", icon: Ye } ]; -class nn extends C { +class dn extends A { static properties = { hass: { attribute: !1 }, _config: { state: !0 }, _now: { state: !0 }, _confirm: { state: !0 }, _optimistic: { state: !0 }, - _toast: { state: !0 } + _toast: { state: !0 }, + _countdown: { state: !0 } }; - static styles = [v(Rt), Qe]; + static styles = [b(Lt), Ke]; hass; _config; _now = /* @__PURE__ */ new Date(); @@ -2426,10 +2650,12 @@ class nn extends C { * answered; the seal shows immediately and reverts on failure. */ _optimistic = /* @__PURE__ */ new Map(); _toast = null; + _countdown = null; _clockTimer; _idleTimer; _confirmTimer; _toastTimer; + _completeTimer; setConfig(t) { if (!t || typeof t != "object") throw new Error("Invalid configuration"); @@ -2441,10 +2667,13 @@ class nn extends C { connectedCallback() { super.connectedCallback(), this._now = /* @__PURE__ */ new Date(), this._clockTimer = window.setInterval(() => { this._now = /* @__PURE__ */ new Date(); - }, 6e4), window.addEventListener("pointerdown", this._onActivity, !0), window.addEventListener("touchstart", this._onActivity, !0), window.addEventListener("keydown", this._onActivity, !0), this._armIdle(); + }, 6e4), window.addEventListener("pointerdown", this._onActivity, !0), window.addEventListener("touchstart", this._onActivity, !0), window.addEventListener("keydown", this._onActivity, !0), this._armIdle(), this._logView().kind === "complete-day" && this._armCompleteTimer(); + } + updated() { + this._logView().kind === "complete-day" ? (this._clearIdle(), this._armCompleteTimer()) : (this._clearCompleteTimer(), this._countdown = null, this._idleTimer === void 0 && this._armIdle()); } disconnectedCallback() { - this._stopClock(), this._clearIdle(), this._clearConfirmTimer(), this._clearToastTimer(), window.removeEventListener("pointerdown", this._onActivity, !0), window.removeEventListener("touchstart", this._onActivity, !0), window.removeEventListener("keydown", this._onActivity, !0), super.disconnectedCallback(); + this._stopClock(), this._clearIdle(), this._clearConfirmTimer(), this._clearToastTimer(), this._clearCompleteTimer(), window.removeEventListener("pointerdown", this._onActivity, !0), window.removeEventListener("touchstart", this._onActivity, !0), window.removeEventListener("keydown", this._onActivity, !0), super.disconnectedCallback(); } render() { const t = this._logView(); @@ -2466,18 +2695,18 @@ class nn extends C { _renderMain(t) { switch (t.kind) { case "no-adventurer": - return this._renderNotice(Ye); + return this._renderNotice(on); case "not-set-up": - return this._renderNotice(tn); + return this._renderNotice(an); case "unreachable": - return this._renderNotice(en); + return this._renderNotice(ln); case "away": { const e = ["The quest log unlocks when they return."]; return t.returns && e.push(`Returns ${t.returns}`), c` ${this._renderHeader(t)}
    - ${Ge} + ${tn} ${t.name} is on travels ${e.map((n) => c`

    ${n}

    `)}
    @@ -2489,7 +2718,7 @@ class nn extends C { ${this._renderHeader(t)}
    - ${Lt} + ${Rt} No quests today

    ${`Nothing was posted for today, ${t.name}. Enjoy the day's rest!`} @@ -2497,20 +2726,38 @@ class nn extends C {

    `; - case "complete-day": + case "complete-day": { + const e = this._completeStats(), n = this._countdown ?? this._completeSeconds(); return c` - ${this._renderHeader(t)} -
    -
    - ${R} - Quest complete -

    - ${`Every quest is claimed, ${t.name}. The seal is set for today.`} -

    -

    Returning to The Party shortly.

    +
    + +

    Quest complete

    +

    ${this._completeSub(t.name)}

    +

    + Returning to The Party in ${n} seconds +

    +
    +
    + ${e.claimed} + Quests claimed +
    +
    + ${e.onTime} + On time +
    +
    + ${e.late} + Late +
    +
    + ${e.party} + The Party +
    + ${this._renderDock()} `; + } default: return c` ${this._renderHeader(t)} @@ -2531,11 +2778,14 @@ class nn extends C { return { kind: "not-set-up" }; if (e === "stale") return { kind: "unreachable" }; - const n = this._childName(t) ?? X(t); + const n = this._childName(t) ?? Y(t); if (!this._childPresent(t)) return { kind: "away", name: n, returns: this._awayReturns(t) }; - const i = this._state(`sensor.nestquest_${t}_quests_due_today`); - return x(y(i?.state, 0)) === 0 ? { kind: "empty-day", name: n } : this._remaining() === 0 ? { kind: "complete-day", name: n } : { kind: "normal" }; + const s = this._state(`binary_sensor.nestquest_${t}_all_done`); + if (s && String(s.state ?? "").trim().toLowerCase() === "on") + return { kind: "complete-day", name: n }; + const r = this._state(`sensor.nestquest_${t}_quests_due_today`); + return v(y(r?.state, 0)) === 0 ? { kind: "empty-day", name: n } : this._remaining() === 0 ? { kind: "complete-day", name: n } : { kind: "normal" }; } /** "missing" when the due sensor does not exist (integration not * configured), "stale" when it answers unavailable/unknown (backend @@ -2552,19 +2802,19 @@ class nn extends C { const n = this._state(`sensor.nestquest_${t}_quests_due_today`)?.attributes?.present; if (typeof n == "boolean") return n; - const i = this._state( + const s = this._state( `binary_sensor.nestquest_${t}_present_today` ); - return i ? String(i.state ?? "").trim().toLowerCase() === "on" : !0; + return s ? String(s.state ?? "").trim().toLowerCase() === "on" : !0; } _awayReturns(t) { const e = this._timeZone(), n = this._state( `binary_sensor.nestquest_${t}_present_today` - ), i = Nt( + ), s = Tt( n?.attributes?.next_present, e ); - return i ? Et(i, e) : null; + return s ? L(s, e) : null; } _renderNotice(t) { return c` @@ -2578,17 +2828,17 @@ class nn extends C { `; } _timeZone() { - return Ue(this.hass); + return De(this.hass); } _state(t) { const n = this.hass?.states?.[t]; return n && typeof n == "object" ? n : null; } _childSlug() { - return Le(); + return Be(); } _boardPath() { - return k(this._config?.board_path).replace(/\/+$/, ""); + return $(this._config?.board_path).replace(/\/+$/, ""); } _idleSeconds() { const t = y(this._config?.idle_return_seconds, 40); @@ -2598,6 +2848,10 @@ class nn extends C { const t = y(this._config?.confirm_timeout_seconds, 15); return t > 0 ? t : 15; } + _completeSeconds() { + const t = y(this._config?.complete_screen_seconds, 12); + return t > 0 ? t : 12; + } _childName(t) { if (!t) return null; @@ -2607,8 +2861,8 @@ class nn extends C { _childCounts(t) { const e = this._state(`sensor.nestquest_${t}_quests_due_today`), n = this._state( `sensor.nestquest_${t}_quests_completed_today` - ), i = x(y(e?.state, 0)), r = x(y(n?.state, 0)); - return { due: i, completed: r }; + ), s = v(y(e?.state, 0)), r = v(y(n?.state, 0)); + return { due: s, completed: r }; } _instances() { const t = this._childSlug(); @@ -2617,21 +2871,21 @@ class nn extends C { const e = this._state(`sensor.nestquest_${t}_quests_due_today`)?.attributes?.instances; if (!Array.isArray(e)) return []; - const n = e.map(Be).filter((r) => r !== null); + const n = e.map(Fe).filter((r) => r !== null); if (this._optimistic.size === 0) return n; - let i = !1; + let s = !1; for (const r of n) { const o = this._optimistic.get(r.id); if (o !== void 0) { if (r.state === "completed") { - this._optimistic.delete(r.id), i = !0; + this._optimistic.delete(r.id), s = !0; continue; } r.state = "completed", r.overdue = !1, r.completed_at = o; } } - return i && (this._optimistic = new Map(this._optimistic)), n; + return s && (this._optimistic = new Map(this._optimistic)), n; } _remaining() { const t = this._childSlug(); @@ -2641,9 +2895,9 @@ class nn extends C { `sensor.nestquest_${t}_quests_remaining_today` ); if (e) - return x(y(e.state, 0)); - const { due: n, completed: i } = this._childCounts(t); - return x(n - i); + return v(y(e.state, 0)); + const { due: n, completed: s } = this._childCounts(t); + return v(n - s); } _partyCounts() { const t = this._state( @@ -2653,24 +2907,39 @@ class nn extends C { ); if (t || e) return { - completed: x(y(e?.state, 0)), - due: x(y(t?.state, 0)) + completed: v(y(e?.state, 0)), + due: v(y(t?.state, 0)) }; - const n = this._childSlug(), { due: i, completed: r } = this._childCounts(n); - return { completed: r, due: i }; + const n = this._childSlug(), { due: s, completed: r } = this._childCounts(n); + return { completed: r, due: s }; + } + _completeStats() { + const t = this._childCounts(this._childSlug()).completed, e = this._instances().filter( + (r) => r.on_time === !0 + ).length, n = v(t - e), s = this._partyCounts(); + return { + claimed: t, + onTime: e, + late: n, + party: `${s.completed}/${s.due}` + }; + } + _completeSub(t) { + const e = L(this._now, this._timeZone()), { claimed: n, onTime: s, late: r } = this._completeStats(); + return n === 0 ? `${t} sealed the day on ${e}.` : r === 0 ? `${t} claimed every quest on ${e} — all on time.` : s === 0 ? `${t} claimed every quest on ${e} — all late.` : `${t} claimed every quest on ${e} — ${s} on time, ${r} late.`; } _otherChildren() { const t = this.hass?.states; if (!t) return []; - const e = this._childSlug(), n = this._timeZone(), i = []; + const e = this._childSlug(), n = this._timeZone(), s = []; for (const r of Object.keys(t)) { const o = /^sensor\.nestquest_(.+)_quests_due_today$/.exec(r); if (!o || o[1] === "household" || o[1] === e) continue; const l = o[1], a = t[r]?.attributes ?? {}; let p = ""; - typeof a.child_name == "string" && a.child_name.trim() ? p = a.child_name.trim() : p = X(l); + typeof a.child_name == "string" && a.child_name.trim() ? p = a.child_name.trim() : p = Y(l); let u = !0; if (typeof a.present == "boolean") u = a.present; @@ -2682,18 +2951,18 @@ class nn extends C { } const d = this._state( `sensor.nestquest_${l}_quests_completed_today` - ), f = x(y(t[r]?.state, 0)), h = x(y(d?.state, 0)); + ), f = v(y(t[r]?.state, 0)), h = v(y(d?.state, 0)); let m = null; if (!u) { const _ = this._state( `binary_sensor.nestquest_${l}_present_today` - ), b = Nt( + ), x = Tt( _?.attributes?.next_present, n ); - m = b ? De(b, n) : null; + m = x ? Ue(x, n) : null; } - i.push({ + s.push({ slug: l, name: p, present: u, @@ -2702,10 +2971,10 @@ class nn extends C { returnsWeekday: m }); } - return i.sort((r, o) => r.name.localeCompare(o.name)); + return s.sort((r, o) => r.name.localeCompare(o.name)); } _renderHeader(t) { - const e = this._childSlug(), n = t.kind === "away", i = n ? t.name : this._childName(e) ?? (e ? X(e) : null), { due: r, completed: o } = this._childCounts(e), l = this._remaining(), a = this._partyCounts(), p = i ? `${i}'s Quest Log` : "Quest Log", u = (i?.charAt(0) || "?").toUpperCase(); + const e = this._childSlug(), n = t.kind === "away", s = n ? t.name : this._childName(e) ?? (e ? Y(e) : null), { due: r, completed: o } = this._childCounts(e), l = this._remaining(), a = this._partyCounts(), p = s ? `${s}'s Quest Log` : "Quest Log", u = (s?.charAt(0) || "?").toUpperCase(); return c`
    - ${i.length}/${e.length} + ${s.length}/${e.length}
    - ${Ct( + ${St( n, (l) => l.id, (l) => this._renderQuest(l, !1, 0) )} - ${Ct( - i, + ${St( + s, (l) => l.id, (l, a) => this._renderQuest(l, !0, a) )} @@ -2794,10 +3063,10 @@ class nn extends C { } _renderQuest(t, e, n) { if (e) { - const o = Mt[n % Mt.length], l = t.completed_at ? He(t.completed_at, this._timeZone()) : ""; + const o = Mt[n % Mt.length], l = t.completed_at ? je(t.completed_at, this._timeZone()) : ""; return c`
    - +
    ${t.title} @@ -2814,11 +3083,11 @@ class nn extends C {
    `; } - const i = je(t.due_time), r = t.overdue ? { - text: i ? `Overdue · due ${i}` : "Overdue", + const s = Le(t.due_time), r = t.overdue ? { + text: s ? `Overdue · due ${s}` : "Overdue", late: !0 } : { - text: i ? `Due by ${i}` : "Due today", + text: s ? `Due by ${s}` : "Due today", late: !1 }; return c` @@ -2833,7 +3102,7 @@ class nn extends C { (o.key === "Enter" || o.key === " ") && (o.preventDefault(), this._openConfirm(t.id)); }} > - +
    ${t.title} ${r.text} @@ -2879,7 +3148,7 @@ class nn extends C { @click=${(n) => n.stopPropagation()} >
    - +
    ${e?.name ?? "Quest"} @@ -2897,7 +3166,7 @@ class nn extends C { type="button" @click=${() => this._closeConfirm()} > - ${We} + ${Je} Not yet
    `; - case "complete-day": + case "complete-day": { + const stats = this._completeStats(); + const remaining = this._countdown ?? this._completeSeconds(); return html` - ${this._renderHeader(view)} -
    -
    - ${ICON_CHECK} - Quest complete -

    - ${`Every quest is claimed, ${view.name}. The seal is set for today.`} -

    -

    Returning to The Party shortly.

    +
    + +

    Quest complete

    +

    ${this._completeSub(view.name)}

    +

    + Returning to The Party in ${remaining} seconds +

    +
    +
    + ${stats.claimed} + Quests claimed +
    +
    + ${stats.onTime} + On time +
    +
    + ${stats.late} + Late +
    +
    + ${stats.party} + The Party +
    + ${this._renderDock()} `; + } default: return html` ${this._renderHeader(view)} @@ -1248,6 +1533,10 @@ export class NestQuestQuestLogCard extends LitElement { if (!this._childPresent(slug)) { return { kind: "away", name, returns: this._awayReturns(slug) }; } + const allDone = this._state(`binary_sensor.nestquest_${slug}_all_done`); + if (allDone && String(allDone.state ?? "").trim().toLowerCase() === "on") { + return { kind: "complete-day", name }; + } const dueSensor = this._state(`sensor.nestquest_${slug}_quests_due_today`); const due = clampCount(asNumber(dueSensor?.state, 0)); if (due === 0) { @@ -1342,6 +1631,11 @@ export class NestQuestQuestLogCard extends LitElement { return seconds > 0 ? seconds : 15; } + private _completeSeconds(): number { + const seconds = asNumber(this._config?.complete_screen_seconds, 12); + return seconds > 0 ? seconds : 12; + } + private _childName(slug: string): string | null { if (!slug) { return null; @@ -1435,6 +1729,41 @@ export class NestQuestQuestLogCard extends LitElement { return { completed, due }; } + private _completeStats(): { + claimed: number; + onTime: number; + late: number; + party: string; + } { + const claimed = this._childCounts(this._childSlug()).completed; + const onTime = this._instances().filter( + (instance) => instance.on_time === true + ).length; + const late = clampCount(claimed - onTime); + const party = this._partyCounts(); + return { + claimed, + onTime, + late, + party: `${party.completed}/${party.due}`, + }; + } + + private _completeSub(name: string): string { + const date = formatDay(this._now, this._timeZone()); + const { claimed, onTime, late } = this._completeStats(); + if (claimed === 0) { + return `${name} sealed the day on ${date}.`; + } + if (late === 0) { + return `${name} claimed every quest on ${date} — all on time.`; + } + if (onTime === 0) { + return `${name} claimed every quest on ${date} — all late.`; + } + return `${name} claimed every quest on ${date} — ${onTime} on time, ${late} late.`; + } + private _otherChildren(): OtherChild[] { const states = this.hass?.states as | Record @@ -1897,6 +2226,112 @@ export class NestQuestQuestLogCard extends LitElement { } } + private _armCompleteTimer(): void { + if (this._completeTimer !== undefined) { + return; + } + if (this._countdown === null) { + this._countdown = this._completeSeconds(); + } + if (this._countdown <= 0) { + this._returnToBoard(); + return; + } + this._completeTimer = window.setInterval(() => { + const remaining = (this._countdown ?? 1) - 1; + if (remaining <= 0) { + this._countdown = 0; + this._clearCompleteTimer(); + this._returnToBoard(); + return; + } + this._countdown = remaining; + }, 1000); + } + + private _clearCompleteTimer(): void { + if (this._completeTimer !== undefined) { + window.clearInterval(this._completeTimer); + this._completeTimer = undefined; + } + } + + private _optionalNumber(value: unknown): number | null { + if (value === null || value === undefined || value === "") { + return null; + } + const parsed = typeof value === "number" ? value : Number(value); + return Number.isFinite(parsed) ? parsed : null; + } + + private _dockWeather(): DockWeather | null { + const entityId = asString(this._config?.weather_entity); + if (!entityId) { + return null; + } + const stateObj = this._state(entityId); + if (!stateObj) { + return null; + } + const condition = String(stateObj.state ?? "").trim(); + if (!condition || condition === "unavailable" || condition === "unknown") { + return null; + } + const attrs = stateObj.attributes ?? {}; + let high: number | null = null; + let low: number | null = null; + const forecast = attrs.forecast; + if (Array.isArray(forecast) && forecast.length > 0) { + const entry = forecast[0]; + if (entry && typeof entry === "object") { + const typed = entry as Record; + high = this._optionalNumber(typed.temperature); + low = this._optionalNumber(typed.templow); + } + } + return { + condition, + temperature: this._optionalNumber(attrs.temperature), + high, + low, + }; + } + + private _renderDock() { + const weather = this._dockWeather(); + if (!weather) { + return html` +
    + ${formatDay(this._now, this._timeZone())} + + ${formatTime(this._now, this._timeZone())} +
    + `; + } + const label = conditionLabel(weather.condition); + const phrase = conditionPhrase(weather.condition, label); + const temperature = + weather.temperature === null + ? nothing + : html`${Math.round(weather.temperature)}°`; + const hiLo = + weather.high === null || weather.low === null + ? null + : `${Math.round(weather.high)}° / ${Math.round(weather.low)}°`; + return html` +
    + ${ICON_CLOUD_MOON} + ${temperature} + + + ${hiLo === null ? label : `${label} · ${hiLo}`} + + + ${phrase} +
    + `; + } + private _returnToBoard(): void { const path = this._boardPath(); if (!path) { @@ -1914,7 +2349,9 @@ export class NestQuestQuestLogCard extends LitElement { } private _onActivity = (): void => { - this._armIdle(); + if (this._logView().kind !== "complete-day") { + this._armIdle(); + } if (this._confirm !== null) { this._armConfirmTimer(); } From 030ba83b14a243daea9dec7ca4b4ef3597dee455 Mon Sep 17 00:00:00 2001 From: dev_agent <17+dev_agent@code.cubecraftcreations.com> Date: Sun, 20 Sep 2026 19:17:52 -0400 Subject: [PATCH 08/11] Subscribe panel cards to live NestQuest events (#130) --- .../nestquest/www/nestquest-cards.js | 981 ++++++++++-------- frontend/src/cards/party-board.ts | 100 ++ frontend/src/cards/quest-log.ts | 97 ++ 3 files changed, 735 insertions(+), 443 deletions(-) diff --git a/custom_components/nestquest/www/nestquest-cards.js b/custom_components/nestquest/www/nestquest-cards.js index a6aa494..ede7aab 100644 --- a/custom_components/nestquest/www/nestquest-cards.js +++ b/custom_components/nestquest/www/nestquest-cards.js @@ -17,55 +17,55 @@ let zt = class { return this.cssText; } }; -const b = (i) => new zt(typeof i == "string" ? i : i + "", void 0, it), Pt = (i, ...t) => { - const e = i.length === 1 ? i[0] : t.reduce((n, s, r) => n + ((o) => { +const v = (s) => new zt(typeof s == "string" ? s : s + "", void 0, it), Pt = (s, ...t) => { + const e = s.length === 1 ? s[0] : t.reduce((n, i, r) => n + ((o) => { if (o._$cssResult$ === !0) return o.cssText; if (typeof o == "number") return o; throw Error("Value passed to 'css' function must be a 'css' function result: " + o + ". Use 'unsafeCSS' to pass non-literal values, but take care to ensure page security."); - })(s) + i[r + 1], i[0]); - return new zt(e, i, it); -}, Bt = (i, t) => { - if (nt) i.adoptedStyleSheets = t.map((e) => e instanceof CSSStyleSheet ? e : e.styleSheet); + })(i) + s[r + 1], s[0]); + return new zt(e, s, it); +}, Bt = (s, t) => { + if (nt) s.adoptedStyleSheets = t.map((e) => e instanceof CSSStyleSheet ? e : e.styleSheet); else for (const e of t) { - const n = document.createElement("style"), s = B.litNonce; - s !== void 0 && n.setAttribute("nonce", s), n.textContent = e.cssText, i.appendChild(n); + const n = document.createElement("style"), i = B.litNonce; + i !== void 0 && n.setAttribute("nonce", i), n.textContent = e.cssText, s.appendChild(n); } -}, dt = nt ? (i) => i : (i) => i instanceof CSSStyleSheet ? ((t) => { +}, ct = nt ? (s) => s : (s) => s instanceof CSSStyleSheet ? ((t) => { let e = ""; for (const n of t.cssRules) e += n.cssText; - return b(e); -})(i) : i; -const { is: Ft, defineProperty: Qt, getOwnPropertyDescriptor: Wt, getOwnPropertyNames: Zt, getOwnPropertySymbols: Vt, getPrototypeOf: Gt } = Object, W = globalThis, ct = W.trustedTypes, Kt = ct ? ct.emptyScript : "", Jt = W.reactiveElementPolyfillSupport, I = (i, t) => i, et = { toAttribute(i, t) { + return v(e); +})(s) : s; +const { is: Ft, defineProperty: Qt, getOwnPropertyDescriptor: Wt, getOwnPropertyNames: Zt, getOwnPropertySymbols: Gt, getPrototypeOf: Vt } = Object, W = globalThis, dt = W.trustedTypes, Kt = dt ? dt.emptyScript : "", Jt = W.reactiveElementPolyfillSupport, I = (s, t) => s, et = { toAttribute(s, t) { switch (t) { case Boolean: - i = i ? Kt : null; + s = s ? Kt : null; break; case Object: case Array: - i = i == null ? i : JSON.stringify(i); + s = s == null ? s : JSON.stringify(s); } - return i; -}, fromAttribute(i, t) { - let e = i; + return s; +}, fromAttribute(s, t) { + let e = s; switch (t) { case Boolean: - e = i !== null; + e = s !== null; break; case Number: - e = i === null ? null : Number(i); + e = s === null ? null : Number(s); break; case Object: case Array: try { - e = JSON.parse(i); + e = JSON.parse(s); } catch { e = null; } } return e; -} }, It = (i, t) => !Ft(i, t), pt = { attribute: !0, type: String, converter: et, reflect: !1, useDefault: !1, hasChanged: It }; +} }, It = (s, t) => !Ft(s, t), pt = { attribute: !0, type: String, converter: et, reflect: !1, useDefault: !1, hasChanged: It }; Symbol.metadata ??= /* @__PURE__ */ Symbol("metadata"), W.litPropertyMetadata ??= /* @__PURE__ */ new WeakMap(); -let T = class extends HTMLElement { +let E = class extends HTMLElement { static addInitializer(t) { this._$Ei(), (this.l ??= []).push(t); } @@ -74,18 +74,18 @@ let T = class extends HTMLElement { } static createProperty(t, e = pt) { if (e.state && (e.attribute = !1), this._$Ei(), this.prototype.hasOwnProperty(t) && ((e = Object.create(e)).wrapped = !0), this.elementProperties.set(t, e), !e.noAccessor) { - const n = /* @__PURE__ */ Symbol(), s = this.getPropertyDescriptor(t, n, e); - s !== void 0 && Qt(this.prototype, t, s); + const n = /* @__PURE__ */ Symbol(), i = this.getPropertyDescriptor(t, n, e); + i !== void 0 && Qt(this.prototype, t, i); } } static getPropertyDescriptor(t, e, n) { - const { get: s, set: r } = Wt(this.prototype, t) ?? { get() { + const { get: i, set: r } = Wt(this.prototype, t) ?? { get() { return this[e]; }, set(o) { this[e] = o; } }; - return { get: s, set(o) { - const l = s?.call(this); + return { get: i, set(o) { + const l = i?.call(this); r?.call(this, o), this.requestUpdate(t, l, n); }, configurable: !0, enumerable: !0 }; } @@ -94,24 +94,24 @@ let T = class extends HTMLElement { } static _$Ei() { if (this.hasOwnProperty(I("elementProperties"))) return; - const t = Gt(this); + const t = Vt(this); t.finalize(), t.l !== void 0 && (this.l = [...t.l]), this.elementProperties = new Map(t.elementProperties); } static finalize() { if (this.hasOwnProperty(I("finalized"))) return; if (this.finalized = !0, this._$Ei(), this.hasOwnProperty(I("properties"))) { - const e = this.properties, n = [...Zt(e), ...Vt(e)]; - for (const s of n) this.createProperty(s, e[s]); + const e = this.properties, n = [...Zt(e), ...Gt(e)]; + for (const i of n) this.createProperty(i, e[i]); } const t = this[Symbol.metadata]; if (t !== null) { const e = litPropertyMetadata.get(t); - if (e !== void 0) for (const [n, s] of e) this.elementProperties.set(n, s); + if (e !== void 0) for (const [n, i] of e) this.elementProperties.set(n, i); } this._$Eh = /* @__PURE__ */ new Map(); for (const [e, n] of this.elementProperties) { - const s = this._$Eu(e, n); - s !== void 0 && this._$Eh.set(s, e); + const i = this._$Eu(e, n); + i !== void 0 && this._$Eh.set(i, e); } this.elementStyles = this.finalizeStyles(this.styles); } @@ -119,8 +119,8 @@ let T = class extends HTMLElement { const e = []; if (Array.isArray(t)) { const n = new Set(t.flat(1 / 0).reverse()); - for (const s of n) e.unshift(dt(s)); - } else t !== void 0 && e.push(dt(t)); + for (const i of n) e.unshift(ct(i)); + } else t !== void 0 && e.push(ct(t)); return e; } static _$Eu(t, e) { @@ -160,31 +160,31 @@ let T = class extends HTMLElement { this._$AK(t, n); } _$ET(t, e) { - const n = this.constructor.elementProperties.get(t), s = this.constructor._$Eu(t, n); - if (s !== void 0 && n.reflect === !0) { + const n = this.constructor.elementProperties.get(t), i = this.constructor._$Eu(t, n); + if (i !== void 0 && n.reflect === !0) { const r = (n.converter?.toAttribute !== void 0 ? n.converter : et).toAttribute(e, n.type); - this._$Em = t, r == null ? this.removeAttribute(s) : this.setAttribute(s, r), this._$Em = null; + this._$Em = t, r == null ? this.removeAttribute(i) : this.setAttribute(i, r), this._$Em = null; } } _$AK(t, e) { - const n = this.constructor, s = n._$Eh.get(t); - if (s !== void 0 && this._$Em !== s) { - const r = n.getPropertyOptions(s), o = typeof r.converter == "function" ? { fromAttribute: r.converter } : r.converter?.fromAttribute !== void 0 ? r.converter : et; - this._$Em = s; + const n = this.constructor, i = n._$Eh.get(t); + if (i !== void 0 && this._$Em !== i) { + const r = n.getPropertyOptions(i), o = typeof r.converter == "function" ? { fromAttribute: r.converter } : r.converter?.fromAttribute !== void 0 ? r.converter : et; + this._$Em = i; const l = o.fromAttribute(e, r.type); - this[s] = l ?? this._$Ej?.get(s) ?? l, this._$Em = null; + this[i] = l ?? this._$Ej?.get(i) ?? l, this._$Em = null; } } - requestUpdate(t, e, n, s = !1, r) { + requestUpdate(t, e, n, i = !1, r) { if (t !== void 0) { const o = this.constructor; - if (s === !1 && (r = this[t]), n ??= o.getPropertyOptions(t), !((n.hasChanged ?? It)(r, e) || n.useDefault && n.reflect && r === this._$Ej?.get(t) && !this.hasAttribute(o._$Eu(t, n)))) return; + if (i === !1 && (r = this[t]), n ??= o.getPropertyOptions(t), !((n.hasChanged ?? It)(r, e) || n.useDefault && n.reflect && r === this._$Ej?.get(t) && !this.hasAttribute(o._$Eu(t, n)))) return; this.C(t, e, n); } this.isUpdatePending === !1 && (this._$ES = this._$EP()); } - C(t, e, { useDefault: n, reflect: s, wrapped: r }, o) { - n && !(this._$Ej ??= /* @__PURE__ */ new Map()).has(t) && (this._$Ej.set(t, o ?? e ?? this[t]), r !== !0 || o !== void 0) || (this._$AL.has(t) || (this.hasUpdated || n || (e = void 0), this._$AL.set(t, e)), s === !0 && this._$Em !== t && (this._$Eq ??= /* @__PURE__ */ new Set()).add(t)); + C(t, e, { useDefault: n, reflect: i, wrapped: r }, o) { + n && !(this._$Ej ??= /* @__PURE__ */ new Map()).has(t) && (this._$Ej.set(t, o ?? e ?? this[t]), r !== !0 || o !== void 0) || (this._$AL.has(t) || (this.hasUpdated || n || (e = void 0), this._$AL.set(t, e)), i === !0 && this._$Em !== t && (this._$Eq ??= /* @__PURE__ */ new Set()).add(t)); } async _$EP() { this.isUpdatePending = !0; @@ -203,13 +203,13 @@ let T = class extends HTMLElement { if (!this.isUpdatePending) return; if (!this.hasUpdated) { if (this.renderRoot ??= this.createRenderRoot(), this._$Ep) { - for (const [s, r] of this._$Ep) this[s] = r; + for (const [i, r] of this._$Ep) this[i] = r; this._$Ep = void 0; } const n = this.constructor.elementProperties; - if (n.size > 0) for (const [s, r] of n) { - const { wrapped: o } = r, l = this[s]; - o !== !0 || this._$AL.has(s) || l === void 0 || this.C(s, void 0, r, l); + if (n.size > 0) for (const [i, r] of n) { + const { wrapped: o } = r, l = this[i]; + o !== !0 || this._$AL.has(i) || l === void 0 || this.C(i, void 0, r, l); } } let t = !1; @@ -246,54 +246,54 @@ let T = class extends HTMLElement { firstUpdated(t) { } }; -T.elementStyles = [], T.shadowRootOptions = { mode: "open" }, T[I("elementProperties")] = /* @__PURE__ */ new Map(), T[I("finalized")] = /* @__PURE__ */ new Map(), Jt?.({ ReactiveElement: T }), (W.reactiveElementVersions ??= []).push("2.1.2"); -const st = globalThis, ht = (i) => i, F = st.trustedTypes, ut = F ? F.createPolicy("lit-html", { createHTML: (i) => i }) : void 0, Dt = "$lit$", w = `lit$${Math.random().toFixed(9).slice(2)}$`, Ut = "?" + w, Xt = `<${Ut}>`, S = document, D = () => S.createComment(""), U = (i) => i === null || typeof i != "object" && typeof i != "function", rt = Array.isArray, Yt = (i) => rt(i) || typeof i?.[Symbol.iterator] == "function", G = `[ -\f\r]`, z = /<(?:(!--|\/[^a-zA-Z])|(\/?[a-zA-Z][^>\s]*)|(\/?$))/g, ft = /-->/g, mt = />/g, q = RegExp(`>|${G}(?:([^\\s"'>=/]+)(${G}*=${G}*(?:[^ -\f\r"'\`<>=]|("|')|))|$)`, "g"), gt = /'/g, _t = /"/g, jt = /^(?:script|style|textarea|title)$/i, te = (i) => (t, ...e) => ({ _$litType$: i, strings: t, values: e }), c = te(1), N = /* @__PURE__ */ Symbol.for("lit-noChange"), g = /* @__PURE__ */ Symbol.for("lit-nothing"), yt = /* @__PURE__ */ new WeakMap(), C = S.createTreeWalker(S, 129); -function Ht(i, t) { - if (!rt(i) || !i.hasOwnProperty("raw")) throw Error("invalid template strings array"); +E.elementStyles = [], E.shadowRootOptions = { mode: "open" }, E[I("elementProperties")] = /* @__PURE__ */ new Map(), E[I("finalized")] = /* @__PURE__ */ new Map(), Jt?.({ ReactiveElement: E }), (W.reactiveElementVersions ??= []).push("2.1.2"); +const st = globalThis, ht = (s) => s, F = st.trustedTypes, ut = F ? F.createPolicy("lit-html", { createHTML: (s) => s }) : void 0, Lt = "$lit$", $ = `lit$${Math.random().toFixed(9).slice(2)}$`, Ut = "?" + $, Xt = `<${Ut}>`, S = document, L = () => S.createComment(""), U = (s) => s === null || typeof s != "object" && typeof s != "function", rt = Array.isArray, Yt = (s) => rt(s) || typeof s?.[Symbol.iterator] == "function", V = `[ +\f\r]`, z = /<(?:(!--|\/[^a-zA-Z])|(\/?[a-zA-Z][^>\s]*)|(\/?$))/g, ft = /-->/g, mt = />/g, q = RegExp(`>|${V}(?:([^\\s"'>=/]+)(${V}*=${V}*(?:[^ +\f\r"'\`<>=]|("|')|))|$)`, "g"), gt = /'/g, _t = /"/g, Rt = /^(?:script|style|textarea|title)$/i, te = (s) => (t, ...e) => ({ _$litType$: s, strings: t, values: e }), d = te(1), T = /* @__PURE__ */ Symbol.for("lit-noChange"), g = /* @__PURE__ */ Symbol.for("lit-nothing"), yt = /* @__PURE__ */ new WeakMap(), C = S.createTreeWalker(S, 129); +function Dt(s, t) { + if (!rt(s) || !s.hasOwnProperty("raw")) throw Error("invalid template strings array"); return ut !== void 0 ? ut.createHTML(t) : t; } -const ee = (i, t) => { - const e = i.length - 1, n = []; - let s, r = t === 2 ? "" : t === 3 ? "" : "", o = z; +const ee = (s, t) => { + const e = s.length - 1, n = []; + let i, r = t === 2 ? "" : t === 3 ? "" : "", o = z; for (let l = 0; l < e; l++) { - const a = i[l]; - let p, u, d = -1, f = 0; - for (; f < a.length && (o.lastIndex = f, u = o.exec(a), u !== null); ) f = o.lastIndex, o === z ? u[1] === "!--" ? o = ft : u[1] !== void 0 ? o = mt : u[2] !== void 0 ? (jt.test(u[2]) && (s = RegExp("" ? (o = s ?? z, d = -1) : u[1] === void 0 ? d = -2 : (d = o.lastIndex - u[2].length, p = u[1], o = u[3] === void 0 ? q : u[3] === '"' ? _t : gt) : o === _t || o === gt ? o = q : o === ft || o === mt ? o = z : (o = q, s = void 0); - const h = o === q && i[l + 1].startsWith("/>") ? " " : ""; - r += o === z ? a + Xt : d >= 0 ? (n.push(p), a.slice(0, d) + Dt + a.slice(d) + w + h) : a + w + (d === -2 ? l : h); + const a = s[l]; + let p, u, c = -1, f = 0; + for (; f < a.length && (o.lastIndex = f, u = o.exec(a), u !== null); ) f = o.lastIndex, o === z ? u[1] === "!--" ? o = ft : u[1] !== void 0 ? o = mt : u[2] !== void 0 ? (Rt.test(u[2]) && (i = RegExp("" ? (o = i ?? z, c = -1) : u[1] === void 0 ? c = -2 : (c = o.lastIndex - u[2].length, p = u[1], o = u[3] === void 0 ? q : u[3] === '"' ? _t : gt) : o === _t || o === gt ? o = q : o === ft || o === mt ? o = z : (o = q, i = void 0); + const h = o === q && s[l + 1].startsWith("/>") ? " " : ""; + r += o === z ? a + Xt : c >= 0 ? (n.push(p), a.slice(0, c) + Lt + a.slice(c) + $ + h) : a + $ + (c === -2 ? l : h); } - return [Ht(i, r + (i[e] || "") + (t === 2 ? "" : t === 3 ? "" : "")), n]; + return [Dt(s, r + (s[e] || "") + (t === 2 ? "" : t === 3 ? "" : "")), n]; }; -class j { +class R { constructor({ strings: t, _$litType$: e }, n) { - let s; + let i; this.parts = []; let r = 0, o = 0; const l = t.length - 1, a = this.parts, [p, u] = ee(t, e); - if (this.el = j.createElement(p, n), C.currentNode = this.el.content, e === 2 || e === 3) { - const d = this.el.content.firstChild; - d.replaceWith(...d.childNodes); + if (this.el = R.createElement(p, n), C.currentNode = this.el.content, e === 2 || e === 3) { + const c = this.el.content.firstChild; + c.replaceWith(...c.childNodes); } - for (; (s = C.nextNode()) !== null && a.length < l; ) { - if (s.nodeType === 1) { - if (s.hasAttributes()) for (const d of s.getAttributeNames()) if (d.endsWith(Dt)) { - const f = u[o++], h = s.getAttribute(d).split(w), m = /([.?@])?(.*)/.exec(f); - a.push({ type: 1, index: r, name: m[2], strings: h, ctor: m[1] === "." ? ie : m[1] === "?" ? se : m[1] === "@" ? re : Z }), s.removeAttribute(d); - } else d.startsWith(w) && (a.push({ type: 6, index: r }), s.removeAttribute(d)); - if (jt.test(s.tagName)) { - const d = s.textContent.split(w), f = d.length - 1; + for (; (i = C.nextNode()) !== null && a.length < l; ) { + if (i.nodeType === 1) { + if (i.hasAttributes()) for (const c of i.getAttributeNames()) if (c.endsWith(Lt)) { + const f = u[o++], h = i.getAttribute(c).split($), m = /([.?@])?(.*)/.exec(f); + a.push({ type: 1, index: r, name: m[2], strings: h, ctor: m[1] === "." ? ie : m[1] === "?" ? se : m[1] === "@" ? re : Z }), i.removeAttribute(c); + } else c.startsWith($) && (a.push({ type: 6, index: r }), i.removeAttribute(c)); + if (Rt.test(i.tagName)) { + const c = i.textContent.split($), f = c.length - 1; if (f > 0) { - s.textContent = F ? F.emptyScript : ""; - for (let h = 0; h < f; h++) s.append(d[h], D()), C.nextNode(), a.push({ type: 2, index: ++r }); - s.append(d[f], D()); + i.textContent = F ? F.emptyScript : ""; + for (let h = 0; h < f; h++) i.append(c[h], L()), C.nextNode(), a.push({ type: 2, index: ++r }); + i.append(c[f], L()); } } - } else if (s.nodeType === 8) if (s.data === Ut) a.push({ type: 2, index: r }); + } else if (i.nodeType === 8) if (i.data === Ut) a.push({ type: 2, index: r }); else { - let d = -1; - for (; (d = s.data.indexOf(w, d + 1)) !== -1; ) a.push({ type: 7, index: r }), d += w.length - 1; + let c = -1; + for (; (c = i.data.indexOf($, c + 1)) !== -1; ) a.push({ type: 7, index: r }), c += $.length - 1; } r++; } @@ -303,11 +303,11 @@ class j { return n.innerHTML = t, n; } } -function E(i, t, e = i, n) { - if (t === N) return t; - let s = n !== void 0 ? e._$Co?.[n] : e._$Cl; +function N(s, t, e = s, n) { + if (t === T) return t; + let i = n !== void 0 ? e._$Co?.[n] : e._$Cl; const r = U(t) ? void 0 : t._$litDirective$; - return s?.constructor !== r && (s?._$AO?.(!1), r === void 0 ? s = void 0 : (s = new r(i), s._$AT(i, e, n)), n !== void 0 ? (e._$Co ??= [])[n] = s : e._$Cl = s), s !== void 0 && (t = E(i, s._$AS(i, t.values), s, n)), t; + return i?.constructor !== r && (i?._$AO?.(!1), r === void 0 ? i = void 0 : (i = new r(s), i._$AT(s, e, n)), n !== void 0 ? (e._$Co ??= [])[n] = i : e._$Cl = i), i !== void 0 && (t = N(s, i._$AS(s, t.values), i, n)), t; } class ne { constructor(t, e) { @@ -320,8 +320,8 @@ class ne { return this._$AM._$AU; } u(t) { - const { el: { content: e }, parts: n } = this._$AD, s = (t?.creationScope ?? S).importNode(e, !0); - C.currentNode = s; + const { el: { content: e }, parts: n } = this._$AD, i = (t?.creationScope ?? S).importNode(e, !0); + C.currentNode = i; let r = C.nextNode(), o = 0, l = 0, a = n[0]; for (; a !== void 0; ) { if (o === a.index) { @@ -330,7 +330,7 @@ class ne { } o !== a?.index && (r = C.nextNode(), o++); } - return C.currentNode = S, s; + return C.currentNode = S, i; } p(t) { let e = 0; @@ -341,8 +341,8 @@ class O { get _$AU() { return this._$AM?._$AU ?? this._$Cv; } - constructor(t, e, n, s) { - this.type = 2, this._$AH = g, this._$AN = void 0, this._$AA = t, this._$AB = e, this._$AM = n, this.options = s, this._$Cv = s?.isConnected ?? !0; + constructor(t, e, n, i) { + this.type = 2, this._$AH = g, this._$AN = void 0, this._$AA = t, this._$AB = e, this._$AM = n, this.options = i, this._$Cv = i?.isConnected ?? !0; } get parentNode() { let t = this._$AA.parentNode; @@ -356,7 +356,7 @@ class O { return this._$AB; } _$AI(t, e = this) { - t = E(this, t, e), U(t) ? t === g || t == null || t === "" ? (this._$AH !== g && this._$AR(), this._$AH = g) : t !== this._$AH && t !== N && this._(t) : t._$litType$ !== void 0 ? this.$(t) : t.nodeType !== void 0 ? this.T(t) : Yt(t) ? this.k(t) : this._(t); + t = N(this, t, e), U(t) ? t === g || t == null || t === "" ? (this._$AH !== g && this._$AR(), this._$AH = g) : t !== this._$AH && t !== T && this._(t) : t._$litType$ !== void 0 ? this.$(t) : t.nodeType !== void 0 ? this.T(t) : Yt(t) ? this.k(t) : this._(t); } O(t) { return this._$AA.parentNode.insertBefore(t, this._$AB); @@ -368,23 +368,23 @@ class O { this._$AH !== g && U(this._$AH) ? this._$AA.nextSibling.data = t : this.T(S.createTextNode(t)), this._$AH = t; } $(t) { - const { values: e, _$litType$: n } = t, s = typeof n == "number" ? this._$AC(t) : (n.el === void 0 && (n.el = j.createElement(Ht(n.h, n.h[0]), this.options)), n); - if (this._$AH?._$AD === s) this._$AH.p(e); + const { values: e, _$litType$: n } = t, i = typeof n == "number" ? this._$AC(t) : (n.el === void 0 && (n.el = R.createElement(Dt(n.h, n.h[0]), this.options)), n); + if (this._$AH?._$AD === i) this._$AH.p(e); else { - const r = new ne(s, this), o = r.u(this.options); + const r = new ne(i, this), o = r.u(this.options); r.p(e), this.T(o), this._$AH = r; } } _$AC(t) { let e = yt.get(t.strings); - return e === void 0 && yt.set(t.strings, e = new j(t)), e; + return e === void 0 && yt.set(t.strings, e = new R(t)), e; } k(t) { rt(this._$AH) || (this._$AH = [], this._$AR()); const e = this._$AH; - let n, s = 0; - for (const r of t) s === e.length ? e.push(n = new O(this.O(D()), this.O(D()), this, this.options)) : n = e[s], n._$AI(r), s++; - s < e.length && (this._$AR(n && n._$AB.nextSibling, s), e.length = s); + let n, i = 0; + for (const r of t) i === e.length ? e.push(n = new O(this.O(L()), this.O(L()), this, this.options)) : n = e[i], n._$AI(r), i++; + i < e.length && (this._$AR(n && n._$AB.nextSibling, i), e.length = i); } _$AR(t = this._$AA.nextSibling, e) { for (this._$AP?.(!1, !0, e); t !== this._$AB; ) { @@ -403,19 +403,19 @@ class Z { get _$AU() { return this._$AM._$AU; } - constructor(t, e, n, s, r) { - this.type = 1, this._$AH = g, this._$AN = void 0, this.element = t, this.name = e, this._$AM = s, this.options = r, n.length > 2 || n[0] !== "" || n[1] !== "" ? (this._$AH = Array(n.length - 1).fill(new String()), this.strings = n) : this._$AH = g; + constructor(t, e, n, i, r) { + this.type = 1, this._$AH = g, this._$AN = void 0, this.element = t, this.name = e, this._$AM = i, this.options = r, n.length > 2 || n[0] !== "" || n[1] !== "" ? (this._$AH = Array(n.length - 1).fill(new String()), this.strings = n) : this._$AH = g; } - _$AI(t, e = this, n, s) { + _$AI(t, e = this, n, i) { const r = this.strings; let o = !1; - if (r === void 0) t = E(this, t, e, 0), o = !U(t) || t !== this._$AH && t !== N, o && (this._$AH = t); + if (r === void 0) t = N(this, t, e, 0), o = !U(t) || t !== this._$AH && t !== T, o && (this._$AH = t); else { const l = t; let a, p; - for (t = r[0], a = 0; a < r.length - 1; a++) p = E(this, l[n + a], e, a), p === N && (p = this._$AH[a]), o ||= !U(p) || p !== this._$AH[a], p === g ? t = g : t !== g && (t += (p ?? "") + r[a + 1]), this._$AH[a] = p; + for (t = r[0], a = 0; a < r.length - 1; a++) p = N(this, l[n + a], e, a), p === T && (p = this._$AH[a]), o ||= !U(p) || p !== this._$AH[a], p === g ? t = g : t !== g && (t += (p ?? "") + r[a + 1]), this._$AH[a] = p; } - o && !s && this.j(t); + o && !i && this.j(t); } j(t) { t === g ? this.element.removeAttribute(this.name) : this.element.setAttribute(this.name, t ?? ""); @@ -438,13 +438,13 @@ class se extends Z { } } class re extends Z { - constructor(t, e, n, s, r) { - super(t, e, n, s, r), this.type = 5; + constructor(t, e, n, i, r) { + super(t, e, n, i, r), this.type = 5; } _$AI(t, e = this) { - if ((t = E(this, t, e, 0) ?? g) === N) return; - const n = this._$AH, s = t === g && n !== g || t.capture !== n.capture || t.once !== n.once || t.passive !== n.passive, r = t !== g && (n === g || s); - s && this.element.removeEventListener(this.name, this, n), r && this.element.addEventListener(this.name, this, t), this._$AH = t; + if ((t = N(this, t, e, 0) ?? g) === T) return; + const n = this._$AH, i = t === g && n !== g || t.capture !== n.capture || t.once !== n.once || t.passive !== n.passive, r = t !== g && (n === g || i); + i && this.element.removeEventListener(this.name, this, n), r && this.element.addEventListener(this.name, this, t), this._$AH = t; } handleEvent(t) { typeof this._$AH == "function" ? this._$AH.call(this.options?.host ?? this.element, t) : this._$AH.handleEvent(t); @@ -458,22 +458,22 @@ class oe { return this._$AM._$AU; } _$AI(t) { - E(this, t); + N(this, t); } } const ae = { I: O }, le = st.litHtmlPolyfillSupport; -le?.(j, O), (st.litHtmlVersions ??= []).push("3.3.3"); -const de = (i, t, e) => { +le?.(R, O), (st.litHtmlVersions ??= []).push("3.3.3"); +const ce = (s, t, e) => { const n = e?.renderBefore ?? t; - let s = n._$litPart$; - if (s === void 0) { + let i = n._$litPart$; + if (i === void 0) { const r = e?.renderBefore ?? null; - n._$litPart$ = s = new O(t.insertBefore(D(), r), r, void 0, e ?? {}); + n._$litPart$ = i = new O(t.insertBefore(L(), r), r, void 0, e ?? {}); } - return s._$AI(i), s; + return i._$AI(s), i; }; const ot = globalThis; -let A = class extends T { +let A = class extends E { constructor() { super(...arguments), this.renderOptions = { host: this }, this._$Do = void 0; } @@ -483,7 +483,7 @@ let A = class extends T { } update(t) { const e = this.render(); - this.hasUpdated || (this.renderOptions.isConnected = this.isConnected), super.update(t), this._$Do = de(e, this.renderRoot, this.renderOptions); + this.hasUpdated || (this.renderOptions.isConnected = this.isConnected), super.update(t), this._$Do = ce(e, this.renderRoot, this.renderOptions); } connectedCallback() { super.connectedCallback(), this._$Do?.setConnected(!0); @@ -492,51 +492,56 @@ let A = class extends T { super.disconnectedCallback(), this._$Do?.setConnected(!1); } render() { - return N; + return T; } }; A._$litElement$ = !0, A.finalized = !0, ot.litElementHydrateSupport?.({ LitElement: A }); -const ce = ot.litElementPolyfillSupport; -ce?.({ LitElement: A }); +const de = ot.litElementPolyfillSupport; +de?.({ LitElement: A }); (ot.litElementVersions ??= []).push("4.2.2"); -const Lt = ':host{--nq-p-parchment-top: #f0e4c7;--nq-p-parchment-bottom: #ddcca3;--nq-p-parchment: radial-gradient(ellipse at 25% 10%, rgba(255,255,255,.5), transparent 55%), radial-gradient(ellipse at 85% 90%, rgba(120,86,44,.28), transparent 60%), repeating-linear-gradient(93deg, rgba(150,115,70,.05) 0 2px, transparent 2px 6px), repeating-linear-gradient(2deg, rgba(150,115,70,.04) 0 3px, transparent 3px 7px), linear-gradient(var(--nq-p-parchment-top), var(--nq-p-parchment-bottom));--nq-p-vignette: inset 0 0 200px rgba(80,52,20,.35);--nq-p-card-open: linear-gradient(#fdf7e6, #f2e7c9);--nq-p-card-done: linear-gradient(#f0e8d3, #e6dcc2);--nq-p-card-panel: linear-gradient(#fcf6e6, #f1e6ca);--nq-p-card-border: rgba(120,88,48,.38);--nq-p-panel-border: rgba(92,62,26,.4);--nq-p-card-shadow: 0 3px 0 rgba(120,88,48,.2), inset 0 1px 0 rgba(255,255,255,.7);--nq-p-panel-shadow: 0 6px 0 rgba(92,62,26,.18), inset 0 2px 0 rgba(255,255,255,.7);--nq-p-frame-outer: 2px solid rgba(92,62,26,.45);--nq-p-frame-inner: 1px solid rgba(92,62,26,.28);--nq-p-rule: 2px solid rgba(92,62,26,.35);--nq-p-ink: #2b1f14;--nq-p-ink-secondary: #5c452a;--nq-p-ink-muted: #6f6455;--nq-p-ink-away: #4d4433;--nq-p-ink-late: #8f1526;--nq-brand-blue: #3B3AB8;--nq-brand-purple: #A035CC;--nq-brand-gradient: linear-gradient(135deg, var(--nq-brand-blue), var(--nq-brand-purple));--nq-p-icon-tile: var(--nq-brand-gradient);--nq-p-icon-tile-done: rgba(92,62,26,.16);--nq-p-crest: var(--nq-brand-gradient);--nq-p-crest-away: linear-gradient(135deg, #6b6b7a, #7a7286);--nq-p-seal: radial-gradient(circle at 35% 30%, #a8283a, #6d1322);--nq-p-seal-shadow: 0 4px 10px rgba(60,10,20,.4), inset 0 0 0 4px rgba(255,255,255,.14);--nq-p-seal-size: 78px;--nq-p-dock-bg: rgba(43,31,20,.9);--nq-p-dock-ink: #f7efdb;--nq-p-dock-ink-secondary: #d8c9a6;--nq-p-dock-divider: rgba(233,220,189,.3);--nq-p-dock-height: 84px;--nq-p-font-display: "Cinzel Decorative", Cinzel, serif;--nq-p-font-heading: Cinzel, serif;--nq-p-font-body: Nunito, system-ui, sans-serif;--nq-p-size-hero: 86px;--nq-p-size-wordmark: 66px;--nq-p-size-title: 56px;--nq-p-size-name: 48px;--nq-p-size-section: 32px;--nq-p-size-quest: 30px;--nq-p-size-body: 22px;--nq-p-size-label: 21px;--nq-p-track-label: .14em;--nq-p-track-kicker: .3em;--nq-p-quest-min-height: 116px;--nq-p-quest-gap: 24px;--nq-p-quest-pad: 22px;--nq-p-tile-size: 64px;--nq-p-button-height: 72px;--nq-p-button-min-width: 150px;--nq-p-confirm-button: 108px;--nq-p-radius-card: 12px;--nq-p-radius-panel: 20px;--nq-p-radius-pill: 9999px;--nq-p-page-inset: 62px;--nq-p-frame-inset: 26px;--nq-ease-out: cubic-bezier(0, 0, .2, 1);--nq-dur-micro: .12s;--nq-dur-base: .2s;--nq-dur-modal: .35s}'; -function at(i) { - window.customCards = window.customCards ?? [], window.customCards.push(i); +const jt = ':host{--nq-p-parchment-top: #f0e4c7;--nq-p-parchment-bottom: #ddcca3;--nq-p-parchment: radial-gradient(ellipse at 25% 10%, rgba(255,255,255,.5), transparent 55%), radial-gradient(ellipse at 85% 90%, rgba(120,86,44,.28), transparent 60%), repeating-linear-gradient(93deg, rgba(150,115,70,.05) 0 2px, transparent 2px 6px), repeating-linear-gradient(2deg, rgba(150,115,70,.04) 0 3px, transparent 3px 7px), linear-gradient(var(--nq-p-parchment-top), var(--nq-p-parchment-bottom));--nq-p-vignette: inset 0 0 200px rgba(80,52,20,.35);--nq-p-card-open: linear-gradient(#fdf7e6, #f2e7c9);--nq-p-card-done: linear-gradient(#f0e8d3, #e6dcc2);--nq-p-card-panel: linear-gradient(#fcf6e6, #f1e6ca);--nq-p-card-border: rgba(120,88,48,.38);--nq-p-panel-border: rgba(92,62,26,.4);--nq-p-card-shadow: 0 3px 0 rgba(120,88,48,.2), inset 0 1px 0 rgba(255,255,255,.7);--nq-p-panel-shadow: 0 6px 0 rgba(92,62,26,.18), inset 0 2px 0 rgba(255,255,255,.7);--nq-p-frame-outer: 2px solid rgba(92,62,26,.45);--nq-p-frame-inner: 1px solid rgba(92,62,26,.28);--nq-p-rule: 2px solid rgba(92,62,26,.35);--nq-p-ink: #2b1f14;--nq-p-ink-secondary: #5c452a;--nq-p-ink-muted: #6f6455;--nq-p-ink-away: #4d4433;--nq-p-ink-late: #8f1526;--nq-brand-blue: #3B3AB8;--nq-brand-purple: #A035CC;--nq-brand-gradient: linear-gradient(135deg, var(--nq-brand-blue), var(--nq-brand-purple));--nq-p-icon-tile: var(--nq-brand-gradient);--nq-p-icon-tile-done: rgba(92,62,26,.16);--nq-p-crest: var(--nq-brand-gradient);--nq-p-crest-away: linear-gradient(135deg, #6b6b7a, #7a7286);--nq-p-seal: radial-gradient(circle at 35% 30%, #a8283a, #6d1322);--nq-p-seal-shadow: 0 4px 10px rgba(60,10,20,.4), inset 0 0 0 4px rgba(255,255,255,.14);--nq-p-seal-size: 78px;--nq-p-dock-bg: rgba(43,31,20,.9);--nq-p-dock-ink: #f7efdb;--nq-p-dock-ink-secondary: #d8c9a6;--nq-p-dock-divider: rgba(233,220,189,.3);--nq-p-dock-height: 84px;--nq-p-font-display: "Cinzel Decorative", Cinzel, serif;--nq-p-font-heading: Cinzel, serif;--nq-p-font-body: Nunito, system-ui, sans-serif;--nq-p-size-hero: 86px;--nq-p-size-wordmark: 66px;--nq-p-size-title: 56px;--nq-p-size-name: 48px;--nq-p-size-section: 32px;--nq-p-size-quest: 30px;--nq-p-size-body: 22px;--nq-p-size-label: 21px;--nq-p-track-label: .14em;--nq-p-track-kicker: .3em;--nq-p-quest-min-height: 116px;--nq-p-quest-gap: 24px;--nq-p-quest-pad: 22px;--nq-p-tile-size: 64px;--nq-p-button-height: 72px;--nq-p-button-min-width: 150px;--nq-p-confirm-button: 108px;--nq-p-radius-card: 12px;--nq-p-radius-panel: 20px;--nq-p-radius-pill: 9999px;--nq-p-page-inset: 62px;--nq-p-frame-inset: 26px;--nq-ease-out: cubic-bezier(0, 0, .2, 1);--nq-dur-micro: .12s;--nq-dur-base: .2s;--nq-dur-modal: .35s}'; +function at(s) { + window.customCards = window.customCards ?? [], window.customCards.push(s); } -const vt = /* @__PURE__ */ new Map(); -function Q(i, t) { - const e = `${i ?? "local"}|${JSON.stringify(t)}`, n = vt.get(e); +const pe = [ + "nestquest_quest_completed", + "nestquest_quest_uncompleted", + "nestquest_quest_missed", + "nestquest_child_day_complete" +], bt = /* @__PURE__ */ new Map(); +function Q(s, t) { + const e = `${s ?? "local"}|${JSON.stringify(t)}`, n = bt.get(e); if (n) return n; - let s; + let i; try { - s = new Intl.DateTimeFormat("en-US", { + i = new Intl.DateTimeFormat("en-US", { ...t, - ...i ? { timeZone: i } : {} + ...s ? { timeZone: s } : {} }); } catch { - s = new Intl.DateTimeFormat("en-US", t); + i = new Intl.DateTimeFormat("en-US", t); } - return vt.set(e, s), s; + return bt.set(e, i), i; } -function pe(i) { - const t = i?.config; +function he(s) { + const t = s?.config; if (!t || typeof t != "object") return; const e = t.time_zone; return typeof e == "string" && e.trim() ? e.trim() : void 0; } -function K(i, t) { - return `${Q(t, { weekday: "long" }).format(i)}, ${Q(t, { month: "short", day: "numeric" }).format(i)}`; +function K(s, t) { + return `${Q(t, { weekday: "long" }).format(s)}, ${Q(t, { month: "short", day: "numeric" }).format(s)}`; } -function he(i, t) { +function ue(s, t) { return Q(t, { hour: "numeric", minute: "2-digit", hour12: !0 - }).format(i); + }).format(s); } -const ue = { +const fe = { "clear-night": "Clear", cloudy: "Cloudy", exceptional: "Clear", @@ -552,7 +557,7 @@ const ue = { sunny: "Sunny", windy: "Windy", "windy-variant": "Windy" -}, fe = { +}, me = { "clear-night": "Clear night skies", cloudy: "Grey skies today", exceptional: "A striking day", @@ -569,25 +574,25 @@ const ue = { windy: "A blustery day", "windy-variant": "A blustery day" }; -function bt(i) { - return typeof i == "string" ? i.trim() : ""; +function vt(s) { + return typeof s == "string" ? s.trim() : ""; } -function J(i, t) { - if (i == null || i === "") +function J(s, t) { + if (s == null || s === "") return t; - const e = typeof i == "number" ? i : Number(i); + const e = typeof s == "number" ? s : Number(s); return Number.isFinite(e) ? e : t; } -function xt(i) { - return Number.isFinite(i) ? Math.min(100, Math.max(0, i)) : 0; +function xt(s) { + return Number.isFinite(s) ? Math.min(100, Math.max(0, s)) : 0; } -function $t(i) { - return Math.max(0, Math.round(i)); +function wt(s) { + return Math.max(0, Math.round(s)); } -function me(i) { - return i.split(/[-_]+/).filter(Boolean).map((t) => t.charAt(0).toUpperCase() + t.slice(1)).join(" "); +function ge(s) { + return s.split(/[-_]+/).filter(Boolean).map((t) => t.charAt(0).toUpperCase() + t.slice(1)).join(" "); } -function ge(i, t) { +function _e(s, t) { const e = Q(t, { year: "numeric", month: "2-digit", @@ -596,10 +601,10 @@ function ge(i, t) { minute: "2-digit", second: "2-digit", hourCycle: "h23" - }).formatToParts(new Date(i)), n = (r) => { + }).formatToParts(new Date(s)), n = (r) => { const o = e.find((l) => l.type === r); return o ? Number(o.value) : Number.NaN; - }, s = Date.UTC( + }, i = Date.UTC( n("year"), n("month") - 1, n("day"), @@ -607,32 +612,32 @@ function ge(i, t) { n("minute"), n("second") ); - return Number.isFinite(s) ? s - i : 0; + return Number.isFinite(i) ? i - s : 0; } -function _e(i, t) { - if (typeof i != "string") +function ye(s, t) { + if (typeof s != "string") return null; - const e = i.split("-"); + const e = s.split("-"); if (e.length !== 3) return null; - const [n, s, r] = e.map((a) => Number(a)); - if (!n || !s || !r) + const [n, i, r] = e.map((a) => Number(a)); + if (!n || !i || !r) return null; if (!t) { - const a = new Date(n, s - 1, r); + const a = new Date(n, i - 1, r); return Number.isNaN(a.getTime()) ? null : a; } - const o = Date.UTC(n, s - 1, r), l = new Date(o - ge(o, t)); + const o = Date.UTC(n, i - 1, r), l = new Date(o - _e(o, t)); return Number.isNaN(l.getTime()) ? null : l; } -function ye(i) { - const t = ue[i]; - return t || i.charAt(0).toUpperCase() + i.slice(1); +function be(s) { + const t = fe[s]; + return t || s.charAt(0).toUpperCase() + s.slice(1); } -function ve(i, t) { - return fe[i] ?? t; +function ve(s, t) { + return me[s] ?? t; } -const wt = "polygon(0 0, 100% 0, 100% 62%, 50% 100%, 0 62%)", be = "polygon(50% 0, 93% 25%, 93% 75%, 50% 100%, 7% 75%, 7% 25%)", xe = Pt` +const $t = "polygon(0 0, 100% 0, 100% 62%, 50% 100%, 0 62%)", xe = "polygon(50% 0, 93% 25%, 93% 75%, 50% 100%, 7% 75%, 7% 25%)", we = Pt` * { box-sizing: border-box; } @@ -704,7 +709,7 @@ const wt = "polygon(0 0, 100% 0, 100% 62%, 50% 100%, 0 62%)", be = "polygon(50% justify-content: center; width: 34px; height: 34px; - clip-path: ${b(be)}; + clip-path: ${v(xe)}; background: var(--nq-brand-gradient); } @@ -798,7 +803,7 @@ const wt = "polygon(0 0, 100% 0, 100% 62%, 50% 100%, 0 62%)", be = "polygon(50% width: 188px; height: 214px; padding: 4px; - clip-path: ${b(wt)}; + clip-path: ${v($t)}; background: rgba(255, 255, 255, 0.22); } @@ -809,7 +814,7 @@ const wt = "polygon(0 0, 100% 0, 100% 62%, 50% 100%, 0 62%)", be = "polygon(50% align-items: center; justify-content: center; padding-bottom: 70px; - clip-path: ${b(wt)}; + clip-path: ${v($t)}; background: var(--nq-p-crest); } @@ -1027,7 +1032,7 @@ const wt = "polygon(0 0, 100% 0, 100% 62%, 50% 100%, 0 62%)", be = "polygon(50% font-weight: 800; line-height: 1; } -`, $e = c` -`, we = c``, qe = d` -`, qe = c``, ke = d` -`, ke = c``, Ce = d` -`, Ce = c``, Ae = d` `, qt = { - icon: ke, + icon: Ce, headline: "NestQuest is not set up yet", body: "A parent needs to finish setting up NestQuest before the party can gather." -}, Ae = { - icon: Ce, +}, Se = { + icon: Ae, headline: "The records cannot be reached", body: "The party's records are quiet right now. NestQuest will return shortly." -}, Se = c` `; -class Ne extends A { +class Ee extends A { static properties = { hass: { attribute: !1 }, _config: { state: !0 }, _now: { state: !0 } }; - static styles = [b(Lt), xe]; + static styles = [v(jt), we]; hass; _config; _now = /* @__PURE__ */ new Date(); _clockTimer; + _unsubs = []; + _subscribed = !1; + _subGeneration = 0; + _retryTimer; setConfig(t) { if (!t || typeof t != "object") throw new Error("Invalid configuration"); this._config = t; } + _hassConnection() { + const t = this.hass?.connection; + return !t || typeof t != "object" || typeof t.subscribeEvents != "function" ? null : t; + } + _subscribeLive() { + if (!this.isConnected || this._subscribed) + return; + const t = this._hassConnection(); + if (!t) + return; + this._subscribed = !0; + const e = ++this._subGeneration, n = (i) => { + i.then((r) => { + this._subscribed && e === this._subGeneration ? this._unsubs.push(r) : r(); + }).catch(() => { + e === this._subGeneration && (this._unsubscribeLive(), this._armSubscribeRetry()); + }); + }; + for (const i of pe) + n( + t.subscribeEvents(() => this.requestUpdate(), i) + ); + } + _armSubscribeRetry() { + this._retryTimer === void 0 && (this._retryTimer = window.setTimeout(() => { + this._retryTimer = void 0, this._subscribeLive(); + }, 1e3)); + } + _clearSubscribeRetry() { + this._retryTimer !== void 0 && (window.clearTimeout(this._retryTimer), this._retryTimer = void 0); + } + _unsubscribeLive() { + this._clearSubscribeRetry(), this._subGeneration++; + for (const t of this._unsubs) + t(); + this._unsubs = [], this._subscribed = !1; + } getCardSize() { return 22; } connectedCallback() { - super.connectedCallback(), this._now = /* @__PURE__ */ new Date(), this._clockTimer = window.setInterval(() => { + super.connectedCallback(), this._subscribeLive(), this._now = /* @__PURE__ */ new Date(), this._clockTimer = window.setInterval(() => { this._now = /* @__PURE__ */ new Date(); }, 1e3); } + updated() { + this._subscribeLive(); + } disconnectedCallback() { - this._clockTimer !== void 0 && (window.clearInterval(this._clockTimer), this._clockTimer = void 0), super.disconnectedCallback(); + this._unsubscribeLive(), this._clockTimer !== void 0 && (window.clearInterval(this._clockTimer), this._clockTimer = void 0), super.disconnectedCallback(); } render() { const t = this._boardNotice(); - return c` + return d`
    @@ -1152,7 +1201,7 @@ class Ne extends A {

    The Party · ${K(this._now, this._timeZone())}

    - ${t ? this._renderNotice(t) : c` + ${t ? this._renderNotice(t) : d`
    ${this._plates().map((e) => this._renderPlate(e))}
    @@ -1164,7 +1213,7 @@ class Ne extends A { `; } _timeZone() { - return pe(this.hass); + return he(this.hass); } _state(t) { const n = this.hass?.states?.[t]; @@ -1182,13 +1231,13 @@ class Ne extends A { return this._childSlugs().map((t) => this._plate(t)); } _plate(t) { - const e = this._dueSensorResolution(t), n = this._state(`sensor.nestquest_${t}_quests_due_today`), s = this._state( + const e = this._dueSensorResolution(t), n = this._state(`sensor.nestquest_${t}_quests_due_today`), i = this._state( `sensor.nestquest_${t}_quests_completed_today` ), r = this._state( `sensor.nestquest_${t}_completion_pct_today` ), o = this._state( `binary_sensor.nestquest_${t}_present_today` - ), l = n?.attributes ?? {}, a = $t(J(n?.state, 0)), p = $t(J(s?.state, 0)), u = r ? J(r.state, Number.NaN) : Number.NaN, d = Number.isFinite(u) ? xt(u) : a > 0 ? xt(p / a * 100) : 0; + ), l = n?.attributes ?? {}, a = wt(J(n?.state, 0)), p = wt(J(i?.state, 0)), u = r ? J(r.state, Number.NaN) : Number.NaN, c = Number.isFinite(u) ? xt(u) : a > 0 ? xt(p / a * 100) : 0; let f = !0; const h = l.present; typeof h == "boolean" ? f = h : o && (f = String(o.state ?? "").trim().toLowerCase() === "on"); @@ -1197,14 +1246,14 @@ class Ne extends A { if (typeof _ == "string" && _.trim()) m = _.trim(); else { - const V = o?.attributes?.child_name; - typeof V == "string" && V.trim() && (m = V.trim()); + const G = o?.attributes?.child_name; + typeof G == "string" && G.trim() && (m = G.trim()); } - m || (m = me(t)); - const x = _e( + m || (m = ge(t)); + const x = ye( o?.attributes?.next_present, this._timeZone() - ), H = x ? `Returns ${K(x, this._timeZone())}` : null; + ), D = x ? `Returns ${K(x, this._timeZone())}` : null; return { slug: t, name: m, @@ -1212,8 +1261,8 @@ class Ne extends A { present: e === null ? f : !1, completed: p, due: a, - pct: d, - returns: H, + pct: c, + returns: D, unresolved: e }; } @@ -1230,10 +1279,10 @@ class Ne extends A { } _boardNotice() { const t = this._plates(); - return t.length === 0 ? qt : t.every((e) => e.unresolved !== null) ? t.some((e) => e.unresolved === "stale") ? Ae : qt : null; + return t.length === 0 ? qt : t.every((e) => e.unresolved !== null) ? t.some((e) => e.unresolved === "stale") ? Se : qt : null; } _renderNotice(t) { - return c` + return d`
    ${t.icon} @@ -1244,7 +1293,7 @@ class Ne extends A { `; } _renderPlate(t) { - return t.unresolved ? c` + return t.unresolved ? d`
    @@ -1253,13 +1302,13 @@ class Ne extends A { ${t.name} - ${qe} + ${ke} Unknown —
    - ` : t.present ? c` + ` : t.present ? d` - ` : c` + ` : d`
    @@ -1292,7 +1341,7 @@ class Ne extends A { ${t.name} - ${we} + ${qe} On travels ${t.returns ?? "Returns —"} @@ -1301,11 +1350,11 @@ class Ne extends A { `; } _openQuestLog(t) { - const e = bt(this._config?.quest_log_path); + const e = vt(this._config?.quest_log_path); e && (window.history.pushState(null, "", `${e.replace(/\/+$/, "")}/${t}`), window.dispatchEvent(new Event("location-changed"))); } _dockWeather() { - const t = bt(this._config?.weather_entity); + const t = vt(this._config?.weather_entity); if (!t) return null; const e = this._state(t); @@ -1314,9 +1363,9 @@ class Ne extends A { const n = String(e.state ?? "").trim(); if (!n || n === "unavailable" || n === "unknown") return null; - const s = e.attributes ?? {}; + const i = e.attributes ?? {}; let r = null, o = null; - const l = s.forecast; + const l = i.forecast; if (Array.isArray(l) && l.length > 0) { const a = l[0]; if (a && typeof a == "object") { @@ -1326,7 +1375,7 @@ class Ne extends A { } return { condition: n, - temperature: this._optionalNumber(s.temperature), + temperature: this._optionalNumber(i.temperature), high: r, low: o }; @@ -1340,18 +1389,18 @@ class Ne extends A { _renderDock() { const t = this._dockWeather(); if (!t) - return c` + return d`
    ${K(this._now, this._timeZone())} - ${he(this._now, this._timeZone())} + ${ue(this._now, this._timeZone())}
    `; - const e = ye(t.condition), n = ve(t.condition, e), s = t.temperature === null ? g : c`${Math.round(t.temperature)}°`, r = t.high === null || t.low === null ? null : `${Math.round(t.high)}° / ${Math.round(t.low)}°`; - return c` + const e = be(t.condition), n = ve(t.condition, e), i = t.temperature === null ? g : d`${Math.round(t.temperature)}°`, r = t.high === null || t.low === null ? null : `${Math.round(t.high)}° / ${Math.round(t.low)}°`; + return d`
    - ${Se} - ${s} + ${Te} + ${i} ${r === null ? e : `${e} · ${r}`} @@ -1362,14 +1411,14 @@ class Ne extends A { `; } } -customElements.define("nestquest-party-board-card", Ne); +customElements.define("nestquest-party-board-card", Ee); at({ type: "nestquest-party-board-card", name: "NestQuest Party Board", description: "Kids' attract screen for present and away adventurers." }); -const Te = { CHILD: 2 }, Ee = (i) => (...t) => ({ _$litDirective$: i, values: t }); -let Me = class { +const Ne = { CHILD: 2 }, Me = (s) => (...t) => ({ _$litDirective$: s, values: t }); +let Oe = class { constructor(t) { } get _$AU() { @@ -1385,126 +1434,131 @@ let Me = class { return this.render(...e); } }; -const { I: Oe } = ae, kt = (i) => i, Ct = () => document.createComment(""), P = (i, t, e) => { - const n = i._$AA.parentNode, s = t === void 0 ? i._$AB : t._$AA; +const { I: ze } = ae, kt = (s) => s, Ct = () => document.createComment(""), P = (s, t, e) => { + const n = s._$AA.parentNode, i = t === void 0 ? s._$AB : t._$AA; if (e === void 0) { - const r = n.insertBefore(Ct(), s), o = n.insertBefore(Ct(), s); - e = new Oe(r, o, i, i.options); + const r = n.insertBefore(Ct(), i), o = n.insertBefore(Ct(), i); + e = new ze(r, o, s, s.options); } else { - const r = e._$AB.nextSibling, o = e._$AM, l = o !== i; + const r = e._$AB.nextSibling, o = e._$AM, l = o !== s; if (l) { let a; - e._$AQ?.(i), e._$AM = i, e._$AP !== void 0 && (a = i._$AU) !== o._$AU && e._$AP(a); + e._$AQ?.(s), e._$AM = s, e._$AP !== void 0 && (a = s._$AU) !== o._$AU && e._$AP(a); } - if (r !== s || l) { + if (r !== i || l) { let a = e._$AA; for (; a !== r; ) { const p = kt(a).nextSibling; - kt(n).insertBefore(a, s), a = p; + kt(n).insertBefore(a, i), a = p; } } } return e; -}, k = (i, t, e = i) => (i._$AI(t, e), i), ze = {}, Pe = (i, t = ze) => i._$AH = t, Ie = (i) => i._$AH, X = (i) => { - i._$AR(), i._$AA.remove(); +}, k = (s, t, e = s) => (s._$AI(t, e), s), Pe = {}, Ie = (s, t = Pe) => s._$AH = t, Le = (s) => s._$AH, X = (s) => { + s._$AR(), s._$AA.remove(); }; -const At = (i, t, e) => { +const At = (s, t, e) => { const n = /* @__PURE__ */ new Map(); - for (let s = t; s <= e; s++) n.set(i[s], s); + for (let i = t; i <= e; i++) n.set(s[i], i); return n; -}, St = Ee(class extends Me { - constructor(i) { - if (super(i), i.type !== Te.CHILD) throw Error("repeat() can only be used in text expressions"); +}, St = Me(class extends Oe { + constructor(s) { + if (super(s), s.type !== Ne.CHILD) throw Error("repeat() can only be used in text expressions"); } - dt(i, t, e) { + dt(s, t, e) { let n; e === void 0 ? e = t : t !== void 0 && (n = t); - const s = [], r = []; + const i = [], r = []; let o = 0; - for (const l of i) s[o] = n ? n(l, o) : o, r[o] = e(l, o), o++; - return { values: r, keys: s }; + for (const l of s) i[o] = n ? n(l, o) : o, r[o] = e(l, o), o++; + return { values: r, keys: i }; } - render(i, t, e) { - return this.dt(i, t, e).values; + render(s, t, e) { + return this.dt(s, t, e).values; } - update(i, [t, e, n]) { - const s = Ie(i), { values: r, keys: o } = this.dt(t, e, n); - if (!Array.isArray(s)) return this.ut = o, r; + update(s, [t, e, n]) { + const i = Le(s), { values: r, keys: o } = this.dt(t, e, n); + if (!Array.isArray(i)) return this.ut = o, r; const l = this.ut ??= [], a = []; - let p, u, d = 0, f = s.length - 1, h = 0, m = r.length - 1; - for (; d <= f && h <= m; ) if (s[d] === null) d++; - else if (s[f] === null) f--; - else if (l[d] === o[h]) a[h] = k(s[d], r[h]), d++, h++; - else if (l[f] === o[m]) a[m] = k(s[f], r[m]), f--, m--; - else if (l[d] === o[m]) a[m] = k(s[d], r[m]), P(i, a[m + 1], s[d]), d++, m--; - else if (l[f] === o[h]) a[h] = k(s[f], r[h]), P(i, s[d], s[f]), f--, h++; - else if (p === void 0 && (p = At(o, h, m), u = At(l, d, f)), p.has(l[d])) if (p.has(l[f])) { - const _ = u.get(o[h]), x = _ !== void 0 ? s[_] : null; + let p, u, c = 0, f = i.length - 1, h = 0, m = r.length - 1; + for (; c <= f && h <= m; ) if (i[c] === null) c++; + else if (i[f] === null) f--; + else if (l[c] === o[h]) a[h] = k(i[c], r[h]), c++, h++; + else if (l[f] === o[m]) a[m] = k(i[f], r[m]), f--, m--; + else if (l[c] === o[m]) a[m] = k(i[c], r[m]), P(s, a[m + 1], i[c]), c++, m--; + else if (l[f] === o[h]) a[h] = k(i[f], r[h]), P(s, i[c], i[f]), f--, h++; + else if (p === void 0 && (p = At(o, h, m), u = At(l, c, f)), p.has(l[c])) if (p.has(l[f])) { + const _ = u.get(o[h]), x = _ !== void 0 ? i[_] : null; if (x === null) { - const H = P(i, s[d]); - k(H, r[h]), a[h] = H; - } else a[h] = k(x, r[h]), P(i, s[d], x), s[_] = null; + const D = P(s, i[c]); + k(D, r[h]), a[h] = D; + } else a[h] = k(x, r[h]), P(s, i[c], x), i[_] = null; h++; - } else X(s[f]), f--; - else X(s[d]), d++; + } else X(i[f]), f--; + else X(i[c]), c++; for (; h <= m; ) { - const _ = P(i, a[m + 1]); + const _ = P(s, a[m + 1]); k(_, r[h]), a[h++] = _; } - for (; d <= f; ) { - const _ = s[d++]; + for (; c <= f; ) { + const _ = i[c++]; _ !== null && X(_); } - return this.ut = o, Pe(i, a), N; + return this.ut = o, Ie(s, a), T; } -}), Nt = /* @__PURE__ */ new Map(); -function M(i, t) { - const e = `${i ?? "local"}|${JSON.stringify(t)}`, n = Nt.get(e); +}), Ue = [ + "nestquest_quest_completed", + "nestquest_quest_uncompleted", + "nestquest_quest_missed", + "nestquest_child_day_complete" +], Tt = /* @__PURE__ */ new Map(); +function M(s, t) { + const e = `${s ?? "local"}|${JSON.stringify(t)}`, n = Tt.get(e); if (n) return n; - let s; + let i; try { - s = new Intl.DateTimeFormat("en-US", { + i = new Intl.DateTimeFormat("en-US", { ...t, - ...i ? { timeZone: i } : {} + ...s ? { timeZone: s } : {} }); } catch { - s = new Intl.DateTimeFormat("en-US", t); + i = new Intl.DateTimeFormat("en-US", t); } - return Nt.set(e, s), s; + return Tt.set(e, i), i; } -function De(i) { - const t = i?.config; +function Re(s) { + const t = s?.config; if (!t || typeof t != "object") return; const e = t.time_zone; return typeof e == "string" && e.trim() ? e.trim() : void 0; } -function L(i, t) { - return `${M(t, { weekday: "long" }).format(i)}, ${M(t, { month: "short", day: "numeric" }).format(i)}`; +function j(s, t) { + return `${M(t, { weekday: "long" }).format(s)}, ${M(t, { month: "short", day: "numeric" }).format(s)}`; } -function Ue(i, t) { - return M(t, { weekday: "long" }).format(i); +function De(s, t) { + return M(t, { weekday: "long" }).format(s); } -function je(i, t) { - const e = new Date(i); +function je(s, t) { + const e = new Date(s); return Number.isNaN(e.getTime()) ? "" : M(t, { hour: "numeric", minute: "2-digit", hour12: !0 }).format(e); } -function He(i, t) { +function He(s, t) { return M(t, { hour: "numeric", minute: "2-digit", hour12: !0 - }).format(i); + }).format(s); } -function Le(i) { - if (!i) +function Be(s) { + if (!s) return null; - const t = /^(\d{1,2}):(\d{2})$/.exec(i.trim()); + const t = /^(\d{1,2}):(\d{2})$/.exec(s.trim()); if (!t) return null; const e = Number(t[1]); @@ -1513,22 +1567,22 @@ function Le(i) { const n = e >= 12 ? "PM" : "AM"; return `${e % 12 === 0 ? 12 : e % 12}:${t[2]} ${n}`; } -function $(i) { - return typeof i == "string" ? i.trim() : ""; +function w(s) { + return typeof s == "string" ? s.trim() : ""; } -function y(i, t) { - if (i == null || i === "") +function y(s, t) { + if (s == null || s === "") return t; - const e = typeof i == "number" ? i : Number(i); + const e = typeof s == "number" ? s : Number(s); return Number.isFinite(e) ? e : t; } -function v(i) { - return Math.max(0, Math.round(i)); +function b(s) { + return Math.max(0, Math.round(s)); } -function Y(i) { - return i.split(/[-_]+/).filter(Boolean).map((t) => t.charAt(0).toUpperCase() + t.slice(1)).join(" "); +function Y(s) { + return s.split(/[-_]+/).filter(Boolean).map((t) => t.charAt(0).toUpperCase() + t.slice(1)).join(" "); } -function Re(i, t) { +function Fe(s, t) { const e = M(t, { year: "numeric", month: "2-digit", @@ -1537,10 +1591,10 @@ function Re(i, t) { minute: "2-digit", second: "2-digit", hourCycle: "h23" - }).formatToParts(new Date(i)), n = (r) => { + }).formatToParts(new Date(s)), n = (r) => { const o = e.find((l) => l.type === r); return o ? Number(o.value) : Number.NaN; - }, s = Date.UTC( + }, i = Date.UTC( n("year"), n("month") - 1, n("day"), @@ -1548,52 +1602,52 @@ function Re(i, t) { n("minute"), n("second") ); - return Number.isFinite(s) ? s - i : 0; + return Number.isFinite(i) ? i - s : 0; } -function Tt(i, t) { - if (typeof i != "string") +function Et(s, t) { + if (typeof s != "string") return null; - const e = i.split("-"); + const e = s.split("-"); if (e.length !== 3) return null; - const [n, s, r] = e.map((a) => Number(a)); - if (!n || !s || !r) + const [n, i, r] = e.map((a) => Number(a)); + if (!n || !i || !r) return null; if (!t) { - const a = new Date(n, s - 1, r); + const a = new Date(n, i - 1, r); return Number.isNaN(a.getTime()) ? null : a; } - const o = Date.UTC(n, s - 1, r), l = new Date(o - Re(o, t)); + const o = Date.UTC(n, i - 1, r), l = new Date(o - Fe(o, t)); return Number.isNaN(l.getTime()) ? null : l; } -function Be() { - const i = window.location.pathname.split("/").filter(Boolean); - return i.length > 0 ? i[i.length - 1].toLowerCase() : ""; +function Qe() { + const s = window.location.pathname.split("/").filter(Boolean); + return s.length > 0 ? s[s.length - 1].toLowerCase() : ""; } -function Fe(i) { - if (!i || typeof i != "object") +function We(s) { + if (!s || typeof s != "object") return null; - const t = i, e = $(t.state).toLowerCase(); + const t = s, e = w(t.state).toLowerCase(); if (e !== "open" && e !== "completed") return null; const n = y(t.id, 0); if (!n) return null; - const s = y(t.child_id, 0); + const i = y(t.child_id, 0); return { id: n, - child_id: s > 0 ? s : null, - title: $(t.title) || "Quest", - icon: $(t.icon) || null, - window: $(t.window).toLowerCase(), - due_time: $(t.due_time) || null, + child_id: i > 0 ? i : null, + title: w(t.title) || "Quest", + icon: w(t.icon) || null, + window: w(t.window).toLowerCase(), + due_time: w(t.due_time) || null, state: e, overdue: t.overdue === !0, - completed_at: $(t.completed_at) || null, + completed_at: w(t.completed_at) || null, on_time: t.on_time === !0 ? !0 : t.on_time === !1 ? !1 : null }; } -const Qe = { +const Ze = { "clear-night": "Clear", cloudy: "Cloudy", exceptional: "Clear", @@ -1609,7 +1663,7 @@ const Qe = { sunny: "Sunny", windy: "Windy", "windy-variant": "Windy" -}, We = { +}, Ge = { "clear-night": "Clear night skies", cloudy: "Grey skies today", exceptional: "A striking day", @@ -1626,14 +1680,14 @@ const Qe = { windy: "A blustery day", "windy-variant": "A blustery day" }; -function Ze(i) { - const t = Qe[i]; - return t || i.charAt(0).toUpperCase() + i.slice(1); +function Ve(s) { + const t = Ze[s]; + return t || s.charAt(0).toUpperCase() + s.slice(1); } -function Ve(i, t) { - return We[i] ?? t; +function Ke(s, t) { + return Ge[s] ?? t; } -const Et = "polygon(0 0, 100% 0, 100% 62%, 50% 100%, 0 62%)", Ge = "polygon(50% 0, 93% 25%, 93% 75%, 50% 100%, 7% 75%, 7% 25%)", Mt = [-9, 6, -4], Ke = Pt` +const Nt = "polygon(0 0, 100% 0, 100% 62%, 50% 100%, 0 62%)", Je = "polygon(50% 0, 93% 25%, 93% 75%, 50% 100%, 7% 75%, 7% 25%)", Mt = [-9, 6, -4], Xe = Pt` * { box-sizing: border-box; } @@ -1693,7 +1747,7 @@ const Et = "polygon(0 0, 100% 0, 100% 62%, 50% 100%, 0 62%)", Ge = "polygon(50% width: 96px; height: 110px; padding: 3px; - clip-path: ${b(Et)}; + clip-path: ${v(Nt)}; background: rgba(255, 255, 255, 0.22); } @@ -1704,7 +1758,7 @@ const Et = "polygon(0 0, 100% 0, 100% 62%, 50% 100%, 0 62%)", Ge = "polygon(50% align-items: center; justify-content: center; padding-bottom: 32px; - clip-path: ${b(Et)}; + clip-path: ${v(Nt)}; background: var(--nq-p-crest); } @@ -1770,7 +1824,7 @@ const Et = "polygon(0 0, 100% 0, 100% 62%, 50% 100%, 0 62%)", Ge = "polygon(50% justify-content: center; width: 44px; height: 44px; - clip-path: ${b(Ge)}; + clip-path: ${v(Je)}; background: var(--nq-brand-gradient); } @@ -2480,7 +2534,7 @@ const Et = "polygon(0 0, 100% 0, 100% 62%, 50% 100%, 0 62%)", Ge = "polygon(50% font-weight: 800; line-height: 1; } -`, R = c` -`, Je = c``, Ye = d` -`, tt = c``, tt = d` -`, Xe = c``, tn = d` -`, Rt = c``, Ht = d` -`, Ye = c``, en = d` -`, tn = c``, nn = d` -`, en = c``, sn = d` -`, nn = c``, rn = d` -`, sn = c``, on = d` -`, rn = c``, an = d` -`, on = { - icon: en, +`, ln = { + icon: sn, headline: "No adventurer chosen", body: "Open The Party and tap your crest to open your quest log." -}, an = { - icon: nn, +}, cn = { + icon: rn, headline: "NestQuest is not set up yet", body: "A parent needs to finish setting up NestQuest." -}, ln = { - icon: sn, +}, dn = { + icon: on, headline: "The records cannot be reached", body: "The party's records are quiet right now. NestQuest will return shortly." }, Ot = [ - { key: "morning", name: "Morning", range: "Until 11:59 AM", icon: Xe }, - { key: "afternoon", name: "Afternoon", range: "12:00–5:00 PM", icon: Rt }, - { key: "evening", name: "Evening", range: "5:00–9:00 PM", icon: Ye } + { key: "morning", name: "Morning", range: "Until 11:59 AM", icon: tn }, + { key: "afternoon", name: "Afternoon", range: "12:00–5:00 PM", icon: Ht }, + { key: "evening", name: "Evening", range: "5:00–9:00 PM", icon: en } ]; -class dn extends A { +class pn extends A { static properties = { hass: { attribute: !1 }, _config: { state: !0 }, @@ -2641,7 +2695,7 @@ class dn extends A { _toast: { state: !0 }, _countdown: { state: !0 } }; - static styles = [b(Lt), Ke]; + static styles = [v(jt), Xe]; hass; _config; _now = /* @__PURE__ */ new Date(); @@ -2656,28 +2710,69 @@ class dn extends A { _confirmTimer; _toastTimer; _completeTimer; + _unsubs = []; + _subscribed = !1; + _subGeneration = 0; + _retryTimer; setConfig(t) { if (!t || typeof t != "object") throw new Error("Invalid configuration"); this._config = t; } + _hassConnection() { + const t = this.hass?.connection; + return !t || typeof t != "object" || typeof t.subscribeEvents != "function" ? null : t; + } + _subscribeLive() { + if (!this.isConnected || this._subscribed) + return; + const t = this._hassConnection(); + if (!t) + return; + this._subscribed = !0; + const e = ++this._subGeneration, n = (i) => { + i.then((r) => { + this._subscribed && e === this._subGeneration ? this._unsubs.push(r) : r(); + }).catch(() => { + e === this._subGeneration && (this._unsubscribeLive(), this._armSubscribeRetry()); + }); + }; + for (const i of Ue) + n( + t.subscribeEvents(() => this.requestUpdate(), i) + ); + } + _armSubscribeRetry() { + this._retryTimer === void 0 && (this._retryTimer = window.setTimeout(() => { + this._retryTimer = void 0, this._subscribeLive(); + }, 1e3)); + } + _clearSubscribeRetry() { + this._retryTimer !== void 0 && (window.clearTimeout(this._retryTimer), this._retryTimer = void 0); + } + _unsubscribeLive() { + this._clearSubscribeRetry(), this._subGeneration++; + for (const t of this._unsubs) + t(); + this._unsubs = [], this._subscribed = !1; + } getCardSize() { return 22; } connectedCallback() { - super.connectedCallback(), this._now = /* @__PURE__ */ new Date(), this._clockTimer = window.setInterval(() => { + super.connectedCallback(), this._subscribeLive(), this._now = /* @__PURE__ */ new Date(), this._clockTimer = window.setInterval(() => { this._now = /* @__PURE__ */ new Date(); }, 6e4), window.addEventListener("pointerdown", this._onActivity, !0), window.addEventListener("touchstart", this._onActivity, !0), window.addEventListener("keydown", this._onActivity, !0), this._armIdle(), this._logView().kind === "complete-day" && this._armCompleteTimer(); } updated() { - this._logView().kind === "complete-day" ? (this._clearIdle(), this._armCompleteTimer()) : (this._clearCompleteTimer(), this._countdown = null, this._idleTimer === void 0 && this._armIdle()); + this._subscribeLive(), this._logView().kind === "complete-day" ? (this._clearIdle(), this._armCompleteTimer()) : (this._clearCompleteTimer(), this._countdown = null, this._idleTimer === void 0 && this._armIdle()); } disconnectedCallback() { - this._stopClock(), this._clearIdle(), this._clearConfirmTimer(), this._clearToastTimer(), this._clearCompleteTimer(), window.removeEventListener("pointerdown", this._onActivity, !0), window.removeEventListener("touchstart", this._onActivity, !0), window.removeEventListener("keydown", this._onActivity, !0), super.disconnectedCallback(); + this._unsubscribeLive(), this._stopClock(), this._clearIdle(), this._clearConfirmTimer(), this._clearToastTimer(), this._clearCompleteTimer(), window.removeEventListener("pointerdown", this._onActivity, !0), window.removeEventListener("touchstart", this._onActivity, !0), window.removeEventListener("keydown", this._onActivity, !0), super.disconnectedCallback(); } render() { const t = this._logView(); - return c` + return d`
    @@ -2688,37 +2783,37 @@ class dn extends A { `; } _renderToast() { - return this._toast === null ? g : c` + return this._toast === null ? g : d`
    ${this._toast}
    `; } _renderMain(t) { switch (t.kind) { case "no-adventurer": - return this._renderNotice(on); - case "not-set-up": - return this._renderNotice(an); - case "unreachable": return this._renderNotice(ln); + case "not-set-up": + return this._renderNotice(cn); + case "unreachable": + return this._renderNotice(dn); case "away": { const e = ["The quest log unlocks when they return."]; - return t.returns && e.push(`Returns ${t.returns}`), c` + return t.returns && e.push(`Returns ${t.returns}`), d` ${this._renderHeader(t)}
    - ${tn} + ${nn} ${t.name} is on travels - ${e.map((n) => c`

    ${n}

    `)} + ${e.map((n) => d`

    ${n}

    `)}
    `; } case "empty-day": - return c` + return d` ${this._renderHeader(t)}
    - ${Rt} + ${Ht} No quests today

    ${`Nothing was posted for today, ${t.name}. Enjoy the day's rest!`} @@ -2728,9 +2823,9 @@ class dn extends A { `; case "complete-day": { const e = this._completeStats(), n = this._countdown ?? this._completeSeconds(); - return c` + return d`

    - +

    Quest complete

    ${this._completeSub(t.name)}

    @@ -2759,7 +2854,7 @@ class dn extends A { `; } default: - return c` + return d` ${this._renderHeader(t)}

    ${Ot.map((e) => this._renderColumn(e))} @@ -2781,11 +2876,11 @@ class dn extends A { const n = this._childName(t) ?? Y(t); if (!this._childPresent(t)) return { kind: "away", name: n, returns: this._awayReturns(t) }; - const s = this._state(`binary_sensor.nestquest_${t}_all_done`); - if (s && String(s.state ?? "").trim().toLowerCase() === "on") + const i = this._state(`binary_sensor.nestquest_${t}_all_done`); + if (i && String(i.state ?? "").trim().toLowerCase() === "on") return { kind: "complete-day", name: n }; const r = this._state(`sensor.nestquest_${t}_quests_due_today`); - return v(y(r?.state, 0)) === 0 ? { kind: "empty-day", name: n } : this._remaining() === 0 ? { kind: "complete-day", name: n } : { kind: "normal" }; + return b(y(r?.state, 0)) === 0 ? { kind: "empty-day", name: n } : this._remaining() === 0 ? { kind: "complete-day", name: n } : { kind: "normal" }; } /** "missing" when the due sensor does not exist (integration not * configured), "stale" when it answers unavailable/unknown (backend @@ -2802,22 +2897,22 @@ class dn extends A { const n = this._state(`sensor.nestquest_${t}_quests_due_today`)?.attributes?.present; if (typeof n == "boolean") return n; - const s = this._state( + const i = this._state( `binary_sensor.nestquest_${t}_present_today` ); - return s ? String(s.state ?? "").trim().toLowerCase() === "on" : !0; + return i ? String(i.state ?? "").trim().toLowerCase() === "on" : !0; } _awayReturns(t) { const e = this._timeZone(), n = this._state( `binary_sensor.nestquest_${t}_present_today` - ), s = Tt( + ), i = Et( n?.attributes?.next_present, e ); - return s ? L(s, e) : null; + return i ? j(i, e) : null; } _renderNotice(t) { - return c` + return d`
    ${t.icon} @@ -2828,17 +2923,17 @@ class dn extends A { `; } _timeZone() { - return De(this.hass); + return Re(this.hass); } _state(t) { const n = this.hass?.states?.[t]; return n && typeof n == "object" ? n : null; } _childSlug() { - return Be(); + return Qe(); } _boardPath() { - return $(this._config?.board_path).replace(/\/+$/, ""); + return w(this._config?.board_path).replace(/\/+$/, ""); } _idleSeconds() { const t = y(this._config?.idle_return_seconds, 40); @@ -2861,8 +2956,8 @@ class dn extends A { _childCounts(t) { const e = this._state(`sensor.nestquest_${t}_quests_due_today`), n = this._state( `sensor.nestquest_${t}_quests_completed_today` - ), s = v(y(e?.state, 0)), r = v(y(n?.state, 0)); - return { due: s, completed: r }; + ), i = b(y(e?.state, 0)), r = b(y(n?.state, 0)); + return { due: i, completed: r }; } _instances() { const t = this._childSlug(); @@ -2871,21 +2966,21 @@ class dn extends A { const e = this._state(`sensor.nestquest_${t}_quests_due_today`)?.attributes?.instances; if (!Array.isArray(e)) return []; - const n = e.map(Fe).filter((r) => r !== null); + const n = e.map(We).filter((r) => r !== null); if (this._optimistic.size === 0) return n; - let s = !1; + let i = !1; for (const r of n) { const o = this._optimistic.get(r.id); if (o !== void 0) { if (r.state === "completed") { - this._optimistic.delete(r.id), s = !0; + this._optimistic.delete(r.id), i = !0; continue; } r.state = "completed", r.overdue = !1, r.completed_at = o; } } - return s && (this._optimistic = new Map(this._optimistic)), n; + return i && (this._optimistic = new Map(this._optimistic)), n; } _remaining() { const t = this._childSlug(); @@ -2895,9 +2990,9 @@ class dn extends A { `sensor.nestquest_${t}_quests_remaining_today` ); if (e) - return v(y(e.state, 0)); - const { due: n, completed: s } = this._childCounts(t); - return v(n - s); + return b(y(e.state, 0)); + const { due: n, completed: i } = this._childCounts(t); + return b(n - i); } _partyCounts() { const t = this._state( @@ -2907,32 +3002,32 @@ class dn extends A { ); if (t || e) return { - completed: v(y(e?.state, 0)), - due: v(y(t?.state, 0)) + completed: b(y(e?.state, 0)), + due: b(y(t?.state, 0)) }; - const n = this._childSlug(), { due: s, completed: r } = this._childCounts(n); - return { completed: r, due: s }; + const n = this._childSlug(), { due: i, completed: r } = this._childCounts(n); + return { completed: r, due: i }; } _completeStats() { const t = this._childCounts(this._childSlug()).completed, e = this._instances().filter( (r) => r.on_time === !0 - ).length, n = v(t - e), s = this._partyCounts(); + ).length, n = b(t - e), i = this._partyCounts(); return { claimed: t, onTime: e, late: n, - party: `${s.completed}/${s.due}` + party: `${i.completed}/${i.due}` }; } _completeSub(t) { - const e = L(this._now, this._timeZone()), { claimed: n, onTime: s, late: r } = this._completeStats(); - return n === 0 ? `${t} sealed the day on ${e}.` : r === 0 ? `${t} claimed every quest on ${e} — all on time.` : s === 0 ? `${t} claimed every quest on ${e} — all late.` : `${t} claimed every quest on ${e} — ${s} on time, ${r} late.`; + const e = j(this._now, this._timeZone()), { claimed: n, onTime: i, late: r } = this._completeStats(); + return n === 0 ? `${t} sealed the day on ${e}.` : r === 0 ? `${t} claimed every quest on ${e} — all on time.` : i === 0 ? `${t} claimed every quest on ${e} — all late.` : `${t} claimed every quest on ${e} — ${i} on time, ${r} late.`; } _otherChildren() { const t = this.hass?.states; if (!t) return []; - const e = this._childSlug(), n = this._timeZone(), s = []; + const e = this._childSlug(), n = this._timeZone(), i = []; for (const r of Object.keys(t)) { const o = /^sensor\.nestquest_(.+)_quests_due_today$/.exec(r); if (!o || o[1] === "household" || o[1] === e) @@ -2949,20 +3044,20 @@ class dn extends A { ); u = _ === null || String(_.state ?? "").trim().toLowerCase() !== "off"; } - const d = this._state( + const c = this._state( `sensor.nestquest_${l}_quests_completed_today` - ), f = v(y(t[r]?.state, 0)), h = v(y(d?.state, 0)); + ), f = b(y(t[r]?.state, 0)), h = b(y(c?.state, 0)); let m = null; if (!u) { const _ = this._state( `binary_sensor.nestquest_${l}_present_today` - ), x = Tt( + ), x = Et( _?.attributes?.next_present, n ); - m = x ? Ue(x, n) : null; + m = x ? De(x, n) : null; } - s.push({ + i.push({ slug: l, name: p, present: u, @@ -2971,11 +3066,11 @@ class dn extends A { returnsWeekday: m }); } - return s.sort((r, o) => r.name.localeCompare(o.name)); + return i.sort((r, o) => r.name.localeCompare(o.name)); } _renderHeader(t) { - const e = this._childSlug(), n = t.kind === "away", s = n ? t.name : this._childName(e) ?? (e ? Y(e) : null), { due: r, completed: o } = this._childCounts(e), l = this._remaining(), a = this._partyCounts(), p = s ? `${s}'s Quest Log` : "Quest Log", u = (s?.charAt(0) || "?").toUpperCase(); - return c` + const e = this._childSlug(), n = t.kind === "away", i = n ? t.name : this._childName(e) ?? (e ? Y(e) : null), { due: r, completed: o } = this._childCounts(e), l = this._remaining(), a = this._partyCounts(), p = i ? `${i}'s Quest Log` : "Quest Log", u = (i?.charAt(0) || "?").toUpperCase(); + return d`
    ${St( @@ -3052,7 +3147,7 @@ class dn extends A { (l) => this._renderQuest(l, !1, 0) )} ${St( - s, + i, (l) => l.id, (l, a) => this._renderQuest(l, !0, a) )} @@ -3064,7 +3159,7 @@ class dn extends A { _renderQuest(t, e, n) { if (e) { const o = Mt[n % Mt.length], l = t.completed_at ? je(t.completed_at, this._timeZone()) : ""; - return c` + return d`
    @@ -3078,19 +3173,19 @@ class dn extends A { aria-hidden="true" style="--seal-rot: ${o}deg" > - ${R} + ${H}
    `; } - const s = Le(t.due_time), r = t.overdue ? { - text: s ? `Overdue · due ${s}` : "Overdue", + const i = Be(t.due_time), r = t.overdue ? { + text: i ? `Overdue · due ${i}` : "Overdue", late: !0 } : { - text: s ? `Due by ${s}` : "Due today", + text: i ? `Due by ${i}` : "Due today", late: !1 }; - return c` + return d`
    this._openConfirm(t.id)} > - ${R} + ${H} Complete
    @@ -3123,7 +3218,7 @@ class dn extends A { if (t.length === 0) return g; const e = t.slice(0, 2).map((n) => n.present ? n.due > 0 && n.completed >= n.due ? `${n.name} has finished their log.` : n.due > n.completed ? `${n.name} has ${n.due - n.completed} quests left.` : `${n.name} has no quests today.` : n.returnsWeekday ? `${n.name} is on travels until ${n.returnsWeekday}.` : `${n.name} is on travels.`); - return c` + return d`
    Party roll-up

    ${e.join(" ")}

    @@ -3139,7 +3234,7 @@ class dn extends A { if (!t) return g; const e = Ot.find((n) => n.key === t.window); - return c` + return d`
    this._closeConfirm()}>
    this._closeConfirm()} > - ${Je} + ${Ye} Not yet
    @@ -3285,7 +3380,7 @@ class dn extends A { return Number.isFinite(e) ? e : null; } _dockWeather() { - const t = $(this._config?.weather_entity); + const t = w(this._config?.weather_entity); if (!t) return null; const e = this._state(t); @@ -3294,9 +3389,9 @@ class dn extends A { const n = String(e.state ?? "").trim(); if (!n || n === "unavailable" || n === "unknown") return null; - const s = e.attributes ?? {}; + const i = e.attributes ?? {}; let r = null, o = null; - const l = s.forecast; + const l = i.forecast; if (Array.isArray(l) && l.length > 0) { const a = l[0]; if (a && typeof a == "object") { @@ -3306,7 +3401,7 @@ class dn extends A { } return { condition: n, - temperature: this._optionalNumber(s.temperature), + temperature: this._optionalNumber(i.temperature), high: r, low: o }; @@ -3314,18 +3409,18 @@ class dn extends A { _renderDock() { const t = this._dockWeather(); if (!t) - return c` + return d`
    - ${L(this._now, this._timeZone())} + ${j(this._now, this._timeZone())} ${He(this._now, this._timeZone())}
    `; - const e = Ze(t.condition), n = Ve(t.condition, e), s = t.temperature === null ? g : c`${Math.round(t.temperature)}°`, r = t.high === null || t.low === null ? null : `${Math.round(t.high)}° / ${Math.round(t.low)}°`; - return c` + const e = Ve(t.condition), n = Ke(t.condition, e), i = t.temperature === null ? g : d`${Math.round(t.temperature)}°`, r = t.high === null || t.low === null ? null : `${Math.round(t.high)}° / ${Math.round(t.low)}°`; + return d`
    - ${rn} - ${s} + ${an} + ${i} ${r === null ? e : `${e} · ${r}`} @@ -3346,19 +3441,19 @@ class dn extends A { this._logView().kind !== "complete-day" && this._armIdle(), this._confirm !== null && this._armConfirmTimer(); }; } -customElements.define("nestquest-quest-log-card", dn); +customElements.define("nestquest-quest-log-card", pn); at({ type: "nestquest-quest-log-card", name: "NestQuest Quest Log", description: "One child's daily quests, grouped by window." }); -const cn = ":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 pn extends A { +const hn = ":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 un extends A { static properties = { hass: { attribute: !1 }, _config: { state: !0 } }; - static styles = [b(cn)]; + static styles = [v(hn)]; hass; _config; setConfig(t) { @@ -3370,17 +3465,17 @@ class pn extends A { return 8; } render() { - return c`
    NestQuest Admin
    `; + return d`
    NestQuest Admin
    `; } } -customElements.define("nestquest-admin-card", pn); +customElements.define("nestquest-admin-card", un); at({ type: "nestquest-admin-card", name: "NestQuest Admin", description: "Parent phone view for today, tasks, schedule, and history." }); -const hn = "/nestquest-static/nestquest-fonts.css"; +const fn = "/nestquest-static/nestquest-fonts.css"; if (!document.querySelector('link[data-nq-fonts=""]')) { - const i = document.createElement("link"); - i.rel = "stylesheet", i.href = hn, i.dataset.nqFonts = "", document.head.appendChild(i); + const s = document.createElement("link"); + s.rel = "stylesheet", s.href = fn, s.dataset.nqFonts = "", document.head.appendChild(s); } diff --git a/frontend/src/cards/party-board.ts b/frontend/src/cards/party-board.ts index 13a022d..d012872 100644 --- a/frontend/src/cards/party-board.ts +++ b/frontend/src/cards/party-board.ts @@ -8,6 +8,20 @@ type StateObject = { attributes?: Record | null; }; +type HassConnection = { + subscribeEvents: ( + callback: (event: unknown) => void, + eventType: string + ) => Promise<() => void>; +}; + +const NESTQUEST_EVENT_TYPES = [ + "nestquest_quest_completed", + "nestquest_quest_uncompleted", + "nestquest_quest_missed", + "nestquest_child_day_complete", +]; + type ChildPlate = { slug: string; name: string; @@ -715,6 +729,10 @@ export class NestQuestPartyBoardCard extends LitElement { _config?: CardConfig; _now = new Date(); private _clockTimer?: number; + private _unsubs: Array<() => void> = []; + private _subscribed = false; + private _subGeneration = 0; + private _retryTimer?: number; setConfig(config: CardConfig): void { if (!config || typeof config !== "object") { @@ -723,19 +741,101 @@ export class NestQuestPartyBoardCard extends LitElement { this._config = config; } + private _hassConnection(): HassConnection | null { + const connection = (this.hass as { connection?: unknown } | undefined) + ?.connection; + if ( + !connection || + typeof connection !== "object" || + typeof (connection as HassConnection).subscribeEvents !== "function" + ) { + return null; + } + return connection as HassConnection; + } + + private _subscribeLive(): void { + if (!this.isConnected) { + return; + } + if (this._subscribed) { + return; + } + const connection = this._hassConnection(); + if (!connection) { + return; + } + this._subscribed = true; + const token = ++this._subGeneration; + const track = (unsubPromise: Promise<() => void>): void => { + unsubPromise + .then((unsub) => { + if (this._subscribed && token === this._subGeneration) { + this._unsubs.push(unsub); + } else { + unsub(); + } + }) + .catch(() => { + if (token === this._subGeneration) { + this._unsubscribeLive(); + this._armSubscribeRetry(); + } + }); + }; + for (const eventType of NESTQUEST_EVENT_TYPES) { + track( + connection.subscribeEvents(() => this.requestUpdate(), eventType) + ); + } + } + + private _armSubscribeRetry(): void { + if (this._retryTimer !== undefined) { + return; + } + this._retryTimer = window.setTimeout(() => { + this._retryTimer = undefined; + this._subscribeLive(); + }, 1000); + } + + private _clearSubscribeRetry(): void { + if (this._retryTimer !== undefined) { + window.clearTimeout(this._retryTimer); + this._retryTimer = undefined; + } + } + + private _unsubscribeLive(): void { + this._clearSubscribeRetry(); + this._subGeneration++; + for (const unsub of this._unsubs) { + unsub(); + } + this._unsubs = []; + this._subscribed = false; + } + getCardSize(): number { return 22; } connectedCallback(): void { super.connectedCallback(); + this._subscribeLive(); this._now = new Date(); this._clockTimer = window.setInterval(() => { this._now = new Date(); }, 1000); } + protected updated(): void { + this._subscribeLive(); + } + disconnectedCallback(): void { + this._unsubscribeLive(); if (this._clockTimer !== undefined) { window.clearInterval(this._clockTimer); this._clockTimer = undefined; diff --git a/frontend/src/cards/quest-log.ts b/frontend/src/cards/quest-log.ts index 3271526..fba8929 100644 --- a/frontend/src/cards/quest-log.ts +++ b/frontend/src/cards/quest-log.ts @@ -13,6 +13,20 @@ type StateObject = { attributes?: Record | null; }; +type HassConnection = { + subscribeEvents: ( + callback: (event: unknown) => void, + eventType: string + ) => Promise<() => void>; +}; + +const NESTQUEST_EVENT_TYPES = [ + "nestquest_quest_completed", + "nestquest_quest_uncompleted", + "nestquest_quest_missed", + "nestquest_child_day_complete", +]; + type PanelInstance = { id: number; child_id: number | null; @@ -1361,6 +1375,10 @@ export class NestQuestQuestLogCard extends LitElement { private _confirmTimer?: number; private _toastTimer?: number; private _completeTimer?: number; + private _unsubs: Array<() => void> = []; + private _subscribed = false; + private _subGeneration = 0; + private _retryTimer?: number; setConfig(config: QuestLogCardConfig): void { if (!config || typeof config !== "object") { @@ -1369,12 +1387,89 @@ export class NestQuestQuestLogCard extends LitElement { this._config = config; } + private _hassConnection(): HassConnection | null { + const connection = (this.hass as { connection?: unknown } | undefined) + ?.connection; + if ( + !connection || + typeof connection !== "object" || + typeof (connection as HassConnection).subscribeEvents !== "function" + ) { + return null; + } + return connection as HassConnection; + } + + private _subscribeLive(): void { + if (!this.isConnected) { + return; + } + if (this._subscribed) { + return; + } + const connection = this._hassConnection(); + if (!connection) { + return; + } + this._subscribed = true; + const token = ++this._subGeneration; + const track = (unsubPromise: Promise<() => void>): void => { + unsubPromise + .then((unsub) => { + if (this._subscribed && token === this._subGeneration) { + this._unsubs.push(unsub); + } else { + unsub(); + } + }) + .catch(() => { + if (token === this._subGeneration) { + this._unsubscribeLive(); + this._armSubscribeRetry(); + } + }); + }; + for (const eventType of NESTQUEST_EVENT_TYPES) { + track( + connection.subscribeEvents(() => this.requestUpdate(), eventType) + ); + } + } + + private _armSubscribeRetry(): void { + if (this._retryTimer !== undefined) { + return; + } + this._retryTimer = window.setTimeout(() => { + this._retryTimer = undefined; + this._subscribeLive(); + }, 1000); + } + + private _clearSubscribeRetry(): void { + if (this._retryTimer !== undefined) { + window.clearTimeout(this._retryTimer); + this._retryTimer = undefined; + } + } + + private _unsubscribeLive(): void { + this._clearSubscribeRetry(); + this._subGeneration++; + for (const unsub of this._unsubs) { + unsub(); + } + this._unsubs = []; + this._subscribed = false; + } + getCardSize(): number { return 22; } connectedCallback(): void { super.connectedCallback(); + this._subscribeLive(); this._now = new Date(); this._clockTimer = window.setInterval(() => { this._now = new Date(); @@ -1389,6 +1484,7 @@ export class NestQuestQuestLogCard extends LitElement { } protected updated(): void { + this._subscribeLive(); if (this._logView().kind === "complete-day") { this._clearIdle(); this._armCompleteTimer(); @@ -1402,6 +1498,7 @@ export class NestQuestQuestLogCard extends LitElement { } disconnectedCallback(): void { + this._unsubscribeLive(); this._stopClock(); this._clearIdle(); this._clearConfirmTimer(); From 4cb07fbca45371cb37150e2b6ca2f8b4985f0e6e Mon Sep 17 00:00:00 2001 From: dev_agent <17+dev_agent@code.cubecraftcreations.com> Date: Sun, 20 Sep 2026 19:29:00 -0400 Subject: [PATCH 09/11] Record 1920x1080 panel layout and TouchHub dock behaviour Record 1920x1080 panel layout and TouchHub dock behaviour --- README.md | 8 ++++++++ tests/test_frontend.py | 8 ++++++++ 2 files changed, 16 insertions(+) diff --git a/README.md b/README.md index 7887abe..fd29aa7 100644 --- a/README.md +++ b/README.md @@ -186,6 +186,14 @@ npm install npm run build ``` +### Kids panel layout + +The Lovelace panel cards are designed for the wall display's confirmed +resolution, 1920×1080 landscape, with no page scroll. TouchHub's bottom +dock overlays the NestQuest weather dock strip until it auto-hides, so +no tappable NestQuest control sits in that strip (the dock carries +`pointer-events: none`). Keep it that way when editing the cards. + ### Running Tests Run the test suite with `pytest`: diff --git a/tests/test_frontend.py b/tests/test_frontend.py index e32b1db..59c5989 100644 --- a/tests/test_frontend.py +++ b/tests/test_frontend.py @@ -93,6 +93,14 @@ def test_readme_documents_npm_build() -> None: assert "nestquest-cards.js" in readme +def test_readme_documents_panel_resolution_and_touchhub_dock() -> None: + readme = (REPO_ROOT / "README.md").read_text(encoding="utf-8") + assert "1920×1080" in readme + assert "TouchHub" in readme + assert "dock" in readme + assert "pointer-events: none" in readme + + WOFF2_FILES = ( "nunito-latin-400-normal.woff2", "nunito-latin-600-normal.woff2", From 43ad44ee4859204ce8cabe71a462e5fefc23e382 Mon Sep 17 00:00:00 2001 From: dev_agent <17+dev_agent@code.cubecraftcreations.com> Date: Sun, 20 Sep 2026 19:41:57 -0400 Subject: [PATCH 10/11] Panel card tests including the no-admin-controls assertion Panel card tests including the no-admin-controls assertion --- tests/test_panel_cards.py | 160 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 160 insertions(+) create mode 100644 tests/test_panel_cards.py diff --git a/tests/test_panel_cards.py b/tests/test_panel_cards.py new file mode 100644 index 0000000..bb0daad --- /dev/null +++ b/tests/test_panel_cards.py @@ -0,0 +1,160 @@ +"""Source-level tests for the kid panel cards. + +The panel cards are Lit/TypeScript modules bundled by Vite; these tests assert +the contract by reading the card sources and the built bundle, in the same +spirit as test_frontend.py. They cover the states decided in +design/PANEL-EMPTY-STATES.md and PANEL-SPEC.md, and guard the kid-facing +bundles against ever gaining a parent-only service call. +""" +from __future__ import annotations + +from pathlib import Path + +REPO_ROOT = Path(__file__).parent.parent +FRONTEND_CARDS_DIR = REPO_ROOT / "frontend" / "src" / "cards" +PARTY_BOARD = FRONTEND_CARDS_DIR / "party-board.ts" +QUEST_LOG = FRONTEND_CARDS_DIR / "quest-log.ts" +BUNDLE_PATH = REPO_ROOT / "custom_components" / "nestquest" / "www" / "nestquest-cards.js" + +PANEL_SOURCES = (PARTY_BOARD, QUEST_LOG) + +# Parent-only NestQuest services. The party board and quest log are the +# kid-facing panel: they may complete a quest for the tapped child, and nothing +# else. Any of these names appearing in a panel source or bundle means an admin +# control has leaked into the kid panel. +ADMIN_ONLY_SERVICES = ( + "uncomplete_quest", + "create_quest_definition", + "update_quest_definition", + "set_quest_definition_active", + "set_presence_pattern", + "create_presence_override", + "delete_presence_override", + "export_history_csv", + "manage_child", + "regenerate", +) + +WINDOW_KEYS = ("morning", "afternoon", "evening") +WINDOW_NAMES = ("Morning", "Afternoon", "Evening") + + +def _read(path: Path) -> str: + return path.read_text(encoding="utf-8") + + +def test_party_board_renders_present_and_away_plates() -> None: + source = _read(PARTY_BOARD) + assert "nestquest-party-board-card" in source + # Present adventurers get the tappable crest plate. + assert "button.plate" in source + assert 'class="plate"' in source + assert "Home today" in source + assert "aria-label=\"Open ${plate.name}'s Quest Log\"" in source + # Away adventurers get the dashed On travels plate, not a tappable one. + assert ".plate.away" in source + assert 'class="plate away"' in source + assert "On travels" in source + assert "next_present" in source + assert "Returns ${formatDay(returnsDate" in source + + +def test_quest_log_renders_three_window_columns() -> None: + source = _read(QUEST_LOG) + for key, name in zip(WINDOW_KEYS, WINDOW_NAMES): + assert f'key: "{key}"' in source, key + assert f'name: "{name}"' in source, name + assert "grid-template-columns: repeat(3, 1fr)" in source + assert 'class="column-head"' in source + assert 'class="column-names"' in source + assert 'class="column-range"' in source + + +def test_empty_window_still_renders_its_column_head() -> None: + """A window with no quests renders the head alone (PANEL-SPEC §3.1).""" + source = _read(QUEST_LOG) + column = source[source.index("private _renderColumn(windowDef: WindowDef)") :] + column = column[: column.index("private _renderQuest(")] + # The head is emitted unconditionally, before the stack is built. + assert 'class="column-head"' in column + assert "sealed.length}/${instances.length}" in column + # There is no early return that would drop the head on an empty window. + assert "instances.length === 0" not in column + assert "return nothing" not in column + + +def test_complete_quest_is_optimistic_with_rollback() -> None: + source = _read(QUEST_LOG) + assert "_optimistic" in source + assert "new Date().toISOString()" in source + assert ".set(instance.id, stampedAt)" in source + # The overlay is applied to the rendered instance before the service answers. + assert 'instance.state = "completed"' in source + # A rejected call reverts the overlay and raises the parchment toast. + assert "reverted.delete(instance.id)" in source + assert "NestQuest could not set the seal just now. Please try again." in source + assert "_showToast(" in source + assert "await this._callCompleteQuest(instance)" in source + assert '"nestquest", "complete_quest"' in source + + +def test_idle_return_to_board() -> None: + source = _read(QUEST_LOG) + assert "idle_return_seconds" in source + assert "_armIdle" in source + assert "_clearIdle" in source + assert "this._idleSeconds() * 1000" in source + assert "board_path" in source + assert "_returnToBoard" in source + assert 'window.dispatchEvent(new Event("location-changed"))' in source + + +def test_decided_empty_and_error_states_are_present() -> None: + log = _read(QUEST_LOG) + board = _read(PARTY_BOARD) + for kind in ( + "no-adventurer", + "not-set-up", + "unreachable", + "away", + "empty-day", + "complete-day", + ): + assert f'kind: "{kind}"' in log, kind + assert "PANEL-EMPTY-STATES" in log + assert "No adventurer chosen" in log + assert "NestQuest is not set up yet" in log + assert "The records cannot be reached" in log + assert "No quests today" in log + assert 'role="status"' in log + # The board distinguishes the same setup/unreachable cases at board level. + assert "NestQuest is not set up yet" in board + assert "The records cannot be reached" in board + assert "BOARD_NOTICE_NOT_SET_UP" in board + assert "BOARD_NOTICE_UNREACHABLE" in board + + +def test_quest_complete_trigger() -> None: + source = _read(QUEST_LOG) + assert "binary_sensor.nestquest_${slug}_all_done" in source + assert "nestquest_child_day_complete" in source + assert "NESTQUEST_EVENT_TYPES" in source + assert 'class="complete-screen"' in source + assert "Quest complete" in source + assert "Returning to The Party in" in source + assert "_armCompleteTimer" in source + assert "complete_screen_seconds" in source + + +def test_panel_cards_reference_no_admin_only_services() -> None: + # Only the kid panel sources are scanned: the bundle also contains the + # sibling admin card, so its parent-only service names are expected there. + for path in PANEL_SOURCES: + text = _read(path) + for service in ADMIN_ONLY_SERVICES: + assert service not in text, f"{path}: {service}" + # The bundle is still built and carries both panel card custom elements. + assert BUNDLE_PATH.is_file() + bundle = _read(BUNDLE_PATH) + assert "nestquest-party-board-card" in bundle + assert "nestquest-quest-log-card" in bundle \ No newline at end of file From 7386999cf7cfa72461d78e2ef347b76e77ba90b9 Mon Sep 17 00:00:00 2001 From: dev_agent <17+dev_agent@code.cubecraftcreations.com> Date: Sun, 20 Sep 2026 20:20:52 -0400 Subject: [PATCH 11/11] TouchHub setup runbook TouchHub setup runbook --- README.md | 79 ++++++++++++++++++++++++++++++++++++++++++ tests/test_frontend.py | 16 +++++++++ 2 files changed, 95 insertions(+) diff --git a/README.md b/README.md index fd29aa7..a590c80 100644 --- a/README.md +++ b/README.md @@ -155,6 +155,85 @@ Sensors the automations read: `sensor.nestquest__quests_due_today` `..._quests_completed_today`, and `binary_sensor.nestquest__present_today`. +## TouchHub setup + +Run these steps in order to display the NestQuest panel on a TouchHub +wall device. The panel is an HA Lovelace dashboard, so the kiosk browser +is simply a Home Assistant client. + +1. **Create a dedicated kiosk user.** In *Settings → People → Users*, add + a new user for the wall device. Make it **non-admin**, and ensure it is + not in the NestQuest admin allowlist. The allowlist blocks the NestQuest + **admin** operations — `uncomplete_quest`, `create_quest_definition`, + `update_quest_definition`, `set_quest_definition_active`, + `set_presence_pattern`, `create_presence_override`, + `delete_presence_override`, `export_history_csv`, and `manage_child` + (plus `regenerate` flows) — it does not make the account view-only. The + kiosk user *can* call `nestquest.complete_quest`, the one open NestQuest + service, to complete quests from the panel, and as a non-admin Home + Assistant user it may also reach other permitted Home Assistant entities + and services. + +2. **Build a dashboard with only the NestQuest panel cards.** Create a + Lovelace dashboard whose **URL slug is `nestquest`** (Settings → + Dashboards → Edit → URL slug). A functional panel needs **two** views + in that dashboard, each with its own card config from + `design/ENTITIES-AND-SERVICES.md` §4. Give the views these exact URL + paths (the path segment after the dashboard slug): + + - **View 1 path `board`** (URL `/nestquest/board`) — party board + (`type: custom:nestquest-party-board-card`): the household quest + board. Set `weather_entity` (optional; the dock falls back to + date/time), `quest_log_path: /nestquest/log` (the view the crest tap + navigates to), and `child_order: [slug1, slug2, slug3]` listing each + child's slug in display order. + + - **View 2 path `log`** (URL `/nestquest/log`) — quest log + (`type: custom:nestquest-quest-log-card`): the per-child quest screen + the party board hops to. Set `board_path: /nestquest/board` (the + party-board view to return to), `weather_entity`, + `idle_return_seconds` (must be shorter than TouchHub Auto-Return), + `confirm_timeout_seconds`, and `complete_screen_seconds`. The quest + log reads the selected child's slug from the view's URL path, so the + board → log hop is a plain Lovelace navigation. + + Each view's layout MUST be set to **Panel** (Edit Dashboard → pencil on the + view → View type: Panel). Home Assistant's default Masonry layout constrains + the 1080px-tall NestQuest card to a narrow column; Panel (single-card) + layout is what renders the card full-screen at 1920×1080. + + The dashboard contains only the NestQuest panel card for each view — + no other cards, no admin controls. + + The `quest_log_path` and `board_path` values must match the dashboard + slug and view paths above: `//`. If you chose `nestquest` + as the dashboard slug with view paths `board` and `log`, the card + configs are already correct. If you pick different names, substitute + your actual paths in **both** card configs — e.g. a dashboard slug + `chores` with view paths `main` and `history` needs + `quest_log_path: /chores/history` and `board_path: /chores/main`. + Navigation between the two views fails (the crest tap or back button + lands on a 404) if the card config paths do not match the dashboard + slug and view paths exactly. + + See `design/ENTITIES-AND-SERVICES.md` §4 for the full card YAML. + +3. **Point TouchHub at it.** In the TouchHub launcher, add the **Home + Assistant Lovelace** app to the dock and paste the dashboard URL from + step 2 — the party-board view URL, e.g. `/nestquest/board` (TouchHub + loads that URL directly as the kiosk landing page). Set TouchHub + **Auto-Return** to a value GREATER than the quest log's + `idle_return_seconds` (default 40s) — recommend **60s or higher**. If + Auto-Return is at or below 40s, TouchHub returns to its launcher before + the quest log's idle return can navigate back to the party board. + +4. **Log the kiosk session in.** Sign in on the device as the dedicated + kiosk user from step 1, and leave that session logged in. + +TouchHub loads the dashboard URL directly. The NestQuest cards are normal +Lovelace cards, not an iframe, so no Home Assistant framing setting is +needed. + ## Developer Setup and Testing This project uses [`uv`](https://github.com/astral-sh/uv) for fast Python package and dependency management. diff --git a/tests/test_frontend.py b/tests/test_frontend.py index 59c5989..3fc7e94 100644 --- a/tests/test_frontend.py +++ b/tests/test_frontend.py @@ -163,3 +163,19 @@ def test_frontend_and_www_have_no_cdn_font_references() -> None: text = path.read_text(encoding="utf-8", errors="ignore") for marker in CDN_MARKERS: assert marker not in text, f"{path}: {marker}" + + +def test_readme_documents_the_touchhub_setup_runbook() -> None: + readme = " ".join( + (REPO_ROOT / "README.md").read_text(encoding="utf-8").split() + ) + for required in ("TouchHub", "kiosk"): + assert required in readme, required + assert "not in the NestQuest admin allowlist" in readme + assert "contains only the NestQuest panel card" in readme + assert "nestquest-party-board-card" in readme + assert "nestquest-quest-log-card" in readme + assert "Panel" in readme + assert "Auto-Return" in readme + assert "use_x_frame_options" not in readme + assert "clickjacking" not in readme