From b94b1b93cd07a856e93c22ba8986d1a5a2803aa7 Mon Sep 17 00:00:00 2001 From: Sergey Matyunin Date: Sun, 16 Aug 2026 21:46:32 +0300 Subject: [PATCH 1/3] docs: specify sun north rotation fix Issue: #166 User-Visible: no --- docs/specs/166-sun-north-rotation.md | 331 +++++++++++++++++++++++++++ docs/specs/README.md | 3 +- 2 files changed, 333 insertions(+), 1 deletion(-) create mode 100644 docs/specs/166-sun-north-rotation.md diff --git a/docs/specs/166-sun-north-rotation.md b/docs/specs/166-sun-north-rotation.md new file mode 100644 index 00000000..22766437 --- /dev/null +++ b/docs/specs/166-sun-north-rotation.md @@ -0,0 +1,331 @@ +# Issue #166 — солнечные лучи зеркально учитывают направление севера + +Статус: **ТЗ на ревью** +Дата: 2026-08-16 +Тип: `bug` · приоритет: `P1` · user value: 8/10 · complexity: 4/10 · risk: 6/10 + +Issue: [#166](https://github.com/Matysh/houseplan-card/issues/166) +Ветка: `issue/166-sun-north-rotation` +Канонические документы: [SCOPE](../SCOPE.md), [SUN](../SUN.md), +[USER-GUIDE](../USER-GUIDE.md), [USER-GUIDE.ru](../USER-GUIDE.ru.md), +[TOUCH-SUPPORT](../TOUCH-SUPPORT.md), [TESTING](../TESTING.md), +[CONFIG-COMPATIBILITY](../CONFIG-COMPATIBILITY.md). + +## 1. Сценарий и продуктовый контекст + +Основная персона — владелец дома, который настроил план по реальной ориентации +здания и ежедневно смотрит его в Full View либо на kiosk-панели. Он поворачивает +стрелку N в настройках туда, где на плане действительно находится север, и +ожидает, что оконные лучи будут соответствовать текущему положению Солнца. + +Это прямой контракт SCOPE J1: House Plan должен правдиво показывать текущее +состояние дома. Выбор окна с неправильной стороны здания является фактически +ложной визуализацией, даже если сам эффект декоративный. + +## 2. Что человек увидит до и после + +**До:** если истинный север не совпадает с верхом холста, поворот стрелки N +зеркально поворачивает солнечное направление. Чтобы осветилось физически верное +окно, пользователю приходится намеренно ставить компас не по реальному северу. +При `north_deg = 0` ошибка незаметна. + +**После:** стрелка N буквально указывает истинный север на плане. House Plan +комбинирует это направление с азимутом `sun.sun`, выбирает окно на физически +правильной стороне и ведёт луч от него внутрь комнаты. Ручная зеркальная +компенсация больше не нужна. + +## 3. Подтверждённая причина + +1. `_compassPoint()` в `src/houseplan-card.ts` вычисляет угол через + `atan2(dx, -dy)`: верх холста = 0°, право = 90°, низ = 180°, лево = 270°. +2. `_renderCompass()` поворачивает стрелку N на сохранённый `north_deg`, поэтому + UI показывает именно направление истинного севера на холсте. +3. `docs/SUN.md` определяет `north_deg` тем же образом: градусы по часовой + стрелке от верха холста до истинного севера. +4. `planSunAngle()` в `src/sun.ts` вопреки этому вычисляет + `norm360(azimuth - northDeg)`. Вычитание отражает поворот относительно + нулевой оси; для заявленной семантики направления должны складываться. +5. `test/sun.test.mjs` закрепляет неверный знак ожиданием «east sun + + `north_deg=90` → up» и выбором верхнего окна. +6. `demo/fixtures/visual-matrix.mjs` использует `north_deg = 0`, поэтому + действующие golden-сцены не различают сложение и вычитание. + +Репорт подтверждён по коду и является дефектом координатного преобразования. + +## 4. Координатный контракт + +Все углы нормализуются в `[0, 360)` и растут по часовой стрелке. + +- `A` (`azimuth`) — направление от истинного севера к Солнцу из `sun.sun`: + N=0°, E=90°, S=180°, W=270°. +- `N` (`north_deg`) — направление истинного севера на холсте: верх=0°, + право=90°, низ=180°, лево=270°. +- `P` — направление **к Солнцу** на холсте. + +Каноническая формула: + +```text +P = norm360(N + A) +toSun = (sin(P), -cos(P)) +awayFromSun = -toSun +``` + +Здесь ось X растёт вправо, ось Y — вниз. `toSun` используется для проверки, +какая внешняя нормаль окна смотрит на Солнце; `awayFromSun` остаётся +направлением хода света от окна внутрь комнаты. + +Контрольные примеры: + +| Север на холсте N | Азимут A | Направление к Солнцу P | Сторона холста | +|---:|---:|---:|---| +| 0° (вверх) | 90° (восток) | 90° | справа | +| 90° (вправо) | 0° (север) | 90° | справа | +| 90° (вправо) | 90° (восток) | 180° | снизу | +| 270° (влево) | 90° (восток) | 0° | сверху | +| 350° | 20° | 10° | wrap через 360° | + +## 5. Scope + +В задачу входят: + +1. исправление преобразования `azimuth + north_deg` в единственной чистой + функции солнечного направления; +2. сохранение действующего контракта вектора к Солнцу и противоположного + вектора луча внутрь комнаты; +3. исправление unit-ожиданий, которые сейчас закрепляют неверный знак; +4. smoke-проверка выбора окон при ненулевом global `north_deg` и per-space + override; +5. детерминированная golden-сцена с ненулевым севером, чтобы знак поворота был + визуально различим; +6. исправление формулы и примеров в `docs/SUN.md` и пользовательских руководствах; +7. предупреждение о ранее компенсированных настройках в обоих changelog; +8. обычные bundle/release-артефакты пользовательского исправления. + +## 6. Non-scope + +В задачу не входят: + +- изменение значений либо частоты обновления `sun.sun`; +- новая астрономическая модель, геолокация или расчёт положения Солнца внутри + House Plan; +- изменение длины, цвета, opacity, fade, rim или clipping солнечных лучей; +- изменение определения внешнего окна либо взаимное затенение крыльями здания; +- изменение четырёхфазного фона `daynight`, который по контракту #146 не + зависит от компаса; +- изменение UI компаса, его диапазона, шага, inheritance или доступности; +- изменение режима редакторов или добавление лучей в статическую + `houseplan-space-card`; +- автоматическая миграция либо эвристическое распознавание пользовательской + компенсации старого дефекта. + +## 7. Функциональное поведение + +### 7.1. Преобразование направления + +`planSunAngle(azimuth, northDeg)` возвращает +`norm360(azimuth + northDeg)`. Для любых конечных входов сохраняется текущая +нормализация угла. `sunDirOnPlan()` продолжает возвращать единичный вектор к +Солнцу в координатах холста. + +Знак меняется только в композиции двух систем координат. Нельзя одновременно +инвертировать `toSun`, нормали окон или `awayFromSun`: это дало бы локально +правильный тест, но снова перепутало бы освещённую сторону либо направление +луча внутри комнаты. + +### 7.2. Выбор окна и геометрия луча + +`windowLit()` продолжает сравнивать внешнюю нормаль окна с `toSun`. После +исправления выбирается окно со стороны холста, соответствующей `P`. Геометрия +луча продолжает строиться вдоль `-toSun`, то есть от освещённого окна внутрь +принимающей комнаты. Все действующие правила exterior/interior, wall depth, +room clipping, physical obstacles, gradient и rim сохраняются. + +Для контрольного плана с окнами на четырёх сторонах, `north_deg=90` и +`azimuth=90` должно выбираться нижнее окно холста, а не верхнее. Луч от нижнего +окна идёт вверх, внутрь комнаты. + +### 7.3. Global и per-space + +Правило наследования не меняется: явный `north_deg` пространства выигрывает у +global; пустой override наследует global; отсутствие обоих отключает оконные +лучи. И global, и override используют одну исправленную формулу. Preview +несохранённого значения в открытом диалоге должен давать тот же результат, +что и значение после сохранения. + +### 7.4. Гейты и соседние режимы + +Без изменений остаются: + +- отсутствие/невалидность `sun.sun` выключает оконные лучи; +- `elevation <= 0` не создаёт геометрию, а 3°-порог и двухсекундный fade + продолжают действовать по текущему контракту; +- `sun_rays=false`, editor mode и отсутствие `north_deg` выключают слой; +- `bg_mode=daynight` и декоративный свет #146 не используют `north_deg`; +- memo key уже содержит `azimuth`, `elevation`, effective north и config epoch; + его состав менять не требуется. + +## 8. UX, accessibility, touch и i18n + +Компас, number input, 1°/15° шаги, pointer capture, клавиатурный ввод и +подписи остаются прежними. Исправляется результат существующей настройки, а +не interaction. + +Full View и kiosk сохраняют полный touch-контракт. Настройка компаса остаётся +desktop-first поверхностью редактора настроек; новых жестов или hover-only +действий нет. Новых i18n-ключей не требуется. EN/RU пользовательская +документация должна однозначно сказать, что стрелка N направляется туда, где +на плане находится истинный север. + +## 9. Данные, migration и совместимость + +Схема не меняется: `north_deg` остаётся integer 0–359 на global и per-space +уровнях. Backend validation, storage version, export/import и default `null` +не меняются. + +Автомиграции `north_deg -> 360 - north_deg` нет. Она повредила бы корректно +сохранённые по документированному смыслу значения и не может отличить их от +намеренной компенсации. После обновления: + +- значения, выставленные по реальному северу, начинают давать правильные лучи; +- `0°` и `180°` визуально сохраняют прежнее направление; +- пользователю, который зеркально компенсировал дефект, нужно один раз вернуть + стрелку N к реальному северу. Это явно указывается в EN/RU changelog. + +## 10. Acceptance criteria и доказательства + +### AC-01 — математический контракт + +`planSunAngle()` использует сложение и нормализацию. Таблица из раздела 4, +включая wrap и некардинальный угол, проходит в `test/sun.test.mjs`. + +**Доказательство:** unit-тесты точных углов и `sunDirOnPlan()` с допуском для +float. + +### AC-02 — семантика компаса совпадает с геометрией + +Положения стрелки N сверху/справа/снизу/слева означают соответственно +0/90/180/270°, а одинаковое значение, заданное dial либо number input, даёт +одинаковое направление лучей до и после сохранения. + +**Доказательство:** действующие compass smoke-проверки плюс направленный smoke +с `north_deg=90`. + +### AC-03 — освещается физически правильное окно + +На плане с четырьмя внешними окнами для `north_deg=90`, `azimuth=90` и +положительной elevation выбирается нижнее окно холста; луч направлен от него +внутрь. Внутреннее окно не участвует. + +**Доказательство:** unit `computeSunRays()` и production-bundle +`demo/smoke_sun.mjs`. + +### AC-04 — inheritance исправлен на обоих уровнях + +Global north и per-space override используют новый знак; удаление override +снова применяет global. Отсутствие обоих по-прежнему не рисует лучи. + +**Доказательство:** unit наследования и smoke текущей full-card конфигурации. + +### AC-05 — соседнее поведение не изменено + +Кардинальные направления при `north_deg=0`, night/elevation gates, exterior +window selection, clipping, wall thickness, obstacle subtraction, ray length, +fade/rim, editor gating и независимый four-phase background проходят +существующие тесты без ослабления assertions. + +**Доказательство:** полный unit-набор и существующие sun smoke; отдельная +проверка, что изменение `north_deg` не меняет resolved day-cycle #146. + +### AC-06 — визуальная регрессия больше не маскируется + +В golden matrix есть хотя бы одна детерминированная сцена с ненулевым +`north_deg` и асимметричным сочетанием азимута/окон, где ошибочный знак выбрал +бы другую сторону плана. + +**Доказательство:** обновлённый manifest/diff golden; ручное принятие только +изменённой ожидаемой сцены. + +### AC-07 — документация и release contract + +`docs/SUN.md` содержит формулу `norm360(north_deg + azimuth)`, а EN/RU guide +объясняет буквальную ориентацию стрелки N. Оба changelog называют исправление +и предупреждают пользователей старой ручной компенсации. + +**Доказательство:** docs diff и terminal trailers `Issue: #166`, +`User-Visible: yes` в продуктовых коммитах реализации. + +### AC-08 — performance, security и поддерживаемые поверхности + +Исправление не добавляет DOM, таймеров, сетевых вызовов, config writes либо +новых пересчётов; геометрия по-прежнему memoized. View/kiosk работают на +desktop и touch; security boundary не меняется. + +**Доказательство:** typecheck/unit/build в цикле, штатный performance gate и +smoke/golden перед бетой, без новых security exceptions. + +## 11. План тестирования + +### В цикле реализации + +1. `typecheck`. +2. Полный unit-набор, включая обновлённый `test/sun.test.mjs`. +3. Production build. + +Точные команды берутся из актуального `package.json`/runbook; набор не +расширяется golden/smoke/performance на каждой итерации. + +### Перед бетой + +1. `demo/smoke_sun.mjs` и связанные sun smoke. +2. Golden matrix с новой ненулевой ориентацией и review diff. +3. Штатный performance gate, чтобы подтвердить отсутствие нового churn. +4. Остальные golden/smoke/performance проверки по release runbook. + +Полный HA harness на Windows не является каноном из-за `fcntl`; его +обязательное доказательство остаётся в Linux CI. + +## 12. Риски и защита от регрессии + +1. **Повторная инверсия не того вектора.** Защита: отдельно тестировать + `toSun`, выбранное окно и `ray.dir` внутрь комнаты. +2. **Тесты снова проходят только при north=0.** Защита: обязательный + `north_deg=90` и некардинальный wrap в unit, smoke и visual fixture. +3. **Незаметное изменение four-phase фона.** Защита: фон резолвится без + compass dependency и получает отдельный regression assertion. +4. **Сломанная настройка у пользователей обходного решения.** Защита: + никаких эвристик/миграции; явное предупреждение в обоих changelog и guide. +5. **Ослабление старых физических ограничений.** Защита: существующие тесты + interior windows, elevation, grazing angle, clipping, wall depth и + obstacles остаются обязательными. + +## 13. Rollback + +Rollback — единый revert формулы, тестов, golden fixture и документации. Данных +для отката нет: schema/storage не меняются. Частичный rollback недопустим — +нельзя оставить старую формулу с новыми тестами/документацией либо наоборот. + +## 14. Release-артефакты + +Реализация является user-visible и обязана в одном продуктовом коммите +содержать: + +- запись в `docs/CHANGELOG.md`; +- запись в `docs/CHANGELOG.ru.md`; +- исправление `docs/SUN.md`; +- синхронное уточнение `docs/USER-GUIDE.md` и `docs/USER-GUIDE.ru.md`; +- обновлённые unit и `demo/smoke_sun.mjs`; +- ненулевую compass-сцену в golden fixture/manifest и принятый diff; +- пересобранные поставляемые bundle-копии по release runbook; +- terminal trailers `Issue: #166` и `User-Visible: yes`. + +## 15. Принятые предположения + +1. `north_deg` всегда означает показанное стрелкой направление истинного + севера на холсте, а не угол, которым нужно повернуть сам план обратно к + северу. +2. Азимут Home Assistant интерпретируется текущим контрактом: 0° — север, + 90° — восток, рост по часовой стрелке. +3. Исправление не мигрирует намеренно неверные обходные значения, потому что + их невозможно надёжно отличить от корректных. +4. Отдельного продуктового решения по UX не требуется: пользователь уже дал + реальный север, меняется только неверный результат этой настройки. diff --git a/docs/specs/README.md b/docs/specs/README.md index 14e8aaaa..5b566457 100644 --- a/docs/specs/README.md +++ b/docs/specs/README.md @@ -1,6 +1,6 @@ # Спецификации задач P1 и P2 -Актуально на 2026-08-14. +Актуально на 2026-08-16. GitHub Issues и GitHub Projects (v2) остаются единственным каноническим backlog проекта. Этот каталог содержит развёрнутые ТЗ: каждое ТЗ ссылается на issue, а issue — на соответствующий файл. Статус, приоритет и факт завершения меняются только в GitHub. @@ -49,6 +49,7 @@ GitHub Issues и GitHub Projects (v2) остаются единственным | [#138](https://github.com/Matysh/houseplan-card/issues/138) Автозамыкание комнаты по существующей стене | [138-adjacent-room-autoclose.md](138-adjacent-room-autoclose.md) | | [#146](https://github.com/Matysh/houseplan-card/issues/146) Четырёхфазный фон «Следует за Солнцем» | [146-four-phase-sun-background.md](146-four-phase-sun-background.md) | | [#156](https://github.com/Matysh/houseplan-card/issues/156) Регрессии Full Performance перед v1.64.0 stable | [156-full-performance-regressions.md](156-full-performance-regressions.md) | +| [#166](https://github.com/Matysh/houseplan-card/issues/166) Солнечные лучи зеркально учитывают направление севера | [166-sun-north-rotation.md](166-sun-north-rotation.md) | ## P2 From 154c662a0bac03b4561094b088767e331e556e88 Mon Sep 17 00:00:00 2001 From: Sergey Matyunin Date: Mon, 17 Aug 2026 11:55:38 +0300 Subject: [PATCH 2/3] docs: address issue 166 spec review Issue: #166 User-Visible: no --- docs/specs/166-sun-north-rotation.md | 27 ++++++++++++++++++++++++--- 1 file changed, 24 insertions(+), 3 deletions(-) diff --git a/docs/specs/166-sun-north-rotation.md b/docs/specs/166-sun-north-rotation.md index 22766437..20b2f8e3 100644 --- a/docs/specs/166-sun-north-rotation.md +++ b/docs/specs/166-sun-north-rotation.md @@ -1,6 +1,6 @@ # Issue #166 — солнечные лучи зеркально учитывают направление севера -Статус: **ТЗ на ревью** +Статус: **ТЗ принято, готово к реализации** Дата: 2026-08-16 Тип: `bug` · приоритет: `P1` · user value: 8/10 · complexity: 4/10 · risk: 6/10 @@ -127,6 +127,11 @@ awayFromSun = -toSun нормализация угла. `sunDirOnPlan()` продолжает возвращать единичный вектор к Солнцу в координатах холста. +Архитектурный инвариант для текущих и будущих потребителей, включая +SUN-CONTRAST: направление Солнца на плане берётся только из +`planSunAngle()`. Ни render-слой, ни contrast-логика не вводят вторую формулу, +зеркальное преобразование или дополнительную поправку знака. + Знак меняется только в композиции двух систем координат. Нельзя одновременно инвертировать `toSun`, нормали окон или `awayFromSun`: это дало бы локально правильный тест, но снова перепутало бы освещённую сторону либо направление @@ -238,9 +243,10 @@ fade/rim, editor gating и независимый four-phase background прох ### AC-06 — визуальная регрессия больше не маскируется -В golden matrix есть хотя бы одна детерминированная сцена с ненулевым +В `GOLDEN_SCENARIOS` есть хотя бы одна детерминированная сцена с ненулевым `north_deg` и асимметричным сочетанием азимута/окон, где ошибочный знак выбрал -бы другую сторону плана. +бы другую сторону плана. Добавление либо семантическое изменение этой сцены +сопровождается повышением `GOLDEN_MATRIX_VERSION`. **Доказательство:** обновлённый manifest/diff golden; ручное принятие только изменённой ожидаемой сцены. @@ -284,6 +290,21 @@ smoke/golden перед бетой, без новых security exceptions. Полный HA harness на Windows не является каноном из-за `fcntl`; его обязательное доказательство остаётся в Linux CI. +### Мутационный гейт + +Перед передачей реализации на код-ревью автор вручную применяет каждый мутант, +запускает указанный узкий тест и фиксирует, что тест красный; после возврата +рабочей реализации тот же тест должен быть зелёным. + +| Мутант | Обязательное доказательство | +|---|---| +| Вернуть в `planSunAngle()` вычитание `azimuth - northDeg` | Табличный unit AC-01 падает хотя бы на контрольных сочетаниях `90° + 90°` и wrap через 360° | +| Оставить сложение углов, но инвертировать `toSun` вместо исправления требуемой семантики вектора | Таблица углов AC-01 остаётся зелёной, а направленный unit AC-03 выбора нижнего окна при `north_deg=90`, `azimuth=90` падает | +| Вернуть `north_deg: 0` в новой golden-сцене | Структурный тест golden matrix падает, потому что обязательная запись в `GOLDEN_SCENARIOS` больше не содержит ненулевой ориентации, а `GOLDEN_MATRIX_VERSION` не соответствует сценарию | + +Гейт не считается доказанным только визуальным diff: для каждого мутанта нужен +исполняемый тест с заранее определённой причиной падения. + ## 12. Риски и защита от регрессии 1. **Повторная инверсия не того вектора.** Защита: отдельно тестировать From 4ed86b38bc16fa9d1ad6a945553cfaf895a7215e Mon Sep 17 00:00:00 2001 From: Sergey Matyunin Date: Mon, 17 Aug 2026 12:01:14 +0300 Subject: [PATCH 3/3] fix: align sun rays with plan north Issue: #166 User-Visible: yes --- .../houseplan/frontend/houseplan-card.js | 4 +- demo/golden/harness.mjs | 4 +- demo/golden/matrix.mjs | 6 +-- demo/smoke_sun.mjs | 7 ++-- demo/srv/assets/houseplan-card.js | 4 +- dist/houseplan-card.js | 4 +- docs/CHANGELOG.md | 6 +++ docs/CHANGELOG.ru.md | 6 +++ docs/SUN.md | 12 +++++- docs/USER-GUIDE.md | 6 +++ docs/USER-GUIDE.ru.md | 6 ++- docs/images/06-device-display-preview.png | Bin 288221 -> 288220 bytes docs/images/06-device-editor.png | Bin 292595 -> 292594 bytes docs/images/screenshots.json | 26 ++++++------- scripts/mutation-gate.mjs | 35 ++++++++++++++++++ src/sun.ts | 2 +- test/golden-matrix.test.mjs | 10 +++++ test/sun.test.mjs | 29 +++++++++------ 18 files changed, 126 insertions(+), 41 deletions(-) diff --git a/custom_components/houseplan/frontend/houseplan-card.js b/custom_components/houseplan/frontend/houseplan-card.js index eac6a210..572e8abe 100755 --- a/custom_components/houseplan/frontend/houseplan-card.js +++ b/custom_components/houseplan/frontend/houseplan-card.js @@ -1,4 +1,4 @@ -globalThis.__HOUSEPLAN_BUILD_FINGERPRINT__="4343126d84c3579b6dc970ed755152b1206c02aacff8abe43b89535f48eee2c1";const t=globalThis,e=t.ShadowRoot&&(void 0===t.ShadyCSS||t.ShadyCSS.nativeShadow)&&"adoptedStyleSheets"in Document.prototype&&"replace"in CSSStyleSheet.prototype,i=Symbol(),s=new WeakMap;let n=class{constructor(t,e,s){if(this._$cssResult$=!0,s!==i)throw Error("CSSResult is not constructable. Use `unsafeCSS` or `css` instead.");this.cssText=t,this.t=e}get styleSheet(){let t=this.o;const i=this.t;if(e&&void 0===t){const e=void 0!==i&&1===i.length;e&&(t=s.get(i)),void 0===t&&((this.o=t=new CSSStyleSheet).replaceSync(this.cssText),e&&s.set(i,t))}return t}toString(){return this.cssText}};const o=(t,...e)=>{const s=1===t.length?t[0]:e.reduce((e,i,s)=>e+(t=>{if(!0===t._$cssResult$)return t.cssText;if("number"==typeof t)return t;throw Error("Value passed to 'css' function must be a 'css' function result: "+t+". Use 'unsafeCSS' to pass non-literal values, but take care to ensure page security.")})(i)+t[s+1],t[0]);return new n(s,t,i)},r=e?t=>t:t=>t instanceof CSSStyleSheet?(t=>{let e="";for(const i of t.cssRules)e+=i.cssText;return(t=>new n("string"==typeof t?t:t+"",void 0,i))(e)})(t):t,{is:a,defineProperty:l,getOwnPropertyDescriptor:c,getOwnPropertyNames:h,getOwnPropertySymbols:d,getPrototypeOf:u}=Object,p=globalThis,_=p.trustedTypes,m=_?_.emptyScript:"",g=p.reactiveElementPolyfillSupport,f=(t,e)=>t,v={toAttribute(t,e){switch(e){case Boolean:t=t?m:null;break;case Object:case Array:t=null==t?t:JSON.stringify(t)}return t},fromAttribute(t,e){let i=t;switch(e){case Boolean:i=null!==t;break;case Number:i=null===t?null:Number(t);break;case Object:case Array:try{i=JSON.parse(t)}catch(t){i=null}}return i}},y=(t,e)=>!a(t,e),b={attribute:!0,type:String,converter:v,reflect:!1,useDefault:!1,hasChanged:y};Symbol.metadata??=Symbol("metadata"),p.litPropertyMetadata??=new WeakMap;let w=class extends HTMLElement{static addInitializer(t){this._$Ei(),(this.l??=[]).push(t)}static get observedAttributes(){return this.finalize(),this._$Eh&&[...this._$Eh.keys()]}static createProperty(t,e=b){if(e.state&&(e.attribute=!1),this._$Ei(),this.prototype.hasOwnProperty(t)&&((e=Object.create(e)).wrapped=!0),this.elementProperties.set(t,e),!e.noAccessor){const i=Symbol(),s=this.getPropertyDescriptor(t,i,e);void 0!==s&&l(this.prototype,t,s)}}static getPropertyDescriptor(t,e,i){const{get:s,set:n}=c(this.prototype,t)??{get(){return this[e]},set(t){this[e]=t}};return{get:s,set(e){const o=s?.call(this);n?.call(this,e),this.requestUpdate(t,o,i)},configurable:!0,enumerable:!0}}static getPropertyOptions(t){return this.elementProperties.get(t)??b}static _$Ei(){if(this.hasOwnProperty(f("elementProperties")))return;const t=u(this);t.finalize(),void 0!==t.l&&(this.l=[...t.l]),this.elementProperties=new Map(t.elementProperties)}static finalize(){if(this.hasOwnProperty(f("finalized")))return;if(this.finalized=!0,this._$Ei(),this.hasOwnProperty(f("properties"))){const t=this.properties,e=[...h(t),...d(t)];for(const i of e)this.createProperty(i,t[i])}const t=this[Symbol.metadata];if(null!==t){const e=litPropertyMetadata.get(t);if(void 0!==e)for(const[t,i]of e)this.elementProperties.set(t,i)}this._$Eh=new Map;for(const[t,e]of this.elementProperties){const i=this._$Eu(t,e);void 0!==i&&this._$Eh.set(i,t)}this.elementStyles=this.finalizeStyles(this.styles)}static finalizeStyles(t){const e=[];if(Array.isArray(t)){const i=new Set(t.flat(1/0).reverse());for(const t of i)e.unshift(r(t))}else void 0!==t&&e.push(r(t));return e}static _$Eu(t,e){const i=e.attribute;return!1===i?void 0:"string"==typeof i?i:"string"==typeof t?t.toLowerCase():void 0}constructor(){super(),this._$Ep=void 0,this.isUpdatePending=!1,this.hasUpdated=!1,this._$Em=null,this._$Ev()}_$Ev(){this._$ES=new Promise(t=>this.enableUpdating=t),this._$AL=new Map,this._$E_(),this.requestUpdate(),this.constructor.l?.forEach(t=>t(this))}addController(t){(this._$EO??=new Set).add(t),void 0!==this.renderRoot&&this.isConnected&&t.hostConnected?.()}removeController(t){this._$EO?.delete(t)}_$E_(){const t=new Map,e=this.constructor.elementProperties;for(const i of e.keys())this.hasOwnProperty(i)&&(t.set(i,this[i]),delete this[i]);t.size>0&&(this._$Ep=t)}createRenderRoot(){const i=this.shadowRoot??this.attachShadow(this.constructor.shadowRootOptions);return((i,s)=>{if(e)i.adoptedStyleSheets=s.map(t=>t instanceof CSSStyleSheet?t:t.styleSheet);else for(const e of s){const s=document.createElement("style"),n=t.litNonce;void 0!==n&&s.setAttribute("nonce",n),s.textContent=e.cssText,i.appendChild(s)}})(i,this.constructor.elementStyles),i}connectedCallback(){this.renderRoot??=this.createRenderRoot(),this.enableUpdating(!0),this._$EO?.forEach(t=>t.hostConnected?.())}enableUpdating(t){}disconnectedCallback(){this._$EO?.forEach(t=>t.hostDisconnected?.())}attributeChangedCallback(t,e,i){this._$AK(t,i)}_$ET(t,e){const i=this.constructor.elementProperties.get(t),s=this.constructor._$Eu(t,i);if(void 0!==s&&!0===i.reflect){const n=(void 0!==i.converter?.toAttribute?i.converter:v).toAttribute(e,i.type);this._$Em=t,null==n?this.removeAttribute(s):this.setAttribute(s,n),this._$Em=null}}_$AK(t,e){const i=this.constructor,s=i._$Eh.get(t);if(void 0!==s&&this._$Em!==s){const t=i.getPropertyOptions(s),n="function"==typeof t.converter?{fromAttribute:t.converter}:void 0!==t.converter?.fromAttribute?t.converter:v;this._$Em=s;const o=n.fromAttribute(e,t.type);this[s]=o??this._$Ej?.get(s)??o,this._$Em=null}}requestUpdate(t,e,i,s=!1,n){if(void 0!==t){const o=this.constructor;if(!1===s&&(n=this[t]),i??=o.getPropertyOptions(t),!((i.hasChanged??y)(n,e)||i.useDefault&&i.reflect&&n===this._$Ej?.get(t)&&!this.hasAttribute(o._$Eu(t,i))))return;this.C(t,e,i)}!1===this.isUpdatePending&&(this._$ES=this._$EP())}C(t,e,{useDefault:i,reflect:s,wrapped:n},o){i&&!(this._$Ej??=new Map).has(t)&&(this._$Ej.set(t,o??e??this[t]),!0!==n||void 0!==o)||(this._$AL.has(t)||(this.hasUpdated||i||(e=void 0),this._$AL.set(t,e)),!0===s&&this._$Em!==t&&(this._$Eq??=new Set).add(t))}async _$EP(){this.isUpdatePending=!0;try{await this._$ES}catch(t){Promise.reject(t)}const t=this.scheduleUpdate();return null!=t&&await t,!this.isUpdatePending}scheduleUpdate(){return this.performUpdate()}performUpdate(){if(!this.isUpdatePending)return;if(!this.hasUpdated){if(this.renderRoot??=this.createRenderRoot(),this._$Ep){for(const[t,e]of this._$Ep)this[t]=e;this._$Ep=void 0}const t=this.constructor.elementProperties;if(t.size>0)for(const[e,i]of t){const{wrapped:t}=i,s=this[e];!0!==t||this._$AL.has(e)||void 0===s||this.C(e,void 0,i,s)}}let t=!1;const e=this._$AL;try{t=this.shouldUpdate(e),t?(this.willUpdate(e),this._$EO?.forEach(t=>t.hostUpdate?.()),this.update(e)):this._$EM()}catch(e){throw t=!1,this._$EM(),e}t&&this._$AE(e)}willUpdate(t){}_$AE(t){this._$EO?.forEach(t=>t.hostUpdated?.()),this.hasUpdated||(this.hasUpdated=!0,this.firstUpdated(t)),this.updated(t)}_$EM(){this._$AL=new Map,this.isUpdatePending=!1}get updateComplete(){return this.getUpdateComplete()}getUpdateComplete(){return this._$ES}shouldUpdate(t){return!0}update(t){this._$Eq&&=this._$Eq.forEach(t=>this._$ET(t,this[t])),this._$EM()}updated(t){}firstUpdated(t){}};w.elementStyles=[],w.shadowRootOptions={mode:"open"},w[f("elementProperties")]=new Map,w[f("finalized")]=new Map,g?.({ReactiveElement:w}),(p.reactiveElementVersions??=[]).push("2.1.2");const k=globalThis,$=t=>t,x=k.trustedTypes,S=x?x.createPolicy("lit-html",{createHTML:t=>t}):void 0,M="$lit$",C=`lit$${Math.random().toFixed(9).slice(2)}$`,D="?"+C,T=`<${D}>`,P=document,R=()=>P.createComment(""),A=t=>null===t||"object"!=typeof t&&"function"!=typeof t,I=Array.isArray,E="[ \t\n\f\r]",z=/<(?:(!--|\/[^a-zA-Z])|(\/?[a-zA-Z][^>\s]*)|(\/?$))/g,F=/-->/g,O=/>/g,N=RegExp(`>|${E}(?:([^\\s"'>=/]+)(${E}*=${E}*(?:[^ \t\n\f\r"'\`<>=]|("|')|))|$)`,"g"),L=/'/g,H=/"/g,q=/^(?:script|style|textarea|title)$/i,B=t=>(e,...i)=>({_$litType$:t,strings:e,values:i}),j=B(1),W=B(2),U=Symbol.for("lit-noChange"),G=Symbol.for("lit-nothing"),V=new WeakMap,K=P.createTreeWalker(P,129);function Y(t,e){if(!I(t)||!t.hasOwnProperty("raw"))throw Error("invalid template strings array");return void 0!==S?S.createHTML(e):e}const X=(t,e)=>{const i=t.length-1,s=[];let n,o=2===e?"":3===e?"":"",r=z;for(let e=0;e"===l[0]?(r=n??z,c=-1):void 0===l[1]?c=-2:(c=r.lastIndex-l[2].length,a=l[1],r=void 0===l[3]?N:'"'===l[3]?H:L):r===H||r===L?r=N:r===F||r===O?r=z:(r=N,n=void 0);const d=r===N&&t[e+1].startsWith("/>")?" ":"";o+=r===z?i+T:c>=0?(s.push(a),i.slice(0,c)+M+i.slice(c)+C+d):i+C+(-2===c?e:d)}return[Y(t,o+(t[i]||"")+(2===e?"":3===e?"":"")),s]};class Z{constructor({strings:t,_$litType$:e},i){let s;this.parts=[];let n=0,o=0;const r=t.length-1,a=this.parts,[l,c]=X(t,e);if(this.el=Z.createElement(l,i),K.currentNode=this.el.content,2===e||3===e){const t=this.el.content.firstChild;t.replaceWith(...t.childNodes)}for(;null!==(s=K.nextNode())&&a.length0){s.textContent=x?x.emptyScript:"";for(let i=0;iI(t)||"function"==typeof t?.[Symbol.iterator])(t)?this.k(t):this._(t)}O(t){return this._$AA.parentNode.insertBefore(t,this._$AB)}T(t){this._$AH!==t&&(this._$AR(),this._$AH=this.O(t))}_(t){this._$AH!==G&&A(this._$AH)?this._$AA.nextSibling.data=t:this.T(P.createTextNode(t)),this._$AH=t}$(t){const{values:e,_$litType$:i}=t,s="number"==typeof i?this._$AC(t):(void 0===i.el&&(i.el=Z.createElement(Y(i.h,i.h[0]),this.options)),i);if(this._$AH?._$AD===s)this._$AH.p(e);else{const t=new Q(s,this),i=t.u(this.options);t.p(e),this.T(i),this._$AH=t}}_$AC(t){let e=V.get(t.strings);return void 0===e&&V.set(t.strings,e=new Z(t)),e}k(t){I(this._$AH)||(this._$AH=[],this._$AR());const e=this._$AH;let i,s=0;for(const n of t)s===e.length?e.push(i=new tt(this.O(R()),this.O(R()),this,this.options)):i=e[s],i._$AI(n),s++;s2||""!==i[0]||""!==i[1]?(this._$AH=Array(i.length-1).fill(new String),this.strings=i):this._$AH=G}_$AI(t,e=this,i,s){const n=this.strings;let o=!1;if(void 0===n)t=J(this,t,e,0),o=!A(t)||t!==this._$AH&&t!==U,o&&(this._$AH=t);else{const s=t;let r,a;for(t=n[0],r=0;r{const s=i?.renderBefore??e;let n=s._$litPart$;if(void 0===n){const t=i?.renderBefore??null;s._$litPart$=n=new tt(e.insertBefore(R(),t),t,void 0,i??{})}return n._$AI(t),n},ct=globalThis;let ht=class extends w{constructor(){super(...arguments),this.renderOptions={host:this},this._$Do=void 0}createRenderRoot(){const t=super.createRenderRoot();return this.renderOptions.renderBefore??=t.firstChild,t}update(t){const e=this.render();this.hasUpdated||(this.renderOptions.isConnected=this.isConnected),super.update(t),this._$Do=lt(e,this.renderRoot,this.renderOptions)}connectedCallback(){super.connectedCallback(),this._$Do?.setConnected(!0)}disconnectedCallback(){super.disconnectedCallback(),this._$Do?.setConnected(!1)}render(){return U}};ht._$litElement$=!0,ht.finalized=!0,ct.litElementHydrateSupport?.({LitElement:ht});const dt=ct.litElementPolyfillSupport;dt?.({LitElement:ht}),(ct.litElementVersions??=[]).push("4.2.2");const ut=2,pt=t=>(...e)=>({_$litDirective$:t,values:e});let _t=class{constructor(t){}get _$AU(){return this._$AM._$AU}_$AT(t,e,i){this._$Ct=t,this._$AM=e,this._$Ci=i}_$AS(t,e){return this.update(t,e)}update(t,e){return this.render(...e)}};const mt={},gt=pt(class extends _t{constructor(){super(...arguments),this.ot=mt}render(t,e){return e()}update(t,[e,i]){if(Array.isArray(e)){if(Array.isArray(this.ot)&&this.ot.length===e.length&&e.every((t,e)=>t===this.ot[e]))return U}else if(this.ot===e)return U;return this.ot=Array.isArray(e)?Array.from(e):e,this.render(e,i)}}),{I:ft}=rt,vt=t=>t,yt=()=>document.createComment(""),bt=(t,e,i)=>{const s=t._$AA.parentNode,n=void 0===e?t._$AB:e._$AA;if(void 0===i){const e=s.insertBefore(yt(),n),o=s.insertBefore(yt(),n);i=new ft(e,o,t,t.options)}else{const e=i._$AB.nextSibling,o=i._$AM,r=o!==t;if(r){let e;i._$AQ?.(t),i._$AM=t,void 0!==i._$AP&&(e=t._$AU)!==o._$AU&&i._$AP(e)}if(e!==n||r){let t=i._$AA;for(;t!==e;){const e=vt(t).nextSibling;vt(s).insertBefore(t,n),t=e}}}return i},wt=(t,e,i=t)=>(t._$AI(e,i),t),kt={},$t=(t,e=kt)=>t._$AH=e,xt=t=>{t._$AR(),t._$AA.remove()},St=(t,e,i)=>{const s=new Map;for(let n=e;n<=i;n++)s.set(t[n],n);return s},Mt=pt(class extends _t{constructor(t){if(super(t),t.type!==ut)throw Error("repeat() can only be used in text expressions")}dt(t,e,i){let s;void 0===i?i=e:void 0!==e&&(s=e);const n=[],o=[];let r=0;for(const e of t)n[r]=s?s(e,r):r,o[r]=i(e,r),r++;return{values:o,keys:n}}render(t,e,i){return this.dt(t,e,i).values}update(t,[e,i,s]){const n=(t=>t._$AH)(t),{values:o,keys:r}=this.dt(e,i,s);if(!Array.isArray(n))return this.ut=r,o;const a=this.ut??=[],l=[];let c,h,d=0,u=n.length-1,p=0,_=o.length-1;for(;d<=u&&p<=_;)if(null===n[d])d++;else if(null===n[u])u--;else if(a[d]===r[p])l[p]=wt(n[d],o[p]),d++,p++;else if(a[u]===r[_])l[_]=wt(n[u],o[_]),u--,_--;else if(a[d]===r[_])l[_]=wt(n[d],o[_]),bt(t,l[_+1],n[d]),d++,_--;else if(a[u]===r[p])l[p]=wt(n[u],o[p]),bt(t,n[d],n[u]),u--,p++;else if(void 0===c&&(c=St(r,p,_),h=St(a,d,u)),c.has(a[d]))if(c.has(a[u])){const e=h.get(r[p]),i=void 0!==e?n[e]:null;if(null===i){const e=bt(t,n[d]);wt(e,o[p]),l[p]=e}else l[p]=wt(i,o[p]),bt(t,n[d],i),n[e]=null;p++}else xt(n[u]),u--;else xt(n[d]),d++;for(;p<=_;){const e=bt(t,l[_+1]);wt(e,o[p]),l[p++]=e}for(;d<=u;){const t=n[d++];null!==t&&xt(t)}return this.ut=r,$t(t,l),U}}),Ct=new WeakMap;let Dt=0;class Tt extends ht{constructor(){super(...arguments),this.title="",this.icon="",this.wide=!1,this.dismissOnScrim=!1,this.hass=null,this._opener=null,this._focusRoot=null,this._useHaDialog=!1,this._closing=!1,this._overlays=[],this._titleId="hp-dialog-title-"+ ++Dt,this._focusInitial=()=>{const t=this._focusableElements(),e=t.find(t=>t.hasAttribute("autofocus"))||t[0]||(this._useHaDialog?null:this.renderRoot.querySelector(".close"))||this.renderRoot.querySelector(".surface")||this.renderRoot.querySelector("ha-dialog");e?.focus({preventScroll:!0})},this._requestClose=()=>{this._closing||(this._closing=!0,this.dispatchEvent(new CustomEvent("hp-close",{bubbles:!0,composed:!0})))},this._onKeyDown=t=>{if("Escape"===t.key){t.preventDefault(),t.stopImmediatePropagation(),this._pruneOverlays();const e=this._overlays[this._overlays.length-1];return e?void this._closeOverlay(e,"escape"):void this._requestClose()}if("Tab"!==t.key||this._useHaDialog)return;const e=this.renderRoot.querySelector(".close"),i=e?[e,...this._focusableElements()]:this._focusableElements();if(!i.length)return t.preventDefault(),void this.renderRoot.querySelector(".surface")?.focus({preventScroll:!0});const s=this._deepActiveElement(),n=i[0],o=i[i.length-1];!t.shiftKey||s!==n&&i.includes(s)?t.shiftKey||s!==o||(t.preventDefault(),n.focus()):(t.preventDefault(),o.focus())},this._onFallbackCancel=t=>{t.preventDefault(),this._requestClose()},this._onFallbackClick=t=>{this.dismissOnScrim&&t.target===t.currentTarget&&this._requestClose()}}connectedCallback(){super.connectedCallback(),this._opener=this._deepActiveElement();const t=this.getRootNode();this._focusRoot=t;const e=Ct.get(t)||{dialogs:new Set,opener:this._opener};e.dialogs.add(this),Ct.set(t,e),this._useHaDialog=!!customElements.get("ha-dialog"),this.addEventListener("keydown",this._onKeyDown,!0)}disconnectedCallback(){this.removeEventListener("keydown",this._onKeyDown,!0);const t=[...this._overlays];this._overlays=[];for(const e of t.reverse())e.close("disconnect");const e=this._focusRoot,i=this._opener;this._opener=null,this._focusRoot=null;const s=e?Ct.get(e):void 0;s?.dialogs.delete(this),super.disconnectedCallback(),e&&s&&requestAnimationFrame(()=>{const t=Ct.get(e);if(!t)return;if(!t.dialogs.size)return t.opener?.isConnected&&t.opener.focus({preventScroll:!0}),void Ct.delete(e);const s=i?.closest("hp-dialog");i?.isConnected&&s&&t.dialogs.has(s)&&i.focus({preventScroll:!0})})}firstUpdated(t){if(super.firstUpdated(t),!this._useHaDialog){const t=this.renderRoot.querySelector("dialog");t&&!t.open&&t.showModal()}queueMicrotask(()=>this._focusInitial())}_deepActiveElement(){let t=document.activeElement;for(;t?.shadowRoot?.activeElement;)t=t.shadowRoot.activeElement;return t&&t!==document.body?t:null}_focusableElements(){const t=["[autofocus]","a[href]","button:not([disabled])",'input:not([disabled]):not([type="hidden"])',"select:not([disabled])","textarea:not([disabled])",'[contenteditable="true"]','[tabindex]:not([tabindex="-1"])'].join(","),e=[],i=new Set,s=n=>{if(!i.has(n)){if(i.add(n),n instanceof HTMLElement){if(n.matches(t)&&e.push(n),n instanceof HTMLSlotElement){for(const t of n.assignedNodes({flatten:!0}))s(t);return}if(n.shadowRoot){for(const t of n.shadowRoot.childNodes)s(t);return}}for(const t of n.childNodes)s(t)}};for(const t of this.childNodes)s(t);const n=this.overlayPortal();return n&&s(n),e.filter(t=>{let e=t;for(;e;){const t=getComputedStyle(e);if(e.hidden||e.inert||"true"===e.getAttribute("aria-hidden")||"none"===t.display||"hidden"===t.visibility)return!1;if(e=e.assignedSlot||e.parentElement||(e.getRootNode()instanceof ShadowRoot?e.getRootNode().host:null),e===this)break}return!0})}_pruneOverlays(){this._overlays=this._overlays.filter(t=>t.owner.isConnected)}_closeOverlay(t,e){const i=this._overlays.findIndex(e=>e.token===t.token);i>=0&&this._overlays.splice(i,1),t.close(e)}registerOverlay(t){this._pruneOverlays();const e=this._overlays.find(e=>e.owner===t.owner);e&&this._overlays.splice(this._overlays.indexOf(e),1);const i=t.group||"transient";for(const t of[...this._overlays].reverse())t.group===i&&this._closeOverlay(t,"exclusive");const s={...t,group:i,token:Symbol("hp-overlay")};this._overlays.push(s);let n=!1;return()=>{if(n)return;n=!0;const t=this._overlays.findIndex(t=>t.token===s.token);t>=0&&this._overlays.splice(t,1)}}closeTransientOverlays(t="outside"){this._pruneOverlays();const e=[...this._overlays].filter(t=>"transient"===(t.group||"transient"));for(const i of e.reverse())this._closeOverlay(i,t);return e.length>0}overlayPortal(){return this.renderRoot.querySelector(".overlay-portal")}render(){const t=j` +globalThis.__HOUSEPLAN_BUILD_FINGERPRINT__="ba7f9be6f4246172fbf3090bbf212631c6dd8e5bdb9a57bc7019f1644d79ef5c";const t=globalThis,e=t.ShadowRoot&&(void 0===t.ShadyCSS||t.ShadyCSS.nativeShadow)&&"adoptedStyleSheets"in Document.prototype&&"replace"in CSSStyleSheet.prototype,i=Symbol(),s=new WeakMap;let n=class{constructor(t,e,s){if(this._$cssResult$=!0,s!==i)throw Error("CSSResult is not constructable. Use `unsafeCSS` or `css` instead.");this.cssText=t,this.t=e}get styleSheet(){let t=this.o;const i=this.t;if(e&&void 0===t){const e=void 0!==i&&1===i.length;e&&(t=s.get(i)),void 0===t&&((this.o=t=new CSSStyleSheet).replaceSync(this.cssText),e&&s.set(i,t))}return t}toString(){return this.cssText}};const o=(t,...e)=>{const s=1===t.length?t[0]:e.reduce((e,i,s)=>e+(t=>{if(!0===t._$cssResult$)return t.cssText;if("number"==typeof t)return t;throw Error("Value passed to 'css' function must be a 'css' function result: "+t+". Use 'unsafeCSS' to pass non-literal values, but take care to ensure page security.")})(i)+t[s+1],t[0]);return new n(s,t,i)},r=e?t=>t:t=>t instanceof CSSStyleSheet?(t=>{let e="";for(const i of t.cssRules)e+=i.cssText;return(t=>new n("string"==typeof t?t:t+"",void 0,i))(e)})(t):t,{is:a,defineProperty:l,getOwnPropertyDescriptor:c,getOwnPropertyNames:h,getOwnPropertySymbols:d,getPrototypeOf:u}=Object,p=globalThis,_=p.trustedTypes,m=_?_.emptyScript:"",g=p.reactiveElementPolyfillSupport,f=(t,e)=>t,v={toAttribute(t,e){switch(e){case Boolean:t=t?m:null;break;case Object:case Array:t=null==t?t:JSON.stringify(t)}return t},fromAttribute(t,e){let i=t;switch(e){case Boolean:i=null!==t;break;case Number:i=null===t?null:Number(t);break;case Object:case Array:try{i=JSON.parse(t)}catch(t){i=null}}return i}},y=(t,e)=>!a(t,e),b={attribute:!0,type:String,converter:v,reflect:!1,useDefault:!1,hasChanged:y};Symbol.metadata??=Symbol("metadata"),p.litPropertyMetadata??=new WeakMap;let w=class extends HTMLElement{static addInitializer(t){this._$Ei(),(this.l??=[]).push(t)}static get observedAttributes(){return this.finalize(),this._$Eh&&[...this._$Eh.keys()]}static createProperty(t,e=b){if(e.state&&(e.attribute=!1),this._$Ei(),this.prototype.hasOwnProperty(t)&&((e=Object.create(e)).wrapped=!0),this.elementProperties.set(t,e),!e.noAccessor){const i=Symbol(),s=this.getPropertyDescriptor(t,i,e);void 0!==s&&l(this.prototype,t,s)}}static getPropertyDescriptor(t,e,i){const{get:s,set:n}=c(this.prototype,t)??{get(){return this[e]},set(t){this[e]=t}};return{get:s,set(e){const o=s?.call(this);n?.call(this,e),this.requestUpdate(t,o,i)},configurable:!0,enumerable:!0}}static getPropertyOptions(t){return this.elementProperties.get(t)??b}static _$Ei(){if(this.hasOwnProperty(f("elementProperties")))return;const t=u(this);t.finalize(),void 0!==t.l&&(this.l=[...t.l]),this.elementProperties=new Map(t.elementProperties)}static finalize(){if(this.hasOwnProperty(f("finalized")))return;if(this.finalized=!0,this._$Ei(),this.hasOwnProperty(f("properties"))){const t=this.properties,e=[...h(t),...d(t)];for(const i of e)this.createProperty(i,t[i])}const t=this[Symbol.metadata];if(null!==t){const e=litPropertyMetadata.get(t);if(void 0!==e)for(const[t,i]of e)this.elementProperties.set(t,i)}this._$Eh=new Map;for(const[t,e]of this.elementProperties){const i=this._$Eu(t,e);void 0!==i&&this._$Eh.set(i,t)}this.elementStyles=this.finalizeStyles(this.styles)}static finalizeStyles(t){const e=[];if(Array.isArray(t)){const i=new Set(t.flat(1/0).reverse());for(const t of i)e.unshift(r(t))}else void 0!==t&&e.push(r(t));return e}static _$Eu(t,e){const i=e.attribute;return!1===i?void 0:"string"==typeof i?i:"string"==typeof t?t.toLowerCase():void 0}constructor(){super(),this._$Ep=void 0,this.isUpdatePending=!1,this.hasUpdated=!1,this._$Em=null,this._$Ev()}_$Ev(){this._$ES=new Promise(t=>this.enableUpdating=t),this._$AL=new Map,this._$E_(),this.requestUpdate(),this.constructor.l?.forEach(t=>t(this))}addController(t){(this._$EO??=new Set).add(t),void 0!==this.renderRoot&&this.isConnected&&t.hostConnected?.()}removeController(t){this._$EO?.delete(t)}_$E_(){const t=new Map,e=this.constructor.elementProperties;for(const i of e.keys())this.hasOwnProperty(i)&&(t.set(i,this[i]),delete this[i]);t.size>0&&(this._$Ep=t)}createRenderRoot(){const i=this.shadowRoot??this.attachShadow(this.constructor.shadowRootOptions);return((i,s)=>{if(e)i.adoptedStyleSheets=s.map(t=>t instanceof CSSStyleSheet?t:t.styleSheet);else for(const e of s){const s=document.createElement("style"),n=t.litNonce;void 0!==n&&s.setAttribute("nonce",n),s.textContent=e.cssText,i.appendChild(s)}})(i,this.constructor.elementStyles),i}connectedCallback(){this.renderRoot??=this.createRenderRoot(),this.enableUpdating(!0),this._$EO?.forEach(t=>t.hostConnected?.())}enableUpdating(t){}disconnectedCallback(){this._$EO?.forEach(t=>t.hostDisconnected?.())}attributeChangedCallback(t,e,i){this._$AK(t,i)}_$ET(t,e){const i=this.constructor.elementProperties.get(t),s=this.constructor._$Eu(t,i);if(void 0!==s&&!0===i.reflect){const n=(void 0!==i.converter?.toAttribute?i.converter:v).toAttribute(e,i.type);this._$Em=t,null==n?this.removeAttribute(s):this.setAttribute(s,n),this._$Em=null}}_$AK(t,e){const i=this.constructor,s=i._$Eh.get(t);if(void 0!==s&&this._$Em!==s){const t=i.getPropertyOptions(s),n="function"==typeof t.converter?{fromAttribute:t.converter}:void 0!==t.converter?.fromAttribute?t.converter:v;this._$Em=s;const o=n.fromAttribute(e,t.type);this[s]=o??this._$Ej?.get(s)??o,this._$Em=null}}requestUpdate(t,e,i,s=!1,n){if(void 0!==t){const o=this.constructor;if(!1===s&&(n=this[t]),i??=o.getPropertyOptions(t),!((i.hasChanged??y)(n,e)||i.useDefault&&i.reflect&&n===this._$Ej?.get(t)&&!this.hasAttribute(o._$Eu(t,i))))return;this.C(t,e,i)}!1===this.isUpdatePending&&(this._$ES=this._$EP())}C(t,e,{useDefault:i,reflect:s,wrapped:n},o){i&&!(this._$Ej??=new Map).has(t)&&(this._$Ej.set(t,o??e??this[t]),!0!==n||void 0!==o)||(this._$AL.has(t)||(this.hasUpdated||i||(e=void 0),this._$AL.set(t,e)),!0===s&&this._$Em!==t&&(this._$Eq??=new Set).add(t))}async _$EP(){this.isUpdatePending=!0;try{await this._$ES}catch(t){Promise.reject(t)}const t=this.scheduleUpdate();return null!=t&&await t,!this.isUpdatePending}scheduleUpdate(){return this.performUpdate()}performUpdate(){if(!this.isUpdatePending)return;if(!this.hasUpdated){if(this.renderRoot??=this.createRenderRoot(),this._$Ep){for(const[t,e]of this._$Ep)this[t]=e;this._$Ep=void 0}const t=this.constructor.elementProperties;if(t.size>0)for(const[e,i]of t){const{wrapped:t}=i,s=this[e];!0!==t||this._$AL.has(e)||void 0===s||this.C(e,void 0,i,s)}}let t=!1;const e=this._$AL;try{t=this.shouldUpdate(e),t?(this.willUpdate(e),this._$EO?.forEach(t=>t.hostUpdate?.()),this.update(e)):this._$EM()}catch(e){throw t=!1,this._$EM(),e}t&&this._$AE(e)}willUpdate(t){}_$AE(t){this._$EO?.forEach(t=>t.hostUpdated?.()),this.hasUpdated||(this.hasUpdated=!0,this.firstUpdated(t)),this.updated(t)}_$EM(){this._$AL=new Map,this.isUpdatePending=!1}get updateComplete(){return this.getUpdateComplete()}getUpdateComplete(){return this._$ES}shouldUpdate(t){return!0}update(t){this._$Eq&&=this._$Eq.forEach(t=>this._$ET(t,this[t])),this._$EM()}updated(t){}firstUpdated(t){}};w.elementStyles=[],w.shadowRootOptions={mode:"open"},w[f("elementProperties")]=new Map,w[f("finalized")]=new Map,g?.({ReactiveElement:w}),(p.reactiveElementVersions??=[]).push("2.1.2");const k=globalThis,$=t=>t,x=k.trustedTypes,S=x?x.createPolicy("lit-html",{createHTML:t=>t}):void 0,M="$lit$",C=`lit$${Math.random().toFixed(9).slice(2)}$`,D="?"+C,T=`<${D}>`,P=document,R=()=>P.createComment(""),A=t=>null===t||"object"!=typeof t&&"function"!=typeof t,I=Array.isArray,E="[ \t\n\f\r]",z=/<(?:(!--|\/[^a-zA-Z])|(\/?[a-zA-Z][^>\s]*)|(\/?$))/g,F=/-->/g,O=/>/g,N=RegExp(`>|${E}(?:([^\\s"'>=/]+)(${E}*=${E}*(?:[^ \t\n\f\r"'\`<>=]|("|')|))|$)`,"g"),L=/'/g,H=/"/g,q=/^(?:script|style|textarea|title)$/i,B=t=>(e,...i)=>({_$litType$:t,strings:e,values:i}),j=B(1),W=B(2),U=Symbol.for("lit-noChange"),G=Symbol.for("lit-nothing"),V=new WeakMap,K=P.createTreeWalker(P,129);function Y(t,e){if(!I(t)||!t.hasOwnProperty("raw"))throw Error("invalid template strings array");return void 0!==S?S.createHTML(e):e}const X=(t,e)=>{const i=t.length-1,s=[];let n,o=2===e?"":3===e?"":"",r=z;for(let e=0;e"===l[0]?(r=n??z,c=-1):void 0===l[1]?c=-2:(c=r.lastIndex-l[2].length,a=l[1],r=void 0===l[3]?N:'"'===l[3]?H:L):r===H||r===L?r=N:r===F||r===O?r=z:(r=N,n=void 0);const d=r===N&&t[e+1].startsWith("/>")?" ":"";o+=r===z?i+T:c>=0?(s.push(a),i.slice(0,c)+M+i.slice(c)+C+d):i+C+(-2===c?e:d)}return[Y(t,o+(t[i]||"")+(2===e?"":3===e?"":"")),s]};class Z{constructor({strings:t,_$litType$:e},i){let s;this.parts=[];let n=0,o=0;const r=t.length-1,a=this.parts,[l,c]=X(t,e);if(this.el=Z.createElement(l,i),K.currentNode=this.el.content,2===e||3===e){const t=this.el.content.firstChild;t.replaceWith(...t.childNodes)}for(;null!==(s=K.nextNode())&&a.length0){s.textContent=x?x.emptyScript:"";for(let i=0;iI(t)||"function"==typeof t?.[Symbol.iterator])(t)?this.k(t):this._(t)}O(t){return this._$AA.parentNode.insertBefore(t,this._$AB)}T(t){this._$AH!==t&&(this._$AR(),this._$AH=this.O(t))}_(t){this._$AH!==G&&A(this._$AH)?this._$AA.nextSibling.data=t:this.T(P.createTextNode(t)),this._$AH=t}$(t){const{values:e,_$litType$:i}=t,s="number"==typeof i?this._$AC(t):(void 0===i.el&&(i.el=Z.createElement(Y(i.h,i.h[0]),this.options)),i);if(this._$AH?._$AD===s)this._$AH.p(e);else{const t=new Q(s,this),i=t.u(this.options);t.p(e),this.T(i),this._$AH=t}}_$AC(t){let e=V.get(t.strings);return void 0===e&&V.set(t.strings,e=new Z(t)),e}k(t){I(this._$AH)||(this._$AH=[],this._$AR());const e=this._$AH;let i,s=0;for(const n of t)s===e.length?e.push(i=new tt(this.O(R()),this.O(R()),this,this.options)):i=e[s],i._$AI(n),s++;s2||""!==i[0]||""!==i[1]?(this._$AH=Array(i.length-1).fill(new String),this.strings=i):this._$AH=G}_$AI(t,e=this,i,s){const n=this.strings;let o=!1;if(void 0===n)t=J(this,t,e,0),o=!A(t)||t!==this._$AH&&t!==U,o&&(this._$AH=t);else{const s=t;let r,a;for(t=n[0],r=0;r{const s=i?.renderBefore??e;let n=s._$litPart$;if(void 0===n){const t=i?.renderBefore??null;s._$litPart$=n=new tt(e.insertBefore(R(),t),t,void 0,i??{})}return n._$AI(t),n},ct=globalThis;let ht=class extends w{constructor(){super(...arguments),this.renderOptions={host:this},this._$Do=void 0}createRenderRoot(){const t=super.createRenderRoot();return this.renderOptions.renderBefore??=t.firstChild,t}update(t){const e=this.render();this.hasUpdated||(this.renderOptions.isConnected=this.isConnected),super.update(t),this._$Do=lt(e,this.renderRoot,this.renderOptions)}connectedCallback(){super.connectedCallback(),this._$Do?.setConnected(!0)}disconnectedCallback(){super.disconnectedCallback(),this._$Do?.setConnected(!1)}render(){return U}};ht._$litElement$=!0,ht.finalized=!0,ct.litElementHydrateSupport?.({LitElement:ht});const dt=ct.litElementPolyfillSupport;dt?.({LitElement:ht}),(ct.litElementVersions??=[]).push("4.2.2");const ut=2,pt=t=>(...e)=>({_$litDirective$:t,values:e});let _t=class{constructor(t){}get _$AU(){return this._$AM._$AU}_$AT(t,e,i){this._$Ct=t,this._$AM=e,this._$Ci=i}_$AS(t,e){return this.update(t,e)}update(t,e){return this.render(...e)}};const mt={},gt=pt(class extends _t{constructor(){super(...arguments),this.ot=mt}render(t,e){return e()}update(t,[e,i]){if(Array.isArray(e)){if(Array.isArray(this.ot)&&this.ot.length===e.length&&e.every((t,e)=>t===this.ot[e]))return U}else if(this.ot===e)return U;return this.ot=Array.isArray(e)?Array.from(e):e,this.render(e,i)}}),{I:ft}=rt,vt=t=>t,yt=()=>document.createComment(""),bt=(t,e,i)=>{const s=t._$AA.parentNode,n=void 0===e?t._$AB:e._$AA;if(void 0===i){const e=s.insertBefore(yt(),n),o=s.insertBefore(yt(),n);i=new ft(e,o,t,t.options)}else{const e=i._$AB.nextSibling,o=i._$AM,r=o!==t;if(r){let e;i._$AQ?.(t),i._$AM=t,void 0!==i._$AP&&(e=t._$AU)!==o._$AU&&i._$AP(e)}if(e!==n||r){let t=i._$AA;for(;t!==e;){const e=vt(t).nextSibling;vt(s).insertBefore(t,n),t=e}}}return i},wt=(t,e,i=t)=>(t._$AI(e,i),t),kt={},$t=(t,e=kt)=>t._$AH=e,xt=t=>{t._$AR(),t._$AA.remove()},St=(t,e,i)=>{const s=new Map;for(let n=e;n<=i;n++)s.set(t[n],n);return s},Mt=pt(class extends _t{constructor(t){if(super(t),t.type!==ut)throw Error("repeat() can only be used in text expressions")}dt(t,e,i){let s;void 0===i?i=e:void 0!==e&&(s=e);const n=[],o=[];let r=0;for(const e of t)n[r]=s?s(e,r):r,o[r]=i(e,r),r++;return{values:o,keys:n}}render(t,e,i){return this.dt(t,e,i).values}update(t,[e,i,s]){const n=(t=>t._$AH)(t),{values:o,keys:r}=this.dt(e,i,s);if(!Array.isArray(n))return this.ut=r,o;const a=this.ut??=[],l=[];let c,h,d=0,u=n.length-1,p=0,_=o.length-1;for(;d<=u&&p<=_;)if(null===n[d])d++;else if(null===n[u])u--;else if(a[d]===r[p])l[p]=wt(n[d],o[p]),d++,p++;else if(a[u]===r[_])l[_]=wt(n[u],o[_]),u--,_--;else if(a[d]===r[_])l[_]=wt(n[d],o[_]),bt(t,l[_+1],n[d]),d++,_--;else if(a[u]===r[p])l[p]=wt(n[u],o[p]),bt(t,n[d],n[u]),u--,p++;else if(void 0===c&&(c=St(r,p,_),h=St(a,d,u)),c.has(a[d]))if(c.has(a[u])){const e=h.get(r[p]),i=void 0!==e?n[e]:null;if(null===i){const e=bt(t,n[d]);wt(e,o[p]),l[p]=e}else l[p]=wt(i,o[p]),bt(t,n[d],i),n[e]=null;p++}else xt(n[u]),u--;else xt(n[d]),d++;for(;p<=_;){const e=bt(t,l[_+1]);wt(e,o[p]),l[p++]=e}for(;d<=u;){const t=n[d++];null!==t&&xt(t)}return this.ut=r,$t(t,l),U}}),Ct=new WeakMap;let Dt=0;class Tt extends ht{constructor(){super(...arguments),this.title="",this.icon="",this.wide=!1,this.dismissOnScrim=!1,this.hass=null,this._opener=null,this._focusRoot=null,this._useHaDialog=!1,this._closing=!1,this._overlays=[],this._titleId="hp-dialog-title-"+ ++Dt,this._focusInitial=()=>{const t=this._focusableElements(),e=t.find(t=>t.hasAttribute("autofocus"))||t[0]||(this._useHaDialog?null:this.renderRoot.querySelector(".close"))||this.renderRoot.querySelector(".surface")||this.renderRoot.querySelector("ha-dialog");e?.focus({preventScroll:!0})},this._requestClose=()=>{this._closing||(this._closing=!0,this.dispatchEvent(new CustomEvent("hp-close",{bubbles:!0,composed:!0})))},this._onKeyDown=t=>{if("Escape"===t.key){t.preventDefault(),t.stopImmediatePropagation(),this._pruneOverlays();const e=this._overlays[this._overlays.length-1];return e?void this._closeOverlay(e,"escape"):void this._requestClose()}if("Tab"!==t.key||this._useHaDialog)return;const e=this.renderRoot.querySelector(".close"),i=e?[e,...this._focusableElements()]:this._focusableElements();if(!i.length)return t.preventDefault(),void this.renderRoot.querySelector(".surface")?.focus({preventScroll:!0});const s=this._deepActiveElement(),n=i[0],o=i[i.length-1];!t.shiftKey||s!==n&&i.includes(s)?t.shiftKey||s!==o||(t.preventDefault(),n.focus()):(t.preventDefault(),o.focus())},this._onFallbackCancel=t=>{t.preventDefault(),this._requestClose()},this._onFallbackClick=t=>{this.dismissOnScrim&&t.target===t.currentTarget&&this._requestClose()}}connectedCallback(){super.connectedCallback(),this._opener=this._deepActiveElement();const t=this.getRootNode();this._focusRoot=t;const e=Ct.get(t)||{dialogs:new Set,opener:this._opener};e.dialogs.add(this),Ct.set(t,e),this._useHaDialog=!!customElements.get("ha-dialog"),this.addEventListener("keydown",this._onKeyDown,!0)}disconnectedCallback(){this.removeEventListener("keydown",this._onKeyDown,!0);const t=[...this._overlays];this._overlays=[];for(const e of t.reverse())e.close("disconnect");const e=this._focusRoot,i=this._opener;this._opener=null,this._focusRoot=null;const s=e?Ct.get(e):void 0;s?.dialogs.delete(this),super.disconnectedCallback(),e&&s&&requestAnimationFrame(()=>{const t=Ct.get(e);if(!t)return;if(!t.dialogs.size)return t.opener?.isConnected&&t.opener.focus({preventScroll:!0}),void Ct.delete(e);const s=i?.closest("hp-dialog");i?.isConnected&&s&&t.dialogs.has(s)&&i.focus({preventScroll:!0})})}firstUpdated(t){if(super.firstUpdated(t),!this._useHaDialog){const t=this.renderRoot.querySelector("dialog");t&&!t.open&&t.showModal()}queueMicrotask(()=>this._focusInitial())}_deepActiveElement(){let t=document.activeElement;for(;t?.shadowRoot?.activeElement;)t=t.shadowRoot.activeElement;return t&&t!==document.body?t:null}_focusableElements(){const t=["[autofocus]","a[href]","button:not([disabled])",'input:not([disabled]):not([type="hidden"])',"select:not([disabled])","textarea:not([disabled])",'[contenteditable="true"]','[tabindex]:not([tabindex="-1"])'].join(","),e=[],i=new Set,s=n=>{if(!i.has(n)){if(i.add(n),n instanceof HTMLElement){if(n.matches(t)&&e.push(n),n instanceof HTMLSlotElement){for(const t of n.assignedNodes({flatten:!0}))s(t);return}if(n.shadowRoot){for(const t of n.shadowRoot.childNodes)s(t);return}}for(const t of n.childNodes)s(t)}};for(const t of this.childNodes)s(t);const n=this.overlayPortal();return n&&s(n),e.filter(t=>{let e=t;for(;e;){const t=getComputedStyle(e);if(e.hidden||e.inert||"true"===e.getAttribute("aria-hidden")||"none"===t.display||"hidden"===t.visibility)return!1;if(e=e.assignedSlot||e.parentElement||(e.getRootNode()instanceof ShadowRoot?e.getRootNode().host:null),e===this)break}return!0})}_pruneOverlays(){this._overlays=this._overlays.filter(t=>t.owner.isConnected)}_closeOverlay(t,e){const i=this._overlays.findIndex(e=>e.token===t.token);i>=0&&this._overlays.splice(i,1),t.close(e)}registerOverlay(t){this._pruneOverlays();const e=this._overlays.find(e=>e.owner===t.owner);e&&this._overlays.splice(this._overlays.indexOf(e),1);const i=t.group||"transient";for(const t of[...this._overlays].reverse())t.group===i&&this._closeOverlay(t,"exclusive");const s={...t,group:i,token:Symbol("hp-overlay")};this._overlays.push(s);let n=!1;return()=>{if(n)return;n=!0;const t=this._overlays.findIndex(t=>t.token===s.token);t>=0&&this._overlays.splice(t,1)}}closeTransientOverlays(t="outside"){this._pruneOverlays();const e=[...this._overlays].filter(t=>"transient"===(t.group||"transient"));for(const i of e.reverse())this._closeOverlay(i,t);return e.length>0}overlayPortal(){return this.renderRoot.querySelector(".overlay-portal")}render(){const t=j` ${this.icon?j``:G} ${this.title} `;return this._useHaDialog?j`[t[0]-e[0],t[1]-e[1]],jo=(t,e)=>[t[0]+e[0],t[1]+e[1]],Wo=(t,e)=>t[0]*e[0]+t[1]*e[1],Uo=t=>Math.hypot(t[0],t[1]);function Go(t){let e=0;for(let i=0;it[1]!=l>t[1]&&t[0]<(a-o)*(t[1]-r)/(l-r)+o&&(i=!i)}return i}([a[0]+r[0]*l,a[1]+r[1]*l],t)&&(r=[-r[0],-r[1]]),r}function Yo(t,e,i,s){const n=s||Ko(t,e),o=(e+1)%t.length;return t.map((t,s)=>s===e||s===o?[t[0]+n[0]*i,t[1]+n[1]*i]:[...t])}function Xo(t,e,i,s){const n=[],o=Bo(i,e),r=Uo(o);if(rs||h>s)continue;const d=Wo(Bo(o,e),a),u=Wo(Bo(l,e),a),p=Math.max(0,Math.min(d,u)),_=Math.min(r,Math.max(d,u));_-p>s&&n.push([[e[0]+a[0]*p,e[1]+a[1]*p],[e[0]+a[0]*_,e[1]+a[1]*_]])}return n}function Zo(t,e,i,s,n){const o=Xo(t,e,i,n);if(!o.length)return null;const r=t=>o.some(([e,i])=>Vo(t,e,i)<=n),a=t.length,l=[];for(let o=0;on||f>n)continue;const v=Wo(Bo(e,c),p),y=Wo(Bo(i,c),p),b=Math.max(0,Math.min(v,y)),w=Math.min(u,Math.max(v,y));if(!(w-b<=n)){if(b>n&&bn&&wUo(Bo(i,t[(s+1)%t.length]))>e);for(let t=0;t<2;t++)i=i.filter((t,s)=>{const n=i[(s-1+i.length)%i.length],o=i[(s+1)%i.length],r=(t[0]-n[0])*(o[1]-n[1])-(t[1]-n[1])*(o[0]-n[0]),a=Uo(Bo(o,n))||1;return Math.abs(r)/a>e});return i.length>=3?i:t}function Qo(t){const e=t.length;if(e<3)return!1;for(let i=0;i(t[0]-n[0])*a[1]-(t[1]-n[1])*a[0],c=t=>(t[0]-n[0])*a[0]+(t[1]-n[1])*a[1],h=i,d=r-i;for(const e of t){const t=Math.abs(l(e));if(t<=i)continue;const n=c(e);n<=h||n>=d||tMath.abs(r+(t-u)/g*(a-r));s=Math.min(s,f(_),f(m))}}return s}function er(t,e,i){if(bi(t,e,i))return!0;if(yi(t,e,i)||yi(e,t,i))return!1;let s=0;try{const i=Ze([[...t.map(t=>[t[0],t[1]]),[t[0][0],t[0][1]]]],[[...e.map(t=>[t[0],t[1]]),[e[0][0],e[0][1]]]]);for(const t of i)t?.[0]&&(s+=ki(t[0]))}catch{return!1}return s>Math.max(1e-7,i*i)}function ir(t,e,i,s,n){const o=[i.n[0]*s,i.n[1]*s],r={polys:{},openings:{},movedSpans:{}};if(Math.abs(s)<1e-9)return r;for(const e of t){if(e.id===i.roomId){r.polys[e.id]=Yo(e.poly,i.edge,s,i.n),r.movedSpans[e.id]=[[jo(i.a,o),jo(i.b,o)]];continue}const t=Xo(e.poly,i.a,i.b,n);if(!t.length)continue;const a=Zo(e.poly,i.a,i.b,o,n);a&&(r.polys[e.id]=a,r.movedSpans[e.id]=t.map(([t,e])=>[jo(t,o),jo(e,o)]))}for(const t of e)Vo([t.x,t.y],i.a,i.b)<=n&&(r.openings[t.id]=[t.x+o[0],t.y+o[1]]);return r}function sr(t,e,i){for(const s of e)for(let e=0;ei)continue;const c=(t.x-n[0])*l[0]+(t.y-n[1])*l[1];if(c-t.length/2>=-i&&c+t.length/2<=a+i)return!0}return!1}function nr(t,e,i){return t.filter(t=>e.some(e=>{for(let s=0;sa.polys[t.id]||t.poly;for(const e of l){const s=t.find(t=>t.id===e),n=a.polys[e];if(!Qo(n))return!1;const l=Go(s.poly),h=Go(n);if(Math.abs(h)t.find(t=>t.id===e).poly),d=t.map(c);for(const t of nr(e,h,2*r)){const e=a.openings[t.id];if(!sr(e?{...t,x:e[0],y:e[1]}:t,d,2*r))return!1}return!0}function rr(t,e,i,s,n,o){const r=t=>[s[0]+(t[0]-s[0])*n,s[1]+(t[1]-s[1])*n],a={poly:t.poly.map(r),openings:{}};for(const s of e){let e=!1;for(let i=0;i{for(let e=0;et.id===i);if(!l)return!1;const c=t.filter(t=>t.id!==i).map(t=>t.poly),h=rr(l,e,c,s,n,2*a),d=h.poly,u=function(t){const e=function(t){const e=[...t].sort((t,e)=>t[0]-e[0]||t[1]-e[1]);if(e.length<3)return e;const i=(t,e,i)=>(e[0]-t[0])*(i[1]-t[1])-(e[1]-t[1])*(i[0]-t[0]),s=[];for(const t of e){for(;s.length>=2&&i(s[s.length-2],s[s.length-1],t)<=0;)s.pop();s.push(t)}const n=[];for(let t=e.length-1;t>=0;t--){const s=e[t];for(;n.length>=2&&i(n[n.length-2],n[n.length-1],s)<=0;)n.pop();n.push(s)}return s.pop(),n.pop(),s.concat(n)}(t);if(e.length<3)return 0;let i=1/0;for(let t=0;tt.id===i?d:t.poly);for(const t of nr(e,[l.poly],2*a)){const e=h.openings[t.id];if(!sr(e?{...t,x:e[0],y:e[1]}:t,p,2*a))return!1}return!0}function lr(t,e,i){const s=i/e;return ki(t)*s*s/1e4}function cr(t,e){return e?`${Math.round(10.7639*t)} ft²`:`${(Math.round(10*t)/10).toFixed(1)} m²`}function hr(t){const e=t%360;return e<0?e+360:e}function dr(t,e){const i=function(t,e){return hr(t-e)}(t,e)*Math.PI/180;return[Math.sin(i),-Math.cos(i)]}const ur=Object.freeze({dawn:Object.freeze({top:"#aabdd1",bottom:"#e8c8b7",horizon:"rgba(255,201,156,.56)",sun:"rgba(255,188,125,.78)",vignette:"rgba(65,72,99,.21)",outlineNear:"rgba(74,57,61,.25)",outlineMid:"rgba(255,238,224,.40)",outlineFar:"rgba(255,224,202,.18)"}),day:Object.freeze({top:"#dce9ef",bottom:"#cbdce3",horizon:"rgba(255,245,220,.45)",sun:"rgba(255,239,190,.72)",vignette:"rgba(65,91,105,.16)",outlineNear:"rgba(45,62,71,.28)",outlineMid:"rgba(255,255,255,.42)",outlineFar:"rgba(255,255,255,.20)"}),dusk:Object.freeze({top:"#48536c",bottom:"#9a7380",horizon:"rgba(242,156,114,.34)",sun:"rgba(255,167,113,.55)",vignette:"rgba(20,26,44,.39)",outlineNear:"rgba(238,219,225,.40)",outlineMid:"rgba(229,207,218,.26)",outlineFar:"rgba(215,190,205,.12)"}),night:Object.freeze({top:"#111a27",bottom:"#1f2f3e",horizon:"rgba(79,120,151,.16)",sun:"rgba(169,208,231,0)",vignette:"rgba(3,8,14,.58)",outlineNear:"rgba(218,238,249,.56)",outlineMid:"rgba(174,215,238,.30)",outlineFar:"rgba(136,194,226,.14)"})}),pr=t=>"number"==typeof t&&Number.isFinite(t),_r=(t,e,i)=>Math.min(i,Math.max(e,t));function mr(t){return t.elevation<=-6?"night":t.elevation>=6?"day":t.rising?"dawn":"dusk"}function gr(t){const e=(Math.floor(Number(t)||0)%1440+1440)%1440;return e>=300&&e<480?"dawn":e>=480&&e<1080?"day":e>=1080&&e<1260?"dusk":"night"}function fr(t){const e=mr(t),i=t.azimuth*Math.PI/180;return{sunX:50-42*Math.sin(i),sunY:78-_r(t.elevation,0,90)/90*64,sunOpacity:"night"===e?0:_r((t.elevation+6)/12,0,1)}}function vr(t){const e=(Math.floor(Number(t)||0)%1440+1440)%1440;if(e<300||e>=1260)return{sunX:50,sunY:78,sunOpacity:0};const i=(e-300)/960,s=(e-300)/120,n=(1260-e)/120;return{sunX:8+84*i,sunY:78-64*Math.sin(i*Math.PI),sunOpacity:Math.max(.18,Math.min(s,n,1))}}function yr(t,e=new Date){const i=function(t){const e=t?.states?.["sun.sun"]?.attributes;return e&&pr(e.azimuth)&&pr(e.elevation)&&"boolean"==typeof e.rising?{azimuth:hr(e.azimuth),elevation:e.elevation,rising:e.rising}:null}(t);if(i)return{phase:mr(i),source:"sun",...fr(i)};const s="number"==typeof e?e:function(t=new Date){return 60*t.getHours()+t.getMinutes()}(e);return{phase:gr(s),source:"clock",...vr(s)}}function br(t){return`${t.source}|${t.phase}|${t.sunX.toFixed(2)}|${t.sunY.toFixed(2)}|`+t.sunOpacity.toFixed(3)}const wr=[[-90,"#070c14"],[-12,"#070c14"],[-4,"#131a28"],[0,"#4a3527"],[10,"#e8ddcf"],[30,"#ffffff"],[90,"#ffffff"]],kr=t=>Math.min(1,Math.max(0,t));function $r(t,e,i=6){const s=t.angle*Math.PI/180,n=[Math.sin(s),-Math.cos(s)],o=s=>{const o=[t.x+n[0]*i*s,t.y+n[1]*i*s];return e.find(t=>t.poly.length>=3&&hi(o,t.poly))||null},r=o(1),a=o(-1);return r&&a?null:r||a?r?{normal:[-n[0],-n[1]],roomId:r.id}:{normal:n,roomId:a.id}:null}function xr(t,e,i){return i>0&&t[0]*e[0]+t[1]*e[1]>.05}function Sr(t,e,i,s){return[[t[0],t[1]],[e[0],e[1]],[e[0]+i[0]*s,e[1]+i[1]*s],[t[0]+i[0]*s,t[1]+i[1]*s]]}function Mr(t,e){try{const i=Ze([[...t.map(t=>[t[0],t[1]]),[t[0][0],t[0][1]]]],[[...e.map(t=>[t[0],t[1]]),[e[0][0],e[0][1]]]]),s=[];for(const t of i){const e=t?.[0];!Array.isArray(e)||e.length<4||s.push(e.slice(0,e.length-1).map(t=>[t[0],t[1]]))}return s}catch{return[]}}function Cr(t,e,i,s,n,o,r){if(!(s>0))return[];const a=dr(i,n),l=[-a[0],-a[1]],c=function(t){const e=Math.min(90,Math.max(0,t));return.7*(.8+1.7*Math.pow(1-e/90,1.6))}(s),h=[];for(const i of e){if(!(i.length>0))continue;const e=$r(i,t);if(!e||!xr(e.normal,a,s))continue;const n=t.find(t=>t.id===e.roomId);if(!n)continue;const d=o&&o[e.roomId]||n.poly,u=i.angle*Math.PI/180,p=i.length/2,_=[-e.normal[0],-e.normal[1]],m=Math.max(0,r?.[i.id]||0),g=i.x+_[0]*m/2,f=i.y+_[1]*m/2,v=Math.cos(u)*p,y=Math.sin(u)*p,b=[g-v,f-y],w=[g+v,f+y],k=c*i.length,$=Mr(Sr(b,w,l,k),d);if(!$.length)continue;const x=l[0]*_[0]+l[1]*_[1];h.push({openingId:i.id,roomId:e.roomId,polys:$,a:b,b:w,dir:l,len:k,normal:_,depth:k*x})}return h}const Dr=t=>"number"==typeof t&&Number.isInteger(t)&&t>=0&&t<=359?t:null;function Tr(t,e){const i=Dr(e?.north_deg);return null!==i?i:Dr(t?.north_deg)}function Pr(t,e){const i=t=>"static"===t||"daynight"===t?t:null;return i(e?.bg_mode)??i(t?.bg_mode)??"static"}function Rr(t,e){const i=e?.sun_rays;return"boolean"==typeof i?i:!0===t?.sun_rays}function Ar(t){const e=t?.states?.["sun.sun"]?.attributes,i=Number(e?.azimuth),s=Number(e?.elevation);return Number.isFinite(i)&&Number.isFinite(s)?{azimuth:i,elevation:s}:null}const Ir=["dawn","day","dusk","night"];function Er(t){if(!t)return"";const e=ur[t.phase];return[`--hp-day-cycle-outline-near:${e.outlineNear}`,`--hp-day-cycle-outline-mid:${e.outlineMid}`,`--hp-day-cycle-outline-far:${e.outlineFar}`].join(";")}function zr(t,e=1){if(!t)return G;const i=[`--hp-day-cycle-sun-x:${t.sunX.toFixed(2)}%`,`--hp-day-cycle-sun-y:${t.sunY.toFixed(2)}%`,`--hp-day-cycle-sun-opacity:${t.sunOpacity.toFixed(3)}`,`opacity:${Math.min(1,Math.max(0,e)).toFixed(4)}`].join(";");return j`