/** Component-specific styles for the maintenance-supporter-panel. */ import { css } from "lit"; export const panelStyles = css` :host { display: block; height: 100%; background: var(--primary-background-color); } .panel { height: 100%; display: flex; flex-direction: column; } .header { display: flex; align-items: center; gap: 4px; background: var(--app-header-background-color, var(--primary-color)); color: var(--app-header-text-color, white); padding: 12px 16px; font-size: 16px; } .header ha-menu-button { margin-right: 4px; color: var(--app-header-text-color, white); } .header ha-icon-button { --mdc-icon-button-size: 36px; --mdc-icon-size: 20px; color: var(--app-header-text-color, white); } .breadcrumbs { display: flex; align-items: center; gap: 4px; } .breadcrumbs a { color: inherit; opacity: 0.8; cursor: pointer; text-decoration: none; } .breadcrumbs a:hover { opacity: 1; text-decoration: underline; } .breadcrumbs .sep { opacity: 0.5; margin: 0 4px; } .breadcrumbs .current { font-weight: 500; } .content { flex: 1; overflow-y: auto; padding: 0 16px 16px; } .filter-bar { display: flex; /* Wrap at EVERY width: with six filter dropdowns + action buttons the bar doesn't fit one line even on wide tablets, and unwrapped flex compressed the selects into unreadable stubs ("— No v", "Al"). */ flex-wrap: wrap; justify-content: space-between; align-items: flex-end; padding: 8px 0; gap: 8px; } /* Narrow-viewport disclosure (UX 2026-07): the collapsed classes are only ever set when the host is narrow — desktop always renders inline. */ .filter-bar.collapsed, .actions-bar.collapsed { display: none; } .mobile-controls { display: flex; gap: 8px; padding: 8px 0 0; } .mobile-controls .mobile-toggle { flex: 1; } .mobile-controls .mobile-toggle.active { --ha-button-background: var(--primary-color); } .actions-bar { display: flex; flex-wrap: wrap; gap: 8px; padding: 4px 0 8px; justify-content: flex-end; } :host([narrow]) .actions-bar { flex-direction: column; align-items: stretch; } .filter-field { display: flex; flex-direction: column; gap: 2px; min-width: 0; } .filter-label { font-size: 11px; font-weight: 500; text-transform: uppercase; letter-spacing: 0.4px; color: var(--secondary-text-color); padding-left: 2px; } .filter-bar select { padding: 8px; border: 1px solid var(--divider-color); border-radius: 4px; background: var(--card-background-color, #fff); color: var(--primary-text-color); /* Readability floor — selects wrap to the next line instead of shrinking their selected value into ellipsis. */ min-width: 96px; } /* Desktop: the LIST owns the 7-column grid and every row is a subgrid spanning all columns. Sharing the tracks across rows is what actually keeps the title (and every other column) aligned regardless of which optional badges/chips a given row carries. A per-row grid can't: each row would size its auto badges column independently, so a row with an NFC badge pushed its title right of the others (issue #66). */ .task-table { display: grid; grid-template-columns: auto /* badges */ minmax(100px, 180px) /* object-name */ minmax(120px, 1fr) /* task-name */ minmax(0, 220px) /* task-sub (chips) */ 100px /* type */ 150px /* due-cell */ auto; /* row-actions */ column-gap: 12px; } .task-row { display: grid; grid-template-columns: subgrid; grid-column: 1 / -1; align-items: center; column-gap: 12px; padding: 10px 12px; border-bottom: 1px solid var(--divider-color); cursor: pointer; transition: background 0.15s; } /* Virtualized task table (large installs): only the scroll window of rows is in the DOM. The spacers span all columns and carry the off-window height so the scrollbar stays honest. The sizer row is invisible and zero-height but its badge cell still participates in subgrid track sizing — pinning the content-sized badge column to the widest badge set across ALL rows, so columns can't jitter while scrolling. */ .virt-spacer { grid-column: 1 / -1; } .task-row.virt-sizer { height: 0; min-height: 0; padding-top: 0; padding-bottom: 0; border: none; overflow: hidden; visibility: hidden; pointer-events: none; } /* Bulk selection: a leading checkbox column while selecting. */ .task-table.bulk { grid-template-columns: auto auto minmax(100px, 180px) minmax(120px, 1fr) minmax(0, 220px) 100px 150px auto; } /* Wide desktop (UX 2026-07): with a 1fr name column the slack landed BETWEEN the task name and its right-aligned chips — a ragged hole in the middle of every row. Size the name track to its content instead and hand the slack to the chips track, chips now left-aligned: the row reads as a left description cluster (badges/object/name/chips) and a right meta cluster (type/due/actions). */ @media (min-width: 1200px) { .task-table { grid-template-columns: auto /* badges */ minmax(100px, 180px) /* object-name */ fit-content(400px) /* task-name — hugs the longest name */ minmax(0, 1fr) /* task-sub (chips) absorbs the slack */ 100px /* type */ 150px /* due-cell */ auto; /* row-actions */ } .task-table.bulk { grid-template-columns: auto auto minmax(100px, 180px) fit-content(400px) minmax(0, 1fr) 100px 150px auto; } .task-table .task-sub { justify-content: flex-start; } } .bulk-check { display: flex; align-items: center; justify-content: center; cursor: pointer; } .bulk-check input, .bulk-selectall input { width: 17px; height: 17px; cursor: pointer; accent-color: var(--primary-color); } .task-row.bulk-selected { background: color-mix(in srgb, var(--primary-color) 12%, transparent); } .bulk-bar { display: flex; align-items: center; gap: 16px; flex-wrap: wrap; padding: 8px 12px; margin-bottom: 8px; border-radius: 8px; background: var(--secondary-background-color); border: 1px solid var(--divider-color); position: sticky; top: 0; z-index: 5; } .bulk-selectall { display: inline-flex; align-items: center; gap: 6px; cursor: pointer; font-size: 13px; } .bulk-count { color: var(--secondary-text-color); font-size: 13px; } .bulk-actions { margin-left: auto; display: inline-flex; gap: 8px; } .bulk-toggle.active { --mdc-theme-primary: var(--primary-color); } /* Collapsible analysis sections on the task-detail overview tab. The header owns the title, so the wrapped card's own title row is hidden to avoid showing it twice. */ .collapsible { margin: 8px 0; border: 1px solid var(--divider-color); border-radius: 10px; overflow: hidden; } .collapsible-head { display: flex; align-items: center; gap: 8px; width: 100%; font: inherit; font-weight: 600; font-size: 14px; text-align: left; padding: 10px 12px; cursor: pointer; background: var(--secondary-background-color); border: none; color: var(--primary-text-color); } .collapsible-head:hover { background: var(--table-row-alternative-background-color, rgba(0,0,0,.04)); } .collapsible-head ha-icon { --mdc-icon-size: 20px; color: var(--secondary-text-color); } .collapsible-body { padding: 4px 12px 12px; } .collapsible-body > .weibull-section > .weibull-title, .collapsible-body > .seasonal-chart > .seasonal-chart-title { display: none; } /* "Today" focus view — mobile-first list grouped by urgency. */ .today-view { display: flex; flex-direction: column; gap: 16px; padding: 4px 0 12px; } .today-section { border: 1px solid var(--divider-color); border-radius: 12px; overflow: hidden; } .today-section-header { display: flex; align-items: center; justify-content: space-between; padding: 10px 14px; font-weight: 600; font-size: 14px; background: var(--secondary-background-color); border-left: 4px solid var(--divider-color); } .today-section-header.overdue { border-left-color: var(--error-color, #f44336); } .today-section-header.due_soon { border-left-color: var(--warning-color, #ff9800); } .today-badge { min-width: 22px; text-align: center; padding: 1px 8px; border-radius: 11px; background: var(--primary-color); color: var(--text-primary-color, #fff); font-size: 12.5px; } .today-row { display: flex; align-items: center; gap: 12px; padding: 11px 14px; border-top: 1px solid var(--divider-color); cursor: pointer; content-visibility: auto; contain-intrinsic-size: auto 46px; } .today-row:hover { background: var(--table-row-alternative-background-color, rgba(0,0,0,.04)); } .today-dot { width: 10px; height: 10px; border-radius: 50%; flex: none; background: var(--success-color, #4caf50); } .today-dot.overdue { background: var(--error-color, #f44336); } .today-dot.due_soon { background: var(--warning-color, #ff9800); } .today-dot.triggered { background: #ff5722; } .today-main { flex: 1; min-width: 0; } .today-task { font-weight: 600; font-size: 14px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; } .today-object { color: var(--secondary-text-color); font-size: 12.5px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; } .today-row .btn-complete { color: var(--success-color, #4caf50); flex: none; } .today-empty { display: flex; flex-direction: column; align-items: center; gap: 10px; padding: 48px 16px; color: var(--secondary-text-color); text-align: center; } .today-empty ha-icon { --mdc-icon-size: 56px; color: var(--success-color, #4caf50); } /* Command palette (Ctrl/Cmd+K). */ .palette-backdrop { position: fixed; inset: 0; z-index: 1100; background: rgba(0,0,0,.4); display: flex; align-items: flex-start; justify-content: center; padding-top: 12vh; animation: toast-in .15s ease; } .palette { width: min(620px, 92vw); max-height: 66vh; display: flex; flex-direction: column; background: var(--card-background-color, #fff); border-radius: 12px; overflow: hidden; box-shadow: 0 12px 48px rgba(0,0,0,.4); } .palette-input { font: inherit; font-size: 16px; padding: 16px 18px; border: none; outline: none; background: transparent; color: var(--primary-text-color); border-bottom: 1px solid var(--divider-color); } .palette-results { overflow-y: auto; } .palette-item { display: flex; align-items: center; gap: 10px; padding: 10px 16px; cursor: pointer; } .palette-item.active { background: color-mix(in srgb, var(--primary-color) 14%, transparent); } .palette-item ha-icon { --mdc-icon-size: 20px; color: var(--secondary-text-color); flex: none; } .palette-label { font-weight: 500; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; } .palette-sub { margin-left: auto; color: var(--secondary-text-color); font-size: 12.5px; flex: none; padding-left: 10px; } .palette-empty { padding: 20px 16px; color: var(--secondary-text-color); text-align: center; } .palette-hint { padding: 8px 16px; font-size: 12px; color: var(--secondary-text-color); border-top: 1px solid var(--divider-color); } /* Template gallery. */ .template-gallery { width: min(720px, 94vw); max-height: 80vh; display: flex; flex-direction: column; background: var(--card-background-color, #fff); border-radius: 12px; overflow: hidden; box-shadow: 0 12px 48px rgba(0,0,0,.4); } .template-gallery-head { display: flex; align-items: center; justify-content: space-between; padding: 12px 8px 12px 18px; font-weight: 600; font-size: 16px; border-bottom: 1px solid var(--divider-color); } .template-gallery-body { overflow-y: auto; padding: 8px 16px 16px; } .template-cat { margin-top: 12px; } .template-cat-head { display: flex; align-items: center; gap: 8px; font-weight: 600; font-size: 13px; color: var(--secondary-text-color); margin-bottom: 8px; } .template-cat-head ha-icon { --mdc-icon-size: 20px; } .template-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(160px, 1fr)); gap: 10px; } .template-card { display: flex; flex-direction: column; gap: 4px; text-align: left; cursor: pointer; padding: 12px 14px; border: 1px solid var(--divider-color); border-radius: 10px; background: var(--card-background-color); font: inherit; color: var(--primary-text-color); } .template-card:hover { border-color: var(--primary-color); background: color-mix(in srgb, var(--primary-color) 6%, transparent); } .template-card[disabled] { opacity: .5; pointer-events: none; } .template-card-name { font-weight: 600; font-size: 14px; } .template-card-count { font-size: 12px; color: var(--secondary-text-color); } .empty-onboard-hint { color: var(--secondary-text-color); font-size: 13px; margin: 4px 0 12px; } .empty-onboard-actions { display: flex; gap: 8px; flex-wrap: wrap; justify-content: center; } .task-row:hover { background: var(--table-row-alternative-background-color, rgba(0, 0, 0, 0.04)); } /* Wrapper for status + optional disabled/NFC badges so they share one grid column */ .cell-badges { display: flex; align-items: center; gap: 6px; } .cell { font-size: 14px; } .cell.object-name { color: var(--primary-color); cursor: pointer; } .cell.task-name { font-weight: 500; } .cell.type { color: var(--secondary-text-color); } /* Task subline chips (group / area / assigned user) — desktop shows inline, mobile wraps below */ .task-sub { display: flex; gap: 6px; align-items: center; font-size: 12px; color: var(--secondary-text-color); flex-wrap: wrap; justify-content: flex-end; } /* Empty subline still occupies its grid slot so neighbouring columns line up */ .task-sub-empty { min-height: 1px; } .sub-chip { display: inline-flex; align-items: center; gap: 4px; white-space: nowrap; padding: 2px 8px; border-radius: 10px; background: var(--secondary-background-color, rgba(127, 127, 127, 0.1)); line-height: 1.4; } .sub-chip ha-icon { --mdc-icon-size: 14px; opacity: 0.75; } /* Row action buttons (Complete / Skip): right-aligned in their column and a bit larger — the default mwc glyph reads small inside its padded button. */ .row-actions { display: flex; align-items: center; justify-content: flex-end; gap: 2px; } .row-actions mwc-icon-button { --mdc-icon-button-size: 44px; --mdc-icon-size: 26px; } /* Custom elements default to display:inline; the task-detail component renders light-DOM and must behave like the block it wraps. */ maintenance-task-detail-view { display: block; } .detail-section { padding: 16px 0; } .detail-header { display: flex; justify-content: space-between; align-items: center; flex-wrap: wrap; gap: 8px; } .detail-header h2 { margin: 0; font-size: 22px; } h3 { margin: 16px 0 8px; font-size: 16px; font-weight: 500; } .meta { color: var(--secondary-text-color); margin: 4px 0; } /* v1.4.10 (#46): per-object free-form notes block */ .object-notes { margin: 12px 0 4px; padding: 12px 14px; background: var(--card-background-color, var(--ha-card-background, #1c1c1c)); border-left: 3px solid var(--primary-color, #03a9f4); border-radius: 4px; } .object-notes-label { font-size: 12px; font-weight: 500; color: var(--secondary-text-color); text-transform: uppercase; letter-spacing: 0.4px; margin-bottom: 6px; } .object-notes-body { color: var(--primary-text-color); white-space: pre-wrap; word-break: break-word; line-height: 1.45; } .empty { color: var(--secondary-text-color); font-style: italic; } .analysis-empty-state { text-align: center; padding: 24px 16px; } .analysis-empty-state .empty { font-size: 15px; margin-bottom: 8px; } .analysis-empty-state .empty-icon { --mdc-icon-size: 48px; color: var(--secondary-text-color); opacity: 0.4; display: block; margin: 0 auto 12px; } .empty-hint { color: var(--secondary-text-color); font-size: 13px; margin: 4px 0; } .analysis-progress { width: 120px; margin: 12px auto 4px; height: 6px; background: var(--divider-color, #e0e0e0); border-radius: 3px; overflow: hidden; } .analysis-progress-bar { height: 100%; background: var(--primary-color); border-radius: 3px; } .info-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(200px, 1fr)); gap: 8px; } .info-item { display: flex; flex-direction: column; padding: 8px; background: var(--card-background-color, #fff); border-radius: 8px; } .info-item .label { font-size: 12px; color: var(--secondary-text-color); margin-bottom: 2px; } /* Dashboard redesign styles */ .task-header { display: flex; justify-content: space-between; align-items: center; padding: 12px 16px; background: var(--card-background-color, #fff); border-radius: 8px; margin-bottom: 16px; gap: 12px; flex-wrap: wrap; } .task-header-title { display: flex; align-items: center; gap: 8px; flex: 1; } .task-name-breadcrumb, .object-name-breadcrumb { cursor: pointer; color: var(--primary-text-color); text-decoration: none; } .task-name-breadcrumb:hover, .object-name-breadcrumb:hover { text-decoration: underline; } .breadcrumb-separator { color: var(--secondary-text-color); margin: 0 4px; } .status-chip { display: inline-block; padding: 4px 12px; border-radius: 12px; font-size: 12px; font-weight: 500; text-transform: uppercase; } /* Same theme tokens as STATUS_COLORS / .status-badge / .cal-status (this set predated the token migration and kept bare hex, so it alone ignored custom/ dark themes — the 3-palette drift the DRY audit flagged). Dark text on the light chips (green/orange): white is 2.2–2.8:1, below the 3:1 WCAG UI floor. */ .status-chip.ok { background: var(--success-color, #4caf50); color: #000; } .status-chip.warning { background: var(--warning-color, #ff9800); color: #000; } .status-chip.overdue { background: var(--error-color, #f44336); color: white; } .status-chip.done { background: var(--maint-done-color, #78909c); color: white; } /* (#67) Warranty status chip — object detail meta + objects table */ .warranty-chip { display: inline-block; padding: 2px 8px; border-radius: 10px; font-size: 12px; font-weight: 500; white-space: nowrap; } .warranty-valid { background: rgba(76, 175, 80, 0.15); color: var(--success-color, #2e7d32); } .warranty-expiring { background: rgba(255, 152, 0, 0.18); color: var(--warning-color, #e65100); } .warranty-expired { background: rgba(244, 67, 54, 0.16); color: var(--error-color, #c62828); } .warranty-none { color: var(--secondary-text-color); } /* (#67) All-Objects view-mode toggle (cards / table) */ .view-toggle { display: inline-flex; border: 1px solid var(--divider-color); border-radius: 8px; overflow: hidden; align-self: end; } .view-toggle-btn { display: inline-flex; align-items: center; justify-content: center; padding: 6px 10px; background: var(--card-background-color, #fff); color: var(--secondary-text-color); border: none; cursor: pointer; } .view-toggle-btn + .view-toggle-btn { border-left: 1px solid var(--divider-color); } .view-toggle-btn.active { background: var(--primary-color); color: var(--text-primary-color, #fff); } .view-toggle-btn ha-icon { --mdc-icon-size: 18px; } /* (#67) Objects table (desktop All-Objects view) */ .objects-table-wrap { overflow-x: auto; border: 1px solid var(--divider-color); border-radius: 8px; } .objects-table { width: 100%; border-collapse: collapse; font-size: 14px; } .objects-table th, .objects-table td { text-align: left; padding: 8px 12px; border-bottom: 1px solid var(--divider-color); white-space: nowrap; } .objects-table thead th { font-weight: 600; color: var(--secondary-text-color); background: var(--secondary-background-color, rgba(0, 0, 0, 0.03)); position: sticky; top: 0; } .objects-table tbody tr { cursor: pointer; } .objects-table tbody tr:hover { background: var(--secondary-background-color, rgba(0, 0, 0, 0.04)); } .objects-table tbody tr:last-child td { border-bottom: none; } .objects-table-name { font-weight: 500; color: var(--primary-text-color); } .doc-badge { display: inline-flex; align-items: center; gap: 2px; vertical-align: middle; margin-left: 8px; padding: 1px 7px 1px 5px; border-radius: 10px; font-size: 12px; font-weight: 600; background: var(--secondary-background-color, rgba(0, 0, 0, 0.06)); color: var(--secondary-text-color, #888); } .doc-badge ha-icon { --mdc-icon-size: 14px; } /* v2.20 (N3): paused marker on object cards + the detail meta line. */ .paused-badge { display: inline-flex; align-items: center; vertical-align: middle; margin-left: 8px; color: var(--info-color, #2196f3); } .paused-badge ha-icon { --mdc-icon-size: 16px; } .paused-meta { display: flex; align-items: center; gap: 6px; color: var(--info-color, #2196f3); font-weight: 500; } .paused-meta ha-icon { --mdc-icon-size: 16px; } .objects-table .oc-notes { max-width: 220px; overflow: hidden; text-overflow: ellipsis; } .objects-table .oc-task_count, .objects-table .oc-actions { text-align: center; } .user-badge { display: inline-flex; align-items: center; gap: 3px; padding: 2px 8px; margin-left: 8px; background: var(--primary-color); color: var(--text-primary-color); border-radius: 10px; font-size: 11px; font-weight: 500; line-height: 1.4; } .user-badge ha-icon { --mdc-icon-size: 12px; } .nfc-badge { display: inline-flex; align-items: center; gap: 3px; padding: 3px 8px; margin-left: 6px; background: var(--secondary-background-color, #e8e8e8); color: var(--primary-text-color); border-radius: 12px; font-size: 11px; font-weight: 500; } .priority-badge { display: inline-flex; align-items: center; margin-left: 6px; border-radius: 12px; padding: 2px; } /* Inside .cell-badges the parent's gap does the spacing — the badges' own margin-left (meant for inline use, e.g. the detail header) would double it and push the priority chevron out of line with the other badges. */ .cell-badges .nfc-badge, .cell-badges .priority-badge { margin-left: 0; } /* Right-anchor the auxiliary badges (disabled / NFC / priority chevron) to the END of the shared badges track. Status pills vary in width per status AND language (min-width 70px only clamps the short ones — "Due Soon"/"Overdue" overflow it), so left-flowing extras landed at a different x in every row: the low-priority chevron, which typically sits next to the short OK pill, fell visibly out of the column formed by the other rows' chevrons. Anchoring the extras group to the track edge gives ONE clean column in every language; the virt-sizer row keeps the track width stable. In the narrow per-row grids the badges area is content-sized (no free space), so the auto margin is inert there. */ .cell-badges > .status-badge + * { margin-left: auto; } .priority-badge ha-icon { --mdc-icon-size: 16px; } .postponed-badge { display: inline-flex; align-items: center; gap: 3px; padding: 2px 8px; margin-left: 8px; background: var(--secondary-background-color, #e8e8e8); color: var(--secondary-text-color); border-radius: 10px; font-size: 11px; font-weight: 500; } .postponed-badge ha-icon { --mdc-icon-size: 13px; } .priority-high { color: var(--error-color, #db4437); } .priority-low { color: var(--secondary-text-color, #888); } .label-chip { background: var(--primary-color, #03a9f4); color: var(--text-primary-color, #fff); opacity: 0.85; } .label-chip ha-icon { --mdc-icon-size: 13px; } .nfc-badge ha-icon { --mdc-icon-size: 14px; } .nfc-badge.unlinked { opacity: 0.4; cursor: pointer; border: 1px dashed var(--divider-color); background: transparent; } .nfc-badge.unlinked:hover { opacity: 0.7; } .task-header-actions { display: flex; gap: 8px; } .more-menu-wrapper { position: relative; } /* Stale-bundle handshake banner (roadmap guard 2) */ .update-banner { display: flex; align-items: center; gap: 10px; padding: 8px 16px; background: color-mix(in srgb, var(--primary-color) 14%, var(--card-background-color, #fff)); border-bottom: 1px solid var(--divider-color); font-size: 14px; } .update-banner span { flex: 1; } .popup-menu { position: absolute; top: 100%; right: 0; background: var(--card-background-color, #fff); border: 1px solid var(--divider-color); border-radius: 8px; box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15); z-index: 100; min-width: 180px; overflow: hidden; } .popup-menu-item { display: flex; align-items: center; gap: 8px; padding: 10px 16px; cursor: pointer; font-size: 14px; color: var(--primary-text-color); } .popup-menu-item:hover { background: var(--table-row-alternative-background-color, rgba(0, 0, 0, 0.04)); } .popup-menu-item.danger { color: var(--error-color, #f44336); } .popup-menu-item ha-icon { --mdc-icon-size: 18px; } .popup-menu-divider { height: 1px; background: var(--divider-color); margin: 4px 0; } .tab-bar { display: flex; gap: 4px; border-bottom: 2px solid var(--divider-color); margin-bottom: 16px; } .tab { padding: 12px 24px; cursor: pointer; font-weight: 500; color: var(--secondary-text-color); border-bottom: 2px solid transparent; margin-bottom: -2px; transition: all 0.2s; } .tab:hover { color: var(--primary-text-color); } .tab.active { color: var(--primary-color); border-bottom-color: var(--primary-color); } .tab-content { padding: 16px 0; } .kpi-bar { display: grid; grid-template-columns: repeat(7, 1fr); gap: 12px; margin-bottom: 24px; } .kpi-card { background: var(--card-background-color, #fff); border-radius: 8px; padding: 16px 12px; text-align: center; border: 1px solid var(--divider-color); } .kpi-card.warning { border-color: #ff9800; background: rgba(255, 152, 0, 0.1); } .kpi-card.overdue { border-color: #f44336; background: rgba(244, 67, 54, 0.1); } .kpi-label { font-size: 11px; color: var(--secondary-text-color); margin-bottom: 6px; text-transform: uppercase; font-weight: 500; } .kpi-value { font-size: 16px; font-weight: 500; color: var(--primary-text-color); } .kpi-value-large { font-size: 22px; font-weight: 600; color: var(--primary-text-color); } .kpi-subtext { font-size: 10px; color: var(--secondary-text-color); margin-top: 4px; } .two-column-layout { display: grid; grid-template-columns: 40% 60%; gap: 16px; margin-bottom: 24px; } .two-column-layout.single-column { grid-template-columns: 1fr; } .left-column, .right-column { display: flex; flex-direction: column; gap: 16px; } .recent-activities { background: var(--card-background-color, #fff); border-radius: 8px; padding: 16px; border: 1px solid var(--divider-color); } .recent-activities h3 { margin: 0 0 12px 0; } .activity-item { display: flex; align-items: center; gap: 12px; padding: 8px 0; border-bottom: 1px solid var(--divider-color); } .activity-item:last-of-type { border-bottom: none; } .activity-icon { font-size: 18px; width: 24px; text-align: center; } .activity-date { font-size: 12px; color: var(--secondary-text-color); min-width: 120px; } .activity-note { flex: 1; font-size: 14px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; } .activity-badge { font-size: 12px; padding: 2px 8px; background: var(--primary-color); color: white; border-radius: 12px; } .activity-show-all { margin-top: 12px; text-align: center; } .history-filters-new { display: flex; justify-content: space-between; gap: 12px; margin-bottom: 16px; flex-wrap: wrap; } .filter-chips { display: flex; gap: 8px; flex-wrap: wrap; } .filter-controls { display: flex; gap: 8px; } .search-input { padding: 8px 12px; border: 1px solid var(--divider-color); border-radius: 4px; background: var(--card-background-color, #fff); color: var(--primary-text-color); font-size: 14px; min-width: 200px; } .search-input:focus { outline: none; border-color: var(--primary-color); } /* Checklist preview card (read-only display in task overview) */ .checklist-preview-card { background: var(--card-background-color, #fff); border-radius: 8px; padding: 12px 16px; border: 1px solid var(--divider-color); margin-top: 8px; } .checklist-preview-header { display: flex; align-items: center; gap: 6px; font-size: 13px; font-weight: 500; color: var(--secondary-text-color); margin-bottom: 8px; } .checklist-preview-header ha-icon { --mdc-icon-size: 18px; } .checklist-preview-list { margin: 0; padding-left: 20px; color: var(--primary-text-color); font-size: 14px; line-height: 1.6; } .checklist-preview-list li { padding: 1px 0; } /* Recommendation Card */ .recommendation-card { background: var(--card-background-color, #fff); border-radius: 8px; padding: 16px; border: 1px solid var(--divider-color); } .recommendation-card h4 { margin: 0 0 12px 0; font-size: 14px; } .interval-comparison { margin-bottom: 16px; } .interval-bar { margin-bottom: 12px; } .interval-label { font-size: 12px; margin-bottom: 4px; display: flex; align-items: center; gap: 8px; } .interval-visual { height: 24px; border-radius: 4px; transition: width 0.3s; } .interval-visual.current { background: var(--secondary-text-color); opacity: 0.5; } .interval-visual.suggested { background: var(--primary-color); } .confidence-badge { font-size: 10px; padding: 2px 6px; border-radius: 3px; background: var(--divider-color); } .confidence-badge.high { background: #4caf50; color: white; } .confidence-badge.medium { background: #ff9800; color: white; } .confidence-badge.low { background: var(--secondary-text-color); color: white; } .recommendation-actions { display: flex; gap: 8px; } /* Seasonal Card Compact */ .seasonal-card-compact { background: var(--card-background-color, #fff); border-radius: 8px; padding: 16px; border: 1px solid var(--divider-color); } .seasonal-card-compact h4 { margin: 0 0 12px 0; font-size: 14px; } .seasonal-mini-chart { display: flex; align-items: flex-end; gap: 4px; height: 60px; margin-bottom: 12px; } .seasonal-bar { flex: 1; border-radius: 2px 2px 0 0; transition: all 0.2s; cursor: pointer; } .seasonal-bar.low { background: #2196f3; } .seasonal-bar.normal { background: var(--secondary-text-color); opacity: 0.5; } .seasonal-bar.high { background: #ff9800; } .seasonal-bar.current { border: 2px solid var(--primary-color); box-sizing: border-box; } .seasonal-legend { display: flex; gap: 12px; font-size: 11px; } .legend-item { display: flex; align-items: center; gap: 4px; } .legend-item .dot { width: 8px; height: 8px; border-radius: 50%; } .legend-item .dot.low { background: #2196f3; } .legend-item .dot.normal { background: var(--secondary-text-color); opacity: 0.5; } .legend-item .dot.high { background: #ff9800; } /* Task meta card (notes + documentation URL) */ .task-meta-card { background: var(--card-background-color, #fff); border: 1px solid var(--divider-color); border-radius: 12px; padding: 12px 16px; margin-bottom: 16px; display: flex; flex-direction: column; gap: 10px; } .task-meta-row { display: flex; align-items: flex-start; gap: 8px; font-size: 14px; color: var(--primary-text-color); } .task-meta-row ha-icon { --mdc-icon-size: 18px; color: var(--secondary-text-color); flex-shrink: 0; margin-top: 2px; } .task-meta-notes { white-space: pre-wrap; word-break: break-word; } .task-meta-link a { color: var(--primary-color); text-decoration: none; } .task-meta-link a:hover { text-decoration: underline; } /* ── Responsive: :host([narrow]) (HA sets narrow on mobile/companion) ── */ :host([narrow]) .content { padding: 0 8px 8px; } :host([narrow]) .header { padding: 8px 12px; font-size: 14px; } :host([narrow]) .kpi-bar { grid-template-columns: repeat(2, 1fr); gap: 8px; margin-bottom: 16px; } :host([narrow]) .kpi-card { padding: 12px 8px; } :host([narrow]) .kpi-label { font-size: 10px; } :host([narrow]) .kpi-value { font-size: 14px; } :host([narrow]) .kpi-value-large { font-size: 18px; } :host([narrow]) .two-column-layout { grid-template-columns: 1fr; } :host([narrow]) .tab { /* Tight enough that the four tabs fit 412px in the longest languages — Ukrainian ("Налаштування") overflowed at 12px 16px (overflow sweep). */ padding: 12px 8px; font-size: 13px; } :host([narrow]) .task-header { flex-direction: column; align-items: flex-start; } :host([narrow]) .task-header-actions { width: 100%; justify-content: flex-start; /* Longer labels (de "Überspringen", fr "Archiver", …) overflow a phone viewport in a nowrap row — the ⋮ menu then needs a horizontal scroll to reach. Wrap instead; language-independent. */ flex-wrap: wrap; } :host([narrow]) .filter-bar { flex-wrap: wrap; } :host([narrow]) .filter-field { flex: 1; min-width: 48%; } :host([narrow]) .filter-bar select { flex: 1; min-width: 0; width: 100%; } :host([narrow]) .task-table { display: block; } :host([narrow]) .task-row { /* Mobile: 4-column grid keeps due-cell + actions at deterministic X-positions across rows regardless of content (sparkline, bar, %). Earlier flex-wrap-based layouts let the row wrap unpredictably so "X days" sometimes sat near the middle, sometimes at the right edge. Grid template: [badges auto | task-name 1fr | due-cell 100px | actions auto] Task-name spans the full top row (own row above), chips span the full bottom row. */ display: grid; grid-column: auto; grid-template-columns: auto minmax(0, 1fr) 100px auto; grid-template-rows: auto auto auto; column-gap: 8px; row-gap: 4px; padding: 12px; } :host([narrow]) .cell.type { display: none; } :host([narrow]) .cell.task-name { grid-column: 1 / -1; grid-row: 1; min-width: 0; } :host([narrow]) .cell-badges { grid-column: 1; grid-row: 2; } :host([narrow]) .cell.object-name { grid-column: 2; grid-row: 2; min-width: 0; /* Cap long object names at 2 lines with ellipsis instead of growing unbounded vertically. The full name is still readable via the panel object-detail view (one tap on the object). */ display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden; line-height: 1.2; } :host([narrow]) .due-cell { grid-column: 3; grid-row: 2; align-items: flex-end; min-width: 0; } :host([narrow]) .row-actions { grid-column: 4; grid-row: 2; } :host([narrow]) .task-sub { grid-column: 1 / -1; grid-row: 3; font-size: 11px; gap: 6px; justify-content: flex-start; flex-wrap: wrap; } :host([narrow]) .task-sub-empty { display: none; } :host([narrow]) .mini-sparkline { width: 50px; } :host([narrow]) .detail-header { flex-direction: column; align-items: flex-start; } :host([narrow]) .info-grid { grid-template-columns: 1fr; } :host([narrow]) .history-filters-new { flex-direction: column; } :host([narrow]) .search-input { min-width: 0; width: 100%; } :host([narrow]) .cost-duration-card { padding: 12px; } :host([narrow]) .card-header { flex-direction: column; align-items: flex-start; gap: 8px; } :host([narrow]) .toggle-buttons { width: 100%; } :host([narrow]) .toggle-btn { flex: 1; padding: 8px; font-size: 12px; } :host([narrow]) .activity-item { flex-wrap: wrap; } :host([narrow]) .activity-date { min-width: auto; } :host([narrow]) .activity-note { flex-basis: 100%; white-space: normal; } :host([narrow]) .popup-menu { right: auto; left: 0; min-width: 160px; } /* Cost/Duration Card with Toggle */ .cost-duration-card { background: var(--card-background-color, #fff); border-radius: 8px; padding: 16px; border: 1px solid var(--divider-color); } .card-header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 16px; } .card-header h3 { margin: 0; font-size: 16px; } .toggle-buttons { display: flex; gap: 4px; background: var(--divider-color); border-radius: 4px; padding: 2px; } .toggle-btn { padding: 6px 12px; border: none; background: transparent; color: var(--primary-text-color); cursor: pointer; border-radius: 3px; font-size: 13px; transition: all 0.2s; } .toggle-btn:hover { background: rgba(0, 0, 0, 0.05); } .toggle-btn.active { background: var(--primary-color); color: white; } /* ── Responsive: @media fallback (when narrow attr not set) ── */ @media (max-width: 768px) { .content { padding: 0 8px 8px; } .header { padding: 8px 12px; font-size: 14px; } .kpi-bar { grid-template-columns: repeat(2, 1fr); gap: 8px; margin-bottom: 16px; } .kpi-card { padding: 12px 8px; } .kpi-label { font-size: 10px; } .kpi-value { font-size: 14px; } .kpi-value-large { font-size: 18px; } .two-column-layout { grid-template-columns: 1fr; } .tab { padding: 12px 8px; font-size: 13px; } .task-header { flex-direction: column; align-items: flex-start; } .task-header-actions { width: 100%; justify-content: flex-start; flex-wrap: wrap; } .filter-bar { flex-wrap: wrap; } .filter-bar select { flex: 1; min-width: 0; } /* Mirror the :host([narrow]) grid layout for narrow desktop windows */ .task-row { display: grid; grid-template-columns: auto minmax(0, 1fr) 100px auto; grid-template-rows: auto auto auto; column-gap: 8px; row-gap: 4px; padding: 12px; } .cell.type { display: none; } .cell.task-name { grid-column: 1 / -1; grid-row: 1; min-width: 0; } .cell-badges { grid-column: 1; grid-row: 2; } .cell.object-name { grid-column: 2; grid-row: 2; min-width: 0; display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden; line-height: 1.2; } .due-cell { grid-column: 3; grid-row: 2; align-items: flex-end; min-width: 0; } .row-actions { grid-column: 4; grid-row: 2; } .task-sub { grid-column: 1 / -1; grid-row: 3; font-size: 11px; gap: 6px; justify-content: flex-start; flex-wrap: wrap; } .task-sub-empty { display: none; } .mini-sparkline { width: 50px; } .detail-header { flex-direction: column; align-items: flex-start; } .info-grid { grid-template-columns: 1fr; } .history-filters-new { flex-direction: column; } .search-input { min-width: 0; width: 100%; } .cost-duration-card { padding: 12px; } .card-header { flex-direction: column; align-items: flex-start; gap: 8px; } .toggle-buttons { width: 100%; } .toggle-btn { flex: 1; padding: 8px; font-size: 12px; } .activity-item { flex-wrap: wrap; } .activity-date { min-width: auto; } .activity-note { flex-basis: 100%; white-space: normal; } .popup-menu { right: auto; left: 0; min-width: 160px; } } /* ha-button handles variant="danger" natively */ .toast { position: fixed; bottom: 24px; left: 50%; transform: translateX(-50%); background: var(--error-color, #f44336); color: #fff; padding: 12px 24px; border-radius: 8px; font-size: 14px; z-index: 1000; box-shadow: 0 2px 8px rgba(0,0,0,.3); animation: toast-in .3s ease; display: flex; align-items: center; gap: 16px; } .toast-undo { font: inherit; font-weight: 600; color: #fff; cursor: pointer; background: transparent; border: 1px solid rgba(255,255,255,.6); border-radius: 6px; padding: 4px 12px; text-transform: uppercase; font-size: 12.5px; } .toast-undo:hover { background: rgba(255,255,255,.15); } @keyframes toast-in { from { opacity: 0; transform: translateX(-50%) translateY(16px); } to { opacity: 1; transform: translateX(-50%) translateY(0); } } `;