Files
NestQuest/custom_components/nestquest/www/nestquest-cards.js
T
overseerandClaude Opus 5.5 235c327608 Keep the quest-log meta line's class when a quest is not late
The open quest card built its meta line as
class="meta ${meta.late ? "late" : nothing}"; Lit drops the whole
attribute when any interpolated part is nothing, so every on-time quest
lost the meta class and its styling. Choose the full class string
instead, rebuild the panel bundle, and add a rendered regression test.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-26 09:39:04 -04:00

3565 lines
104 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
const B = globalThis, lt = B.ShadowRoot && (B.ShadyCSS === void 0 || B.ShadyCSS.nativeShadow) && "adoptedStyleSheets" in Document.prototype && "replace" in CSSStyleSheet.prototype, ct = /* @__PURE__ */ Symbol(), ft = /* @__PURE__ */ new WeakMap();
let Dt = class {
constructor(t, e, n) {
if (this._$cssResult$ = !0, n !== ct) 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 (lt && t === void 0) {
const n = e !== void 0 && e.length === 1;
n && (t = ft.get(e)), t === void 0 && ((this.o = t = new CSSStyleSheet()).replaceSync(this.cssText), n && ft.set(e, t));
}
return t;
}
toString() {
return this.cssText;
}
};
const x = (s) => new Dt(typeof s == "string" ? s : s + "", void 0, ct), Ht = (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.");
})(i) + s[r + 1], s[0]);
return new Dt(e, s, ct);
}, Jt = (s, t) => {
if (lt) s.adoptedStyleSheets = t.map((e) => e instanceof CSSStyleSheet ? e : e.styleSheet);
else for (const e of t) {
const n = document.createElement("style"), i = B.litNonce;
i !== void 0 && n.setAttribute("nonce", i), n.textContent = e.cssText, s.appendChild(n);
}
}, mt = lt ? (s) => s : (s) => s instanceof CSSStyleSheet ? ((t) => {
let e = "";
for (const n of t.cssRules) e += n.cssText;
return x(e);
})(s) : s;
const { is: Xt, defineProperty: Yt, getOwnPropertyDescriptor: te, getOwnPropertyNames: ee, getOwnPropertySymbols: ne, getPrototypeOf: se } = Object, V = globalThis, gt = V.trustedTypes, ie = gt ? gt.emptyScript : "", re = V.reactiveElementPolyfillSupport, I = (s, t) => s, it = { toAttribute(s, t) {
switch (t) {
case Boolean:
s = s ? ie : null;
break;
case Object:
case Array:
s = s == null ? s : JSON.stringify(s);
}
return s;
}, fromAttribute(s, t) {
let e = s;
switch (t) {
case Boolean:
e = s !== null;
break;
case Number:
e = s === null ? null : Number(s);
break;
case Object:
case Array:
try {
e = JSON.parse(s);
} catch {
e = null;
}
}
return e;
} }, Bt = (s, t) => !Xt(s, t), _t = { attribute: !0, type: String, converter: it, reflect: !1, useDefault: !1, hasChanged: Bt };
Symbol.metadata ??= /* @__PURE__ */ Symbol("metadata"), V.litPropertyMetadata ??= /* @__PURE__ */ new WeakMap();
let E = 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 = _t) {
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 && Yt(this.prototype, t, i);
}
}
static getPropertyDescriptor(t, e, n) {
const { get: i, set: r } = te(this.prototype, t) ?? { get() {
return this[e];
}, set(o) {
this[e] = o;
} };
return { get: i, set(o) {
const l = i?.call(this);
r?.call(this, o), this.requestUpdate(t, l, n);
}, configurable: !0, enumerable: !0 };
}
static getPropertyOptions(t) {
return this.elementProperties.get(t) ?? _t;
}
static _$Ei() {
if (this.hasOwnProperty(I("elementProperties"))) return;
const t = se(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 = [...ee(e), ...ne(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, i] of e) this.elementProperties.set(n, i);
}
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);
}
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 i of n) e.unshift(mt(i));
} else t !== void 0 && e.push(mt(t));
return e;
}
static _$Eu(t, e) {
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();
}
_$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 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 Jt(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, n) {
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 : it).toAttribute(e, n.type);
this._$Em = t, r == null ? this.removeAttribute(i) : this.setAttribute(i, 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 : it;
this._$Em = i;
const l = o.fromAttribute(e, r.type);
this[i] = l ?? this._$Ej?.get(i) ?? l, this._$Em = null;
}
}
requestUpdate(t, e, n, i = !1, r) {
if (t !== void 0) {
const o = this.constructor;
if (i === !1 && (r = this[t]), n ??= o.getPropertyOptions(t), !((n.hasChanged ?? Bt)(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));
}
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, r] of this._$Ep) this[i] = 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);
}
}
let t = !1;
const e = this._$AL;
try {
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);
}
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) {
}
};
E.elementStyles = [], E.shadowRootOptions = { mode: "open" }, E[I("elementProperties")] = /* @__PURE__ */ new Map(), E[I("finalized")] = /* @__PURE__ */ new Map(), re?.({ ReactiveElement: E }), (V.reactiveElementVersions ??= []).push("2.1.2");
const dt = globalThis, yt = (s) => s, F = dt.trustedTypes, bt = F ? F.createPolicy("lit-html", { createHTML: (s) => s }) : void 0, Ft = "$lit$", $ = `lit$${Math.random().toFixed(9).slice(2)}$`, Zt = "?" + $, oe = `<${Zt}>`, S = document, U = () => S.createComment(""), R = (s) => s === null || typeof s != "object" && typeof s != "function", pt = Array.isArray, ae = (s) => pt(s) || typeof s?.[Symbol.iterator] == "function", X = `[
\f\r]`, z = /<(?:(!--|\/[^a-zA-Z])|(\/?[a-zA-Z][^>\s]*)|(\/?$))/g, vt = /-->/g, xt = />/g, q = RegExp(`>|${X}(?:([^\\s"'>=/]+)(${X}*=${X}*(?:[^
\f\r"'\`<>=]|("|')|))|$)`, "g"), wt = /'/g, $t = /"/g, Qt = /^(?:script|style|textarea|title)$/i, le = (s) => (t, ...e) => ({ _$litType$: s, strings: t, values: e }), d = le(1), T = /* @__PURE__ */ Symbol.for("lit-noChange"), g = /* @__PURE__ */ Symbol.for("lit-nothing"), qt = /* @__PURE__ */ new WeakMap(), A = S.createTreeWalker(S, 129);
function Wt(s, t) {
if (!pt(s) || !s.hasOwnProperty("raw")) throw Error("invalid template strings array");
return bt !== void 0 ? bt.createHTML(t) : t;
}
const ce = (s, t) => {
const e = s.length - 1, n = [];
let i, r = t === 2 ? "<svg>" : t === 3 ? "<math>" : "", o = z;
for (let l = 0; l < e; l++) {
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 = vt : u[1] !== void 0 ? o = xt : u[2] !== void 0 ? (Qt.test(u[2]) && (i = RegExp("</" + u[2], "g")), o = q) : u[3] !== void 0 && (o = q) : o === q ? u[0] === ">" ? (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 : wt) : o === $t || o === wt ? o = q : o === vt || o === xt ? o = z : (o = q, i = void 0);
const h = o === q && s[l + 1].startsWith("/>") ? " " : "";
r += o === z ? a + oe : c >= 0 ? (n.push(p), a.slice(0, c) + Ft + a.slice(c) + $ + h) : a + $ + (c === -2 ? l : h);
}
return [Wt(s, r + (s[e] || "<?>") + (t === 2 ? "</svg>" : t === 3 ? "</math>" : "")), n];
};
class L {
constructor({ strings: t, _$litType$: e }, n) {
let i;
this.parts = [];
let r = 0, o = 0;
const l = t.length - 1, a = this.parts, [p, u] = ce(t, e);
if (this.el = L.createElement(p, n), A.currentNode = this.el.content, e === 2 || e === 3) {
const c = this.el.content.firstChild;
c.replaceWith(...c.childNodes);
}
for (; (i = A.nextNode()) !== null && a.length < l; ) {
if (i.nodeType === 1) {
if (i.hasAttributes()) for (const c of i.getAttributeNames()) if (c.endsWith(Ft)) {
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] === "." ? pe : m[1] === "?" ? he : m[1] === "@" ? ue : K }), i.removeAttribute(c);
} else c.startsWith($) && (a.push({ type: 6, index: r }), i.removeAttribute(c));
if (Qt.test(i.tagName)) {
const c = i.textContent.split($), f = c.length - 1;
if (f > 0) {
i.textContent = F ? F.emptyScript : "";
for (let h = 0; h < f; h++) i.append(c[h], U()), A.nextNode(), a.push({ type: 2, index: ++r });
i.append(c[f], U());
}
}
} else if (i.nodeType === 8) if (i.data === Zt) a.push({ type: 2, index: r });
else {
let c = -1;
for (; (c = i.data.indexOf($, c + 1)) !== -1; ) a.push({ type: 7, index: r }), c += $.length - 1;
}
r++;
}
}
static createElement(t, e) {
const n = S.createElement("template");
return n.innerHTML = t, n;
}
}
function N(s, t, e = s, n) {
if (t === T) return t;
let i = n !== void 0 ? e._$Co?.[n] : e._$Cl;
const r = R(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 = N(s, i._$AS(s, t.values), i, n)), t;
}
class de {
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: n } = this._$AD, i = (t?.creationScope ?? S).importNode(e, !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 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 fe(r, this, t)), this._$AV.push(p), a = n[++l];
}
o !== a?.index && (r = A.nextNode(), o++);
}
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++;
}
}
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;
}
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 = N(this, t, e), R(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) : ae(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 !== g && R(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 = L.createElement(Wt(n.h, n.h[0]), this.options)), n);
if (this._$AH?._$AD === i) this._$AH.p(e);
else {
const r = new de(i, this), o = r.u(this.options);
r.p(e), this.T(o), this._$AH = r;
}
}
_$AC(t) {
let e = qt.get(t.strings);
return e === void 0 && qt.set(t.strings, e = new L(t)), e;
}
k(t) {
pt(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 O(this.O(U()), this.O(U()), 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 n = yt(t).nextSibling;
yt(t).remove(), t = n;
}
}
setConnected(t) {
this._$AM === void 0 && (this._$Cv = t, this._$AP?.(t));
}
}
class K {
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;
}
_$AI(t, e = this, n, i) {
const r = this.strings;
let o = !1;
if (r === void 0) t = N(this, t, e, 0), o = !R(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 = N(this, l[n + a], e, a), p === T && (p = this._$AH[a]), o ||= !R(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);
}
j(t) {
t === g ? this.element.removeAttribute(this.name) : this.element.setAttribute(this.name, t ?? "");
}
}
class pe extends K {
constructor() {
super(...arguments), this.type = 3;
}
j(t) {
this.element[this.name] = t === g ? void 0 : t;
}
}
class he extends K {
constructor() {
super(...arguments), this.type = 4;
}
j(t) {
this.element.toggleAttribute(this.name, !!t && t !== g);
}
}
class ue extends K {
constructor(t, e, n, i, r) {
super(t, e, n, i, r), this.type = 5;
}
_$AI(t, e = this) {
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);
}
}
class fe {
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) {
N(this, t);
}
}
const me = { I: O }, ge = dt.litHtmlPolyfillSupport;
ge?.(L, O), (dt.litHtmlVersions ??= []).push("3.3.3");
const _e = (s, t, e) => {
const n = e?.renderBefore ?? t;
let i = n._$litPart$;
if (i === void 0) {
const r = e?.renderBefore ?? null;
n._$litPart$ = i = new O(t.insertBefore(U(), r), r, void 0, e ?? {});
}
return i._$AI(s), i;
};
const ht = globalThis;
let C = class extends E {
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 = _e(e, this.renderRoot, this.renderOptions);
}
connectedCallback() {
super.connectedCallback(), this._$Do?.setConnected(!0);
}
disconnectedCallback() {
super.disconnectedCallback(), this._$Do?.setConnected(!1);
}
render() {
return T;
}
};
C._$litElement$ = !0, C.finalized = !0, ht.litElementHydrateSupport?.({ LitElement: C });
const ye = ht.litElementPolyfillSupport;
ye?.({ LitElement: C });
(ht.litElementVersions ??= []).push("4.2.2");
const Gt = ':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 ut(s) {
window.customCards = window.customCards ?? [], window.customCards.push(s);
}
const be = [
"nestquest_quest_completed",
"nestquest_quest_uncompleted",
"nestquest_quest_missed",
"nestquest_child_day_complete"
], kt = /* @__PURE__ */ new Map();
function Z(s, t) {
const e = `${s ?? "local"}|${JSON.stringify(t)}`, n = kt.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 kt.set(e, i), i;
}
function ve(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 Y(s, t) {
return `${Z(t, { weekday: "long" }).format(s)}, ${Z(t, { month: "short", day: "numeric" }).format(s)}`;
}
function xe(s, t) {
return Z(t, {
hour: "numeric",
minute: "2-digit",
hour12: !0
}).format(s);
}
const we = {
"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"
}, $e = {
"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 At(s) {
return typeof s == "string" ? s.trim() : "";
}
function tt(s, t) {
if (s == null || s === "")
return t;
const e = typeof s == "number" ? s : Number(s);
return Number.isFinite(e) ? e : t;
}
function qe(s) {
if (s === null || typeof s != "object" || Array.isArray(s))
return "";
const t = s.slug;
return typeof t == "string" ? t.trim() : "";
}
function Ct(s) {
return Number.isFinite(s) ? Math.min(100, Math.max(0, s)) : 0;
}
function St(s) {
return Math.max(0, Math.round(s));
}
function ke(s) {
return s.split(/[-_]+/).filter(Boolean).map((t) => t.charAt(0).toUpperCase() + t.slice(1)).join(" ");
}
function Ae(s, t) {
const e = Z(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 o = e.find((l) => l.type === r);
return o ? Number(o.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 Ce(s, t) {
if (typeof s != "string")
return null;
const e = s.split("-");
if (e.length !== 3)
return null;
const [n, i, r] = e.map((a) => Number(a));
if (!n || !i || !r)
return null;
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 - Ae(o, t));
return Number.isNaN(l.getTime()) ? null : l;
}
function Se(s) {
const t = we[s];
return t || s.charAt(0).toUpperCase() + s.slice(1);
}
function Te(s, t) {
return $e[s] ?? t;
}
const Tt = "polygon(0 0, 100% 0, 100% 62%, 50% 100%, 0 62%)", Et = "M50 3 44.5 24.1 50 26 55.5 24.1Z M100 50 75.9 44.5 74 50 75.9 55.5Z M50 97 55.5 75.9 50 74 44.5 75.9Z M0 50 24.1 55.5 26 50 24.1 44.5Z M29.1 29.1 29.7 36.3 36.3 29.7Z M70.9 29.1 63.7 29.7 70.3 36.3Z M70.9 70.9 70.3 63.7 63.7 70.3Z M29.1 70.9 36.3 70.3 29.7 63.7Z", Ee = "M50 26.5 70.4 38.2 70.4 61.8 50 73.5 29.6 61.8 29.6 38.2Z", Ne = "M29.6 38.2H70.4M29.6 61.8H70.4M50 26.5 36.5 38.2M50 26.5 63.5 38.2M50 73.5 36.5 61.8M50 73.5 63.5 61.8M36.5 38.2V61.8M63.5 38.2V61.8M29.6 38.2 36.5 61.8M36.5 38.2 29.6 61.8M70.4 38.2 63.5 61.8M63.5 38.2 70.4 61.8", Me = d`<svg
class="monogram"
viewBox="0 0 100 100"
aria-hidden="true"
focusable="false"
>
<defs>
<linearGradient
id="nq-monogram-fill"
gradientUnits="userSpaceOnUse"
x1="0"
y1="0"
x2="100"
y2="100"
>
<stop offset="0" style="stop-color: var(--nq-brand-blue)" />
<stop offset="1" style="stop-color: var(--nq-brand-purple)" />
</linearGradient>
<mask id="nq-monogram-points-gap" maskUnits="userSpaceOnUse">
<rect width="100" height="100" fill="#fff" />
<path d=${Et} stroke="#000" stroke-width="3.5" />
</mask>
<mask id="nq-monogram-facets" maskUnits="userSpaceOnUse">
<rect width="100" height="100" fill="#fff" />
<path d=${Ne} stroke="#000" stroke-width="1.6" />
<text
x="50"
y="59.4"
text-anchor="middle"
font-family="'Cinzel Decorative', Cinzel, serif"
font-weight="900"
font-size="25"
fill="#fff"
stroke="#000"
stroke-width="2"
paint-order="stroke"
>N</text>
</mask>
</defs>
<g fill="url(#nq-monogram-fill)">
<path d=${Et} />
<g mask="url(#nq-monogram-points-gap)">
<circle
cx="50"
cy="50"
r="34.25"
fill="none"
stroke="url(#nq-monogram-fill)"
stroke-width="6.5"
/>
<path mask="url(#nq-monogram-facets)" d=${Ee} />
</g>
</g>
</svg>`, Oe = Ht`
* {
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);
}
.monogram {
flex: none;
display: block;
width: 80px;
height: 80px;
}
.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: ${x(Tt)};
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: ${x(Tt)};
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: 896px;
left: 0;
right: 0;
margin: 0;
font-family: var(--nq-p-font-body);
font-size: 27px;
font-weight: 600;
line-height: 1.4;
text-align: center;
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;
}
.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;
}
`, ze = d`<svg
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
aria-hidden="true"
>
<circle cx="12" cy="12" r="10"></circle>
<path d="m9 12 2 2 4-4"></path>
</svg>`, Pe = d`<svg
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
aria-hidden="true"
>
<path d="M3.5 21 12 3.5 20.5 21"></path>
<path d="M9 21l3-8 3 8"></path>
</svg>`, Ie = d`<svg
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
aria-hidden="true"
>
<circle cx="12" cy="12" r="10"></circle>
<polygon points="16.24 7.76 14.12 14.12 7.76 16.24 9.88 9.88 16.24 7.76"></polygon>
</svg>`, Ue = d`<svg
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
aria-hidden="true"
>
<path d="m21.73 18-8-14a2 2 0 0 0-3.48 0l-8 14A2 2 0 0 0 4 21h16a2 2 0 0 0 1.73-3Z"></path>
<path d="M12 9v4"></path>
<path d="M12 17h.01"></path>
</svg>`, Re = d`<svg
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
aria-hidden="true"
>
<path d="M17.5 19H9a7 7 0 1 1 6.71-9h1.79a4.5 4.5 0 1 1 0 9Z"></path>
<path d="m2 2 20 20"></path>
</svg>`, Nt = {
icon: Ue,
headline: "NestQuest is not set up yet",
body: "A parent needs to finish setting up NestQuest before the party can gather."
}, Le = {
icon: Re,
headline: "The records cannot be reached",
body: "The party's records are quiet right now. NestQuest will return shortly."
}, je = d`<svg
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
aria-hidden="true"
>
<path d="M12 2v2"></path>
<path d="m4.93 4.93 1.41 1.41"></path>
<path d="M20 12h2"></path>
<path d="m19.07 4.93-1.41 1.41"></path>
<path d="M15.947 12.65a4 4 0 0 0-5.925-4.128"></path>
<path d="M13 22H7a5 5 0 1 1 4.9-6H13a3 3 0 0 1 0 6Z"></path>
</svg>`, Q = class Q extends C {
constructor() {
super(...arguments), this._now = /* @__PURE__ */ new Date(), this._unsubs = [], this._subscribed = !1, this._subGeneration = 0;
}
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 be)
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._subscribeLive(), this._now = /* @__PURE__ */ new Date(), this._clockTimer = window.setInterval(() => {
this._now = /* @__PURE__ */ new Date();
}, 1e3);
}
updated() {
this._subscribeLive();
}
disconnectedCallback() {
this._unsubscribeLive(), this._clockTimer !== void 0 && (window.clearInterval(this._clockTimer), this._clockTimer = void 0), super.disconnectedCallback();
}
render() {
const t = this._boardNotice();
return d`
<div class="board">
<div class="frame frame-outer"></div>
<div class="frame frame-inner"></div>
<div class="wordmark-row">
<span class="rule"></span>
${Me}
<span class="wordmark">NestQuest</span>
<span class="rule"></span>
</div>
<p class="kicker">The Party · ${Y(this._now, this._timeZone())}</p>
${t ? this._renderNotice(t) : d`
<div class="plates">
${this._plates().map((e) => this._renderPlate(e))}
</div>
<p class="hint">Tap your crest to open your Quest Log</p>
`}
<div class="spacer"></div>
${this._renderDock()}
</div>
`;
}
_timeZone() {
return ve(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()) : this._discoveredChildSlugs();
}
/** Zero-config discovery (Feature 20): the ordered child roster the
* integration publishes on the household rollup
* (custom_components/nestquest/sensor.py — ``child_roster``, the
* snapshot's sort_order, then id), each entry carrying the slug the
* per-child entity ids are built from. Consulted only when the
* config has no child_order — explicit configuration wins. */
_discoveredChildSlugs() {
const e = this._state(
"sensor.nestquest_household_quests_due_today"
)?.attributes?.child_roster;
return Array.isArray(e) ? e.map((n) => qe(n)).filter((n) => n.length > 0) : [];
}
_plates() {
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(
`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 = St(tt(n?.state, 0)), p = St(tt(i?.state, 0)), u = r ? tt(r.state, Number.NaN) : Number.NaN, c = Number.isFinite(u) ? Ct(u) : a > 0 ? Ct(p / 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 _ = l.child_name;
if (typeof _ == "string" && _.trim())
m = _.trim();
else {
const J = o?.attributes?.child_name;
typeof J == "string" && J.trim() && (m = J.trim());
}
m || (m = ke(t));
const v = Ce(
o?.attributes?.next_present,
this._timeZone()
), j = v ? `Returns ${Y(v, this._timeZone())}` : null;
return {
slug: t,
name: m,
initial: (m.charAt(0) || "?").toUpperCase(),
present: e === null ? f : !1,
completed: p,
due: a,
pct: c,
returns: j,
unresolved: e
};
}
/** "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(t) {
const e = this._state(`sensor.nestquest_${t}_quests_due_today`);
if (!e)
return "missing";
const n = String(e.state ?? "").trim().toLowerCase();
return !n || n === "unavailable" || n === "unknown" ? "stale" : null;
}
_boardNotice() {
const t = this._plates();
return t.length === 0 ? Nt : t.every((e) => e.unresolved !== null) ? t.some((e) => e.unresolved === "stale") ? Le : Nt : null;
}
_renderNotice(t) {
return d`
<div class="notice-wrap">
<div class="notice" role="status">
${t.icon}
<span class="notice-headline">${t.headline}</span>
<p class="notice-body">${t.body}</p>
</div>
</div>
`;
}
_renderPlate(t) {
return t.unresolved ? d`
<div class="plate unknown">
<span class="crest away">
<span class="crest-face">
<span class="initial">${t.initial}</span>
</span>
</span>
<span class="name">${t.name}</span>
<span class="pill away">
${Ie}
<span>Unknown</span>
</span>
<span class="progress-line">&mdash;</span>
<span class="bar"></span>
</div>
` : t.present ? d`
<button
class="plate"
type="button"
aria-label="Open ${t.name}'s Quest Log"
@click=${() => this._openQuestLog(t.slug)}
>
<span class="crest">
<span class="crest-face">
<span class="initial">${t.initial}</span>
</span>
</span>
<span class="name">${t.name}</span>
<span class="pill">
${ze}
<span>Home today</span>
</span>
<span class="progress-line">
${t.completed} of ${t.due} quests claimed
</span>
<span class="bar">
<span class="fill" style="width: ${t.pct}%"></span>
</span>
</button>
` : d`
<div class="plate away">
<span class="crest away">
<span class="crest-face">
<span class="initial">${t.initial}</span>
</span>
</span>
<span class="name">${t.name}</span>
<span class="pill away">
${Pe}
<span>On travels</span>
</span>
<span class="progress-line">${t.returns ?? "Returns —"}</span>
<span class="bar"></span>
</div>
`;
}
_openQuestLog(t) {
const e = At(this._config?.quest_log_path);
e && (window.history.pushState(null, "", `${e.replace(/\/+$/, "")}/${t}`), window.dispatchEvent(new Event("location-changed")));
}
_dockWeather() {
const t = At(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, o = null;
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);
}
}
return {
condition: n,
temperature: this._optionalNumber(i.temperature),
high: r,
low: o
};
}
_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 d`
<div class="dock">
<span class="date">${Y(this._now, this._timeZone())}</span>
<span class="divider"></span>
<span class="clock">${xe(this._now, this._timeZone())}</span>
</div>
`;
const e = Se(t.condition), n = Te(t.condition, e), i = t.temperature === null ? g : d`<span class="temp">${Math.round(t.temperature)}°</span>`, r = t.high === null || t.low === null ? null : `${Math.round(t.high)}° / ${Math.round(t.low)}°`;
return d`
<div class="dock">
${je}
${i}
<span class="divider"></span>
<span class="condition">
${r === null ? e : `${e} · ${r}`}
</span>
<span class="divider"></span>
<span class="condition">${n}</span>
</div>
`;
}
};
Q.properties = {
hass: { attribute: !1 },
_config: { state: !0 },
_now: { state: !0 }
}, Q.styles = [x(Gt), Oe];
let rt = Q;
customElements.define("nestquest-party-board-card", rt);
ut({
type: "nestquest-party-board-card",
name: "NestQuest Party Board",
description: "Kids' attract screen for present and away adventurers."
});
const De = { CHILD: 2 }, He = (s) => (...t) => ({ _$litDirective$: s, values: t });
let Be = 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: Fe } = me, Mt = (s) => s, Ot = () => 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(Ot(), i), o = n.insertBefore(Ot(), i);
e = new Fe(r, o, s, s.options);
} else {
const r = e._$AB.nextSibling, o = e._$AM, l = o !== s;
if (l) {
let 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 = e._$AA;
for (; a !== r; ) {
const p = Mt(a).nextSibling;
Mt(n).insertBefore(a, i), a = p;
}
}
}
return e;
}, k = (s, t, e = s) => (s._$AI(t, e), s), Ze = {}, Qe = (s, t = Ze) => s._$AH = t, We = (s) => s._$AH, et = (s) => {
s._$AR(), s._$AA.remove();
};
const zt = (s, t, e) => {
const n = /* @__PURE__ */ new Map();
for (let i = t; i <= e; i++) n.set(s[i], i);
return n;
}, Pt = He(class extends Be {
constructor(s) {
if (super(s), s.type !== De.CHILD) throw Error("repeat() can only be used in text expressions");
}
dt(s, t, e) {
let n;
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] = e(l, o), o++;
return { values: r, keys: i };
}
render(s, t, e) {
return this.dt(s, t, e).values;
}
update(s, [t, e, n]) {
const i = We(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, 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 = zt(o, h, m), u = zt(l, c, f)), p.has(l[c])) if (p.has(l[f])) {
const _ = u.get(o[h]), v = _ !== void 0 ? i[_] : null;
if (v === null) {
const j = P(s, i[c]);
k(j, r[h]), a[h] = j;
} else a[h] = k(v, r[h]), P(s, i[c], v), i[_] = null;
h++;
} else et(i[f]), f--;
else et(i[c]), c++;
for (; h <= m; ) {
const _ = P(s, a[m + 1]);
k(_, r[h]), a[h++] = _;
}
for (; c <= f; ) {
const _ = i[c++];
_ !== null && et(_);
}
return this.ut = o, Qe(s, a), T;
}
}), Ge = [
"nestquest_quest_completed",
"nestquest_quest_uncompleted",
"nestquest_quest_missed",
"nestquest_child_day_complete"
], It = /* @__PURE__ */ new Map();
function M(s, t) {
const e = `${s ?? "local"}|${JSON.stringify(t)}`, n = It.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 It.set(e, i), i;
}
function Ve(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 D(s, t) {
return `${M(t, { weekday: "long" }).format(s)}, ${M(t, { month: "short", day: "numeric" }).format(s)}`;
}
function Ke(s, t) {
return M(t, { weekday: "long" }).format(s);
}
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 Xe(s, t) {
return M(t, {
hour: "numeric",
minute: "2-digit",
hour12: !0
}).format(s);
}
function Ye(s) {
if (!s)
return null;
const t = /^(\d{1,2}):(\d{2})$/.exec(s.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 w(s) {
return typeof s == "string" ? s.trim() : "";
}
function y(s, t) {
if (s == null || s === "")
return t;
const e = typeof s == "number" ? s : Number(s);
return Number.isFinite(e) ? e : t;
}
function b(s) {
return Math.max(0, Math.round(s));
}
function nt(s) {
return s.split(/[-_]+/).filter(Boolean).map((t) => t.charAt(0).toUpperCase() + t.slice(1)).join(" ");
}
function tn(s, t) {
const e = M(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 o = e.find((l) => l.type === r);
return o ? Number(o.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 Ut(s, t) {
if (typeof s != "string")
return null;
const e = s.split("-");
if (e.length !== 3)
return null;
const [n, i, r] = e.map((a) => Number(a));
if (!n || !i || !r)
return null;
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 - tn(o, t));
return Number.isNaN(l.getTime()) ? null : l;
}
function en() {
const s = window.location.pathname.split("/").filter(Boolean);
return s.length > 0 ? s[s.length - 1].toLowerCase() : "";
}
function nn(s) {
if (!s || typeof s != "object")
return null;
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 i = y(t.child_id, 0);
return {
id: n,
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: w(t.completed_at) || null,
on_time: t.on_time === !0 ? !0 : t.on_time === !1 ? !1 : null
};
}
const sn = {
"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"
}, rn = {
"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 on(s) {
const t = sn[s];
return t || s.charAt(0).toUpperCase() + s.slice(1);
}
function an(s, t) {
return rn[s] ?? t;
}
const Rt = "polygon(0 0, 100% 0, 100% 62%, 50% 100%, 0 62%)", ln = "polygon(50% 0, 93% 25%, 93% 75%, 50% 100%, 7% 75%, 7% 25%)", Lt = [-9, 6, -4], cn = Ht`
* {
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: 4px;
clip-path: ${x(Rt)};
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: ${x(Rt)};
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;
}
.crest.away .crest-face {
background: var(--nq-p-crest-away);
}
.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: ${x(ln)};
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);
}
.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;
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;
}
.toast {
position: absolute;
left: 50%;
bottom: 158px;
z-index: 20;
transform: translateX(-50%);
max-width: calc(100% - 124px);
padding: 20px 32px;
border: 2px solid var(--nq-p-panel-border);
border-radius: 14px;
background: var(--nq-p-card-panel);
box-shadow: var(--nq-p-panel-shadow);
font-family: var(--nq-p-font-body);
font-size: 23px;
font-weight: 600;
line-height: 1.2;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
color: var(--nq-p-ink-secondary);
animation: toast-in var(--nq-dur-base) var(--nq-ease-out);
pointer-events: none;
}
@keyframes toast-in {
from {
opacity: 0;
transform: translate(-50%, 12px);
}
to {
opacity: 1;
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;
}
`, H = d`<svg
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
aria-hidden="true"
>
<path d="M20 6 9 17l-5-5"></path>
</svg>`, dn = d`<svg
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
aria-hidden="true"
>
<path d="M18 6 6 18"></path>
<path d="m6 6 12 12"></path>
</svg>`, st = d`<svg
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
aria-hidden="true"
>
<polygon
points="12 2 15.09 8.26 22 9.27 17 14.14 18.18 21.02 12 17.77 5.82 21.02 7 14.14 2 9.27 8.91 8.26"
></polygon>
</svg>`, pn = d`<svg
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
aria-hidden="true"
>
<path d="M12 2v8"></path>
<path d="m4.93 10.93 1.41 1.41"></path>
<path d="M2 18h2"></path>
<path d="M20 18h2"></path>
<path d="m19.07 10.93-1.41 1.41"></path>
<path d="M22 22H2"></path>
<path d="m8 6 4-4 4 4"></path>
<path d="M16 18a4 4 0 0 0-8 0"></path>
</svg>`, Vt = d`<svg
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
aria-hidden="true"
>
<circle cx="12" cy="12" r="4"></circle>
<path d="M12 2v2"></path>
<path d="M12 20v2"></path>
<path d="m4.93 4.93 1.41 1.41"></path>
<path d="m17.66 17.66 1.41 1.41"></path>
<path d="M2 12h2"></path>
<path d="M20 12h2"></path>
<path d="m6.34 17.66-1.41 1.41"></path>
<path d="m19.07 4.93-1.41 1.41"></path>
</svg>`, hn = d`<svg
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
aria-hidden="true"
>
<path d="M12 3a6 6 0 0 0 9 9 9 9 0 1 1-9-9Z"></path>
</svg>`, un = d`<svg
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
aria-hidden="true"
>
<path d="M3.5 21 12 3.5 20.5 21"></path>
<path d="M9 21l3-8 3 8"></path>
</svg>`, fn = d`<svg
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
aria-hidden="true"
>
<circle cx="12" cy="12" r="10"></circle>
<polygon points="16.24 7.76 14.12 14.12 7.76 16.24 9.88 9.88 16.24 7.76"></polygon>
</svg>`, mn = d`<svg
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
aria-hidden="true"
>
<path d="m21.73 18-8-14a2 2 0 0 0-3.48 0l-8 14A2 2 0 0 0 4 21h16a2 2 0 0 0 1.73-3Z"></path>
<path d="M12 9v4"></path>
<path d="M12 17h.01"></path>
</svg>`, gn = d`<svg
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
aria-hidden="true"
>
<path d="M17.5 19H9a7 7 0 1 1 6.71-9h1.79a4.5 4.5 0 1 1 0 9Z"></path>
<path d="m2 2 20 20"></path>
</svg>`, _n = d`<svg
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
aria-hidden="true"
>
<path d="M13 16a3 3 0 1 1 0 6H7a5 5 0 1 1 4.9-6Z"></path>
<path d="M10.1 9A6 6 0 0 1 16 4a4.5 4.5 0 0 0 8.9 4.2v.1a6 6 0 0 1-5.2 5.7"></path>
</svg>`, yn = {
icon: fn,
headline: "No adventurer chosen",
body: "Open The Party and tap your crest to open your quest log."
}, bn = {
icon: mn,
headline: "NestQuest is not set up yet",
body: "A parent needs to finish setting up NestQuest."
}, vn = {
icon: gn,
headline: "The records cannot be reached",
body: "The party's records are quiet right now. NestQuest will return shortly."
}, jt = [
{ key: "morning", name: "Morning", range: "Until 11:59 AM", icon: pn },
{ key: "afternoon", name: "Afternoon", range: "12:00–5:00 PM", icon: Vt },
{ key: "evening", name: "Evening", range: "5:00–9:00 PM", icon: hn }
], W = class W extends C {
constructor() {
super(...arguments), this._now = /* @__PURE__ */ new Date(), this._confirm = null, this._optimistic = /* @__PURE__ */ new Map(), this._toast = null, this._countdown = null, this._unsubs = [], this._subscribed = !1, this._subGeneration = 0, this._onActivity = () => {
this._logView().kind !== "complete-day" && this._armIdle(), this._confirm !== null && this._armConfirmTimer();
};
}
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 Ge)
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._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._subscribeLive(), this._logView().kind === "complete-day" ? (this._clearIdle(), this._armCompleteTimer()) : (this._clearCompleteTimer(), this._countdown = null, this._idleTimer === void 0 && this._armIdle());
}
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 d`
<div class="board">
<div class="frame frame-outer"></div>
<div class="frame frame-inner"></div>
${this._renderMain(t)}
${this._renderConfirm()}
${this._renderToast()}
</div>
`;
}
_renderToast() {
return this._toast === null ? g : d`
<div class="toast" role="status">${this._toast}</div>
`;
}
_renderMain(t) {
switch (t.kind) {
case "no-adventurer":
return this._renderNotice(yn);
case "not-set-up":
return this._renderNotice(bn);
case "unreachable":
return this._renderNotice(vn);
case "away": {
const e = ["The quest log unlocks when they return."];
return t.returns && e.push(`Returns ${t.returns}`), d`
${this._renderHeader(t)}
<div class="notice-wrap">
<div class="notice away" role="status">
${un}
<span class="notice-headline">${t.name} is on travels</span>
${e.map((n) => d`<p class="notice-body">${n}</p>`)}
</div>
</div>
`;
}
case "empty-day":
return d`
${this._renderHeader(t)}
<div class="notice-wrap">
<div class="notice" role="status">
${Vt}
<span class="notice-headline">No quests today</span>
<p class="notice-body">
${`Nothing was posted for today, ${t.name}. Enjoy the day's rest!`}
</p>
</div>
</div>
`;
case "complete-day": {
const e = this._completeStats(), n = this._countdown ?? this._completeSeconds();
return d`
<div class="complete-screen" role="status">
<span class="complete-seal" aria-hidden="true">${H}</span>
<h1 class="complete-title">Quest complete</h1>
<p class="complete-sub">${this._completeSub(t.name)}</p>
<p class="complete-countdown">
Returning to The Party in ${n} seconds
</p>
<div class="complete-stats">
<div class="complete-stat">
<span class="numeral">${e.claimed}</span>
<span class="label">Quests claimed</span>
</div>
<div class="complete-stat">
<span class="numeral">${e.onTime}</span>
<span class="label">On time</span>
</div>
<div class="complete-stat">
<span class="numeral">${e.late}</span>
<span class="label">Late</span>
</div>
<div class="complete-stat">
<span class="numeral">${e.party}</span>
<span class="label">The Party</span>
</div>
</div>
</div>
${this._renderDock()}
`;
}
default:
return d`
${this._renderHeader(t)}
<div class="columns">
${jt.map((e) => this._renderColumn(e))}
</div>
`;
}
}
/** Resolve the log's single render state; the priority order is
* design/PANEL-EMPTY-STATES.md §2. */
_logView() {
const t = this._childSlug();
if (!t)
return { kind: "no-adventurer" };
const e = this._dueSensorResolution(t);
if (e === "missing")
return { kind: "not-set-up" };
if (e === "stale")
return { kind: "unreachable" };
const n = this._childName(t) ?? nt(t);
if (!this._childPresent(t))
return { kind: "away", name: n, returns: this._awayReturns(t) };
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 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
* unreachable or the child is absent from the snapshot), null when
* it resolves. */
_dueSensorResolution(t) {
const e = this._state(`sensor.nestquest_${t}_quests_due_today`);
if (!e)
return "missing";
const n = String(e.state ?? "").trim().toLowerCase();
return !n || n === "unavailable" || n === "unknown" ? "stale" : null;
}
_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_${t}_present_today`
);
return i ? String(i.state ?? "").trim().toLowerCase() === "on" : !0;
}
_awayReturns(t) {
const e = this._timeZone(), n = this._state(
`binary_sensor.nestquest_${t}_present_today`
), i = Ut(
n?.attributes?.next_present,
e
);
return i ? D(i, e) : null;
}
_renderNotice(t) {
return d`
<div class="notice-wrap">
<div class="notice" role="status">
${t.icon}
<span class="notice-headline">${t.headline}</span>
<p class="notice-body">${t.body}</p>
</div>
</div>
`;
}
_timeZone() {
return Ve(this.hass);
}
_state(t) {
const n = this.hass?.states?.[t];
return n && typeof n == "object" ? n : null;
}
_childSlug() {
return en();
}
_boardPath() {
return w(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;
}
_completeSeconds() {
const t = y(this._config?.complete_screen_seconds, 12);
return t > 0 ? t : 12;
}
_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`
), i = b(y(e?.state, 0)), r = b(y(n?.state, 0));
return { due: i, completed: r };
}
_instances() {
const t = this._childSlug();
if (!t)
return [];
const e = this._state(`sensor.nestquest_${t}_quests_due_today`)?.attributes?.instances;
if (!Array.isArray(e))
return [];
const n = e.map(nn).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 t = this._childSlug();
if (!t)
return 0;
const e = this._state(
`sensor.nestquest_${t}_quests_remaining_today`
);
if (e)
return b(y(e.state, 0));
const { due: n, completed: i } = this._childCounts(t);
return b(n - i);
}
_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: b(y(e?.state, 0)),
due: b(y(t?.state, 0))
};
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 = b(t - e), i = this._partyCounts();
return {
claimed: t,
onTime: e,
late: n,
party: `${i.completed}/${i.due}`
};
}
_completeSub(t) {
const e = D(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(), 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)
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 = nt(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 c = this._state(
`sensor.nestquest_${l}_quests_completed_today`
), 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`
), v = Ut(
_?.attributes?.next_present,
n
);
m = v ? Ke(v, n) : null;
}
i.push({
slug: l,
name: p,
present: u,
due: f,
completed: h,
returnsWeekday: m
});
}
return i.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 ? nt(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`
<header class="header">
<span class="crest${n ? " away" : ""}" aria-hidden="true">
<span class="crest-face">
<span class="initial">${u}</span>
</span>
</span>
<div class="titles">
<h1 class="title">${p}</h1>
<p class="sub">
${D(this._now, this._timeZone())} ·
${n ? "On travels" : `${o} of ${r} claimed`}
</p>
</div>
${n ? g : d`
<div class="remaining">
<span class="d20" aria-hidden="true">
<span class="d20-numeral">${l}</span>
</span>
<div class="remaining-text">
<span class="remaining-count">
${l} quest${l === 1 ? "" : "s"} left
</span>
<span class="party-line">
Party progress · ${a.completed} of ${a.due} today
</span>
</div>
</div>
`}
</header>
`;
}
_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 = e.filter(
(l) => l.state === "completed"
), r = e.length;
let o = g;
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))
), c = Math.max(56, Math.min(72, u - 2));
let f = `--nq-p-quest-gap: ${p}px; --nq-p-quest-min-height: ${u}px; --nq-p-button-height: ${c}px`;
if (r >= 5) {
const h = Math.max(0, Math.min(22, Math.floor((u - c - 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 d`
<section
class="column"
aria-label="${t.name} quests"
style=${o}
>
<div class="column-head">
${t.icon}
<div class="column-names">
<span class="column-name">${t.name}</span>
<span class="column-range">${t.range}</span>
</div>
<span class="column-count">${i.length}/${e.length}</span>
</div>
<div class="stack">
${Pt(
n,
(l) => l.id,
(l) => this._renderQuest(l, !1, 0)
)}
${Pt(
i,
(l) => l.id,
(l, a) => this._renderQuest(l, !0, a)
)}
${t.key === "afternoon" ? this._renderRollup() : g}
</div>
</section>
`;
}
_renderQuest(t, e, n) {
if (e) {
const o = Lt[n % Lt.length], l = t.completed_at ? Je(t.completed_at, this._timeZone()) : "";
return d`
<div class="quest sealed" data-instance-id=${t.id}>
<span class="tile" aria-hidden="true">${st}</span>
<div class="body">
<span class="quest-title">${t.title}</span>
<span class="meta">
${l ? `Claimed ${l}` : "Claimed"}
</span>
</div>
<span
class="seal"
aria-hidden="true"
style="--seal-rot: ${o}deg"
>
${H}
</span>
</div>
`;
}
const i = Ye(t.due_time), r = t.overdue ? {
text: i ? `Overdue · due ${i}` : "Overdue",
late: !0
} : {
text: i ? `Due by ${i}` : "Due today",
late: !1
};
return d`
<div
class="quest tappable"
role="button"
tabindex="0"
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(t.id));
}}
>
<span class="tile" aria-hidden="true">${st}</span>
<div class="body">
<span class="quest-title">${t.title}</span>
<span class="${r.late ? "meta late" : "meta"}">${r.text}</span>
</div>
<button
class="complete"
type="button"
@click=${() => this._openConfirm(t.id)}
>
${H}
<span>Complete</span>
</button>
</div>
`;
}
_renderRollup() {
const t = this._otherChildren();
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 d`
<div class="rollup">
<span class="rollup-label">Party roll-up</span>
<p class="rollup-body">${e.join(" ")}</p>
</div>
`;
}
_renderConfirm() {
if (this._confirm === null)
return g;
const t = this._instances().find(
(n) => n.id === this._confirm
);
if (!t)
return g;
const e = jt.find((n) => n.key === t.window);
return d`
<div class="scrim" @click=${() => this._closeConfirm()}>
<div
class="dialog"
role="dialog"
aria-modal="true"
@click=${(n) => n.stopPropagation()}
>
<div class="dialog-header">
<span class="dialog-tile" aria-hidden="true">${st}</span>
<div class="dialog-titles">
<span class="dialog-kicker">
${e?.name ?? "Quest"}
</span>
<span class="dialog-quest-title">${t.title}</span>
</div>
</div>
<p class="dialog-body">
Mark this quest complete? Once the seal is set, only a parent can
undo it.
</p>
<div class="dialog-buttons">
<button
class="confirm-button not-yet"
type="button"
@click=${() => this._closeConfirm()}
>
${dn}
<span>Not yet</span>
</button>
<button
class="confirm-button do-complete"
type="button"
@click=${() => this._confirmComplete()}
>
${H}
<span>Complete</span>
</button>
</div>
</div>
</div>
`;
}
_openConfirm(t) {
this._confirm = t, this._armConfirmTimer();
}
_closeConfirm() {
this._clearConfirmTimer(), this._confirm = null;
}
_confirmComplete() {
const t = this._confirm === null ? void 0 : this._instances().find(
(e) => e.id === this._confirm
);
this._closeConfirm(), !(!t || t.state !== "open") && this._completeQuest(t);
}
/** 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. */
async _completeQuest(t) {
if (this._optimistic.has(t.id))
return;
const e = (/* @__PURE__ */ new Date()).toISOString();
this._optimistic = new Map(this._optimistic).set(t.id, e);
try {
await this._callCompleteQuest(t);
} catch {
const n = new Map(this._optimistic);
n.delete(t.id), this._optimistic = n, this._showToast(
"NestQuest could not set the seal just now. Please try again."
);
}
}
async _callCompleteQuest(t) {
const e = this.hass;
if (typeof e?.callService != "function")
throw new Error("Home Assistant is not connected");
const n = this._actorChildId(t);
if (n === null)
throw new Error("The tapped adventurer is unknown");
await e.callService.call(e, "nestquest", "complete_quest", {
instance_id: t.id,
actor: "panel",
actor_child_id: n
});
}
/** The tapped profile (decision 9): the instance's own child_id, falling
* back to the due sensor's child-level attribute. */
_actorChildId(t) {
if (t.child_id !== null)
return t.child_id;
const e = this._childSlug(), n = y(
this._state(`sensor.nestquest_${e}_quests_due_today`)?.attributes?.child_id,
0
);
return n > 0 ? n : null;
}
_showToast(t) {
this._toast = t, this._clearToastTimer(), this._toastTimer = window.setTimeout(() => {
this._toastTimer = void 0, this._toast = null;
}, 6e3);
}
_clearToastTimer() {
this._toastTimer !== void 0 && (window.clearTimeout(this._toastTimer), this._toastTimer = void 0);
}
_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);
}
_armCompleteTimer() {
if (this._completeTimer === void 0) {
if (this._countdown === null && (this._countdown = this._completeSeconds()), this._countdown <= 0) {
this._returnToBoard();
return;
}
this._completeTimer = window.setInterval(() => {
const t = (this._countdown ?? 1) - 1;
if (t <= 0) {
this._countdown = 0, this._clearCompleteTimer(), this._returnToBoard();
return;
}
this._countdown = t;
}, 1e3);
}
}
_clearCompleteTimer() {
this._completeTimer !== void 0 && (window.clearInterval(this._completeTimer), this._completeTimer = void 0);
}
_optionalNumber(t) {
if (t == null || t === "")
return null;
const e = typeof t == "number" ? t : Number(t);
return Number.isFinite(e) ? e : null;
}
_dockWeather() {
const t = w(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, o = null;
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);
}
}
return {
condition: n,
temperature: this._optionalNumber(i.temperature),
high: r,
low: o
};
}
_renderDock() {
const t = this._dockWeather();
if (!t)
return d`
<div class="dock">
<span class="date">${D(this._now, this._timeZone())}</span>
<span class="divider"></span>
<span class="clock">${Xe(this._now, this._timeZone())}</span>
</div>
`;
const e = on(t.condition), n = an(t.condition, e), i = t.temperature === null ? g : d`<span class="temp">${Math.round(t.temperature)}°</span>`, r = t.high === null || t.low === null ? null : `${Math.round(t.high)}° / ${Math.round(t.low)}°`;
return d`
<div class="dock">
${_n}
${i}
<span class="divider"></span>
<span class="condition">
${r === null ? e : `${e} · ${r}`}
</span>
<span class="divider"></span>
<span class="condition">${n}</span>
</div>
`;
}
_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);
}
};
W.properties = {
hass: { attribute: !1 },
_config: { state: !0 },
_now: { state: !0 },
_confirm: { state: !0 },
_optimistic: { state: !0 },
_toast: { state: !0 },
_countdown: { state: !0 }
}, W.styles = [x(Gt), cn];
let ot = W;
customElements.define("nestquest-quest-log-card", ot);
ut({
type: "nestquest-quest-log-card",
name: "NestQuest Quest Log",
description: "One child's daily quests, grouped by window."
});
const xn = ":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}", G = class G extends C {
setConfig(t) {
if (!t || typeof t != "object")
throw new Error("Invalid configuration");
this._config = t;
}
getCardSize() {
return 8;
}
render() {
return d`<div>NestQuest Admin</div>`;
}
};
G.properties = {
hass: { attribute: !1 },
_config: { state: !0 }
}, G.styles = [x(xn)];
let at = G;
customElements.define("nestquest-admin-card", at);
ut({
type: "nestquest-admin-card",
name: "NestQuest Admin",
description: "Parent phone view for today, tasks, schedule, and history."
});
function Kt(s) {
return typeof s == "string" ? s.trim() : "";
}
function wn(s) {
if (s === null || typeof s != "object" || Array.isArray(s))
return "";
const t = s.slug;
return typeof t == "string" ? t.trim() : "";
}
function $n(s) {
const t = Kt(s.url_path).replace(/^\/+|\/+$/g, "");
return t || (window.location.pathname.split("/").filter(Boolean)[0] ?? "");
}
function qn(s) {
if (s === null || typeof s != "object" || Array.isArray(s))
return "";
const t = s.name;
return typeof t == "string" ? t.trim() : "";
}
function kn(s) {
const n = s?.states?.["sensor.nestquest_household_quests_due_today"]?.attributes?.child_roster;
return Array.isArray(n) ? n.map((i) => ({ slug: wn(i), name: qn(i) })).filter((i) => i.slug.length > 0) : [];
}
class An {
static async generate(t, e) {
const n = `/${$n(t)}`, i = Kt(t.weather_entity), r = {
type: "custom:nestquest-party-board-card",
quest_log_path: n
};
i && (r.weather_entity = i);
const o = {
type: "custom:nestquest-quest-log-card",
board_path: n
};
return i && (o.weather_entity = i), {
views: [
{
path: "party",
title: "The Party",
type: "panel",
cards: [r]
},
...kn(e).map((l) => ({
path: l.slug,
title: l.name ? `${l.name}'s Quest Log` : `${l.slug}'s Quest Log`,
type: "panel",
cards: [{ ...o }]
}))
]
};
}
}
window.customStrategies = window.customStrategies ?? {};
window.customStrategies["nestquest-party"] = An;
const Cn = "/nestquest-static/nestquest-fonts.css";
if (!document.querySelector('link[data-nq-fonts=""]')) {
const s = document.createElement("link");
s.rel = "stylesheet", s.href = Cn, s.dataset.nqFonts = "", document.head.appendChild(s);
}