/** Component-specific styles for the maintenance-supporter-panel. */ import { css } from "lit"; export const panelStyles = css` /* #170: reference chips in front of list names. */ .task-name .ref-chip, .today-task .ref-chip, .object-card-name .ref-chip, .objects-table-name .ref-chip { margin-right: 6px; } :host { display: block; height: 100%; background: var(--primary-background-color); } /* The panel lays itself out: a bounded host, the header on top and .content as THE scroll container (virtualized table, sticky bulk bar and the docked split-view detail all hang off its scroll position). HA 2026.8 started wrapping custom panels in a height-less, safe-area padded block — which turned our 100% into "auto" and moved scrolling to the document. panel.py opts out of that wrapper (handle_safe_area), so the insets are ours to apply: same padding HA would have added, inside the bounded box. The vars are 0 on desktop / older cores. */ .panel { height: 100%; box-sizing: border-box; padding: var(--safe-area-inset-top, 0px) var(--safe-area-content-inset-right, var(--safe-area-inset-right, 0px)) var(--safe-area-inset-bottom, 0px) var(--safe-area-content-inset-left, var(--safe-area-inset-left, 0px)); display: flex; flex-direction: column; } /* #174: inside a card the dashboard wrapper owns the insets. */ :host([embedded]) .panel { padding: 0; } .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 class is only ever set when the host is narrow — desktop always renders inline. */ .filter-bar.collapsed { display: none; } .mobile-controls { display: flex; gap: 8px; padding: 8px 0 0; } .mobile-controls .mobile-toggle, .mobile-controls .new-menu-wrapper { flex: 1; } .mobile-controls .new-menu-wrapper .new-menu-button { width: 100%; } .mobile-controls .mobile-toggle.active { --ha-button-background: var(--primary-color); } /* #125: the one "New" menu that replaced the six-button actions bar. */ .new-menu-wrapper { position: relative; margin-left: auto; } .new-menu-popup { right: 0; left: auto; min-width: 256px; } .new-menu-popup .popup-menu-item { display: flex; align-items: center; gap: 10px; } .new-menu-popup .popup-menu-item ha-icon { --mdc-icon-size: 18px; color: var(--secondary-text-color); } :host([narrow]) .new-menu-popup { right: 0; left: auto; } /* #125: getting-started chips — visibly temporary onboarding hints. */ .gs-chips-wrap { margin: 2px 0 12px; } .gs-chips-label { font-size: 11px; color: var(--secondary-text-color); text-transform: uppercase; letter-spacing: 0.6px; margin-bottom: 7px; } .gs-chips { display: flex; gap: 10px; flex-wrap: wrap; } .gs-chip { display: flex; align-items: center; gap: 9px; background: var(--secondary-background-color); border: 1px dashed var(--divider-color); border-radius: 16px; padding: 7px 8px 7px 13px; font-size: 12.5px; cursor: pointer; } .gs-chip ha-icon { --mdc-icon-size: 16px; color: var(--primary-color); } .gs-chip-x { display: inline-flex; padding: 0 3px; } .gs-chip-x ha-icon { --mdc-icon-size: 14px; color: var(--secondary-text-color); } .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; } /* Saved-views "save" icon-button sits in the select row: size it to the select (36px) so its icon centres on the select, not 6px above it. HA ≥2025.11 sizes ha-icon-button via --ha-icon-button-size (the old --mdc-icon-button-size is a no-op there); older cores the other way round, so declare both. */ .views-save-btn { --ha-icon-button-size: 36px; --mdc-icon-button-size: 36px; --mdc-icon-size: 20px; color: var(--secondary-text-color); margin: 0 -2px 0 -4px; } .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; } /* Group-by sections: the OUTER .task-table owns the column tracks (every breakpoint's template above applies to it unchanged); each section card and its row block are column subgrids, so the badge/object/name columns line up across sections — a section whose rows are all "OK" no longer starts its names 60px left of one that also carries "Overdue". */ .task-table.grouped > .group-section { grid-column: 1 / -1; display: grid; grid-template-columns: subgrid; } .task-table.grouped .group-section-header { grid-column: 1 / -1; } .task-table.grouped .group-rows { grid-column: 1 / -1; display: grid; grid-template-columns: subgrid; padding: 0 12px; } /* Object page task list: it has no object-name cell (the page IS the object), so under the shared 7-track template every cell sat one track to the left — the ~190px action pair landed in the 150px due track and poked past the row's right edge (a horizontal scrollbar as soon as a classic vertical one took its 17px; wider still with German labels). Six tracks for six cells. Below 769px the narrow/tight layouts place the cells explicitly and keep their own four tracks. */ @media (min-width: 769px) { .task-table.object-tasks { grid-template-columns: auto minmax(120px, 1fr) minmax(0, 220px) 100px 150px auto; } } @media (min-width: 1200px) { .task-table.object-tasks { grid-template-columns: auto fit-content(400px) minmax(0, 1fr) 100px 150px auto; } } /* Master-detail split (>=1500px panel, 2026-09-01): list left, docked task detail right - the width finally carries content instead of slack. Only the dashboard tab uses it; below the threshold everything is unchanged. */ .split-layout { display: grid; grid-template-columns: minmax(0, 1fr) minmax(430px, 41%); gap: 20px; align-items: start; } .split-list { min-width: 0; } /* Inside the split the list is ~60% wide — the type and chips columns are redundant there (the docked detail names the type, assignee and labels), and squeezed chips read as clutter. Drop both; the task name gets the room and the list stays calm. */ .split-list .task-table { grid-template-columns: auto minmax(100px, 180px) minmax(120px, 1fr) 150px auto; } .split-list .task-table.bulk { grid-template-columns: auto auto minmax(100px, 180px) minmax(120px, 1fr) 150px auto; } .split-list .cell.type { display: none; } .split-list .task-sub { display: none; } /* Grouped list: the section cards carry a 12px vertical margin that does not collapse inside the grid, so the first card sat 12px below the pane's top edge and the two cards read as misaligned. Level them. */ .split-list .task-table.grouped > .group-section:first-child { margin-top: 0; } /* The pane never scrolls on its own: it is as tall as the detail and docks scroll-aware (helpers/sticky-pane sets top/margin-top inline) — top edge while scrolling up, bottom edge while scrolling down, so a long detail is read by scrolling the list, not a nested scrollbar. */ .split-pane { min-width: 0; position: sticky; top: 8px; border: 1px solid var(--divider-color); border-radius: 12px; padding: 4px 16px 16px; background: var(--card-background-color, #fff); } .split-pane-empty { display: flex; flex-direction: column; align-items: center; justify-content: center; min-height: 240px; gap: 8px; color: var(--secondary-text-color); text-align: center; } .split-pane-empty ha-icon { --mdc-icon-size: 40px; opacity: 0.5; } .task-row.selected { background: rgba(var(--rgb-primary-color, 3, 169, 244), 0.1); box-shadow: inset 3px 0 0 var(--primary-color); } .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-more-wrapper { position: relative; display: inline-flex; } .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; } /* #169: the sub-line is a flex row of two parts that truncate on their own — a long object name shortens itself instead of pushing the person chip out of the row on a phone (a single nowrap text line cut the chip first, i.e. entirely). The chip keeps a readable minimum. */ .today-object { color: var(--secondary-text-color); font-size: 12.5px; display: flex; align-items: center; gap: 8px; min-width: 0; } .today-object-text { flex: 1 1 auto; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; } .today-object .today-person { flex: 0 0 auto; min-width: 0; max-width: 45%; font-size: 12px; } .today-object .today-person .person-avatar { width: 18px; height: 18px; font-size: 9px; } /* Narrow: the avatar alone — initials + colour identify the member, the object name keeps its room. */ :host([narrow]) .today-object .today-person .person-name, :host([tight]) .today-object .today-person .person-name { display: none; } /* The row actions are the shared _renderRowActions markup (Complete only, no Skip on Today); .row-actions carries the icon/button sizing. */ .today-row .row-actions { flex: none; } .today-row .btn-complete { color: var(--success-color, #4caf50); } :host([narrow]) .today-row .row-actions.as-buttons ha-button, :host([tight]) .today-row .row-actions.as-buttons ha-button { min-width: 0; --ha-button-height: 36px; } .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); } .header .header-search { margin-left: auto; color: var(--app-header-text-color, white); --ha-icon-button-size: 40px; flex: none; } .tab-bar .tab-search { margin-left: auto; align-self: center; color: var(--secondary-text-color); --ha-icon-button-size: 40px; flex: none; } /* Global search overlay ("/" or the header magnifier; #171). */ .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-main { min-width: 0; flex: 1; } .palette-line { display: flex; align-items: center; gap: 8px; min-width: 0; } .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; max-width: 45%; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; } .palette-page { flex: none; font-size: 11.5px; padding: 1px 7px; border-radius: 10px; background: color-mix(in srgb, var(--primary-color) 14%, transparent); color: var(--primary-color); } .palette-snippet { font-size: 12.5px; color: var(--secondary-text-color); margin-top: 2px; overflow: hidden; display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; } .palette-group { padding: 10px 16px 4px; font-size: 11px; font-weight: 600; letter-spacing: .06em; text-transform: uppercase; color: var(--secondary-text-color); } .palette-group + .palette-item { border-top: none; } .palette-waiting { font-weight: 400; text-transform: none; letter-spacing: 0; padding-bottom: 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; } /* Task name + object name travel as one group: in the wide grids the wrapper dissolves (display: contents) so both stay direct grid items in their own tracks; the narrow/tight layouts turn it into the first row (name left, object right — #150). */ .row-head { display: contents; } .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; /* The chips track is minmax(0, …): when the row gets squeezed the track may shrink below the chips' natural width — clip instead of painting over the neighbouring type column (duty-rotation GIF, 2026-08-30). */ min-width: 0; overflow: hidden; /* …and all-or-nothing: below 60 px of track there is no readable chip, only fragments — hide them entirely via the container query below. */ container-type: inline-size; } @container (max-width: 60px) { .sub-chip { display: none; } } /* 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; /* A single chip wider than the squeezed track truncates instead of bleeding into the next column. */ max-width: 100%; overflow: hidden; text-overflow: ellipsis; } .sub-chip ha-icon { --mdc-icon-size: 14px; opacity: 0.75; } /* #169 follow-up: the member avatar sits inside the pill at chip height */ .sub-chip .person-avatar { width: 16px; height: 16px; font-size: 8.5px; margin-left: -4px; } /* 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; } /* DESIGN PROTOTYPE (#145 wish 1): labelled, colour-coded action buttons instead of bare icons. Complete = filled success pill, Skip = outlined warning (orange) pill; both keep the icon as a leading glyph. */ .row-actions.as-buttons { gap: 4px; } .row-actions.as-buttons ha-button { --ha-button-font-size: 13px; white-space: nowrap; } .row-actions.as-buttons ha-icon { --mdc-icon-size: 18px; } /* 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; } /* #179: object-page sections fold away. A chevron gutter beside each section; open sections keep their own heading line (the child component's h3 — buttons and all), the chevron is aligned to it per section because their top margins differ. Collapsed = heading row only. */ .obj-section { display: grid; grid-template-columns: 28px minmax(0, 1fr); align-items: start; } .obj-section-toggle { grid-column: 1; width: 28px; height: 28px; padding: 0; margin: 12px 0 0; display: inline-flex; align-items: center; justify-content: center; border: none; border-radius: 50%; background: none; font: inherit; color: var(--secondary-text-color); cursor: pointer; } .obj-section-toggle:hover { background: var(--secondary-background-color); color: var(--primary-text-color); } .obj-section-toggle:focus-visible { outline: 2px solid var(--primary-color); outline-offset: 1px; } .obj-section-toggle ha-icon { --mdc-icon-size: 22px; } .obj-section-body, .obj-section-title { grid-column: 2; min-width: 0; } .obj-section-title { display: flex; align-items: center; gap: 8px; cursor: pointer; user-select: none; } .obj-section-count { font-size: 12px; font-weight: 400; color: var(--secondary-text-color); background: var(--secondary-background-color); padding: 2px 8px; border-radius: 999px; } .obj-section.parts.open > .obj-section-toggle { margin-top: 17px; } .obj-section.history.open > .obj-section-toggle { margin-top: 32px; } @media (max-width: 768px) { .obj-section { grid-template-columns: 24px minmax(0, 1fr); } .obj-section-toggle { width: 24px; } } .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; } /* Markdown notes: parses the source itself — the container's pre-wrap must not leak into its rendered paragraphs. */ .object-notes-body ha-markdown, .task-meta-notes ha-markdown { white-space: normal; } .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; } /* (#130) All-parts view: sibling chip in the breadcrumb + table extras */ .sibling-view-chip { display: inline-flex; align-items: center; gap: 4px; margin-left: 12px; padding: 4px 10px; border: 1px solid var(--divider-color); border-radius: 14px; background: none; color: var(--secondary-text-color); font: inherit; font-size: 13px; cursor: pointer; } .sibling-view-chip:hover { background: var(--secondary-background-color, rgba(0, 0, 0, 0.04)); color: var(--primary-text-color); } .sibling-view-chip ha-icon { --mdc-icon-size: 16px; } .part-low-icon { --mdc-icon-size: 16px; color: var(--warning-color, #ff9800); vertical-align: middle; margin-left: 6px; } .part-consumer-chip { display: inline-block; margin: 1px 4px 1px 0; padding: 1px 8px; border: 1px solid var(--divider-color); border-radius: 10px; font-size: 12px; color: var(--secondary-text-color); } .part-consumer-chip.pooled { border-style: dashed; } /* (#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: 5px; padding: 2px 8px 2px 3px; margin-left: 8px; background: var(--secondary-background-color, rgba(127, 127, 127, 0.12)); color: var(--primary-text-color); border-radius: 12px; font-size: 11px; font-weight: 500; line-height: 1.4; } .user-badge .person-avatar { width: 18px; height: 18px; font-size: 9px; } .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; flex-wrap: wrap; align-items: center; gap: 6px 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; } /* The text claims the row; on a phone the buttons wrap under it instead of squeezing it into a one-word-per-line column (#150 round, 2026-09-02). */ .update-banner span { flex: 1 1 220px; } .update-banner ha-button { margin-left: auto; } .update-banner ha-button + ha-button { margin-left: 0; } .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); } /* #142: "add a past completion" entry point in the history tab — the backdate field lives in the complete dialog, but people LOOK for it here. */ .history-add-past { display: flex; justify-content: flex-end; margin-bottom: 4px; } .history-add-past-btn ha-icon { --mdc-icon-size: 18px; margin-right: 4px; } /* #139: cycle-phase strip in the task overview */ .phases-card { background: var(--card-background-color, #fff); border-radius: 8px; padding: 12px 16px; border: 1px solid var(--divider-color); margin-top: 8px; } .phases-card-header { display: flex; align-items: center; gap: 6px; font-size: 13px; font-weight: 500; margin-bottom: 8px; } .phases-card-header ha-icon { --mdc-icon-size: 18px; color: var(--secondary-text-color); } .phases-strip { display: flex; flex-wrap: wrap; gap: 8px; } .phase-step { display: flex; flex-direction: column; gap: 2px; padding: 6px 12px; border-radius: 8px; border: 1px solid var(--divider-color); font-size: 13px; cursor: pointer; } .phase-step.current { border-color: var(--primary-color); background: color-mix(in srgb, var(--primary-color) 12%, transparent); font-weight: 500; cursor: default; } .phase-step-last { font-size: 11px; color: var(--secondary-text-color); font-weight: 400; } /* 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; } /* #73: interactive in-cycle ticks. */ .checklist-preview-list label { display: inline-flex; gap: 8px; align-items: baseline; cursor: pointer; } .checklist-preview-list input[type="checkbox"] { accent-color: var(--primary-color); cursor: pointer; } .checklist-preview-list li.checked label span { text-decoration: line-through; opacity: 0.6; } /* 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%; } /* Shared column tracks across rows (the wide layout's #66 lesson, applied to narrow 2026-09-01): with per-row grids the auto badge/actions columns sized independently, so the object-name column started at a different X in every row — an "OK" pill vs "Due Soon" + priority chevron made the list read jittery. The LIST owns the four tracks, rows subgrid them. */ :host([narrow]) .task-table { display: grid; grid-template-columns: auto minmax(76px, 1fr) 100px auto; column-gap: 8px; } :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. Two rows since #150 (was three: name / badge+object+due+actions / chips — a phone list scrolled forever): row 1 task name ................................ object name row 2 [status icon | chips | due 100px | actions] The status pill drops its label (icon + colour stay, title/aria keep the word), the object name moves up beside the task name, and the chips take the freed middle track. */ display: grid; grid-column: 1 / -1; grid-template-columns: subgrid; grid-template-rows: auto auto; row-gap: 6px; padding: 12px; } :host([narrow]) .cell.type { display: none; } :host([narrow]) .row-head { display: flex; grid-column: 1 / -1; grid-row: 1; align-items: baseline; justify-content: space-between; gap: 10px; min-width: 0; } /* The object page's rows have no object name — there the task name is a direct grid item and the grid placement applies; inside .row-head the flex rule wins and the grid lines are inert. */ :host([narrow]) .cell.task-name { grid-column: 1 / -1; grid-row: 1; flex: 1 1 auto; min-width: 0; } :host([narrow]) .cell.object-name { order: 1; flex: 0 1 auto; max-width: 50%; font-size: 12px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; text-align: right; } /* Bulk selection on a phone: the checkbox takes the badge column of the first row, the name group starts one track further right. */ :host([narrow]) .task-table.bulk .bulk-check { grid-column: 1; grid-row: 1; align-self: start; } :host([narrow]) .task-table.bulk .row-head { grid-column: 2 / -1; } :host([narrow]) .cell-badges { grid-column: 1; grid-row: 2; /* Stack extra badges (priority chevron, NFC, disabled) under the status pill: in the shared-track table the widest badge ROW sized column 1 for every row, and a pill+chevron combo pushed Complete/Skip off the right edge at phone widths (2026-09-01). */ flex-direction: column; align-items: flex-start; gap: 4px; } /* Icon-only status pill (#150): a round 22 px disc; the colour + shape still carry the state, the label lives in title/aria-label. */ :host([narrow]) .task-row .status-label { display: none; } :host([narrow]) .task-row .status-badge { min-width: 0; padding: 3px; border-radius: 50%; } :host([narrow]) .task-row .status-badge ha-icon { --mdc-icon-size: 16px; margin-left: 0; } :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; } /* Prototype: on phones the two labelled pills stack, so the row keeps its deterministic column X-positions and neither label gets truncated. */ /* Labelled buttons stack when the row cannot hold them side by side: narrow viewports AND a tight panel (iPad portrait with the sidebar docked — the viewport is not narrow but the panel is ~768 px). */ :host([narrow]) .row-actions.as-buttons, :host([tight]) .row-actions.as-buttons { flex-direction: column; align-items: stretch; gap: 4px; } :host([narrow]) .row-actions.as-buttons ha-button, :host([tight]) .row-actions.as-buttons ha-button { --ha-button-font-size: 12px; } /* buttons_compact: the labelled buttons collapse to two icon-only ha-buttons side by side (solid green Complete, outlined orange Skip) — the colour carries the meaning, the row keeps the icon-era height. --wa-form-control-padding-inline is the wa-button inset HA's ha-button forwards (16 px default -> a 58 px pill around a 20 px glyph). */ :host([narrow]) .row-actions.as-buttons.compact, :host([tight]) .row-actions.as-buttons.compact { flex-direction: row; align-items: center; gap: 4px; } :host([narrow]) .row-actions.compact ha-button, :host([tight]) .row-actions.compact ha-button { --ha-button-height: 36px; --wa-form-control-padding-inline: 10px; min-width: 0; } :host([narrow]) .row-actions.compact ha-icon, :host([tight]) .row-actions.compact ha-icon { --mdc-icon-size: 20px; } /* A TIGHT panel that is not narrow (iPad portrait with HA's sidebar docked: 1024 px viewport, ~768 px panel) gets the narrow row layout too — the wide subgrid overflowed its last column at that width even before the buttons. Same rules as the :host([narrow]) block above / the max-width:768px media mirror below, keyed on the panel's own width. */ :host([tight]:not([narrow])) .task-table { display: grid; grid-template-columns: auto minmax(76px, 1fr) 100px auto; column-gap: 8px; } :host([tight]:not([narrow])) .task-row { display: grid; grid-column: 1 / -1; grid-template-columns: subgrid; grid-template-rows: auto auto; row-gap: 6px; padding: 12px; min-width: 0; } :host([tight]:not([narrow])) .cell.type { display: none; } :host([tight]:not([narrow])) .row-head { display: flex; grid-column: 1 / -1; grid-row: 1; align-items: baseline; justify-content: space-between; gap: 10px; min-width: 0; } :host([tight]:not([narrow])) .cell.task-name { grid-column: 1 / -1; grid-row: 1; flex: 1 1 auto; min-width: 0; } :host([tight]:not([narrow])) .cell.object-name { order: 1; flex: 0 1 auto; max-width: 50%; font-size: 12px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; text-align: right; } :host([tight]:not([narrow])) .task-table.bulk .bulk-check { grid-column: 1; grid-row: 1; align-self: start; } :host([tight]:not([narrow])) .task-table.bulk .row-head { grid-column: 2 / -1; } :host([tight]:not([narrow])) .cell-badges { grid-column: 1; grid-row: 2; flex-direction: column; align-items: flex-start; gap: 4px; } :host([tight]:not([narrow])) .task-row .status-label { display: none; } :host([tight]:not([narrow])) .task-row .status-badge { min-width: 0; padding: 3px; border-radius: 50%; } :host([tight]:not([narrow])) .task-row .status-badge ha-icon { --mdc-icon-size: 16px; margin-left: 0; } :host([tight]:not([narrow])) .due-cell { grid-column: 3; grid-row: 2; align-items: flex-end; min-width: 0; } :host([tight]:not([narrow])) .row-actions { grid-column: 4; grid-row: 2; } :host([tight]:not([narrow])) .task-sub { grid-column: 2; grid-row: 2; align-self: center; font-size: 11px; gap: 4px; justify-content: flex-start; flex-wrap: wrap; } :host([tight]:not([narrow])) .task-sub-empty { display: none; } :host([tight]:not([narrow])) .mini-sparkline { display: none; } :host([tight]:not([narrow])) .trend-arrow { display: inline; } /* Chips sit in the middle track of row 2 — the slot the object name vacated — vertically centred on the 36 px action buttons. */ :host([narrow]) .task-sub { grid-column: 2; grid-row: 2; align-self: center; font-size: 11px; gap: 4px; justify-content: flex-start; flex-wrap: wrap; } :host([narrow]) .task-sub-empty { display: none; } :host([narrow]) .mini-sparkline { display: none; } :host([narrow]) .trend-arrow { display: inline; } /* Phone band (≤429px): with SHARED tracks the widest badge sizes column 1 for every row, and the fixed 100px due column + both action buttons no longer fit — Skip fell off the right edge. Tighter floors, slimmer row padding and a smaller Skip icon-button. Bars stay uniform per breakpoint (100px above, 84px here). */ @media (max-width: 429px) { :host([narrow]) .task-table { grid-template-columns: auto minmax(64px, 1fr) 84px auto; column-gap: 6px; } :host([narrow]) .task-row { padding: 12px 8px; } } :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 — incl. the shared-track table so rows align (2026-09-01). */ .task-table { display: grid; grid-template-columns: auto minmax(76px, 1fr) 100px auto; column-gap: 8px; } .task-row { display: grid; grid-column: 1 / -1; grid-template-columns: subgrid; grid-template-rows: auto auto; row-gap: 6px; padding: 12px; } .cell.type { display: none; } .row-head { display: flex; grid-column: 1 / -1; grid-row: 1; align-items: baseline; justify-content: space-between; gap: 10px; min-width: 0; } .cell.task-name { grid-column: 1 / -1; grid-row: 1; flex: 1 1 auto; min-width: 0; } .cell.object-name { order: 1; flex: 0 1 auto; max-width: 50%; font-size: 12px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; text-align: right; } .task-table.bulk .bulk-check { grid-column: 1; grid-row: 1; align-self: start; } .task-table.bulk .row-head { grid-column: 2 / -1; } .cell-badges { grid-column: 1; grid-row: 2; flex-direction: column; align-items: flex-start; gap: 4px; } .task-row .status-label { display: none; } .task-row .status-badge { min-width: 0; padding: 3px; border-radius: 50%; } .task-row .status-badge ha-icon { --mdc-icon-size: 16px; margin-left: 0; } .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: 2; grid-row: 2; align-self: center; font-size: 11px; gap: 4px; justify-content: flex-start; flex-wrap: wrap; } .task-sub-empty { display: none; } .mini-sparkline { display: none; } .trend-arrow { display: inline; } .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); } } `;