updated apps

This commit is contained in:
2026-07-14 23:57:03 -04:00
parent 6cc7212cef
commit 010e828e9c
797 changed files with 45153 additions and 4246 deletions
+1 -1
View File
@@ -17,5 +17,5 @@
"iot_class": "calculated",
"issue_tracker": "https://github.com/tempus2016/taskmate/issues",
"requirements": [],
"version": "4.5.0"
"version": "4.5.1"
}
@@ -531,7 +531,7 @@
"panel.btn_deactivate_chore": "Aufgabe deaktivieren",
"panel.btn_delete": "Löschen",
"panel.btn_edit": "Bearbeiten",
"panel.btn_from_template": "Aus Vorlage",
"panel.btn_from_template": "Aus Vorlage erstellen",
"panel.btn_reorder_chores": "Neu ordnen",
"panel.btn_retry": "Wiederholen",
"panel.btn_review": "Überprüfen",
@@ -562,7 +562,7 @@
"panel.btn_delete": "Delete",
"panel.btn_edit": "Edit",
"panel.btn_clone_chore": "Duplicate",
"panel.btn_from_template": "From template",
"panel.btn_from_template": "Create from template",
"panel.btn_reorder_chores": "Reorder",
"panel.btn_retry": "Retry",
"panel.btn_review": "Review",
@@ -562,7 +562,7 @@
"panel.btn_delete": "Delete",
"panel.btn_edit": "Edit",
"panel.btn_clone_chore": "Duplicate",
"panel.btn_from_template": "From template",
"panel.btn_from_template": "Create from template",
"panel.btn_reorder_chores": "Reorder",
"panel.btn_retry": "Retry",
"panel.btn_review": "Review",
@@ -531,7 +531,7 @@
"panel.btn_deactivate_chore": "Désactiver la tâche",
"panel.btn_delete": "Supprimer",
"panel.btn_edit": "Modifier",
"panel.btn_from_template": "Depuis un modèle",
"panel.btn_from_template": "Créer depuis un modèle",
"panel.btn_reorder_chores": "Réorganiser",
"panel.btn_retry": "Réessayer",
"panel.btn_review": "Vérifier",
@@ -531,7 +531,7 @@
"panel.btn_deactivate_chore": "Deaktiver oppgave",
"panel.btn_delete": "Slett",
"panel.btn_edit": "Rediger",
"panel.btn_from_template": "Fra mal",
"panel.btn_from_template": "Opprett fra mal",
"panel.btn_reorder_chores": "Omorganiser",
"panel.btn_retry": "Prøv igjen",
"panel.btn_review": "Gjennomgå",
@@ -531,7 +531,7 @@
"panel.btn_deactivate_chore": "Deaktiver oppgåve",
"panel.btn_delete": "Slett",
"panel.btn_edit": "Rediger",
"panel.btn_from_template": "Frå mal",
"panel.btn_from_template": "Opprett frå mal",
"panel.btn_reorder_chores": "Omorganiser",
"panel.btn_retry": "Prøv på nytt",
"panel.btn_review": "Gjennomgå",
@@ -531,7 +531,7 @@
"panel.btn_deactivate_chore": "Desativar tarefa",
"panel.btn_delete": "Excluir",
"panel.btn_edit": "Editar",
"panel.btn_from_template": "De modelo",
"panel.btn_from_template": "Criar de modelo",
"panel.btn_reorder_chores": "Reordenar",
"panel.btn_retry": "Tentar novamente",
"panel.btn_review": "Revisar",
@@ -536,7 +536,7 @@
"panel.btn_deactivate_chore": "Desativar tarefa",
"panel.btn_delete": "Eliminar",
"panel.btn_edit": "Editar",
"panel.btn_from_template": "A partir de modelo",
"panel.btn_from_template": "Criar a partir de modelo",
"panel.btn_reorder_chores": "Reordenar",
"panel.btn_retry": "Tentar novamente",
"panel.btn_review": "Rever",
@@ -206,6 +206,7 @@ class TaskMateApprovalsCard extends LitElement {
.item-details {
display: flex;
flex-wrap: wrap;
align-items: center;
gap: 12px;
font-size: 0.85em;
@@ -87,14 +87,16 @@ class TaskMateCalendarCard extends LitElement {
display: flex;
align-items: center;
justify-content: space-between;
flex-wrap: wrap;
row-gap: 8px;
padding: 14px 18px;
background: var(--taskmate-header-bg, #3498db);
color: white;
}
.header-content { display: flex; align-items: center; gap: 10px; }
.header-icon { --mdc-icon-size: 28px; opacity: 0.9; }
.header-title { font-size: 1.2rem; font-weight: 600; }
.header-content { display: flex; align-items: center; gap: 10px; min-width: 0; }
.header-icon { --mdc-icon-size: 28px; opacity: 0.9; flex: none; }
.header-title { font-size: 1.2rem; font-weight: 600; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.day-nav {
display: flex;
@@ -250,7 +252,7 @@ class TaskMateCalendarCard extends LitElement {
.cal-head { margin-bottom: 8px; }
.cal-name { font-weight: 800; }
.cal-chips { gap: 7px; flex-wrap: wrap; }
.cal-chip { white-space: nowrap; }
.cal-chip { white-space: normal; overflow-wrap: anywhere; }
.cal-empty-line { font-size: 12px; font-style: italic; }
.cal-card-pl { background: var(--tmd-surface-2); border-radius: 18px; padding: 11px 12px; }
@@ -201,8 +201,9 @@ class TaskMateGraphCard extends LitElement {
.empty-state .submessage { font-size: 0.85rem; margin-top: 4px; }
@media (max-width: 480px) {
.card-header { padding: 12px 14px; }
.card-header { padding: 12px 14px; flex-wrap: wrap; row-gap: 8px; }
.header-title { font-size: 1rem; }
.mode-toggle { flex-shrink: 1; }
.mode-btn { padding: 3px 8px; font-size: 0.7rem; }
.card-content { padding: 12px; }
}
@@ -262,6 +262,7 @@ export function createIncentiveCard(P) {
flex-direction: column;
gap: 4px;
flex: 1;
min-width: 0;
}
.form-field label { font-size: 0.8rem; font-weight: 600; color: var(--text-secondary); text-transform: uppercase; letter-spacing: 0.4px; }
.form-field input {
@@ -342,7 +343,9 @@ export function createIncentiveCard(P) {
z-index: 9999;
pointer-events: none;
animation: toast-in 0.25s ease, toast-out 0.3s ease 2s forwards;
white-space: nowrap;
white-space: normal;
max-width: calc(100vw - 32px);
text-align: center;
}
@keyframes toast-in {
from { opacity: 0; transform: translateX(-50%) translateY(12px); }
@@ -324,6 +324,7 @@ class TaskMateOverviewCard extends LitElement {
/* Overview — children grid */
.ov-kids { display: grid; grid-template-columns: repeat(3, 1fr); gap: 10px; max-height: 360px; overflow-y: auto; }
@media (max-width: 480px) { .ov-kids { grid-template-columns: repeat(2, 1fr); } }
.ov-kid { text-align: center; padding: 11px; border-radius: 16px; background: var(--tmd-surface-2); position: relative; }
.ov-kid.tm-clickable { cursor: pointer; }
.ov-kid-flags { position: absolute; top: 6px; right: 7px; display: flex; gap: 4px; }
+166 -89
View File
@@ -133,6 +133,7 @@ class TaskMatePanel extends HTMLElement {
this._bulkMode = false; // chores multi-select mode
this._bulkSel = new Set(); // selected chore ids for bulk actions
this._activeTab = "children";
this._mobileNavOpen = false; // mobile section-picker dropdown state
this._dialog = null;
this._dialogInitialHash = null; // for confirm-on-leave
this._filter = ""; // per-tab search/filter
@@ -413,11 +414,13 @@ class TaskMatePanel extends HTMLElement {
const act = t.dataset.act;
if (act === "tab") {
this._activeTab = t.dataset.tab; this._filter = ""; this._render();
this._activeTab = t.dataset.tab; this._filter = ""; this._mobileNavOpen = false; this._render();
// Settings tab lists HA users for the parent-role picker (#661); load then repaint.
if (this._activeTab === "settings") this._ensureHaUsers().then(() => this._render());
return;
}
if (act === "toggle-mobile-nav") { this._mobileNavOpen = !this._mobileNavOpen; this._render(); return; }
if (act === "close-mobile-nav") { this._mobileNavOpen = false; this._render(); return; }
if (act === "close-dialog") { this._closeDialog(); return; }
if (act === "clear-field") { this._setEntityField(t.dataset.field, ""); this._render(); return; }
if (act === "pick-entity") { this._setEntityField(t.dataset.field, t.dataset.value); this._closeEntityDropdown(); this._render(); return; }
@@ -2136,7 +2139,6 @@ class TaskMatePanel extends HTMLElement {
this._zoneCache = {};
this._applyDesign();
this._bindHaPickers();
this._scrollActiveMobileTabIntoView();
return;
}
@@ -2157,10 +2159,6 @@ class TaskMatePanel extends HTMLElement {
savedScroll = dialogBody ? dialogBody.scrollTop : 0;
}
const mtabsZone = this.querySelector('[data-zone="mtabs"]');
const existingMtabs = mtabsZone ? mtabsZone.querySelector(".tm-mobile-tabs") : null;
const savedMtabsScroll = existingMtabs ? existingMtabs.scrollLeft : 0;
if (this._dialog) {
const openSet = this._dialog._openAdvanced instanceof Set ? this._dialog._openAdvanced : new Set();
this.querySelectorAll("details.tm-advanced[data-section]").forEach(el => {
@@ -2190,13 +2188,7 @@ class TaskMatePanel extends HTMLElement {
if (newBody) newBody.scrollTop = savedScroll;
}
if (savedMtabsScroll) {
const newMtabs = this.querySelector(".tm-mobile-tabs");
if (newMtabs) newMtabs.scrollLeft = savedMtabsScroll;
}
this._applyDesign();
this._scrollActiveMobileTabIntoView();
}
// The admin panel intentionally stays CLASSIC regardless of the global card
@@ -2207,19 +2199,6 @@ class TaskMatePanel extends HTMLElement {
if (shell) shell.removeAttribute("data-tm-design");
}
_scrollActiveMobileTabIntoView() {
const nav = this.querySelector(".tm-mobile-tabs");
if (!nav) return;
const active = nav.querySelector(".tm-mtab-active");
if (!active) return;
const navRect = nav.getBoundingClientRect();
const btnRect = active.getBoundingClientRect();
if (btnRect.left < navRect.left || btnRect.right > navRect.right) {
const target = active.offsetLeft - (nav.clientWidth - active.offsetWidth) / 2;
nav.scrollTo({ left: Math.max(0, target), behavior: "smooth" });
}
}
_sidebarGroups() {
const counts = this._state ? {
children: (this._state.children || []).length,
@@ -2324,19 +2303,43 @@ class TaskMatePanel extends HTMLElement {
_mobileTabs() {
const groups = this._sidebarGroups();
const open = !!this._mobileNavOpen;
const current = groups.flatMap(g => g.items).find(it => it.id === this._activeTab)
|| { id: this._activeTab, label: "", icon: "mdi:cog-outline" };
const curUrgent = current.id === "activity" && current.count > 0;
const curShowCount = current.count != null && current.count > 0;
return `
<nav class="tm-mobile-tabs">
${groups.flatMap(g => g.items).map(it => {
const active = it.id === this._activeTab;
const urgent = it.id === "activity" && it.count > 0;
const showCount = it.count != null && it.count > 0;
return `
<button type="button" class="tm-mtab ${active ? "tm-mtab-active" : ""}" data-act="tab" data-tab="${it.id}">
${this._esc(it.label)}${showCount ? `<span class="tm-mtab-pill ${urgent ? "tm-mtab-pill-urgent" : ""}">${it.count}</span>` : ""}
</button>
`;
}).join("")}
</nav>
<div class="tm-mobilenav ${open ? "tm-mobilenav-open" : ""}">
<button type="button" class="tm-mnav-trigger" data-act="toggle-mobile-nav"
aria-haspopup="true" aria-expanded="${open ? "true" : "false"}">
<span class="tm-mnav-ico"><ha-icon icon="${this._esc(current.icon)}"></ha-icon></span>
<span class="tm-mnav-main">
<span class="tm-mnav-crumb">TaskMate</span>
<span class="tm-mnav-title">${this._esc(current.label)}</span>
</span>
${curShowCount ? `<span class="tm-mnav-count ${curUrgent ? "tm-mnav-count-urgent" : ""}">${current.count}</span>` : ""}
<span class="tm-mnav-chev"><ha-icon icon="mdi:chevron-down"></ha-icon></span>
</button>
${open ? `
<div class="tm-mnav-scrim" data-act="close-mobile-nav"></div>
<nav class="tm-mnav-sheet">
${groups.map(g => `
<div class="tm-mnav-grp-head">${this._esc(g.head)}</div>
${g.items.map(it => {
const active = it.id === this._activeTab;
const urgent = it.id === "activity" && it.count > 0;
const showCount = it.count != null && it.count > 0;
return `
<button type="button" class="tm-mnav-item ${active ? "tm-mnav-item-active" : ""}" data-act="tab" data-tab="${it.id}">
<ha-icon class="tm-mnav-item-ico" icon="${this._esc(it.icon)}"></ha-icon>
<span class="tm-mnav-item-label">${this._esc(it.label)}</span>
${showCount ? `<span class="tm-mnav-count ${urgent ? "tm-mnav-count-urgent" : ""}">${it.count}</span>` : ""}
</button>`;
}).join("")}
`).join("")}
</nav>
` : ""}
</div>
`;
}
@@ -2441,7 +2444,7 @@ class TaskMatePanel extends HTMLElement {
<div class="tm-meta">${child.availability_entity ? `<code>${this._esc(child.availability_entity)}</code>${child.availability_inverted ? ` <em>${this._t("panel.child_inverted")}</em>` : ""}` : `${this._t("panel.child_no_availability")}`}${child.unavailability_entity ? ` · ${this._t("panel.child_busy_label")} <code>${this._esc(child.unavailability_entity)}</code>` : ""}</div>
</div>
</div>
<div class="tm-stats-row">
<div class="tm-stats-row" style="grid-template-columns: 1fr 1fr;">
<div class="tm-stat tm-stat-highlight"><div class="tm-stat-value">${this._fmtNum(child.points || 0)}</div><div class="tm-stat-label">${this._esc(pointsName)}</div></div>
<div class="tm-stat"><div class="tm-stat-value">${this._fmtNum(child.level || 1)}</div><div class="tm-stat-label">${this._t("panel.child_stat_level")}</div></div>
<div class="tm-stat"><div class="tm-stat-value">${this._fmtNum(child.total_points_earned || 0)}</div><div class="tm-stat-label">${this._t("panel.child_stat_earned")}</div></div>
@@ -3381,7 +3384,7 @@ class TaskMatePanel extends HTMLElement {
const count = (tpl.chores || []).length;
const pts = (tpl.chores || []).reduce((s, c) => s + (c.points || 0), 0);
return `
<div class="tm-manage-tpl ${locked ? "tm-manage-tpl-locked" : ""}">
<div class="tm-manage-tpl ${locked ? "tm-manage-tpl-locked" : ""}" data-act="tpl-select" data-id="${this._esc(tpl.id)}" style="cursor:pointer">
<div class="tm-tpl-icon"><ha-icon icon="${this._esc(tpl.icon || "mdi:clipboard-list")}"></ha-icon></div>
<div class="tm-manage-tpl-info">
<div class="tm-manage-tpl-name">${this._esc(tpl.name)}${locked ? ' <span class="tm-text-vfaint" style="font-size:12px">🔒</span>' : ""}</div>
@@ -3740,9 +3743,9 @@ class TaskMatePanel extends HTMLElement {
</div></div>
<div class="tm-section-body">
${(this._haUsers || []).filter(u => !u.is_admin).map(u => `
<label class="tm-setting-row" style="cursor:pointer">
<label class="tm-setting-row tm-role-row">
<div class="tm-setting-label">${this._esc(u.name)}</div>
<input type="checkbox" data-parent-user="${this._esc(u.id)}" ${(s.parent_user_ids || []).includes(u.id) ? "checked" : ""}>
<input type="checkbox" class="tm-role-check" data-parent-user="${this._esc(u.id)}" ${(s.parent_user_ids || []).includes(u.id) ? "checked" : ""}>
</label>`).join("")
|| `<p class="tm-meta">${this._t("panel.settings_parents_empty")}</p>`}
</div>
@@ -4084,7 +4087,7 @@ class TaskMatePanel extends HTMLElement {
<h4>${this._t("panel.notif_recipients_parents")}</h4>
${ns.recipients.parents.map(p => `
<div class="tm-row" style="display:flex;gap:10px;align-items:center;padding:8px 0;border-top:1px solid var(--tm-border,#3a3a3a)">
<input type="text" value="${this._esc(p.name)}" data-act="notif-rename-parent" data-parent-id="${this._esc(p.id)}" style="flex:1;background:transparent;border:none;color:inherit;font-size:14px;font-weight:500">
<input type="text" value="${this._esc(p.name)}" data-act="notif-rename-parent" data-parent-id="${this._esc(p.id)}" style="flex:1;min-width:60px;background:transparent;border:none;color:inherit;font-size:14px;font-weight:500">
<select class="tm-notif-select" data-act="notif-set-parent-notify" data-parent-id="${this._esc(p.id)}">
${optTags(p.notify_service)}
</select>
@@ -4127,7 +4130,7 @@ class TaskMatePanel extends HTMLElement {
<td>
<div style="display:flex;gap:10px;align-items:flex-start">
<input type="checkbox" class="tm-notif-switch" data-act="notif-set-master" data-type-id="${this._esc(t.id)}" ${active ? "checked" : ""}>
<div>
<div style="max-width:240px;white-space:normal">
<strong>${this._t("notification." + t.id + ".name")}</strong>
<div class="tm-meta">${this._t("notification." + t.id + ".description")}</div>
${t.id === "streak_at_risk" ? `
@@ -4183,8 +4186,8 @@ class TaskMatePanel extends HTMLElement {
_renderNotifCustomSection(ns, recipients) {
return `
<div class="tm-card" style="margin-bottom:16px">
<div style="display:flex;justify-content:space-between;align-items:center">
<h3>${this._t("panel.notif_section_custom")}</h3>
<div style="display:flex;flex-wrap:wrap;gap:10px;justify-content:space-between;align-items:center">
<h3 style="margin:0">${this._t("panel.notif_section_custom")}</h3>
<button type="button" class="tm-btn tm-btn-raised" data-act="notif-add-custom">+ ${this._t("panel.notif_add_custom")}</button>
</div>
<p class="tm-meta">${this._t("panel.notif_section_custom_desc")}</p>
@@ -5426,8 +5429,8 @@ class TaskMatePanel extends HTMLElement {
}
@keyframes tm-pulse { 0%,100% { opacity: 1; } 50% { opacity: 0.45; } }
/* ===== Mobile tabs (hidden by default, shown on narrow) ===== */
.tm-mobile-tabs { display: none; }
/* ===== Mobile section picker (hidden by default, shown on narrow) ===== */
.tm-mobilenav { display: none; }
/* Approval banner */
.tm-approval-banner {
@@ -5945,6 +5948,11 @@ class TaskMatePanel extends HTMLElement {
border-top: 1px solid var(--tm-border-soft);
}
.tm-setting-row:first-child { border-top: 0; }
.tm-role-row { grid-template-columns: 1fr auto; cursor: pointer; }
.tm-role-check {
width: 20px; height: 20px; margin: 0;
justify-self: end; accent-color: var(--tm-accent); cursor: pointer;
}
.tm-setting-label { color: var(--tm-text); font-size: 13px; font-weight: 500; }
.tm-setting-label small { display: block; color: var(--tm-text-faint); font-weight: 400; font-size: 12px; margin-top: 2px; }
.tm-difficulty-mults { display: flex; gap: 12px; flex-wrap: wrap; }
@@ -5965,7 +5973,17 @@ class TaskMatePanel extends HTMLElement {
grid-template-columns: 56px minmax(140px, 1fr) auto 36px;
gap: 12px;
}
.tm-period-row ha-icon-picker { max-width: 56px; }
.tm-period-row ha-icon-picker {
width: 56px; max-width: 56px; height: 56px;
border-radius: var(--tm-radius-sm, 8px); overflow: hidden;
/* Older HA renders the picker as a filled mwc-textfield: drop the fill
and its dark active-indicator line so no stray underline shows. */
--mdc-text-field-fill-color: transparent;
--mdc-text-field-idle-line-color: transparent;
--mdc-text-field-hover-line-color: transparent;
--mdc-text-field-focused-line-color: transparent;
--mdc-text-field-disabled-line-color: transparent;
}
.tm-period-row > .tm-input { max-width: none; }
.tm-vacation-row {
grid-template-columns: minmax(140px, 1fr) auto 36px;
@@ -6563,7 +6581,7 @@ class TaskMatePanel extends HTMLElement {
background: var(--tm-surface-0); border: 1px solid var(--tm-border);
border-radius: var(--tm-radius-sm); padding: 7px 10px;
color: var(--tm-text); font-size: 12.5px; font-family: inherit;
cursor: pointer; max-width: 240px; transition: border-color 0.1s;
cursor: pointer; min-width: 0; max-width: 240px; transition: border-color 0.1s;
}
.tm-notif-select:hover { border-color: var(--tm-border-strong); }
.tm-notif-select:focus { outline: 0; border-color: var(--tm-accent); box-shadow: var(--tm-shadow-focus); }
@@ -6602,54 +6620,112 @@ class TaskMatePanel extends HTMLElement {
.tm-sidebar { display: none; }
.tm-menu-btn { display: inline-flex; }
.tm-topbar { padding: 0 12px; }
.tm-mobile-tabs {
display: flex;
overflow-x: auto;
overflow-y: hidden;
overscroll-behavior: contain;
touch-action: pan-x;
scrollbar-width: none;
padding: 0 12px;
/* Redundant with the section picker below — hide on mobile. */
.tm-crumbs { display: none; }
.tm-approval-pill { margin-left: auto; }
/* Section picker replaces the old horizontal tab strip. */
.tm-main { position: relative; }
.tm-mobilenav {
display: block;
position: relative;
z-index: 5;
flex-shrink: 0;
background: var(--tm-surface-0);
border-bottom: 1px solid var(--tm-border);
flex-shrink: 0;
padding: 8px 12px;
}
.tm-mobile-tabs::-webkit-scrollbar { display: none; }
.tm-mtab {
position: relative;
background: transparent;
border: 0;
color: var(--tm-text-faint);
padding: 12px 14px;
cursor: pointer;
font-size: 13px;
font-weight: 500;
font-family: inherit;
white-space: nowrap;
display: flex; align-items: center; gap: 6px;
transition: color 0.1s var(--tm-easing);
}
.tm-mtab:hover { color: var(--tm-text); }
.tm-mtab-active { color: var(--tm-text); }
.tm-mtab-active::after {
content: ""; position: absolute;
left: 14px; right: 14px; bottom: -1px;
height: 2px; background: var(--tm-accent);
border-radius: 2px 2px 0 0;
}
.tm-mtab-pill {
.tm-mnav-trigger {
width: 100%;
display: flex; align-items: center; gap: 10px;
background: var(--tm-surface-2);
color: var(--tm-text-muted);
font-size: 10.5px; font-weight: 600;
padding: 1px 6px; border-radius: 999px;
border: 1px solid var(--tm-border);
border-radius: var(--tm-radius-md, 12px);
padding: 8px 12px;
cursor: pointer;
font-family: inherit;
color: var(--tm-text);
text-align: left;
transition: background 0.12s var(--tm-easing), border-color 0.12s var(--tm-easing);
}
.tm-mtab-active .tm-mtab-pill {
.tm-mnav-trigger:active { background: var(--tm-surface-3); }
.tm-mobilenav-open .tm-mnav-trigger { border-color: var(--tm-accent); }
.tm-mnav-ico {
width: 30px; height: 30px; flex-shrink: 0;
border-radius: var(--tm-radius-sm, 8px);
background: var(--tm-accent-soft);
color: var(--tm-accent-text);
display: grid; place-items: center;
--mdc-icon-size: 20px;
}
.tm-mtab-pill-urgent {
background: var(--tm-warning-soft) !important;
color: var(--tm-warning) !important;
.tm-mnav-main { flex: 1; min-width: 0; display: flex; flex-direction: column; }
.tm-mnav-crumb { font-size: 11px; color: var(--tm-text-faint); line-height: 1.2; }
.tm-mnav-title {
font-size: 15px; font-weight: 600; line-height: 1.25;
white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.tm-mnav-chev {
color: var(--tm-text-muted); flex-shrink: 0;
--mdc-icon-size: 22px;
transition: transform 0.18s var(--tm-easing);
}
.tm-mobilenav-open .tm-mnav-chev { transform: rotate(180deg); }
.tm-mnav-count {
flex-shrink: 0;
background: var(--tm-surface-0);
border: 1px solid var(--tm-border);
color: var(--tm-text-muted);
font-size: 11px; font-weight: 600;
padding: 1px 7px; border-radius: 999px;
}
.tm-mnav-count-urgent {
background: var(--tm-warning-soft);
border-color: transparent;
color: var(--tm-warning);
}
.tm-mnav-scrim {
position: absolute;
top: 100%; left: 0; right: 0;
height: 100vh;
background: rgba(10, 15, 22, 0.32);
z-index: 30;
animation: tm-mnav-fade 0.18s var(--tm-easing);
}
.tm-mnav-sheet {
position: absolute;
top: calc(100% + 6px); left: 8px; right: 8px;
z-index: 31;
background: var(--tm-surface-0);
border: 1px solid var(--tm-border);
border-radius: var(--tm-radius-md, 14px);
box-shadow: 0 20px 48px rgba(0, 0, 0, 0.24);
max-height: calc(100vh - 200px);
overflow-y: auto;
padding: 4px 0 8px;
animation: tm-mnav-drop 0.18s var(--tm-easing);
}
.tm-mnav-grp-head {
font-size: 11px; font-weight: 700; letter-spacing: 0.06em;
text-transform: uppercase; color: var(--tm-text-faint);
padding: 12px 16px 4px;
}
.tm-mnav-item {
display: flex; align-items: center; gap: 12px; width: 100%;
background: none; border: 0; font-family: inherit; cursor: pointer;
padding: 11px 16px; color: var(--tm-text); text-align: left;
}
.tm-mnav-item:active { background: var(--tm-surface-2); }
.tm-mnav-item-ico {
flex-shrink: 0; color: var(--tm-text-muted);
--mdc-icon-size: 20px;
}
.tm-mnav-item-label { flex: 1; font-size: 15px; }
.tm-mnav-item-active { background: var(--tm-accent-soft); color: var(--tm-accent-text); }
.tm-mnav-item-active .tm-mnav-item-ico { color: var(--tm-accent-text); }
@keyframes tm-mnav-fade { from { opacity: 0; } to { opacity: 1; } }
@keyframes tm-mnav-drop {
from { opacity: 0; transform: translateY(-8px) scale(0.985); }
to { opacity: 1; transform: none; }
}
.tm-body { padding: 16px; }
.tm-toolbar { flex-direction: column; align-items: stretch; }
@@ -6658,6 +6734,7 @@ class TaskMatePanel extends HTMLElement {
.tm-scrim { padding: 0; }
.tm-dialog-body .tm-field-row { grid-template-columns: 1fr; }
.tm-setting-row { grid-template-columns: 1fr; gap: 6px; padding: 12px 16px; }
.tm-role-row { grid-template-columns: 1fr auto; gap: 12px; align-items: center; }
.tm-section-head, .tm-setting-row { padding-left: 16px; padding-right: 16px; }
.tm-timeline-row { grid-template-columns: 1fr auto; }
.tm-timeline-time, .tm-timeline-icon { display: none; }
@@ -311,8 +311,8 @@ class TaskMatePointsCard extends LitElement {
background: var(--card-background-color, white);
border-radius: 16px;
padding: 24px;
min-width: 300px;
max-width: 400px;
min-width: 0;
max-width: min(400px, calc(100vw - 24px));
box-shadow: var(--ha-card-box-shadow, 0 8px 32px rgba(0, 0, 0, 0.2));
animation: slide-up 0.3s ease;
}
@@ -480,6 +480,8 @@ class TaskMatePointsCard extends LitElement {
bottom: 24px;
left: 50%;
transform: translateX(-50%);
max-width: calc(100vw - 24px);
white-space: normal;
padding: 12px 24px;
border-radius: 8px;
color: var(--text-primary-color, #fff);
@@ -237,6 +237,7 @@ class TaskMateRewardProgressCard extends LitElement {
.progress-stat-row {
display: flex;
flex-wrap: wrap;
justify-content: space-between;
align-items: center;
font-size: 0.82rem;
@@ -426,6 +427,7 @@ class TaskMateRewardProgressCard extends LitElement {
.rp-frac { font-size: 18px; }
.rp-need { font-size: 12px; }
.rp-kids { grid-template-columns: repeat(var(--n, 3), 1fr); gap: 9px; margin-top: 16px; }
@media (max-width: 430px) { .rp-kids { grid-template-columns: repeat(auto-fit, minmax(64px, 1fr)); } }
.rp-kid { background: var(--tmd-surface-2); border: 1px solid var(--tmd-border);
border-radius: var(--tmd-radius-sm); padding: 10px; display: flex;
flex-direction: column; align-items: center; gap: 4px; }
@@ -302,7 +302,7 @@ class TaskMateRewardsCard extends LitElement {
font-weight: 600;
color: var(--text-secondary);
white-space: nowrap;
min-width: 70px;
min-width: 0;
text-align: right;
}
@@ -656,7 +656,7 @@ class TaskMateRewardsCard extends LitElement {
}
/* Responsive adjustments */
@media (max-width: 400px) {
@media (max-width: 430px) {
.card-header {
padding: 14px 18px;
}
@@ -234,7 +234,7 @@ class TaskMateStreakCard extends LitElement {
border-radius: 10px; padding: 11px; }
.sk-combo-label { font-size: 10px; }
.sk-combo { font-size: 24px; color: var(--tmd-accent); }
.sk-segs { display: flex; gap: 4px; margin-top: 9px; }
.sk-segs { display: flex; flex-wrap: wrap; gap: 4px; margin-top: 9px; }
.sk-seg { flex: 1; height: 8px; border-radius: 2px; background: #0b1424; }
.sk-seg.on { background: var(--tmd-accent);
box-shadow: 0 0 8px color-mix(in srgb, var(--tmd-accent) 70%, transparent); }
@@ -243,7 +243,7 @@ class TaskMateStreakCard extends LitElement {
/* Clean Pro */
.sk-grid-cp { display: flex; flex-direction: column; gap: 13px; }
.sk-row-cp .num { font-size: 17px; }
.sk-segs-cp { display: flex; gap: 5px; margin-top: 8px; }
.sk-segs-cp { display: flex; flex-wrap: wrap; gap: 5px; margin-top: 8px; }
.sk-seg-cp { flex: 1; height: 9px; border-radius: 3px; background: var(--tmd-surface-2); }
.sk-seg-cp.on { background: var(--tmd-good); }
.sk-badges-cp { display: flex; gap: 6px; flex-wrap: wrap; margin-top: 9px; }